mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 23:21:30 -07:00
Initial commit
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
import { Command } from '@commander-js/extra-typings'
|
||||
import { validateArg } from '@jahands/cli-tools/args'
|
||||
import * as esbuild from 'esbuild'
|
||||
import pMap from 'p-map'
|
||||
import { match } from 'ts-pattern'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { TSHelpers } from '../tsconfig'
|
||||
|
||||
import type { CompilerOptions as TSCompilerOptions } from 'typescript'
|
||||
|
||||
export const buildCmd = new Command('build').description('Scripts to build things')
|
||||
|
||||
type Format = z.infer<typeof Format>
|
||||
const Format = z.enum(['esm', 'cjs'])
|
||||
|
||||
buildCmd
|
||||
.command('tsc')
|
||||
.description('Build a library with tsc')
|
||||
.argument('<entrypoint...>', 'Entrypoint(s) for the program')
|
||||
.option('-r, --root-dir <string>', 'Root dir for tsc to use (overrides tsconfig.json)')
|
||||
.action(async (entrypoints, { rootDir }) => {
|
||||
const tsHelpers = await new TSHelpers().init()
|
||||
await $`rm -rf ./dist`
|
||||
|
||||
const tsOptions: TSCompilerOptions = {
|
||||
...tsHelpers.getTSConfig(),
|
||||
...(rootDir !== undefined && { rootDir }),
|
||||
}
|
||||
|
||||
tsHelpers.ts.createProgram(entrypoints, tsOptions).emit()
|
||||
})
|
||||
|
||||
buildCmd
|
||||
.command('bundle-lib')
|
||||
.alias('lib')
|
||||
.description('Bundle library with esbuild')
|
||||
|
||||
.argument('<entrypoints...>', 'Entrypoint(s) of the app. e.g. src/index.ts')
|
||||
.option('-d, --root-dir <string>', 'Root directory to look for entrypoints')
|
||||
.option('-f, --format <format...>', 'Formats to use (options: esm, cjs)', ['esm'])
|
||||
.option('--no-minify', `Don't minify output`)
|
||||
.option(
|
||||
'--sourcemap <string>',
|
||||
`Include sourcemaps in the output. (options: both, linked, inline, external, true, false)`,
|
||||
validateArg(z.union([z.enum(['both', 'linked', 'inline', 'external']), z.coerce.boolean()])),
|
||||
'both'
|
||||
)
|
||||
.option(
|
||||
'--platform <string>',
|
||||
'Optional platform to target. (options: cloudflare_workers,browser, node, neutral)',
|
||||
validateArg(z.enum(['cloudflare_workers', 'browser', 'node', 'neutral'])),
|
||||
'cloudflare_workers'
|
||||
)
|
||||
.option('--no-types', `Don't include .d.ts types in output (usually not recommended)`)
|
||||
|
||||
.action(
|
||||
async (entryPoints, { format: moduleFormats, platform, rootDir, minify, sourcemap, types }) => {
|
||||
entryPoints = z
|
||||
.string()
|
||||
.array()
|
||||
.min(1)
|
||||
.decode(entryPoints)
|
||||
.map((d) => path.join(rootDir ?? '.', d))
|
||||
|
||||
const formats = Format.array().parse(moduleFormats)
|
||||
|
||||
await fs.rm('./dist/', { force: true, recursive: true })
|
||||
|
||||
const maybeOutputTypes = types
|
||||
? $({
|
||||
stdio: 'inherit',
|
||||
})`runx build bundle-lib-build-types ${entryPoints}`
|
||||
: undefined
|
||||
|
||||
await Promise.all([
|
||||
maybeOutputTypes,
|
||||
...formats.map(async (outFormat) => {
|
||||
type Config = {
|
||||
format: Format
|
||||
outExt: string
|
||||
}
|
||||
|
||||
const { format, outExt } = match<'esm' | 'cjs', Config>(outFormat)
|
||||
.with('esm', () => ({
|
||||
format: 'esm',
|
||||
outExt: '.mjs',
|
||||
}))
|
||||
.with('cjs', () => ({
|
||||
format: 'cjs',
|
||||
outExt: '.cjs',
|
||||
}))
|
||||
.exhaustive()
|
||||
|
||||
const external: string[] = []
|
||||
if (platform === 'cloudflare_workers') {
|
||||
external.push('node:events', 'node:async_hooks', 'node:buffer', 'cloudflare:test')
|
||||
}
|
||||
|
||||
const opts: esbuild.BuildOptions = {
|
||||
entryPoints,
|
||||
outdir: './dist/',
|
||||
logLevel: 'warning',
|
||||
outExtension: {
|
||||
'.js': outExt,
|
||||
},
|
||||
target: 'es2022',
|
||||
bundle: true,
|
||||
minify,
|
||||
format,
|
||||
sourcemap,
|
||||
treeShaking: true,
|
||||
external,
|
||||
}
|
||||
|
||||
if (platform !== 'cloudflare_workers') {
|
||||
opts.platform = platform
|
||||
}
|
||||
|
||||
await esbuild.build(opts)
|
||||
}),
|
||||
])
|
||||
}
|
||||
)
|
||||
|
||||
buildCmd
|
||||
.command('bundle-lib-build-types')
|
||||
.description('Separate command to build types (so that we can run them concurrently)')
|
||||
.argument('<entrypoints...>', 'Entrypoint(s) of the app. e.g. src/index.ts')
|
||||
.action(async (entryPoints) => {
|
||||
const tsHelpers = await new TSHelpers().init()
|
||||
const { ts } = tsHelpers
|
||||
z.string().array().min(1).decode(entryPoints)
|
||||
|
||||
const tsCompOpts = {
|
||||
...tsHelpers.getTSConfig(),
|
||||
declaration: true,
|
||||
declarationMap: true,
|
||||
emitDeclarationOnly: true,
|
||||
noEmit: false,
|
||||
outDir: './dist/',
|
||||
} satisfies TSCompilerOptions
|
||||
|
||||
const program = ts.createProgram(entryPoints, tsCompOpts)
|
||||
program.emit()
|
||||
})
|
||||
|
||||
buildCmd
|
||||
.command('bun')
|
||||
.description('Bundle with Bun')
|
||||
|
||||
.argument('<entrypoints...>', 'Entrypoint(s) of the app. e.g. src/index.ts')
|
||||
.option('-f, --format <format...>', 'Formats to use (options: esm, cjs)', ['esm'])
|
||||
.option('--no-minify', `Don't minify output`)
|
||||
.option('--no-sourcemap', `Don't include sourcemaps`)
|
||||
|
||||
.action(async (entryPoints, { format, minify, sourcemap }) => {
|
||||
await fs.rm('./dist/', { force: true, recursive: true })
|
||||
|
||||
const formats = await z
|
||||
.array(Format)
|
||||
.parseAsync(format)
|
||||
.catch((e) => {
|
||||
throw new Error(`Invalid format: ${z.prettifyError(e)}`)
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
$({
|
||||
stdio: 'inherit',
|
||||
})`runx build bundle-lib-build-types ${entryPoints}`,
|
||||
|
||||
...formats.map(async (fmt) => {
|
||||
const distDir = `./dist/${fmt}`
|
||||
|
||||
await Bun.build({
|
||||
entrypoints: entryPoints,
|
||||
outdir: distDir,
|
||||
target: 'node',
|
||||
minify,
|
||||
format: fmt,
|
||||
})
|
||||
|
||||
const outExt = match(fmt)
|
||||
.with('esm', () => '.mjs')
|
||||
.with('cjs', () => '.cjs')
|
||||
.exhaustive()
|
||||
|
||||
// change output files to mjs/cjs
|
||||
await pMap(await glob(`${distDir}/**/*.js`), async (file) => {
|
||||
await fs.rename(file, file.replace(/\.js$/, outExt))
|
||||
})
|
||||
}),
|
||||
])
|
||||
|
||||
const cleanupSourcemaps = async () => {
|
||||
if (sourcemap === false) {
|
||||
const files = await glob('dist/**/*.map')
|
||||
await Promise.all(files.map((file) => fs.rm(file)))
|
||||
}
|
||||
}
|
||||
|
||||
// executables don't need declaration files
|
||||
const cleanupBin = async () => {
|
||||
const files = await glob('dist/bin/*.d.ts')
|
||||
await Promise.all(files.map((file) => fs.rm(file)))
|
||||
}
|
||||
|
||||
await Promise.all([cleanupSourcemaps(), cleanupBin()])
|
||||
|
||||
// check if bin is empty
|
||||
const files = await glob('dist/bin/*')
|
||||
if (files.length === 0) {
|
||||
await fs.rm('dist/bin', { recursive: true })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,163 @@
|
||||
import { Command } from '@commander-js/extra-typings'
|
||||
import Table from 'cli-table3'
|
||||
|
||||
import { getRepoRoot } from '../path'
|
||||
import { getOutcome, SHFMT_SKIPPED_EXIT_CODE } from '../proc'
|
||||
|
||||
export const checkCmd = new Command('check')
|
||||
.description(
|
||||
'Check for issues with deps/lint/types/format. If no options are provided, all checks are run.'
|
||||
)
|
||||
|
||||
.option('-r, --root', 'Run checks from root of repo. Defaults to cwd', false)
|
||||
.option('-d, --deps', 'Check for dependency issues with Syncpack')
|
||||
.option('-l, --lint', 'Check for oxlint issues')
|
||||
.option('-t, --types', 'Check for TypeScript issues')
|
||||
.option(
|
||||
'-f, --format',
|
||||
'Check for formatting issues with prettier. Also checks shell scripts if shfmt and rg (ripgrep) are available'
|
||||
)
|
||||
|
||||
.option('--continue', 'Use --continue when executing turbo commands', false)
|
||||
|
||||
.action(async ({ root, deps, lint, types, format, continue: useContinue }) => {
|
||||
const repoRoot = getRepoRoot()
|
||||
if (root) {
|
||||
cd(repoRoot)
|
||||
}
|
||||
|
||||
// Run all if none are selected
|
||||
if (!deps && !lint && !types && !format) {
|
||||
deps = true
|
||||
lint = true
|
||||
types = true
|
||||
format = true
|
||||
}
|
||||
|
||||
const cwd = process.cwd()
|
||||
const runFromRoot = cwd === repoRoot
|
||||
const cwdName = path.basename(cwd)
|
||||
|
||||
const turboFlags: string[] = []
|
||||
|
||||
if (useContinue) {
|
||||
turboFlags.push('--continue')
|
||||
}
|
||||
|
||||
const checks = {
|
||||
deps: ['syncpack', 'lint'],
|
||||
// oxlint can be run from anywhere and it'll automatically only lint the current dir and children
|
||||
lint: ['run-oxlint'],
|
||||
types: ['turbo', ...turboFlags, 'check:types'],
|
||||
format: ['prettier', '.', '--cache', '--check', '--log-level=warn'],
|
||||
formatShell: ['runx', 'shfmt', 'check', '--skip-if-unavailable'],
|
||||
workersTypes: ['turbo', ...turboFlags, 'check:workers-types'],
|
||||
} as const satisfies { [key: string]: string[] }
|
||||
|
||||
type TableRow = [string, string, string, string]
|
||||
const table = new Table({
|
||||
head: [
|
||||
chalk.whiteBright('Name'),
|
||||
chalk.whiteBright('Command'),
|
||||
chalk.whiteBright('Outcome'),
|
||||
chalk.whiteBright('Ran From'),
|
||||
] satisfies TableRow,
|
||||
})
|
||||
|
||||
$.stdio = 'inherit'
|
||||
$.verbose = true
|
||||
$.nothrow = true
|
||||
|
||||
let didErr = false
|
||||
function getAndCheckOutcome({
|
||||
exitCode,
|
||||
skippedCode,
|
||||
}: {
|
||||
exitCode: number | null
|
||||
skippedCode?: number
|
||||
}): string {
|
||||
if (exitCode !== 0 && exitCode !== skippedCode) {
|
||||
didErr = true
|
||||
}
|
||||
return getOutcome({ exitCode, skippedCode })
|
||||
}
|
||||
|
||||
if (deps) {
|
||||
const exitCode = await $({
|
||||
cwd: repoRoot, // Must be run from root
|
||||
})`${checks.deps}`.exitCode
|
||||
table.push([
|
||||
'deps',
|
||||
checks.deps.join(' '),
|
||||
getAndCheckOutcome({ exitCode }),
|
||||
'Root',
|
||||
] satisfies TableRow)
|
||||
}
|
||||
|
||||
if (lint) {
|
||||
const exitCode = await $`${checks.lint}`.exitCode
|
||||
table.push([
|
||||
'lint',
|
||||
checks.lint.join(' '),
|
||||
getAndCheckOutcome({ exitCode }),
|
||||
runFromRoot ? 'Root' : `cwd (${cwdName})`,
|
||||
] satisfies TableRow)
|
||||
}
|
||||
|
||||
if (types) {
|
||||
const exitCode = await $`${checks.types}`.exitCode
|
||||
table.push([
|
||||
'types',
|
||||
checks.types.join(' '),
|
||||
getAndCheckOutcome({ exitCode }),
|
||||
runFromRoot ? 'Root' : `cwd (${cwdName})`,
|
||||
] satisfies TableRow)
|
||||
}
|
||||
|
||||
if (format) {
|
||||
echo(chalk.dim('checking formatting with prettier (and shfmt if available)...'))
|
||||
|
||||
const [prettierProc, shfmtProc] = await Promise.all([
|
||||
$({
|
||||
cwd: repoRoot, // Must be run from root
|
||||
})`${checks.format}`,
|
||||
|
||||
$({
|
||||
cwd: repoRoot, // Must be run from root
|
||||
})`${checks.formatShell}`,
|
||||
])
|
||||
|
||||
table.push(
|
||||
[
|
||||
'format',
|
||||
checks.format.join(' '),
|
||||
getAndCheckOutcome({ exitCode: prettierProc.exitCode }),
|
||||
'Root',
|
||||
] satisfies TableRow,
|
||||
[
|
||||
'format shell',
|
||||
checks.formatShell.join(' '),
|
||||
getAndCheckOutcome({
|
||||
exitCode: shfmtProc.exitCode,
|
||||
skippedCode: SHFMT_SKIPPED_EXIT_CODE,
|
||||
}),
|
||||
'Root',
|
||||
] satisfies TableRow
|
||||
)
|
||||
|
||||
const workersTypesExitCode = await $({
|
||||
cwd: repoRoot, // Must be run from root
|
||||
})`${checks.workersTypes}`.exitCode
|
||||
table.push([
|
||||
'workers types',
|
||||
checks.workersTypes.join(' '),
|
||||
getAndCheckOutcome({ exitCode: workersTypesExitCode }),
|
||||
'Root',
|
||||
] satisfies TableRow)
|
||||
}
|
||||
|
||||
echo(table.toString())
|
||||
if (didErr) {
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Command } from '@commander-js/extra-typings'
|
||||
|
||||
import type { Options as ZXOptions } from 'zx'
|
||||
|
||||
export const ciCmd = new Command('ci').description('Scripts used in CI')
|
||||
|
||||
function opts(): Partial<ZXOptions> {
|
||||
return {
|
||||
verbose: true,
|
||||
env: {
|
||||
FORCE_COLOR: '1',
|
||||
...process.env,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
ciCmd
|
||||
.command('check')
|
||||
.description('Run CI checks')
|
||||
.action(async () => {
|
||||
await $(opts())`bun turbo check:ci`
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Command } from '@commander-js/extra-typings'
|
||||
|
||||
import { getRepoRoot } from '../path'
|
||||
|
||||
export const devCmd = new Command('dev')
|
||||
.description(
|
||||
'Run development server for Workers projects, etc. Use --runx-help to see all options.'
|
||||
)
|
||||
.argument(
|
||||
'[args...]',
|
||||
'Arguments to pass to the dev script. May need to use -- to pass options to the dev script.'
|
||||
)
|
||||
.allowUnknownOption()
|
||||
// allow passing --help to the dev script
|
||||
.helpOption('--runx-help')
|
||||
.action(async (args) => {
|
||||
const cwd = process.cwd()
|
||||
const repoRoot = getRepoRoot()
|
||||
const isRepoRoot = cwd === repoRoot
|
||||
|
||||
const [hasDevScript, hasWranglerJsonc] = await Promise.all([
|
||||
fs
|
||||
.readJson('./package.json')
|
||||
.then((packageJson) => packageJson.scripts?.dev !== undefined)
|
||||
.catch(() => {
|
||||
return false
|
||||
}),
|
||||
fs.pathExists('./wrangler.jsonc'),
|
||||
])
|
||||
|
||||
$.stdio = 'inherit'
|
||||
|
||||
if (!isRepoRoot && (hasWranglerJsonc || hasDevScript)) {
|
||||
await $`pnpm dev ${args}`
|
||||
} else {
|
||||
const argsWithSeparator = args.length > 0 ? ['--', ...args] : args
|
||||
await $`pnpm turbo dev ${argsWithSeparator}`
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,144 @@
|
||||
import { Command } from '@commander-js/extra-typings'
|
||||
import Table from 'cli-table3'
|
||||
|
||||
import { getRepoRoot } from '../path'
|
||||
import { getOutcome, SHFMT_SKIPPED_EXIT_CODE } from '../proc'
|
||||
|
||||
export const fixCmd = new Command('fix')
|
||||
.description('Fix deps/lint/format issues. If no options are provided, all fixes are run.')
|
||||
|
||||
.option('-r, --root', 'Run fixes from root of repo. Defaults to cwd', false)
|
||||
.option('-d, --deps', 'Fix dependency versions with syncpack')
|
||||
.option('-l, --lint', 'Fix oxlint issues')
|
||||
.option('-f, --format', 'Format code with prettier')
|
||||
.option(
|
||||
'-w, --workers-types',
|
||||
'Generate Workers runtime types (worker-configuration.d.ts) via wrangler types'
|
||||
)
|
||||
|
||||
.action(async ({ root, deps, lint, format, workersTypes }) => {
|
||||
const repoRoot = getRepoRoot()
|
||||
if (root) {
|
||||
cd(repoRoot)
|
||||
}
|
||||
|
||||
// Run all if none are selected
|
||||
if (!deps && !lint && !format && !workersTypes) {
|
||||
deps = true
|
||||
lint = true
|
||||
format = true
|
||||
workersTypes = true
|
||||
}
|
||||
|
||||
const cwd = process.cwd()
|
||||
const runFromRoot = cwd === repoRoot
|
||||
const cwdName = path.basename(cwd)
|
||||
|
||||
const fixes = {
|
||||
deps: ['run-fix-deps'],
|
||||
lint: ['run-oxlint', '--fix'],
|
||||
workersTypes: ['turbo', 'fix:workers-types'],
|
||||
format: ['prettier', '.', '--cache', '--write', '--log-level=warn'],
|
||||
formatShell: ['runx', 'shfmt', 'fix', '--skip-if-unavailable'],
|
||||
} as const satisfies { [key: string]: string[] }
|
||||
|
||||
type TableRow = [string, string, string, string]
|
||||
const table = new Table({
|
||||
head: [
|
||||
chalk.whiteBright('Name'),
|
||||
chalk.whiteBright('Command'),
|
||||
chalk.whiteBright('Outcome'),
|
||||
chalk.whiteBright('Ran From'),
|
||||
] satisfies TableRow,
|
||||
})
|
||||
|
||||
$.stdio = 'inherit'
|
||||
$.verbose = true
|
||||
$.nothrow = true
|
||||
|
||||
let didErr = false
|
||||
function getAndCheckOutcome({
|
||||
exitCode,
|
||||
skippedCode,
|
||||
}: {
|
||||
exitCode: number | null
|
||||
skippedCode?: number
|
||||
}): string {
|
||||
if (exitCode !== 0 && exitCode !== skippedCode) {
|
||||
didErr = true
|
||||
}
|
||||
return getOutcome({ exitCode, skippedCode })
|
||||
}
|
||||
|
||||
if (deps) {
|
||||
const exitCode = await $({
|
||||
cwd: repoRoot, // Must be run from root
|
||||
})`${fixes.deps}`.exitCode
|
||||
table.push([
|
||||
'deps',
|
||||
fixes.deps.join(' '),
|
||||
getAndCheckOutcome({ exitCode }),
|
||||
'Root',
|
||||
] satisfies TableRow)
|
||||
}
|
||||
|
||||
if (lint) {
|
||||
const exitCode = await $`${fixes.lint}`.exitCode
|
||||
table.push([
|
||||
'lint',
|
||||
fixes.lint.join(' '),
|
||||
getAndCheckOutcome({ exitCode }),
|
||||
runFromRoot ? 'Root' : `cwd (${cwdName})`,
|
||||
] satisfies TableRow)
|
||||
}
|
||||
|
||||
if (workersTypes) {
|
||||
const exitCode = await $({
|
||||
cwd: repoRoot, // Must be run from root
|
||||
})`${fixes.workersTypes}`.exitCode
|
||||
table.push([
|
||||
'workers types',
|
||||
fixes.workersTypes.join(' '),
|
||||
getAndCheckOutcome({ exitCode }),
|
||||
runFromRoot ? 'Root' : `cwd (${cwdName})`,
|
||||
] satisfies TableRow)
|
||||
}
|
||||
|
||||
if (format) {
|
||||
echo(chalk.dim('formatting with prettier (and shfmt if available)...'))
|
||||
|
||||
const [prettierProc, shfmtProc] = await Promise.all([
|
||||
$({
|
||||
cwd: repoRoot, // Must be run from root
|
||||
})`${fixes.format}`,
|
||||
|
||||
$({
|
||||
cwd: repoRoot, // Must be run from root
|
||||
})`${fixes.formatShell}`,
|
||||
])
|
||||
|
||||
table.push(
|
||||
[
|
||||
'format',
|
||||
fixes.format.join(' '),
|
||||
getAndCheckOutcome({ exitCode: prettierProc.exitCode }),
|
||||
'Root',
|
||||
] satisfies TableRow,
|
||||
[
|
||||
'format shell',
|
||||
fixes.formatShell.join(' '),
|
||||
getAndCheckOutcome({
|
||||
exitCode: shfmtProc.exitCode,
|
||||
skippedCode: SHFMT_SKIPPED_EXIT_CODE,
|
||||
}),
|
||||
'Root',
|
||||
] satisfies TableRow
|
||||
)
|
||||
}
|
||||
|
||||
echo(table.toString())
|
||||
|
||||
if (didErr) {
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Command } from '@commander-js/extra-typings'
|
||||
|
||||
import { SHFMT_SKIPPED_EXIT_CODE } from '../proc'
|
||||
|
||||
export const shfmtCmd = new Command('shfmt').description('Format shell scripts with shfmt')
|
||||
|
||||
shfmtCmd
|
||||
.command('fix')
|
||||
.description('Format shell scripts with shfmt')
|
||||
.option(
|
||||
'--skip-if-unavailable',
|
||||
'Only run if shfmt and ripgrep are available, otherwise skip with a warning.',
|
||||
false
|
||||
)
|
||||
.action(async ({ skipIfUnavailable }) => {
|
||||
const mode: Mode = 'write'
|
||||
|
||||
await checkShfmtAvailable(skipIfUnavailable, mode)
|
||||
|
||||
await runShfmt(mode)
|
||||
})
|
||||
|
||||
shfmtCmd
|
||||
.command('check')
|
||||
.description('Check shell scripts with shfmt')
|
||||
.option(
|
||||
'--skip-if-unavailable',
|
||||
'Only run if shfmt and ripgrep are available, otherwise skip with a warning.',
|
||||
false
|
||||
)
|
||||
.action(async ({ skipIfUnavailable }) => {
|
||||
const mode: Mode = 'diff'
|
||||
|
||||
await checkShfmtAvailable(skipIfUnavailable, mode)
|
||||
|
||||
await runShfmt(mode)
|
||||
})
|
||||
|
||||
type Mode = 'write' | 'diff'
|
||||
|
||||
async function runShfmt(mode: Mode) {
|
||||
await Promise.all([
|
||||
$`rg --files-with-matches '^#!.*\\b(sh|bash|zsh|fish|dash|ksh|csh)\\b' -g '!*.*' .`
|
||||
.pipe($`xargs shfmt --case-indent ${`--${mode}`}`)
|
||||
.pipe(process.stderr),
|
||||
$({
|
||||
nothrow: true, // may not be any .sh files
|
||||
})`rg --files-with-matches '^#!.*\\b(sh|bash|zsh|fish|dash|ksh|csh)\\b' -g '*.sh' .`
|
||||
.pipe($`xargs shfmt --case-indent ${`--${mode}`}`)
|
||||
.pipe(process.stderr),
|
||||
])
|
||||
}
|
||||
|
||||
async function checkShfmtAvailable(skipIfUnavailable: boolean, mode: Mode): Promise<void> {
|
||||
const [shfmtExit, rgExit] = await Promise.all([
|
||||
which('shfmt', { nothrow: true }),
|
||||
which('rg', { nothrow: true }),
|
||||
])
|
||||
|
||||
const missing: string[] = []
|
||||
if (shfmtExit === null) {
|
||||
missing.push('shfmt')
|
||||
}
|
||||
if (rgExit === null) {
|
||||
missing.push('rg (ripgrep)')
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
const missingStr = `${missing.join(' and ')} ${missing.length === 1 ? 'is' : 'are'} unavailable`
|
||||
|
||||
if (skipIfUnavailable) {
|
||||
echo(chalk.yellow(`warning: ${missingStr}, skipping shell formatting`))
|
||||
process.exit(SHFMT_SKIPPED_EXIT_CODE)
|
||||
} else {
|
||||
const action = mode === 'write' ? 'fix' : 'check'
|
||||
echo(chalk.red(`error: ${missingStr}, unable to ${action} shell formatting`))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Command } from '@commander-js/extra-typings'
|
||||
|
||||
import { getRepoRoot } from '../path'
|
||||
import { updatePnpm } from '../update-pnpm'
|
||||
|
||||
export const updateCmd = new Command('update')
|
||||
.description('Update things in the repo')
|
||||
.hook('preAction', () => {
|
||||
cd(getRepoRoot())
|
||||
$.verbose = true
|
||||
$.stdio = 'inherit'
|
||||
$.env.FORCE_COLOR = '1'
|
||||
})
|
||||
|
||||
updateCmd
|
||||
.command('deps')
|
||||
.description('Update dependencies via syncpack')
|
||||
.action(async () => {
|
||||
await $`syncpack update`
|
||||
|
||||
// Run fix if there are any changes
|
||||
const status = await $({
|
||||
stdio: 'pipe',
|
||||
})`git status --porcelain`.text()
|
||||
if (status.includes('package.json') || status.includes('pnpm-lock.yaml')) {
|
||||
await $`just fix --deps`
|
||||
}
|
||||
})
|
||||
|
||||
updateCmd
|
||||
.command('pnpm')
|
||||
.description('Update pnpm version')
|
||||
.action(async () => {
|
||||
await updatePnpm()
|
||||
})
|
||||
|
||||
updateCmd
|
||||
.command('turbo')
|
||||
.description('Update turbo version (must have clean working tree)')
|
||||
.action(async () => {
|
||||
await $`pnpm dlx @turbo/codemod@latest update`
|
||||
})
|
||||
Reference in New Issue
Block a user