4 Commits

Author SHA1 Message Date
Nick Cipollo
6c34249ffc Prepare 1.6.1 release
Some checks failed
PR Checks / check_pr (push) Has been cancelled
2020-03-05 13:25:53 -05:00
Nick Cipollo
c4e12dcecf Build debug 2020-03-05 13:22:45 -05:00
Nick Cipollo
791611e03e Tiny logging tweek 2020-03-05 13:12:22 -05:00
Nick Cipollo
2ea88b1f32 Add retry for uploading artifact (#13) 2020-03-05 11:39:02 -05:00
10 changed files with 269 additions and 228 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
#node_modules/ # node_modules/
__tests__/runner/* __tests__/runner/*

View File

@@ -1,6 +1,7 @@
import { Artifact } from "../src/Artifact" import { Artifact } from "../src/Artifact"
import { GithubArtifactUploader } from "../src/ArtifactUploader" import { GithubArtifactUploader } from "../src/ArtifactUploader"
import { Releases } from "../src/Releases"; import { Releases } from "../src/Releases";
import { RequestError } from '@octokit/request-error'
const artifacts = [ const artifacts = [
new Artifact('a/art1'), new Artifact('a/art1'),
@@ -32,6 +33,7 @@ describe('ArtifactUploader', () => {
it('replaces all artifacts', async () => { it('replaces all artifacts', async () => {
mockDeleteSuccess() mockDeleteSuccess()
mockListWithAssets() mockListWithAssets()
mockUploadArtifact()
const uploader = createUploader(true) const uploader = createUploader(true)
await uploader.uploadArtifacts(artifacts, releaseId, url) await uploader.uploadArtifacts(artifacts, releaseId, url)
@@ -46,10 +48,69 @@ describe('ArtifactUploader', () => {
expect(deleteMock).toBeCalledWith(1) expect(deleteMock).toBeCalledWith(1)
expect(deleteMock).toBeCalledWith(2) expect(deleteMock).toBeCalledWith(2)
}) })
it('replaces no artifacts when previous asset list empty', async () => { it('replaces no artifacts when previous asset list empty', async () => {
mockDeleteSuccess() mockDeleteSuccess()
mockListWithoutAssets() mockListWithoutAssets()
mockUploadArtifact()
const uploader = createUploader(true)
await uploader.uploadArtifacts(artifacts, releaseId, url)
expect(uploadMock).toBeCalledTimes(2)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1')
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2')
expect(deleteMock).toBeCalledTimes(0)
})
it('retry when upload failed with 5xx response', async () => {
mockListWithoutAssets()
mockUploadArtifact(500, 2)
const uploader = createUploader(true)
await uploader.uploadArtifacts(artifacts, releaseId, url)
expect(uploadMock).toBeCalledTimes(4)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1')
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1')
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1')
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2')
expect(deleteMock).toBeCalledTimes(0)
})
it('abort when upload failed with 5xx response after 3 attemps', async () => {
mockListWithoutAssets()
mockUploadArtifact(500, 4)
const uploader = createUploader(true)
await uploader.uploadArtifacts(artifacts, releaseId, url)
expect(uploadMock).toBeCalledTimes(5)
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1')
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1')
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1')
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2')
expect(uploadMock)
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2')
expect(deleteMock).toBeCalledTimes(0)
})
it('abort when upload failed with non-5xx response', async () => {
mockListWithoutAssets()
mockUploadArtifact(401, 2)
const uploader = createUploader(true) const uploader = createUploader(true)
await uploader.uploadArtifacts(artifacts, releaseId, url) await uploader.uploadArtifacts(artifacts, releaseId, url)
@@ -66,6 +127,7 @@ describe('ArtifactUploader', () => {
it('throws error from replace', async () => { it('throws error from replace', async () => {
mockDeleteError() mockDeleteError()
mockListWithAssets() mockListWithAssets()
mockUploadArtifact()
const uploader = createUploader(true) const uploader = createUploader(true)
expect.hasAssertions() expect.hasAssertions()
@@ -79,6 +141,7 @@ describe('ArtifactUploader', () => {
it('updates all artifacts, delete none', async () => { it('updates all artifacts, delete none', async () => {
mockDeleteError() mockDeleteError()
mockListWithAssets() mockListWithAssets()
mockUploadArtifact()
const uploader = createUploader(false) const uploader = createUploader(false)
await uploader.uploadArtifacts(artifacts, releaseId, url) await uploader.uploadArtifacts(artifacts, releaseId, url)
@@ -93,7 +156,6 @@ describe('ArtifactUploader', () => {
}) })
function createUploader(replaces: boolean): GithubArtifactUploader { function createUploader(replaces: boolean): GithubArtifactUploader {
uploadMock.mockResolvedValue({})
const MockReleases = jest.fn<Releases, any>(() => { const MockReleases = jest.fn<Releases, any>(() => {
return { return {
create: jest.fn(), create: jest.fn(),
@@ -134,4 +196,12 @@ describe('ArtifactUploader', () => {
function mockListWithoutAssets() { function mockListWithoutAssets() {
listArtifactsMock.mockResolvedValue({ data: [] }) listArtifactsMock.mockResolvedValue({ data: [] })
} }
function mockUploadArtifact(status: number = 200, failures: number = 0) {
const error = new RequestError(`HTTP ${status}`, status, { headers: {}, request: { method: 'GET', url: '', headers: {} } })
for (let index = 0; index < failures; index++) {
uploadMock.mockRejectedValueOnce(error)
}
uploadMock.mockResolvedValue({})
}
}); });

View File

@@ -6,6 +6,7 @@ inputs:
description: 'An optional flag which indicates if we should update a release if it already exists. Defaults to false.' description: 'An optional flag which indicates if we should update a release if it already exists. Defaults to false.'
default: '' default: ''
artifact: artifact:
deprecationMessage: Use 'artifacts' instead.
description: 'An optional set of paths representing artifacts to upload to the release. This may be a single path or a comma delimited list of paths (or globs)' description: 'An optional set of paths representing artifacts to upload to the release. This may be a single path or a comma delimited list of paths (or globs)'
default: '' default: ''
artifacts: artifacts:

View File

@@ -1,13 +1,4 @@
"use strict"; "use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const ErrorMessage_1 = require("./ErrorMessage"); const ErrorMessage_1 = require("./ErrorMessage");
class Action { class Action {
@@ -16,74 +7,62 @@ class Action {
this.releases = releases; this.releases = releases;
this.uploader = uploader; this.uploader = uploader;
} }
perform() { async perform() {
return __awaiter(this, void 0, void 0, function* () { const releaseResponse = await this.createOrUpdateRelease();
const releaseResponse = yield this.createOrUpdateRelease(); const releaseId = releaseResponse.id;
const releaseId = releaseResponse.id; const uploadUrl = releaseResponse.upload_url;
const uploadUrl = releaseResponse.upload_url; const artifacts = this.inputs.artifacts;
const artifacts = this.inputs.artifacts; if (artifacts.length > 0) {
if (artifacts.length > 0) { await this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl);
yield this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl); }
}
});
} }
createOrUpdateRelease() { async createOrUpdateRelease() {
return __awaiter(this, void 0, void 0, function* () { if (this.inputs.allowUpdates) {
if (this.inputs.allowUpdates) { try {
try { const getResponse = await this.releases.getByTag(this.inputs.tag);
const getResponse = yield this.releases.getByTag(this.inputs.tag); return await this.updateRelease(getResponse.data.id);
return yield this.updateRelease(getResponse.data.id); }
catch (error) {
if (this.noPublishedRelease(error)) {
return await this.updateDraftOrCreateRelease();
} }
catch (error) { else {
if (this.noPublishedRelease(error)) { throw error;
return yield this.updateDraftOrCreateRelease();
}
else {
throw error;
}
} }
} }
else { }
return yield this.createRelease(); else {
} return await this.createRelease();
}); }
} }
updateRelease(id) { async updateRelease(id) {
return __awaiter(this, void 0, void 0, function* () { const response = await this.releases.update(id, this.inputs.tag, this.inputs.body, this.inputs.commit, this.inputs.draft, this.inputs.name, this.inputs.prerelease);
const response = yield this.releases.update(id, this.inputs.tag, this.inputs.body, this.inputs.commit, this.inputs.draft, this.inputs.name, this.inputs.prerelease); return response.data;
return response.data;
});
} }
noPublishedRelease(error) { noPublishedRelease(error) {
const errorMessage = new ErrorMessage_1.ErrorMessage(error); const errorMessage = new ErrorMessage_1.ErrorMessage(error);
return errorMessage.status == 404; return errorMessage.status == 404;
} }
updateDraftOrCreateRelease() { async updateDraftOrCreateRelease() {
return __awaiter(this, void 0, void 0, function* () { const draftReleaseId = await this.findMatchingDraftReleaseId();
const draftReleaseId = yield this.findMatchingDraftReleaseId(); if (draftReleaseId) {
if (draftReleaseId) { return await this.updateRelease(draftReleaseId);
return yield this.updateRelease(draftReleaseId); }
} else {
else { return await this.createRelease();
return yield this.createRelease(); }
}
});
} }
findMatchingDraftReleaseId() { async findMatchingDraftReleaseId() {
var _a; var _a;
return __awaiter(this, void 0, void 0, function* () { const tag = this.inputs.tag;
const tag = this.inputs.tag; const response = await this.releases.listReleases();
const response = yield this.releases.listReleases(); const releases = response.data;
const releases = response.data; const draftRelease = releases.find(release => release.draft && release.tag_name == tag);
const draftRelease = releases.find(release => release.draft && release.tag_name == tag); return (_a = draftRelease) === null || _a === void 0 ? void 0 : _a.id;
return (_a = draftRelease) === null || _a === void 0 ? void 0 : _a.id;
});
} }
createRelease() { async createRelease() {
return __awaiter(this, void 0, void 0, function* () { const response = await this.releases.create(this.inputs.tag, this.inputs.body, this.inputs.commit, this.inputs.draft, this.inputs.name, this.inputs.prerelease);
const response = yield this.releases.create(this.inputs.tag, this.inputs.body, this.inputs.commit, this.inputs.draft, this.inputs.name, this.inputs.prerelease); return response.data;
return response.data;
});
} }
} }
exports.Action = Action; exports.Action = Action;

View File

@@ -1,13 +1,4 @@
"use strict"; "use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) { var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
@@ -18,43 +9,47 @@ var __importStar = (this && this.__importStar) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core")); const core = __importStar(require("@actions/core"));
class GithubArtifactUploader { class GithubArtifactUploader {
constructor(releases, replacesExistingArtifacts) { constructor(releases, replacesExistingArtifacts = true) {
this.replacesExistingArtifacts = true;
this.releases = releases; this.releases = releases;
this.replacesExistingArtifacts = replacesExistingArtifacts; this.replacesExistingArtifacts = replacesExistingArtifacts;
} }
uploadArtifacts(artifacts, releaseId, uploadUrl) { async uploadArtifacts(artifacts, releaseId, uploadUrl) {
return __awaiter(this, void 0, void 0, function* () { if (this.replacesExistingArtifacts) {
if (this.replacesExistingArtifacts) { await this.deleteUpdatedArtifacts(artifacts, releaseId);
yield this.deleteUpdatedArtifacts(artifacts, releaseId); }
} for (const artifact of artifacts) {
for (const artifact of artifacts) { await this.uploadArtifact(artifact, uploadUrl);
try { }
yield this.releases.uploadArtifact(uploadUrl, artifact.contentLength, artifact.contentType, artifact.readFile(), artifact.name);
}
catch (error) {
const message = `Failed to upload artifact ${artifact.name}. Does it already exist?`;
core.warning(message);
}
}
return Promise.resolve();
});
} }
deleteUpdatedArtifacts(artifacts, releaseId) { async uploadArtifact(artifact, uploadUrl, retry = 3) {
return __awaiter(this, void 0, void 0, function* () { try {
const response = yield this.releases.listArtifactsForRelease(releaseId); core.debug(`Uploading artifact ${artifact.name}...`);
const releaseAssets = response.data; await this.releases.uploadArtifact(uploadUrl, artifact.contentLength, artifact.contentType, artifact.readFile(), artifact.name);
const assetByName = new Map(); }
releaseAssets.forEach(asset => { catch (error) {
assetByName[asset.name] = asset; if (error.status >= 500 && retry > 0) {
}); core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`);
for (const artifact of artifacts) { await this.uploadArtifact(artifact, uploadUrl, retry - 1);
const asset = assetByName[artifact.name];
if (asset) {
yield this.releases.deleteArtifact(asset.id);
}
} }
else {
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}.`);
}
}
}
async deleteUpdatedArtifacts(artifacts, releaseId) {
const response = await this.releases.listArtifactsForRelease(releaseId);
const releaseAssets = response.data;
const assetByName = {};
releaseAssets.forEach(asset => {
assetByName[asset.name] = asset;
}); });
for (const artifact of artifacts) {
const asset = assetByName[artifact.name];
if (asset) {
core.debug(`Deleting existing artifact ${artifact.name}...`);
await this.releases.deleteArtifact(asset.id);
}
}
} }
} }
exports.GithubArtifactUploader = GithubArtifactUploader; exports.GithubArtifactUploader = GithubArtifactUploader;

View File

@@ -1,13 +1,4 @@
"use strict"; "use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) { var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
@@ -24,17 +15,15 @@ const Action_1 = require("./Action");
const ArtifactUploader_1 = require("./ArtifactUploader"); const ArtifactUploader_1 = require("./ArtifactUploader");
const ArtifactGlobber_1 = require("./ArtifactGlobber"); const ArtifactGlobber_1 = require("./ArtifactGlobber");
const ErrorMessage_1 = require("./ErrorMessage"); const ErrorMessage_1 = require("./ErrorMessage");
function run() { async function run() {
return __awaiter(this, void 0, void 0, function* () { try {
try { const action = createAction();
const action = createAction(); await action.perform();
yield action.perform(); }
} catch (error) {
catch (error) { const errorMessage = new ErrorMessage_1.ErrorMessage(error);
const errorMessage = new ErrorMessage_1.ErrorMessage(error); core.setFailed(errorMessage.toString());
core.setFailed(errorMessage.toString()); }
}
});
} }
function createAction() { function createAction() {
const token = core.getInput('token'); const token = core.getInput('token');

View File

@@ -1,94 +1,71 @@
"use strict"; "use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
class GithubReleases { class GithubReleases {
constructor(context, git) { constructor(context, git) {
this.context = context; this.context = context;
this.git = git; this.git = git;
} }
create(tag, body, commitHash, draft, name, prerelease) { async create(tag, body, commitHash, draft, name, prerelease) {
return __awaiter(this, void 0, void 0, function* () { return this.git.repos.createRelease({
return this.git.repos.createRelease({ body: body,
body: body, name: name,
name: name, draft: draft,
draft: draft, owner: this.context.repo.owner,
owner: this.context.repo.owner, prerelease: prerelease,
prerelease: prerelease, repo: this.context.repo.repo,
repo: this.context.repo.repo, target_commitish: commitHash,
target_commitish: commitHash, tag_name: tag
tag_name: tag
});
}); });
} }
deleteArtifact(assetId) { async deleteArtifact(assetId) {
return __awaiter(this, void 0, void 0, function* () { return this.git.repos.deleteReleaseAsset({
return this.git.repos.deleteReleaseAsset({ asset_id: assetId,
asset_id: assetId, owner: this.context.repo.owner,
owner: this.context.repo.owner, repo: this.context.repo.repo
repo: this.context.repo.repo
});
}); });
} }
listArtifactsForRelease(releaseId) { async listArtifactsForRelease(releaseId) {
return __awaiter(this, void 0, void 0, function* () { return this.git.repos.listAssetsForRelease({
return this.git.repos.listAssetsForRelease({ owner: this.context.repo.owner,
owner: this.context.repo.owner, release_id: releaseId,
release_id: releaseId, repo: this.context.repo.repo
repo: this.context.repo.repo
});
}); });
} }
listReleases() { async listReleases() {
return __awaiter(this, void 0, void 0, function* () { return this.git.repos.listReleases({
return this.git.repos.listReleases({ owner: this.context.repo.owner,
owner: this.context.repo.owner, repo: this.context.repo.repo
repo: this.context.repo.repo
});
}); });
} }
getByTag(tag) { async getByTag(tag) {
return __awaiter(this, void 0, void 0, function* () { return this.git.repos.getReleaseByTag({
return this.git.repos.getReleaseByTag({ owner: this.context.repo.owner,
owner: this.context.repo.owner, repo: this.context.repo.repo,
repo: this.context.repo.repo, tag: tag
tag: tag
});
}); });
} }
update(id, tag, body, commitHash, draft, name, prerelease) { async update(id, tag, body, commitHash, draft, name, prerelease) {
return __awaiter(this, void 0, void 0, function* () { return this.git.repos.updateRelease({
return this.git.repos.updateRelease({ release_id: id,
release_id: id, body: body,
body: body, name: name,
name: name, draft: draft,
draft: draft, owner: this.context.repo.owner,
owner: this.context.repo.owner, prerelease: prerelease,
prerelease: prerelease, repo: this.context.repo.repo,
repo: this.context.repo.repo, target_commitish: commitHash,
target_commitish: commitHash, tag_name: tag
tag_name: tag
});
}); });
} }
uploadArtifact(assetUrl, contentLength, contentType, file, name) { async uploadArtifact(assetUrl, contentLength, contentType, file, name) {
return __awaiter(this, void 0, void 0, function* () { return this.git.repos.uploadReleaseAsset({
return this.git.repos.uploadReleaseAsset({ url: assetUrl,
url: assetUrl, headers: {
headers: { "content-length": contentLength,
"content-length": contentLength, "content-type": contentType
"content-type": contentType },
}, file: file,
file: file, name: name
name: name
});
}); });
} }
} }

35
node_modules/.yarn-integrity generated vendored
View File

@@ -1,5 +1,5 @@
{ {
"systemParams": "darwin-x64-72", "systemParams": "darwin-x64-79",
"modulesFolders": [ "modulesFolders": [
"node_modules" "node_modules"
], ],
@@ -646,11 +646,36 @@
"files": [], "files": [],
"artifacts": { "artifacts": {
"fsevents@1.2.9": [ "fsevents@1.2.9": [
"lib", "build",
"lib/binding", "build/.target.mk",
"build/Makefile",
"build/Release",
"build/Release/.deps",
"build/Release/.deps/Release",
"build/Release/.deps/Release/.node.d",
"build/Release/.deps/Release/fse.node.d",
"build/Release/.deps/Release/obj.target",
"build/Release/.deps/Release/obj.target/action_after_build.stamp.d",
"build/Release/.deps/Release/obj.target/fse",
"build/Release/.deps/Release/obj.target/fse/fsevents.o.d",
"build/Release/.deps/Users",
"build/Release/.deps/Users/nickcipollo",
"build/Release/.deps/Users/nickcipollo/src",
"build/Release/.deps/Users/nickcipollo/src/release-action",
"build/Release/.node",
"build/Release/fse.node",
"build/Release/obj.target",
"build/Release/obj.target/action_after_build.stamp",
"build/Release/obj.target/fse",
"build/Release/obj.target/fse/fsevents.o",
"build/action_after_build.target.mk",
"build/binding.Makefile",
"build/config.gypi",
"build/fse.target.mk",
"build/gyp-mac-tool",
"lib/binding/Release", "lib/binding/Release",
"lib/binding/Release/node-v72-darwin-x64", "lib/binding/Release/node-v79-darwin-x64",
"lib/binding/Release/node-v72-darwin-x64/fse.node" "lib/binding/Release/node-v79-darwin-x64/fse.node"
] ]
} }
} }

View File

@@ -2,53 +2,58 @@ import * as core from '@actions/core';
import { Artifact } from "./Artifact"; import { Artifact } from "./Artifact";
import { Releases } from "./Releases"; import { Releases } from "./Releases";
import { ReposListAssetsForReleaseResponseItem } from "@octokit/rest"; import { ReposListAssetsForReleaseResponseItem } from "@octokit/rest";
import { ErrorMessage } from './ErrorMessage';
export interface ArtifactUploader { export interface ArtifactUploader {
uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<void> uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<void>
} }
export class GithubArtifactUploader implements ArtifactUploader { export class GithubArtifactUploader implements ArtifactUploader {
private releases: Releases constructor(
private replacesExistingArtifacts: boolean = true private releases: Releases,
private replacesExistingArtifacts: boolean = true,
constructor(releases: Releases, replacesExistingArtifacts: boolean) { ) {
this.releases = releases
this.replacesExistingArtifacts = replacesExistingArtifacts
} }
async uploadArtifacts(artifacts: Artifact[], async uploadArtifacts(artifacts: Artifact[],
releaseId: number, releaseId: number,
uploadUrl: string): Promise<void> { uploadUrl: string): Promise<void> {
if(this.replacesExistingArtifacts) { if (this.replacesExistingArtifacts) {
await this.deleteUpdatedArtifacts(artifacts, releaseId) await this.deleteUpdatedArtifacts(artifacts, releaseId)
} }
for (const artifact of artifacts) { for (const artifact of artifacts) {
try { await this.uploadArtifact(artifact, uploadUrl)
await this.releases.uploadArtifact(uploadUrl,
artifact.contentLength,
artifact.contentType,
artifact.readFile(),
artifact.name)
} catch (error) {
const message = `Failed to upload artifact ${artifact.name}. Does it already exist?`
core.warning(message)
}
} }
return Promise.resolve()
} }
async deleteUpdatedArtifacts(artifacts: Artifact[], releaseId: number) { private async uploadArtifact(artifact: Artifact, uploadUrl: string, retry = 3) {
const response = await this.releases.listArtifactsForRelease(releaseId) try {
core.debug(`Uploading artifact ${artifact.name}...`)
await this.releases.uploadArtifact(uploadUrl,
artifact.contentLength,
artifact.contentType,
artifact.readFile(),
artifact.name)
} catch (error) {
if (error.status >= 500 && retry > 0) {
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`)
await this.uploadArtifact(artifact, uploadUrl, retry - 1)
} else {
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}.`)
}
}
}
private async deleteUpdatedArtifacts(artifacts: Artifact[], releaseId: number): Promise<void> {
const response = await this.releases.listArtifactsForRelease(releaseId)
const releaseAssets = response.data const releaseAssets = response.data
const assetByName = new Map<string, ReposListAssetsForReleaseResponseItem>() const assetByName: Record<string, ReposListAssetsForReleaseResponseItem> = {}
releaseAssets.forEach(asset => { releaseAssets.forEach(asset => {
assetByName[asset.name] = asset assetByName[asset.name] = asset
}); });
for (const artifact of artifacts) { for (const artifact of artifacts) {
const asset = assetByName[artifact.name] const asset = assetByName[artifact.name]
if (asset) { if (asset) {
core.debug(`Deleting existing artifact ${artifact.name}...`)
await this.releases.deleteArtifact(asset.id) await this.releases.deleteArtifact(asset.id)
} }
} }

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
/* Basic Options */ /* Basic Options */
// "incremental": true, /* Enable incremental compilation */ // "incremental": true, /* Enable incremental compilation */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "allowJs": true, /* Allow javascript files to be compiled. */ // "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */ // "checkJs": true, /* Report errors in .js files. */