| import { |
| execSync, |
| execFileSync, |
| spawn, |
| ExecSyncOptionsWithStringEncoding, |
| } from 'child_process' |
| import { existsSync } from 'fs' |
| import globOrig from 'glob' |
| import { join } from 'path' |
| import { promisify } from 'util' |
|
|
| export const glob = promisify(globOrig) |
|
|
| export const NEXT_DIR = join(__dirname, '..') |
|
|
| |
| |
| |
| |
| |
| |
| export function exec(title, command, opts?: ExecSyncOptionsWithStringEncoding) { |
| if (Array.isArray(command)) { |
| logCommand(title, command) |
| return execFileSync(command[0], command.slice(1), { |
| stdio: 'inherit', |
| cwd: NEXT_DIR, |
| ...opts, |
| }) |
| } else { |
| logCommand(title, command) |
| return execSync(command, { |
| stdio: 'inherit', |
| cwd: NEXT_DIR, |
| ...opts, |
| }) |
| } |
| } |
|
|
| class ExecError extends Error { |
| code: number | null |
| stdout: Buffer |
| stderr: Buffer |
| } |
|
|
| type ExecOutput = { |
| stdout: Buffer |
| stderr: Buffer |
| } |
|
|
| |
| |
| |
| |
| |
| export function execAsyncWithOutput( |
| title, |
| command, |
| opts?: Partial<ExecSyncOptionsWithStringEncoding> |
| ): Promise<ExecOutput> { |
| logCommand(title, command) |
| const proc = spawn(command[0], command.slice(1), { |
| encoding: 'utf8', |
| stdio: ['inherit', 'pipe', 'pipe'], |
| cwd: NEXT_DIR, |
| ...opts, |
| }) |
|
|
| if (!proc || !proc.stdout || !proc.stderr) { |
| throw new Error(`Failed to spawn: ${title}`) |
| } |
|
|
| const stdout: Buffer[] = [] |
| proc.stdout.on('data', (data) => { |
| process.stdout.write(data) |
| stdout.push(data) |
| }) |
| const stderr: Buffer[] = [] |
| proc.stderr.on('data', (data) => { |
| process.stderr.write(data) |
| stderr.push(data) |
| }) |
| return new Promise((resolve, reject) => { |
| proc.on('exit', (code) => { |
| if (code === 0) { |
| return resolve({ |
| stdout: Buffer.concat(stdout), |
| stderr: Buffer.concat(stderr), |
| }) |
| } |
| const err = new ExecError( |
| `Command failed with exit code ${code}: ${prettyCommand(command)}` |
| ) |
| err.code = code |
| err.stdout = Buffer.concat(stdout) |
| err.stderr = Buffer.concat(stderr) |
| reject(err) |
| }) |
| }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function execFn<T>(title: string, fn: () => T): T { |
| logCommand(title, fn.toString()) |
| return fn() |
| } |
|
|
| |
| |
| |
| function prettyCommand(command: string | string[]): string { |
| if (Array.isArray(command)) command = command.join(' ') |
| return command.replace(/ -- .*/, ' -- …') |
| } |
|
|
| |
| |
| |
| |
| export function logCommand(title: string, command: string | string[]) { |
| if (command) { |
| const pretty = prettyCommand(command) |
| console.log(`\n\x1b[1;4m${title}\x1b[0m\n> \x1b[1m${pretty}\x1b[0m\n`) |
| } else { |
| console.log(`\n\x1b[1;4m${title}\x1b[0m\n`) |
| } |
| } |
|
|
| const DEFAULT_GLOBS = ['**', '!target', '!node_modules', '!crates', '!.turbo'] |
| const FORCED_GLOBS = ['package.json', 'README*', 'LICENSE*', 'LICENCE*'] |
|
|
| |
| |
| |
| |
| export async function packageFiles(path: string): Promise<string[]> { |
| const { files = DEFAULT_GLOBS, main, bin } = require(`${path}/package.json`) |
|
|
| const allFiles: string[] = files.concat( |
| FORCED_GLOBS, |
| main ?? [], |
| Object.values(bin ?? {}) |
| ) |
| const isGlob = (f) => f.includes('*') || f.startsWith('!') |
| const simpleFiles = allFiles |
| .filter((f) => !isGlob(f) && existsSync(join(path, f))) |
| .map((f) => f.replace(/^\.\//, '')) |
| const globFiles = allFiles.filter(isGlob) |
| const globbedFiles = await glob( |
| `+(${globFiles.filter((f) => !f.startsWith('!')).join('|')})`, |
| { |
| cwd: path, |
| ignore: `+(${globFiles |
| .filter((f) => f.startsWith('!')) |
| .map((f) => f.slice(1)) |
| .join('|')})`, |
| } |
| ) |
| const packageFiles = [...globbedFiles, ...simpleFiles].sort() |
| const set = new Set() |
| return packageFiles.filter((f) => { |
| if (set.has(f)) return false |
| |
| |
| |
| set.add(f) |
| while (f.includes('/')) { |
| f = f.replace(/\/[^/]+$/, '') |
| if (set.has(f)) return false |
| } |
| return true |
| }) |
| } |
|
|