feat: add pathspec_error_handling input (#280)
* feat: add pathspec_error_handling input * fix: show add/rm errors on same line * docs(README): add docs for new input
This commit is contained in:
@@ -54,6 +54,13 @@ Add a step like this to your workflow:
|
|||||||
# Default: 'Commit from GitHub Actions (name of the workflow)'
|
# Default: 'Commit from GitHub Actions (name of the workflow)'
|
||||||
message: 'Your commit message'
|
message: 'Your commit message'
|
||||||
|
|
||||||
|
# The way the action should handle pathspec errors from the add and remove commands. Three options are available:
|
||||||
|
# - ignore -> errors will be logged but the step won't fail
|
||||||
|
# - exitImmediately -> the action will stop right away, and the step will fail
|
||||||
|
# - exitAtEnd -> the action will go on, every pathspec error will be logged at the end, the step will fail.
|
||||||
|
# Default: ignore
|
||||||
|
pathspec_error_handling: ignore
|
||||||
|
|
||||||
# The flag used on the pull strategy. Use NO-PULL to avoid the action pulling at all.
|
# The flag used on the pull strategy. Use NO-PULL to avoid the action pulling at all.
|
||||||
# Default: '--no-rebase'
|
# Default: '--no-rebase'
|
||||||
pull_strategy: 'NO-PULL or --no-rebase or --no-ff or --rebase'
|
pull_strategy: 'NO-PULL or --no-rebase or --no-ff or --rebase'
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ inputs:
|
|||||||
message:
|
message:
|
||||||
description: The message for the commit
|
description: The message for the commit
|
||||||
required: false
|
required: false
|
||||||
|
pathspec_error_handling:
|
||||||
|
description: The way the action should handle pathspec errors from the add and remove commands.
|
||||||
|
required: false
|
||||||
|
default: ignore
|
||||||
pull_strategy:
|
pull_strategy:
|
||||||
description: The flag used on the pull strategy. Use NO-PULL to avoid the action pulling at all.
|
description: The flag used on the pull strategy. Use NO-PULL to avoid the action pulling at all.
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
6
lib/index.js
generated
6
lib/index.js
generated
File diff suppressed because one or more lines are too long
137
src/main.ts
137
src/main.ts
@@ -5,7 +5,7 @@ import YAML from 'js-yaml'
|
|||||||
import {
|
import {
|
||||||
getInput,
|
getInput,
|
||||||
getUserInfo,
|
getUserInfo,
|
||||||
Input,
|
input,
|
||||||
log,
|
log,
|
||||||
matchGitArgs,
|
matchGitArgs,
|
||||||
outputs,
|
outputs,
|
||||||
@@ -15,6 +15,9 @@ import {
|
|||||||
|
|
||||||
const baseDir = path.join(process.cwd(), getInput('cwd') || '')
|
const baseDir = path.join(process.cwd(), getInput('cwd') || '')
|
||||||
const git = simpleGit({ baseDir })
|
const git = simpleGit({ baseDir })
|
||||||
|
|
||||||
|
const exitErrors: Error[] = []
|
||||||
|
|
||||||
core.info(`Running in ${baseDir}`)
|
core.info(`Running in ${baseDir}`)
|
||||||
;(async () => {
|
;(async () => {
|
||||||
await checkInputs().catch(core.setFailed)
|
await checkInputs().catch(core.setFailed)
|
||||||
@@ -22,14 +25,15 @@ core.info(`Running in ${baseDir}`)
|
|||||||
core.startGroup('Internal logs')
|
core.startGroup('Internal logs')
|
||||||
core.info('> Staging files...')
|
core.info('> Staging files...')
|
||||||
|
|
||||||
|
const peh = getInput('pathspec_error_handling')
|
||||||
if (getInput('add')) {
|
if (getInput('add')) {
|
||||||
core.info('> Adding files...')
|
core.info('> Adding files...')
|
||||||
await add()
|
await add(peh == 'ignore' ? 'pathspec' : 'none')
|
||||||
} else core.info('> No files to add.')
|
} else core.info('> No files to add.')
|
||||||
|
|
||||||
if (getInput('remove')) {
|
if (getInput('remove')) {
|
||||||
core.info('> Removing files...')
|
core.info('> Removing files...')
|
||||||
await remove()
|
await remove(peh == 'ignore' ? 'pathspec' : 'none')
|
||||||
} else core.info('> No files to remove.')
|
} else core.info('> No files to remove.')
|
||||||
|
|
||||||
core.info('> Checking for uncommitted changes in the git working tree...')
|
core.info('> Checking for uncommitted changes in the git working tree...')
|
||||||
@@ -71,8 +75,8 @@ core.info(`Running in ${baseDir}`)
|
|||||||
}
|
}
|
||||||
|
|
||||||
core.info('> Re-staging files...')
|
core.info('> Re-staging files...')
|
||||||
if (getInput('add')) await add({ ignoreErrors: true })
|
if (getInput('add')) await add('all')
|
||||||
if (getInput('remove')) await remove({ ignoreErrors: true })
|
if (getInput('remove')) await remove('all')
|
||||||
|
|
||||||
core.info('> Creating commit...')
|
core.info('> Creating commit...')
|
||||||
await git.commit(
|
await git.commit(
|
||||||
@@ -97,7 +101,7 @@ core.info(`Running in ${baseDir}`)
|
|||||||
if (getInput('tag')) {
|
if (getInput('tag')) {
|
||||||
core.info('> Tagging commit...')
|
core.info('> Tagging commit...')
|
||||||
await git
|
await git
|
||||||
.tag(matchGitArgs(getInput('tag')), (err, data?) => {
|
.tag(matchGitArgs(getInput('tag') || ''), (err, data?) => {
|
||||||
if (data) setOutput('tagged', 'true')
|
if (data) setOutput('tagged', 'true')
|
||||||
return log(err, data)
|
return log(err, data)
|
||||||
})
|
})
|
||||||
@@ -159,7 +163,7 @@ core.info(`Running in ${baseDir}`)
|
|||||||
{
|
{
|
||||||
'--delete': null,
|
'--delete': null,
|
||||||
origin: null,
|
origin: null,
|
||||||
[matchGitArgs(getInput('tag')).filter(
|
[matchGitArgs(getInput('tag') || '').filter(
|
||||||
(w) => !w.startsWith('-')
|
(w) => !w.startsWith('-')
|
||||||
)[0]]: null
|
)[0]]: null
|
||||||
},
|
},
|
||||||
@@ -177,6 +181,14 @@ core.info(`Running in ${baseDir}`)
|
|||||||
core.info('> Working tree clean. Nothing to commit.')
|
core.info('> Working tree clean. Nothing to commit.')
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
.then(() => {
|
||||||
|
// Check for exit errors
|
||||||
|
if (exitErrors.length == 1) throw exitErrors[0]
|
||||||
|
else if (exitErrors.length > 1) {
|
||||||
|
exitErrors.forEach((e) => core.error(e))
|
||||||
|
throw 'There have been multiple runtime errors.'
|
||||||
|
}
|
||||||
|
})
|
||||||
.then(logOutputs)
|
.then(logOutputs)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
@@ -185,11 +197,11 @@ core.info(`Running in ${baseDir}`)
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function checkInputs() {
|
async function checkInputs() {
|
||||||
function setInput(input: Input, value: string | undefined) {
|
function setInput(input: input, value: string | undefined) {
|
||||||
if (value) return (process.env[`INPUT_${input.toUpperCase()}`] = value)
|
if (value) return (process.env[`INPUT_${input.toUpperCase()}`] = value)
|
||||||
else return delete process.env[`INPUT_${input.toUpperCase()}`]
|
else return delete process.env[`INPUT_${input.toUpperCase()}`]
|
||||||
}
|
}
|
||||||
function setDefault(input: Input, value: string) {
|
function setDefault(input: input, value: string) {
|
||||||
if (!getInput(input)) setInput(input, value)
|
if (!getInput(input)) setInput(input, value)
|
||||||
return getInput(input)
|
return getInput(input)
|
||||||
}
|
}
|
||||||
@@ -219,7 +231,7 @@ async function checkInputs() {
|
|||||||
else core.setFailed('Add input: array length < 1')
|
else core.setFailed('Add input: array length < 1')
|
||||||
}
|
}
|
||||||
if (getInput('remove')) {
|
if (getInput('remove')) {
|
||||||
const parsed = parseInputArray(getInput('remove'))
|
const parsed = parseInputArray(getInput('remove') || '')
|
||||||
if (parsed.length == 1)
|
if (parsed.length == 1)
|
||||||
core.info(
|
core.info(
|
||||||
'Remove input parsed as single string, running 1 git rm command.'
|
'Remove input parsed as single string, running 1 git rm command.'
|
||||||
@@ -327,25 +339,16 @@ async function checkInputs() {
|
|||||||
core.info(`> Running for a PR, the action will use '${branch}' as ref.`)
|
core.info(`> Running for a PR, the action will use '${branch}' as ref.`)
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region signoff
|
// #region pathspec_error_handling
|
||||||
if (getInput('signoff')) {
|
const peh_valid = ['ignore', 'exitImmediately', 'exitAtEnd']
|
||||||
const parsed = getInput('signoff', true)
|
if (!peh_valid.includes(getInput('pathspec_error_handling')))
|
||||||
|
throw new Error(
|
||||||
if (parsed === undefined)
|
`"${getInput(
|
||||||
throw new Error(
|
'pathspec_error_handling'
|
||||||
`"${getInput(
|
)}" is not a valid value for the 'pathspec_error_handling' input. Valid values are: ${peh_valid.join(
|
||||||
'signoff'
|
', '
|
||||||
)}" is not a valid value for the 'signoff' input: only "true" and "false" are allowed.`
|
)}`
|
||||||
)
|
|
||||||
|
|
||||||
if (!parsed) setInput('signoff', undefined)
|
|
||||||
|
|
||||||
core.debug(
|
|
||||||
`Current signoff option: ${getInput('signoff')} (${typeof getInput(
|
|
||||||
'signoff'
|
|
||||||
)})`
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region pull_strategy
|
// #region pull_strategy
|
||||||
@@ -368,6 +371,27 @@ async function checkInputs() {
|
|||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
|
// #region signoff
|
||||||
|
if (getInput('signoff')) {
|
||||||
|
const parsed = getInput('signoff', true)
|
||||||
|
|
||||||
|
if (parsed === undefined)
|
||||||
|
throw new Error(
|
||||||
|
`"${getInput(
|
||||||
|
'signoff'
|
||||||
|
)}" is not a valid value for the 'signoff' input: only "true" and "false" are allowed.`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!parsed) setInput('signoff', undefined)
|
||||||
|
|
||||||
|
core.debug(
|
||||||
|
`Current signoff option: ${getInput('signoff')} (${typeof getInput(
|
||||||
|
'signoff'
|
||||||
|
)})`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
// #region github_token
|
// #region github_token
|
||||||
if (!getInput('github_token'))
|
if (!getInput('github_token'))
|
||||||
core.warning(
|
core.warning(
|
||||||
@@ -376,9 +400,9 @@ async function checkInputs() {
|
|||||||
// #endregion
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
async function add({ logWarning = true, ignoreErrors = false } = {}): Promise<
|
async function add(
|
||||||
(void | Response<void>)[]
|
ignoreErrors: 'all' | 'pathspec' | 'none' = 'none'
|
||||||
> {
|
): Promise<(void | Response<void>)[]> {
|
||||||
const input = getInput('add')
|
const input = getInput('add')
|
||||||
if (!input) return []
|
if (!input) return []
|
||||||
|
|
||||||
@@ -391,19 +415,26 @@ async function add({ logWarning = true, ignoreErrors = false } = {}): Promise<
|
|||||||
// If any of them fails, the whole function will return a Promise rejection
|
// If any of them fails, the whole function will return a Promise rejection
|
||||||
await git
|
await git
|
||||||
.add(matchGitArgs(args), (err: any, data?: any) =>
|
.add(matchGitArgs(args), (err: any, data?: any) =>
|
||||||
log(ignoreErrors ? null : err, data)
|
log(ignoreErrors == 'all' ? null : err, data)
|
||||||
)
|
)
|
||||||
.catch((e: Error) => {
|
.catch((e: Error) => {
|
||||||
if (ignoreErrors) return
|
// if I should ignore every error, return
|
||||||
|
if (ignoreErrors == 'all') return
|
||||||
|
|
||||||
|
// if it's a pathspec error...
|
||||||
if (
|
if (
|
||||||
e.message.includes('fatal: pathspec') &&
|
e.message.includes('fatal: pathspec') &&
|
||||||
e.message.includes('did not match any files') &&
|
e.message.includes('did not match any files')
|
||||||
logWarning
|
) {
|
||||||
)
|
if (ignoreErrors == 'pathspec') return
|
||||||
core.warning(
|
|
||||||
`Add command did not match any file:\n git add ${args}`
|
const peh = getInput('pathspec_error_handling'),
|
||||||
)
|
err = new Error(
|
||||||
else throw e
|
`Add command did not match any file: git add ${args}`
|
||||||
|
)
|
||||||
|
if (peh == 'exitImmediately') throw err
|
||||||
|
if (peh == 'exitAtEnd') exitErrors.push(err)
|
||||||
|
} else throw e
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -411,10 +442,9 @@ async function add({ logWarning = true, ignoreErrors = false } = {}): Promise<
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove({
|
async function remove(
|
||||||
logWarning = true,
|
ignoreErrors: 'all' | 'pathspec' | 'none' = 'none'
|
||||||
ignoreErrors = false
|
): Promise<(void | Response<void>)[]> {
|
||||||
} = {}): Promise<(void | Response<void>)[]> {
|
|
||||||
const input = getInput('remove')
|
const input = getInput('remove')
|
||||||
if (!input) return []
|
if (!input) return []
|
||||||
|
|
||||||
@@ -427,19 +457,26 @@ async function remove({
|
|||||||
// If any of them fails, the whole function will return a Promise rejection
|
// If any of them fails, the whole function will return a Promise rejection
|
||||||
await git
|
await git
|
||||||
.rm(matchGitArgs(args), (e: any, d?: any) =>
|
.rm(matchGitArgs(args), (e: any, d?: any) =>
|
||||||
log(ignoreErrors ? null : e, d)
|
log(ignoreErrors == 'all' ? null : e, d)
|
||||||
)
|
)
|
||||||
.catch((e: Error) => {
|
.catch((e: Error) => {
|
||||||
if (ignoreErrors) return
|
// if I should ignore every error, return
|
||||||
|
if (ignoreErrors == 'all') return
|
||||||
|
|
||||||
|
// if it's a pathspec error...
|
||||||
if (
|
if (
|
||||||
e.message.includes('fatal: pathspec') &&
|
e.message.includes('fatal: pathspec') &&
|
||||||
e.message.includes('did not match any files')
|
e.message.includes('did not match any files')
|
||||||
)
|
) {
|
||||||
logWarning &&
|
if (ignoreErrors == 'pathspec') return
|
||||||
core.warning(
|
|
||||||
|
const peh = getInput('pathspec_error_handling'),
|
||||||
|
err = new Error(
|
||||||
`Remove command did not match any file:\n git rm ${args}`
|
`Remove command did not match any file:\n git rm ${args}`
|
||||||
)
|
)
|
||||||
else throw e
|
if (peh == 'exitImmediately') throw err
|
||||||
|
if (peh == 'exitAtEnd') exitErrors.push(err)
|
||||||
|
} else throw e
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
78
src/util.ts
78
src/util.ts
@@ -3,27 +3,44 @@ import * as core from '@actions/core'
|
|||||||
import { Toolkit } from 'actions-toolkit'
|
import { Toolkit } from 'actions-toolkit'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
export type Input =
|
interface InputTypes {
|
||||||
| 'add'
|
add: string
|
||||||
| 'author_name'
|
author_name: string
|
||||||
| 'author_email'
|
author_email: string
|
||||||
| 'branch'
|
branch: string
|
||||||
| 'committer_name'
|
committer_name: string
|
||||||
| 'committer_email'
|
committer_email: string
|
||||||
| 'cwd'
|
cwd: string
|
||||||
| 'default_author'
|
default_author: 'github_actor' | 'user_info' | 'github_actions'
|
||||||
| 'message'
|
message: string
|
||||||
| 'pull_strategy'
|
pathspec_error_handling: 'ignore' | 'exitImmediately' | 'exitAtEnd'
|
||||||
| 'push'
|
pull_strategy: string
|
||||||
| 'remove'
|
push: string
|
||||||
| 'signoff'
|
remove: string | undefined
|
||||||
| 'tag'
|
signoff: undefined
|
||||||
| 'github_token'
|
tag: string | undefined
|
||||||
|
|
||||||
export type Output = 'committed' | 'commit_sha' | 'pushed' | 'tagged'
|
github_token: string | undefined
|
||||||
|
}
|
||||||
|
export type input = keyof InputTypes
|
||||||
|
|
||||||
|
interface OutputTypes {
|
||||||
|
committed: 'true' | 'false'
|
||||||
|
commit_sha: string | undefined
|
||||||
|
pushed: 'true' | 'false'
|
||||||
|
tagged: 'true' | 'false'
|
||||||
|
}
|
||||||
|
export type output = keyof OutputTypes
|
||||||
|
|
||||||
|
export const outputs: OutputTypes = {
|
||||||
|
committed: 'false',
|
||||||
|
commit_sha: undefined,
|
||||||
|
pushed: 'false',
|
||||||
|
tagged: 'false'
|
||||||
|
}
|
||||||
|
|
||||||
type RecordOf<T extends string> = Record<T, string | undefined>
|
type RecordOf<T extends string> = Record<T, string | undefined>
|
||||||
export const tools = new Toolkit<RecordOf<Input>, RecordOf<Output>>({
|
export const tools = new Toolkit<RecordOf<input>, RecordOf<output>>({
|
||||||
secrets: [
|
secrets: [
|
||||||
'GITHUB_EVENT_PATH',
|
'GITHUB_EVENT_PATH',
|
||||||
'GITHUB_EVENT_NAME',
|
'GITHUB_EVENT_NAME',
|
||||||
@@ -31,18 +48,19 @@ export const tools = new Toolkit<RecordOf<Input>, RecordOf<Output>>({
|
|||||||
'GITHUB_ACTOR'
|
'GITHUB_ACTOR'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export const outputs: Record<Output, any> = {
|
|
||||||
committed: 'false',
|
|
||||||
commit_sha: undefined,
|
|
||||||
pushed: 'false',
|
|
||||||
tagged: 'false'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInput(name: Input, bool: true): boolean
|
export function getInput<T extends input>(name: T, parseAsBool: true): boolean
|
||||||
export function getInput(name: Input, bool?: false): string
|
export function getInput<T extends input>(
|
||||||
export function getInput(name: Input, bool = false) {
|
name: T,
|
||||||
if (bool) return core.getBooleanInput(name)
|
parseAsBool?: false
|
||||||
return tools.inputs[name] || ''
|
): InputTypes[T]
|
||||||
|
export function getInput<T extends input>(
|
||||||
|
name: T,
|
||||||
|
parseAsBool = false
|
||||||
|
): InputTypes[T] | boolean {
|
||||||
|
if (parseAsBool) return core.getBooleanInput(name)
|
||||||
|
// @ts-expect-error
|
||||||
|
return core.getInput(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserInfo(username?: string) {
|
export async function getUserInfo(username?: string) {
|
||||||
@@ -113,7 +131,7 @@ export function readJSON(filePath: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setOutput<T extends Output>(name: T, value: typeof outputs[T]) {
|
export function setOutput<T extends output>(name: T, value: OutputTypes[T]) {
|
||||||
core.debug(`Setting output: ${name}=${value}`)
|
core.debug(`Setting output: ${name}=${value}`)
|
||||||
outputs[name] = value
|
outputs[name] = value
|
||||||
core.setOutput(name, value)
|
core.setOutput(name, value)
|
||||||
|
|||||||
Reference in New Issue
Block a user