This commit is contained in:
2025-08-18 23:06:34 +08:00
parent 0bc04fb659
commit ed18af0cad
1926 changed files with 275098 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
const { log } = require('proc-log')
const fs = require('node:fs')
const { dirname } = require('node:path')
const os = require('node:os')
const { inspect, format } = require('node:util')
const { bin: options } = require('./options.js')
// add a meta method to proc-log for passing optional
// metadata through to log handlers
const META = Symbol('meta')
const parseArgs = (...args) => {
const { [META]: isMeta } = args[args.length - 1] || {}
return isMeta
? [args[args.length - 1], ...args.slice(0, args.length - 1)]
: [{}, ...args]
}
log.meta = (meta = {}) => ({ [META]: true, ...meta })
const levels = new Map([
'silly',
'verbose',
'info',
'http',
'notice',
'warn',
'error',
'silent',
].map((level, index) => [level, index]))
const addLogListener = (write, { eol = os.EOL, loglevel = 'silly', colors = false } = {}) => {
const levelIndex = levels.get(loglevel)
const magenta = m => colors ? `\x1B[35m${m}\x1B[39m` : m
const dim = m => colors ? `\x1B[2m${m}\x1B[22m` : m
const red = m => colors ? `\x1B[31m${m}\x1B[39m` : m
const formatter = (level, ...args) => {
const depth = level === 'error' && args[0] && args[0].code === 'ERESOLVE' ? Infinity : 10
if (level === 'info' && args[0] === 'timeEnd') {
args[1] = dim(args[1])
} else if (level === 'error' && args[0] === 'timeError') {
args[1] = red(args[1])
}
const messages = args.map(a => typeof a === 'string' ? a : inspect(a, { depth, colors }))
const pref = `${process.pid} ${magenta(level)} `
return pref + format(...messages).trim().split('\n').join(`${eol}${pref}`) + eol
}
process.on('log', (...args) => {
const [meta, level, ...logArgs] = parseArgs(...args)
if (levelIndex <= levels.get(level) || meta.force) {
write(formatter(level, ...logArgs))
}
})
}
if (options.loglevel !== 'silent') {
addLogListener((v) => process.stderr.write(v), {
eol: '\n',
colors: options.colors,
loglevel: options.loglevel,
})
}
if (options.logfile) {
log.silly('logfile', options.logfile)
fs.mkdirSync(dirname(options.logfile), { recursive: true })
const fd = fs.openSync(options.logfile, 'a')
addLogListener((str) => fs.writeSync(fd, str))
}
module.exports = log

View File

@@ -0,0 +1,123 @@
const nopt = require('nopt')
const path = require('node:path')
const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k)
const cleanPath = (val) => {
const k = Symbol('key')
const data = {}
nopt.typeDefs.path.validate(data, k, val)
return data[k]
}
const parse = (...noptArgs) => {
const binOnlyOpts = {
command: String,
loglevel: String,
colors: Boolean,
timing: ['always', Boolean],
logfile: String,
}
const arbOpts = {
add: Array,
rm: Array,
omit: Array,
update: Array,
workspaces: Array,
global: Boolean,
force: Boolean,
'global-style': Boolean,
'prefer-dedupe': Boolean,
'legacy-peer-deps': Boolean,
'update-all': Boolean,
before: Date,
path: path,
cache: path,
...binOnlyOpts,
}
const short = {
quiet: ['--loglevel', 'warn'],
logs: ['--logfile', 'true'],
w: '--workspaces',
g: '--global',
f: '--force',
}
const defaults = {
// key order is important for command and path
// since they shift positional args
// command is 1st, path is 2nd
command: (o) => o.argv.remain.shift(),
path: (o) => cleanPath(o.argv.remain.shift() || '.'),
colors: has(process.env, 'NO_COLOR') ? false : !!process.stderr.isTTY,
loglevel: 'silly',
timing: (o) => o.loglevel === 'silly',
cache: `${process.env.HOME}/.npm/_cacache`,
}
const derived = [
// making update either `all` or an array of names but not both
({ updateAll: all, update: names, ...o }) => {
if (all || names) {
o.update = all != null ? { all } : { names }
}
return o
},
({ logfile, ...o }) => {
// logfile is parsed as a string so if its true or set but empty
// then set the default logfile
if (logfile === 'true' || logfile === '') {
logfile = `arb-log-${new Date().toISOString().replace(/[.:]/g, '_')}.log`
}
// then parse it the same as nopt parses other paths
if (logfile) {
o.logfile = cleanPath(logfile)
}
return o
},
]
const transforms = [
// Camelcase all top level keys
(o) => {
const entries = Object.entries(o).map(([k, v]) => [
k.replace(/-./g, s => s[1].toUpperCase()),
v,
])
return Object.fromEntries(entries)
},
// Set defaults on unset keys
(o) => {
for (const [k, v] of Object.entries(defaults)) {
if (!has(o, k)) {
o[k] = typeof v === 'function' ? v(o) : v
}
}
return o
},
// Set/unset derived values
...derived.map((derive) => (o) => derive(o) || o),
// Separate bin and arborist options
({ argv: { remain: _ }, ...o }) => {
const bin = { _ }
for (const k of Object.keys(binOnlyOpts)) {
if (has(o, k)) {
bin[k] = o[k]
delete o[k]
}
}
return { bin, arb: o }
},
]
let options = nopt(arbOpts, short, ...noptArgs)
for (const t of transforms) {
options = t(options)
}
return options
}
module.exports = parse()

View File

@@ -0,0 +1,4 @@
const { inspect } = require('node:util')
const log = require('./logging.js')
module.exports = tree => log.info(inspect(tree.toJSON(), { depth: Infinity }))

View File

@@ -0,0 +1,33 @@
const { bin: options } = require('./options.js')
const log = require('./logging.js')
const timers = new Map()
const finished = new Map()
process.on('time', (level, name) => {
if (level === 'start') {
if (timers.has(name)) {
throw new Error('conflicting timer! ' + name)
}
timers.set(name, process.hrtime.bigint())
} else if (level === 'end') {
if (!timers.has(name)) {
throw new Error('timer not started! ' + name)
}
const elapsed = Number(process.hrtime.bigint() - timers.get(name))
timers.delete(name)
finished.set(name, elapsed)
if (options.timing) {
log.info('timeEnd', `${name} ${elapsed / 1e9}s`, log.meta({ force: options.timing === 'always' }))
}
}
})
process.on('exit', () => {
for (const name of timers.keys()) {
log.error('timeError', 'Dangling timer:', name)
process.exitCode = 1
}
})
module.exports = finished