* feat: branded postinstall with @clack/prompts Replace raw ANSI ASCII art dump with structured, branded installer flow using @clack/prompts and picocolors: - Branded intro header with product name and version - Animated spinners during patch and Playwright install steps - Subprocess output captured (no more raw npm/Playwright noise) - Boxed summary note with status indicators (✓/⚠) - Clean outro with next-step instructions - Graceful fallback to minimal output if clack unavailable - All output routed to stderr for npm lifecycle visibility - Async subprocess execution (not execSync) so spinners animate * fix: restore ASCII banner alongside clack postinstall UI The branded ASCII art banner is a key differentiator. Keep it as the first thing users see, then follow with clack spinner steps for the setup progress. Fallback path also simplified since the banner already shows the version. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
127 lines
4.8 KiB
JavaScript
127 lines
4.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { exec as execCb } from 'child_process'
|
|
import { createRequire } from 'module'
|
|
import os from 'os'
|
|
import { dirname, resolve } from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
const require = createRequire(import.meta.url)
|
|
const pkg = require(resolve(__dirname, '..', 'package.json'))
|
|
const cwd = resolve(__dirname, '..')
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Async exec helper — captures stdout+stderr, never inherits to terminal
|
|
// ---------------------------------------------------------------------------
|
|
function run(cmd, options = {}) {
|
|
return new Promise((resolve) => {
|
|
execCb(cmd, { cwd, ...options }, (error, stdout, stderr) => {
|
|
resolve({ ok: !error, stdout, stderr, error })
|
|
})
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Redirect stdout → stderr so npm always shows postinstall output.
|
|
// npm ≥7 suppresses stdout from lifecycle scripts by default; stderr is
|
|
// always forwarded. Clack writes to process.stdout, so we reroute it.
|
|
// ---------------------------------------------------------------------------
|
|
process.stdout.write = process.stderr.write.bind(process.stderr)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ASCII banner — printed before clack UI for brand recognition
|
|
// ---------------------------------------------------------------------------
|
|
const cyan = '\x1b[36m'
|
|
const dim = '\x1b[2m'
|
|
const reset = '\x1b[0m'
|
|
|
|
const banner =
|
|
'\n' +
|
|
cyan +
|
|
' ██████╗ ███████╗██████╗ \n' +
|
|
' ██╔════╝ ██╔════╝██╔══██╗\n' +
|
|
' ██║ ███╗███████╗██║ ██║\n' +
|
|
' ██║ ██║╚════██║██║ ██║\n' +
|
|
' ╚██████╔╝███████║██████╔╝\n' +
|
|
' ╚═════╝ ╚══════╝╚═════╝ ' +
|
|
reset + '\n' +
|
|
'\n' +
|
|
` Get Shit Done ${dim}v${pkg.version}${reset}\n`
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Main — wrapped in async IIFE, with graceful fallback if clack fails
|
|
// ---------------------------------------------------------------------------
|
|
;(async () => {
|
|
process.stderr.write(banner)
|
|
|
|
let p, pc
|
|
|
|
try {
|
|
p = await import('@clack/prompts')
|
|
pc = (await import('picocolors')).default
|
|
} catch {
|
|
// Clack or picocolors unavailable — fall back to minimal output
|
|
process.stderr.write(` Run gsd to get started.\n\n`)
|
|
await run('npx patch-package')
|
|
await run('npx playwright install chromium')
|
|
return
|
|
}
|
|
|
|
// --- Branded intro -------------------------------------------------------
|
|
p.intro('Setup')
|
|
|
|
const results = []
|
|
const s = p.spinner()
|
|
|
|
// --- Step 1: Apply patches -----------------------------------------------
|
|
s.start('Applying patches…')
|
|
const patchResult = await run('npx patch-package')
|
|
if (patchResult.ok) {
|
|
s.stop('Patches applied')
|
|
results.push({ label: 'Patches applied', ok: true })
|
|
} else {
|
|
s.stop(pc.yellow('Patches — skipped (non-fatal)'))
|
|
results.push({
|
|
label: 'Patches skipped — run ' + pc.cyan('npx patch-package') + ' manually',
|
|
ok: false,
|
|
})
|
|
}
|
|
|
|
// --- Step 2: Playwright browser ------------------------------------------
|
|
// Avoid --with-deps: install scripts should not block on interactive sudo
|
|
// prompts. If Linux libs are missing, suggest the explicit follow-up.
|
|
s.start('Setting up browser tools…')
|
|
const pwResult = await run('npx playwright install chromium')
|
|
if (pwResult.ok) {
|
|
s.stop('Browser tools ready')
|
|
results.push({ label: 'Browser tools ready', ok: true })
|
|
} else {
|
|
const output = `${pwResult.stdout ?? ''}${pwResult.stderr ?? ''}`
|
|
if (os.platform() === 'linux' && output.includes('Host system is missing dependencies to run browsers.')) {
|
|
s.stop(pc.yellow('Browser downloaded, missing Linux deps'))
|
|
results.push({
|
|
label: 'Run ' + pc.cyan('sudo npx playwright install-deps chromium') + ' to finish setup',
|
|
ok: false,
|
|
})
|
|
} else {
|
|
s.stop(pc.yellow('Browser tools — skipped (non-fatal)'))
|
|
results.push({
|
|
label: 'Browser tools unavailable — run ' + pc.cyan('npx playwright install chromium'),
|
|
ok: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
// --- Summary note --------------------------------------------------------
|
|
const lines = results.map(
|
|
(r) => (r.ok ? pc.green('✓') : pc.yellow('⚠')) + ' ' + r.label
|
|
)
|
|
lines.push('')
|
|
lines.push('Run ' + pc.cyan('gsd') + ' to get started.')
|
|
|
|
p.note(lines.join('\n'), 'Installed')
|
|
|
|
// --- Outro ---------------------------------------------------------------
|
|
p.outro(pc.green('Done!'))
|
|
})()
|