Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add upload one artifact per file #354

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/workflows/test-per-file.yml
@@ -0,0 +1,94 @@
name: Test Artifact Per File
on:
workflow_dispatch:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'

jobs:

build:
name: Build

strategy:
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false

runs-on: ${{ matrix.runs-on }}

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Set Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x

- name: Install dependencies
run: npm ci

- name: Compile
run: npm run build

- name: npm test
run: npm test

- name: Lint
run: npm run lint

- name: Format
run: npm run format-check

# Test end-to-end by uploading two artifacts and then downloading them
- name: Create artifact files
run: |
mkdir -p path/to/dir-1
mkdir -p path/to/dir-2
mkdir -p path/to/dir-3
mkdir -p path/from/dir-1

echo > path/to/dir-1/file1.txt "path/to/dir-1/file1.txt"
echo > path/to/dir-2/file1.txt "path/to/dir-2/file1.txt"
echo > path/to/dir-2/file2.txt "path/to/dir-2/file2.txt"
echo > path/to/dir-3/file1.txt "path/to/dir-3/file1.txt"
echo > path/to/dir-3/file2.txt "path/to/dir-3/file2.txt"
echo > path/to/dir-3/file3.txt "path/to/dir-3/file3.txt"
echo > path/from/dir-1/file1.txt "path/from/dir-1/file1.txt"

