Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"update": "node scripts/update.mts",
"// Setup": "",
"setup": "node scripts/setup.mts",
"preinstall": "node scripts/bootstrap-firewall-deps.mts",
"postinstall": "node scripts/setup.mts --install --quiet",
"prepare": "husky",
"pretest": "pnpm run build:cli"
Expand All @@ -74,6 +73,7 @@
"@pnpm/lockfile.detect-dep-types": "catalog:",
"@pnpm/lockfile.fs": "catalog:",
"@pnpm/logger": "catalog:",
"@sinclair/typebox": "catalog:",
"@socketregistry/hyrious__bun.lockb": "catalog:",
"@socketregistry/indent-string": "catalog:",
"@socketregistry/is-interactive": "catalog:",
Expand Down
21 changes: 12 additions & 9 deletions packages/cli/src/cli-entry.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// Set global Socket theme for consistent CLI branding.
import { isError } from '@socketsecurity/lib/errors'
import { setTheme } from '@socketsecurity/lib/themes'
import { setTheme } from '@socketsecurity/lib/themes/context'
setTheme('socket')

import { promises as fs } from 'node:fs'
Expand Down Expand Up @@ -214,8 +214,9 @@ void (async () => {
try {
logger.error('Fatal error:', err)
} catch {
// Fallback to console if logger fails.
console.error('Fatal error:', err)
// Last-ditch fallback when logger itself throws — the catch
// ensures we still report the original error before exit.
console.error('Fatal error:', err) // # socket-hook: allow logger
}

// Track CLI error for fatal exceptions.
Expand All @@ -234,8 +235,8 @@ process.on('uncaughtException', async err => {
try {
logger.error('Uncaught exception:', err)
} catch {
// Fallback to console if logger fails.
console.error('Uncaught exception:', err)
// Last-ditch fallback when logger itself throws.
console.error('Uncaught exception:', err) // # socket-hook: allow logger
}

// Track CLI error for uncaught exception.
Expand All @@ -248,7 +249,8 @@ process.on('uncaughtException', async err => {
try {
logger.error('Error in uncaughtException handler:', e)
} catch {
console.error('Error in uncaughtException handler:', e)
// Last-ditch fallback when logger itself throws.
console.error('Error in uncaughtException handler:', e) // # socket-hook: allow logger
}
} finally {
// eslint-disable-next-line n/no-process-exit
Expand All @@ -262,8 +264,8 @@ process.on('unhandledRejection', async (reason, promise) => {
try {
logger.error('Unhandled rejection at:', promise, 'reason:', reason)
} catch {
// Fallback to console if logger fails.
console.error('Unhandled rejection at:', promise, 'reason:', reason)
// Last-ditch fallback when logger itself throws.
console.error('Unhandled rejection at:', promise, 'reason:', reason) // # socket-hook: allow logger
}

// Track CLI error for unhandled rejection.
Expand All @@ -277,7 +279,8 @@ process.on('unhandledRejection', async (reason, promise) => {
try {
logger.error('Error in unhandledRejection handler:', e)
} catch {
console.error('Error in unhandledRejection handler:', e)
// Last-ditch fallback when logger itself throws.
console.error('Error in unhandledRejection handler:', e) // # socket-hook: allow logger
}
} finally {
// eslint-disable-next-line n/no-process-exit
Expand Down
104 changes: 59 additions & 45 deletions packages/cli/src/utils/terminal/ascii-header.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

import colors from 'yoctocolors-cjs'

import { applyShimmer } from '@socketsecurity/lib/effects/text-shimmer'
import { configToSpec, frameColors } from '@socketsecurity/lib/effects/shimmer'
import { colorsToAnsi } from '@socketsecurity/lib/effects/shimmer-terminal'

import type {
ShimmerColorGradient,
ShimmerState,
} from '@socketsecurity/lib/effects/text-shimmer'
Palette,
RGB,
ShimmerSpec,
} from '@socketsecurity/lib/effects/shimmer'

/**
* Color themes for header styling.
Expand Down Expand Up @@ -93,60 +95,72 @@ function applyHexColor(text: string, hexColor: string): string {
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`
}

/**
* Pick the brighter of two RGB colors. Used to compose two shimmer
* waves into one frame: each wave's `frameColors[i]` is computed
* independently, then merged so the brighter highlight wins per char.
* Treats luminance as the simple sum of channels — fine here because
* both waves share base + highlight palettes.
*/
function brighterRgb(a: RGB, b: RGB): RGB {
return a[0] + a[1] + a[2] >= b[0] + b[1] + b[2] ? a : b
}

/**
* Render ASCII logo with shimmer effect for given frame.
* Uses socket-registry's applyShimmer with theme color gradients.
* Features dual shimmer waves and slanted diagonal movement.
*
* Uses socket-lib's @socketsecurity/lib/effects/shimmer engine
* (5.26.1+). Builds two ShimmerSpecs per line — primary + secondary
* offset by 35 frames — and merges their per-char colors with
* `brighterRgb` so the dual-wave look is preserved. Each line gets a
* `slantOffset = i * 4` added to the frame counter, producing a
* diagonal wave across the logo. Applies bold via ANSI before the
* shimmer's truecolor escape so terminals render the highlight bold.
*/
export function renderShimmerFrame(
frame: number,
theme: HeaderTheme = 'default',
): string {
const themeGradient = THEME_COLORS_RGB[
theme
] as unknown as ShimmerColorGradient
const themePalette = THEME_COLORS_RGB[theme] as unknown as Palette

// Apply shimmer to each line of the ASCII logo with slanted offset.
const lines: string[] = []
for (let i = 0; i < ASCII_LOGO.length; i++) {
const line = ASCII_LOGO[i]!
const lineLength = line.length

// Apply bold formatting first so applyShimmer can detect and preserve it.
const boldLine = `\x1b[1m${line}\x1b[0m`

// Create slanted shimmer by offsetting each line's frame position.
// This creates a diagonal wave effect across the logo.
// Slant the wave by offsetting each line's frame counter — same
// 4-frame-per-row delta as the previous implementation.
const slantOffset = i * 4

// Primary shimmer wave.
const shimmerState1: ShimmerState = {
currentDir: 'ltr',
mode: 'ltr',
speed: 0.25,
step: frame + slantOffset,
}

// Secondary shimmer wave (offset to create dual wave effect).
const shimmerState2: ShimmerState = {
currentDir: 'ltr',
mode: 'ltr',
speed: 0.25,
step: frame + slantOffset + 35,
}

// Apply first shimmer pass (will detect and preserve bold).
const shimmered1 = applyShimmer(boldLine, shimmerState1, {
color: themeGradient,
direction: 'ltr',
})

// Apply second shimmer pass for dual wave effect.
const shimmered2 = applyShimmer(shimmered1, shimmerState2, {
color: themeGradient,
direction: 'ltr',
})

lines.push(shimmered2)
const speed = 0.25

// Build the shimmer spec once and reuse for both waves — the
// spec is frame-independent (positionAt is a closure over speed
// + textLength + direction). The two waves differ only in the
// frame counter passed to `frameColors`.
const spec: ShimmerSpec = configToSpec(
{
color: themePalette,
dir: 'ltr',
speed,
},
lineLength,
)

// Compute per-char colors for both waves and merge.
const primaryColors = frameColors(spec, lineLength, frame + slantOffset)
const secondaryColors = frameColors(
spec,
lineLength,
frame + slantOffset + 35,
)
const merged: RGB[] = primaryColors.map((c, idx) =>
brighterRgb(c, secondaryColors[idx]!),
)

// Render to ANSI truecolor + wrap in bold for the brighter look
// the previous implementation produced. \x1b[1m turns bold on,
// colorsToAnsi emits per-char truecolor codes, \x1b[0m resets.
lines.push(`\x1b[1m${colorsToAnsi(line, merged)}\x1b[0m`)
}

return lines.join('\n')
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/test/unit/constants/paths.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ describe('paths constants', () => {

it('getBinCliPath returns path to CLI entry point', () => {
const result = getBinCliPath()
expect(result).toContain('cli.js')
// The bundle entry is `dist/index.js` (was `dist/cli.js` before
// the unified-build rename in src/constants/paths.mts).
expect(result).toContain('dist/index.js')
})

it('getDistPath returns distPath', () => {
Expand Down
20 changes: 20 additions & 0 deletions packages/package-builder/templates/socketaddon-main/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,30 @@ function loadNativeAddon() {
}

if (buildOutDir) {
// First: look for a sibling-package-style layout
// `socketaddon-iocraft-<platformId>/iocraft.node` next to the
// main package.
const siblingPath = join(buildOutDir, `socketaddon-iocraft-${platformId}`, 'iocraft.node')
if (existsSync(siblingPath)) {
return require(siblingPath)
}
// Second: look inside the main package's bundled
// `node_modules/@socketaddon/iocraft-<platformId>/iocraft.node`.
// This is where pnpm leaves the optionalDependency when it's
// installed into the file: package's local node_modules but
// not lifted into the consumer's .pnpm store (which happens
// for `file:` deps that declare optionalDependencies).
const bundledPath = join(
buildOutDir,
'socketaddon-iocraft',
'node_modules',
'@socketaddon',
`iocraft-${platformId}`,
'iocraft.node',
)
if (existsSync(bundledPath)) {
return require(bundledPath)
}
}

throw new Error('Not in development build structure')
Expand Down
Loading
Loading