From ee5fe7718d7617699bfda5af74feab4ff4ff3e7b Mon Sep 17 00:00:00 2001 From: Konrad Pabjan Date: Tue, 14 Jul 2020 12:20:40 +0200 Subject: [PATCH 1/7] Update test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9be1d28..0233186 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: Test on: push: branches: - - master + - main paths-ignore: - '**.md' pull_request: From 8ec57c93cb3b1ac57041867f31f3d25af8336753 Mon Sep 17 00:00:00 2001 From: Konrad Pabjan Date: Tue, 14 Jul 2020 12:25:21 +0200 Subject: [PATCH 2/7] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cabcd5d..1f1e3eb 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ steps: !path/**/*.tmp ``` -For supported wildcards along with behavior and documentation, see [@actions/glob](https://github.com/actions/toolkit/tree/master/packages/glob) which is used internally to search for files. +For supported wildcards along with behavior and documentation, see [@actions/glob](https://github.com/actions/toolkit/tree/main/packages/glob) which is used internally to search for files. If a wildcard pattern is used, the path hierarchy will be preserved after the first wildcard pattern. @@ -86,7 +86,7 @@ If multiple paths are provided as input, the least common ancestor of all the se Relative and absolute file paths are both allowed. Relative paths are rooted against the current working directory. Paths that begin with a wildcard character should be quoted to avoid being interpreted as YAML aliases. -The [@actions/artifact](https://github.com/actions/toolkit/tree/master/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. +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. ### Conditional Artifact Upload From 589ca5fbdd432d7e4f377aced7a8752631dad642 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2020 12:45:55 +0200 Subject: [PATCH 3/7] Bump lodash from 4.17.15 to 4.17.19 (#99) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d217fbd..61facf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7975,9 +7975,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "lodash.memoize": { From 5f948bc1f0a251f88bb4c9b1c3dfa6cbd1327dc5 Mon Sep 17 00:00:00 2001 From: Konrad Pabjan Date: Mon, 27 Jul 2020 15:41:16 +0200 Subject: [PATCH 4/7] Correctly check symlinks (#103) --- dist/index.js | 6 +++++- src/search.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/dist/index.js b/dist/index.js index 85179f2..4208115 100644 --- a/dist/index.js +++ b/dist/index.js @@ -6225,6 +6225,8 @@ const path = __importStar(__webpack_require__(622)); const core_1 = __webpack_require__(470); const fs_1 = __webpack_require__(747); const path_1 = __webpack_require__(622); +const util_1 = __webpack_require__(669); +const stats = util_1.promisify(fs_1.stat); function getDefaultGlobOptions() { return { followSymbolicLinks: true, @@ -6293,7 +6295,9 @@ function findFilesToUpload(searchPath, globOptions) { directories so filter any directories out from the raw search results */ for (const searchResult of rawSearchResults) { - if (!fs_1.lstatSync(searchResult).isDirectory()) { + const fileStats = yield stats(searchResult); + // isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead + if (!fileStats.isDirectory()) { core_1.debug(`File:${searchResult} was found using the provided searchPath`); searchResults.push(searchResult); } diff --git a/src/search.ts b/src/search.ts index f507f40..5a4911a 100644 --- a/src/search.ts +++ b/src/search.ts @@ -1,8 +1,10 @@ import * as glob from '@actions/glob' import * as path from 'path' import {debug, info} from '@actions/core' -import {lstatSync} from 'fs' +import {stat} from 'fs' import {dirname} from 'path' +import {promisify} from 'util' +const stats = promisify(stat) export interface SearchResult { filesToUpload: string[] @@ -92,7 +94,9 @@ export async function findFilesToUpload( directories so filter any directories out from the raw search results */ for (const searchResult of rawSearchResults) { - if (!lstatSync(searchResult).isDirectory()) { + const fileStats = await stats(searchResult) + // isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead + if (!fileStats.isDirectory()) { debug(`File:${searchResult} was found using the provided searchPath`) searchResults.push(searchResult) } else { From 5ba29a7d5bc755310062446302393a8b9087adb8 Mon Sep 17 00:00:00 2001 From: Konrad Pabjan Date: Fri, 31 Jul 2020 17:27:37 +0200 Subject: [PATCH 5/7] Add new option to specify behavior if no files found (#104) * Add new option to specify behavior if no files found --- README.md | 12 ++++++ action.yml | 11 +++++- dist/index.js | 85 ++++++++++++++++++++++++++++++++++++------ src/constants.ts | 20 ++++++++-- src/input-helper.ts | 30 +++++++++++++++ src/upload-artifact.ts | 37 +++++++++++++----- src/upload-inputs.ts | 18 +++++++++ 7 files changed, 188 insertions(+), 25 deletions(-) create mode 100644 src/input-helper.ts create mode 100644 src/upload-inputs.ts 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 +} From c8879bf5aef7bef66f9b82b197f34c4eeeb1731b Mon Sep 17 00:00:00 2001 From: Konrad Pabjan Date: Fri, 31 Jul 2020 19:22:08 +0200 Subject: [PATCH 6/7] Detect case insensitive uploads + Bump @actions/artifact to version 0.3.3 (#106) * Detect case insensitive uploads * PR feedback --- README.md | 27 ++++++++++++++++++++++++++- dist/index.js | 29 +++++++++++++++++++++-------- package-lock.json | 6 +++--- package.json | 2 +- src/search.ts | 17 ++++++++++++++++- 5 files changed, 67 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d576747..27881df 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,32 @@ Environment variables along with context expressions can also be used for input. In the top right corner of a workflow run, once the run is over, if you used this action, there will be a `Artifacts` dropdown which you can download items from. Here's a screenshot of what it looks like
-There is a trashcan icon that can be used to delete the artifact. This icon will only appear for users who have write permissions to the repository. +There is a trashcan icon that can be used to delete the artifact. This icon will only appear for users who have write permissions to the repository. + +# Limitations + +### Permission Loss + +:exclamation: File permissions are not maintained during artifact upload :exclamation: For example, if you make a file executable using `chmod` and then upload that file, post-download the file is no longer guaranteed to be set as an executable. + +### Case Insensitive Uploads + +:exclamation: File uploads are case insensitive :exclamation: If you upload `A.txt` and `a.txt` with the same root path, only a single file will be saved and available during download. + +### Maintaining file permissions and case sensitive files + +If file permissions and case sensitivity are required, you can `tar` all of your files together before artifact upload. Post download, the `tar` file will maintain file permissions and case sensitivity. + +```yaml + - name: 'Tar files' + run: tar -cvf my_files.tar /path/to/my/directory + + - name: 'Upload Artifact' + uses: actions/upload-artifact@v2 + with: + name: my-artifact + path: my_files.tar +``` ## Additional Documentation diff --git a/dist/index.js b/dist/index.js index e9321ce..264f2de 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4992,11 +4992,12 @@ const utils_1 = __webpack_require__(870); * Used for managing http clients during either upload or download */ class HttpManager { - constructor(clientCount) { + constructor(clientCount, userAgent) { if (clientCount < 1) { throw new Error('There must be at least one client'); } - this.clients = new Array(clientCount).fill(utils_1.createHttpClient()); + this.userAgent = userAgent; + this.clients = new Array(clientCount).fill(utils_1.createHttpClient(userAgent)); } getClient(index) { return this.clients[index]; @@ -5005,7 +5006,7 @@ class HttpManager { // for more information see: https://github.com/actions/http-client/blob/04e5ad73cd3fd1f5610a32116b0759eddf6570d2/index.ts#L292 disposeAndReplaceClient(index) { this.clients[index].dispose(); - this.clients[index] = utils_1.createHttpClient(); + this.clients[index] = utils_1.createHttpClient(this.userAgent); } disposeAndReplaceAllClients() { for (const [index] of this.clients.entries()) { @@ -6288,7 +6289,7 @@ function getMultiPathLCA(searchPaths) { } return true; } - // Loop over all the search paths until there is a non-common ancestor or we go out of bounds + // loop over all the search paths until there is a non-common ancestor or we go out of bounds while (splitIndex < smallestPathLength) { if (!isPathTheSame()) { break; @@ -6304,6 +6305,11 @@ function findFilesToUpload(searchPath, globOptions) { const searchResults = []; const globber = yield glob.create(searchPath, globOptions || getDefaultGlobOptions()); const rawSearchResults = yield globber.glob(); + /* + 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 + */ + const set = new Set(); /* Directories will be rejected if attempted to be uploaded. This includes just empty directories so filter any directories out from the raw search results @@ -6314,6 +6320,13 @@ function findFilesToUpload(searchPath, globOptions) { if (!fileStats.isDirectory()) { core_1.debug(`File:${searchResult} was found using the provided searchPath`); searchResults.push(searchResult); + // detect any files that would be overwritten because of case insensitivity + if (set.has(searchResult.toLowerCase())) { + core_1.info(`Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path`); + } + else { + set.add(searchResult.toLowerCase()); + } } else { core_1.debug(`Removing ${searchResult} from rawSearchResults because it is a directory`); @@ -6645,7 +6658,7 @@ const upload_gzip_1 = __webpack_require__(647); const stat = util_1.promisify(fs.stat); class UploadHttpClient { constructor() { - this.uploadHttpManager = new http_manager_1.HttpManager(config_variables_1.getUploadFileConcurrency()); + this.uploadHttpManager = new http_manager_1.HttpManager(config_variables_1.getUploadFileConcurrency(), 'actions/upload-artifact'); this.statusReporter = new status_reporter_1.StatusReporter(10000); } /** @@ -7399,7 +7412,7 @@ const http_manager_1 = __webpack_require__(452); const config_variables_1 = __webpack_require__(401); class DownloadHttpClient { constructor() { - this.downloadHttpManager = new http_manager_1.HttpManager(config_variables_1.getDownloadFileConcurrency()); + this.downloadHttpManager = new http_manager_1.HttpManager(config_variables_1.getDownloadFileConcurrency(), 'actions/download-artifact'); // downloads are usually significantly faster than uploads so display status information every second this.statusReporter = new status_reporter_1.StatusReporter(1000); } @@ -8034,8 +8047,8 @@ function getUploadHeaders(contentType, isKeepAlive, isGzip, uncompressedLength, return requestOptions; } exports.getUploadHeaders = getUploadHeaders; -function createHttpClient() { - return new http_client_1.HttpClient('actions/artifact', [ +function createHttpClient(userAgent) { + return new http_client_1.HttpClient(userAgent, [ new auth_1.BearerCredentialHandler(config_variables_1.getRuntimeToken()) ]); } diff --git a/package-lock.json b/package-lock.json index 61facf7..e789485 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@actions/artifact": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-0.3.2.tgz", - "integrity": "sha512-KzUe5DEeVXprAodxfGKtx9f7ukuVKE6V6pge6t5GDGk0cdkfiMEfahoq7HfBsOsmVy4J7rr1YZQPUTvXveYinw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-0.3.3.tgz", + "integrity": "sha512-sKC1uA5p6064C6Qypmmt6O8iKlpDyMTfqqDlS4/zfJX1Hs8NbbzPLLN81RpewuJPWQNnroeF52w4VCWypbSNaA==", "dev": true, "requires": { "@actions/core": "^1.2.1", diff --git a/package.json b/package.json index e13d64c..12511ee 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/actions/upload-artifact#readme", "devDependencies": { - "@actions/artifact": "^0.3.2", + "@actions/artifact": "^0.3.3", "@actions/core": "^1.2.3", "@actions/glob": "^0.1.0", "@actions/io": "^1.0.2", diff --git a/src/search.ts b/src/search.ts index 5a4911a..bd80164 100644 --- a/src/search.ts +++ b/src/search.ts @@ -66,7 +66,7 @@ function getMultiPathLCA(searchPaths: string[]): string { return true } - // Loop over all the search paths until there is a non-common ancestor or we go out of bounds + // loop over all the search paths until there is a non-common ancestor or we go out of bounds while (splitIndex < smallestPathLength) { if (!isPathTheSame()) { break @@ -89,6 +89,12 @@ export async function findFilesToUpload( ) const rawSearchResults: string[] = await globber.glob() + /* + 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 + */ + const set = new Set() + /* Directories will be rejected if attempted to be uploaded. This includes just empty directories so filter any directories out from the raw search results @@ -99,6 +105,15 @@ export async function findFilesToUpload( if (!fileStats.isDirectory()) { debug(`File:${searchResult} was found using the provided searchPath`) searchResults.push(searchResult) + + // detect any files that would be overwritten because of case insensitivity + if (set.has(searchResult.toLowerCase())) { + info( + `Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path` + ) + } else { + set.add(searchResult.toLowerCase()) + } } else { debug( `Removing ${searchResult} from rawSearchResults because it is a directory` From 268d7547644ab8a9d0c1163299e59a1f5d93f39b Mon Sep 17 00:00:00 2001 From: Konrad Pabjan Date: Tue, 4 Aug 2020 17:36:48 +0200 Subject: [PATCH 7/7] Retry on 413 response codes (#108) --- dist/index.js | 9 +++++---- package-lock.json | 6 +++--- package.json | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/dist/index.js b/dist/index.js index 264f2de..b1a9863 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4052,7 +4052,7 @@ exports.getUploadFileConcurrency = getUploadFileConcurrency; // When uploading large files that can't be uploaded with a single http call, this controls // the chunk size that is used during upload function getUploadChunkSize() { - return 4 * 1024 * 1024; // 4 MB Chunks + return 8 * 1024 * 1024; // 8 MB Chunks } exports.getUploadChunkSize = getUploadChunkSize; // The maximum number of retries that can be attempted before an upload or download fails @@ -6658,7 +6658,7 @@ const upload_gzip_1 = __webpack_require__(647); const stat = util_1.promisify(fs.stat); class UploadHttpClient { constructor() { - this.uploadHttpManager = new http_manager_1.HttpManager(config_variables_1.getUploadFileConcurrency(), 'actions/upload-artifact'); + this.uploadHttpManager = new http_manager_1.HttpManager(config_variables_1.getUploadFileConcurrency(), '@actions/artifact-upload'); this.statusReporter = new status_reporter_1.StatusReporter(10000); } /** @@ -7412,7 +7412,7 @@ const http_manager_1 = __webpack_require__(452); const config_variables_1 = __webpack_require__(401); class DownloadHttpClient { constructor() { - this.downloadHttpManager = new http_manager_1.HttpManager(config_variables_1.getDownloadFileConcurrency(), 'actions/download-artifact'); + this.downloadHttpManager = new http_manager_1.HttpManager(config_variables_1.getDownloadFileConcurrency(), '@actions/artifact-download'); // downloads are usually significantly faster than uploads so display status information every second this.statusReporter = new status_reporter_1.StatusReporter(1000); } @@ -7942,7 +7942,8 @@ function isRetryableStatusCode(statusCode) { http_client_1.HttpCodes.BadGateway, http_client_1.HttpCodes.ServiceUnavailable, http_client_1.HttpCodes.GatewayTimeout, - http_client_1.HttpCodes.TooManyRequests + http_client_1.HttpCodes.TooManyRequests, + 413 // Payload Too Large ]; return retryableStatusCodes.includes(statusCode); } diff --git a/package-lock.json b/package-lock.json index e789485..bc58505 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@actions/artifact": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-0.3.3.tgz", - "integrity": "sha512-sKC1uA5p6064C6Qypmmt6O8iKlpDyMTfqqDlS4/zfJX1Hs8NbbzPLLN81RpewuJPWQNnroeF52w4VCWypbSNaA==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-0.3.5.tgz", + "integrity": "sha512-y27pBEnUjOqCP2zUf86YkiqGOp1r0C9zUOmGmcxizsHMls0wvk+FJwd+l8JIoukvj1BeBHYP+c+9AEqOt5AqMA==", "dev": true, "requires": { "@actions/core": "^1.2.1", diff --git a/package.json b/package.json index 12511ee..7867d1f 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/actions/upload-artifact#readme", "devDependencies": { - "@actions/artifact": "^0.3.3", + "@actions/artifact": "^0.3.5", "@actions/core": "^1.2.3", "@actions/glob": "^0.1.0", "@actions/io": "^1.0.2",