From 5e9b79c3bb93a10bf201af59cc4d2aa52a9a62a7 Mon Sep 17 00:00:00 2001 From: Nick Alteen Date: Tue, 7 Jan 2025 16:40:12 -0500 Subject: [PATCH] Add platform utility support --- __fixtures__/exec.ts | 9 + __tests__/stubs/core/core.test.ts | 597 +++++++++++++++++++++ __tests__/stubs/core/path-utils.test.ts | 46 ++ docs/supported-functionality.md | 2 +- package-lock.json | 4 +- package.json | 2 +- src/stubs/core/core.ts | 656 ++++++++++++++++++++++++ src/stubs/core/path-utils.ts | 40 ++ src/stubs/core/platform.ts | 96 ++++ src/stubs/core/summary.ts | 432 ++++++++++++++++ src/types.ts | 87 ---- tsconfig.eslint.json | 1 + 12 files changed, 1881 insertions(+), 91 deletions(-) create mode 100644 __fixtures__/exec.ts create mode 100644 __tests__/stubs/core/core.test.ts create mode 100644 __tests__/stubs/core/path-utils.test.ts create mode 100644 src/stubs/core/core.ts create mode 100644 src/stubs/core/path-utils.ts create mode 100644 src/stubs/core/platform.ts create mode 100644 src/stubs/core/summary.ts diff --git a/__fixtures__/exec.ts b/__fixtures__/exec.ts new file mode 100644 index 0000000..411cbe4 --- /dev/null +++ b/__fixtures__/exec.ts @@ -0,0 +1,9 @@ +import { jest } from '@jest/globals' + +export const exec = jest.fn().mockImplementation(() => {}) +export const getExecOutput = jest.fn().mockImplementation(() => {}) + +export default { + exec, + getExecOutput +} diff --git a/__tests__/stubs/core/core.test.ts b/__tests__/stubs/core/core.test.ts new file mode 100644 index 0000000..3d6a972 --- /dev/null +++ b/__tests__/stubs/core/core.test.ts @@ -0,0 +1,597 @@ +import { jest } from '@jest/globals' +import { + CoreMeta, + ResetCoreMetadata, + addPath, + debug, + endGroup, + error, + exportVariable, + getBooleanInput, + getIDToken, + getInput, + getMultilineInput, + getState, + group, + info, + isDebug, + log, + notice, + saveState, + setCommandEcho, + setFailed, + setOutput, + setSecret, + startGroup, + warning +} from '../../../src/stubs/core/core.js' +import { EnvMeta, ResetEnvMetadata } from '../../../src/stubs/env.js' +import type { CoreMetadata } from '../../../src/types.js' + +/** Empty CoreMetadata Object */ +const empty: CoreMetadata = { + exitCode: 0, + exitMessage: '', + outputs: {}, + secrets: [], + stepDebug: process.env.ACTIONS_STEP_DEBUG === 'true', + stepSummaryPath: process.env.GITHUB_STEP_SUMMARY ?? '', + echo: false, + state: {}, + colors: { + cyan: console.log, + blue: console.log, + gray: console.log, + green: console.log, + magenta: console.log, + red: console.log, + white: console.log, + yellow: console.log + } +} + +// Prevent output during tests +jest.spyOn(console, 'log').mockImplementation(() => {}) +jest.spyOn(console, 'table').mockImplementation(() => {}) + +describe('Core', () => { + beforeEach(() => { + // Reset metadata + ResetEnvMetadata() + ResetCoreMetadata() + }) + + afterEach(() => { + // Reset all spies + jest.resetAllMocks() + }) + + describe('CoreMeta', () => { + it('Tracks updates to the core metadata', () => { + // Initial state should be empty + expect(CoreMeta.exitCode).toEqual(empty.exitCode) + expect(CoreMeta.exitMessage).toEqual(empty.exitMessage) + expect(CoreMeta.outputs).toMatchObject(empty.outputs) + expect(CoreMeta.secrets).toMatchObject(empty.secrets) + expect(CoreMeta.stepDebug).toEqual(empty.stepDebug) + expect(CoreMeta.stepSummaryPath).toEqual(empty.stepSummaryPath) + expect(CoreMeta.echo).toEqual(empty.echo) + expect(CoreMeta.state).toMatchObject(empty.state) + + // Update the metadata + CoreMeta.exitCode = 1 + CoreMeta.exitMessage = 'test' + CoreMeta.outputs = { 'my-output': 'test' } + CoreMeta.secrets = ['secret-value-1234'] + CoreMeta.stepDebug = true + CoreMeta.stepSummaryPath = 'test' + CoreMeta.echo = true + CoreMeta.state = { 'my-state': 'test' } + + // Verify the updated metadata + expect(CoreMeta.exitCode).toEqual(1) + expect(CoreMeta.exitMessage).toEqual('test') + expect(CoreMeta.outputs).toMatchObject({ 'my-output': 'test' }) + expect(CoreMeta.secrets).toMatchObject(['secret-value-1234']) + expect(CoreMeta.stepDebug).toEqual(true) + expect(CoreMeta.stepSummaryPath).toEqual('test') + expect(CoreMeta.echo).toEqual(true) + expect(CoreMeta.state).toMatchObject({ 'my-state': 'test' }) + + // Reset the metadata + ResetCoreMetadata() + + // Verify the reset metadata + expect(CoreMeta.exitCode).toEqual(empty.exitCode) + expect(CoreMeta.exitMessage).toEqual(empty.exitMessage) + expect(CoreMeta.outputs).toMatchObject(empty.outputs) + expect(CoreMeta.secrets).toMatchObject(empty.secrets) + expect(CoreMeta.stepDebug).toEqual(empty.stepDebug) + expect(CoreMeta.stepSummaryPath).toEqual(empty.stepSummaryPath) + expect(CoreMeta.echo).toEqual(empty.echo) + expect(CoreMeta.state).toMatchObject(empty.state) + }) + + it('Defaults stepSummaryPath to an empty string', () => { + delete process.env.GITHUB_STEP_SUMMARY + + ResetCoreMetadata() + expect(CoreMeta.stepSummaryPath).toEqual('') + }) + + it('Sets stepSummaryPath from the environment', () => { + process.env.GITHUB_STEP_SUMMARY = 'summary.md' + + ResetCoreMetadata() + expect(CoreMeta.stepSummaryPath).toEqual('summary.md') + }) + }) + + describe('Core Stubs', () => { + describe('exportVariable()', () => { + it('Exports an environment variable', () => { + exportVariable('TEST', 'test') + expect(EnvMeta.env).toMatchObject({ TEST: 'test' }) + }) + + it('Exports undefined and null as empty strings', () => { + exportVariable('UNDEFINED_TEST', undefined) + exportVariable('NULL_TEST', undefined) + expect(EnvMeta.env).toMatchObject({ UNDEFINED_TEST: '', NULL_TEST: '' }) + }) + + it('Exports objects as stringified JSON', () => { + const testVariable = { + my_key: 'my_value' + } + + exportVariable('TEST', testVariable) + expect(EnvMeta.env).toMatchObject({ + TEST: JSON.stringify(testVariable) + }) + }) + }) + + describe('setSecre()', () => { + it('Sets a secret to mask', () => { + setSecret('test') + expect(CoreMeta.secrets).toMatchObject(['test']) + }) + }) + + describe('addPath()', () => { + it('Appends to the path', () => { + addPath('/usr/test') + expect(EnvMeta.path.includes('/usr/test')).toBeTruthy() + }) + }) + + describe('getInput()', () => { + it('Gets action inputs', () => { + // Test both upper and lower case versions of the input + process.env.INPUT_TEST = 'test-upper' + expect(getInput('test')).toEqual('test-upper') + + delete process.env.INPUT_TEST + + process.env.INPUT_test = 'test-lower' + expect(getInput('test')).toEqual('test-lower') + }) + + it('Returns an empty string', () => { + expect(getInput('test-input-missing')).toEqual('') + }) + + it('Throws an error if the input is required and not found', () => { + expect(() => + getInput('test-input-missing', { required: true }) + ).toThrow() + }) + + it('Trims whitespace', () => { + process.env.INPUT_TEST = ' test ' + expect(getInput('test', { trimWhitespace: true })).toEqual('test') + }) + }) + + describe('getMultilineInput()', () => { + it('Gets action inputs', () => { + // Test both upper and lower case versions of the input + process.env.INPUT_TEST = `test\nmultiline\nupper` + expect(getMultilineInput('test')).toMatchObject([ + 'test', + 'multiline', + 'upper' + ]) + + delete process.env.INPUT_TEST + + process.env.INPUT_test = `test\nmultiline\nlower` + expect(getMultilineInput('test')).toMatchObject([ + 'test', + 'multiline', + 'lower' + ]) + }) + + it('Returns an empty list if the input is not found', () => { + expect(getMultilineInput('test-input-missing')).toMatchObject([]) + }) + + it('Throws an error if the input is required and not found', () => { + expect(() => + getMultilineInput('test-input-missing', { + required: true + }) + ).toThrow() + }) + + it('Trims whitespace from the input', () => { + process.env.INPUT_TEST = ' test \n muliline \n spaces ' + expect( + getMultilineInput('test', { trimWhitespace: true }) + ).toMatchObject(['test', 'muliline', 'spaces']) + }) + }) + + describe('getBooleanInput()', () => { + it('Gets the action inputs', () => { + // Test both upper and lower case versions of the input + process.env.INPUT_TEST = 'true' + expect(getBooleanInput('test')).toBeTruthy() + + delete process.env.INPUT_TEST + + process.env.INPUT_test = 'false' + expect(getBooleanInput('test')).toBeFalsy() + }) + + it('Throws an error if the input is required and not found', () => { + expect(() => + getBooleanInput('test-input-missing', { + required: true + }) + ).toThrow() + }) + + it('Returns true or false for valid YAML boolean values', () => { + process.env.INPUT_TEST = 'true' + expect(getBooleanInput('test')).toBeTruthy() + + process.env.INPUT_TEST = 'True' + expect(getBooleanInput('test')).toBeTruthy() + + process.env.INPUT_TEST = 'TRUE' + expect(getBooleanInput('test')).toBeTruthy() + + process.env.INPUT_TEST = 'false' + expect(getBooleanInput('test')).toBeFalsy() + + process.env.INPUT_TEST = 'False' + expect(getBooleanInput('test')).toBeFalsy() + + process.env.INPUT_TEST = 'FALSE' + expect(getBooleanInput('test')).toBeFalsy() + }) + + it('Throws an error if the input is not a valid YAML boolean value', () => { + process.env.INPUT_TEST = 'This is not a valid boolean value' + expect(() => getBooleanInput('test')).toThrow() + }) + }) + + describe('setOutput()', () => { + it('Sets the action outputs', () => { + jest.spyOn(CoreMeta.colors, 'cyan').mockImplementation(() => {}) + + setOutput('my-output', 'output-value') + + expect(CoreMeta.outputs['my-output']).toEqual('output-value') + }) + + it('Logs the output to the console', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'cyan') + .mockImplementation(() => {}) + + setOutput('my-output', 'output-value') + + expect(core_outputSpy).toHaveBeenCalledWith( + '::set-output name=my-output::output-value' + ) + }) + }) + + describe('setCommandEcho()', () => { + it('Sets the command echo flag', () => { + setCommandEcho(true) + expect(CoreMeta.echo).toBeTruthy() + + setCommandEcho(false) + expect(CoreMeta.echo).toBeFalsy() + }) + }) + + describe('setFailed()', () => { + it('Sets the exit code to failure', () => { + jest.spyOn(CoreMeta.colors, 'red').mockImplementation(() => {}) + + setFailed('test') + + expect(CoreMeta.exitCode).toEqual(1) + expect(CoreMeta.exitMessage).toEqual('test') + }) + }) + + describe('log()', () => { + it('Throws an error if startLine and endLine are different when columns are set', () => { + expect((): void => + log('group', 'my message', { + startLine: 1, + endLine: 2, + startColumn: 1 + }) + ).toThrow() + + expect((): void => + log('group', 'my message', { + startLine: 1, + endLine: 2, + endColumn: 2 + }) + ).toThrow() + }) + + it('Logs only the color when no message is provided', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'blue') + .mockImplementation(() => {}) + + log('group') + + expect(core_outputSpy).toHaveBeenCalledWith('::group::') + }) + + it('Redacts secrets from the output', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'blue') + .mockImplementation(() => {}) + + // Set a secret to mask + CoreMeta.secrets = ['secret-value-1234'] + + log('group', 'my secret is secret-value-1234') + + expect(core_outputSpy).toHaveBeenCalledWith( + '::group::my secret is ****' + ) + }) + + it('Includes annotations in the output', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'blue') + .mockImplementation(() => {}) + + log('group', 'my message', { + title: 'my title', + file: 'my-file.txt' + }) + + expect(core_outputSpy).toHaveBeenCalledWith( + '::group title=my title,file=my-file.txt::my message' + ) + }) + + it('Defaults the endLine property to startLine', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'white') + .mockImplementation(() => {}) + + log('info', 'my message', { + startLine: 1 + }) + + expect(core_outputSpy).toHaveBeenCalledWith( + '::info line=1,endLine=1::my message' + ) + }) + + it('Defaults the endColumn property to startColumn', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'white') + .mockImplementation(() => {}) + + log('info', 'my message', { + startColumn: 1 + }) + + expect(core_outputSpy).toHaveBeenCalledWith( + '::info col=1,endColumn=1::my message' + ) + }) + }) + + describe('isDebug()', () => { + it('Returns the step debug setting', () => { + CoreMeta.stepDebug = true + expect(isDebug()).toBeTruthy() + + CoreMeta.stepDebug = false + expect(isDebug()).toBeFalsy() + }) + }) + + describe('debug()', () => { + it('Logs to the console if debug logging is enabled', () => { + // Enable step debug logging + CoreMeta.stepDebug = true + + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'gray') + .mockImplementation(() => {}) + + debug('test') + + expect(core_outputSpy).toHaveBeenCalledWith('::debug::test') + }) + + it('Does not log to the console if debug logging is disabled', () => { + // Disable step debug logging + CoreMeta.stepDebug = false + + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'gray') + .mockImplementation(() => {}) + + debug('test') + + expect(core_outputSpy).not.toHaveBeenCalled() + }) + }) + + describe('error()', () => { + it('Logs to the console', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'red') + .mockImplementation(() => {}) + + error('test') + + expect(core_outputSpy).toHaveBeenCalledWith('::error::test') + }) + }) + + describe('warning()', () => { + it('Logs to the console', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'yellow') + .mockImplementation(() => {}) + + warning('test') + + expect(core_outputSpy).toHaveBeenCalledWith('::warning::test') + }) + }) + + describe('notice()', () => { + it('Logs to the console', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'magenta') + .mockImplementation(() => {}) + + notice('test') + + expect(core_outputSpy).toHaveBeenCalledWith('::notice::test') + }) + }) + + describe('info()', () => { + it('Logs to the console', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'white') + .mockImplementation(() => {}) + + info('test') + + expect(core_outputSpy).toHaveBeenCalledWith('::info::test') + }) + }) + + describe('startGroup()', () => { + it('Logs to the console', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'blue') + .mockImplementation(() => {}) + + startGroup('test') + + expect(core_outputSpy).toHaveBeenCalledWith('::group::test') + }) + }) + + describe('endGroup()', () => { + it('Logs to the console', () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'blue') + .mockImplementation(() => {}) + + endGroup() + + expect(core_outputSpy).toHaveBeenCalledWith('::endgroup::') + }) + }) + + describe('group()', () => { + it('Logs grouped messages to the console', async () => { + const core_outputSpy = jest + .spyOn(CoreMeta.colors, 'blue') + .mockImplementation(() => {}) + + const core_infoSpy = jest.spyOn(CoreMeta.colors, 'white') + + await group('my-group', async () => { + info('test') + + await Promise.resolve() + + return + }) + + expect(core_outputSpy).toHaveBeenCalledWith('::group::my-group') + expect(core_infoSpy).toHaveBeenCalledWith('::info::test') + expect(core_outputSpy).toHaveBeenCalledWith('::endgroup::') + }) + }) + + describe('saveState()', () => { + it('Saves string data', () => { + saveState( + 'string-test', + JSON.stringify({ 'my-state': 'test-string-info' }) + ) + + expect(CoreMeta.state).toMatchObject({ + 'string-test': '{"my-state":"test-string-info"}' + }) + }) + + it('Saves JSON data', () => { + saveState('json-test', { 'my-state': 'test-json-info' }) + + expect(CoreMeta.state).toMatchObject({ + 'json-test': '{"my-state":"test-json-info"}' + }) + }) + + it('Saves null and undefined as empty strings', () => { + saveState('undefined-test', undefined) + saveState('null-test', null) + + expect(CoreMeta.state).toMatchObject({ + 'undefined-test': '', + 'null-test': '' + }) + }) + }) + + describe('getState()', () => { + it('Gets the state from the environment', () => { + CoreMeta.state = { + test: '{"my-state":"test-info"}' + } + + expect(getState('test')).toEqual('{"my-state":"test-info"}') + }) + + it('Returns an empty string for values not in the state', () => { + expect(getState('nonexistent-test')).toEqual('') + }) + }) + + describe('getIDToken()', () => { + it('Throws an error', async () => { + await expect(getIDToken()).rejects.toThrow('Not implemented') + }) + }) + }) +}) diff --git a/__tests__/stubs/core/path-utils.test.ts b/__tests__/stubs/core/path-utils.test.ts new file mode 100644 index 0000000..7211a7f --- /dev/null +++ b/__tests__/stubs/core/path-utils.test.ts @@ -0,0 +1,46 @@ +import { jest } from '@jest/globals' +import path from 'path' +import { ResetCoreMetadata } from '../../../src/stubs/core/core.js' +import { + toPlatformPath, + toPosixPath, + toWin32Path +} from '../../../src/stubs/core/path-utils.js' +import { ResetEnvMetadata } from '../../../src/stubs/env.js' + +// Prevent output during tests +jest.spyOn(console, 'log').mockImplementation(() => {}) +jest.spyOn(console, 'table').mockImplementation(() => {}) + +describe('path-utils', () => { + beforeEach(() => { + // Reset metadata + ResetEnvMetadata() + ResetCoreMetadata() + }) + + afterEach(() => { + // Reset all spies + jest.resetAllMocks() + }) + + describe('toWin32Path()', () => { + it('Returns a WIN32 path', () => { + expect(toWin32Path('C:/Users/mona/Desktop')).toEqual( + 'C:\\Users\\mona\\Desktop' + ) + }) + }) + + describe('toPlatformPath()', () => { + it('Returns a platform-specific path', () => { + expect(toPlatformPath('C:/Users/mona/Desktop')).toEqual( + `C:${path.sep}Users${path.sep}mona${path.sep}Desktop` + ) + + expect(toPosixPath('C:\\Users\\mona\\Desktop')).toEqual( + `C:${path.sep}Users${path.sep}mona${path.sep}Desktop` + ) + }) + }) +}) diff --git a/docs/supported-functionality.md b/docs/supported-functionality.md index 7317276..80ee968 100644 --- a/docs/supported-functionality.md +++ b/docs/supported-functionality.md @@ -66,7 +66,7 @@ to the `local-action` command. | `toPosixPath()` | :white_check_mark: | | | `toWin32Path()` | :white_check_mark: | | | `toPlatformPath()` | :white_check_mark: | | -| `platform.*` | :no_entry: | | +| `platform.*` | :white_check_mark: | | ## Under Investigation diff --git a/package-lock.json b/package-lock.json index 5286ec1..e8a7467 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@github/local-action", - "version": "2.3.0", + "version": "2.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@github/local-action", - "version": "2.3.0", + "version": "2.4.0", "license": "MIT", "dependencies": { "@actions/artifact": "^2.2.0", diff --git a/package.json b/package.json index c887ce6..6342ee8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@github/local-action", "description": "Local Debugging for GitHub Actions", - "version": "2.3.0", + "version": "2.4.0", "type": "module", "author": "Nick Alteen ", "private": false, diff --git a/src/stubs/core/core.ts b/src/stubs/core/core.ts new file mode 100644 index 0000000..a571934 --- /dev/null +++ b/src/stubs/core/core.ts @@ -0,0 +1,656 @@ +import path from 'path' +import type { CoreMetadata } from '../../types.js' +import { EnvMeta } from '../env.js' +import { toPlatformPath, toPosixPath, toWin32Path } from './path-utils.js' +import * as platform from './platform.js' +import { Summary } from './summary.js' + +export const CORE_STUBS = { + addPath, + debug, + endGroup, + error, + exportVariable, + getBooleanInput, + getIDToken, + getInput, + getMultilineInput, + getState, + group, + info, + isDebug, + notice, + platform, + saveState, + setCommandEcho, + setFailed, + setOutput, + setSecret, + startGroup, + summary: new Summary(), + toPlatformPath, + toPosixPath, + toWin32Path, + warning +} + +/** + * Metadata for `@actions/core` + */ +export const CoreMeta: CoreMetadata = { + echo: false, + exitCode: 0, + exitMessage: '', + outputs: {}, + secrets: [], + state: {}, + stepDebug: process.env.ACTIONS_STEP_DEBUG === 'true', + stepSummaryPath: + /* istanbul ignore next */ process.env.GITHUB_STEP_SUMMARY ?? '', + colors: { + cyan: /* istanbul ignore next */ (msg: string) => console.log(msg), + blue: /* istanbul ignore next */ (msg: string) => console.log(msg), + gray: /* istanbul ignore next */ (msg: string) => console.log(msg), + green: /* istanbul ignore next */ (msg: string) => console.log(msg), + magenta: /* istanbul ignore next */ (msg: string) => console.log(msg), + red: /* istanbul ignore next */ (msg: string) => console.log(msg), + white: /* istanbul ignore next */ (msg: string) => console.log(msg), + yellow: /* istanbul ignore next */ (msg: string) => console.log(msg) + } +} + +/** + * Resets the core metadata + * + * @returns void + */ +export function ResetCoreMetadata(): void { + CoreMeta.echo = false + CoreMeta.exitCode = 0 + CoreMeta.exitMessage = '' + CoreMeta.outputs = {} + CoreMeta.secrets = [] + CoreMeta.state = {} + CoreMeta.stepDebug = process.env.ACTIONS_STEP_DEBUG === 'true' + CoreMeta.stepSummaryPath = process.env.GITHUB_STEP_SUMMARY ?? '' +} + +/** + * @github/local-action Unmodified + * + * Optional properties that can be sent with annotation commands (notice, error, + * and warning). + */ +export type AnnotationProperties = { + /** A title for the annotation. */ + title?: string + + /** The path of the file for which the annotation should be created. */ + file?: string + + /** The start line for the annotation. */ + startLine?: number + + /** + * The end line for the annotation. Defaults to `startLine` when `startLine` + * is provided. + */ + endLine?: number + + /** + * The start column for the annotation. Cannot be sent when `startLine` and + * `endLine` are different values. + */ + startColumn?: number + + /** + * The end column for the annotation. Cannot be sent when `startLine` and + * `endLine` are different values. Defaults to `startColumn` when + * `startColumn` is provided. + */ + endColumn?: number +} + +/** + * @github/local-action Unmodified + * + * Options for getInput. + */ +export type InputOptions = { + /** + * Optional. Whether the input is required. If required and not present, + * will throw. Defaults to false. + */ + required?: boolean + + /** + * Optional. Whether leading/trailing whitespace will be trimmed for the + * input. Defaults to true. + */ + trimWhitespace?: boolean +} + +/** + * @github/local-action Modified + * + * Prepends to the PATH + * + * @param inputPath The path to prepend to the PATH + * @returns void + */ +export function addPath(inputPath: string): void { + EnvMeta.path = `${inputPath}${path.delimiter}${process.env.PATH}` + process.env.PATH = EnvMeta.path +} + +//----------------------------------------------------------------------- +// Variables +//----------------------------------------------------------------------- + +/** + * @github/local-action Modified + * + * Saves an environment variable + * + * @param name The name of the environment variable + * @param value The value of the environment variable + * @returns void + */ +export function exportVariable( + name: string, + value: string | undefined | null | object +): void { + // Convert the value to a string + value = + value === undefined || value === null + ? '' + : typeof value === 'string' || value instanceof String + ? value.toString() + : JSON.stringify(value) + + EnvMeta.env[name] = value + process.env[name] = value +} + +/** + * @github/local-action Modified + * + * Register a secret to mask it in logs + * + * @param secret The value to register + * @returns void + */ +export function setSecret(secret: string): void { + CoreMeta.secrets.push(secret) +} + +/** + * @github/local-action Modified + * + * Gets the action input from the environment variables + * + * @param name The name of the input + * @param options The options for the input + * @returns The value of the input + */ +export function getInput(name: string, options?: InputOptions): string { + // Get input by name, or an empty string if not found + let input: string = + process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || + process.env[`INPUT_${name.replace(/ /g, '_')}`] || + '' + + // Throw an error if the input is required and not supplied + if (options && options.required === true && input === '') + throw new Error(`Input required and not supplied: ${name}`) + + // Trim whitespace, if specified + if (options && options.trimWhitespace === true) input = input.trim() + + return input +} + +/** + * @github/local-action Modified + * + * Gets multiline inputs from environment variables + * + * @param name The name of the input + * @param options The options for the input + * @returns The value of the input + */ +export function getMultilineInput( + name: string, + options?: InputOptions +): string[] { + // Get input by name, split by newline, and filter out empty strings + const input: string[] = ( + process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || + process.env[`INPUT_${name.replace(/ /g, '_')}`] || + '' + ) + .split('\n') + .filter(x => x !== '') + + // Throw an error if the input is required and not supplied + if (options && options.required === true && input.length === 0) + throw new Error(`Input required and not supplied: ${name}`) + + // Trim whitespace, if specified + if (options && options.trimWhitespace === true) + return input.map(x => x.trim()) + + return input +} + +/** + * @github/local-action Modified + * + * Gets boolean inputs from environment variables + * + * @param name The name of the input + * @param options The options for the input + * @returns The value of the input + */ +export function getBooleanInput(name: string, options?: InputOptions): boolean { + // This is effectively a copy of the actual `getInput` function, instead of + // using proxyquire's `callThru()` option. + + // Get input by name, or an empty string if not found + const input: string = ( + process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || + process.env[`INPUT_${name.replace(/ /g, '_')}`] || + '' + ).trim() + + // Throw an error if the input is required and not supplied + if (options && options.required === true && input === '') + throw new Error(`Input required and not supplied: ${name}`) + + if (['true', 'True', 'TRUE'].includes(input)) return true + if (['false', 'False', 'FALSE'].includes(input)) return false + + // Throw an error if the input is not a boolean + throw new TypeError( + `Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` + + `Support boolean input list: \`true | True | TRUE | false | False | FALSE\`` + ) +} + +/** + * @github/local-action Modified + * + * Saves outputs and logs to the console + * + * @param name The name of the output + * @param value The value of the output + * @returns void + */ +export function setOutput(name: string, value: string): void { + CoreMeta.outputs[name] = value + + // This command is deprecated...it is being used here so there's meaningful + // log output for debugging purposes. + CoreMeta.colors.cyan(`::set-output name=${name}::${value}`) +} + +/** + * @github/local-action Modified + * + * Enables or disables the echoing of commands into stdout. + * + * @todo Currently this does nothing. + * + * @param enabled Whether to enable command echoing + * @returns void + */ +export function setCommandEcho(enabled: boolean): void { + CoreMeta.echo = enabled +} + +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- + +/** + * @github/local-action Modified + * + * Set the action status to failed + * + * @param message The message to log + * @returns void + */ +export function setFailed(message: string | Error): void { + CoreMeta.exitCode = 1 + CoreMeta.exitMessage = message.toString() + + error(message.toString()) +} + +//----------------------------------------------------------------------- +// Logging Commands +//----------------------------------------------------------------------- + +/** + * @github/local-action New + * + * Logs a message with optional annotations + * + * This is used internally by the other logging functions. It doesn't need to be + * called directly. + * + * @param type The type of log message + * @param message The message to log + * @param properties The annotation properties + * @returns void + */ +export function log( + type: string, + message?: string, + properties: AnnotationProperties = { + title: undefined, + file: undefined, + startLine: undefined, + endLine: undefined, + startColumn: undefined, + endColumn: undefined + } +): void { + const params: string[] = [] + + /* istanbul ignore next */ + const color = + { + debug: CoreMeta.colors.gray, + error: CoreMeta.colors.red, + warning: CoreMeta.colors.yellow, + notice: CoreMeta.colors.magenta, + info: CoreMeta.colors.white, + group: CoreMeta.colors.blue, + endgroup: CoreMeta.colors.blue + }[type] ?? CoreMeta.colors.gray + + // Default endLine to startLine if not provided + if (properties.startLine && !properties.endLine) + properties.endLine = properties.startLine + + // Default endColumn to startColumn if not provided + if (properties.startColumn && !properties.endColumn) + properties.endColumn = properties.startColumn + + // Throw an error if startLine and endLine are different and startColumn or + // endColumn are set + if ( + properties.startLine && + properties.endLine && + properties.startLine !== properties.endLine && + (properties.startColumn || properties.endColumn) + ) + throw new Error( + 'Error: Annotations spanning multiple lines must not include startColumn or endColumn' + ) + + if (properties.title) params.push(`title=${properties.title}`) + if (properties.file) params.push(`file=${properties.file}`) + if (properties.startLine) params.push(`line=${properties.startLine}`) + if (properties.endLine) params.push(`endLine=${properties.endLine}`) + if (properties.startColumn) params.push(`col=${properties.startColumn}`) + if (properties.endColumn) params.push(`endColumn=${properties.endColumn}`) + + if (message === undefined) return color(`::${type}::`) + + // Check for any secrets and redact them + for (const secret of CoreMeta.secrets) + if (message?.includes(secret)) message = message.replaceAll(secret, '****') + + if (params.length > 0) color(`::${type} ${params.join(',')}::${message}`) + else color(`::${type}::${message}`) +} + +/** + * @github/local-action Modified + * + * Returns true if debugging is enabled + * + * @returns Whether debugging is enabled + */ +export function isDebug(): boolean { + return CoreMeta.stepDebug +} + +/** + * @github/local-action Modified + * + * Logs a debug message to the console + * + * E.g. `::debug::{message}` + * + * @param message The message to log + * @returns void + */ +export function debug(message: string): void { + // Only log debug messages if the `stepDebug` flag is set + if (!CoreMeta.stepDebug) return + + log('debug', message) +} + +/** + * @github/local-action Modified + * + * Logs an error message to the console + * + * E.g. `::error file={name},line={line},endLine={endLine},title={title}::{message}` + * + * @param message The message to log + * @param properties The annotation properties + * @returns void + */ +export function error( + message: string | Error, + properties: AnnotationProperties = { + title: undefined, + file: undefined, + startLine: undefined, + endLine: undefined, + startColumn: undefined, + endColumn: undefined + } +): void { + log( + 'error', + /* istanbul ignore next */ + message instanceof Error ? message.toString() : message, + properties + ) +} + +/** + * @github/local-action Modified + * + * Logs a warning message to the console + * + * E.g. `::warning file={name},line={line},endLine={endLine},title={title}::{message}` + * + * @param message The message to log + * @param properties The annotation properties + * @returns void + */ +export function warning( + message: string | Error, + properties: AnnotationProperties = { + title: undefined, + file: undefined, + startLine: undefined, + endLine: undefined, + startColumn: undefined, + endColumn: undefined + } +): void { + log( + 'warning', + /* istanbul ignore next */ + message instanceof Error ? message.toString() : message, + properties + ) +} + +/** + * @github/local-action Modified + * + * Logs a notice message to the console + * + * E.g. `::notice file={name},line={line},endLine={endLine},title={title}::{message}` + * + * @param message The message to log + * @param properties The annotation properties + * @returns void + */ +export function notice( + message: string | Error, + properties: AnnotationProperties = { + title: undefined, + file: undefined, + startLine: undefined, + endLine: undefined, + startColumn: undefined, + endColumn: undefined + } +): void { + log( + 'notice', + /* istanbul ignore next */ + message instanceof Error ? message.toString() : message, + properties + ) +} + +/** + * @github/local-action Modified + * + * Logs an info message to the console + * + * E.g. `::info::{message}` + * + * @param message The message to log + * @returns void + */ +export function info(message: string): void { + log('info', message) +} + +/** + * @github/local-action Modified + * + * Starts a group of log lines + * + * @param title The title of the group + * @returns void + */ +export function startGroup(title: string): void { + log('group', title, { + title: undefined, + file: undefined, + startLine: undefined, + endLine: undefined, + startColumn: undefined, + endColumn: undefined + }) +} + +/** + * @github/local-action Modified + * + * Ends a group of log lines + * + * @param title The title of the group + * @returns void + */ +export function endGroup(): void { + log('endgroup', undefined, { + title: undefined, + file: undefined, + startLine: undefined, + endLine: undefined, + startColumn: undefined, + endColumn: undefined + }) +} + +/** + * @github/local-action Unmodified + * + * Wraps an asynchronous function call in a group + * + * @param name The name of the group + * @param fn The function to call + * @returns A promise that resolves the result of the function + */ +export async function group(name: string, fn: () => Promise): Promise { + // Start the group + startGroup(name) + + let result: T + + try { + // Await the function + result = await fn() + } finally { + endGroup() + } + + return result +} + +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- + +/** + * @github/local-action Modified + * + * Save the state of the action. + * + * For testing purposes, this does nothing other than save it to the `state` + * property. + * + * @param name The name of the state + * @param value The value of the state + * @returns void + */ +export function saveState(name: string, value: unknown): void { + if (value === undefined || value === null) CoreMeta.state[name] = '' + else if (typeof value === 'string' || value instanceof String) + CoreMeta.state[name] = value as string + else CoreMeta.state[name] = JSON.stringify(value) +} + +/** + * @github/local-action Modified + * + * Get the state for the action. + * + * For testing purposes, this does nothing other than return the value from the + * `state` property. + * + * @param name The name of the state + * @returns The value of the state + */ +export function getState(name: string): string { + return CoreMeta.state[name] || '' +} + +/** + * @github/local-action Modified + * + * Gets an OIDC token + * + * @todo Implement + * + * @param aud The audience for the token + * @returns A promise that resolves the OIDC token + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export async function getIDToken(aud?: string): Promise { + throw new Error('Not implemented') +} diff --git a/src/stubs/core/path-utils.ts b/src/stubs/core/path-utils.ts new file mode 100644 index 0000000..edc468a --- /dev/null +++ b/src/stubs/core/path-utils.ts @@ -0,0 +1,40 @@ +import * as path from 'path' + +/** + * @github/local-action Unmodified + * + * Converts the given path to the posix form. On Windows, `\\` will be replaced + * with `/`. + * + * @param pth Path to transform + * @return Posix path + */ +export function toPosixPath(pth: string): string { + return pth.replace(/[\\]/g, '/') +} + +/** + * @github/local-action Unmodified + * + * Converts the given path to the win32 form. On Linux, `/` will be replaced + * with `\\`. + * + * @param pth Path to transform + * @return Win32 path + */ +export function toWin32Path(pth: string): string { + return pth.replace(/[/]/g, '\\') +} + +/** + * @github/local-action Unmodified + * + * Converts the given path to a platform-specific path. It does this by + * replacing instances of `/` and `\` with the platform-specific path separator. + * + * @param pth The path to platformize + * @return The platform-specific path + */ +export function toPlatformPath(pth: string): string { + return pth.replace(/[/\\]/g, path.sep) +} diff --git a/src/stubs/core/platform.ts b/src/stubs/core/platform.ts new file mode 100644 index 0000000..719a93b --- /dev/null +++ b/src/stubs/core/platform.ts @@ -0,0 +1,96 @@ +/** + * @github/local-action Unmodified + */ +/* istanbul ignore file */ + +import * as exec from '@actions/exec' +import os from 'os' + +const getWindowsInfo = async (): Promise<{ name: string; version: string }> => { + const { stdout: version } = await exec.getExecOutput( + 'powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', + undefined, + { + silent: true + } + ) + + const { stdout: name } = await exec.getExecOutput( + 'powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"', + undefined, + { + silent: true + } + ) + + return { + name: name.trim(), + version: version.trim() + } +} + +const getMacOsInfo = async (): Promise<{ + name: string + version: string +}> => { + const { stdout } = await exec.getExecOutput('sw_vers', undefined, { + silent: true + }) + + const version = stdout.match(/ProductVersion:\s*(.+)/)?.[1] ?? '' + const name = stdout.match(/ProductName:\s*(.+)/)?.[1] ?? '' + + return { + name, + version + } +} + +const getLinuxInfo = async (): Promise<{ + name: string + version: string +}> => { + const { stdout } = await exec.getExecOutput( + 'lsb_release', + ['-i', '-r', '-s'], + { + silent: true + } + ) + + const [name, version] = stdout.trim().split('\n') + + return { + name, + version + } +} + +export const platform = os.platform() +export const arch = os.arch() +export const isWindows = platform === 'win32' +export const isMacOS = platform === 'darwin' +export const isLinux = platform === 'linux' + +export async function getDetails(): Promise<{ + name: string + platform: string + arch: string + version: string + isWindows: boolean + isMacOS: boolean + isLinux: boolean +}> { + return { + ...(await (isWindows + ? getWindowsInfo() + : isMacOS + ? getMacOsInfo() + : getLinuxInfo())), + platform, + arch, + isWindows, + isMacOS, + isLinux + } +} diff --git a/src/stubs/core/summary.ts b/src/stubs/core/summary.ts new file mode 100644 index 0000000..f93ee97 --- /dev/null +++ b/src/stubs/core/summary.ts @@ -0,0 +1,432 @@ +import fs from 'fs' +import { EOL } from 'os' +import path from 'path' +import { CoreMeta } from './core.js' + +/** + * @github/local-action Unmodified + * + * A row for a summary table. + */ +export type SummaryTableRow = (SummaryTableCell | string)[] + +/** + * @github/local-action Unmodified + */ +export interface SummaryTableCell { + /** + * Cell content + */ + data: string + /** + * Render cell as header + * (optional) default: false + */ + header?: boolean + /** + * Number of columns the cell extends + * (optional) default: '1' + */ + colspan?: string + /** + * Number of rows the cell extends + * (optional) default: '1' + */ + rowspan?: string +} + +/** + * @github/local-action Unmodified + */ +export interface SummaryImageOptions { + /** + * The width of the image in pixels. Must be an integer without a unit. + * (optional) + */ + width?: string + /** + * The height of the image in pixels. Must be an integer without a unit. + * (optional) + */ + height?: string +} + +/** + * @github/local-action Unmodified + */ +export interface SummaryWriteOptions { + /** + * Replace all existing content in summary file with buffer contents + * (optional) default: false + */ + overwrite?: boolean +} + +/** + * A class for creating and writing job step summaries. + */ +export class Summary { + /** Output buffer to write to the summary file. */ + private _buffer: string + + /** The path to the summary file. */ + private _filePath?: string + + /** + * @github/local-action Unmodified + * + * Initialize with an empty buffer. + */ + constructor() { + this._buffer = '' + } + + /** + * @github/local-action Modified + * + * Finds the summary file path from the environment. Rejects if the + * environment variable is not set/empty or the file does not exist. + * + * @returns Step summary file path. + */ + async filePath(): Promise { + // Return the current value, if available. + if (this._filePath) return this._filePath + + // Throw if the path is not set/empty. + if (!CoreMeta.stepSummaryPath) + throw new Error( + 'Unable to find environment variable for $GITHUB_STEP_SUMMARY. Check if your runtime environment supports job summaries.' + ) + + try { + // Resolve the full path to the file. + CoreMeta.stepSummaryPath = path.resolve( + process.cwd(), + CoreMeta.stepSummaryPath + ) + + // If the file does not exist, create it. GitHub Actions runners normally + // create this file automatically. When testing with local-action, we do + // not know if the file already exists. + if (!fs.existsSync(CoreMeta.stepSummaryPath)) + fs.writeFileSync(CoreMeta.stepSummaryPath, '', { encoding: 'utf8' }) + + // Test access to the file (read or write). + fs.accessSync( + CoreMeta.stepSummaryPath, + fs.constants.R_OK | fs.constants.W_OK + ) + } catch { + throw new Error( + `Unable to access summary file: '${CoreMeta.stepSummaryPath}'. Check if the file has correct read/write permissions.` + ) + } + + this._filePath = CoreMeta.stepSummaryPath + return Promise.resolve(this._filePath) + } + + /** + * @github/local-action Unmodified + * + * Wraps content in the provided HTML tag and adds any specified attributes. + * + * @param tag HTML tag to wrap. Example: 'html', 'body', 'div', etc. + * @param content The content to wrap within the tag. + * @param attrs A key-value list of HTML attributes to add. + * @returns Content wrapped in an HTML element. + */ + wrap( + tag: string, + content: string | null, + attrs: { [attribute: string]: string } = {} + ): string { + const htmlAttrs: string = Object.entries(attrs) + .map(([key, value]) => ` ${key}="${value}"`) + .join('') + + return !content + ? `<${tag}${htmlAttrs}>` + : `<${tag}${htmlAttrs}>${content}` + } + + /** + * @github/local-action Modified + * + * Writes the buffer to the summary file and empties the buffer. This can + * append (default) or overwrite the file. + * + * @param options Options for the write operation. + * @returns A promise that resolves to the Summary instance for chaining. + */ + async write( + options: SummaryWriteOptions = { overwrite: false } + ): Promise { + // Set the function to call based on the overwrite setting. + const writeFunc = options.overwrite ? fs.writeFileSync : fs.appendFileSync + + // If the file does not exist, create it. GitHub Actions runners normally + // create this file automatically. When testing with local-action, we do not + // know if the file already exists. + const filePath: string = await this.filePath() + + // Call the write function. + writeFunc(filePath, this._buffer, { encoding: 'utf8' }) + + // Empty the buffer. + return this.emptyBuffer() + } + + /** + * @github/local-action Unmodified + * + * Clears the buffer and summary file. + * + * @returns A promise that resolve to the Summary instance for chaining. + */ + async clear(): Promise { + return this.emptyBuffer().write({ overwrite: true }) + } + + /** + * @github/local-action Unmodified + * + * Returns the current buffer as a string. + * + * @returns Current buffer contents. + */ + stringify(): string { + return this._buffer + } + + /** + * @github/local-action Unmodified + * + * Returns `true` the buffer is empty, `false` otherwise. + * + * @returns Whether the buffer is empty. + */ + isEmptyBuffer(): boolean { + return this._buffer.length === 0 + } + + /** + * @github/local-action Unmodified + * + * Resets the buffer without writing to the summary file. + * + * @returns The Summary instance for chaining. + */ + emptyBuffer(): Summary { + this._buffer = '' + return this + } + + /** + * @github/local-action Unmodified + * + * Adds raw text to the buffer. + * + * @param text The content to add. + * @param addEOL Whether to append `EOL` to the raw text (default: `false`). + * + * @returns The Summary instance for chaining. + */ + addRaw(text: string, addEOL: boolean = false): Summary { + this._buffer += text + return addEOL ? this.addEOL() : this + } + + /** + * @github/local-action Unmodified + * + * Adds the operating system-specific `EOL` marker to the buffer. + * + * @returns The Summary instance for chaining. + */ + addEOL(): Summary { + return this.addRaw(EOL) + } + + /** + * @github/local-action Modified + * + * Adds a code block (\) to the buffer. + * + * @param code Content to render within the code block. + * @param lang Language to use for syntax highlighting. + * @returns Summary instance for chaining. + */ + addCodeBlock(code: string, lang?: string): Summary { + return this.addRaw( + this.wrap('pre', this.wrap('code', code), lang ? { lang } : {}) + ).addEOL() + } + + /** + * @github/local-action Modified + * + * Adds a list (\) element to the buffer. + * + * @param items List of items to render. + * @param ordered Whether the list should be ordered. + * @returns Summary instance for chaining. + */ + addList(items: string[], ordered: boolean = false): Summary { + return this.addRaw( + this.wrap( + ordered ? 'ol' : 'ul', + items.map(item => this.wrap('li', item)).join('') + ) + ).addEOL() + } + + /** + * @github/local-action Modified + * + * Adds a table (\) element to the buffer. + * + * @param rows Table rows to render. + * @returns Summary instance for chaining. + */ + addTable(rows: SummaryTableRow[]): Summary { + return this.addRaw( + this.wrap( + 'table', + // The table body consists of a list of rows, each with a list of cells. + rows + .map(row => { + const cells: string = row + .map(cell => { + // Cell is a string, return as-is. + if (typeof cell === 'string') return this.wrap('td', cell) + + // Cell is a SummaryTableCell, extract the data and attributes. + return this.wrap(cell.header ? 'th' : 'td', cell.data, { + ...(cell.colspan ? { colspan: cell.colspan } : {}), + ...(cell.rowspan ? { rowspan: cell.rowspan } : {}) + }) + }) + .join('') + + return this.wrap('tr', cells) + }) + .join('') + ) + ).addEOL() + } + + /** + * @github/local-action Modified + * + * Adds a details (\) element to the buffer. + * + * @param label Text for the \ element. + * @param content Text for the \ container. + * @returns Summary instance for chaining. + */ + addDetails(label: string, content: string): Summary { + return this.addRaw( + this.wrap('details', this.wrap('summary', label) + content) + ).addEOL() + } + + /** + * @github/local-action Modified + * + * Adds an image (\) element to the buffer. + * + * @param src Path to the image to embed. + * @param alt Text description of the image. + * @param options Additional image attributes. + * @returns Summary instance for chaining. + */ + addImage( + src: string, + alt: string, + options: SummaryImageOptions = {} + ): Summary { + return this.addRaw( + this.wrap('img', null, { + src, + alt, + ...(options.width ? { width: options.width } : {}), + ...(options.height ? { height: options.height } : {}) + }) + ).addEOL() + } + + /** + * @github/local-action Modified + * + * Adds a heading (\) element to the buffer. + * + * @param text Heading text to render. + * @param level Heading level. Defaults to `1`. + * @returns Summary instance for chaining. + */ + addHeading(text: string, level: number | string = 1): Summary { + // If level is a string, attempt to parse it as a number. + const levelAsNum = typeof level === 'string' ? parseInt(level) : level + + // If level is less than 1 or greater than 6, default to `h1`. + const tag = + Number.isNaN(levelAsNum) || levelAsNum < 1 || levelAsNum > 6 + ? 'h1' + : `h${level}` + + const element = this.wrap(tag, text) + return this.addRaw(element).addEOL() + } + + /** + * @github/local-action Modified + * + * Adds a horizontal rule (\) element to the buffer. + * + * @returns Summary instance for chaining. + */ + addSeparator(): Summary { + return this.addRaw(this.wrap('hr', null)).addEOL() + } + + /** + * @github/local-action Modified + * + * Adds a line break (\) to the buffer. + * + * @returns Summary instance for chaining. + */ + addBreak(): Summary { + return this.addRaw(this.wrap('br', null)).addEOL() + } + + /** + * @github/local-action Modified + * + * Adds a block quote \ element to the buffer. + * + * @param text Quote text to render. + * @param cite (Optional) Citation URL. + * @returns Summary instance for chaining. + */ + addQuote(text: string, cite?: string): Summary { + return this.addRaw( + this.wrap('blockquote', text, cite ? { cite } : {}) + ).addEOL() + } + + /** + * @github/local-action Modified + * + * Adds an anchor (\) element to the buffer. + * + * @param text Text content to render. + * @param href Hyperlink to the target. + * @returns Summary instance for chaining. + */ + addLink(text: string, href: string): Summary { + return this.addRaw(this.wrap('a', text, { href })).addEOL() + } +} diff --git a/src/types.ts b/src/types.ts index 68abec9..7d3e831 100644 --- a/src/types.ts +++ b/src/types.ts @@ -137,90 +137,3 @@ export type Runs = { /** Conditions for the `post` script to run */ 'post-if'?: () => boolean } - -/** - * Optional properties that can be sent with annotation commands (notice, error, - * and warning). - */ -export type AnnotationProperties = { - /** A title for the annotation. */ - title?: string - - /** The path of the file for which the annotation should be created. */ - file?: string - - /** The start line for the annotation. */ - startLine?: number - - /** - * The end line for the annotation. Defaults to `startLine` when `startLine` - * is provided. - */ - endLine?: number - - /** - * The start column for the annotation. Cannot be sent when `startLine` and - * `endLine` are different values. - */ - startColumn?: number - - /** - * The end column for the annotation. Cannot be sent when `startLine` and - * `endLine` are different values. Defaults to `startColumn` when - * `startColumn` is provided. - */ - endColumn?: number -} - -/** - * Options for getInput. - */ -export type InputOptions = { - /** - * Optional. Whether the input is required. If required and not present, - * will throw. Defaults to false. - */ - required?: boolean - - /** - * Optional. Whether leading/trailing whitespace will be trimmed for the - * input. Defaults to true. - */ - trimWhitespace?: boolean -} - -/** A table cell from a job step summary. */ -export type SummaryTableCell = { - /** Cell content */ - data: string - - /** Optional. Render cell as header. Defaults to `false`. */ - header?: boolean - - /** Optional. Number of columns the cell extends. Defaults to `1`. */ - colspan?: string - - /** Optional. Number of rows the cell extends. Defaults to '1'. */ - rowspan?: string -} - -/** A row for a summary table. */ -export type SummaryTableRow = (SummaryTableCell | string)[] - -/** The formatting options for an image in a job step summary. */ -export type SummaryImageOptions = { - /** Optional. The width of the image in pixels. */ - width?: string - - /** Optional. The height of the image in pixels. */ - height?: string -} - -/** The options for writing a job step summary. */ -export type SummaryWriteOptions = { - /** - * Optional. Replace all existing content in the summary file with the - * contents of the buffer. Defaults to `false`. - */ - overwrite?: boolean -} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 4705547..f1c1fe0 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -8,6 +8,7 @@ "__fixtures__/stream/", "__fixtures__/core.ts", "__fixtures__/crypto.ts", + "__fixtures__/exec.ts", "__fixtures__/fs.ts", "__fixtures__/tsconfig-paths.ts", "__tests__",