diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d56122..391b4ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,10 +19,16 @@ jobs: - name: pester tests shell: pwsh run: | - if (-not (Get-Module -ListAvailable Pester)) { - Install-Module Pester -Force + $neededModules = @( + 'Pester' + 'GitHubActions' + ) + $neededModules | % { + if (-not (Get-Module -ListAvailable $_)) { + Install-Module $_ -Force + } } - ./tests/ActionCore_tests.ps1 + ./tests/GitHubActions_tests.ps1 # - name: bundle distributable components # shell: pwsh @@ -37,7 +43,6 @@ jobs: run: | mkdir ./dist Copy-Item ./_init ./dist/ -Recurse - Copy-Item ./lib ./dist/ -Recurse Copy-Item ./SAMPLE-* ./dist/ Copy-Item ./LICENSE ./dist/ Copy-Item ./README.md ./dist/ @@ -51,8 +56,7 @@ jobs: path: ./dist publish: - runs-on: ubuntu-16.04 - #runs-on: ubuntu-latest + runs-on: ubuntu-latest needs: build if: github.event_name == 'release' steps: diff --git a/.gitignore b/.gitignore index 3e759b7..be52dc8 100644 --- a/.gitignore +++ b/.gitignore @@ -328,3 +328,8 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ + + +## Standard Ignores +_IGNORE/ +_TMP/ diff --git a/README.md b/README.md index 0e8c4c5..1a9b7cf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ Base support for implementing GitHub Actions in PowerShell Core [![GitHub Workflow - CI](https://github.com/ebekker/pwsh-github-action-base/workflows/CI/badge.svg)](https://github.com/ebekker/pwsh-github-action-base/actions?workflow=CI) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/ebekker/pwsh-github-action-base)](https://github.com/ebekker/pwsh-github-action-base/releases/latest/download/pwsh-github-action-base-dist.zip) -[![docs for lib/ActionsCore](https://img.shields.io/badge/docs-lib/ActionsCore-blueviolet)](docs/README.md) --- @@ -18,9 +17,6 @@ The [distribution](https://github.com/ebekker/pwsh-github-action-base/releases/l * **[`_init/index.js`](_init/index.js)** - The entry point into invoking the Action. -* **[`lib/ActionsCore.ps1`](lib/ActionsCore.ps1)** - - A collection of cmdlets that support interfacing to the - Actions/Workflow environment for input, output and messaging. * **[`SAMPLE-action.ps1`](SAMPLE-action.ps1)** - A sample script implementing a simple Action script demonstrating some of the features made available from the Core library. @@ -28,6 +24,11 @@ The [distribution](https://github.com/ebekker/pwsh-github-action-base/releases/l A sample Action metadata file that describes various attributes such as a description, licensing, branding and formal input and output values. +Additionally, the sample Action shows how to make use of the +[GitHubActions module](https://www.powershellgallery.com/packages/GitHubActions) +to get access to the GH Actions/Workflow environment for input, output +and messaging. More details can be found [below](#optional-support-module) + ## Required Components ### `action.ps1` - The PowerShell Entry Point @@ -43,16 +44,19 @@ the GitHub API. ### `action.yml` - The Action Metadata -As per the GitHub Actions mechanism, you must provide a [metadata file](https://help.github.com/en/articles/metadata-syntax-for-github-actions) that describes -various attributes about your Action, including any formal inputs and outputs. -You use this metadata file to enumerate any _required_ inputs that must be -provided by a Workflow definition. +As per the GitHub Actions mechanism, you must provide a +[metadata file](https://help.github.com/en/articles/metadata-syntax-for-github-actions) +that describes various attributes about your Action, including any formal inputs +and outputs. You use this metadata file to enumerate any _required_ inputs that +must be provided by a Workflow definition. -##### `runs` Entry Point Attribute +#### `runs` Entry Point Attribute The most important attribute in this file for our purposes is the `runs` setting which has two child settings, `using` and `main`. This attribute -indicates what is the [_type_](https://help.github.com/en/articles/about-actions#types-of-actions) of your Action and how to run it. +indicates what is the +[_type_](https://help.github.com/en/articles/about-actions#types-of-actions) +of your Action and how to run it. There are two main types of Actions, one based on Docker containers and one based on JavaScript (NodeJS). While Docker Actions give you the ability @@ -60,8 +64,9 @@ to define and _carry_ the entire runtime with you, they are slower to start and limited to only executing in Linux environments. JavaScript Actions however are simpler and more lightweight, and therefore -quicker to start, and they can run on any of the supported platforms (Linux, Windows, MacOS). They also execute directly in the hosted virtual machine -where the Workflow runs instead of a dedicated container. +quicker to start, and they can run on any of the supported platforms +(Linux, Windows, MacOS). They also execute directly in the hosted virtual +machine where the Workflow runs instead of a dedicated container. Because of these advantages, this repo hosts a solution that is based on JavaScript-type Actions. A stub JavaScript script is provided to bootstrap @@ -99,15 +104,16 @@ The working directory is the same as at the start of the bootstrap script which is the root of the cloned repository of the Workflow in which the action is being invoked. -## Optional Support Library +## Optional Support Module -In addition to the required components above, you may choose to make use -of the **[`lib/ActionsCore.ps1`](lib/ActionsCore.ps1)** utility script that -defines a number of cmdlets that help interact with the Worklfow/Action -environment context in a more natural way for PowerShell scripts. -These cmdlets are adaptations of the JavaScript Actions +In addition to the required components above, you may choose to make use of the +**[`GitHubActions` PowerShell module](https://www.powershellgallery.com/packages/GitHubActions)** +utility script that defines a number of cmdlets that help interact with the +Worklfow/Action environment context in a more natural way for PowerShell +scripts. These cmdlets are adaptations of the JavaScript Actions [core package](https://github.com/actions/toolkit/tree/master/packages/core) provided in the [Actions Toolkit](https://github.com/actions/toolkit). See that package description for details about what it provides -For details about the counterpart cmdlets, go to the [docs](docs/README.md). +For details about the counterpart cmdlets, go to the +[docs](https://github.com/ebekker/pwsh-github-action-tools/blob/master/docs/GitHubActions/README.md). diff --git a/SAMPLE-action.ps1 b/SAMPLE-action.ps1 index ec5102f..94b2a2d 100644 --- a/SAMPLE-action.ps1 +++ b/SAMPLE-action.ps1 @@ -4,14 +4,24 @@ ## This is a sample GitHub Action script written in PowerShell Core. ## You can write your logic in PWSH to perform GitHub Actions. ## + + ## You interface with the Actions/Workflow system by interacting -## with the environment. The `ActionsCore.ps1` library makes this -## easier and more natural. -## +## with the environment. The `GitHubActions` module makes this +## easier and more natural by wrapping up access to the Workflow +## environment in PowerShell-friendly constructions and idioms +if (-not (Get-Module -ListAvailable GitHubActions)) { + ## Make sure the GH Actions module is installed from the Gallery + Install-Module GitHubActions -Force +} ## Load up some common functionality for interacting ## with the GitHub Actions/Workflow environment -. ./lib/ActionsCore.ps1 +Import-Module GitHubActions + +## +## ***** Put your logic here ***** +## ## Pull in some inputs $salutation = Get-ActionInput salutation -Required diff --git a/_init/index.js b/_init/index.js index e86b11d..8282ed5 100644 --- a/_init/index.js +++ b/_init/index.js @@ -66,7 +66,7 @@ const exec = __webpack_require__(871); async function run() { try { - const pwshFolder = __dirname.replace(/\/_init$/, ''); + const pwshFolder = __dirname.replace(/[/\\]_init$/, ''); const pwshScript = `${pwshFolder}/action.ps1` await exec.exec('pwsh', [ '-f', pwshScript ]); } catch (error) { @@ -76,6 +76,208 @@ async function run() { run(); +/***/ }), + +/***/ 159: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"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 _a; +Object.defineProperty(exports, "__esModule", { value: true }); +const assert_1 = __webpack_require__(357); +const fs = __webpack_require__(747); +const path = __webpack_require__(622); +_a = fs.promises, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink; +exports.IS_WINDOWS = process.platform === 'win32'; +function exists(fsPath) { + return __awaiter(this, void 0, void 0, function* () { + try { + yield exports.stat(fsPath); + } + catch (err) { + if (err.code === 'ENOENT') { + return false; + } + throw err; + } + return true; + }); +} +exports.exists = exists; +function isDirectory(fsPath, useStat = false) { + return __awaiter(this, void 0, void 0, function* () { + const stats = useStat ? yield exports.stat(fsPath) : yield exports.lstat(fsPath); + return stats.isDirectory(); + }); +} +exports.isDirectory = isDirectory; +/** + * On OSX/Linux, true if path starts with '/'. On Windows, true for paths like: + * \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases). + */ +function isRooted(p) { + p = normalizeSeparators(p); + if (!p) { + throw new Error('isRooted() parameter "p" cannot be empty'); + } + if (exports.IS_WINDOWS) { + return (p.startsWith('\\') || /^[A-Z]:/i.test(p) // e.g. \ or \hello or \\hello + ); // e.g. C: or C:\hello + } + return p.startsWith('/'); +} +exports.isRooted = isRooted; +/** + * Recursively create a directory at `fsPath`. + * + * This implementation is optimistic, meaning it attempts to create the full + * path first, and backs up the path stack from there. + * + * @param fsPath The path to create + * @param maxDepth The maximum recursion depth + * @param depth The current recursion depth + */ +function mkdirP(fsPath, maxDepth = 1000, depth = 1) { + return __awaiter(this, void 0, void 0, function* () { + assert_1.ok(fsPath, 'a path argument must be provided'); + fsPath = path.resolve(fsPath); + if (depth >= maxDepth) + return exports.mkdir(fsPath); + try { + yield exports.mkdir(fsPath); + return; + } + catch (err) { + switch (err.code) { + case 'ENOENT': { + yield mkdirP(path.dirname(fsPath), maxDepth, depth + 1); + yield exports.mkdir(fsPath); + return; + } + default: { + let stats; + try { + stats = yield exports.stat(fsPath); + } + catch (err2) { + throw err; + } + if (!stats.isDirectory()) + throw err; + } + } + } + }); +} +exports.mkdirP = mkdirP; +/** + * Best effort attempt to determine whether a file exists and is executable. + * @param filePath file path to check + * @param extensions additional file extensions to try + * @return if file exists and is executable, returns the file path. otherwise empty string. + */ +function tryGetExecutablePath(filePath, extensions) { + return __awaiter(this, void 0, void 0, function* () { + let stats = undefined; + try { + // test file exists + stats = yield exports.stat(filePath); + } + catch (err) { + if (err.code !== 'ENOENT') { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + } + if (stats && stats.isFile()) { + if (exports.IS_WINDOWS) { + // on Windows, test for valid extension + const upperExt = path.extname(filePath).toUpperCase(); + if (extensions.some(validExt => validExt.toUpperCase() === upperExt)) { + return filePath; + } + } + else { + if (isUnixExecutable(stats)) { + return filePath; + } + } + } + // try each extension + const originalFilePath = filePath; + for (const extension of extensions) { + filePath = originalFilePath + extension; + stats = undefined; + try { + stats = yield exports.stat(filePath); + } + catch (err) { + if (err.code !== 'ENOENT') { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + } + if (stats && stats.isFile()) { + if (exports.IS_WINDOWS) { + // preserve the case of the actual file (since an extension was appended) + try { + const directory = path.dirname(filePath); + const upperName = path.basename(filePath).toUpperCase(); + for (const actualName of yield exports.readdir(directory)) { + if (upperName === actualName.toUpperCase()) { + filePath = path.join(directory, actualName); + break; + } + } + } + catch (err) { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`); + } + return filePath; + } + else { + if (isUnixExecutable(stats)) { + return filePath; + } + } + } + } + return ''; + }); +} +exports.tryGetExecutablePath = tryGetExecutablePath; +function normalizeSeparators(p) { + p = p || ''; + if (exports.IS_WINDOWS) { + // convert slashes on Windows + p = p.replace(/\//g, '\\'); + // remove redundant slashes + return p.replace(/\\\\+/g, '\\'); + } + // remove redundant slashes + return p.replace(/\/\/+/g, '/'); +} +// on Mac/Linux, test the execute bit +// R W X R W X R W X +// 256 128 64 32 16 8 4 2 1 +function isUnixExecutable(stats) { + return ((stats.mode & 1) > 0 || + ((stats.mode & 8) > 0 && stats.gid === process.getgid()) || + ((stats.mode & 64) > 0 && stats.uid === process.getuid())); +} +//# sourceMappingURL=io-util.js.map + /***/ }), /***/ 200: @@ -92,10 +294,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +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 os = __webpack_require__(87); -const events = __webpack_require__(614); -const child = __webpack_require__(129); +const os = __importStar(__webpack_require__(87)); +const events = __importStar(__webpack_require__(614)); +const child = __importStar(__webpack_require__(129)); +const path = __importStar(__webpack_require__(622)); +const io = __importStar(__webpack_require__(460)); +const ioUtil = __importStar(__webpack_require__(159)); /* eslint-disable @typescript-eslint/unbound-method */ const IS_WINDOWS = process.platform === 'win32'; /* @@ -441,6 +653,16 @@ class ToolRunner extends events.EventEmitter { */ exec() { return __awaiter(this, void 0, void 0, function* () { + // root the tool path if it is unrooted and contains relative pathing + if (!ioUtil.isRooted(this.toolPath) && + (this.toolPath.includes('/') || + (IS_WINDOWS && this.toolPath.includes('\\')))) { + // prefer options.cwd if it is specified, however options.cwd may also need to be rooted + this.toolPath = path.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath); + } + // if the tool is only a file name, then resolve it from the PATH + // otherwise verify it exists (add extension on Windows if necessary) + this.toolPath = yield io.which(this.toolPath, true); return new Promise((resolve, reject) => { this._debug(`exec tool: ${this.toolPath}`); this._debug('arguments:'); @@ -529,6 +751,12 @@ class ToolRunner extends events.EventEmitter { resolve(exitCode); } }); + if (this.options.input) { + if (!cp.stdin) { + throw new Error('child process missing stdin'); + } + cp.stdin.end(this.options.input); + } }); }); } @@ -664,17 +892,24 @@ class ExecState extends events.EventEmitter { "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 os = __webpack_require__(87); +const os = __importStar(__webpack_require__(87)); /** * Commands * * Command Format: - * ##[name key=value;key=value]message + * ::name key=value,key=value::message * * Examples: - * ##[warning]This is the user warning message - * ##[set-secret name=mypassword]definitelyNotAPassword! + * ::warning::This is the message + * ::set-env name=MY_VAR::some value */ function issueCommand(command, properties, message) { const cmd = new Command(command, properties, message); @@ -699,39 +934,362 @@ class Command { let cmdStr = CMD_STRING + this.command; if (this.properties && Object.keys(this.properties).length > 0) { cmdStr += ' '; + let first = true; for (const key in this.properties) { if (this.properties.hasOwnProperty(key)) { const val = this.properties[key]; if (val) { - // safely append the val - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - cmdStr += `${key}=${escape(`${val || ''}`)},`; + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; } } } } - cmdStr += CMD_STRING; - // safely append the message - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - const message = `${this.message || ''}`; - cmdStr += escapeData(message); + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; return cmdStr; } } +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; function escapeData(s) { - return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A'); + return toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); } -function escape(s) { - return s +function escapeProperty(s) { + return toCommandValue(s) + .replace(/%/g, '%25') .replace(/\r/g, '%0D') .replace(/\n/g, '%0A') - .replace(/]/g, '%5D') - .replace(/;/g, '%3B'); + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); } //# sourceMappingURL=command.js.map /***/ }), +/***/ 357: +/***/ (function(module) { + +module.exports = require("assert"); + +/***/ }), + +/***/ 460: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"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 }); +const childProcess = __webpack_require__(129); +const path = __webpack_require__(622); +const util_1 = __webpack_require__(669); +const ioUtil = __webpack_require__(159); +const exec = util_1.promisify(childProcess.exec); +/** + * Copies a file or folder. + * Based off of shelljs - https://github.com/shelljs/shelljs/blob/9237f66c52e5daa40458f94f9565e18e8132f5a6/src/cp.js + * + * @param source source path + * @param dest destination path + * @param options optional. See CopyOptions. + */ +function cp(source, dest, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + const { force, recursive } = readCopyOptions(options); + const destStat = (yield ioUtil.exists(dest)) ? yield ioUtil.stat(dest) : null; + // Dest is an existing file, but not forcing + if (destStat && destStat.isFile() && !force) { + return; + } + // If dest is an existing directory, should copy inside. + const newDest = destStat && destStat.isDirectory() + ? path.join(dest, path.basename(source)) + : dest; + if (!(yield ioUtil.exists(source))) { + throw new Error(`no such file or directory: ${source}`); + } + const sourceStat = yield ioUtil.stat(source); + if (sourceStat.isDirectory()) { + if (!recursive) { + throw new Error(`Failed to copy. ${source} is a directory, but tried to copy without recursive flag.`); + } + else { + yield cpDirRecursive(source, newDest, 0, force); + } + } + else { + if (path.relative(source, newDest) === '') { + // a file cannot be copied to itself + throw new Error(`'${newDest}' and '${source}' are the same file`); + } + yield copyFile(source, newDest, force); + } + }); +} +exports.cp = cp; +/** + * Moves a path. + * + * @param source source path + * @param dest destination path + * @param options optional. See MoveOptions. + */ +function mv(source, dest, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + if (yield ioUtil.exists(dest)) { + let destExists = true; + if (yield ioUtil.isDirectory(dest)) { + // If dest is directory copy src into dest + dest = path.join(dest, path.basename(source)); + destExists = yield ioUtil.exists(dest); + } + if (destExists) { + if (options.force == null || options.force) { + yield rmRF(dest); + } + else { + throw new Error('Destination already exists'); + } + } + } + yield mkdirP(path.dirname(dest)); + yield ioUtil.rename(source, dest); + }); +} +exports.mv = mv; +/** + * Remove a path recursively with force + * + * @param inputPath path to remove + */ +function rmRF(inputPath) { + return __awaiter(this, void 0, void 0, function* () { + if (ioUtil.IS_WINDOWS) { + // Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another + // program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del. + try { + if (yield ioUtil.isDirectory(inputPath, true)) { + yield exec(`rd /s /q "${inputPath}"`); + } + else { + yield exec(`del /f /a "${inputPath}"`); + } + } + catch (err) { + // if you try to delete a file that doesn't exist, desired result is achieved + // other errors are valid + if (err.code !== 'ENOENT') + throw err; + } + // Shelling out fails to remove a symlink folder with missing source, this unlink catches that + try { + yield ioUtil.unlink(inputPath); + } + catch (err) { + // if you try to delete a file that doesn't exist, desired result is achieved + // other errors are valid + if (err.code !== 'ENOENT') + throw err; + } + } + else { + let isDir = false; + try { + isDir = yield ioUtil.isDirectory(inputPath); + } + catch (err) { + // if you try to delete a file that doesn't exist, desired result is achieved + // other errors are valid + if (err.code !== 'ENOENT') + throw err; + return; + } + if (isDir) { + yield exec(`rm -rf "${inputPath}"`); + } + else { + yield ioUtil.unlink(inputPath); + } + } + }); +} +exports.rmRF = rmRF; +/** + * Make a directory. Creates the full path with folders in between + * Will throw if it fails + * + * @param fsPath path to create + * @returns Promise + */ +function mkdirP(fsPath) { + return __awaiter(this, void 0, void 0, function* () { + yield ioUtil.mkdirP(fsPath); + }); +} +exports.mkdirP = mkdirP; +/** + * Returns path of a tool had the tool actually been invoked. Resolves via paths. + * If you check and the tool does not exist, it will throw. + * + * @param tool name of the tool + * @param check whether to check if tool exists + * @returns Promise path to tool + */ +function which(tool, check) { + return __awaiter(this, void 0, void 0, function* () { + if (!tool) { + throw new Error("parameter 'tool' is required"); + } + // recursive when check=true + if (check) { + const result = yield which(tool, false); + if (!result) { + if (ioUtil.IS_WINDOWS) { + throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`); + } + else { + throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`); + } + } + } + try { + // build the list of extensions to try + const extensions = []; + if (ioUtil.IS_WINDOWS && process.env.PATHEXT) { + for (const extension of process.env.PATHEXT.split(path.delimiter)) { + if (extension) { + extensions.push(extension); + } + } + } + // if it's rooted, return it if exists. otherwise return empty. + if (ioUtil.isRooted(tool)) { + const filePath = yield ioUtil.tryGetExecutablePath(tool, extensions); + if (filePath) { + return filePath; + } + return ''; + } + // if any path separators, return empty + if (tool.includes('/') || (ioUtil.IS_WINDOWS && tool.includes('\\'))) { + return ''; + } + // build the list of directories + // + // Note, technically "where" checks the current directory on Windows. From a toolkit perspective, + // it feels like we should not do this. Checking the current directory seems like more of a use + // case of a shell, and the which() function exposed by the toolkit should strive for consistency + // across platforms. + const directories = []; + if (process.env.PATH) { + for (const p of process.env.PATH.split(path.delimiter)) { + if (p) { + directories.push(p); + } + } + } + // return the first match + for (const directory of directories) { + const filePath = yield ioUtil.tryGetExecutablePath(directory + path.sep + tool, extensions); + if (filePath) { + return filePath; + } + } + return ''; + } + catch (err) { + throw new Error(`which failed with message ${err.message}`); + } + }); +} +exports.which = which; +function readCopyOptions(options) { + const force = options.force == null ? true : options.force; + const recursive = Boolean(options.recursive); + return { force, recursive }; +} +function cpDirRecursive(sourceDir, destDir, currentDepth, force) { + return __awaiter(this, void 0, void 0, function* () { + // Ensure there is not a run away recursive copy + if (currentDepth >= 255) + return; + currentDepth++; + yield mkdirP(destDir); + const files = yield ioUtil.readdir(sourceDir); + for (const fileName of files) { + const srcFile = `${sourceDir}/${fileName}`; + const destFile = `${destDir}/${fileName}`; + const srcFileStat = yield ioUtil.lstat(srcFile); + if (srcFileStat.isDirectory()) { + // Recurse + yield cpDirRecursive(srcFile, destFile, currentDepth, force); + } + else { + yield copyFile(srcFile, destFile, force); + } + } + // Change the mode for the newly created directory + yield ioUtil.chmod(destDir, (yield ioUtil.stat(sourceDir)).mode); + }); +} +// Buffered file copy +function copyFile(srcFile, destFile, force) { + return __awaiter(this, void 0, void 0, function* () { + if ((yield ioUtil.lstat(srcFile)).isSymbolicLink()) { + // unlink/re-link it + try { + yield ioUtil.lstat(destFile); + yield ioUtil.unlink(destFile); + } + catch (e) { + // Try to override file permission + if (e.code === 'EPERM') { + yield ioUtil.chmod(destFile, '0666'); + yield ioUtil.unlink(destFile); + } + // other errors = it doesn't exist, no work to do + } + // Copy over symlink + const symlinkFull = yield ioUtil.readlink(srcFile); + yield ioUtil.symlink(symlinkFull, destFile, ioUtil.IS_WINDOWS ? 'junction' : null); + } + else if (!(yield ioUtil.exists(destFile)) || force) { + yield ioUtil.copyFile(srcFile, destFile); + } + }); +} +//# sourceMappingURL=io.js.map + +/***/ }), + /***/ 580: /***/ (function(__unusedmodule, exports, __webpack_require__) { @@ -746,10 +1304,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +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 command_1 = __webpack_require__(260); -const os = __webpack_require__(87); -const path = __webpack_require__(622); +const os = __importStar(__webpack_require__(87)); +const path = __importStar(__webpack_require__(622)); /** * The code to exit an action */ @@ -770,11 +1335,13 @@ var ExitCode; /** * Sets env variable for this action and future actions in the job * @param name the name of the variable to set - * @param val the value of the variable + * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any function exportVariable(name, val) { - process.env[name] = val; - command_1.issueCommand('set-env', { name }, val); + const convertedVal = command_1.toCommandValue(val); + process.env[name] = convertedVal; + command_1.issueCommand('set-env', { name }, convertedVal); } exports.exportVariable = exportVariable; /** @@ -813,12 +1380,22 @@ exports.getInput = getInput; * Sets the value of an output. * * @param name name of the output to set - * @param value value to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any function setOutput(name, value) { command_1.issueCommand('set-output', { name }, value); } exports.setOutput = setOutput; +/** + * Enables or disables the echoing of commands into stdout for the rest of the step. + * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. + * + */ +function setCommandEcho(enabled) { + command_1.issue('echo', enabled ? 'on' : 'off'); +} +exports.setCommandEcho = setCommandEcho; //----------------------------------------------------------------------- // Results //----------------------------------------------------------------------- @@ -835,6 +1412,13 @@ exports.setFailed = setFailed; //----------------------------------------------------------------------- // Logging Commands //----------------------------------------------------------------------- +/** + * Gets whether Actions Step Debug is on or not + */ +function isDebug() { + return process.env['RUNNER_DEBUG'] === '1'; +} +exports.isDebug = isDebug; /** * Writes debug message to user log * @param message debug message @@ -845,18 +1429,18 @@ function debug(message) { exports.debug = debug; /** * Adds an error issue - * @param message error issue message + * @param message error issue message. Errors will be converted to string via toString() */ function error(message) { - command_1.issue('error', message); + command_1.issue('error', message instanceof Error ? message.toString() : message); } exports.error = error; /** * Adds an warning issue - * @param message warning issue message + * @param message warning issue message. Errors will be converted to string via toString() */ function warning(message) { - command_1.issue('warning', message); + command_1.issue('warning', message instanceof Error ? message.toString() : message); } exports.warning = warning; /** @@ -907,6 +1491,30 @@ function group(name, fn) { }); } exports.group = group; +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- +/** + * Saves state for current action, the state can only be retrieved by this action's post job execution. + * + * @param name name of the state to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function saveState(name, value) { + command_1.issueCommand('save-state', { name }, value); +} +exports.saveState = saveState; +/** + * Gets the value of an state set by this action's main execution. + * + * @param name name of the state to get + * @returns string + */ +function getState(name) { + return process.env[`STATE_${name}`] || ''; +} +exports.getState = getState; //# sourceMappingURL=core.js.map /***/ }), @@ -925,6 +1533,20 @@ module.exports = require("path"); /***/ }), +/***/ 669: +/***/ (function(module) { + +module.exports = require("util"); + +/***/ }), + +/***/ 747: +/***/ (function(module) { + +module.exports = require("fs"); + +/***/ }), + /***/ 871: /***/ (function(__unusedmodule, exports, __webpack_require__) { @@ -939,8 +1561,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +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 tr = __webpack_require__(200); +const tr = __importStar(__webpack_require__(200)); /** * Exec a command. * Output will be streamed to the live console. diff --git a/build-docs.ps1 b/build-docs.ps1 deleted file mode 100644 index 9f1441b..0000000 --- a/build-docs.ps1 +++ /dev/null @@ -1,26 +0,0 @@ - -pwsh -c @' - . ./lib/ActionsCore.ps1 - - if (-not (Test-Path docs)) { mkdir docs } - Write-Output ""| Cmdlet | Synopsis |"" > docs/README.md - Write-Output ""|-|-|"" >> docs/README.md - Get-Command *-Action* | % { Get-Help $_.Name | Select-Object @{ - Name = ""Row"" - Expression = { - $n = $_.Name.Trim() - $s = $_.Synopsis.Trim() - ""| [$($n)]($($n).md) | $($s) |"" - } - } } | Select-Object -Expand Row >> docs/README.md - Get-Command *-Action* | % { Get-Help -Full $_.Name | Select-Object @{ - Name = ""Row"" - Expression = { - $n = $_.Name.Trim() - ""# $n"" - ""``````"" - $_ - ""``````"" - } - } | Select-Object -Expand Row > ""docs/$($_.Name).md"" } -'@ diff --git a/build.ps1 b/build.ps1 index 50a2fad..60a735b 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,2 +1,10 @@ +param( + [switch]$UpgradePackages +) + +if ($UpgradePackages) { + & npm upgrade "@actions/core" + & npm upgrade "@actions/exec" +} ncc build .\invoke-pwsh.js -o _init diff --git a/docs/Add-ActionPath.md b/docs/Add-ActionPath.md deleted file mode 100644 index ff8074d..0000000 --- a/docs/Add-ActionPath.md +++ /dev/null @@ -1,51 +0,0 @@ -# Add-ActionPath -``` - -NAME - Add-ActionPath - -SYNOPSIS - Prepends path to the PATH (for this action and future actions). - - -SYNTAX - Add-ActionPath [-Path] [-SkipLocal] [] - - -DESCRIPTION - - -PARAMETERS - -Path - The new path to add. - - Required? true - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - -SkipLocal [] - Do not prepend path to current action's/step's environment PATH. - - Required? false - Position? named - Default value False - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Add-ActionSecretMask.md b/docs/Add-ActionSecretMask.md deleted file mode 100644 index 7ff95d1..0000000 --- a/docs/Add-ActionSecretMask.md +++ /dev/null @@ -1,42 +0,0 @@ -# Add-ActionSecretMask -``` - -NAME - Add-ActionSecretMask - -SYNOPSIS - Registers a secret which will get masked from logs. - - -SYNTAX - Add-ActionSecretMask [-Secret] [] - - -DESCRIPTION - - -PARAMETERS - -Secret - The value of the secret. - - Required? true - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Enter-ActionOutputGroup.md b/docs/Enter-ActionOutputGroup.md deleted file mode 100644 index fdb64b0..0000000 --- a/docs/Enter-ActionOutputGroup.md +++ /dev/null @@ -1,43 +0,0 @@ -# Enter-ActionOutputGroup -``` - -NAME - Enter-ActionOutputGroup - -SYNOPSIS - Begin an output group. - - -SYNTAX - Enter-ActionOutputGroup [-Name] [] - - -DESCRIPTION - Output until the next `groupEnd` will be foldable in this group. - - -PARAMETERS - -Name - Name of the output group. - - Required? true - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Exit-ActionOutputGroup.md b/docs/Exit-ActionOutputGroup.md deleted file mode 100644 index 7d595fc..0000000 --- a/docs/Exit-ActionOutputGroup.md +++ /dev/null @@ -1,33 +0,0 @@ -# Exit-ActionOutputGroup -``` - -NAME - Exit-ActionOutputGroup - -SYNOPSIS - End an output group. - - -SYNTAX - Exit-ActionOutputGroup [] - - -DESCRIPTION - - -PARAMETERS - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Get-ActionInput.md b/docs/Get-ActionInput.md deleted file mode 100644 index c8054c8..0000000 --- a/docs/Get-ActionInput.md +++ /dev/null @@ -1,51 +0,0 @@ -# Get-ActionInput -``` - -NAME - Get-ActionInput - -SYNOPSIS - Gets the value of an input. The value is also trimmed. - - -SYNTAX - Get-ActionInput [-Name] [-Required] [] - - -DESCRIPTION - - -PARAMETERS - -Name - Name of the input to get - - Required? true - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - -Required [] - Whether the input is required. If required and not present, will throw. - - Required? false - Position? named - Default value False - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Get-ActionInputs.md b/docs/Get-ActionInputs.md deleted file mode 100644 index 2f9a201..0000000 --- a/docs/Get-ActionInputs.md +++ /dev/null @@ -1,35 +0,0 @@ -# Get-ActionInputs -``` - -NAME - Get-ActionInputs - -SYNOPSIS - Returns a map of all the available inputs and their values. - - -SYNTAX - Get-ActionInputs [] - - -DESCRIPTION - Lookups in the returned map are case-insensitive, as per the - behavior of individual input lookup. - - -PARAMETERS - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Invoke-ActionWithinOutputGroup.md b/docs/Invoke-ActionWithinOutputGroup.md deleted file mode 100644 index ac7364d..0000000 --- a/docs/Invoke-ActionWithinOutputGroup.md +++ /dev/null @@ -1,51 +0,0 @@ -# Invoke-ActionWithinOutputGroup -``` - -NAME - Invoke-ActionWithinOutputGroup - -SYNOPSIS - Executes the argument script block within and output group. - - -SYNTAX - Invoke-ActionWithinOutputGroup [-Name] [-ScriptBlock] [] - - -DESCRIPTION - - -PARAMETERS - -Name - Name of the output group. - - Required? true - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - -ScriptBlock - Script block to execute in between opening and closing output group. - - Required? true - Position? 2 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 62aedde..0000000 --- a/docs/README.md +++ /dev/null @@ -1,17 +0,0 @@ -| Cmdlet | Synopsis | -|-|-| -| [Add-ActionPath](Add-ActionPath.md) | Prepends path to the PATH (for this action and future actions). | -| [Add-ActionSecretMask](Add-ActionSecretMask.md) | Registers a secret which will get masked from logs. | -| [Enter-ActionOutputGroup](Enter-ActionOutputGroup.md) | Begin an output group. | -| [Exit-ActionOutputGroup](Exit-ActionOutputGroup.md) | End an output group. | -| [Get-ActionInput](Get-ActionInput.md) | Gets the value of an input. The value is also trimmed. | -| [Get-ActionInputs](Get-ActionInputs.md) | Returns a map of all the available inputs and their values. | -| [Invoke-ActionWithinOutputGroup](Invoke-ActionWithinOutputGroup.md) | Executes the argument script block within and output group. | -| [Send-ActionCommand](Send-ActionCommand.md) | Sends a command to the hosting Workflow/Action context. | -| [Set-ActionFailed](Set-ActionFailed.md) | TODO: NOT IMPLEMENTED! | -| [Set-ActionOutput](Set-ActionOutput.md) | Sets the value of an output. | -| [Set-ActionVariable](Set-ActionVariable.md) | Sets env variable for this action and future actions in the job. | -| [Write-ActionDebug](Write-ActionDebug.md) | Writes debug message to user log. | -| [Write-ActionError](Write-ActionError.md) | Adds an error issue. | -| [Write-ActionInfo](Write-ActionInfo.md) | Writes info to log with console.log. | -| [Write-ActionWarning](Write-ActionWarning.md) | Adds a warning issue. | diff --git a/docs/Send-ActionCommand.md b/docs/Send-ActionCommand.md deleted file mode 100644 index cf7e52f..0000000 --- a/docs/Send-ActionCommand.md +++ /dev/null @@ -1,79 +0,0 @@ -# Send-ActionCommand -``` - -NAME - Send-ActionCommand - -SYNOPSIS - Sends a command to the hosting Workflow/Action context. - - -SYNTAX - Send-ActionCommand [-Command] [-Properties] [[-Message] ] [] - - Send-ActionCommand [-Command] [[-Message] ] [] - - -DESCRIPTION - Command Format: - ::name key=value;key=value##message - - -PARAMETERS - -Command - - Required? true - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - -Properties - - Required? true - Position? 2 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - -Message - - Required? false - Position? 3 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -------------------------- EXAMPLE 1 -------------------------- - - PS > ::warning::This is the user warning message - - - - - - - -------------------------- EXAMPLE 2 -------------------------- - - PS > ::set-secret name=mypassword::definitelyNotAPassword! - - - - - - - -RELATED LINKS - -``` - diff --git a/docs/Set-ActionFailed.md b/docs/Set-ActionFailed.md deleted file mode 100644 index 700cc12..0000000 --- a/docs/Set-ActionFailed.md +++ /dev/null @@ -1,33 +0,0 @@ -# Set-ActionFailed -``` - -NAME - Set-ActionFailed - -SYNOPSIS - TODO: NOT IMPLEMENTED! - - -SYNTAX - Set-ActionFailed [] - - -DESCRIPTION - - -PARAMETERS - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Set-ActionOutput.md b/docs/Set-ActionOutput.md deleted file mode 100644 index ea0d616..0000000 --- a/docs/Set-ActionOutput.md +++ /dev/null @@ -1,51 +0,0 @@ -# Set-ActionOutput -``` - -NAME - Set-ActionOutput - -SYNOPSIS - Sets the value of an output. - - -SYNTAX - Set-ActionOutput [-Name] [-Value] [] - - -DESCRIPTION - - -PARAMETERS - -Name - Name of the output to set. - - Required? true - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - -Value - Value to store. - - Required? true - Position? 2 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Set-ActionVariable.md b/docs/Set-ActionVariable.md deleted file mode 100644 index 249d213..0000000 --- a/docs/Set-ActionVariable.md +++ /dev/null @@ -1,60 +0,0 @@ -# Set-ActionVariable -``` - -NAME - Set-ActionVariable - -SYNOPSIS - Sets env variable for this action and future actions in the job. - - -SYNTAX - Set-ActionVariable [-Name] [-Value] [-SkipLocal] [] - - -DESCRIPTION - - -PARAMETERS - -Name - The name of the variable to set - - Required? true - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - -Value - The value of the variable - - Required? true - Position? 2 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - -SkipLocal [] - Do not set variable in current action's/step's environment. - - Required? false - Position? named - Default value False - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Write-ActionDebug.md b/docs/Write-ActionDebug.md deleted file mode 100644 index 8fdef88..0000000 --- a/docs/Write-ActionDebug.md +++ /dev/null @@ -1,42 +0,0 @@ -# Write-ActionDebug -``` - -NAME - Write-ActionDebug - -SYNOPSIS - Writes debug message to user log. - - -SYNTAX - Write-ActionDebug [[-Message] ] [] - - -DESCRIPTION - - -PARAMETERS - -Message - Debug message - - Required? false - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Write-ActionError.md b/docs/Write-ActionError.md deleted file mode 100644 index ef71f2c..0000000 --- a/docs/Write-ActionError.md +++ /dev/null @@ -1,42 +0,0 @@ -# Write-ActionError -``` - -NAME - Write-ActionError - -SYNOPSIS - Adds an error issue. - - -SYNTAX - Write-ActionError [[-Message] ] [] - - -DESCRIPTION - - -PARAMETERS - -Message - Error issue message - - Required? false - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Write-ActionInfo.md b/docs/Write-ActionInfo.md deleted file mode 100644 index d8aaa6f..0000000 --- a/docs/Write-ActionInfo.md +++ /dev/null @@ -1,42 +0,0 @@ -# Write-ActionInfo -``` - -NAME - Write-ActionInfo - -SYNOPSIS - Writes info to log with console.log. - - -SYNTAX - Write-ActionInfo [[-Message] ] [] - - -DESCRIPTION - - -PARAMETERS - -Message - Info message - - Required? false - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/docs/Write-ActionWarning.md b/docs/Write-ActionWarning.md deleted file mode 100644 index 808c20f..0000000 --- a/docs/Write-ActionWarning.md +++ /dev/null @@ -1,42 +0,0 @@ -# Write-ActionWarning -``` - -NAME - Write-ActionWarning - -SYNOPSIS - Adds a warning issue. - - -SYNTAX - Write-ActionWarning [[-Message] ] [] - - -DESCRIPTION - - -PARAMETERS - -Message - Warning issue message - - Required? false - Position? 1 - Default value - Accept pipeline input? false - Accept wildcard characters? false - - - This cmdlet supports the common parameters: Verbose, Debug, - ErrorAction, ErrorVariable, WarningAction, WarningVariable, - OutBuffer, PipelineVariable, and OutVariable. For more information, see - about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). - -INPUTS - -OUTPUTS - - -RELATED LINKS - -``` - diff --git a/lib/ActionsCore.ps1 b/lib/ActionsCore.ps1 deleted file mode 100644 index 1891823..0000000 --- a/lib/ActionsCore.ps1 +++ /dev/null @@ -1,340 +0,0 @@ - -## Adapted from: -## https://github.com/actions/toolkit/blob/a6e72497764b1cf53192eb720f551d7f0db3a4b4/packages/core/src/core.ts - -<# -.SYNOPSIS -Sets env variable for this action and future actions in the job. -.PARAMETER Name -The name of the variable to set -.PARAMETER Value -The value of the variable -.PARAMETER SkipLocal -Do not set variable in current action's/step's environment. -#> -function Set-ActionVariable { - param( - [Parameter(Position=0, Mandatory)] - [string]$Name, - [Parameter(Position=1, Mandatory)] - [string]$Value, - [switch]$SkipLocal - ) - - ## To take effect only in the current action/step - if (-not $SkipLocal) { - [System.Environment]::SetEnvironmentVariable($Name, $Value) - } - - ## To take effect for all subsequent actions/steps - Send-ActionCommand set-env @{ - name = $Name - } -Message $Value -} - -<# -.SYNOPSIS -Registers a secret which will get masked from logs. -.PARAMETER Secret -The value of the secret. -#> -function Add-ActionSecretMask { - param( - [Parameter(Position=0, Mandatory)] - [string]$Secret - ) - - Send-ActionCommand add-mask $Secret -} - -<# -.SYNOPSIS -Prepends path to the PATH (for this action and future actions). -.PARAMETER Path -The new path to add. -.PARAMETER SkipLocal -Do not prepend path to current action's/step's environment PATH. -#> -function Add-ActionPath { - param( - [Parameter(Position=0, Mandatory)] - [string]$Path, - [switch]$SkipLocal - ) - - ## To take effect only in the current action/step - if (-not $SkipLocal) { - $oldPath = [System.Environment]::GetEnvironmentVariable('PATH') - $newPath = "$Path$([System.IO.Path]::PathSeparator)$oldPath" - [System.Environment]::SetEnvironmentVariable('PATH', $newPath) - } - - ## To take effect for all subsequent actions/steps - Send-ActionCommand add-path $Path -} - -## Used to identify inputs from env vars in Action/Workflow context -if (-not (Get-Variable -Scope Script -Name INPUT_PREFIX -ErrorAction SilentlyContinue)) { - Set-Variable -Scope Script -Option Constant -Name INPUT_PREFIX -Value 'INPUT_' -} - -<# -.SYNOPSIS -Gets the value of an input. The value is also trimmed. -.PARAMETER Name -Name of the input to get -.PARAMETER Required -Whether the input is required. If required and not present, will throw. -#> -function Get-ActionInput { - param( - [Parameter(Position=0, Mandatory)] - [string]$Name, - [switch]$Required - ) - - $cleanName = ($Name -replace ' ','_').ToUpper() - $inputValue = Get-ChildItem "Env:$($INPUT_PREFIX)$($cleanName)" -ErrorAction SilentlyContinue - if ($Required -and (-not $inputValue)) { - throw "Input required and not supplied: $($Name)" - } - - return "$($inputValue.Value)".Trim() -} - -<# -.SYNOPSIS -Returns a map of all the available inputs and their values. -.DESCRIPTION -Lookups in the returned map are case-insensitive, as per the -behavior of individual input lookup. -#> -function Get-ActionInputs { - ## This makes sure the returned map looks up keys case-insensitively - $inputsMap = [hashtable]::new([StringComparer]::OrdinalIgnoreCase) - - $envInputs = Get-ChildItem Env: | Where-Object { $_.Name.StartsWith($INPUT_PREFIX) } - foreach ($ei in $envInputs) { - $inputsMap[$ei.Name.Substring($INPUT_PREFIX.Length)] = $ei.Value - } - - return $inputsMap -} - -<# -.SYNOPSIS -Sets the value of an output. -.PARAMETER Name -Name of the output to set. -.PARAMETER Value -Value to store. -#> -function Set-ActionOutput { - param( - [Parameter(Position=0, Mandatory)] - [string]$Name, - [Parameter(Position=1, Mandatory)] - [string]$Value - ) - - Send-ActionCommand set-output @{ - name = $Name - } -Message $Value -} - -<# -.SYNOPSIS -TODO: NOT IMPLEMENTED! -#> -function Set-ActionFailed { - ## Not implemented for now... - throw "Not Implemented" -} - -<# -.SYNOPSIS -Writes debug message to user log. -.PARAMETER Message -Debug message - #> -function Write-ActionDebug { - param( - [string]$Message="" - ) - - Send-ActionCommand debug $Message -} - -<# -.SYNOPSIS -Adds an error issue. -.PARAMETER Message -Error issue message - #> - function Write-ActionError { - param( - [string]$Message="" - ) - - Send-ActionCommand error $Message -} - -<# -.SYNOPSIS -Adds a warning issue. -.PARAMETER Message -Warning issue message - #> - function Write-ActionWarning { - param( - [string]$Message="" - ) - - Send-ActionCommand warning $Message -} - -<# -.SYNOPSIS -Writes info to log with console.log. -.PARAMETER Message -Info message - #> - function Write-ActionInfo { - param( - [string]$Message="" - ) - - ## Hmm, which one?? - #Write-Host "$($Message)$([System.Environment]::NewLine)" - Write-Output "$($Message)$([System.Environment]::NewLine)" -} - -<# -.SYNOPSIS -Begin an output group. -.DESCRIPTION -Output until the next `groupEnd` will be foldable in this group. -.PARAMETER Name -Name of the output group. - #> - function Enter-ActionOutputGroup { - param( - [Parameter(Position=0, Mandatory)] - [string]$Name - ) - - Send-ActionCommand group $Name -} - -<# -.SYNOPSIS -End an output group. - #> - function Exit-ActionOutputGroup { - Send-ActionCommand endgroup -} - -<# -.SYNOPSIS -Executes the argument script block within and output group. -.PARAMETER Name -Name of the output group. -.PARAMETER ScriptBlock -Script block to execute in between opening and closing output group. -#> -function Invoke-ActionWithinOutputGroup { - param( - [Parameter(Position=0, Mandatory)] - [string]$Name, - [Parameter(Position=1, Mandatory)] - [scriptblock]$ScriptBlock - ) - - Enter-ActionOutputGroup -Name $Name - try { - return $ScriptBlock.Invoke() - } - finally { - Exit-ActionOutputGroup - } -} - - -########################################################################### -## Internal Implementation - Private for Now... -########################################################################### - -## Used to signal output that is a command to Action/Workflow context -if (-not (Get-Variable -Scope Script -Name CMD_STRING -ErrorAction SilentlyContinue)) { - Set-Variable -Scope Script -Option Constant -Name CMD_STRING -Value '::' -} - -<# -.SYNOPSIS -Sends a command to the hosting Workflow/Action context. -.DESCRIPTION -Command Format: - ::name key=value;key=value##message - -.EXAMPLE -::warning::This is the user warning message -.EXAMPLE -::set-secret name=mypassword::definitelyNotAPassword! -#> -function Send-ActionCommand { - param( - [Parameter(Position=0, Mandatory)] - [string]$Command, - - [Parameter(ParameterSetName="WithProps", Position=1, Mandatory)] - [hashtable]$Properties, - - [Parameter(ParameterSetName="WithProps", Position=2)] - [Parameter(ParameterSetName="SkipProps", Position=1)] - [string]$Message='' - ) - - if (-not $Command) { - $Command = 'missing.command' - } - - $cmdStr = "$($CMD_STRING)$($Command)" - if ($Properties.Count -gt 0) { - $cmdStr += ' ' - foreach ($key in $Properties.Keys) { - $val = ConvertTo-EscapedValue -Value $Properties[$key] - $cmdStr += "$($key)=$($val)" - } - } - $cmdStr += $CMD_STRING - $cmdStr += ConvertTo-EscapedData -Value $Message - $cmdStr += [System.Environment]::NewLine - - return $cmdStr -} - -function ConvertTo-EscapedData { - param( - [Parameter(Mandatory)] - [AllowEmptyString()] - [string]$Value - ) - return $Value. - Replace("`r",'%0D'). - Replace("`n",'%0A') -} - -function ConvertTo-EscapedValue { - param( - [Parameter(Mandatory)] - [AllowEmptyString()] - [string]$Value - ) - return $Value. - Replace("`r",'%0D'). - Replace("`n",'%0A'). - Replace(';','%3B'). - Replace(']','%5D'). - Replace(',','%2C'). - Replace(':','%3A') -} diff --git a/package-lock.json b/package-lock.json index 5b70650..77f6310 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,14 +5,22 @@ "requires": true, "dependencies": { "@actions/core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.1.3.tgz", - "integrity": "sha512-2BIib53Jh4Cfm+1XNuZYYGTeRo8yiWEAUMoliMh1qQGMaqTF4VUlhhcsBylTu4qWmUx45DrY0y0XskimAHSqhw==" + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.4.tgz", + "integrity": "sha512-YJCEq8BE3CdN8+7HPZ/4DxJjk/OkZV2FFIf+DlZTC/4iBlzYCD5yjRR6eiOS5llO11zbRltIRuKAjMKaWTE6cg==" }, "@actions/exec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.1.tgz", - "integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz", + "integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==", + "requires": { + "@actions/io": "^1.0.1" + } + }, + "@actions/io": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz", + "integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg==" } } } diff --git a/package.json b/package.json index be1bd22..7629c51 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "author": "EBekker", "license": "MIT", "dependencies": { - "@actions/core": "^1.1.3", - "@actions/exec": "^1.0.1" + "@actions/core": "^1.2.4", + "@actions/exec": "^1.0.4" } } diff --git a/tests/ActionCore_tests.ps1 b/tests/GitHubActions_tests.ps1 similarity index 98% rename from tests/ActionCore_tests.ps1 rename to tests/GitHubActions_tests.ps1 index d26f310..3d0d61e 100644 --- a/tests/ActionCore_tests.ps1 +++ b/tests/GitHubActions_tests.ps1 @@ -1,9 +1,8 @@ Import-Module Pester +Import-Module GitHubActions -. $PSScriptRoot/../lib/ActionsCore.ps1 - -Set-Variable -Scope Script -Option Constant -Name EOL -Value ([System.Environment]::NewLine) +Set-Variable -Scope Script -Option Constant -Name EOL -Value ([System.Environment]::NewLine) -ErrorAction Ignore Describe 'Set-ActionVariable' { $testCases = @(