Skip to content

Commit 8598a74

Browse files
committed
refactor: consolidate utilities to use @socketsecurity/lib
Replaced local utility implementations with centralized versions from @socketsecurity/lib: - changed-test-mapper.mjs: Use lib git and path utilities - interactive-runner.mjs: Use lib runWithMask (simplified from 269 to 71 lines) - run-command.mjs: Use lib spawn with proper error handling - test.mjs: Use lib getDefaultSpinner Removed redundant local utility files: - git.mjs (replaced by @socketsecurity/lib/git) - path.mjs (replaced by @socketsecurity/lib/path) - spinner.mjs (replaced by @socketsecurity/lib/spinner) Benefits: - Reduced code duplication by 455 lines - Consistent behavior across all socket-* repositories - Better error handling with try/catch in run-command.mjs - Simplified maintenance with centralized utilities
1 parent c927d65 commit 8598a74

File tree

7 files changed

+61
-455
lines changed

7 files changed

+61
-455
lines changed

scripts/test.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ import { spawn } from 'node:child_process'
77
import { existsSync } from 'node:fs'
88
import path from 'node:path'
99
import { fileURLToPath } from 'node:url'
10+
import { getDefaultSpinner } from '@socketsecurity/lib/spinner'
11+
1012
import { getLocalPackageAliases } from './utils/get-local-package-aliases.mjs'
1113
import { getTestsToRun } from './utils/changed-test-mapper.mjs'
1214
import { printHeader } from './utils/helpers.mjs'
1315
import { logger } from './utils/logger.mjs'
1416
import { parseArgs } from './utils/parse-args.mjs'
1517
import { onExit } from './utils/signal-exit.mjs'
16-
import { spinner } from './utils/spinner.mjs'
18+
19+
const spinner = getDefaultSpinner()
1720

1821
const WIN32 = process.platform === 'win32'
1922

scripts/utils/changed-test-mapper.mjs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
import { existsSync } from 'node:fs'
77
import path from 'node:path'
88

9-
import { getChangedFilesSync, getStagedFilesSync } from './git.mjs'
10-
import { normalizePath } from './path.mjs'
9+
import {
10+
getChangedFilesSync,
11+
getStagedFilesSync,
12+
} from '@socketsecurity/lib/git'
13+
import { normalizePath } from '@socketsecurity/lib/path'
1114

1215
const rootPath = path.resolve(process.cwd())
1316

scripts/utils/git.mjs

Lines changed: 0 additions & 117 deletions
This file was deleted.

scripts/utils/interactive-runner.mjs

Lines changed: 7 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@
33
* Standardized across all socket-* repositories.
44
*/
55

6-
import { spawn } from 'node:child_process'
7-
import readline from 'node:readline'
8-
9-
import { spinner } from './spinner.mjs'
10-
11-
// Will import from registry once built:
12-
// import { attachOutputMask, clearLine, writeOutput } from '@socketsecurity/lib/stdio/mask'
6+
import { runWithMask } from '@socketsecurity/lib/stdio/mask'
137

