diff --git a/__tests__/ArtifactGlobber.test.ts b/__tests__/ArtifactGlobber.test.ts index 7e510da..b2b3080 100644 --- a/__tests__/ArtifactGlobber.test.ts +++ b/__tests__/ArtifactGlobber.test.ts @@ -13,6 +13,18 @@ jest.mock('@actions/core', () => { return {warning: warnMock}; }) +jest.mock('fs', () => { + return { + statSync: () => { + return { + isDirectory(): boolean { + return false + } + } + } + }; +}) + describe("ArtifactGlobber", () => { beforeEach(() => { globMock.mockClear() diff --git a/__tests__/ArtifactPathValidator.test.ts b/__tests__/ArtifactPathValidator.test.ts new file mode 100644 index 0000000..9eba3a8 --- /dev/null +++ b/__tests__/ArtifactPathValidator.test.ts @@ -0,0 +1,60 @@ +const directoryMock = jest.fn() +const warnMock = jest.fn() + +import {ArtifactPathValidator} from "../src/ArtifactPathValidator"; + +const pattern = 'pattern' + +jest.mock('@actions/core', () => { + return {warning: warnMock}; +}) + +jest.mock('fs', () => { + return { + statSync: () => { + return {isDirectory: directoryMock} + } + }; +}) + +describe("ArtifactPathValidator", () => { + beforeEach(() => { + warnMock.mockClear() + directoryMock.mockClear() + }) + + it("warns and filters out path which points to a directory", () => { + const paths = ['path1', 'path2'] + directoryMock.mockReturnValueOnce(true).mockReturnValueOnce(false) + + const validator = new ArtifactPathValidator(false, paths, pattern) + + const result = validator.validate() + expect(warnMock).toBeCalled() + expect(result).toEqual(['path2']) + }) + + it("warns when no glob results are produced and empty results shouldn't throw", () => { + const validator = new ArtifactPathValidator(false, [], pattern) + const result = validator.validate() + expect(warnMock).toBeCalled() + }) + + it("throws when no glob results are produced and empty results shouild throw", () => { + const validator = new ArtifactPathValidator(true, [], pattern) + expect(() => { + validator.validate() + }).toThrow() + }) + + it("throws when path points to directory", () => { + const paths = ['path1', 'path2'] + directoryMock.mockReturnValueOnce(true).mockReturnValueOnce(false) + + const validator = new ArtifactPathValidator(true, paths, pattern) + + expect(() => { + validator.validate() + }).toThrow() + }) +}) \ No newline at end of file diff --git a/__tests__/Integration.test.ts b/__tests__/Integration.test.ts index fd00962..093d09b 100644 --- a/__tests__/Integration.test.ts +++ b/__tests__/Integration.test.ts @@ -70,7 +70,7 @@ describe.skip('Integration Test', () => { function artifacts() { const globber = new FileArtifactGlobber() const artifactPath = path.join(__dirname, 'Integration.test.ts') - const artifactString = `~/Desktop/test.txt,blarg.tx, ${artifactPath}` + const artifactString = `~/Desktop,~/Desktop/test.txt,blarg.tx, ${artifactPath}` return globber.globArtifactString(artifactString, "raw", false) } diff --git a/lib/ArtifactGlobber.js b/lib/ArtifactGlobber.js index 5eda18e..685cdc7 100644 --- a/lib/ArtifactGlobber.js +++ b/lib/ArtifactGlobber.js @@ -27,28 +27,34 @@ const core = __importStar(require("@actions/core")); const Globber_1 = require("./Globber"); const Artifact_1 = require("./Artifact"); const untildify_1 = __importDefault(require("untildify")); +const ArtifactPathValidator_1 = require("./ArtifactPathValidator"); class FileArtifactGlobber { constructor(globber = new Globber_1.FileGlobber()) { this.globber = globber; } - globArtifactString(artifact, contentType, throwsWhenNoFiles) { + globArtifactString(artifact, contentType, errorsFailBuild) { return artifact.split(',') .map(path => FileArtifactGlobber.expandPath(path)) - .map(pattern => this.globPattern(pattern, throwsWhenNoFiles)) + .map(pattern => this.globPattern(pattern, errorsFailBuild)) + .map((globResult) => FileArtifactGlobber.validatePattern(errorsFailBuild, globResult[1], globResult[0])) .reduce((accumulated, current) => accumulated.concat(current)) .map(path => new Artifact_1.Artifact(path, contentType)); } - globPattern(pattern, throwsWhenNoFiles) { + globPattern(pattern, errorsFailBuild) { const paths = this.globber.glob(pattern); if (paths.length == 0) { - if (throwsWhenNoFiles) { + if (errorsFailBuild) { FileArtifactGlobber.throwGlobError(pattern); } else { FileArtifactGlobber.reportGlobWarning(pattern); } } - return paths; + return [pattern, paths]; + } + static validatePattern(errorsFailBuild, paths, pattern) { + const validator = new ArtifactPathValidator_1.ArtifactPathValidator(errorsFailBuild, paths, pattern); + return validator.validate(); } static reportGlobWarning(pattern) { core.warning(`Artifact pattern :${pattern} did not match any files`); diff --git a/lib/ArtifactPathValidator.js b/lib/ArtifactPathValidator.js new file mode 100644 index 0000000..a1b2ddd --- /dev/null +++ b/lib/ArtifactPathValidator.js @@ -0,0 +1,58 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ArtifactPathValidator = void 0; +const core = __importStar(require("@actions/core")); +const fs_1 = require("fs"); +class ArtifactPathValidator { + constructor(errorsFailBuild, paths, pattern) { + this.paths = paths; + this.pattern = pattern; + this.errorsFailBuild = errorsFailBuild; + } + validate() { + this.verifyPathsNotEmpty(); + return this.paths.filter((path) => this.verifyNotDirectory(path)); + } + verifyPathsNotEmpty() { + if (this.paths.length == 0) { + const message = `Artifact pattern :${this.pattern} did not match any files`; + this.reportError(message); + } + } + verifyNotDirectory(path) { + const isDir = fs_1.statSync(path).isDirectory(); + if (isDir) { + const message = `Artifact is a directory :${path}. Directories can not be uploaded to a release.`; + this.reportError(message); + } + return !isDir; + } + reportError(message) { + if (this.errorsFailBuild) { + throw Error(message); + } + else { + core.warning(message); + } + } +} +exports.ArtifactPathValidator = ArtifactPathValidator; diff --git a/src/ArtifactGlobber.ts b/src/ArtifactGlobber.ts index 19b7a41..824a331 100644 --- a/src/ArtifactGlobber.ts +++ b/src/ArtifactGlobber.ts @@ -2,9 +2,10 @@ import * as core from '@actions/core'; import {Globber, FileGlobber} from "./Globber"; import {Artifact} from "./Artifact"; import untildify from "untildify"; +import {ArtifactPathValidator} from "./ArtifactPathValidator"; export interface ArtifactGlobber { - globArtifactString(artifact: string, contentType: string, throwsWhenNoFiles: boolean): Artifact[] + globArtifactString(artifact: string, contentType: string, errorsFailBuild: boolean): Artifact[] } export class FileArtifactGlobber implements ArtifactGlobber { @@ -14,24 +15,30 @@ export class FileArtifactGlobber implements ArtifactGlobber { this.globber = globber } - globArtifactString(artifact: string, contentType: string, throwsWhenNoFiles: boolean): Artifact[] { + globArtifactString(artifact: string, contentType: string, errorsFailBuild: boolean): Artifact[] { return artifact.split(',') .map(path => FileArtifactGlobber.expandPath(path)) - .map(pattern => this.globPattern(pattern, throwsWhenNoFiles)) + .map(pattern => this.globPattern(pattern, errorsFailBuild)) + .map((globResult) => FileArtifactGlobber.validatePattern(errorsFailBuild, globResult[1], globResult[0])) .reduce((accumulated, current) => accumulated.concat(current)) .map(path => new Artifact(path, contentType)) } - private globPattern(pattern: string, throwsWhenNoFiles: boolean): string[] { + private globPattern(pattern: string, errorsFailBuild: boolean): [string, string[]] { const paths = this.globber.glob(pattern) if (paths.length == 0) { - if (throwsWhenNoFiles) { + if (errorsFailBuild) { FileArtifactGlobber.throwGlobError(pattern) } else { FileArtifactGlobber.reportGlobWarning(pattern) } } - return paths + return [pattern, paths] + } + + private static validatePattern(errorsFailBuild: boolean, paths: string[], pattern: string): string[] { + const validator = new ArtifactPathValidator(errorsFailBuild, paths, pattern) + return validator.validate() } private static reportGlobWarning(pattern: string) { diff --git a/src/ArtifactPathValidator.ts b/src/ArtifactPathValidator.ts new file mode 100644 index 0000000..d017437 --- /dev/null +++ b/src/ArtifactPathValidator.ts @@ -0,0 +1,43 @@ +import * as core from "@actions/core"; +import {statSync} from "fs"; + +export class ArtifactPathValidator { + private readonly errorsFailBuild: boolean; + private paths: string[]; + private readonly pattern: string + + constructor(errorsFailBuild: boolean, paths: string[], pattern: string) { + this.paths = paths; + this.pattern = pattern + this.errorsFailBuild = errorsFailBuild; + } + + validate(): string[] { + this.verifyPathsNotEmpty() + return this.paths.filter((path) => this.verifyNotDirectory(path)) + } + + private verifyPathsNotEmpty() { + if (this.paths.length == 0) { + const message = `Artifact pattern:${this.pattern} did not match any files` + this.reportError(message) + } + } + + private verifyNotDirectory(path: string): boolean { + const isDir = statSync(path).isDirectory() + if (isDir) { + const message = `Artifact is a directory:${path}. Directories can not be uploaded to a release.` + this.reportError(message) + } + return !isDir + } + + private reportError(message: string) { + if (this.errorsFailBuild) { + throw Error(message) + } else { + core.warning(message) + } + } +} \ No newline at end of file