init claude-code
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
import { feature } from 'bun:bundle'
|
||||
import type { WriteFileOptions } from 'fs'
|
||||
import {
|
||||
closeSync,
|
||||
writeFileSync as fsWriteFileSync,
|
||||
fsyncSync,
|
||||
openSync,
|
||||
} from 'fs'
|
||||
// biome-ignore lint: This file IS the cloneDeep wrapper - it must import the original
|
||||
import lodashCloneDeep from 'lodash-es/cloneDeep.js'
|
||||
import { addSlowOperation } from '../bootstrap/state.js'
|
||||
import { logForDebugging } from './debug.js'
|
||||
|
||||
// Extended WriteFileOptions to include 'flush' which is available in Node.js 20.1.0+
|
||||
// but not yet in @types/node
|
||||
type WriteFileOptionsWithFlush =
|
||||
| WriteFileOptions
|
||||
| (WriteFileOptions & { flush?: boolean })
|
||||
|
||||
// --- Slow operation logging infrastructure ---
|
||||
|
||||
/**
|
||||
* Threshold in milliseconds for logging slow JSON/clone operations.
|
||||
* Operations taking longer than this will be logged for debugging.
|
||||
* - Override: set CLAUDE_CODE_SLOW_OPERATION_THRESHOLD_MS to a number
|
||||
* - Dev builds: 20ms (lower threshold for development)
|
||||
* - Ants: 300ms (enabled for all internal users)
|
||||
*/
|
||||
const SLOW_OPERATION_THRESHOLD_MS = (() => {
|
||||
const envValue = process.env.CLAUDE_CODE_SLOW_OPERATION_THRESHOLD_MS
|
||||
if (envValue !== undefined) {
|
||||
const parsed = Number(envValue)
|
||||
if (!Number.isNaN(parsed) && parsed >= 0) {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return 20
|
||||
}
|
||||
if (process.env.USER_TYPE === 'ant') {
|
||||
return 300
|
||||
}
|
||||
return Infinity
|
||||
})()
|
||||
|
||||
// Re-export for callers that still need the threshold value directly
|
||||
export { SLOW_OPERATION_THRESHOLD_MS }
|
||||
|
||||
// Module-level re-entrancy guard. logForDebugging writes to a debug file via
|
||||
// appendFileSync, which goes through slowLogging again. Without this guard,
|
||||
// a slow appendFileSync → dispose → logForDebugging → appendFileSync → dispose → ...
|
||||
let isLogging = false
|
||||
|
||||
/**
|
||||
* Extract the first stack frame outside this file, so the DevBar warning
|
||||
* points at the actual caller instead of a useless `Object{N keys}`.
|
||||
* Only called when an operation was actually slow — never on the fast path.
|
||||
*/
|
||||
export function callerFrame(stack: string | undefined): string {
|
||||
if (!stack) return ''
|
||||
for (const line of stack.split('\n')) {
|
||||
if (line.includes('slowOperations')) continue
|
||||
const m = line.match(/([^/\\]+?):(\d+):\d+\)?$/)
|
||||
if (m) return ` @ ${m[1]}:${m[2]}`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a human-readable description from tagged template arguments.
|
||||
* Only called when an operation was actually slow — never on the fast path.
|
||||
*
|
||||
* args[0] = TemplateStringsArray, args[1..n] = interpolated values
|
||||
*/
|
||||
function buildDescription(args: IArguments): string {
|
||||
const strings = args[0] as TemplateStringsArray
|
||||
let result = ''
|
||||
for (let i = 0; i < strings.length; i++) {
|
||||
result += strings[i]
|
||||
if (i + 1 < args.length) {
|
||||
const v = args[i + 1]
|
||||
if (Array.isArray(v)) {
|
||||
result += `Array[${(v as unknown[]).length}]`
|
||||
} else if (v !== null && typeof v === 'object') {
|
||||
result += `Object{${Object.keys(v as Record<string, unknown>).length} keys}`
|
||||
} else if (typeof v === 'string') {
|
||||
result += v.length > 80 ? `${v.slice(0, 80)}…` : v
|
||||
} else {
|
||||
result += String(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
class AntSlowLogger {
|
||||
startTime: number
|
||||
args: IArguments
|
||||
err: Error
|
||||
|
||||
constructor(args: IArguments) {
|
||||
this.startTime = performance.now()
|
||||
this.args = args
|
||||
// V8/JSC capture the stack at construction but defer the expensive string
|
||||
// formatting until .stack is read — so this stays off the fast path.
|
||||
this.err = new Error()
|
||||
}
|
||||
|
||||
[Symbol.dispose](): void {
|
||||
const duration = performance.now() - this.startTime
|
||||
if (duration > SLOW_OPERATION_THRESHOLD_MS && !isLogging) {
|
||||
isLogging = true
|
||||
try {
|
||||
const description =
|
||||
buildDescription(this.args) + callerFrame(this.err.stack)
|
||||
logForDebugging(
|
||||
`[SLOW OPERATION DETECTED] ${description} (${duration.toFixed(1)}ms)`,
|
||||
)
|
||||
addSlowOperation(description, duration)
|
||||
} finally {
|
||||
isLogging = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const NOOP_LOGGER: Disposable = { [Symbol.dispose]() {} }
|
||||
|
||||
// Must be regular functions (not arrows) to access `arguments`
|
||||
function slowLoggingAnt(
|
||||
_strings: TemplateStringsArray,
|
||||
..._values: unknown[]
|
||||
): AntSlowLogger {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
return new AntSlowLogger(arguments)
|
||||
}
|
||||
|
||||
function slowLoggingExternal(): Disposable {
|
||||
return NOOP_LOGGER
|
||||
}
|
||||
|
||||
/**
|
||||
* Tagged template for slow operation logging.
|
||||
*
|
||||
* In ANT builds: creates an AntSlowLogger that times the operation and logs
|
||||
* if it exceeds the threshold. Description is built lazily only when slow.
|
||||
*
|
||||
* In external builds: returns a singleton no-op disposable. Zero allocations,
|
||||
* zero timing. AntSlowLogger and buildDescription are dead-code-eliminated.
|
||||
*
|
||||
* @example
|
||||
* using _ = slowLogging`structuredClone(${value})`
|
||||
* const result = structuredClone(value)
|
||||
*/
|
||||
export const slowLogging: {
|
||||
(strings: TemplateStringsArray, ...values: unknown[]): Disposable
|
||||
} = feature('SLOW_OPERATION_LOGGING') ? slowLoggingAnt : slowLoggingExternal
|
||||
|
||||
// --- Wrapped operations ---
|
||||
|
||||
/**
|
||||
* Wrapped JSON.stringify with slow operation logging.
|
||||
* Use this instead of JSON.stringify directly to detect performance issues.
|
||||
*
|
||||
* @example
|
||||
* import { jsonStringify } from './slowOperations.js'
|
||||
* const json = jsonStringify(data)
|
||||
* const prettyJson = jsonStringify(data, null, 2)
|
||||
*/
|
||||
export function jsonStringify(
|
||||
value: unknown,
|
||||
replacer?: (this: unknown, key: string, value: unknown) => unknown,
|
||||
space?: string | number,
|
||||
): string
|
||||
export function jsonStringify(
|
||||
value: unknown,
|
||||
replacer?: (number | string)[] | null,
|
||||
space?: string | number,
|
||||
): string
|
||||
export function jsonStringify(
|
||||
value: unknown,
|
||||
replacer?:
|
||||
| ((this: unknown, key: string, value: unknown) => unknown)
|
||||
| (number | string)[]
|
||||
| null,
|
||||
space?: string | number,
|
||||
): string {
|
||||
using _ = slowLogging`JSON.stringify(${value})`
|
||||
return JSON.stringify(
|
||||
value,
|
||||
replacer as Parameters<typeof JSON.stringify>[1],
|
||||
space,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapped JSON.parse with slow operation logging.
|
||||
* Use this instead of JSON.parse directly to detect performance issues.
|
||||
*
|
||||
* @example
|
||||
* import { jsonParse } from './slowOperations.js'
|
||||
* const data = jsonParse(jsonString)
|
||||
*/
|
||||
export const jsonParse: typeof JSON.parse = (text, reviver) => {
|
||||
using _ = slowLogging`JSON.parse(${text})`
|
||||
// V8 de-opts JSON.parse when a second argument is passed, even if undefined.
|
||||
// Branch explicitly so the common (no-reviver) path stays on the fast path.
|
||||
return typeof reviver === 'undefined'
|
||||
? JSON.parse(text)
|
||||
: JSON.parse(text, reviver)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapped structuredClone with slow operation logging.
|
||||
* Use this instead of structuredClone directly to detect performance issues.
|
||||
*
|
||||
* @example
|
||||
* import { clone } from './slowOperations.js'
|
||||
* const copy = clone(originalObject)
|
||||
*/
|
||||
export function clone<T>(value: T, options?: StructuredSerializeOptions): T {
|
||||
using _ = slowLogging`structuredClone(${value})`
|
||||
return structuredClone(value, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapped cloneDeep with slow operation logging.
|
||||
* Use this instead of lodash cloneDeep directly to detect performance issues.
|
||||
*
|
||||
* @example
|
||||
* import { cloneDeep } from './slowOperations.js'
|
||||
* const copy = cloneDeep(originalObject)
|
||||
*/
|
||||
export function cloneDeep<T>(value: T): T {
|
||||
using _ = slowLogging`cloneDeep(${value})`
|
||||
return lodashCloneDeep(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around fs.writeFileSync with slow operation logging.
|
||||
* Supports flush option to ensure data is written to disk before returning.
|
||||
* @param filePath The path to the file to write to
|
||||
* @param data The data to write (string or Buffer)
|
||||
* @param options Optional write options (encoding, mode, flag, flush)
|
||||
* @deprecated Use `fs.promises.writeFile` instead for non-blocking writes.
|
||||
* Sync file writes block the event loop and cause performance issues.
|
||||
*/
|
||||
export function writeFileSync_DEPRECATED(
|
||||
filePath: string,
|
||||
data: string | NodeJS.ArrayBufferView,
|
||||
options?: WriteFileOptionsWithFlush,
|
||||
): void {
|
||||
using _ = slowLogging`fs.writeFileSync(${filePath}, ${data})`
|
||||
|
||||
// Check if flush is requested (for object-style options)
|
||||
const needsFlush =
|
||||
options !== null &&
|
||||
typeof options === 'object' &&
|
||||
'flush' in options &&
|
||||
options.flush === true
|
||||
|
||||
if (needsFlush) {
|
||||
// Manual flush: open file, write, fsync, close
|
||||
const encoding =
|
||||
typeof options === 'object' && 'encoding' in options
|
||||
? options.encoding
|
||||
: undefined
|
||||
const mode =
|
||||
typeof options === 'object' && 'mode' in options
|
||||
? options.mode
|
||||
: undefined
|
||||
let fd: number | undefined
|
||||
try {
|
||||
fd = openSync(filePath, 'w', mode)
|
||||
fsWriteFileSync(fd, data, { encoding: encoding ?? undefined })
|
||||
fsyncSync(fd)
|
||||
} finally {
|
||||
if (fd !== undefined) {
|
||||
closeSync(fd)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No flush needed, use standard writeFileSync
|
||||
fsWriteFileSync(filePath, data, options as WriteFileOptions)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user