tar -zvcf path/to/dir-3/all.gz path/to/dir-3/*

- name: 'Upload artifact #1'
uses: ./
with:
path: path/to/dir-1/file1.txt
artifact-per-file: true

- name: 'Upload artifact #2'
uses: ./
with:
path: path/to/dir-2/*
artifact-per-file: true
artifact-name-rule: ${dir}-${base}

- name: 'Upload artifact #3'
uses: ./
with:
path: path/to/dir-3/*.gz
artifact-per-file: true
artifact-name-rule: ${path}-${name}${ext}

- name: 'Upload artifact #4'
uses: ./
with:
path: |
path/**/dir-1/
!path/to/dir-3/*.gz
artifact-per-file: true
artifact-name-rule: ${{ matrix.runs-on }}-${path}-${name}
32 changes: 30 additions & 2 deletions action.yml
Expand Up @@ -3,8 +3,8 @@ description: 'Upload a build artifact that can be used by subsequent workflow st
author: 'GitHub'
inputs:
name:
description: 'Artifact name'
default: 'artifact'
description: 'Artifacts name'
default: 'artifacts'
path:
description: 'A file, directory or wildcard pattern that describes what to upload'
required: true
Expand All @@ -23,6 +23,34 @@ inputs:

Minimum 1 day.
Maximum 90 days unless changed from the repository settings page.
artifact-per-file:
description: enable otption for uploading one artifact per file
default: "false"
artifact-name-rule:
description: >
// https://nodejs.org/docs/latest-v16.x/api/path.html#pathparsepath
// Modified from path.parse()

path.parse('/home/user/dir/file.txt');
// Returns:
// { root: '/',
// dir: '/home/user/dir',
// path: 'home/user/dir'
// base: 'file.txt',
// ext: '.txt',
// name: 'file' }

┌─────────────────────┬────────────┐
│ dir sep base │
├──────┬──────────────┼──────┬─────┤
│ root │ path │ name │ ext │
" / home/user/dir / file .txt "
└──────┴──────────────┴──────┴─────┘
(All spaces in the "" line should be ignored. They are purely for formatting.)

Every key need in wrapper: ${}
sep just for prompt, can't be used
default: ${base}
runs:
using: 'node16'
main: 'dist/index.js'
152 changes: 135 additions & 17 deletions dist/index.js
Expand Up @@ -4733,12 +4733,16 @@ var __importStar = (this && this.__importStar) || function (mod) {
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(__webpack_require__(470));
const artifact_1 = __webpack_require__(214);
const search_1 = __webpack_require__(575);
const input_helper_1 = __webpack_require__(583);
const constants_1 = __webpack_require__(694);
const path_1 = __importDefault(__webpack_require__(622));
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
Expand Down Expand Up @@ -4775,12 +4779,91 @@ function run() {
if (inputs.retentionDays) {
options.retentionDays = inputs.retentionDays;
}
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.`);
const artifactsName = inputs['artifactsName'] || 'artifacts';
const artifactPerFile = inputs['artifactPerFile'] || false;
// GitHub workspace
let githubWorkspacePath = process.env['GITHUB_WORKSPACE'] || undefined;
if (!githubWorkspacePath) {
core.warning('GITHUB_WORKSPACE not defined');
}
else {
githubWorkspacePath = path_1.default.resolve(githubWorkspacePath);
core.info(`GITHUB_WORKSPACE = '${githubWorkspacePath}'`);
}
const rootDirectory = searchResult.rootDirectory;
core.info('rootDirectory: ' + rootDirectory);
if (!artifactPerFile) {
const uploadResponse = yield artifactClient.uploadArtifact(artifactsName, searchResult.filesToUpload, 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.`);
}
else {
core.info(`Artifact ${uploadResponse.artifactName} has been successfully uploaded!`);
}
}
else {
core.info(`Artifact ${uploadResponse.artifactName} has been successfully uploaded!`);
const filesToUpload = searchResult.filesToUpload;
const SuccessedItems = [];
const FailedItems = [];
const artifactNameRule = inputs['artifactNameRule'];
for (let i = 0; i < filesToUpload.length; i++) {
const file = filesToUpload[i];
core.info('file: ' + file);
const pathObject = Object.assign({}, path_1.default.parse(file));
const pathBase = pathObject.base;
const pathRoot = githubWorkspacePath
? githubWorkspacePath
: path_1.default.parse(rootDirectory).dir;
pathObject.root = pathRoot;
core.info('root: ' + pathRoot);
pathObject['path'] = file.slice(pathRoot.length, file.length - path_1.default.sep.length - pathBase.length);
core.info('path: ' + pathObject['path']);
let artifactName = artifactNameRule;
for (const key of Object.keys(pathObject)) {
const re = `$\{${key}}`;
if (artifactNameRule.includes(re)) {
const value = pathObject[key] || '';
artifactName = artifactName.replace(re, value);
}
}
if (artifactName.startsWith(path_1.default.sep)) {
core.warning(`${artifactName} startsWith ${path_1.default.sep}`);
artifactName = artifactName.slice(path_1.default.sep.length);
}
if (artifactName.includes(':')) {
core.warning(`${artifactName} includes :`);
artifactName = artifactName.split(':').join('-');
}
if (artifactName.includes(path_1.default.sep)) {
core.warning(`${artifactName} includes ${path_1.default.sep}`);
artifactName = artifactName.split(path_1.default.sep).join('_');
}
core.debug(artifactName);
const artifactItemExist = SuccessedItems.includes(artifactName);
if (artifactItemExist) {
const oldArtifactName = artifactName;
core.warning(`${artifactName} artifact alreay exist`);
artifactName = `${i}__${artifactName}`;
core.warning(`${oldArtifactName} => ${artifactName}`);
}
const uploadResponse = yield artifactClient.uploadArtifact(artifactName, [file], rootDirectory, options);
if (uploadResponse.failedItems.length > 0) {
FailedItems.push(artifactName);
}
else {
SuccessedItems.push(artifactName);
}
}
if (FailedItems.length > 0) {
let errMsg = `${FailedItems.length} artifacts failed to upload, they were:\n`;
errMsg += FailedItems.join('\n');
core.setFailed(errMsg);
}
if (SuccessedItems.length > 0) {
let infoMsg = `${SuccessedItems.length} artifacts has been successfully uploaded! They were:\n`;
infoMsg += SuccessedItems.join('\n');
core.info(infoMsg);
}
}
}
}
Expand Down Expand Up @@ -7174,26 +7257,59 @@ 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 TRUE_MAP = ['true', 'True', 'TRUE'];
let artifactPerFile = false;
const artifactPerFileStr = core.getInput(constants_1.Inputs.ArtifactPerFile);
if (artifactPerFileStr) {
artifactPerFile = TRUE_MAP.includes(artifactPerFileStr) ? true : false;
}
let name = '';
let artifactNameRule = '';
if (!artifactPerFile) {
name = core.getInput(constants_1.Inputs.Name);
}
else {
artifactNameRule = core.getInput(constants_1.Inputs.ArtifactNameRule) || '${base}';
}
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)}`);
}
const inputs = {
artifactName: name,
searchPath: path,
ifNoFilesFound: noFileBehavior
};
const retentionDaysStr = core.getInput(constants_1.Inputs.RetentionDays);
if (retentionDaysStr) {
inputs.retentionDays = parseInt(retentionDaysStr);
if (isNaN(inputs.retentionDays)) {
core.setFailed('Invalid retention-days');
const typedInputs = (artifactPerFile) => {
const retentionDaysStr = core.getInput(constants_1.Inputs.RetentionDays);
if (!artifactPerFile) {
const inputs = {
artifactsName: name,
searchPath: path,
ifNoFilesFound: noFileBehavior
};
if (retentionDaysStr) {
inputs.retentionDays = parseInt(retentionDaysStr);
if (isNaN(inputs.retentionDays)) {
core.setFailed('Invalid retention-days');
}
}
return inputs;
}
}
return inputs;
else {
const inputs = {
searchPath: path,
ifNoFilesFound: noFileBehavior,
artifactPerFile: artifactPerFile,
artifactNameRule: artifactNameRule
};
if (retentionDaysStr) {
inputs.retentionDays = parseInt(retentionDaysStr);
if (isNaN(inputs.retentionDays)) {
core.setFailed('Invalid retention-days');
}
}
return inputs;
}
};
return typedInputs(artifactPerFile);
}
exports.getInputs = getInputs;

Expand Down Expand Up @@ -8196,6 +8312,8 @@ var Inputs;
Inputs["Path"] = "path";
Inputs["IfNoFilesFound"] = "if-no-files-found";
Inputs["RetentionDays"] = "retention-days";
Inputs["ArtifactPerFile"] = "artifact-per-file";
Inputs["ArtifactNameRule"] = "artifact-name-rule";
})(Inputs = exports.Inputs || (exports.Inputs = {}));
var NoFileOptions;
(function (NoFileOptions) {
Expand Down
4 changes: 3 additions & 1 deletion src/constants.ts
Expand Up @@ -2,7 +2,9 @@ export enum Inputs {
Name = 'name',
Path = 'path',
IfNoFilesFound = 'if-no-files-found',
RetentionDays = 'retention-days'
RetentionDays = 'retention-days',
ArtifactPerFile = 'artifact-per-file',
ArtifactNameRule = 'artifact-name-rule'
}

export enum NoFileOptions {
Expand Down