mirror of https://github.com/actions/toolkit
commit
026ad6f559
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
packages/*/node_modules/
|
||||||
|
packages/*/lib/
|
|
@ -10,7 +10,8 @@
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"eslint-comments/no-use": "off",
|
"eslint-comments/no-use": "off",
|
||||||
"import/no-namespace": "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": {
|
"env": {
|
||||||
"node": true,
|
"node": true,
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
packages/*/node_modules/
|
||||||
|
packages/*/lib/
|
|
@ -47,10 +47,16 @@ export interface InputOptions {
|
||||||
export function getInput(name: string, options?: InputOptions): string | undefined
|
export function getInput(name: string, options?: InputOptions): string | undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fail the action
|
* sets the status of the action to neutral
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
export function setFailure(message: string): void
|
export function setNeutral(message: string): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets the status of the action to failed
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
export function setFailed(message: string): void
|
||||||
```
|
```
|
||||||
|
|
||||||
### IO spec
|
### IO spec
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
"bootstrap": "lerna bootstrap",
|
"bootstrap": "lerna bootstrap",
|
||||||
"build": "lerna run tsc",
|
"build": "lerna run tsc",
|
||||||
"check-all": "concurrently \"npm:format-check\" \"npm:lint\" \"npm:test\" \"npm:build -- -- --noEmit\"",
|
"check-all": "concurrently \"npm:format-check\" \"npm:lint\" \"npm:test\" \"npm:build -- -- --noEmit\"",
|
||||||
"format": "prettier --write packages/*/src/**/*.ts",
|
"format": "prettier --write packages/**/*.ts",
|
||||||
"format-check": "prettier --check packages/*/src/**/*.ts",
|
"format-check": "prettier --check packages/**/*.ts",
|
||||||
"lint": "eslint packages/*/src/**/*.ts",
|
"lint": "eslint packages/**/*.ts",
|
||||||
"new-package": "scripts/create-package",
|
"new-package": "scripts/create-package",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# `@actions/core`
|
||||||
|
|
||||||
|
> Core functions for setting results, logging, registering secrets and exporting variables across actions
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
See [src/core.ts](src/core.ts).
|
|
@ -0,0 +1,159 @@
|
||||||
|
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(() => {
|
||||||
|
for (const key in testEnvVars)
|
||||||
|
process.env[key] = testEnvVars[key as keyof typeof testEnvVars]
|
||||||
|
|
||||||
|
process.stdout.write = jest.fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const key in testEnvVars) Reflect.deleteProperty(testEnvVars, key)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('exportVariable produces the correct command and sets the env', () => {
|
||||||
|
core.exportVariable('my var', 'var val')
|
||||||
|
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-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-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-env name=my secret;]secret val${os.EOL}`,
|
||||||
|
`##[set-secret]secret val${os.EOL}`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
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-env name=special char secret %0D%0A%5D%3B;]special secret val${
|
||||||
|
os.EOL
|
||||||
|
}`,
|
||||||
|
`##[set-secret]special secret val${os.EOL}`
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
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-env 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 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 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 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('setFailure sets the correct exit code and failure message', () => {
|
||||||
|
core.setFailed('Failure message')
|
||||||
|
expect(process.exitCode).toBe(1)
|
||||||
|
assertWriteCalls([`##[error]Failure message${os.EOL}`])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('setFailure escapes the failure message', () => {
|
||||||
|
core.setFailed('Failure \r\n\nmessage\r')
|
||||||
|
expect(process.exitCode).toBe(1)
|
||||||
|
assertWriteCalls([`##[error]Failure %0D%0A%0Amessage%0D${os.EOL}`])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('error sets the correct error message', () => {
|
||||||
|
core.error('Error message')
|
||||||
|
assertWriteCalls([`##[error]Error message${os.EOL}`])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('error escapes the error message', () => {
|
||||||
|
core.error('Error message\r\n\n')
|
||||||
|
assertWriteCalls([`##[error]Error message%0D%0A%0A${os.EOL}`])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('warning sets the correct message', () => {
|
||||||
|
core.warning('Warning')
|
||||||
|
assertWriteCalls([`##[warning]Warning${os.EOL}`])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('warning escapes the message', () => {
|
||||||
|
core.warning('\r\nwarning\n')
|
||||||
|
assertWriteCalls([`##[warning]%0D%0Awarning%0A${os.EOL}`])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('debug sets the correct message', () => {
|
||||||
|
core.debug('Debug')
|
||||||
|
assertWriteCalls([`##[debug]Debug${os.EOL}`])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('debug escapes the message', () => {
|
||||||
|
core.debug('\r\ndebug\n')
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"name": "@actions/core",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Actions core lib",
|
||||||
|
"keywords": [
|
||||||
|
"core",
|
||||||
|
"actions"
|
||||||
|
],
|
||||||
|
"author": "Bryan MacFarlane <bryanmac@microsoft.com>",
|
||||||
|
"homepage": "https://github.com/actions/toolkit/tree/master/packages/core",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "lib/core.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"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^12.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/exit": "^0.0.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
import * as os from 'os'
|
||||||
|
|
||||||
|
// For internal use, subject to change.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
command: string
|
||||||
|
message: string
|
||||||
|
properties: {[key: string]: string}
|
||||||
|
|
||||||
|
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')
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
import {ExitCode} from '@actions/exit'
|
||||||
|
import {issue, issueCommand} from './command'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
process.env[name] = val
|
||||||
|
issueCommand('set-env', {name}, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
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?: InputOptions): string {
|
||||||
|
const 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
// Results
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the action status to neutral
|
||||||
|
*/
|
||||||
|
export function setNeutral() {
|
||||||
|
process.exitCode = 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 = ExitCode.Failure
|
||||||
|
error(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
// Logging Commands
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes debug message to user log
|
||||||
|
* @param message debug message
|
||||||
|
*/
|
||||||
|
export function debug(message: string) {
|
||||||
|
issueCommand('debug', {}, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an error issue
|
||||||
|
* @param message error issue message
|
||||||
|
*/
|
||||||
|
export function error(message: string) {
|
||||||
|
issue('error', message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an warning issue
|
||||||
|
* @param message warning issue message
|
||||||
|
*/
|
||||||
|
export function warning(message: string) {
|
||||||
|
issue('warning', message)
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"outDir": "./lib",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src"
|
||||||
|
]
|
||||||
|
}
|
|
@ -4,8 +4,4 @@
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
See [src/exit.ts](src/exit.ts).
|
||||||
const exit = require('@actions/exit');
|
|
||||||
|
|
||||||
// TODO: DEMONSTRATE API
|
|
||||||
```
|
|
|
@ -4,8 +4,4 @@
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
See [src/toolkit.ts](src/toolkit.ts).
|
||||||
const github = require('@actions/toolkit');
|
|
||||||
|
|
||||||
// TODO: DEMONSTRATE API
|
|
||||||
```
|
|
Loading…
Reference in New Issue