Fixes #1 Allow for multiple artifacts
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
This action will create a github release and optionally upload an artifact to it.
|
||||
|
||||
## Action Inputs
|
||||
- **artifact**: A path to an optional artifact to upload to the release.
|
||||
- **artifact**: '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).
|
||||
- **artifacts**: '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).
|
||||
- **artifactContentType**: The content type of the artifact. Defaults to raw.
|
||||
- **body**: An optional body for the release.
|
||||
- **bodyFile**: An optional body file for the release. This should be the path to the file.
|
||||
@@ -32,7 +33,7 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifact: "release.tar.gz"
|
||||
artifacts: "release.tar.gz,foo/*.txt"
|
||||
bodyFile: "body.md"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { Action } from "../src/Action";
|
||||
import { Artifact } from "../src/Artifact";
|
||||
import { Inputs } from "../src/Inputs";
|
||||
import { Releases } from "../src/Releases";
|
||||
import { ArtifactUploader } from "../src/ArtifactUploader";
|
||||
|
||||
const createMock = jest.fn()
|
||||
const uploadMock = jest.fn()
|
||||
|
||||
const artifactPath = 'a/path'
|
||||
const artifactName = 'path'
|
||||
const artifactData = Buffer.from('blob','utf-8')
|
||||
const artifacts = [
|
||||
new Artifact('a/art1'),
|
||||
new Artifact('b/art2')
|
||||
]
|
||||
const artifactData = Buffer.from('blob', 'utf-8')
|
||||
const body = 'body'
|
||||
const commit = 'commit'
|
||||
const contentType = "raw"
|
||||
@@ -44,7 +48,7 @@ describe("Action", () => {
|
||||
await action.perform()
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).toBeCalledWith(url, contentLength, contentType, artifactData, 'path')
|
||||
expect(uploadMock).toBeCalledWith(artifacts, url)
|
||||
})
|
||||
|
||||
it('throws error when create fails', async () => {
|
||||
@@ -79,28 +83,25 @@ describe("Action", () => {
|
||||
}
|
||||
|
||||
expect(createMock).toBeCalledWith(tag, body, commit, draft, name)
|
||||
expect(uploadMock).toBeCalledWith(url, contentLength, contentType, artifactData, 'path')
|
||||
expect(uploadMock).toBeCalledWith(artifacts, url)
|
||||
})
|
||||
|
||||
function createAction(hasArtifact: boolean): Action {
|
||||
let artifact: string
|
||||
let inputArtifact: Artifact[]
|
||||
if (hasArtifact) {
|
||||
artifact = artifactPath
|
||||
inputArtifact = artifacts
|
||||
} else {
|
||||
artifact = ''
|
||||
inputArtifact = []
|
||||
}
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
return {
|
||||
create: createMock,
|
||||
uploadArtifact: uploadMock
|
||||
uploadArtifact: jest.fn()
|
||||
}
|
||||
})
|
||||
const MockInputs = jest.fn<Inputs, any>(() => {
|
||||
return {
|
||||
artifact: artifact,
|
||||
artifactName: artifactName,
|
||||
artifactContentType: contentType,
|
||||
artifactContentLength: contentLength,
|
||||
artifacts: inputArtifact,
|
||||
body: body,
|
||||
commit: commit,
|
||||
draft: draft,
|
||||
@@ -110,9 +111,16 @@ describe("Action", () => {
|
||||
readArtifact: () => artifactData
|
||||
}
|
||||
})
|
||||
const MockUploader = jest.fn<ArtifactUploader, any>(() => {
|
||||
return {
|
||||
uploadArtifacts: uploadMock
|
||||
}
|
||||
})
|
||||
|
||||
const inputs = new MockInputs()
|
||||
const releases = new MockReleases()
|
||||
const uploader = new MockUploader()
|
||||
|
||||
return new Action(inputs, releases)
|
||||
return new Action(inputs, releases, uploader)
|
||||
}
|
||||
})
|
||||
38
__tests__/Artifact.test.ts
Normal file
38
__tests__/Artifact.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Artifact } from "../src/Artifact";
|
||||
|
||||
const fileContents = Buffer.from('artful facts', 'utf-8')
|
||||
const contentLength = 42
|
||||
|
||||
jest.mock('fs', () => {
|
||||
return {
|
||||
readFileSync: () => fileContents,
|
||||
statSync: () => { return { size: contentLength } }
|
||||
};
|
||||
})
|
||||
|
||||
describe("Artifact", () => {
|
||||
it('defaults contentType to raw', () => {
|
||||
const artifact = new Artifact('')
|
||||
expect(artifact.contentType).toBe('raw')
|
||||
})
|
||||
|
||||
it('generates name from path', () => {
|
||||
const artifact = new Artifact('some/artifact')
|
||||
expect(artifact.name).toBe('artifact')
|
||||
})
|
||||
|
||||
it('provides contentLength', () => {
|
||||
const artifact = new Artifact('some/artifact')
|
||||
expect(artifact.contentLength).toBe(contentLength)
|
||||
})
|
||||
|
||||
it('provides path', () => {
|
||||
const artifact = new Artifact('some/artifact')
|
||||
expect(artifact.path).toBe('some/artifact')
|
||||
})
|
||||
|
||||
it('reads artifact', () => {
|
||||
const artifact = new Artifact('some/artifact')
|
||||
expect(artifact.readFile()).toBe(fileContents)
|
||||
})
|
||||
})
|
||||
39
__tests__/ArtifactGlobber.test.ts
Normal file
39
__tests__/ArtifactGlobber.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { FileArtifactGlobber } from "../src/ArtifactGlobber"
|
||||
import { Globber } from "../src/Globber";
|
||||
import { Artifact } from "../src/Artifact";
|
||||
|
||||
const contentType = "raw"
|
||||
const globResults = ["file1", "file2"]
|
||||
|
||||
describe("ArtifactGlobber", () => {
|
||||
it("globs simple path", () => {
|
||||
const globber = createArtifactGlobber()
|
||||
|
||||
const expectedArtifacts =
|
||||
globResults.map((path) => new Artifact(path, contentType))
|
||||
|
||||
expect(globber.globArtifactString('path', 'raw'))
|
||||
.toEqual(expectedArtifacts)
|
||||
})
|
||||
|
||||
it("splits multiple paths", () => {
|
||||
const globber = createArtifactGlobber()
|
||||
|
||||
const expectedArtifacts =
|
||||
globResults
|
||||
.concat(globResults)
|
||||
.map((path) => new Artifact(path, contentType))
|
||||
|
||||
expect(globber.globArtifactString('path1,path2', 'raw'))
|
||||
.toEqual(expectedArtifacts)
|
||||
})
|
||||
|
||||
function createArtifactGlobber(): FileArtifactGlobber {
|
||||
const MockGlobber = jest.fn<Globber, any>(() => {
|
||||
return {
|
||||
glob: () => globResults
|
||||
}
|
||||
})
|
||||
return new FileArtifactGlobber(new MockGlobber())
|
||||
}
|
||||
})
|
||||
43
__tests__/ArtifactUploader.test.ts
Normal file
43
__tests__/ArtifactUploader.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Artifact } from "../src/Artifact"
|
||||
import { GithubArtifactUploader } from "../src/ArtifactUploader"
|
||||
import { Releases } from "../src/Releases";
|
||||
|
||||
const artifacts = [
|
||||
new Artifact('a/art1'),
|
||||
new Artifact('b/art2')
|
||||
]
|
||||
const fileContents = Buffer.from('artful facts', 'utf-8')
|
||||
const contentLength = 42
|
||||
const uploadMock = jest.fn()
|
||||
const url = 'http://api.example.com'
|
||||
|
||||
jest.mock('fs', () => {
|
||||
return {
|
||||
readFileSync: () => fileContents,
|
||||
statSync: () => { return { size: contentLength } }
|
||||
};
|
||||
})
|
||||
|
||||
describe('ArtifactUploader', () => {
|
||||
it('uploads artifacts', () => {
|
||||
const uploader = createUploader()
|
||||
|
||||
uploader.uploadArtifacts(artifacts, url)
|
||||
|
||||
expect(uploadMock).toBeCalledTimes(2)
|
||||
expect(uploadMock)
|
||||
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1')
|
||||
expect(uploadMock)
|
||||
.toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2')
|
||||
})
|
||||
|
||||
function createUploader(): GithubArtifactUploader {
|
||||
const MockReleases = jest.fn<Releases, any>(() => {
|
||||
return {
|
||||
create: jest.fn(),
|
||||
uploadArtifact: uploadMock
|
||||
}
|
||||
})
|
||||
return new GithubArtifactUploader(new MockReleases())
|
||||
}
|
||||
});
|
||||
@@ -1,89 +1,100 @@
|
||||
const mockGetInput = jest.fn();
|
||||
const mockGlob = jest.fn()
|
||||
const mockReadFileSync = jest.fn();
|
||||
const mockStatSync = jest.fn();
|
||||
|
||||
import { Inputs, CoreInputs } from "../src/Inputs";
|
||||
import { Artifact } from "../src/Artifact";
|
||||
import { ArtifactGlobber } from "../src/ArtifactGlobber";
|
||||
import { Context } from "@actions/github/lib/context";
|
||||
import { Inputs, CoreInputs } from "../src/Inputs";
|
||||
|
||||
const artifacts = [
|
||||
new Artifact('a/art1'),
|
||||
new Artifact('b/art2')
|
||||
]
|
||||
|
||||
jest.mock('@actions/core', () => {
|
||||
return { getInput: mockGetInput };
|
||||
})
|
||||
|
||||
jest.mock('fs', () => {
|
||||
return {
|
||||
return {
|
||||
readFileSync: mockReadFileSync,
|
||||
statSync: mockStatSync
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
describe('Inputs', () => {
|
||||
let context: Context;
|
||||
let inputs: Inputs;
|
||||
beforeEach(() => {
|
||||
mockGetInput.mockReturnValue(null)
|
||||
mockGetInput.mockReset()
|
||||
context = new Context()
|
||||
inputs = new CoreInputs(context)
|
||||
})
|
||||
|
||||
it('reads artifact', () => {
|
||||
mockGetInput.mockReturnValue("a/path")
|
||||
mockReadFileSync.mockReturnValue('file')
|
||||
expect(inputs.artifact).toBe("a/path")
|
||||
})
|
||||
|
||||
it('returns artifact', () => {
|
||||
mockGetInput.mockReturnValue("a/path")
|
||||
expect(inputs.artifact).toBe("a/path")
|
||||
})
|
||||
|
||||
it('returns artifactContentLength', () => {
|
||||
mockGetInput.mockReturnValue("a/path")
|
||||
mockStatSync.mockReturnValue({size: 100})
|
||||
expect(inputs.artifactContentLength).toBe(100)
|
||||
})
|
||||
|
||||
it('returns artifactName', () => {
|
||||
mockGetInput.mockReturnValue("a/path")
|
||||
expect(inputs.artifactName).toBe("path")
|
||||
inputs = new CoreInputs(createGlobber(), context)
|
||||
})
|
||||
|
||||
it('returns targetCommit', () => {
|
||||
mockGetInput.mockReturnValue("42")
|
||||
expect(inputs.commit).toBe("42")
|
||||
mockGetInput.mockReturnValue('42')
|
||||
expect(inputs.commit).toBe('42')
|
||||
})
|
||||
|
||||
it('returns token', () => {
|
||||
mockGetInput.mockReturnValue("42")
|
||||
expect(inputs.token).toBe("42")
|
||||
mockGetInput.mockReturnValue('42')
|
||||
expect(inputs.token).toBe('42')
|
||||
})
|
||||
|
||||
describe('artifactContentType', () => {
|
||||
it('returns input content-type', () => {
|
||||
mockGetInput.mockReturnValue("type")
|
||||
expect(inputs.artifactContentType).toBe("type")
|
||||
describe('artifacts', () => {
|
||||
it('returns empty artifacts', () => {
|
||||
mockGetInput.mockReturnValueOnce('')
|
||||
.mockReturnValueOnce('')
|
||||
|
||||
expect(inputs.artifacts).toEqual([])
|
||||
expect(mockGlob).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it('returns raw as default', () => {
|
||||
expect(inputs.artifactContentType).toBe("raw")
|
||||
it('returns input.artifacts', () => {
|
||||
mockGetInput.mockReturnValueOnce('art1')
|
||||
.mockReturnValueOnce('contentType')
|
||||
|
||||
expect(inputs.artifacts).toEqual(artifacts)
|
||||
expect(mockGlob).toBeCalledTimes(1)
|
||||
expect(mockGlob).toBeCalledWith('art1', 'contentType')
|
||||
})
|
||||
|
||||
it('returns input.artifacts with default contentType', () => {
|
||||
mockGetInput.mockReturnValueOnce('art1')
|
||||
|
||||
expect(inputs.artifacts).toEqual(artifacts)
|
||||
expect(mockGlob).toBeCalledTimes(1)
|
||||
expect(mockGlob).toBeCalledWith('art1', 'raw')
|
||||
})
|
||||
|
||||
it('returns input.artifact', () => {
|
||||
mockGetInput.mockReturnValueOnce('')
|
||||
.mockReturnValueOnce('art2')
|
||||
.mockReturnValueOnce('contentType')
|
||||
|
||||
expect(inputs.artifacts).toEqual(artifacts)
|
||||
expect(mockGlob).toBeCalledTimes(1)
|
||||
expect(mockGlob).toBeCalledWith('art2', 'contentType')
|
||||
})
|
||||
})
|
||||
|
||||
describe('body', () => {
|
||||
it('returns input body', () => {
|
||||
mockGetInput.mockReturnValue("body")
|
||||
expect(inputs.body).toBe("body")
|
||||
mockGetInput.mockReturnValue('body')
|
||||
expect(inputs.body).toBe('body')
|
||||
})
|
||||
|
||||
it('returns body file contents', () => {
|
||||
mockGetInput.mockReturnValueOnce("").mockReturnValueOnce("a/path")
|
||||
mockReadFileSync.mockReturnValue("file")
|
||||
mockGetInput.mockReturnValueOnce('').mockReturnValueOnce('a/path')
|
||||
mockReadFileSync.mockReturnValue('file')
|
||||
|
||||
expect(inputs.body).toBe("file")
|
||||
expect(inputs.body).toBe('file')
|
||||
})
|
||||
|
||||
it('returns empty', () => {
|
||||
expect(inputs.body).toBe("")
|
||||
expect(inputs.body).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -91,43 +102,53 @@ describe('Inputs', () => {
|
||||
it('returns false', () => {
|
||||
expect(inputs.draft).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
it('returns true', () => {
|
||||
mockGetInput.mockReturnValue("true")
|
||||
mockGetInput.mockReturnValue('true')
|
||||
expect(inputs.draft).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('name', () => {
|
||||
it('returns input name', () => {
|
||||
mockGetInput.mockReturnValue("name")
|
||||
expect(inputs.name).toBe("name")
|
||||
mockGetInput.mockReturnValue('name')
|
||||
expect(inputs.name).toBe('name')
|
||||
})
|
||||
|
||||
it('returns tag', () => {
|
||||
mockGetInput.mockReturnValue("")
|
||||
context.ref = "refs/tags/sha-tag"
|
||||
expect(inputs.name).toBe("sha-tag")
|
||||
mockGetInput.mockReturnValue('')
|
||||
context.ref = 'refs/tags/sha-tag'
|
||||
expect(inputs.name).toBe('sha-tag')
|
||||
})
|
||||
})
|
||||
|
||||
describe('tag', () => {
|
||||
it('returns input tag', () => {
|
||||
mockGetInput.mockReturnValue("tag")
|
||||
expect(inputs.tag).toBe("tag")
|
||||
mockGetInput.mockReturnValue('tag')
|
||||
expect(inputs.tag).toBe('tag')
|
||||
})
|
||||
it('returns context sha when input is empty', () => {
|
||||
mockGetInput.mockReturnValue("")
|
||||
context.ref = "refs/tags/sha-tag"
|
||||
expect(inputs.tag).toBe("sha-tag")
|
||||
mockGetInput.mockReturnValue('')
|
||||
context.ref = 'refs/tags/sha-tag'
|
||||
expect(inputs.tag).toBe('sha-tag')
|
||||
})
|
||||
it('returns context sha when input is null', () => {
|
||||
mockGetInput.mockReturnValue(null)
|
||||
context.ref = "refs/tags/sha-tag"
|
||||
expect(inputs.tag).toBe("sha-tag")
|
||||
context.ref = 'refs/tags/sha-tag'
|
||||
expect(inputs.tag).toBe('sha-tag')
|
||||
})
|
||||
it('throws if no tag', () => {
|
||||
expect(() => inputs.tag).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
function createGlobber(): ArtifactGlobber {
|
||||
const MockGlobber = jest.fn<ArtifactGlobber, any>(() => {
|
||||
return {
|
||||
globArtifactString: mockGlob
|
||||
}
|
||||
})
|
||||
mockGlob.mockImplementation(() => artifacts)
|
||||
return new MockGlobber()
|
||||
}
|
||||
})
|
||||
@@ -3,8 +3,11 @@ description: 'Creates github releases'
|
||||
author: 'Nick Cipollo'
|
||||
inputs:
|
||||
artifact:
|
||||
description: 'A path to an optional artifact to upload to the release.'
|
||||
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: ''
|
||||
artifacts:
|
||||
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: ''
|
||||
artifactContentType:
|
||||
description: 'The content type of the artifact. Defaults to raw'
|
||||
default: ''
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Contributors
|
||||
|
||||
### Checkin
|
||||
|
||||
- Do checkin source (src)
|
||||
- Do checkin build output (lib)
|
||||
- Do checkin runtime node_modules
|
||||
- Do not checkin devDependency node_modules (husky can help see below)
|
||||
|
||||
### devDependencies
|
||||
|
||||
In order to handle correctly checking in node_modules without devDependencies, we run [Husky](https://github.com/typicode/husky) before each commit.
|
||||
This step ensures that formatting and checkin rules are followed and that devDependencies are excluded. To make sure Husky runs correctly, please use the following workflow:
|
||||
|
||||
```
|
||||
npm install # installs all devDependencies including Husky
|
||||
git add abc.ext # Add the files you've changed. This should include files in src, lib, and node_modules (see above)
|
||||
git commit -m "Informative commit message" # Commit. This will run Husky
|
||||
```
|
||||
|
||||
During the commit step, Husky will take care of formatting all files with [Prettier](https://github.com/prettier/prettier) as well as pruning out devDependencies using `npm prune --production`.
|
||||
It will also make sure these changes are appropriately included in your commit (no further work is needed)
|
||||
@@ -9,16 +9,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class Action {
|
||||
constructor(inputs, releases) {
|
||||
constructor(inputs, releases, uploader) {
|
||||
this.inputs = inputs;
|
||||
this.releases = releases;
|
||||
this.uploader = uploader;
|
||||
}
|
||||
perform() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const createResult = yield this.releases.create(this.inputs.tag, this.inputs.body, this.inputs.commit, this.inputs.draft, this.inputs.name);
|
||||
if (this.inputs.artifact) {
|
||||
const artifactData = this.inputs.readArtifact();
|
||||
yield this.releases.uploadArtifact(createResult.data.upload_url, this.inputs.artifactContentLength, this.inputs.artifactContentType, artifactData, this.inputs.artifactName);
|
||||
const artifacts = this.inputs.artifacts;
|
||||
if (artifacts.length > 0) {
|
||||
yield this.uploader.uploadArtifacts(artifacts, createResult.data.upload_url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
18
lib/Artifact.js
Normal file
18
lib/Artifact.js
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const path_1 = require("path");
|
||||
const fs_1 = require("fs");
|
||||
class Artifact {
|
||||
constructor(path, contentType = "raw") {
|
||||
this.path = path;
|
||||
this.name = path_1.basename(path);
|
||||
this.contentType = contentType;
|
||||
}
|
||||
get contentLength() {
|
||||
return fs_1.statSync(this.path).size;
|
||||
}
|
||||
readFile() {
|
||||
return fs_1.readFileSync(this.path);
|
||||
}
|
||||
}
|
||||
exports.Artifact = Artifact;
|
||||
16
lib/ArtifactGlobber.js
Normal file
16
lib/ArtifactGlobber.js
Normal file
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Globber_1 = require("./Globber");
|
||||
const Artifact_1 = require("./Artifact");
|
||||
class FileArtifactGlobber {
|
||||
constructor(globber = new Globber_1.FileGlobber()) {
|
||||
this.globber = globber;
|
||||
}
|
||||
globArtifactString(artifact, contentType) {
|
||||
return artifact.split(',')
|
||||
.map((path) => this.globber.glob(path))
|
||||
.reduce((accumulated, current) => accumulated.concat(current))
|
||||
.map((path) => new Artifact_1.Artifact(path, contentType));
|
||||
}
|
||||
}
|
||||
exports.FileArtifactGlobber = FileArtifactGlobber;
|
||||
23
lib/ArtifactUploader.js
Normal file
23
lib/ArtifactUploader.js
Normal file
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class GithubArtifactUploader {
|
||||
constructor(releases) {
|
||||
this.releases = releases;
|
||||
}
|
||||
uploadArtifacts(artifacts, uploadUrl) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
artifacts.forEach((artifact) => __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.releases.uploadArtifact(uploadUrl, artifact.contentLength, artifact.contentType, artifact.readFile(), artifact.name);
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.GithubArtifactUploader = GithubArtifactUploader;
|
||||
9
lib/Globber.js
Normal file
9
lib/Globber.js
Normal file
@@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const glob_1 = require("glob");
|
||||
class FileGlobber {
|
||||
glob(pattern) {
|
||||
return new glob_1.GlobSync(pattern, { mark: true }).found;
|
||||
}
|
||||
}
|
||||
exports.FileGlobber = FileGlobber;
|
||||
@@ -9,26 +9,25 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const fs_1 = require("fs");
|
||||
const path_1 = require("path");
|
||||
class CoreInputs {
|
||||
constructor(context) {
|
||||
constructor(artifactGlobber, context) {
|
||||
this.artifactGlobber = artifactGlobber;
|
||||
this.context = context;
|
||||
}
|
||||
get artifact() {
|
||||
return core.getInput('artifact');
|
||||
}
|
||||
get artifactName() {
|
||||
return path_1.basename(this.artifact);
|
||||
}
|
||||
get artifactContentType() {
|
||||
const type = core.getInput('artifactContentType');
|
||||
if (type) {
|
||||
return type;
|
||||
get artifacts() {
|
||||
let artifacts = core.getInput('artifacts');
|
||||
if (!artifacts) {
|
||||
artifacts = core.getInput('artifacts');
|
||||
}
|
||||
return 'raw';
|
||||
}
|
||||
get artifactContentLength() {
|
||||
return fs_1.statSync(this.artifact).size;
|
||||
if (artifacts) {
|
||||
let contentType = core.getInput('artifactContentType');
|
||||
if (!contentType) {
|
||||
contentType = 'raw';
|
||||
}
|
||||
return this.artifactGlobber
|
||||
.globArtifactString(artifacts, contentType);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
get body() {
|
||||
const body = core.getInput('body');
|
||||
@@ -70,9 +69,6 @@ class CoreInputs {
|
||||
get token() {
|
||||
return core.getInput('token', { required: true });
|
||||
}
|
||||
readArtifact() {
|
||||
return fs_1.readFileSync(this.artifact);
|
||||
}
|
||||
stringFromFile(path) {
|
||||
return fs_1.readFileSync(path, 'utf-8');
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ const core = __importStar(require("@actions/core"));
|
||||
const Inputs_1 = require("./Inputs");
|
||||
const Releases_1 = require("./Releases");
|
||||
const Action_1 = require("./Action");
|
||||
const ArtifactUploader_1 = require("./ArtifactUploader");
|
||||
const ArtifactGlobber_1 = require("./ArtifactGlobber");
|
||||
function run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
@@ -35,8 +37,10 @@ function createAction() {
|
||||
const token = core.getInput('token');
|
||||
const context = github.context;
|
||||
const git = new github.GitHub(token);
|
||||
const globber = new ArtifactGlobber_1.FileArtifactGlobber();
|
||||
const inputs = new Inputs_1.CoreInputs(globber, context);
|
||||
const releases = new Releases_1.GithubReleases(context, git);
|
||||
const inputs = new Inputs_1.CoreInputs(context);
|
||||
return new Action_1.Action(inputs, releases);
|
||||
const uploader = new ArtifactUploader_1.GithubArtifactUploader(releases);
|
||||
return new Action_1.Action(inputs, releases, uploader);
|
||||
}
|
||||
run();
|
||||
|
||||
@@ -21,9 +21,11 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.0.0",
|
||||
"@actions/github": "^1.0.0"
|
||||
"@actions/github": "^1.0.0",
|
||||
"glob": "^7.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/node": "^12.0.4",
|
||||
"jest": "^24.8.0",
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Inputs } from "./Inputs";
|
||||
import { Releases } from "./Releases";
|
||||
import { basename } from "path";
|
||||
import { readFileSync } from "fs";
|
||||
import { ArtifactUploader } from "./ArtifactUploader";
|
||||
|
||||
export class Action {
|
||||
private inputs: Inputs
|
||||
private releases: Releases
|
||||
private uploader: ArtifactUploader
|
||||
|
||||
constructor(inputs: Inputs, releases: Releases) {
|
||||
constructor(inputs: Inputs, releases: Releases, uploader: ArtifactUploader) {
|
||||
this.inputs = inputs
|
||||
this.releases = releases
|
||||
this.uploader = uploader
|
||||
}
|
||||
|
||||
async perform() {
|
||||
@@ -21,14 +22,11 @@ export class Action {
|
||||
this.inputs.name
|
||||
)
|
||||
|
||||
if (this.inputs.artifact) {
|
||||
const artifactData = this.inputs.readArtifact()
|
||||
await this.releases.uploadArtifact(
|
||||
createResult.data.upload_url,
|
||||
this.inputs.artifactContentLength,
|
||||
this.inputs.artifactContentType,
|
||||
artifactData,
|
||||
this.inputs.artifactName
|
||||
const artifacts = this.inputs.artifacts
|
||||
if (artifacts.length > 0) {
|
||||
await this.uploader.uploadArtifacts(
|
||||
artifacts,
|
||||
createResult.data.upload_url
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
22
src/Artifact.ts
Normal file
22
src/Artifact.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { basename } from "path";
|
||||
import { readFileSync, statSync } from "fs";
|
||||
|
||||
export class Artifact {
|
||||
readonly contentType: string
|
||||
readonly name: string
|
||||
readonly path: string
|
||||
|
||||
constructor(path: string, contentType: string = "raw") {
|
||||
this.path = path
|
||||
this.name = basename(path)
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
get contentLength(): number {
|
||||
return statSync(this.path).size
|
||||
}
|
||||
|
||||
readFile(): Buffer {
|
||||
return readFileSync(this.path)
|
||||
}
|
||||
}
|
||||
22
src/ArtifactGlobber.ts
Normal file
22
src/ArtifactGlobber.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Globber, FileGlobber } from "./Globber";
|
||||
import { Artifact } from "./Artifact";
|
||||
|
||||
export interface ArtifactGlobber {
|
||||
globArtifactString(artifact: string, contentType: string): Artifact[]
|
||||
}
|
||||
|
||||
export class FileArtifactGlobber implements ArtifactGlobber {
|
||||
private globber: Globber
|
||||
|
||||
constructor(globber: Globber = new FileGlobber()) {
|
||||
this.globber = globber
|
||||
}
|
||||
|
||||
globArtifactString(artifact: string, contentType: string): Artifact[] {
|
||||
return artifact.split(',')
|
||||
.map((path) => this.globber.glob(path))
|
||||
.reduce((accumulated, current) => accumulated.concat(current))
|
||||
.map((path) => new Artifact(path, contentType))
|
||||
}
|
||||
}
|
||||
|
||||
24
src/ArtifactUploader.ts
Normal file
24
src/ArtifactUploader.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Artifact } from "./Artifact";
|
||||
import { Releases } from "./Releases";
|
||||
|
||||
export interface ArtifactUploader {
|
||||
uploadArtifacts(artifacts: Artifact[], uploadUrl: string): void
|
||||
}
|
||||
|
||||
export class GithubArtifactUploader implements ArtifactUploader {
|
||||
private releases: Releases
|
||||
|
||||
constructor(releases: Releases) {
|
||||
this.releases = releases
|
||||
}
|
||||
|
||||
async uploadArtifacts(artifacts: Artifact[], uploadUrl: string) {
|
||||
artifacts.forEach(async artifact => {
|
||||
await this.releases.uploadArtifact(uploadUrl,
|
||||
artifact.contentLength,
|
||||
artifact.contentType,
|
||||
artifact.readFile(),
|
||||
artifact.name)
|
||||
});
|
||||
}
|
||||
}
|
||||
11
src/Globber.ts
Normal file
11
src/Globber.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { GlobSync } from "glob";
|
||||
|
||||
export interface Globber {
|
||||
glob(pattern: string): string[]
|
||||
}
|
||||
|
||||
export class FileGlobber implements Globber {
|
||||
glob(pattern: string): string[] {
|
||||
return new GlobSync(pattern, { mark: true }).found
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,42 @@
|
||||
import * as core from '@actions/core';
|
||||
import { Context } from "@actions/github/lib/context";
|
||||
import { readFileSync, statSync } from 'fs';
|
||||
import { basename } from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
import { ArtifactGlobber } from './ArtifactGlobber';
|
||||
import { Artifact } from './Artifact';
|
||||
|
||||
export interface Inputs {
|
||||
readonly artifact: string
|
||||
readonly artifactName: string
|
||||
readonly artifactContentType: string
|
||||
readonly artifactContentLength: number
|
||||
readonly artifacts: Artifact[]
|
||||
readonly body: string
|
||||
readonly commit: string
|
||||
readonly draft: boolean
|
||||
readonly name: string
|
||||
readonly tag: string
|
||||
readonly token: string
|
||||
|
||||
readArtifact(): Buffer
|
||||
}
|
||||
|
||||
export class CoreInputs implements Inputs {
|
||||
private artifactGlobber: ArtifactGlobber
|
||||
private context: Context
|
||||
|
||||
constructor(context: Context) {
|
||||
constructor(artifactGlobber: ArtifactGlobber, context: Context) {
|
||||
this.artifactGlobber = artifactGlobber
|
||||
this.context = context
|
||||
}
|
||||
|
||||
get artifact(): string {
|
||||
return core.getInput('artifact')
|
||||
}
|
||||
|
||||
get artifactName(): string {
|
||||
return basename(this.artifact)
|
||||
}
|
||||
|
||||
get artifactContentType(): string {
|
||||
const type = core.getInput('artifactContentType')
|
||||
if(type) {
|
||||
return type;
|
||||
get artifacts(): Artifact[] {
|
||||
let artifacts = core.getInput('artifacts')
|
||||
if (!artifacts) {
|
||||
artifacts = core.getInput('artifacts')
|
||||
}
|
||||
|
||||
return 'raw'
|
||||
}
|
||||
|
||||
get artifactContentLength(): number {
|
||||
return statSync(this.artifact).size
|
||||
if (artifacts) {
|
||||
let contentType = core.getInput('artifactContentType')
|
||||
if (!contentType) {
|
||||
contentType = 'raw'
|
||||
}
|
||||
return this.artifactGlobber
|
||||
.globArtifactString(artifacts, contentType)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
get body(): string {
|
||||
@@ -97,10 +90,6 @@ export class CoreInputs implements Inputs {
|
||||
return core.getInput('token', { required: true })
|
||||
}
|
||||
|
||||
readArtifact(): Buffer {
|
||||
return readFileSync(this.artifact)
|
||||
}
|
||||
|
||||
stringFromFile(path: string): string {
|
||||
return readFileSync(path, 'utf-8')
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import * as core from '@actions/core';
|
||||
import { CoreInputs } from './Inputs';
|
||||
import { GithubReleases } from './Releases';
|
||||
import { Action } from './Action';
|
||||
import { GithubArtifactUploader } from './ArtifactUploader';
|
||||
import { FileArtifactGlobber } from './ArtifactGlobber';
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
@@ -17,9 +19,12 @@ function createAction(): Action {
|
||||
const token = core.getInput('token')
|
||||
const context = github.context
|
||||
const git = new github.GitHub(token)
|
||||
const globber = new FileArtifactGlobber()
|
||||
|
||||
const inputs = new CoreInputs(globber, context)
|
||||
const releases = new GithubReleases(context, git)
|
||||
const inputs = new CoreInputs(context)
|
||||
return new Action(inputs, releases)
|
||||
const uploader = new GithubArtifactUploader(releases)
|
||||
return new Action(inputs, releases, uploader)
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
26
yarn.lock
26
yarn.lock
@@ -391,6 +391,20 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/events@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
|
||||
|
||||
"@types/glob@^7.1.1":
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
|
||||
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
|
||||
dependencies:
|
||||
"@types/events" "*"
|
||||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
|
||||
@@ -423,6 +437,16 @@
|
||||
dependencies:
|
||||
"@types/jest-diff" "*"
|
||||
|
||||
"@types/minimatch@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||
|
||||
"@types/node@*":
|
||||
version "12.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.3.tgz#27b3f40addaf2f580459fdb405222685542f907a"
|
||||
integrity sha512-3SiLAIBkDWDg6vFo0+5YJyHPWU9uwu40Qe+v+0MH8wRKYBimHvvAOyk3EzMrD/TrIlLYfXrqDqrg913PynrMJQ==
|
||||
|
||||
"@types/node@^12.0.4":
|
||||
version "12.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.2.tgz#c4e63af5e8823ce9cc3f0b34f7b998c2171f0c44"
|
||||
@@ -1346,7 +1370,7 @@ getpass@^0.1.1:
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
|
||||
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
|
||||
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
|
||||
|
||||
Reference in New Issue
Block a user