From 5f66339fde9cd037162ae60032e7782a0b90cc77 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Thu, 16 May 2019 16:40:21 -0400 Subject: [PATCH 01/34] Add Bryan's core code --- packages/core/README.md | 11 ++ packages/core/__tests__/lib.test.ts | 29 ++++++ packages/core/package.json | 34 +++++++ packages/core/src/interfaces.ts | 31 ++++++ packages/core/src/internal.ts | 149 ++++++++++++++++++++++++++++ packages/core/src/lib.ts | 56 +++++++++++ packages/core/tsconfig.json | 11 ++ 7 files changed, 321 insertions(+) create mode 100644 packages/core/README.md create mode 100644 packages/core/__tests__/lib.test.ts create mode 100644 packages/core/package.json create mode 100644 packages/core/src/interfaces.ts create mode 100644 packages/core/src/internal.ts create mode 100644 packages/core/src/lib.ts create mode 100644 packages/core/tsconfig.json diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 00000000..9193971e --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,11 @@ +# `@actions/core` + +> TODO: description + +## Usage + +``` +const core = require('@actions/core'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts new file mode 100644 index 00000000..3917550c --- /dev/null +++ b/packages/core/__tests__/lib.test.ts @@ -0,0 +1,29 @@ +'use strict'; + +import * as core from '../src/lib' + +describe('@actions/core', () => { + it('needs tests', () => { + + }); +}); + + +// it('exits successfully', () => { +// jest.spyOn(process, 'exit').mockImplementation() +// core.fail('testing fail'); +// exit.success() +// expect(process.exit).toHaveBeenCalledWith(0) +// }) + +// it('exits as a failure', () => { +// jest.spyOn(process, 'exit').mockImplementation() +// exit.failure() +// expect(process.exit).toHaveBeenCalledWith(1) +// }) + +// it('exits neutrally', () => { +// jest.spyOn(process, 'exit').mockImplementation() +// exit.neutral() +// expect(process.exit).toHaveBeenCalledWith(78) +// }) diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 00000000..1a922ff2 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,34 @@ +{ + "name": "@actions/core", + "version": "1.0.0", + "description": "Actions core lib", + "keywords": [ + "core", + "actions" + ], + "author": "Bryan MacFarlane ", + "homepage": "https://github.com/actions/toolkit/tree/master/packages/io", + "license": "MIT", + "main": "lib/lib.js", + "directories": { + "lib": "lib", + "test": "__tests__" + }, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/actions/toolkit.git" + }, + "scripts": { + "test": "echo \"Error: run tests from root\" && exit 1", + "tsc": "tsc" + }, + "bugs": { + "url": "https://github.com/actions/toolkit/issues" + } +} diff --git a/packages/core/src/interfaces.ts b/packages/core/src/interfaces.ts new file mode 100644 index 00000000..164ca103 --- /dev/null +++ b/packages/core/src/interfaces.ts @@ -0,0 +1,31 @@ +export interface FileDetails { + /** + * Full path to the file causing the issue. + * Note: the agent will translate to the proper repo when posting + * the issue back to the timeline + */ + File: string, + Line: number, + Column: number +} + +/** + * The code to exit an action + * Spec: https://github.com/github/dreamlifter/blob/master/docs/actions-model.md#exit-codes + */ +export enum ExitCode { + /** + * A code indicating that the action was successful + */ + Success = 0, + + /** + * A code indicating that the action was a failure + */ + Failure = 1, + + /** + * A code indicating that the action is complete, but neither succeeded nor failed + */ + Neutral = 78 +} \ No newline at end of file diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts new file mode 100644 index 00000000..1d2e867c --- /dev/null +++ b/packages/core/src/internal.ts @@ -0,0 +1,149 @@ +import os = require('os'); + + +/** + * Commands + * Spec: https://github.com/github/dreamlifter/blob/master/docs/actions-model.md#logging-commands + * + * Command Format: + * ##[name key=value;key=value]message + * + * Examples: + * ##[warning]This is the user warning message + * ##[set-secret name=mypassword]definatelyNotAPassword! + */ +export function _issueCommand(command: string, properties: {[key: string]: string}, message: string) { + var cmd = new _Command(command, properties, message); + _writeLine(cmd.toString()); +} + +export function _issue(name: string, message: string) { + _issueCommand(name, {}, message); +} + +let CMD_PREFIX = '##['; + +export class _Command { + constructor(command: string, properties: any, message: string) { + if (!command) { + command = 'missing.command'; + } + + this.command = command; + this.properties = properties; + this.message = message; + } + + public command: string; + public message: string; + public properties: any; + + public toString() { + var cmdStr = CMD_PREFIX + this.command; + + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' '; + for (var key in this.properties) { + if (this.properties.hasOwnProperty(key)) { + var 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 || '')) + ';'; + } + } + } + } + + cmdStr += ']'; + + // safely append the message - avoid blowing up when attempting to + // call .replace() if message is not a string for some reason + let message: string = '' + (this.message || ''); + cmdStr += escapedata(message); + + return cmdStr; + } +} + +export function _commandFromString(commandLine: string) { + var preLen = CMD_PREFIX.length; + var lbPos = commandLine.indexOf('['); + var rbPos = commandLine.indexOf(']'); + if (lbPos == -1 || rbPos == -1 || rbPos - lbPos < 3) { + throw new Error('Invalid command brackets'); + } + var cmdInfo = commandLine.substring(lbPos + 1, rbPos); + var spaceIdx = cmdInfo.indexOf(' '); + + var command = cmdInfo; + var properties: {[key: string]: string} = {}; + + if (spaceIdx > 0) { + command = cmdInfo.trim().substring(0, spaceIdx); + var propSection = cmdInfo.trim().substring(spaceIdx+1); + + var propLines: string[] = propSection.split(';'); + propLines.forEach(function (propLine: string) { + propLine = propLine.trim(); + if (propLine.length > 0) { + var eqIndex = propLine.indexOf('='); + if (eqIndex == -1){ + throw new Error('Invalid property: ' + propLine); + } + + var key: string = propLine.substring(0, eqIndex); + var val: string = propLine.substring(eqIndex+1); + + properties[key] = unescape(val); + } + }); + } + + let msg: string = unescapedata(commandLine.substring(rbPos + 1)); + var cmd = new _Command(command, properties, msg); + return cmd; +} + +function escapedata(s: string) : string { + return s.replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} + +function unescapedata(s: string) : string { + return s.replace(/%0D/g, '\r') + .replace(/%0A/g, '\n'); +} + +function escape(s: string) : string { + return s.replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/]/g, '%5D') + .replace(/;/g, '%3B'); +} + +function unescape(s: string) : string { + return s.replace(/%0D/g, '\r') + .replace(/%0A/g, '\n') + .replace(/%5D/g, ']') + .replace(/%3B/g, ';'); +} + +//----------------------------------------------------- +// Streams: allow to override the stream +//----------------------------------------------------- + +let _outStream = process.stdout; +let _errStream = process.stderr; + +export function _writeLine(str: string): void { + _outStream.write(str + os.EOL); +} + +export function _setStdStream(stdStream: NodeJS.WriteStream): void { + _outStream = stdStream; +} + +export function _setErrStream(errStream: NodeJS.WriteStream): void { + _errStream = errStream; +} \ No newline at end of file diff --git a/packages/core/src/lib.ts b/packages/core/src/lib.ts new file mode 100644 index 00000000..6b662560 --- /dev/null +++ b/packages/core/src/lib.ts @@ -0,0 +1,56 @@ +import im = require('./interfaces'); +import intm = require('./internal'); +import process = require('process'); + +/** + * 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 + */ +export function setVariable(name: string, val: string) { + process.env[name] = val; + intm._issueCommand('set-variable', {'name': name}, val); +} + +/** + * sets a variable which will get masked from logs + * @param name name of the secret variable + * @param val value of the secret variable + */ +export function setSecret(name: string, val: string) { + intm._issueCommand('set-secret', {'name': name}, val); + setVariable(name, val); +} + +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- +/** + * fail the action + * @param message + */ +export function fail(message: string) { + process.exitCode = im.ExitCode.Failure; + error(message); +} + +//----------------------------------------------------------------------- +// Logging Commands +// https://github.com/github/dreamlifter/blob/master/docs/actions-model.md#logging-commands +// +// error and warning issues do not take FileDetails because while possible, +// that's typically reserved for the agent and the problem matchers. +// +//----------------------------------------------------------------------- + +export function addPath(path: string) { + intm._issueCommand('add-path', {}, path); +} + +export function error(message: string) { + intm._issue('error', message); +} + +export function warning(message: string) { + intm._issue('warning', message); +} \ No newline at end of file diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 00000000..a8b812a6 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./lib", + "rootDir": "./src" + }, + "include": [ + "./src" + ] +} \ No newline at end of file From 917c389219cf89dcc3bb0068f8f5b16d2b282627 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Thu, 16 May 2019 17:16:39 -0400 Subject: [PATCH 02/34] Match core to spec (still work to do) --- packages/core/__tests__/lib.test.ts | 24 +++++++++++- packages/core/package.json | 2 +- packages/core/src/internal.ts | 3 +- packages/core/src/lib.ts | 61 ++++++++++++++++++----------- 4 files changed, 63 insertions(+), 27 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index 3917550c..012d0963 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -3,8 +3,28 @@ import * as core from '../src/lib' describe('@actions/core', () => { - it('needs tests', () => { - + it('tests exportVariable', () => { + // TODO + }); + + it('tests getInput', () => { + // TODO + }); + + it('tests setFailure', () => { + // TODO + }); + + it('tests error', () => { + // TODO + }); + + it('tests warning', () => { + // TODO + }); + + it('tests debug', () => { + // TODO }); }); diff --git a/packages/core/package.json b/packages/core/package.json index 1a922ff2..52200e5b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -7,7 +7,7 @@ "actions" ], "author": "Bryan MacFarlane ", - "homepage": "https://github.com/actions/toolkit/tree/master/packages/io", + "homepage": "https://github.com/actions/toolkit/tree/master/packages/core", "license": "MIT", "main": "lib/lib.js", "directories": { diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 1d2e867c..38a7fd8e 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -10,7 +10,7 @@ import os = require('os'); * * Examples: * ##[warning]This is the user warning message - * ##[set-secret name=mypassword]definatelyNotAPassword! + * ##[set-secret name=mypassword]definitelyNotAPassword! */ export function _issueCommand(command: string, properties: {[key: string]: string}, message: string) { var cmd = new _Command(command, properties, message); @@ -67,7 +67,6 @@ export class _Command { } export function _commandFromString(commandLine: string) { - var preLen = CMD_PREFIX.length; var lbPos = commandLine.indexOf('['); var rbPos = commandLine.indexOf(']'); if (lbPos == -1 || rbPos == -1 || rbPos - lbPos < 3) { diff --git a/packages/core/src/lib.ts b/packages/core/src/lib.ts index 6b662560..68e97094 100644 --- a/packages/core/src/lib.ts +++ b/packages/core/src/lib.ts @@ -3,33 +3,53 @@ import intm = require('./internal'); import process = require('process'); /** - * 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 + * Interface for exportVariable options */ -export function setVariable(name: string, val: string) { +export interface ExportOptions { + /** Optional. Whether the variable should be marked as secret (will be masked from logs). Defaults to false */ + isSecret?: boolean; +} + +/** + * 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 options optional. See ExportOptions. + */ +export function exportVariable(name: string, val: string, options?: ExportOptions): void { + if (options && options.isSecret) { + intm._issueCommand('set-secret', {'name': name}, val); + } process.env[name] = val; intm._issueCommand('set-variable', {'name': name}, val); } /** - * sets a variable which will get masked from logs - * @param name name of the secret variable - * @param val value of the secret variable + * Interface for getInput options */ -export function setSecret(name: string, val: string) { - intm._issueCommand('set-secret', {'name': name}, val); - setVariable(name, val); -} +export interface InputOptions { + /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ + required?: boolean; +} + +/** + * Gets the value of an input. The value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +export function getInput(name: string, options?: InputOptions): string | undefined { + // TODO - how are we passing in actions inputs? + return ''; +} -//----------------------------------------------------------------------- -// Results -//----------------------------------------------------------------------- /** * fail the action * @param message */ -export function fail(message: string) { +export function setFailure(message: string): void { process.exitCode = im.ExitCode.Failure; error(message); } @@ -38,19 +58,16 @@ export function fail(message: string) { // Logging Commands // https://github.com/github/dreamlifter/blob/master/docs/actions-model.md#logging-commands // -// error and warning issues do not take FileDetails because while possible, -// that's typically reserved for the agent and the problem matchers. -// //----------------------------------------------------------------------- -export function addPath(path: string) { - intm._issueCommand('add-path', {}, path); -} - export function error(message: string) { intm._issue('error', message); } export function warning(message: string) { intm._issue('warning', message); +} + +export function debug(message: string): void { + intm._issue('debug', message); } \ No newline at end of file From 780a5985b46ab5d626d73dae1e4b64c9e8927026 Mon Sep 17 00:00:00 2001 From: Bryan MacFarlane Date: Thu, 16 May 2019 23:36:45 -0400 Subject: [PATCH 03/34] starting on core --- packages/core/README.md | 53 +++++++++++++++++++++-- packages/core/package-lock.json | 14 ++++++ packages/core/package.json | 5 ++- packages/core/src/core.ts | 75 +++++++++++++++++++++++++++++++++ packages/core/src/interfaces.ts | 20 ++++----- packages/core/src/internal.ts | 6 +-- packages/core/src/lib.ts | 73 -------------------------------- 7 files changed, 154 insertions(+), 92 deletions(-) create mode 100644 packages/core/package-lock.json create mode 100644 packages/core/src/core.ts delete mode 100644 packages/core/src/lib.ts diff --git a/packages/core/README.md b/packages/core/README.md index 9193971e..6a1807fb 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,11 +1,58 @@ # `@actions/core` -> TODO: description +> Core functions for setting results, logging, registering secrets and exporting variables across actions ## Usage ``` -const core = require('@actions/core'); +/** + * 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 + */ +export function exportVariable(name: string, val: string, options?:im.ExportOptions); -// TODO: DEMONSTRATE API +/** + * registers a secret which will get masked from logs + * @param val value of the secret + */ +export function setSecret(val: string); + +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- + +/** + * Sets the action status to neutral + */ +export function setNeutral(); + +/** + * Sets the action status to failed. + * When the action exits it will be with an exit code of 1 + * @param message add error issue message + */ +export function setFailed(message: string); + +//----------------------------------------------------------------------- +// Logging Commands +//----------------------------------------------------------------------- + +/** + * Writes debug message to user log + * @param message debug message + */ +export function debug(message: string); + +/** + * Adds an error issue + * @param message error issue message + */ +export function error(message: string); + +/** + * Adds an warning issue + * @param message warning issue message + */ +export function warning(message: string); ``` diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json new file mode 100644 index 00000000..fef7996a --- /dev/null +++ b/packages/core/package-lock.json @@ -0,0 +1,14 @@ +{ + "name": "@actions/core", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", + "integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==", + "dev": true + } + } +} diff --git a/packages/core/package.json b/packages/core/package.json index 52200e5b..1c6b1181 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@actions/core", - "version": "1.0.0", + "version": "0.1.0", "description": "Actions core lib", "keywords": [ "core", @@ -30,5 +30,8 @@ }, "bugs": { "url": "https://github.com/actions/toolkit/issues" + }, + "devDependencies": { + "@types/node": "^12.0.2" } } diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts new file mode 100644 index 00000000..87910a0c --- /dev/null +++ b/packages/core/src/core.ts @@ -0,0 +1,75 @@ +import im = require('./interfaces'); +import intm = require('./internal'); +import process = require('process'); + +/** + * 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 + */ +export function exportVariable(name: string, val: string, options?:im.ExportOptions) { + process.env[name] = val; + let props = {'name': name, 'isSecret': options? options.isSecret : false}; + intm._issueCommand('set-variable', props, val); +} + +/** + * registers a secret which will get masked from logs + * @param val value of the secret + */ +export function setSecret(val: string) { + intm._issueCommand('set-secret', {}, val); +} + +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- + +/** + * Sets the action status to neutral + */ +export function setNeutral() { + process.exitCode = im.ExitCode.Neutral; +} + +/** + * Sets the action status to failed. + * When the action exits it will be with an exit code of 1 + * @param message add error issue message + */ +export function setFailed(message: string) { + process.exitCode = im.ExitCode.Failure; + error(message); +} + +//----------------------------------------------------------------------- +// Logging Commands +// +// error and warning issues do not take FileDetails because while possible, +// that's typically reserved for the agent and the problem matchers. +// +//----------------------------------------------------------------------- + +/** + * Writes debug message to user log + * @param message debug message + */ +export function debug(message: string) { + intm._issueCommand('debug', {}, message); +} + +/** + * Adds an error issue + * @param message error issue message + */ +export function error(message: string) { + intm._issue('error', message); +} + +/** + * Adds an warning issue + * @param message warning issue message + */ +export function warning(message: string) { + intm._issue('warning', message); +} diff --git a/packages/core/src/interfaces.ts b/packages/core/src/interfaces.ts index 164ca103..16696ed9 100644 --- a/packages/core/src/interfaces.ts +++ b/packages/core/src/interfaces.ts @@ -1,17 +1,5 @@ -export interface FileDetails { - /** - * Full path to the file causing the issue. - * Note: the agent will translate to the proper repo when posting - * the issue back to the timeline - */ - File: string, - Line: number, - Column: number -} - /** * The code to exit an action - * Spec: https://github.com/github/dreamlifter/blob/master/docs/actions-model.md#exit-codes */ export enum ExitCode { /** @@ -28,4 +16,12 @@ export enum ExitCode { * A code indicating that the action is complete, but neither succeeded nor failed */ Neutral = 78 +} + +/** + * Interface for exportVariable options + */ +export interface ExportOptions { + /** Optional. Whether the variable should be marked as secret (will be masked from logs). Defaults to false */ + isSecret?: boolean; } \ No newline at end of file diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 38a7fd8e..af00f9a9 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -3,16 +3,15 @@ import os = require('os'); /** * Commands - * Spec: https://github.com/github/dreamlifter/blob/master/docs/actions-model.md#logging-commands * * Command Format: * ##[name key=value;key=value]message * * Examples: * ##[warning]This is the user warning message - * ##[set-secret name=mypassword]definitelyNotAPassword! + * ##[set-secret name=mypassword]definatelyNotAPassword! */ -export function _issueCommand(command: string, properties: {[key: string]: string}, message: string) { +export function _issueCommand(command: string, properties: any, message: string) { var cmd = new _Command(command, properties, message); _writeLine(cmd.toString()); } @@ -67,6 +66,7 @@ export class _Command { } export function _commandFromString(commandLine: string) { + var preLen = CMD_PREFIX.length; var lbPos = commandLine.indexOf('['); var rbPos = commandLine.indexOf(']'); if (lbPos == -1 || rbPos == -1 || rbPos - lbPos < 3) { diff --git a/packages/core/src/lib.ts b/packages/core/src/lib.ts deleted file mode 100644 index 68e97094..00000000 --- a/packages/core/src/lib.ts +++ /dev/null @@ -1,73 +0,0 @@ -import im = require('./interfaces'); -import intm = require('./internal'); -import process = require('process'); - -/** - * Interface for exportVariable options - */ -export interface ExportOptions { - /** Optional. Whether the variable should be marked as secret (will be masked from logs). Defaults to false */ - isSecret?: boolean; -} - -/** - * 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 options optional. See ExportOptions. - */ -export function exportVariable(name: string, val: string, options?: ExportOptions): void { - if (options && options.isSecret) { - intm._issueCommand('set-secret', {'name': name}, val); - } - process.env[name] = val; - intm._issueCommand('set-variable', {'name': name}, val); -} - -/** - * Interface for getInput options - */ -export interface InputOptions { - /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ - required?: boolean; -} - -/** - * Gets the value of an input. The value is also trimmed. - * - * @param name name of the input to get - * @param options optional. See InputOptions. - * @returns string - */ -export function getInput(name: string, options?: InputOptions): string | undefined { - // TODO - how are we passing in actions inputs? - return ''; -} - -/** - * fail the action - * @param message - */ -export function setFailure(message: string): void { - process.exitCode = im.ExitCode.Failure; - error(message); -} - -//----------------------------------------------------------------------- -// Logging Commands -// https://github.com/github/dreamlifter/blob/master/docs/actions-model.md#logging-commands -// -//----------------------------------------------------------------------- - -export function error(message: string) { - intm._issue('error', message); -} - -export function warning(message: string) { - intm._issue('warning', message); -} - -export function debug(message: string): void { - intm._issue('debug', message); -} \ No newline at end of file From 2697357344e14ad6af08a8c3ce1b1eba2578394f Mon Sep 17 00:00:00 2001 From: Bryan MacFarlane Date: Thu, 16 May 2019 18:45:34 -0700 Subject: [PATCH 04/34] Spec for tool config apis (#3) * creating an initial branch for config apis spec * Add package specs * mkdirP should return promise * Which should return a promise * Respond to feedback * export => exportVariable * Respond to feedback on spec * Remove code for now * trailing space * Clarify throwing --- docs/package-specs.md | 200 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 docs/package-specs.md diff --git a/docs/package-specs.md b/docs/package-specs.md new file mode 100644 index 00000000..21861fb0 --- /dev/null +++ b/docs/package-specs.md @@ -0,0 +1,200 @@ +# Package Specs + +In order to support the node-config action, I propose adding the following into 4 libraries (tool-cache, core, io, exec), with tool-cache having dependencies on the other 3 libs: + +### Core spec + +Holds all the functions necessary for interacting with the runner/environment. + +``` +// Logging functions +export function debug(message: string): void +export function warning(message: string): void +export function error(message: string): void + +/** + * Interface for exportVariable options + */ +export interface ExportOptions { + /** Optional. Whether the variable should be marked as secret (will be masked from logs). Defaults to false */ + isSecret?: bool; +} + +/** + * 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 options optional. See ExportOptions. + */ +export function exportVariable(name: string, val: string, options?: ExportOptions): void + +/** + * Interface for getInput options + */ +export interface InputOptions { + /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ + required?: bool; +} + +/** + * Gets the value of an input. The value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +export function getInput(name: string, options?: InputOptions): string | undefined + +/** + * fail the action + * @param message + */ +export function setFailure(message: string): void +``` + +### IO spec + +Holds all the functions necessary for file system manipulation (cli scenarios, not fs replacements): + +``` +/** + * Interface for cp/mv options + */ +export interface CopyOptions { + /** Optional. Whether to recursively copy all subdirectories. Defaults to false */ + recursive?: boolean; + /** Optional. Whether to overwrite existing files in the destination. Defaults to true */ + force?: boolean; +} + +/** + * Copies a file or folder. + * + * @param source source path + * @param dest destination path + * @param options optional. See CopyOptions. + */ +export function cp(source: string, dest: string, options?: CopyOptions): Promise + +/** + * Remove a path recursively with force + * + * @param path path to remove + */ +export function rmRF(path: string): Promise + +/** + * Make a directory. Creates the full path with folders in between + * + * @param p path to create + * @returns Promise + */ +export function mkdirP(p: string): Promise + +/** + * Moves a path. + * + * @param source source path + * @param dest destination path + * @param options optional. See CopyOptions. + */ +export function mv(source: string, dest: string, options?: CopyOptions): Promise + +/** + * Interface for which options + */ +export interface WhichOptions { + /** Optional. Whether to check if tool exists. If true, will throw if it fails. Defaults to false */ + check?: boolean; +} + +/** + * Returns path of a tool had the tool actually been invoked. Resolves via paths. + * + * @param tool name of the tool + * @param options optional. See WhichOptions. + * @returns Promise path to tool + */ +export function which(tool: string, options?: WhichOptions): Promise +``` + +### Exec spec + +Holds all the functions necessary for running the tools node-config depends on (aka 7-zip and tar) + +``` +/** + * Interface for exec options + */ +export interface IExecOptions + +/** +* Exec a command. +* Output will be streamed to the live console. +* Returns promise with return code +* +* @param commandLine command to execute +* @param args optional additional arguments +* @param options optional exec options. See IExecOptions +* @returns Promise return code +*/ +export function exec(commandLine: string, args?: string[], options?: IExecOptions): Promise +``` + +### Tool-Cache spec: + +Holds all the functions necessary for downloading and caching node. + +``` +/** + * Download a tool from an url and stream it into a file + * + * @param url url of tool to download + * @returns path to downloaded tool + */ +export async function downloadTool(url: string): Promise + +/** + * Extract a .7z file + * + * @param file path to the .7z file + * @param dest destination directory. Optional. + * @returns path to the destination directory + */ +export async function extract7z(file: string, dest?: string): Promise + +/** + * Extract a tar + * + * @param file path to the tar + * @param dest destination directory. Optional. + * @returns path to the destination directory + */ +export async function extractTar(file: string, destination?: string): Promise + +/** + * Caches a directory and installs it into the tool cacheDir + * + * @param sourceDir the directory to cache into tools + * @param tool tool name + * @param version version of the tool. semver format + * @param arch architecture of the tool. Optional. Defaults to machine architecture + */ +export async function cacheDir(sourceDir: string, tool: string, version: string, arch?: string): Promise + +/** + * finds the path to a tool in the local installed tool cache + * + * @param toolName name of the tool + * @param versionSpec version of the tool + * @param arch optional arch. defaults to arch of computer + */ +export function find(toolName: string, versionSpec: string, arch?: string): string + +/** + * Prepends inputPath to the PATH + * @param inputPath + */ +export function addPath(inputPath: string): void +``` From d7423efd98754922df234848eba17398db59356c Mon Sep 17 00:00:00 2001 From: Bryan MacFarlane Date: Fri, 17 May 2019 10:04:20 -0400 Subject: [PATCH 05/34] spec update --- docs/package-specs.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/package-specs.md b/docs/package-specs.md index 21861fb0..115df9e2 100644 --- a/docs/package-specs.md +++ b/docs/package-specs.md @@ -12,14 +12,6 @@ export function debug(message: string): void export function warning(message: string): void export function error(message: string): void -/** - * Interface for exportVariable options - */ -export interface ExportOptions { - /** Optional. Whether the variable should be marked as secret (will be masked from logs). Defaults to false */ - isSecret?: bool; -} - /** * sets env variable for this action and future actions in the job * @@ -27,7 +19,7 @@ export interface ExportOptions { * @param val the value of the variable * @param options optional. See ExportOptions. */ -export function exportVariable(name: string, val: string, options?: ExportOptions): void +export function exportVariable(name: string, val: string): void /** * Interface for getInput options @@ -47,10 +39,16 @@ export interface InputOptions { export function getInput(name: string, options?: InputOptions): string | undefined /** - * fail the action + * sets the status of the action to neutral * @param message */ -export function setFailure(message: string): void +export function setFailed(message: string): void + +/** + * sets the status of the action to failed + * @param message + */ +export function setFailed(message: string): void ``` ### IO spec From a97380e90dadcf56ed35a6d638002c420507f0c0 Mon Sep 17 00:00:00 2001 From: Bryan MacFarlane Date: Fri, 17 May 2019 10:23:01 -0400 Subject: [PATCH 06/34] update inputs and variables --- packages/core/README.md | 10 ++++++++-- packages/core/src/core.ts | 35 ++++++++++++++++++++++++--------- packages/core/src/interfaces.ts | 8 ++++---- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/packages/core/README.md b/packages/core/README.md index 6a1807fb..8baaab5f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -5,18 +5,24 @@ ## Usage ``` +//----------------------------------------------------------------------- +// Variables, Inputs and Outputs +//----------------------------------------------------------------------- + /** * 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 */ -export function exportVariable(name: string, val: string, options?:im.ExportOptions); +export function exportVariable(name: string, val: string); /** * registers a secret which will get masked from logs * @param val value of the secret */ -export function setSecret(val: string); +export function setSecret(name: string, val: string); + +// TODO: follow up and see if we need anything for outputs //----------------------------------------------------------------------- // Results diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 87910a0c..013a9a28 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -2,25 +2,46 @@ import im = require('./interfaces'); import intm = require('./internal'); import process = require('process'); +//----------------------------------------------------------------------- +// Variables +//----------------------------------------------------------------------- + /** * 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 */ -export function exportVariable(name: string, val: string, options?:im.ExportOptions) { +export function exportVariable(name: string, val: string) { process.env[name] = val; - let props = {'name': name, 'isSecret': options? options.isSecret : false}; - intm._issueCommand('set-variable', props, val); + intm._issueCommand('set-variable', {'name': name}, val); } /** - * registers a secret which will get masked from logs + * exports the variable and registers a secret which will get masked from logs + * @param name the name of the variable to set * @param val value of the secret */ -export function setSecret(val: string) { +export function setSecret(name: string, val: string) { + exportVariable(name, val); intm._issueCommand('set-secret', {}, val); } +/** + * Gets the value of an input. The value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +export function getInput(name: string, options?: im.InputOptions): string { + let val:string = process.env['INPUT_' + name]; + if (options && options.required && !val) { + throw new Error(`Input required and not supplied: ${name}`); + } + + return val; +} + //----------------------------------------------------------------------- // Results //----------------------------------------------------------------------- @@ -44,10 +65,6 @@ export function setFailed(message: string) { //----------------------------------------------------------------------- // Logging Commands -// -// error and warning issues do not take FileDetails because while possible, -// that's typically reserved for the agent and the problem matchers. -// //----------------------------------------------------------------------- /** diff --git a/packages/core/src/interfaces.ts b/packages/core/src/interfaces.ts index 16696ed9..bca96a40 100644 --- a/packages/core/src/interfaces.ts +++ b/packages/core/src/interfaces.ts @@ -19,9 +19,9 @@ export enum ExitCode { } /** - * Interface for exportVariable options + * Interface for getInput options */ -export interface ExportOptions { - /** Optional. Whether the variable should be marked as secret (will be masked from logs). Defaults to false */ - isSecret?: boolean; +export interface InputOptions { + /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ + required?: boolean; } \ No newline at end of file From a526749ce96e66c4e8c32ddc43f099c6830d0345 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Fri, 17 May 2019 10:23:39 -0400 Subject: [PATCH 07/34] Add tests --- packages/core/__tests__/lib.test.ts | 104 ++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 30 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index 012d0963..bae8f580 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -1,49 +1,93 @@ 'use strict'; -import * as core from '../src/lib' +import * as core from '../src/core'; +import * as os from 'os'; describe('@actions/core', () => { - it('tests exportVariable', () => { - // TODO + beforeAll(() => { + // Set inputs + process.env['INPUT_MY_INPUT'] = 'val'; + process.env['INPUT_MISSING'] = ''; + process.env['INPUT_SPECIAL_CHARS_\'\t\"\\'] = '\'\t\"\\ repsonse '; }); - it('tests getInput', () => { - // TODO + it('getInput gets non-required input', () => { + expect(core.getInput('my input')).toBe('val'); }); - it('tests setFailure', () => { - // TODO + it('getInput gets required input', () => { + expect(core.getInput('my input', {required: true})).toBe('val'); }); - it('tests error', () => { - // TODO + it('getInput throws on missing required input', () => { + expect(() => core.getInput('missing', {required: true})).toThrow('Failed to find input missing'); }); - it('tests warning', () => { - // TODO + it('getInput doesnt throw on missing non-required input', () => { + expect(core.getInput('missing', {required: false})).toBe(''); }); - it('tests debug', () => { - // TODO + it('getInput is case insensitive', () => { + expect(core.getInput('My InPuT')).toBe('val'); }); -}); + it('getInput handles special characters', () => { + expect(core.getInput('special chars_\'\t\"\\')).toBe('\'\t\"\\ repsonse'); + }); -// it('exits successfully', () => { -// jest.spyOn(process, 'exit').mockImplementation() -// core.fail('testing fail'); -// exit.success() -// expect(process.exit).toHaveBeenCalledWith(0) -// }) + it('setNeutral sets the correct exit code', () => { + core.setFailed('Failure message'); + expect(process.exitCode).toBe(1); + }); -// it('exits as a failure', () => { -// jest.spyOn(process, 'exit').mockImplementation() -// exit.failure() -// expect(process.exit).toHaveBeenCalledWith(1) -// }) + it('setFailure sets the correct exit code and failure message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } -// it('exits neutrally', () => { -// jest.spyOn(process, 'exit').mockImplementation() -// exit.neutral() -// expect(process.exit).toHaveBeenCalledWith(78) -// }) + core.setFailed('Failure message'); + expect(process.exitCode).toBe(1); + expect(output).toBe('##[error]Failure message' + os.EOL); + }); + + it('error sets the correct error message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.error('Error message'); + expect(output).toBe('##[error]Error message' + os.EOL); + }); + + it('warning sets the correct message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.warning('Warning'); + expect(output).toBe('##[warning]Warning' + os.EOL); + }); + + it('debug sets the correct message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.debug('Debug'); + expect(output).toBe('##[debug]Debug' + os.EOL); + }); + + // TODO - test escaping for all commands +}); \ No newline at end of file From 792122be542f295d3fe0c6bd6b413e60e07831ae Mon Sep 17 00:00:00 2001 From: Bryan MacFarlane Date: Fri, 17 May 2019 10:29:35 -0400 Subject: [PATCH 08/34] update specs --- docs/package-specs.md | 2 +- packages/core/README.md | 66 ++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/docs/package-specs.md b/docs/package-specs.md index 115df9e2..f8c9e4e0 100644 --- a/docs/package-specs.md +++ b/docs/package-specs.md @@ -42,7 +42,7 @@ export function getInput(name: string, options?: InputOptions): string | undefin * sets the status of the action to neutral * @param message */ -export function setFailed(message: string): void +export function setNeutral(message: string): void /** * sets the status of the action to failed diff --git a/packages/core/README.md b/packages/core/README.md index 8baaab5f..a4fa04d8 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -5,60 +5,46 @@ ## Usage ``` -//----------------------------------------------------------------------- -// Variables, Inputs and Outputs -//----------------------------------------------------------------------- +// Logging functions +export function debug(message: string): void +export function warning(message: string): void +export function error(message: string): void /** * 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 name the name of the variable to set + * @param val the value of the variable + * @param options optional. See ExportOptions. */ -export function exportVariable(name: string, val: string); +export function exportVariable(name: string, val: string): void /** - * registers a secret which will get masked from logs - * @param val value of the secret + * Interface for getInput options */ -export function setSecret(name: string, val: string); - -// TODO: follow up and see if we need anything for outputs - -//----------------------------------------------------------------------- -// Results -//----------------------------------------------------------------------- +export interface InputOptions { + /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ + required?: bool; +} /** - * Sets the action status to neutral + * Gets the value of an input. The value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string */ -export function setNeutral(); +export function getInput(name: string, options?: InputOptions): string | undefined /** - * Sets the action status to failed. - * When the action exits it will be with an exit code of 1 - * @param message add error issue message + * sets the status of the action to neutral + * @param message */ -export function setFailed(message: string); - -//----------------------------------------------------------------------- -// Logging Commands -//----------------------------------------------------------------------- +export function setNeutral(message: string): void /** - * Writes debug message to user log - * @param message debug message + * sets the status of the action to failed + * @param message */ -export function debug(message: string); - -/** - * Adds an error issue - * @param message error issue message - */ -export function error(message: string); - -/** - * Adds an warning issue - * @param message warning issue message - */ -export function warning(message: string); +export function setFailed(message: string): void ``` From 5f31b6acfcfd59b296734c6dd5521ff670045356 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Fri, 17 May 2019 10:31:07 -0400 Subject: [PATCH 09/34] Add tests, getInput should be case-insensitive and trim output --- packages/core/__tests__/lib.test.ts | 2 +- packages/core/package.json | 2 +- packages/core/src/core.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index bae8f580..292f20c6 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -20,7 +20,7 @@ describe('@actions/core', () => { }); it('getInput throws on missing required input', () => { - expect(() => core.getInput('missing', {required: true})).toThrow('Failed to find input missing'); + expect(() => core.getInput('missing', {required: true})).toThrow('Input required and not supplied: missing'); }); it('getInput doesnt throw on missing non-required input', () => { diff --git a/packages/core/package.json b/packages/core/package.json index 1c6b1181..3f39447a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -9,7 +9,7 @@ "author": "Bryan MacFarlane ", "homepage": "https://github.com/actions/toolkit/tree/master/packages/core", "license": "MIT", - "main": "lib/lib.js", + "main": "lib/core.js", "directories": { "lib": "lib", "test": "__tests__" diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 013a9a28..a9baa76d 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -34,12 +34,12 @@ export function setSecret(name: string, val: string) { * @returns string */ export function getInput(name: string, options?: im.InputOptions): string { - let val:string = process.env['INPUT_' + name]; + let val: string = process.env['INPUT_' + name.replace(' ', '_').toUpperCase()] || ''; if (options && options.required && !val) { throw new Error(`Input required and not supplied: ${name}`); } - return val; + return val.trim(); } //----------------------------------------------------------------------- From 2e729086fa3e4303a6b57f5d751c513fe3b6591b Mon Sep 17 00:00:00 2001 From: Bryan MacFarlane Date: Fri, 17 May 2019 10:38:16 -0400 Subject: [PATCH 10/34] spec update --- docs/package-specs.md | 10 ++++++++++ packages/core/README.md | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/package-specs.md b/docs/package-specs.md index f8c9e4e0..76dcecf4 100644 --- a/docs/package-specs.md +++ b/docs/package-specs.md @@ -21,6 +21,16 @@ export function error(message: string): void */ export function exportVariable(name: string, val: string): void +/** + * exports the variable and registers a secret which will get masked from logs + * @param name the name of the variable to set + * @param val value of the secret + */ +export function exportSecret(name: string, val: string) { + exportVariable(name, val); + intm._issueCommand('set-secret', {}, val); +} + /** * Interface for getInput options */ diff --git a/packages/core/README.md b/packages/core/README.md index a4fa04d8..3aca1690 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -19,6 +19,16 @@ export function error(message: string): void */ export function exportVariable(name: string, val: string): void +/** + * exports the variable and registers a secret which will get masked from logs + * @param name the name of the variable to set + * @param val value of the secret + */ +export function exportSecret(name: string, val: string) { + exportVariable(name, val); + intm._issueCommand('set-secret', {}, val); +} + /** * Interface for getInput options */ From e6cc6dc147409537c4207089716af6bf70716203 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Fri, 17 May 2019 14:12:54 -0400 Subject: [PATCH 11/34] Finish test suite --- packages/core/__tests__/lib.test.ts | 137 +++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index 292f20c6..5b2a3319 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -4,13 +4,99 @@ import * as core from '../src/core'; import * as os from 'os'; describe('@actions/core', () => { - beforeAll(() => { + beforeEach(() => { + // Clear variables + process.env['my var'] = ''; + process.env['special char var \r\n];'] = ''; + process.env['my var2'] = ''; + process.env['my secret'] = ''; + process.env['special char secret \r\n];'] = ''; + process.env['my secret2'] = ''; + // Set inputs process.env['INPUT_MY_INPUT'] = 'val'; process.env['INPUT_MISSING'] = ''; process.env['INPUT_SPECIAL_CHARS_\'\t\"\\'] = '\'\t\"\\ repsonse '; }); + it('exportVariable produces the correct command and sets the env', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.exportVariable('my var', 'var val'); + expect(process.env['my var']).toBe('var val'); + expect(output).toBe('##[set-variable name=my var;]var val' + os.EOL); + }); + + it('exportVariable escapes variable names', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.exportVariable('special char var \r\n];', 'special val'); + expect(process.env['special char var \r\n];']).toBe('special val'); + expect(output).toBe('##[set-variable name=special char var %0D%0A%5D%3B;]special val' + os.EOL); + }); + + it('exportVariable escapes variable values', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.exportVariable('my var2', 'var val\r\n'); + expect(process.env['my var2']).toBe('var val\r\n'); + expect(output).toBe('##[set-variable name=my var2;]var val%0D%0A' + os.EOL); + }); + + it('setSecret produces the correct commands and sets the env', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.setSecret('my secret', 'secret val'); + expect(process.env['my secret']).toBe('secret val'); + expect(output).toBe('##[set-variable name=my secret;]secret val' + os.EOL + '##[set-secret]secret val' + os.EOL); + }); + + it('setSecret escapes secret names', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.setSecret('special char secret \r\n];', 'special secret val'); + expect(process.env['special char secret \r\n];']).toBe('special secret val'); + expect(output).toBe('##[set-variable name=special char secret %0D%0A%5D%3B;]special secret val' + os.EOL + '##[set-secret]special secret val' + os.EOL); + }); + + it('setSecret escapes secret values', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.setSecret('my secret2', 'secret val\r\n'); + expect(process.env['my secret2']).toBe('secret val\r\n'); + expect(output).toBe('##[set-variable name=my secret2;]secret val%0D%0A' + os.EOL + '##[set-secret]secret val%0D%0A' + os.EOL); + }); + it('getInput gets non-required input', () => { expect(core.getInput('my input')).toBe('val'); }); @@ -53,6 +139,19 @@ describe('@actions/core', () => { expect(output).toBe('##[error]Failure message' + os.EOL); }); + it('setFailure escapes the failure message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.setFailed('Failure \r\n\nmessage\r'); + expect(process.exitCode).toBe(1); + expect(output).toBe('##[error]Failure %0D%0A%0Amessage%0D' + os.EOL); + }); + it('error sets the correct error message', () => { // Override stdout and append to output so that we capture the command that is sent let output = ''; @@ -65,6 +164,18 @@ describe('@actions/core', () => { expect(output).toBe('##[error]Error message' + os.EOL); }); + it('error escapes the error message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.error('Error message\r\n\n'); + expect(output).toBe('##[error]Error message%0D%0A%0A' + os.EOL); + }); + it('warning sets the correct message', () => { // Override stdout and append to output so that we capture the command that is sent let output = ''; @@ -77,6 +188,18 @@ describe('@actions/core', () => { expect(output).toBe('##[warning]Warning' + os.EOL); }); + it('warning escapes the message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.warning('\r\nwarning\n'); + expect(output).toBe('##[warning]%0D%0Awarning%0A' + os.EOL); + }); + it('debug sets the correct message', () => { // Override stdout and append to output so that we capture the command that is sent let output = ''; @@ -89,5 +212,15 @@ describe('@actions/core', () => { expect(output).toBe('##[debug]Debug' + os.EOL); }); - // TODO - test escaping for all commands + it('debug escapes the message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = ''; + process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { + output += p1; + return true; + } + + core.debug('\r\ndebug\n'); + expect(output).toBe('##[debug]%0D%0Adebug%0A' + os.EOL); + }); }); \ No newline at end of file From 97c1e7df5a03aa5fa3bb1d6d26a4cdc9451c7e98 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Mon, 20 May 2019 19:17:56 -0400 Subject: [PATCH 12/34] Run linter --- packages/core/src/core.ts | 43 +++---- packages/core/src/interfaces.ts | 34 +++--- packages/core/src/internal.ts | 193 ++++++++++++++++---------------- 3 files changed, 137 insertions(+), 133 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index a9baa76d..af0fae37 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -1,6 +1,6 @@ -import im = require('./interfaces'); -import intm = require('./internal'); -import process = require('process'); +import im = require('./interfaces') +import intm = require('./internal') +import process = require('process') //----------------------------------------------------------------------- // Variables @@ -12,8 +12,8 @@ import process = require('process'); * @param val the value of the variable */ export function exportVariable(name: string, val: string) { - process.env[name] = val; - intm._issueCommand('set-variable', {'name': name}, val); + process.env[name] = val + intm._issueCommand('set-variable', {name: name}, val) } /** @@ -22,24 +22,25 @@ export function exportVariable(name: string, val: string) { * @param val value of the secret */ export function setSecret(name: string, val: string) { - exportVariable(name, val); - intm._issueCommand('set-secret', {}, val); -} + exportVariable(name, val) + intm._issueCommand('set-secret', {}, val) +} /** * Gets the value of an input. The value is also trimmed. - * + * * @param name name of the input to get * @param options optional. See InputOptions. * @returns string */ export function getInput(name: string, options?: im.InputOptions): string { - let val: string = process.env['INPUT_' + name.replace(' ', '_').toUpperCase()] || ''; - if (options && options.required && !val) { - throw new Error(`Input required and not supplied: ${name}`); - } + let val: string = + process.env['INPUT_' + name.replace(' ', '_').toUpperCase()] || '' + if (options && options.required && !val) { + throw new Error(`Input required and not supplied: ${name}`) + } - return val.trim(); + return val.trim() } //----------------------------------------------------------------------- @@ -50,17 +51,17 @@ export function getInput(name: string, options?: im.InputOptions): string { * Sets the action status to neutral */ export function setNeutral() { - process.exitCode = im.ExitCode.Neutral; + process.exitCode = im.ExitCode.Neutral } /** - * Sets the action status to failed. + * Sets the action status to failed. * When the action exits it will be with an exit code of 1 * @param message add error issue message */ export function setFailed(message: string) { - process.exitCode = im.ExitCode.Failure; - error(message); + process.exitCode = im.ExitCode.Failure + error(message) } //----------------------------------------------------------------------- @@ -72,7 +73,7 @@ export function setFailed(message: string) { * @param message debug message */ export function debug(message: string) { - intm._issueCommand('debug', {}, message); + intm._issueCommand('debug', {}, message) } /** @@ -80,7 +81,7 @@ export function debug(message: string) { * @param message error issue message */ export function error(message: string) { - intm._issue('error', message); + intm._issue('error', message) } /** @@ -88,5 +89,5 @@ export function error(message: string) { * @param message warning issue message */ export function warning(message: string) { - intm._issue('warning', message); + intm._issue('warning', message) } diff --git a/packages/core/src/interfaces.ts b/packages/core/src/interfaces.ts index bca96a40..247f8e99 100644 --- a/packages/core/src/interfaces.ts +++ b/packages/core/src/interfaces.ts @@ -2,26 +2,26 @@ * The code to exit an action */ export enum ExitCode { - /** - * A code indicating that the action was successful - */ - Success = 0, - - /** - * A code indicating that the action was a failure - */ - Failure = 1, - - /** - * A code indicating that the action is complete, but neither succeeded nor failed - */ - Neutral = 78 + /** + * A code indicating that the action was successful + */ + Success = 0, + + /** + * A code indicating that the action was a failure + */ + Failure = 1, + + /** + * A code indicating that the action is complete, but neither succeeded nor failed + */ + Neutral = 78 } /** * Interface for getInput options */ export interface InputOptions { - /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ - required?: boolean; -} \ No newline at end of file + /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ + required?: boolean +} diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index af00f9a9..8218a210 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -1,148 +1,151 @@ -import os = require('os'); +import os = require('os') - -/** +/** * Commands * * Command Format: * ##[name key=value;key=value]message - * + * * Examples: * ##[warning]This is the user warning message * ##[set-secret name=mypassword]definatelyNotAPassword! */ -export function _issueCommand(command: string, properties: any, message: string) { - var cmd = new _Command(command, properties, message); - _writeLine(cmd.toString()); +export function _issueCommand( + command: string, + properties: any, + message: string +) { + var cmd = new _Command(command, properties, message) + _writeLine(cmd.toString()) } export function _issue(name: string, message: string) { - _issueCommand(name, {}, message); + _issueCommand(name, {}, message) } -let CMD_PREFIX = '##['; +let CMD_PREFIX = '##[' export class _Command { - constructor(command: string, properties: any, message: string) { - if (!command) { - command = 'missing.command'; - } - - this.command = command; - this.properties = properties; - this.message = message; + constructor(command: string, properties: any, message: string) { + if (!command) { + command = 'missing.command' } - public command: string; - public message: string; - public properties: any; + this.command = command + this.properties = properties + this.message = message + } - public toString() { - var cmdStr = CMD_PREFIX + this.command; + public command: string + public message: string + public properties: any - if (this.properties && Object.keys(this.properties).length > 0) { - cmdStr += ' '; - for (var key in this.properties) { - if (this.properties.hasOwnProperty(key)) { - var 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 || '')) + ';'; - } - } - } + public toString() { + var cmdStr = CMD_PREFIX + this.command + + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' ' + for (var key in this.properties) { + if (this.properties.hasOwnProperty(key)) { + var 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 || '')) + ';' + } } - - cmdStr += ']'; - - // safely append the message - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - let message: string = '' + (this.message || ''); - cmdStr += escapedata(message); - - return cmdStr; + } } + + cmdStr += ']' + + // safely append the message - avoid blowing up when attempting to + // call .replace() if message is not a string for some reason + let message: string = '' + (this.message || '') + cmdStr += escapedata(message) + + return cmdStr + } } export function _commandFromString(commandLine: string) { - var preLen = CMD_PREFIX.length; - var lbPos = commandLine.indexOf('['); - var rbPos = commandLine.indexOf(']'); - if (lbPos == -1 || rbPos == -1 || rbPos - lbPos < 3) { - throw new Error('Invalid command brackets'); - } - var cmdInfo = commandLine.substring(lbPos + 1, rbPos); - var spaceIdx = cmdInfo.indexOf(' '); + var preLen = CMD_PREFIX.length + var lbPos = commandLine.indexOf('[') + var rbPos = commandLine.indexOf(']') + if (lbPos == -1 || rbPos == -1 || rbPos - lbPos < 3) { + throw new Error('Invalid command brackets') + } + var cmdInfo = commandLine.substring(lbPos + 1, rbPos) + var spaceIdx = cmdInfo.indexOf(' ') - var command = cmdInfo; - var properties: {[key: string]: string} = {}; + var command = cmdInfo + var properties: {[key: string]: string} = {} - if (spaceIdx > 0) { - command = cmdInfo.trim().substring(0, spaceIdx); - var propSection = cmdInfo.trim().substring(spaceIdx+1); + if (spaceIdx > 0) { + command = cmdInfo.trim().substring(0, spaceIdx) + var propSection = cmdInfo.trim().substring(spaceIdx + 1) - var propLines: string[] = propSection.split(';'); - propLines.forEach(function (propLine: string) { - propLine = propLine.trim(); - if (propLine.length > 0) { - var eqIndex = propLine.indexOf('='); - if (eqIndex == -1){ - throw new Error('Invalid property: ' + propLine); - } + var propLines: string[] = propSection.split(';') + propLines.forEach(function(propLine: string) { + propLine = propLine.trim() + if (propLine.length > 0) { + var eqIndex = propLine.indexOf('=') + if (eqIndex == -1) { + throw new Error('Invalid property: ' + propLine) + } - var key: string = propLine.substring(0, eqIndex); - var val: string = propLine.substring(eqIndex+1); + var key: string = propLine.substring(0, eqIndex) + var val: string = propLine.substring(eqIndex + 1) - properties[key] = unescape(val); - } - }); - } + properties[key] = unescape(val) + } + }) + } - let msg: string = unescapedata(commandLine.substring(rbPos + 1)); - var cmd = new _Command(command, properties, msg); - return cmd; + let msg: string = unescapedata(commandLine.substring(rbPos + 1)) + var cmd = new _Command(command, properties, msg) + return cmd } -function escapedata(s: string) : string { - return s.replace(/\r/g, '%0D') - .replace(/\n/g, '%0A'); +function escapedata(s: string): string { + return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A') } -function unescapedata(s: string) : string { - return s.replace(/%0D/g, '\r') - .replace(/%0A/g, '\n'); +function unescapedata(s: string): string { + return s.replace(/%0D/g, '\r').replace(/%0A/g, '\n') } -function escape(s: string) : string { - return s.replace(/\r/g, '%0D') - .replace(/\n/g, '%0A') - .replace(/]/g, '%5D') - .replace(/;/g, '%3B'); +function escape(s: string): string { + return s + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/]/g, '%5D') + .replace(/;/g, '%3B') } -function unescape(s: string) : string { - return s.replace(/%0D/g, '\r') - .replace(/%0A/g, '\n') - .replace(/%5D/g, ']') - .replace(/%3B/g, ';'); +function unescape(s: string): string { + return s + .replace(/%0D/g, '\r') + .replace(/%0A/g, '\n') + .replace(/%5D/g, ']') + .replace(/%3B/g, ';') } //----------------------------------------------------- // Streams: allow to override the stream //----------------------------------------------------- -let _outStream = process.stdout; -let _errStream = process.stderr; +let _outStream = process.stdout +let _errStream = process.stderr export function _writeLine(str: string): void { - _outStream.write(str + os.EOL); + _outStream.write(str + os.EOL) } export function _setStdStream(stdStream: NodeJS.WriteStream): void { - _outStream = stdStream; + _outStream = stdStream } export function _setErrStream(errStream: NodeJS.WriteStream): void { - _errStream = errStream; -} \ No newline at end of file + _errStream = errStream +} From 0f12556586f9c2e7ab5385376ff6de2e2f875309 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 10:09:24 -0400 Subject: [PATCH 13/34] Fix formatter to check all TypeScript --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c93a4cda..edd9d2f7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "scripts": { "bootstrap": "lerna bootstrap", "build": "lerna run tsc", - "format": "prettier --check packages/*/src/**/*.ts", + "format": "prettier --check packages/**/*.ts", "new-package": "scripts/create-package", "test": "jest" }, From 3ca580438b77c1033ab688d670429a6a6b4e609f Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 10:11:39 -0400 Subject: [PATCH 14/34] Format lib.test.ts --- packages/core/__tests__/lib.test.ts | 449 ++++++++++++++++------------ 1 file changed, 262 insertions(+), 187 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index 5b2a3319..d3d7d2e0 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -1,226 +1,301 @@ -'use strict'; +'use strict' -import * as core from '../src/core'; -import * as os from 'os'; +import * as core from '../src/core' +import * as os from 'os' describe('@actions/core', () => { - beforeEach(() => { - // Clear variables - process.env['my var'] = ''; - process.env['special char var \r\n];'] = ''; - process.env['my var2'] = ''; - process.env['my secret'] = ''; - process.env['special char secret \r\n];'] = ''; - process.env['my secret2'] = ''; + beforeEach(() => { + // Clear variables + process.env['my var'] = '' + process.env['special char var \r\n];'] = '' + process.env['my var2'] = '' + process.env['my secret'] = '' + process.env['special char secret \r\n];'] = '' + process.env['my secret2'] = '' - // Set inputs - process.env['INPUT_MY_INPUT'] = 'val'; - process.env['INPUT_MISSING'] = ''; - process.env['INPUT_SPECIAL_CHARS_\'\t\"\\'] = '\'\t\"\\ repsonse '; - }); + // Set inputs + process.env['INPUT_MY_INPUT'] = 'val' + process.env['INPUT_MISSING'] = '' + process.env['INPUT_SPECIAL_CHARS_\'\t"\\'] = '\'\t"\\ repsonse ' + }) - it('exportVariable produces the correct command and sets the env', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('exportVariable produces the correct command and sets the env', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.exportVariable('my var', 'var val'); - expect(process.env['my var']).toBe('var val'); - expect(output).toBe('##[set-variable name=my var;]var val' + os.EOL); - }); + core.exportVariable('my var', 'var val') + expect(process.env['my var']).toBe('var val') + expect(output).toBe('##[set-variable name=my var;]var val' + os.EOL) + }) - it('exportVariable escapes variable names', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('exportVariable escapes variable names', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.exportVariable('special char var \r\n];', 'special val'); - expect(process.env['special char var \r\n];']).toBe('special val'); - expect(output).toBe('##[set-variable name=special char var %0D%0A%5D%3B;]special val' + os.EOL); - }); + core.exportVariable('special char var \r\n];', 'special val') + expect(process.env['special char var \r\n];']).toBe('special val') + expect(output).toBe( + '##[set-variable name=special char var %0D%0A%5D%3B;]special val' + os.EOL + ) + }) - it('exportVariable escapes variable values', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('exportVariable escapes variable values', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.exportVariable('my var2', 'var val\r\n'); - expect(process.env['my var2']).toBe('var val\r\n'); - expect(output).toBe('##[set-variable name=my var2;]var val%0D%0A' + os.EOL); - }); + core.exportVariable('my var2', 'var val\r\n') + expect(process.env['my var2']).toBe('var val\r\n') + expect(output).toBe('##[set-variable name=my var2;]var val%0D%0A' + os.EOL) + }) - it('setSecret produces the correct commands and sets the env', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('setSecret produces the correct commands and sets the env', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.setSecret('my secret', 'secret val'); - expect(process.env['my secret']).toBe('secret val'); - expect(output).toBe('##[set-variable name=my secret;]secret val' + os.EOL + '##[set-secret]secret val' + os.EOL); - }); + core.setSecret('my secret', 'secret val') + expect(process.env['my secret']).toBe('secret val') + expect(output).toBe( + '##[set-variable name=my secret;]secret val' + + os.EOL + + '##[set-secret]secret val' + + os.EOL + ) + }) - it('setSecret escapes secret names', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('setSecret escapes secret names', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.setSecret('special char secret \r\n];', 'special secret val'); - expect(process.env['special char secret \r\n];']).toBe('special secret val'); - expect(output).toBe('##[set-variable name=special char secret %0D%0A%5D%3B;]special secret val' + os.EOL + '##[set-secret]special secret val' + os.EOL); - }); + core.setSecret('special char secret \r\n];', 'special secret val') + expect(process.env['special char secret \r\n];']).toBe('special secret val') + expect(output).toBe( + '##[set-variable name=special char secret %0D%0A%5D%3B;]special secret val' + + os.EOL + + '##[set-secret]special secret val' + + os.EOL + ) + }) - it('setSecret escapes secret values', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('setSecret escapes secret values', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.setSecret('my secret2', 'secret val\r\n'); - expect(process.env['my secret2']).toBe('secret val\r\n'); - expect(output).toBe('##[set-variable name=my secret2;]secret val%0D%0A' + os.EOL + '##[set-secret]secret val%0D%0A' + os.EOL); - }); + core.setSecret('my secret2', 'secret val\r\n') + expect(process.env['my secret2']).toBe('secret val\r\n') + expect(output).toBe( + '##[set-variable name=my secret2;]secret val%0D%0A' + + os.EOL + + '##[set-secret]secret val%0D%0A' + + os.EOL + ) + }) - it('getInput gets non-required input', () => { - expect(core.getInput('my input')).toBe('val'); - }); + it('getInput gets non-required input', () => { + expect(core.getInput('my input')).toBe('val') + }) - it('getInput gets required input', () => { - expect(core.getInput('my input', {required: true})).toBe('val'); - }); + it('getInput gets required input', () => { + expect(core.getInput('my input', {required: true})).toBe('val') + }) - it('getInput throws on missing required input', () => { - expect(() => core.getInput('missing', {required: true})).toThrow('Input required and not supplied: missing'); - }); + it('getInput throws on missing required input', () => { + expect(() => core.getInput('missing', {required: true})).toThrow( + 'Input required and not supplied: missing' + ) + }) - it('getInput doesnt throw on missing non-required input', () => { - expect(core.getInput('missing', {required: false})).toBe(''); - }); + it('getInput doesnt throw on missing non-required input', () => { + expect(core.getInput('missing', {required: false})).toBe('') + }) - it('getInput is case insensitive', () => { - expect(core.getInput('My InPuT')).toBe('val'); - }); + it('getInput is case insensitive', () => { + expect(core.getInput('My InPuT')).toBe('val') + }) - it('getInput handles special characters', () => { - expect(core.getInput('special chars_\'\t\"\\')).toBe('\'\t\"\\ repsonse'); - }); + it('getInput handles special characters', () => { + expect(core.getInput('special chars_\'\t"\\')).toBe('\'\t"\\ repsonse') + }) - it('setNeutral sets the correct exit code', () => { - core.setFailed('Failure message'); - expect(process.exitCode).toBe(1); - }); + it('setNeutral sets the correct exit code', () => { + core.setFailed('Failure message') + expect(process.exitCode).toBe(1) + }) - it('setFailure sets the correct exit code and failure message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('setFailure sets the correct exit code and failure message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.setFailed('Failure message'); - expect(process.exitCode).toBe(1); - expect(output).toBe('##[error]Failure message' + os.EOL); - }); + core.setFailed('Failure message') + expect(process.exitCode).toBe(1) + expect(output).toBe('##[error]Failure message' + os.EOL) + }) - it('setFailure escapes the failure message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('setFailure escapes the failure message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.setFailed('Failure \r\n\nmessage\r'); - expect(process.exitCode).toBe(1); - expect(output).toBe('##[error]Failure %0D%0A%0Amessage%0D' + os.EOL); - }); + core.setFailed('Failure \r\n\nmessage\r') + expect(process.exitCode).toBe(1) + expect(output).toBe('##[error]Failure %0D%0A%0Amessage%0D' + os.EOL) + }) - it('error sets the correct error message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('error sets the correct error message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.error('Error message'); - expect(output).toBe('##[error]Error message' + os.EOL); - }); + core.error('Error message') + expect(output).toBe('##[error]Error message' + os.EOL) + }) - it('error escapes the error message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('error escapes the error message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.error('Error message\r\n\n'); - expect(output).toBe('##[error]Error message%0D%0A%0A' + os.EOL); - }); + core.error('Error message\r\n\n') + expect(output).toBe('##[error]Error message%0D%0A%0A' + os.EOL) + }) - it('warning sets the correct message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('warning sets the correct message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.warning('Warning'); - expect(output).toBe('##[warning]Warning' + os.EOL); - }); + core.warning('Warning') + expect(output).toBe('##[warning]Warning' + os.EOL) + }) - it('warning escapes the message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('warning escapes the message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.warning('\r\nwarning\n'); - expect(output).toBe('##[warning]%0D%0Awarning%0A' + os.EOL); - }); + core.warning('\r\nwarning\n') + expect(output).toBe('##[warning]%0D%0Awarning%0A' + os.EOL) + }) - it('debug sets the correct message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('debug sets the correct message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.debug('Debug'); - expect(output).toBe('##[debug]Debug' + os.EOL); - }); + core.debug('Debug') + expect(output).toBe('##[debug]Debug' + os.EOL) + }) - it('debug escapes the message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = ''; - process.stdout.write = (p1: string | Buffer | Uint8Array, p2?: string | ((err?: Error) => void), p3?: (err?: Error) => void): boolean => { - output += p1; - return true; - } + it('debug escapes the message', () => { + // Override stdout and append to output so that we capture the command that is sent + let output = '' + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output += p1 + return true + } - core.debug('\r\ndebug\n'); - expect(output).toBe('##[debug]%0D%0Adebug%0A' + os.EOL); - }); -}); \ No newline at end of file + core.debug('\r\ndebug\n') + expect(output).toBe('##[debug]%0D%0Adebug%0A' + os.EOL) + }) +}) From 70a281f1078a94feeaccba162f0e3fe4a52e6065 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 10:11:44 -0400 Subject: [PATCH 15/34] Add .prettierignore --- .prettierignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..ea4d4ad1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +node_modules/ +packages/*/node_modules/ +packages/*/lib/ \ No newline at end of file From 451bb07ec4f2f93fd1e3d914b998766206efece7 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 10:51:22 -0400 Subject: [PATCH 16/34] Run npm run lint -- --fix --- packages/core/src/core.ts | 6 ++--- packages/core/src/internal.ts | 44 +++++++++++++++++------------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index af0fae37..f01ab77c 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -13,7 +13,7 @@ import process = require('process') */ export function exportVariable(name: string, val: string) { process.env[name] = val - intm._issueCommand('set-variable', {name: name}, val) + intm._issueCommand('set-variable', {name}, val) } /** @@ -34,8 +34,8 @@ export function setSecret(name: string, val: string) { * @returns string */ export function getInput(name: string, options?: im.InputOptions): string { - let val: string = - process.env['INPUT_' + name.replace(' ', '_').toUpperCase()] || '' + const val: string = + process.env[`INPUT_${name.replace(' ', '_').toUpperCase()}`] || '' if (options && options.required && !val) { throw new Error(`Input required and not supplied: ${name}`) } diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 8218a210..71a56535 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -15,7 +15,7 @@ export function _issueCommand( properties: any, message: string ) { - var cmd = new _Command(command, properties, message) + const cmd = new _Command(command, properties, message) _writeLine(cmd.toString()) } @@ -23,7 +23,7 @@ export function _issue(name: string, message: string) { _issueCommand(name, {}, message) } -let CMD_PREFIX = '##[' +const CMD_PREFIX = '##[' export class _Command { constructor(command: string, properties: any, message: string) { @@ -41,17 +41,17 @@ export class _Command { public properties: any public toString() { - var cmdStr = CMD_PREFIX + this.command + let cmdStr = CMD_PREFIX + this.command if (this.properties && Object.keys(this.properties).length > 0) { cmdStr += ' ' - for (var key in this.properties) { + for (const key in this.properties) { if (this.properties.hasOwnProperty(key)) { - var val = this.properties[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 || '')) + ';' + cmdStr += `${key}=${escape(`${val || ''}`)};` } } } @@ -61,7 +61,7 @@ export class _Command { // safely append the message - avoid blowing up when attempting to // call .replace() if message is not a string for some reason - let message: string = '' + (this.message || '') + const message: string = `${this.message || ''}` cmdStr += escapedata(message) return cmdStr @@ -69,41 +69,41 @@ export class _Command { } export function _commandFromString(commandLine: string) { - var preLen = CMD_PREFIX.length - var lbPos = commandLine.indexOf('[') - var rbPos = commandLine.indexOf(']') + const preLen = CMD_PREFIX.length + const lbPos = commandLine.indexOf('[') + const rbPos = commandLine.indexOf(']') if (lbPos == -1 || rbPos == -1 || rbPos - lbPos < 3) { throw new Error('Invalid command brackets') } - var cmdInfo = commandLine.substring(lbPos + 1, rbPos) - var spaceIdx = cmdInfo.indexOf(' ') + const cmdInfo = commandLine.substring(lbPos + 1, rbPos) + const spaceIdx = cmdInfo.indexOf(' ') - var command = cmdInfo - var properties: {[key: string]: string} = {} + let command = cmdInfo + const properties: {[key: string]: string} = {} if (spaceIdx > 0) { command = cmdInfo.trim().substring(0, spaceIdx) - var propSection = cmdInfo.trim().substring(spaceIdx + 1) + const propSection = cmdInfo.trim().substring(spaceIdx + 1) - var propLines: string[] = propSection.split(';') + const propLines: string[] = propSection.split(';') propLines.forEach(function(propLine: string) { propLine = propLine.trim() if (propLine.length > 0) { - var eqIndex = propLine.indexOf('=') + const eqIndex = propLine.indexOf('=') if (eqIndex == -1) { - throw new Error('Invalid property: ' + propLine) + throw new Error(`Invalid property: ${propLine}`) } - var key: string = propLine.substring(0, eqIndex) - var val: string = propLine.substring(eqIndex + 1) + const key: string = propLine.substring(0, eqIndex) + const val: string = propLine.substring(eqIndex + 1) properties[key] = unescape(val) } }) } - let msg: string = unescapedata(commandLine.substring(rbPos + 1)) - var cmd = new _Command(command, properties, msg) + const msg: string = unescapedata(commandLine.substring(rbPos + 1)) + const cmd = new _Command(command, properties, msg) return cmd } From 978b6e02dd8cf55fbb4fb26cf383321b89331ae8 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 10:54:28 -0400 Subject: [PATCH 17/34] Fix ESLint errors --- packages/core/src/internal.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 71a56535..2a066c09 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -69,10 +69,10 @@ export class _Command { } export function _commandFromString(commandLine: string) { - const preLen = CMD_PREFIX.length const lbPos = commandLine.indexOf('[') const rbPos = commandLine.indexOf(']') - if (lbPos == -1 || rbPos == -1 || rbPos - lbPos < 3) { + + if (lbPos === -1 || rbPos === -1 || rbPos - lbPos < 3) { throw new Error('Invalid command brackets') } const cmdInfo = commandLine.substring(lbPos + 1, rbPos) @@ -86,11 +86,14 @@ export function _commandFromString(commandLine: string) { const propSection = cmdInfo.trim().substring(spaceIdx + 1) const propLines: string[] = propSection.split(';') - propLines.forEach(function(propLine: string) { + + for (let propLine of propLines) { propLine = propLine.trim() + if (propLine.length > 0) { const eqIndex = propLine.indexOf('=') - if (eqIndex == -1) { + + if (eqIndex === -1) { throw new Error(`Invalid property: ${propLine}`) } @@ -99,7 +102,7 @@ export function _commandFromString(commandLine: string) { properties[key] = unescape(val) } - }) + } } const msg: string = unescapedata(commandLine.substring(rbPos + 1)) @@ -136,7 +139,7 @@ function unescape(s: string): string { //----------------------------------------------------- let _outStream = process.stdout -let _errStream = process.stderr +let _errStream = process.stderr // eslint-disable-line @typescript-eslint/no-unused-vars export function _writeLine(str: string): void { _outStream.write(str + os.EOL) From c999afbce19b75a25e4f3e651e98dd114217cff6 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 11:05:50 -0400 Subject: [PATCH 18/34] Add links to code in package READMEs Intsead of having to update the READMEs for each package whenever TypeScript signatures change, link directly to the TypeScript code. Later, we can run a Typedoc server, perhaps. --- packages/core/README.md | 55 +------------------------------------- packages/exit/README.md | 6 +---- packages/toolkit/README.md | 6 +---- 3 files changed, 3 insertions(+), 64 deletions(-) diff --git a/packages/core/README.md b/packages/core/README.md index 3aca1690..121d64c3 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -4,57 +4,4 @@ ## Usage -``` -// Logging functions -export function debug(message: string): void -export function warning(message: string): void -export function error(message: string): void - -/** - * 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 options optional. See ExportOptions. - */ -export function exportVariable(name: string, val: string): void - -/** - * exports the variable and registers a secret which will get masked from logs - * @param name the name of the variable to set - * @param val value of the secret - */ -export function exportSecret(name: string, val: string) { - exportVariable(name, val); - intm._issueCommand('set-secret', {}, val); -} - -/** - * Interface for getInput options - */ -export interface InputOptions { - /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ - required?: bool; -} - -/** - * Gets the value of an input. The value is also trimmed. - * - * @param name name of the input to get - * @param options optional. See InputOptions. - * @returns string - */ -export function getInput(name: string, options?: InputOptions): string | undefined - -/** - * sets the status of the action to neutral - * @param message - */ -export function setNeutral(message: string): void - -/** - * sets the status of the action to failed - * @param message - */ -export function setFailed(message: string): void -``` +See [src/core.tsx](src/core.tsx). diff --git a/packages/exit/README.md b/packages/exit/README.md index c54d04da..cbbe0eef 100644 --- a/packages/exit/README.md +++ b/packages/exit/README.md @@ -4,8 +4,4 @@ ## Usage -``` -const exit = require('@actions/exit'); - -// TODO: DEMONSTRATE API -``` +See [src/exit.ts](src/exit.ts). \ No newline at end of file diff --git a/packages/toolkit/README.md b/packages/toolkit/README.md index 1e2abbd7..fa5277da 100644 --- a/packages/toolkit/README.md +++ b/packages/toolkit/README.md @@ -4,8 +4,4 @@ ## Usage -``` -const github = require('@actions/toolkit'); - -// TODO: DEMONSTRATE API -``` +See [src/toolkit.ts](src/toolkit.ts). \ No newline at end of file From f5d5c79c59ebefc791ddcdc9cf66ab547bb4c702 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 11:07:42 -0400 Subject: [PATCH 19/34] Fix .tsx typo --- packages/core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/README.md b/packages/core/README.md index 121d64c3..d5bf5bab 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -4,4 +4,4 @@ ## Usage -See [src/core.tsx](src/core.tsx). +See [src/core.ts](src/core.ts). From 4439c53a4015130af2fb1b24a0ddbf8fac7f251a Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 11:09:41 -0400 Subject: [PATCH 20/34] 'use strict' not necessary in TypeScript --- packages/core/__tests__/lib.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index d3d7d2e0..1384f893 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -1,7 +1,5 @@ -'use strict' - -import * as core from '../src/core' import * as os from 'os' +import * as core from '../src/core' describe('@actions/core', () => { beforeEach(() => { From a52644e6dd609c499c7166df94f773472d35a74f Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 11:23:35 -0400 Subject: [PATCH 21/34] DRY up core tests --- .eslintignore | 3 + package.json | 2 +- packages/core/__tests__/lib.test.ts | 219 +++++++--------------------- 3 files changed, 59 insertions(+), 165 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..ea4d4ad1 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules/ +packages/*/node_modules/ +packages/*/lib/ \ No newline at end of file diff --git a/package.json b/package.json index c3782a1b..da897d50 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "check-all": "concurrently \"npm:format-check\" \"npm:lint\" \"npm:test\" \"npm:build -- -- --noEmit\"", "format": "prettier --write packages/**/*.ts", "format-check": "prettier --check packages/**/*.ts", - "lint": "eslint packages/*/src/**/*.ts", + "lint": "eslint packages/**/*.ts", "new-package": "scripts/create-package", "test": "jest" }, diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index 1384f893..80d135f6 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -18,121 +18,66 @@ describe('@actions/core', () => { }) it('exportVariable produces the correct command and sets the env', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.exportVariable('my var', 'var val') expect(process.env['my var']).toBe('var val') - expect(output).toBe('##[set-variable name=my var;]var val' + os.EOL) + expect(output.value).toBe(`##[set-variable name=my var;]var val${os.EOL}`) }) it('exportVariable escapes variable names', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.exportVariable('special char var \r\n];', 'special val') expect(process.env['special char var \r\n];']).toBe('special val') - expect(output).toBe( - '##[set-variable name=special char var %0D%0A%5D%3B;]special val' + os.EOL + expect(output.value).toBe( + `##[set-variable name=special char var %0D%0A%5D%3B;]special val${os.EOL}` ) }) it('exportVariable escapes variable values', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.exportVariable('my var2', 'var val\r\n') expect(process.env['my var2']).toBe('var val\r\n') - expect(output).toBe('##[set-variable name=my var2;]var val%0D%0A' + os.EOL) + expect(output.value).toBe( + `##[set-variable name=my var2;]var val%0D%0A${os.EOL}` + ) }) it('setSecret produces the correct commands and sets the env', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.setSecret('my secret', 'secret val') expect(process.env['my secret']).toBe('secret val') - expect(output).toBe( - '##[set-variable name=my secret;]secret val' + - os.EOL + - '##[set-secret]secret val' + + expect(output.value).toBe( + `##[set-variable name=my secret;]secret val${ os.EOL + }##[set-secret]secret val${os.EOL}` ) }) it('setSecret escapes secret names', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.setSecret('special char secret \r\n];', 'special secret val') expect(process.env['special char secret \r\n];']).toBe('special secret val') - expect(output).toBe( - '##[set-variable name=special char secret %0D%0A%5D%3B;]special secret val' + - os.EOL + - '##[set-secret]special secret val' + + expect(output.value).toBe( + `##[set-variable name=special char secret %0D%0A%5D%3B;]special secret val${ os.EOL + }##[set-secret]special secret val${os.EOL}` ) }) it('setSecret escapes secret values', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.setSecret('my secret2', 'secret val\r\n') expect(process.env['my secret2']).toBe('secret val\r\n') - expect(output).toBe( - '##[set-variable name=my secret2;]secret val%0D%0A' + - os.EOL + - '##[set-secret]secret val%0D%0A' + + expect(output.value).toBe( + `##[set-variable name=my secret2;]secret val%0D%0A${ os.EOL + }##[set-secret]secret val%0D%0A${os.EOL}` ) }) @@ -168,132 +113,78 @@ describe('@actions/core', () => { }) it('setFailure sets the correct exit code and failure message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.setFailed('Failure message') expect(process.exitCode).toBe(1) - expect(output).toBe('##[error]Failure message' + os.EOL) + expect(output.value).toBe(`##[error]Failure message${os.EOL}`) }) it('setFailure escapes the failure message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.setFailed('Failure \r\n\nmessage\r') expect(process.exitCode).toBe(1) - expect(output).toBe('##[error]Failure %0D%0A%0Amessage%0D' + os.EOL) + expect(output.value).toBe(`##[error]Failure %0D%0A%0Amessage%0D${os.EOL}`) }) it('error sets the correct error message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.error('Error message') - expect(output).toBe('##[error]Error message' + os.EOL) + expect(output.value).toBe(`##[error]Error message${os.EOL}`) }) it('error escapes the error message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.error('Error message\r\n\n') - expect(output).toBe('##[error]Error message%0D%0A%0A' + os.EOL) + expect(output.value).toBe(`##[error]Error message%0D%0A%0A${os.EOL}`) }) it('warning sets the correct message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.warning('Warning') - expect(output).toBe('##[warning]Warning' + os.EOL) + expect(output.value).toBe(`##[warning]Warning${os.EOL}`) }) it('warning escapes the message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.warning('\r\nwarning\n') - expect(output).toBe('##[warning]%0D%0Awarning%0A' + os.EOL) + expect(output.value).toBe(`##[warning]%0D%0Awarning%0A${os.EOL}`) }) it('debug sets the correct message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.debug('Debug') - expect(output).toBe('##[debug]Debug' + os.EOL) + expect(output.value).toBe(`##[debug]Debug${os.EOL}`) }) it('debug escapes the message', () => { - // Override stdout and append to output so that we capture the command that is sent - let output = '' - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output += p1 - return true - } + const output = overrideStdoutWrite() core.debug('\r\ndebug\n') - expect(output).toBe('##[debug]%0D%0Adebug%0A' + os.EOL) + expect(output.value).toBe(`##[debug]%0D%0Adebug%0A${os.EOL}`) }) }) + +// Override stdout and append to output so that we capture the command that is sent +function overrideStdoutWrite(value: string = ''): {value: string} { + const output = {value} + + /* eslint-disable @typescript-eslint/no-unused-vars */ + process.stdout.write = ( + p1: string | Buffer | Uint8Array, + p2?: string | ((err?: Error) => void), + p3?: (err?: Error) => void + ): boolean => { + output.value += p1 + return true + } + /* eslint-enable */ + + return output +} From 90008fc46b52ad9f522168f6a81c38cb7d0a82c4 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 11:46:02 -0400 Subject: [PATCH 22/34] Use ES imports --- packages/core/src/core.ts | 5 ++--- packages/core/src/internal.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index f01ab77c..f0e1e105 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -1,6 +1,5 @@ -import im = require('./interfaces') -import intm = require('./internal') -import process = require('process') +import * as im from './interfaces' +import * as intm from './internal' //----------------------------------------------------------------------- // Variables diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 2a066c09..032ee4ff 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -1,4 +1,4 @@ -import os = require('os') +import * as os from 'os' /** * Commands From 062a6bb046d6986d6f7815288b0f2089ad6c3382 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 12:00:23 -0400 Subject: [PATCH 23/34] Use ExitCode from @actions/exit --- packages/core/package.json | 3 +++ packages/core/src/core.ts | 14 +++++++++++--- packages/core/src/interfaces.ts | 27 --------------------------- 3 files changed, 14 insertions(+), 30 deletions(-) delete mode 100644 packages/core/src/interfaces.ts diff --git a/packages/core/package.json b/packages/core/package.json index 3f39447a..df9eb6b7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -33,5 +33,8 @@ }, "devDependencies": { "@types/node": "^12.0.2" + }, + "dependencies": { + "@actions/exit": "^0.0.0" } } diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index f0e1e105..d34d8077 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -1,6 +1,14 @@ -import * as im from './interfaces' +import {ExitCode} from '@actions/exit' import * as intm from './internal' +/** + * Interface for getInput options + */ +export interface InputOptions { + /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ + required?: boolean +} + //----------------------------------------------------------------------- // Variables //----------------------------------------------------------------------- @@ -50,7 +58,7 @@ export function getInput(name: string, options?: im.InputOptions): string { * Sets the action status to neutral */ export function setNeutral() { - process.exitCode = im.ExitCode.Neutral + process.exitCode = ExitCode.Neutral } /** @@ -59,7 +67,7 @@ export function setNeutral() { * @param message add error issue message */ export function setFailed(message: string) { - process.exitCode = im.ExitCode.Failure + process.exitCode = ExitCode.Failure error(message) } diff --git a/packages/core/src/interfaces.ts b/packages/core/src/interfaces.ts deleted file mode 100644 index 247f8e99..00000000 --- a/packages/core/src/interfaces.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * The code to exit an action - */ -export enum ExitCode { - /** - * A code indicating that the action was successful - */ - Success = 0, - - /** - * A code indicating that the action was a failure - */ - Failure = 1, - - /** - * A code indicating that the action is complete, but neither succeeded nor failed - */ - Neutral = 78 -} - -/** - * Interface for getInput options - */ -export interface InputOptions { - /** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */ - required?: boolean -} From c3ed3b105f3d48a4e31262f32923d11584afe468 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 12:03:13 -0400 Subject: [PATCH 24/34] Fix namespace use in core.ts --- packages/core/src/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index d34d8077..23ee6770 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -40,7 +40,7 @@ export function setSecret(name: string, val: string) { * @param options optional. See InputOptions. * @returns string */ -export function getInput(name: string, options?: im.InputOptions): string { +export function getInput(name: string, options?: InputOptions): string { const val: string = process.env[`INPUT_${name.replace(' ', '_').toUpperCase()}`] || '' if (options && options.required && !val) { From 11d5a53a98ac071c6eff9fe25ed0a47ca34cf914 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 12:14:15 -0400 Subject: [PATCH 25/34] Remove environment variables after we set them --- packages/core/__tests__/lib.test.ts | 30 ++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index 80d135f6..e43c9e15 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -1,20 +1,28 @@ import * as os from 'os' import * as core from '../src/core' +const testEnvVars = { + 'my var': '', + 'special char var \r\n];': '', + 'my var2': '', + 'my secret': '', + 'special char secret \r\n];': '', + 'my secret2': '', + + // Set inputs + INPUT_MY_INPUT: 'val', + INPUT_MISSING: '', + 'INPUT_SPECIAL_CHARS_\'\t"\\': '\'\t"\\ repsonse ' +} + describe('@actions/core', () => { beforeEach(() => { - // Clear variables - process.env['my var'] = '' - process.env['special char var \r\n];'] = '' - process.env['my var2'] = '' - process.env['my secret'] = '' - process.env['special char secret \r\n];'] = '' - process.env['my secret2'] = '' + for (const key in testEnvVars) + process.env[key] = testEnvVars[key as keyof typeof testEnvVars] + }) - // Set inputs - process.env['INPUT_MY_INPUT'] = 'val' - process.env['INPUT_MISSING'] = '' - process.env['INPUT_SPECIAL_CHARS_\'\t"\\'] = '\'\t"\\ repsonse ' + afterEach(() => { + for (const key in testEnvVars) Reflect.deleteProperty(testEnvVars, key) }) it('exportVariable produces the correct command and sets the env', () => { From 406242718f34c652aa2be2be5782c84be8f97205 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 12:27:43 -0400 Subject: [PATCH 26/34] Use a Jest mock to test process.stdout.write Instead of manually mocking, we can instead use `jest.fn()` and assert that the correct calls were made. --- packages/core/__tests__/lib.test.ts | 117 ++++++++++++---------------- 1 file changed, 50 insertions(+), 67 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index e43c9e15..0db0987c 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -19,6 +19,8 @@ describe('@actions/core', () => { beforeEach(() => { for (const key in testEnvVars) process.env[key] = testEnvVars[key as keyof typeof testEnvVars] + + process.stdout.write = jest.fn() }) afterEach(() => { @@ -26,66 +28,67 @@ describe('@actions/core', () => { }) it('exportVariable produces the correct command and sets the env', () => { - const output = overrideStdoutWrite() - core.exportVariable('my var', 'var val') expect(process.env['my var']).toBe('var val') - expect(output.value).toBe(`##[set-variable name=my var;]var val${os.EOL}`) + expect(process.stdout.write).toHaveBeenCalledWith( + `##[set-variable name=my var;]var val${os.EOL}` + ) }) it('exportVariable escapes variable names', () => { - const output = overrideStdoutWrite() - core.exportVariable('special char var \r\n];', 'special val') expect(process.env['special char var \r\n];']).toBe('special val') - expect(output.value).toBe( + expect(process.stdout.write).toHaveBeenCalledWith( `##[set-variable name=special char var %0D%0A%5D%3B;]special val${os.EOL}` ) }) it('exportVariable escapes variable values', () => { - const output = overrideStdoutWrite() - core.exportVariable('my var2', 'var val\r\n') expect(process.env['my var2']).toBe('var val\r\n') - expect(output.value).toBe( + expect(process.stdout.write).toHaveBeenCalledWith( `##[set-variable name=my var2;]var val%0D%0A${os.EOL}` ) }) it('setSecret produces the correct commands and sets the env', () => { - const output = overrideStdoutWrite() - core.setSecret('my secret', 'secret val') expect(process.env['my secret']).toBe('secret val') - expect(output.value).toBe( - `##[set-variable name=my secret;]secret val${ - os.EOL - }##[set-secret]secret val${os.EOL}` + expect(process.stdout.write).toHaveBeenNthCalledWith( + 1, + `##[set-variable name=my secret;]secret val${os.EOL}` + ) + expect(process.stdout.write).toHaveBeenNthCalledWith( + 2, + `##[set-secret]secret val${os.EOL}` ) }) it('setSecret escapes secret names', () => { - const output = overrideStdoutWrite() - core.setSecret('special char secret \r\n];', 'special secret val') expect(process.env['special char secret \r\n];']).toBe('special secret val') - expect(output.value).toBe( + expect(process.stdout.write).toHaveBeenNthCalledWith( + 1, `##[set-variable name=special char secret %0D%0A%5D%3B;]special secret val${ os.EOL - }##[set-secret]special secret val${os.EOL}` + }` + ) + expect(process.stdout.write).toHaveBeenNthCalledWith( + 2, + `##[set-secret]special secret val${os.EOL}` ) }) it('setSecret escapes secret values', () => { - const output = overrideStdoutWrite() - core.setSecret('my secret2', 'secret val\r\n') expect(process.env['my secret2']).toBe('secret val\r\n') - expect(output.value).toBe( - `##[set-variable name=my secret2;]secret val%0D%0A${ - os.EOL - }##[set-secret]secret val%0D%0A${os.EOL}` + expect(process.stdout.write).toHaveBeenNthCalledWith( + 1, + `##[set-variable name=my secret2;]secret val%0D%0A${os.EOL}` + ) + expect(process.stdout.write).toHaveBeenNthCalledWith( + 2, + `##[set-secret]secret val%0D%0A${os.EOL}` ) }) @@ -121,78 +124,58 @@ describe('@actions/core', () => { }) it('setFailure sets the correct exit code and failure message', () => { - const output = overrideStdoutWrite() - core.setFailed('Failure message') expect(process.exitCode).toBe(1) - expect(output.value).toBe(`##[error]Failure message${os.EOL}`) + expect(process.stdout.write).toHaveBeenCalledWith( + `##[error]Failure message${os.EOL}` + ) }) it('setFailure escapes the failure message', () => { - const output = overrideStdoutWrite() - core.setFailed('Failure \r\n\nmessage\r') expect(process.exitCode).toBe(1) - expect(output.value).toBe(`##[error]Failure %0D%0A%0Amessage%0D${os.EOL}`) + expect(process.stdout.write).toHaveBeenCalledWith( + `##[error]Failure %0D%0A%0Amessage%0D${os.EOL}` + ) }) it('error sets the correct error message', () => { - const output = overrideStdoutWrite() - core.error('Error message') - expect(output.value).toBe(`##[error]Error message${os.EOL}`) + expect(process.stdout.write).toHaveBeenCalledWith( + `##[error]Error message${os.EOL}` + ) }) it('error escapes the error message', () => { - const output = overrideStdoutWrite() - core.error('Error message\r\n\n') - expect(output.value).toBe(`##[error]Error message%0D%0A%0A${os.EOL}`) + expect(process.stdout.write).toHaveBeenCalledWith( + `##[error]Error message%0D%0A%0A${os.EOL}` + ) }) it('warning sets the correct message', () => { - const output = overrideStdoutWrite() - core.warning('Warning') - expect(output.value).toBe(`##[warning]Warning${os.EOL}`) + expect(process.stdout.write).toHaveBeenCalledWith( + `##[warning]Warning${os.EOL}` + ) }) it('warning escapes the message', () => { - const output = overrideStdoutWrite() - core.warning('\r\nwarning\n') - expect(output.value).toBe(`##[warning]%0D%0Awarning%0A${os.EOL}`) + expect(process.stdout.write).toHaveBeenCalledWith( + `##[warning]%0D%0Awarning%0A${os.EOL}` + ) }) it('debug sets the correct message', () => { - const output = overrideStdoutWrite() - core.debug('Debug') - expect(output.value).toBe(`##[debug]Debug${os.EOL}`) + expect(process.stdout.write).toHaveBeenCalledWith(`##[debug]Debug${os.EOL}`) }) it('debug escapes the message', () => { - const output = overrideStdoutWrite() - core.debug('\r\ndebug\n') - expect(output.value).toBe(`##[debug]%0D%0Adebug%0A${os.EOL}`) + expect(process.stdout.write).toHaveBeenCalledWith( + `##[debug]%0D%0Adebug%0A${os.EOL}` + ) }) }) - -// Override stdout and append to output so that we capture the command that is sent -function overrideStdoutWrite(value: string = ''): {value: string} { - const output = {value} - - /* eslint-disable @typescript-eslint/no-unused-vars */ - process.stdout.write = ( - p1: string | Buffer | Uint8Array, - p2?: string | ((err?: Error) => void), - p3?: (err?: Error) => void - ): boolean => { - output.value += p1 - return true - } - /* eslint-enable */ - - return output -} From f30996e6bb35b741bfb85010de513f53cad2bef0 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 12:34:47 -0400 Subject: [PATCH 27/34] DRY up asserts on process.stdout.write --- packages/core/__tests__/lib.test.ts | 82 +++++++++++------------------ 1 file changed, 30 insertions(+), 52 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index 0db0987c..b1202d1c 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -29,67 +29,50 @@ describe('@actions/core', () => { it('exportVariable produces the correct command and sets the env', () => { core.exportVariable('my var', 'var val') - expect(process.env['my var']).toBe('var val') - expect(process.stdout.write).toHaveBeenCalledWith( - `##[set-variable name=my var;]var val${os.EOL}` - ) + assertWriteCalls([`##[set-variable name=my var;]var val${os.EOL}`]) }) it('exportVariable escapes variable names', () => { core.exportVariable('special char var \r\n];', 'special val') expect(process.env['special char var \r\n];']).toBe('special val') - expect(process.stdout.write).toHaveBeenCalledWith( + assertWriteCalls([ `##[set-variable name=special char var %0D%0A%5D%3B;]special val${os.EOL}` - ) + ]) }) it('exportVariable escapes variable values', () => { core.exportVariable('my var2', 'var val\r\n') expect(process.env['my var2']).toBe('var val\r\n') - expect(process.stdout.write).toHaveBeenCalledWith( - `##[set-variable name=my var2;]var val%0D%0A${os.EOL}` - ) + assertWriteCalls([`##[set-variable name=my var2;]var val%0D%0A${os.EOL}`]) }) it('setSecret produces the correct commands and sets the env', () => { core.setSecret('my secret', 'secret val') expect(process.env['my secret']).toBe('secret val') - expect(process.stdout.write).toHaveBeenNthCalledWith( - 1, - `##[set-variable name=my secret;]secret val${os.EOL}` - ) - expect(process.stdout.write).toHaveBeenNthCalledWith( - 2, + assertWriteCalls([ + `##[set-variable name=my secret;]secret val${os.EOL}`, `##[set-secret]secret val${os.EOL}` - ) + ]) }) it('setSecret escapes secret names', () => { core.setSecret('special char secret \r\n];', 'special secret val') expect(process.env['special char secret \r\n];']).toBe('special secret val') - expect(process.stdout.write).toHaveBeenNthCalledWith( - 1, + assertWriteCalls([ `##[set-variable name=special char secret %0D%0A%5D%3B;]special secret val${ os.EOL - }` - ) - expect(process.stdout.write).toHaveBeenNthCalledWith( - 2, + }`, `##[set-secret]special secret val${os.EOL}` - ) + ]) }) it('setSecret escapes secret values', () => { core.setSecret('my secret2', 'secret val\r\n') expect(process.env['my secret2']).toBe('secret val\r\n') - expect(process.stdout.write).toHaveBeenNthCalledWith( - 1, - `##[set-variable name=my secret2;]secret val%0D%0A${os.EOL}` - ) - expect(process.stdout.write).toHaveBeenNthCalledWith( - 2, + assertWriteCalls([ + `##[set-variable name=my secret2;]secret val%0D%0A${os.EOL}`, `##[set-secret]secret val%0D%0A${os.EOL}` - ) + ]) }) it('getInput gets non-required input', () => { @@ -126,56 +109,51 @@ describe('@actions/core', () => { it('setFailure sets the correct exit code and failure message', () => { core.setFailed('Failure message') expect(process.exitCode).toBe(1) - expect(process.stdout.write).toHaveBeenCalledWith( - `##[error]Failure message${os.EOL}` - ) + assertWriteCalls([`##[error]Failure message${os.EOL}`]) }) it('setFailure escapes the failure message', () => { core.setFailed('Failure \r\n\nmessage\r') expect(process.exitCode).toBe(1) - expect(process.stdout.write).toHaveBeenCalledWith( - `##[error]Failure %0D%0A%0Amessage%0D${os.EOL}` - ) + assertWriteCalls([`##[error]Failure %0D%0A%0Amessage%0D${os.EOL}`]) }) it('error sets the correct error message', () => { core.error('Error message') - expect(process.stdout.write).toHaveBeenCalledWith( - `##[error]Error message${os.EOL}` - ) + assertWriteCalls([`##[error]Error message${os.EOL}`]) }) it('error escapes the error message', () => { core.error('Error message\r\n\n') - expect(process.stdout.write).toHaveBeenCalledWith( - `##[error]Error message%0D%0A%0A${os.EOL}` - ) + assertWriteCalls([`##[error]Error message%0D%0A%0A${os.EOL}`]) }) it('warning sets the correct message', () => { core.warning('Warning') - expect(process.stdout.write).toHaveBeenCalledWith( - `##[warning]Warning${os.EOL}` - ) + assertWriteCalls([`##[warning]Warning${os.EOL}`]) }) it('warning escapes the message', () => { core.warning('\r\nwarning\n') - expect(process.stdout.write).toHaveBeenCalledWith( - `##[warning]%0D%0Awarning%0A${os.EOL}` - ) + assertWriteCalls([`##[warning]%0D%0Awarning%0A${os.EOL}`]) }) it('debug sets the correct message', () => { core.debug('Debug') - expect(process.stdout.write).toHaveBeenCalledWith(`##[debug]Debug${os.EOL}`) + assertWriteCalls([`##[debug]Debug${os.EOL}`]) }) it('debug escapes the message', () => { core.debug('\r\ndebug\n') - expect(process.stdout.write).toHaveBeenCalledWith( - `##[debug]%0D%0Adebug%0A${os.EOL}` - ) + assertWriteCalls([`##[debug]%0D%0Adebug%0A${os.EOL}`]) }) }) + +// Assert that process.stdout.write calls called only with the given arguments. +function assertWriteCalls(calls: string[]) { + expect(process.stdout.write).toHaveBeenCalledTimes(calls.length) + + for (let i = 0; i < calls.length; i++) { + expect(process.stdout.write).toHaveBeenNthCalledWith(i + 1, calls[i]) + } +} From ea0dd28ea794123383a132d1a7cf7f4118db032a Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Tue, 21 May 2019 14:38:29 -0400 Subject: [PATCH 28/34] Clean up/respond to feedback --- packages/core/__tests__/lib.test.ts | 12 +-- packages/core/src/command.ts | 81 +++++++++++++++ packages/core/src/core.ts | 14 +-- packages/core/src/internal.ts | 154 ---------------------------- 4 files changed, 94 insertions(+), 167 deletions(-) create mode 100644 packages/core/src/command.ts delete mode 100644 packages/core/src/internal.ts diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index b1202d1c..c15132bd 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -46,8 +46,8 @@ describe('@actions/core', () => { assertWriteCalls([`##[set-variable name=my var2;]var val%0D%0A${os.EOL}`]) }) - it('setSecret produces the correct commands and sets the env', () => { - core.setSecret('my secret', 'secret val') + it('exportSecret produces the correct commands and sets the env', () => { + core.exportSecret('my secret', 'secret val') expect(process.env['my secret']).toBe('secret val') assertWriteCalls([ `##[set-variable name=my secret;]secret val${os.EOL}`, @@ -55,8 +55,8 @@ describe('@actions/core', () => { ]) }) - it('setSecret escapes secret names', () => { - core.setSecret('special char secret \r\n];', 'special secret val') + it('exportSecret escapes secret names', () => { + core.exportSecret('special char secret \r\n];', 'special secret val') expect(process.env['special char secret \r\n];']).toBe('special secret val') assertWriteCalls([ `##[set-variable name=special char secret %0D%0A%5D%3B;]special secret val${ @@ -66,8 +66,8 @@ describe('@actions/core', () => { ]) }) - it('setSecret escapes secret values', () => { - core.setSecret('my secret2', 'secret val\r\n') + it('exportSecret escapes secret values', () => { + core.exportSecret('my secret2', 'secret val\r\n') expect(process.env['my secret2']).toBe('secret val\r\n') assertWriteCalls([ `##[set-variable name=my secret2;]secret val%0D%0A${os.EOL}`, diff --git a/packages/core/src/command.ts b/packages/core/src/command.ts new file mode 100644 index 00000000..0f949779 --- /dev/null +++ b/packages/core/src/command.ts @@ -0,0 +1,81 @@ +import * as os from 'os' + +/** + * Commands + * + * Command Format: + * ##[name key=value;key=value]message + * + * Examples: + * ##[warning]This is the user warning message + * ##[set-secret name=mypassword]definatelyNotAPassword! + */ +export function issueCommand( + command: string, + properties: any, + message: string +) { + const cmd = new Command(command, properties, message) + process.stdout.write(cmd.toString() + os.EOL) +} + +export function issue(name: string, message: string) { + issueCommand(name, {}, message) +} + +const CMD_PREFIX = '##[' + +class Command { + constructor(command: string, properties: {[key: string]: string}, message: string) { + if (!command) { + command = 'missing.command' + } + + this.command = command + this.properties = properties + this.message = message + } + + public command: string + public message: string + public properties: {[key: string]: string} + + public toString() { + let cmdStr = CMD_PREFIX + this.command + + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' ' + 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 || ''}`)};` + } + } + } + } + + cmdStr += ']' + + // safely append the message - avoid blowing up when attempting to + // call .replace() if message is not a string for some reason + const message: string = `${this.message || ''}` + cmdStr += escapedata(message) + + return cmdStr + } +} + +function escapedata(s: string): string { + return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A') +} + +function escape(s: string): string { + return s + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/]/g, '%5D') + .replace(/;/g, '%3B') +} \ No newline at end of file diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 23ee6770..6e6c548c 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -1,5 +1,5 @@ import {ExitCode} from '@actions/exit' -import * as intm from './internal' +import * as intm from './command' /** * Interface for getInput options @@ -20,7 +20,7 @@ export interface InputOptions { */ export function exportVariable(name: string, val: string) { process.env[name] = val - intm._issueCommand('set-variable', {name}, val) + intm.issueCommand('set-variable', {name}, val) } /** @@ -28,9 +28,9 @@ export function exportVariable(name: string, val: string) { * @param name the name of the variable to set * @param val value of the secret */ -export function setSecret(name: string, val: string) { +export function exportSecret(name: string, val: string) { exportVariable(name, val) - intm._issueCommand('set-secret', {}, val) + intm.issueCommand('set-secret', {}, val) } /** @@ -80,7 +80,7 @@ export function setFailed(message: string) { * @param message debug message */ export function debug(message: string) { - intm._issueCommand('debug', {}, message) + intm.issueCommand('debug', {}, message) } /** @@ -88,7 +88,7 @@ export function debug(message: string) { * @param message error issue message */ export function error(message: string) { - intm._issue('error', message) + intm.issue('error', message) } /** @@ -96,5 +96,5 @@ export function error(message: string) { * @param message warning issue message */ export function warning(message: string) { - intm._issue('warning', message) + intm.issue('warning', message) } diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts deleted file mode 100644 index 032ee4ff..00000000 --- a/packages/core/src/internal.ts +++ /dev/null @@ -1,154 +0,0 @@ -import * as os from 'os' - -/** - * Commands - * - * Command Format: - * ##[name key=value;key=value]message - * - * Examples: - * ##[warning]This is the user warning message - * ##[set-secret name=mypassword]definatelyNotAPassword! - */ -export function _issueCommand( - command: string, - properties: any, - message: string -) { - const cmd = new _Command(command, properties, message) - _writeLine(cmd.toString()) -} - -export function _issue(name: string, message: string) { - _issueCommand(name, {}, message) -} - -const CMD_PREFIX = '##[' - -export class _Command { - constructor(command: string, properties: any, message: string) { - if (!command) { - command = 'missing.command' - } - - this.command = command - this.properties = properties - this.message = message - } - - public command: string - public message: string - public properties: any - - public toString() { - let cmdStr = CMD_PREFIX + this.command - - if (this.properties && Object.keys(this.properties).length > 0) { - cmdStr += ' ' - 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 || ''}`)};` - } - } - } - } - - cmdStr += ']' - - // safely append the message - avoid blowing up when attempting to - // call .replace() if message is not a string for some reason - const message: string = `${this.message || ''}` - cmdStr += escapedata(message) - - return cmdStr - } -} - -export function _commandFromString(commandLine: string) { - const lbPos = commandLine.indexOf('[') - const rbPos = commandLine.indexOf(']') - - if (lbPos === -1 || rbPos === -1 || rbPos - lbPos < 3) { - throw new Error('Invalid command brackets') - } - const cmdInfo = commandLine.substring(lbPos + 1, rbPos) - const spaceIdx = cmdInfo.indexOf(' ') - - let command = cmdInfo - const properties: {[key: string]: string} = {} - - if (spaceIdx > 0) { - command = cmdInfo.trim().substring(0, spaceIdx) - const propSection = cmdInfo.trim().substring(spaceIdx + 1) - - const propLines: string[] = propSection.split(';') - - for (let propLine of propLines) { - propLine = propLine.trim() - - if (propLine.length > 0) { - const eqIndex = propLine.indexOf('=') - - if (eqIndex === -1) { - throw new Error(`Invalid property: ${propLine}`) - } - - const key: string = propLine.substring(0, eqIndex) - const val: string = propLine.substring(eqIndex + 1) - - properties[key] = unescape(val) - } - } - } - - const msg: string = unescapedata(commandLine.substring(rbPos + 1)) - const cmd = new _Command(command, properties, msg) - return cmd -} - -function escapedata(s: string): string { - return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A') -} - -function unescapedata(s: string): string { - return s.replace(/%0D/g, '\r').replace(/%0A/g, '\n') -} - -function escape(s: string): string { - return s - .replace(/\r/g, '%0D') - .replace(/\n/g, '%0A') - .replace(/]/g, '%5D') - .replace(/;/g, '%3B') -} - -function unescape(s: string): string { - return s - .replace(/%0D/g, '\r') - .replace(/%0A/g, '\n') - .replace(/%5D/g, ']') - .replace(/%3B/g, ';') -} - -//----------------------------------------------------- -// Streams: allow to override the stream -//----------------------------------------------------- - -let _outStream = process.stdout -let _errStream = process.stderr // eslint-disable-line @typescript-eslint/no-unused-vars - -export function _writeLine(str: string): void { - _outStream.write(str + os.EOL) -} - -export function _setStdStream(stdStream: NodeJS.WriteStream): void { - _outStream = stdStream -} - -export function _setErrStream(errStream: NodeJS.WriteStream): void { - _errStream = errStream -} From b25c8772ede48419c1389417269746e91ef15dc5 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Tue, 21 May 2019 14:40:57 -0400 Subject: [PATCH 29/34] set-env instead of set-variable --- packages/core/__tests__/lib.test.ts | 12 ++++++------ packages/core/src/core.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/__tests__/lib.test.ts b/packages/core/__tests__/lib.test.ts index c15132bd..a21b51e1 100644 --- a/packages/core/__tests__/lib.test.ts +++ b/packages/core/__tests__/lib.test.ts @@ -29,28 +29,28 @@ describe('@actions/core', () => { it('exportVariable produces the correct command and sets the env', () => { core.exportVariable('my var', 'var val') - assertWriteCalls([`##[set-variable name=my var;]var val${os.EOL}`]) + assertWriteCalls([`##[set-env name=my var;]var val${os.EOL}`]) }) it('exportVariable escapes variable names', () => { core.exportVariable('special char var \r\n];', 'special val') expect(process.env['special char var \r\n];']).toBe('special val') assertWriteCalls([ - `##[set-variable name=special char var %0D%0A%5D%3B;]special val${os.EOL}` + `##[set-env name=special char var %0D%0A%5D%3B;]special val${os.EOL}` ]) }) it('exportVariable escapes variable values', () => { core.exportVariable('my var2', 'var val\r\n') expect(process.env['my var2']).toBe('var val\r\n') - assertWriteCalls([`##[set-variable name=my var2;]var val%0D%0A${os.EOL}`]) + assertWriteCalls([`##[set-env name=my var2;]var val%0D%0A${os.EOL}`]) }) it('exportSecret produces the correct commands and sets the env', () => { core.exportSecret('my secret', 'secret val') expect(process.env['my secret']).toBe('secret val') assertWriteCalls([ - `##[set-variable name=my secret;]secret val${os.EOL}`, + `##[set-env name=my secret;]secret val${os.EOL}`, `##[set-secret]secret val${os.EOL}` ]) }) @@ -59,7 +59,7 @@ describe('@actions/core', () => { core.exportSecret('special char secret \r\n];', 'special secret val') expect(process.env['special char secret \r\n];']).toBe('special secret val') assertWriteCalls([ - `##[set-variable name=special char secret %0D%0A%5D%3B;]special secret val${ + `##[set-env name=special char secret %0D%0A%5D%3B;]special secret val${ os.EOL }`, `##[set-secret]special secret val${os.EOL}` @@ -70,7 +70,7 @@ describe('@actions/core', () => { core.exportSecret('my secret2', 'secret val\r\n') expect(process.env['my secret2']).toBe('secret val\r\n') assertWriteCalls([ - `##[set-variable name=my secret2;]secret val%0D%0A${os.EOL}`, + `##[set-env name=my secret2;]secret val%0D%0A${os.EOL}`, `##[set-secret]secret val%0D%0A${os.EOL}` ]) }) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 6e6c548c..9fb0d782 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -20,7 +20,7 @@ export interface InputOptions { */ export function exportVariable(name: string, val: string) { process.env[name] = val - intm.issueCommand('set-variable', {name}, val) + intm.issueCommand('set-env', {name}, val) } /** From 6bd119002c6f37f1c87d68fd0c4b9ee80677aafe Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Tue, 21 May 2019 14:44:37 -0400 Subject: [PATCH 30/34] Add internal disclaimer in command.ts --- packages/core/src/command.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/command.ts b/packages/core/src/command.ts index 0f949779..2235a464 100644 --- a/packages/core/src/command.ts +++ b/packages/core/src/command.ts @@ -1,5 +1,7 @@ import * as os from 'os' +// For internal use, subject to change. + /** * Commands * From 140d6cd367c91b715b672fcaea5ee4df34e96bb5 Mon Sep 17 00:00:00 2001 From: Danny McCormick Date: Tue, 21 May 2019 14:45:27 -0400 Subject: [PATCH 31/34] Run formatter --- packages/core/src/command.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/core/src/command.ts b/packages/core/src/command.ts index 2235a464..a3d241b6 100644 --- a/packages/core/src/command.ts +++ b/packages/core/src/command.ts @@ -28,7 +28,11 @@ export function issue(name: string, message: string) { const CMD_PREFIX = '##[' class Command { - constructor(command: string, properties: {[key: string]: string}, message: string) { + constructor( + command: string, + properties: {[key: string]: string}, + message: string + ) { if (!command) { command = 'missing.command' } @@ -80,4 +84,4 @@ function escape(s: string): string { .replace(/\n/g, '%0A') .replace(/]/g, '%5D') .replace(/;/g, '%3B') -} \ No newline at end of file +} From 8cdf51d18234441906c40ac779495b89687f5ff0 Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 15:23:21 -0400 Subject: [PATCH 32/34] Remove "public" explicit accessibility --- .eslintrc.json | 5 +++-- packages/core/src/command.ts | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index db3c608d..241986aa 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -10,11 +10,12 @@ "no-unused-vars": "off", "eslint-comments/no-use": "off", "import/no-namespace": "off", - "@typescript-eslint/no-unused-vars": "error" + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}] }, "env": { "node": true, "es6": true, "jest/globals": true } -} \ No newline at end of file +} diff --git a/packages/core/src/command.ts b/packages/core/src/command.ts index a3d241b6..41724d37 100644 --- a/packages/core/src/command.ts +++ b/packages/core/src/command.ts @@ -42,11 +42,11 @@ class Command { this.message = message } - public command: string - public message: string - public properties: {[key: string]: string} + command: string + message: string + properties: {[key: string]: string} - public toString() { + toString() { let cmdStr = CMD_PREFIX + this.command if (this.properties && Object.keys(this.properties).length > 0) { From ab9b8a0c872a6e24b2d21f05c3c5e08b452f0f7e Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 15:24:46 -0400 Subject: [PATCH 33/34] Rename escapedata to escapeData --- packages/core/src/command.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/command.ts b/packages/core/src/command.ts index 41724d37..b277bfb2 100644 --- a/packages/core/src/command.ts +++ b/packages/core/src/command.ts @@ -68,13 +68,13 @@ class Command { // safely append the message - avoid blowing up when attempting to // call .replace() if message is not a string for some reason const message: string = `${this.message || ''}` - cmdStr += escapedata(message) + cmdStr += escapeData(message) return cmdStr } } -function escapedata(s: string): string { +function escapeData(s: string): string { return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A') } From b05738788e10b17ef8f921ed771f848aef65aaed Mon Sep 17 00:00:00 2001 From: Jonathan Clem Date: Tue, 21 May 2019 15:26:44 -0400 Subject: [PATCH 34/34] Rename command import in core.ts --- packages/core/src/core.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 9fb0d782..408b9ad1 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -1,5 +1,5 @@ import {ExitCode} from '@actions/exit' -import * as intm from './command' +import {issue, issueCommand} from './command' /** * Interface for getInput options @@ -20,7 +20,7 @@ export interface InputOptions { */ export function exportVariable(name: string, val: string) { process.env[name] = val - intm.issueCommand('set-env', {name}, val) + issueCommand('set-env', {name}, val) } /** @@ -30,7 +30,7 @@ export function exportVariable(name: string, val: string) { */ export function exportSecret(name: string, val: string) { exportVariable(name, val) - intm.issueCommand('set-secret', {}, val) + issueCommand('set-secret', {}, val) } /** @@ -80,7 +80,7 @@ export function setFailed(message: string) { * @param message debug message */ export function debug(message: string) { - intm.issueCommand('debug', {}, message) + issueCommand('debug', {}, message) } /** @@ -88,7 +88,7 @@ export function debug(message: string) { * @param message error issue message */ export function error(message: string) { - intm.issue('error', message) + issue('error', message) } /** @@ -96,5 +96,5 @@ export function error(message: string) { * @param message warning issue message */ export function warning(message: string) { - intm.issue('warning', message) + issue('warning', message) }