diff --git a/README.md b/README.md index 1f1e3eb..d576747 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,18 @@ Relative and absolute file paths are both allowed. Relative paths are rooted aga The [@actions/artifact](https://github.com/actions/toolkit/tree/main/packages/artifact) package is used internally to handle most of the logic around uploading an artifact. There is extra documentation around upload limitations and behavior in the toolkit repo that is worth checking out. +### Customization if no files are found + +If a path (or paths), result in no files being found for the artifact, the action will succeed but print out a warning. In certain scenarios it may be desirable to fail the action or suppress the warning. The `if-no-files-found` option allows you to customize the behavior of the action if no files are found. + +```yaml +- uses: actions/upload-artifact@v2 + with: + name: my-artifact + path: path/to/artifact/ + if-no-files-found: error # 'warn' or 'ignore' are also available, defaults to `warn` +``` + ### Conditional Artifact Upload To upload artifacts only when the previous step of a job failed, use [`if: failure()`](https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions#job-status-check-functions): diff --git a/action.yml b/action.yml index 129a2a0..dbdbc92 100644 --- a/action.yml +++ b/action.yml @@ -4,10 +4,19 @@ author: 'GitHub' inputs: name: description: 'Artifact name' - required: false + default: 'artifact' path: description: 'A file, directory or wildcard pattern that describes what to upload' required: true + if-no-files-found: + description: > + The desired behavior if no files are found using the provided path. + + 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: 'warn' runs: using: 'node12' main: 'dist/index.js' \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 4208115..e9321ce 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3987,25 +3987,39 @@ var __importStar = (this && this.__importStar) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(470)); const artifact_1 = __webpack_require__(214); -const constants_1 = __webpack_require__(694); const search_1 = __webpack_require__(575); +const input_helper_1 = __webpack_require__(583); +const constants_1 = __webpack_require__(694); function run() { return __awaiter(this, void 0, void 0, function* () { try { - const name = core.getInput(constants_1.Inputs.Name, { required: false }); - const path = core.getInput(constants_1.Inputs.Path, { required: true }); - const searchResult = yield search_1.findFilesToUpload(path); + const inputs = input_helper_1.getInputs(); + const searchResult = yield search_1.findFilesToUpload(inputs.searchPath); if (searchResult.filesToUpload.length === 0) { - core.warning(`No files were found for the provided path: ${path}. No artifacts will be uploaded.`); + // 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 files were found with the provided path: ${inputs.searchPath}. No artifacts will be uploaded.`); + break; + } + case constants_1.NoFileOptions.error: { + core.setFailed(`No files were found with the provided path: ${inputs.searchPath}. No artifacts will be uploaded.`); + break; + } + case constants_1.NoFileOptions.ignore: { + core.info(`No files were found with the provided path: ${inputs.searchPath}. No artifacts will be uploaded.`); + break; + } + } } else { - core.info(`With the provided path, there will be ${searchResult.filesToUpload.length} files uploaded`); + core.info(`With the provided path, there will be ${searchResult.filesToUpload.length} file(s) uploaded`); core.debug(`Root artifact directory is ${searchResult.rootDirectory}`); const artifactClient = artifact_1.create(); const options = { continueOnError: false }; - const uploadResponse = yield artifactClient.uploadArtifact(name || constants_1.getDefaultArtifactName(), searchResult.filesToUpload, searchResult.rootDirectory, options); + const uploadResponse = yield artifactClient.uploadArtifact(inputs.artifactName, searchResult.filesToUpload, searchResult.rootDirectory, options); if (uploadResponse.failedItems.length > 0) { core.setFailed(`An error was encountered when uploading ${uploadResponse.artifactName}. There were ${uploadResponse.failedItems.length} items that failed to upload.`); } @@ -6335,6 +6349,43 @@ function findFilesToUpload(searchPath, globOptions) { exports.findFilesToUpload = findFilesToUpload; +/***/ }), + +/***/ 583: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = __importStar(__webpack_require__(470)); +const constants_1 = __webpack_require__(694); +/** + * Helper to get all the inputs for the action + */ +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); + 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)}`); + } + return { + artifactName: name, + searchPath: path, + ifNoFilesFound: noFileBehavior + }; +} +exports.getInputs = getInputs; + + /***/ }), /***/ 590: @@ -7249,11 +7300,23 @@ var Inputs; (function (Inputs) { Inputs["Name"] = "name"; Inputs["Path"] = "path"; + Inputs["IfNoFilesFound"] = "if-no-files-found"; })(Inputs = exports.Inputs || (exports.Inputs = {})); -function getDefaultArtifactName() { - return 'artifact'; -} -exports.getDefaultArtifactName = getDefaultArtifactName; +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 = {})); /***/ }), diff --git a/src/constants.ts b/src/constants.ts index d53402c..f01f984 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,8 +1,22 @@ export enum Inputs { Name = 'name', - Path = 'path' + Path = 'path', + IfNoFilesFound = 'if-no-files-found' } -export function getDefaultArtifactName(): string { - return 'artifact' +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' } diff --git a/src/input-helper.ts b/src/input-helper.ts new file mode 100644 index 0000000..c58d52a --- /dev/null +++ b/src/input-helper.ts @@ -0,0 +1,30 @@ +import * as core from '@actions/core' +import {Inputs, NoFileOptions} from './constants' +import {UploadInputs} from './upload-inputs' + +/** + * Helper to get all the inputs for the action + */ +export function getInputs(): UploadInputs { + const name = core.getInput(Inputs.Name) + const path = core.getInput(Inputs.Path, {required: true}) + + 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 + )}` + ) + } + + return { + artifactName: name, + searchPath: path, + ifNoFilesFound: noFileBehavior + } +} diff --git a/src/upload-artifact.ts b/src/upload-artifact.ts index af42a9f..e37c6b4 100644 --- a/src/upload-artifact.ts +++ b/src/upload-artifact.ts @@ -1,21 +1,38 @@ import * as core from '@actions/core' import {create, UploadOptions} from '@actions/artifact' -import {Inputs, getDefaultArtifactName} from './constants' import {findFilesToUpload} from './search' +import {getInputs} from './input-helper' +import {NoFileOptions} from './constants' async function run(): Promise { try { - const name = core.getInput(Inputs.Name, {required: false}) - const path = core.getInput(Inputs.Path, {required: true}) - - const searchResult = await findFilesToUpload(path) + const inputs = getInputs() + const searchResult = await findFilesToUpload(inputs.searchPath) if (searchResult.filesToUpload.length === 0) { - core.warning( - `No files were found for the provided path: ${path}. No artifacts will be uploaded.` - ) + // 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 files were found with the provided path: ${inputs.searchPath}. No artifacts will be uploaded.` + ) + break + } + case NoFileOptions.error: { + core.setFailed( + `No files were found with the provided path: ${inputs.searchPath}. No artifacts will be uploaded.` + ) + break + } + case NoFileOptions.ignore: { + core.info( + `No files were found with the provided path: ${inputs.searchPath}. No artifacts will be uploaded.` + ) + break + } + } } else { core.info( - `With the provided path, there will be ${searchResult.filesToUpload.length} files uploaded` + `With the provided path, there will be ${searchResult.filesToUpload.length} file(s) uploaded` ) core.debug(`Root artifact directory is ${searchResult.rootDirectory}`) @@ -24,7 +41,7 @@ async function run(): Promise { continueOnError: false } const uploadResponse = await artifactClient.uploadArtifact( - name || getDefaultArtifactName(), + inputs.artifactName, searchResult.filesToUpload, searchResult.rootDirectory, options diff --git a/src/upload-inputs.ts b/src/upload-inputs.ts new file mode 100644 index 0000000..60e4a84 --- /dev/null +++ b/src/upload-inputs.ts @@ -0,0 +1,18 @@ +import {NoFileOptions} from './constants' + +export interface UploadInputs { + /** + * The name of the artifact that will be uploaded + */ + artifactName: string + + /** + * The search path used to describe what to upload as part of the artifact + */ + searchPath: string + + /** + * The desired behavior if no files are found with the provided search path + */ + ifNoFilesFound: NoFileOptions +}