@modernpoacher/deps
Version:
Update NPM package dependencies from the command line
577 lines (481 loc) • 12.6 kB
JavaScript
import debug from 'debug'
import stripAnsi from 'strip-ansi'
import {
normalize
} from 'node:path'
import {
exec
} from 'node:child_process'
import {
BIN,
VERSION,
PLATFORM
} from '#deps/src/common/env'
import {
getOptions
} from '#deps/src/common/options'
export const DIRECTORY = '.'
const BRANCH = 'master'
const ADD = 'package.json package-lock.json'
export const MESSAGE = PLATFORM === 'win32'
? 'Updated `package.json` &/ `package-lock.json`'
: 'Updated \\`package.json\\` &/ \\`package-lock.json\\`'
export const AUTHOR = 'Modern Poacher Limited <modernpoacher@modernpoacher.com>'
const CAT_GIT_REFS_REMOTES_ORIGIN_HEAD = `
DIR=$(echo "./.git/refs/remotes/origin/HEAD'" | sed -e "s/\\//\\\\\\/g" -e "s/://" | cat 2> /dev/null)
[[ $DIR =~ "[-0-9a-zA-Z]*$" ]]
echo "\${BASH_REMATCH[0]}"
`
const GIT_PULL = `
source "${BIN}/common.sh"
source_home "${BIN}"
git pull
`
const GIT_PUSH = `
source "${BIN}/common.sh"
source_home "${BIN}"
git push
`
const GIT_PUSH_TAGS = `
source "${BIN}/common.sh"
source_home "${BIN}"
git push --tags
`
const log = debug('@modernpoacher/deps')
log(`\`common/git\` (${VERSION} - ${PLATFORM}) is awake`)
/**
* @param {string} v
* @returns {string}
*/
const trim = (v) => v.split(String.fromCharCode(10)).map((v) => v.trimEnd()).join(String.fromCharCode(10)).trim()
/**
* @param {string} v
* @returns {boolean}
*/
function filter (v) {
return Boolean(stripAnsi(v).trim())
}
/**
* @param {string} directory
* @returns {(s: string) => boolean}
*/
function getIsDirectory (directory) {
return function isDirectory (s) {
return directory === s
}
}
/**
* @param {string} v
* @returns {boolean}
*/
function isFatal (v) {
return v.toLowerCase().startsWith('fatal: not a git repository')
}
/**
* @param {string} key
* @returns {(value: string) => void}
*/
export function use (key) {
const log = debug(`@modernpoacher/deps:${key}`)
/**
* @param {string} v
* @returns {void}
*/
function write (v) {
log(v.trimEnd())
}
return function use (value) {
value.split(String.fromCharCode(10))
.filter(filter)
.forEach(write)
}
}
/**
* @param {string} key
* @param {string} directory
* @returns {(value: string) => void}
*/
export function out (key, directory) {
const log = debug(`@modernpoacher/deps:${key}`)
const isDirectory = getIsDirectory(directory)
/**
* @param {string} v
* @returns {boolean}
*/
function filterOut (v) {
const s = v.trim()
return (
!isDirectory(s)
)
}
/**
* @param {string} v
* @returns {void}
*/
function write (v) {
if (v.trim()) log(v.trimEnd())
}
return function out (value) {
value.split(String.fromCharCode(10))
.filter(filter).filter(filterOut)
.forEach(write)
}
}
/**
* @param {string} key
* @param {string} directory
* @returns {(value: string) => void}
*/
export function err (key, directory) {
const log = debug(`@modernpoacher/deps:${key}`)
const isDirectory = getIsDirectory(directory)
/**
* @param {string} v
* @returns {boolean}
*/
function filterErr (v) {
const s = v.trim()
return (
!isDirectory(s) && !isFatal(s)
)
}
/**
* @param {string} v
* @returns {void}
*/
function write (v) {
if (v.trim()) log(v.trimEnd())
}
return function err (value) {
value.split(String.fromCharCode(10))
.filter(filter).filter(filterErr)
.forEach(write)
}
}
/**
* @function getErrorCode
*
* Get an error code from an error
*
* @param {{code?: number, message?: string}} e
* @returns {number}
*/
const getErrorCode = ({ code = 0 } = {}) => code
/**
* @function getErrorMessage
*
* Get an error message from an error
*
* @param {{code?: number, message?: string}} e
* @returns {string}
*/
const getErrorMessage = ({ message = '' } = {}) => message
/**
* @function isCommandError
*
* Determine whether an error is a safe non-zero exit from a Git command
*
* @param {{code?: number, message?: string}} e
* @returns {boolean}
*/
function isCommandError (e) {
log('isCommandError')
return (
getErrorCode(e) === 1 &&
getErrorMessage(e).toLowerCase().startsWith('command failed:')
)
}
/**
* @function catGitRefsRemotesOriginHead
*
* Get the default branch from `.git/refs/remotes/origin/HEAD`
*
* @param {string} d - A directory configured for Git
* @returns {Promise<string>}
*/
export function catGitRefsRemotesOriginHead (d = DIRECTORY) {
log('catGitRefsRemotesOriginHead')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = CAT_GIT_REFS_REMOTES_ORIGIN_HEAD.trim()
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e) ? resolve(trim(v)) : reject(e)
})
if (stdout) stdout.on('data', out('cat-git-refs-remotes-origin-head', directory))
if (stderr) stderr.on('data', err('cat-git-refs-remotes-origin-head', directory))
})
)
}
/**
* @function awkGitRemoteShowOriginHead
*
* Get the remote default branch
*
* @param {string} d - A directory configured for Git
* @returns {Promise<string>}
*/
export function awkGitRemoteShowOriginHead (d = DIRECTORY) {
log('awkGitRemoteShowOriginHead')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = 'git remote show origin | awk \'/HEAD branch/ {print $NF}\''
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e) ? resolve(trim(v)) : reject(e)
})
if (stdout) stdout.on('data', out('git-remote-show-origin-head', directory))
if (stderr) stderr.on('data', err('git-remote-show-origin-head', directory))
})
)
}
/**
* @function gitRevParseShowTopLevel
*
* Determine whether a directory is configured for Git
*
* @param {string} d - A directory
* @returns {Promise<string>}
*/
export function gitRevParseShowTopLevel (d = DIRECTORY) {
log('gitRevParseShowTopLevel')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = 'git rev-parse --show-toplevel'
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e) ? resolve(trim(v)) : reject(e)
})
if (stdout) stdout.on('data', out('git-rev-parse-show-toplevel', directory))
if (stderr) stderr.on('data', err('git-rev-parse-show-toplevel', directory))
})
)
}
/**
* @function gitRevParseAbbrevRefHead
*
* Determine which branch is HEAD
*
* @param {string} d - A directory
* @returns {Promise<string>}
*/
export function gitRevParseAbbrevRefHead (d = DIRECTORY) {
log('gitRevParseAbbrevRefHead')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = 'git rev-parse --abbrev-ref HEAD'
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e) ? resolve(trim(v)) : reject(e)
})
if (stdout) stdout.on('data', out('git-rev-parse-abbrev-ref-head', directory))
if (stderr) stderr.on('data', err('git-rev-parse-abbrev-ref-head', directory))
})
)
}
/**
* @function gitCheckout
*
* Switch to the default value if none is supplied
*
* @param {string} d - A directory configured for Git
* @param {string} branch - The Git branch
* @returns {Promise<string>}
*/
export function gitCheckout (d = DIRECTORY, branch = BRANCH) {
log('gitCheckout')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = `git checkout ${branch}`
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e)
? resolve(v)
: isCommandError(e)
? resolve(v)
: reject(e)
})
const log = use('git-checkout')
if (stdout) stdout.on('data', log)
if (stderr) stderr.on('data', log)
})
)
}
/**
* @function gitPull
*
* Pull changes from the Git remote
*
* @param {string} d - A directory configured for Git
* @returns {Promise<string>}
*/
export function gitPull (d = DIRECTORY) {
log('gitPull')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = GIT_PULL.trim()
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e)
? resolve(v)
: isCommandError(e)
? resolve(v)
: reject(e)
})
const log = use('git-pull')
if (stdout) stdout.on('data', log)
if (stderr) stderr.on('data', log)
})
)
}
/**
* @function gitPush
*
* Push changes to the Git remote
*
* @param {string} d - A directory configured for Git
* @returns {Promise<string>}
*/
export function gitPush (d = DIRECTORY) {
log('gitPush')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = GIT_PUSH.trim()
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e)
? resolve(v)
: isCommandError(e)
? resolve(v)
: reject(e)
})
const log = use('git-push')
if (stdout) stdout.on('data', log)
if (stderr) stderr.on('data', log)
})
)
}
/**
* @function gitPushTags
*
* Push tags changes to the Git remote
*
* @param {string} d - A directory configured for Git
* @returns {Promise<string>}
*/
export function gitPushTags (d = DIRECTORY) {
log('gitPushTags')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = GIT_PUSH_TAGS.trim()
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e)
? resolve(v)
: isCommandError(e)
? resolve(v)
: reject(e)
})
const log = use('git-push-tags')
if (stdout) stdout.on('data', log)
if (stderr) stderr.on('data', log)
})
)
}
/**
* @function gitAdd
*
* Add changes to Git with a default value if none is supplied
*
* @param {string} d - A directory configured for Git
* @param {string} add - An add parameter
* @returns {Promise<string>}
*/
export function gitAdd (d = DIRECTORY, add = ADD) {
log('gitAdd')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = `git add ${add}`
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e)
? resolve(v)
: isCommandError(e)
? resolve(v)
: reject(e)
})
const log = use('git-add')
if (stdout) stdout.on('data', log)
if (stderr) stderr.on('data', log)
})
)
}
/**
* @function gitCommit
*
* Commit changes to Git with a default value if none is supplied
*
* @param {string} d - A directory configured for Git
* @param {string} message - A commit message
* @param {string | { name: string; email: string }} author - The commit author
* @returns {Promise<string>}
*/
export function gitCommit (d = DIRECTORY, message = MESSAGE, author = AUTHOR) {
log('gitCommit')
const directory = normalize(d.trim())
return (
new Promise((resolve, reject) => {
const commands = `git commit -m "${message}" --author "${author}"`
const options = getOptions(directory)
const {
stdout,
stderr
} = exec(commands, options, (e = null, v = '') => {
return (!e)
? resolve(v)
: isCommandError(e)
? resolve(v)
: reject(e)
})
const log = use('git-commit')
if (stdout) stdout.on('data', log)
if (stderr) stderr.on('data', log)
})
)
}