mirror of
https://github.com/actions/upload-artifact.git
synced 2025-06-13 23:37:12 +02:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
268d754764 | |||
c8879bf5ae | |||
5ba29a7d5b | |||
5f948bc1f0 | |||
589ca5fbdd | |||
8ec57c93cb | |||
ee5fe7718d | |||
0c366cb4fc | |||
63d6076e6f |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -2,7 +2,7 @@ name: Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
pull_request:
|
||||
|
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at opensource@github.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
52
CONTRIBUTING.md
Normal file
52
CONTRIBUTING.md
Normal file
@ -0,0 +1,52 @@
|
||||
## Contributing
|
||||
|
||||
[fork]: https://github.com/actions/upload-artifact/fork
|
||||
[pr]: https://github.com/actions/upload-artifact/compare
|
||||
[style]: https://github.com/styleguide/js
|
||||
[code-of-conduct]: CODE_OF_CONDUCT.md
|
||||
|
||||
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
|
||||
|
||||
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE).
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
|
||||
|
||||
## Found a bug?
|
||||
|
||||
- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/actions/upload-artifact/issues).
|
||||
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/actions/upload-artifact/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or a **reproducable test case** demonstrating the expected behavior that is not occurring.
|
||||
- If possible, use the relevant bug report templates to create the issue.
|
||||
|
||||
## What should I know before submitting a pull request or issue
|
||||
|
||||
The code related to `upload-artifact` is split between this repository and [actions/toolkit](https://github.com/actions/toolkit) where the `@actions/artifact` npm package is housed. The npm package contains the core functionality to interact with artifacts. Any extra functionality on top of interacting with the apis such as search is inside this repository.
|
||||
Artifact related issues will be tracked in this repository so please do not open duplicate issues in `actions/toolkit`.
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
1. [Fork][fork] and clone the repository
|
||||
2. Configure and install the dependencies: `npm install`
|
||||
3. Make sure the tests pass on your machine: `npm run test`
|
||||
4. Create a new branch: `git checkout -b my-branch-name`
|
||||
5. Make your change, add tests, and make sure the tests still pass
|
||||
6. Make sure your code is correctly formatted: `npm run format`
|
||||
7. Make sure your code passes linting: `npm run lint`
|
||||
8. Update `dist/index.js` using `npm run release`. This creates a single javascript file that is used as an entry-point for the action
|
||||
7. Push to your fork and [submit a pull request][pr]
|
||||
8. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
||||
|
||||
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
|
||||
|
||||
- Write tests.
|
||||
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
|
||||
## Resources
|
||||
|
||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
||||
- [GitHub Help](https://help.github.com)
|
||||
|
||||
Thanks! :heart: :heart: :heart:
|
||||
|
||||
GitHub Actions Team :octocat:
|
43
README.md
43
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,19 @@ 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.
|
||||
|
||||
### 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
|
||||
|
||||
@ -191,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<br/>
|
||||
<img src="https://user-images.githubusercontent.com/16109154/72556687-20235a80-386d-11ea-9e2a-b534faa77083.png" width="375" height="140">
|
||||
|
||||
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
|
||||
|
||||
|
11
action.yml
11
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'
|
125
dist/index.js
vendored
125
dist/index.js
vendored
@ -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.`);
|
||||
}
|
||||
@ -4038,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
|
||||
@ -4978,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];
|
||||
@ -4991,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()) {
|
||||
@ -6225,6 +6240,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,
|
||||
@ -6272,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;
|
||||
@ -6288,14 +6305,28 @@ 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
|
||||
*/
|
||||
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);
|
||||
// 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`);
|
||||
@ -6331,6 +6362,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:
|
||||
@ -6590,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/artifact-upload');
|
||||
this.statusReporter = new status_reporter_1.StatusReporter(10000);
|
||||
}
|
||||
/**
|
||||
@ -7245,11 +7313,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 = {}));
|
||||
|
||||
|
||||
/***/ }),
|
||||
@ -7332,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/artifact-download');
|
||||
// downloads are usually significantly faster than uploads so display status information every second
|
||||
this.statusReporter = new status_reporter_1.StatusReporter(1000);
|
||||
}
|
||||
@ -7862,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);
|
||||
}
|
||||
@ -7967,8 +8048,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())
|
||||
]);
|
||||
}
|
||||
|
12
package-lock.json
generated
12
package-lock.json
generated
@ -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.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",
|
||||
@ -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": {
|
||||
|
@ -29,7 +29,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/actions/upload-artifact#readme",
|
||||
"devDependencies": {
|
||||
"@actions/artifact": "^0.3.2",
|
||||
"@actions/artifact": "^0.3.5",
|
||||
"@actions/core": "^1.2.3",
|
||||
"@actions/glob": "^0.1.0",
|
||||
"@actions/io": "^1.0.2",
|
||||
|
@ -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'
|
||||
}
|
||||
|
30
src/input-helper.ts
Normal file
30
src/input-helper.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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[]
|
||||
@ -64,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
|
||||
@ -87,14 +89,31 @@ 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<string>()
|
||||
|
||||
/*
|
||||
Directories will be rejected if attempted to be uploaded. This includes just empty
|
||||
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)
|
||||
|
||||
// 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`
|
||||
|
@ -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<void> {
|
||||
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<void> {
|
||||
continueOnError: false
|
||||
}
|
||||
const uploadResponse = await artifactClient.uploadArtifact(
|
||||
name || getDefaultArtifactName(),
|
||||
inputs.artifactName,
|
||||
searchResult.filesToUpload,
|
||||
searchResult.rootDirectory,
|
||||
options
|
||||
|
18
src/upload-inputs.ts
Normal file
18
src/upload-inputs.ts
Normal file
@ -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
|
||||
}
|
Reference in New Issue
Block a user