Skip to content

Commit c4b1339

Browse files
authored
fix: remaining fixes from PR 1025 (#1027)
* fix: remaining fixes from PR 1025 - Add prebuild hook and generate-packages.mjs script to ensure template packages are generated before CLI build runs - Always copy cli.js to dist directory (required for dist/index.js to work) - Add Node.js CLI flags whitelist to prevent --help, --version, etc from being forwarded to Python CLI - Remove unused --prod flag from build script - Simplify cli.js copy to use copyFileSync directly instead of spawning node * test: fix flaky shadow npm and path-resolve tests in CI - Add isDebug mock to npm-base.test.mts and install.test.mts to ensure --loglevel args are consistently added (isDebug() was returning true in CI due to environment variables) - Use hoisted mocks for whichRealSync in path-resolve.test.mts to ensure reliable mock behavior in CI (dynamic import pattern was failing) * fix: address PR review feedback - Handle --flag=value syntax in nodeCliFlags whitelist by extracting base flag name - Use nullish coalescing for process.exit in generate-packages.mjs to handle signal-killed processes (code is null, which coerces to 0) * fix: correct mock function name in path-resolve tests The test was mocking `resolveBinPathSync` but the actual function is `resolveRealBinSync`. This caused tests to use the real implementation in CI, which resolved to the actual npm path on the runner. * fix: always run CLI build in CI test jobs The CI workflow was using `lookup-only: true` for cache restore, which only checks if the cache exists but doesn't actually restore files. This caused test jobs to skip the build step when cache existed, but the build artifacts weren't actually present on disk. Remove `lookup-only` and always run the build step. The build script already has its own caching logic and will skip unnecessary work.
1 parent 94f7852 commit c4b1339

File tree

8 files changed

+74
-52
lines changed

8 files changed

+74
-52
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,8 @@ jobs:
108108
key: cli-build-${{ runner.os }}-${{ steps.cli-cache-key.outputs.hash }}
109109
restore-keys: |
110110
cli-build-${{ runner.os }}-
111-
lookup-only: true
112111
113112
- name: Build CLI
114-
if: steps.cli-build-cache.outputs.cache-hit != 'true'
115113
working-directory: packages/cli
116114
run: pnpm run build
117115

@@ -155,10 +153,8 @@ jobs:
155153
key: cli-build-${{ runner.os }}-${{ steps.cli-cache-key.outputs.hash }}
156154
restore-keys: |
157155
cli-build-${{ runner.os }}-
158-
lookup-only: true
159156
160157
- name: Build CLI
161-
if: steps.cli-build-cache.outputs.cache-hit != 'true'
162158
working-directory: packages/cli
163159
run: pnpm run build
164160

@@ -315,10 +311,8 @@ jobs:
315311
key: cli-build-${{ runner.os }}-${{ steps.cli-cache-key.outputs.hash }}
316312
restore-keys: |
317313
cli-build-${{ runner.os }}-
318-
lookup-only: true
319314
320315
- name: Build CLI
321-
if: steps.cli-build-cache.outputs.cache-hit != 'true'
322316
working-directory: packages/cli
323317
run: pnpm run build
324318

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"logo-light.png"
1919
],
2020
"scripts": {
21+
"prebuild": "node scripts/generate-packages.mjs",
2122
"build": "node --max-old-space-size=8192 --import=./scripts/load.mjs scripts/build.mjs",
2223
"build:force": "node --max-old-space-size=8192 --import=./scripts/load.mjs scripts/build.mjs --force",
2324
"build:watch": "node --max-old-space-size=8192 --import=./scripts/load.mjs scripts/build.mjs --watch",

packages/cli/scripts/build.mjs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/**
22
* Build script for Socket CLI.
3-
* Options: --quiet, --verbose, --prod, --force, --watch
3+
* Options: --quiet, --verbose, --force, --watch
44
*/
55

6+
import { copyFileSync } from 'node:fs'
67
import { promises as fs } from 'node:fs'
78
import path from 'node:path'
89
import { fileURLToPath } from 'node:url'
@@ -97,7 +98,6 @@ async function main() {
9798
const verbose = isVerbose()
9899
const watch = process.argv.includes('--watch')
99100
const force = process.argv.includes('--force')
100-
const prod = process.argv.includes('--prod')
101101

102102
// Pass --force flag via environment variable.
103103
if (force) {
@@ -202,19 +202,6 @@ async function main() {
202202
command: 'node',
203203
args: [...NODE_MEMORY_FLAGS, '.config/esbuild.inject.config.mjs'],
204204
},
205-
// Copy CLI to dist for production builds.
206-
...(prod
207-
? [
208-
{
209-
name: 'Copy CLI to dist',
210-
command: 'node',
211-
args: [
212-
'-e',
213-
'require("fs").copyFileSync("build/cli.js", "dist/cli.js")',
214-
],
215-
},
216-
]
217-
: []),
218205
]
219206

220207
// Run build steps sequentially.
@@ -248,6 +235,9 @@ async function main() {
248235
}
249236
}
250237

238+
// Copy CLI bundle to dist (required for dist/index.js to work).
239+
copyFileSync('build/cli.js', 'dist/cli.js')
240+
251241
// Post-process: Fix node-gyp strings to prevent bundler issues.
252242
if (!quiet && verbose) {
253243
log.info('Post-processing build output...')
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Generate template-based packages required for CLI build.
3+
* Runs the package generation scripts from package-builder.
4+
*/
5+
6+
import { spawn } from '@socketsecurity/lib/spawn'
7+
8+
const scripts = [
9+
'../package-builder/scripts/generate-cli-sentry-package.mjs',
10+
'../package-builder/scripts/generate-socket-package.mjs',
11+
'../package-builder/scripts/generate-socketbin-packages.mjs',
12+
]
13+
14+
for (const script of scripts) {
15+
const result = await spawn('node', [script], { stdio: 'inherit' })
16+
if (result.code !== 0) {
17+
// Use nullish coalescing to handle signal-killed processes (code is null).
18+
process.exit(result.code ?? 1)
19+
}
20+
}

packages/cli/src/utils/cli/with-subcommands.mts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,28 @@ export async function meowWithSubcommands(
615615

616616
// If first arg is a flag (starts with --), try Python CLI forwarding.
617617
// This enables: socket --repo owner/repo --target-path .
618-
if (commandOrAliasName?.startsWith('--')) {
618+
// Exception: Don't forward Node.js CLI built-in flags (help, version, etc).
619+
const nodeCliFlags = new Set([
620+
'--compact-header',
621+
'--config',
622+
'--dry-run',
623+
'--help',
624+
'--help-full',
625+
'--json',
626+
'--markdown',
627+
'--no-banner',
628+
'--no-spinner',
629+
'--nobanner',
630+
'--org',
631+
'--spinner',
632+
'--version',
633+
])
634+
// Extract base flag name for --flag=value syntax (e.g., '--config=/path' -> '--config').
635+
const baseFlagName = commandOrAliasName?.split('=')[0]
636+
if (
637+
commandOrAliasName?.startsWith('--') &&
638+
!nodeCliFlags.has(baseFlagName ?? '')
639+
) {
619640
const pythonResult = await spawnSocketPython(argv, {
620641
stdio: 'inherit',
621642
})

packages/cli/test/unit/shadow/npm-base.test.mts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ vi.mock('@socketsecurity/lib/constants/node', () => ({
117117
supportsNodePermissionFlag: vi.fn(() => true),
118118
}))
119119

120+
// Mock isDebug to always return false so --loglevel args are added.
121+
vi.mock('@socketsecurity/lib/debug', () => ({
122+
isDebug: vi.fn(() => false),
123+
}))
124+
120125
describe('shadowNpmBase', () => {
121126
const mockSpawnResult = Promise.resolve({
122127
success: true,

packages/cli/test/unit/shadow/npm/install.test.mts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ vi.mock('../../../../src/constants/cli.mts', () => ({
9595
FLAG_LOGLEVEL: '--loglevel',
9696
}))
9797

98+
// Mock isDebug to always return false so --loglevel args are added.
99+
vi.mock('@socketsecurity/lib/debug', () => ({
100+
isDebug: vi.fn(() => false),
101+
}))
102+
98103
describe('shadowNpmInstall', () => {
99104
const mockProcess = {
100105
send: vi.fn(),

packages/cli/test/unit/utils/fs/path-resolve.test.mts

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,19 @@ import { createTestWorkspace } from '../../../helpers/workspace-helper.mts'
3737

3838
const PACKAGE_JSON = 'package.json'
3939

40+
// Hoisted mocks for better CI reliability.
41+
const mockWhichRealSync = vi.hoisted(() => vi.fn())
42+
const mockResolveRealBinSync = vi.hoisted(() => vi.fn((p: string) => p))
43+
4044
// Mock dependencies for new tests.
4145
vi.mock('@socketsecurity/lib/bin', async () => {
4246
const actual = await vi.importActual<
4347
typeof import('@socketsecurity/lib/bin')
4448
>('@socketsecurity/lib/bin')
4549
return {
4650
...actual,
47-
resolveBinPathSync: vi.fn(p => p),
48-
whichRealSync: vi.fn(),
51+
resolveRealBinSync: mockResolveRealBinSync,
52+
whichRealSync: mockWhichRealSync,
4953
}
5054
})
5155

@@ -382,11 +386,8 @@ describe('Path Resolve', () => {
382386
vi.clearAllMocks()
383387
})
384388

385-
it('finds bin path when available', async () => {
386-
const { whichRealSync } = vi.mocked(
387-
await import('@socketsecurity/lib/bin'),
388-
)
389-
whichRealSync.mockReturnValue(['/usr/local/bin/npm'])
389+
it('finds bin path when available', () => {
390+
mockWhichRealSync.mockReturnValue(['/usr/local/bin/npm'])
390391

391392
const result = findBinPathDetailsSync('npm')
392393

@@ -400,10 +401,7 @@ describe('Path Resolve', () => {
400401
it('handles shadowed bin paths', async () => {
401402
const constants = await import('../../../../src/constants.mts')
402403
const shadowBinPath = constants.default.shadowBinPath
403-
const { whichRealSync } = vi.mocked(
404-
await import('@socketsecurity/lib/bin'),
405-
)
406-
whichRealSync.mockReturnValue([
404+
mockWhichRealSync.mockReturnValue([
407405
`${shadowBinPath}/npm`,
408406
'/usr/local/bin/npm',
409407
])
@@ -417,11 +415,8 @@ describe('Path Resolve', () => {
417415
})
418416
})
419417

420-
it('handles no bin path found', async () => {
421-
const { whichRealSync } = vi.mocked(
422-
await import('@socketsecurity/lib/bin'),
423-
)
424-
whichRealSync.mockReturnValue(null)
418+
it('handles no bin path found', () => {
419+
mockWhichRealSync.mockReturnValue(null)
425420

426421
const result = findBinPathDetailsSync('nonexistent')
427422

@@ -432,11 +427,8 @@ describe('Path Resolve', () => {
432427
})
433428
})
434429

435-
it('handles empty array result', async () => {
436-
const { whichRealSync } = vi.mocked(
437-
await import('@socketsecurity/lib/bin'),
438-
)
439-
whichRealSync.mockReturnValue([])
430+
it('handles empty array result', () => {
431+
mockWhichRealSync.mockReturnValue([])
440432

441433
const result = findBinPathDetailsSync('npm')
442434

@@ -447,11 +439,8 @@ describe('Path Resolve', () => {
447439
})
448440
})
449441

450-
it('handles single string result', async () => {
451-
const { whichRealSync } = vi.mocked(
452-
await import('@socketsecurity/lib/bin'),
453-
)
454-
whichRealSync.mockReturnValue('/usr/local/bin/npm' as any)
442+
it('handles single string result', () => {
443+
mockWhichRealSync.mockReturnValue('/usr/local/bin/npm' as any)
455444

456445
const result = findBinPathDetailsSync('npm')
457446

@@ -465,10 +454,7 @@ describe('Path Resolve', () => {
465454
it('handles only shadow bin in path', async () => {
466455
const constants = await import('../../../../src/constants.mts')
467456
const shadowBinPath = constants.default.shadowBinPath
468-
const { whichRealSync } = vi.mocked(
469-
await import('@socketsecurity/lib/bin'),
470-
)
471-
whichRealSync.mockReturnValue([`${shadowBinPath}/npm`])
457+
mockWhichRealSync.mockReturnValue([`${shadowBinPath}/npm`])
472458

473459
const result = findBinPathDetailsSync('npm')
474460

0 commit comments

Comments
 (0)