148
/**
159
* Run a command with interactive output control.
@@ -29,208 +23,16 @@ export async function runWithOutput(command, args = [], options = {}) {
2923
cwd = process.cwd(),
3024
env = process.env,
3125
message = 'Running',
32-
showOnError = true,
3326
toggleText = 'to see output',
3427
verbose = false,
3528
} = options
3629

37-
return new Promise((resolve, reject) => {
38-
let isSpinning = false
39-
let outputBuffer = []
40-
let showOutput = verbose
41-
let hasTestFailures = false
42-
let hasWorkerTerminationError = false
43-
44-
// Start spinner if not verbose and TTY
45-
if (!showOutput && process.stdout.isTTY) {
46-
spinner.start(`${message} (ctrl+o ${toggleText})`)
47-
isSpinning = true
48-
}
49-
50-
const child = spawn(command, args, {
51-
cwd,
52-
env,
53-
stdio: ['inherit', 'pipe', 'pipe'],
54-
})
55-
56-
// Setup keyboard handling for TTY
57-
if (process.stdin.isTTY && !verbose) {
58-
readline.emitKeypressEvents(process.stdin)
59-
process.stdin.setRawMode(true)
60-
61-
const keypressHandler = (_str, key) => {
62-
// ctrl+o toggles output
63-
if (key?.ctrl && key.name === 'o') {
64-
showOutput = !showOutput
65-
66-
if (showOutput) {
67-
// Stop spinner and show buffered output
68-
if (isSpinning) {
69-
spinner.stop()
70-
isSpinning = false
71-
}
72-
73-
// Clear line and show buffer
74-
process.stdout.write('\r\x1b[K')
75-
if (outputBuffer.length > 0) {
76-
console.log('--- Showing output ---')
77-
outputBuffer.forEach(line => {
78-
process.stdout.write(line)
79-
})
80-
outputBuffer = []
81-
}
82-
} else {
83-
// Hide output and restart spinner
84-
process.stdout.write('\r\x1b[K')
85-
if (!isSpinning) {
86-
spinner.start(`${message} (ctrl+o ${toggleText})`)
87-
isSpinning = true
88-
}
89-
}
90-
}
91-
// ctrl+c to cancel
92-
else if (key?.ctrl && key.name === 'c') {
93-
child.kill('SIGTERM')
94-
if (process.stdin.isTTY) {
95-
process.stdin.setRawMode(false)
96-
}
97-
process.exit(130)
98-
}
99-
}
100-
101-
process.stdin.on('keypress', keypressHandler)
102-
103-
// Cleanup on exit
104-
child.on('exit', () => {
105-
if (process.stdin.isTTY) {
106-
process.stdin.setRawMode(false)
107-
process.stdin.removeListener('keypress', keypressHandler)
108-
}
109-
})
110-
}
111-
112-
// Handle stdout
113-
if (child.stdout) {
114-
child.stdout.on('data', data => {
115-
const text = data.toString()
116-
117-
// Filter out known non-fatal warnings (can appear in stdout too)
118-
const isFilteredWarning =
119-
text.includes('Terminating worker thread') ||
120-
text.includes('Unhandled Rejection') ||
121-
text.includes('Object.ThreadTermination') ||
122-
text.includes('tinypool@')
123-
124-
if (isFilteredWarning) {
125-
hasWorkerTerminationError = true
126-
// Skip these warnings - they're non-fatal cleanup messages
127-
// But continue to check for test failures in the same output
128-
}
129-
130-
// Check for test failures in vitest output
131-
if (
132-
text.includes('FAIL') ||
133-
text.match(/Test Files.*\d+ failed/) ||
134-
text.match(/Tests\s+\d+ failed/)
135-
) {
136-
hasTestFailures = true
137-
}
138-
139-
// Don't write filtered warnings to output
140-
if (isFilteredWarning) {
141-
return
142-
}
143-
144-
if (showOutput) {
145-
process.stdout.write(text)
146-
} else {
147-
outputBuffer.push(text)
148-
// Keep buffer reasonable (last 1000 lines)
149-
const lines = outputBuffer.join('').split('\n')
150-
if (lines.length > 1000) {
151-
outputBuffer = [lines.slice(-1000).join('\n')]
152-
}
153-
}
154-
})
155-
}
156-
157-
// Handle stderr
158-
if (child.stderr) {
159-
child.stderr.on('data', data => {
160-
const text = data.toString()
161-
// Filter out known non-fatal warnings
162-
const isFilteredWarning =
163-
text.includes('Terminating worker thread') ||
164-
text.includes('Unhandled Rejection') ||
165-
text.includes('Object.ThreadTermination') ||
166-
text.includes('tinypool@')
167-
168-
if (isFilteredWarning) {
169-
hasWorkerTerminationError = true
170-
// Skip these warnings - they're non-fatal cleanup messages
171-
return
172-
}
173-
174-
// Check for test failures
175-
if (
176-
text.includes('FAIL') ||
177-
text.match(/Test Files.*\d+ failed/) ||
178-
text.match(/Tests\s+\d+ failed/)
179-
) {
180-
hasTestFailures = true
181-
}
182-
183-
if (showOutput) {
184-
process.stderr.write(text)
185-
} else {
186-
outputBuffer.push(text)
187-
}
188-
})
189-
}
190-
191-
child.on('exit', code => {
192-
// Cleanup keyboard if needed
193-
if (process.stdin.isTTY && !verbose) {
194-
process.stdin.setRawMode(false)
195-
}
196-
197-
// Override exit code if we only have worker termination errors
198-
// and no actual test failures
199-
let finalCode = code || 0
200-
if (code !== 0 && hasWorkerTerminationError && !hasTestFailures) {
201-
// This is the known non-fatal worker thread cleanup issue
202-
// All tests passed, so return success
203-
finalCode = 0
204-
}
205-
206-
if (isSpinning) {
207-
if (finalCode === 0) {
208-
spinner.success(`${message} completed`)
209-
} else {
210-
spinner.fail(`${message} failed`)
211-
// Show output on error if configured
212-
if (showOnError && outputBuffer.length > 0) {
213-
console.log('\n--- Output ---')
214-
outputBuffer.forEach(line => {
215-
process.stdout.write(line)
216-
})
217-
}
218-
}
219-
}
220-
221-
resolve(finalCode)
222-
})
223-
224-
child.on('error', error => {
225-
if (process.stdin.isTTY && !verbose) {
226-
process.stdin.setRawMode(false)
227-
}
228-
229-
if (isSpinning) {
230-
spinner.fail(`${message} error: ${error.message}`)
231-
}
232-
reject(error)
233-
})
30+
return runWithMask(command, args, {
31+
cwd,
32+
env,
33+
message,
34+
showOutput: verbose,
35+
toggleText,
23436
})
23537
}
23638

scripts/utils/path.mjs

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)