Skip to content

Commit b40531e

Browse files
billxinlijdalton
andauthored
feat(telemetry): adding initial telemetry functionality to the cli (#956)
* feat(telemetry): adding initial telemetry functionality to the cli * feat(cr): cr --------- Co-authored-by: John-David Dalton <jdalton@users.noreply.github.com>
1 parent 486f1af commit b40531e

File tree

16 files changed

+3418
-46
lines changed

16 files changed

+3418
-46
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
"@socketregistry/packageurl-js": "1.0.9",
123123
"@socketsecurity/config": "3.0.1",
124124
"@socketsecurity/registry": "1.1.17",
125-
"@socketsecurity/sdk": "1.4.94",
125+
"@socketsecurity/sdk": "1.4.95",
126126
"@types/blessed": "0.1.25",
127127
"@types/cmd-shim": "5.0.2",
128128
"@types/js-yaml": "4.0.9",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli.mts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,27 @@ import { AuthError, InputError, captureException } from './utils/errors.mts'
1818
import { failMsgWithBadge } from './utils/fail-msg-with-badge.mts'
1919
import { meowWithSubcommands } from './utils/meow-with-subcommands.mts'
2020
import { serializeResultJson } from './utils/serialize-result-json.mts'
21+
import {
22+
finalizeTelemetry,
23+
setupTelemetryExitHandlers,
24+
trackCliComplete,
25+
trackCliError,
26+
trackCliStart,
27+
} from './utils/telemetry/integration.mts'
2128
import { socketPackageLink } from './utils/terminal-link.mts'
2229

2330
const __filename = fileURLToPath(import.meta.url)
2431

32+
// Capture CLI start time at module level for global error handlers.
33+
const cliStartTime = Date.now()
34+
35+
// Set up telemetry exit handlers early to catch all exit scenarios.
36+
setupTelemetryExitHandlers()
37+
2538
void (async () => {
39+
// Track CLI start for telemetry.
40+
await trackCliStart(process.argv)
41+
2642
const registryUrl = lookupRegistryUrl()
2743
await updateNotifier({
2844
authInfo: lookupRegistryAuthToken(registryUrl, { recursive: true }),
@@ -50,8 +66,14 @@ void (async () => {
5066
},
5167
{ aliases: rootAliases },
5268
)
69+
70+
// Track successful CLI completion.
71+
await trackCliComplete(process.argv, cliStartTime, process.exitCode)
5372
} catch (e) {
5473
process.exitCode = 1
74+
75+
// Track CLI error for telemetry.
76+
await trackCliError(process.argv, cliStartTime, e, process.exitCode)
5577
debugFn('error', 'CLI uncaught error')
5678
debugDir('error', e)
5779

@@ -105,4 +127,45 @@ void (async () => {
105127

106128
await captureException(e)
107129
}
108-
})()
130+
})().catch(async err => {
131+
// Fatal error in main async function.
132+
console.error('Fatal error:', err)
133+
134+
// Track CLI error for fatal exceptions.
135+
await trackCliError(process.argv, cliStartTime, err, 1)
136+
137+
// Finalize telemetry before fatal exit.
138+
await finalizeTelemetry()
139+
140+
// eslint-disable-next-line n/no-process-exit
141+
process.exit(1)
142+
})
143+
144+
// Handle uncaught exceptions.
145+
process.on('uncaughtException', async err => {
146+
console.error('Uncaught exception:', err)
147+
148+
// Track CLI error for uncaught exception.
149+
await trackCliError(process.argv, cliStartTime, err, 1)
150+
151+
// Finalize telemetry before exit.
152+
await finalizeTelemetry()
153+
154+
// eslint-disable-next-line n/no-process-exit
155+
process.exit(1)
156+
})
157+
158+
// Handle unhandled promise rejections.
159+
process.on('unhandledRejection', async (reason, promise) => {
160+
console.error('Unhandled rejection at:', promise, 'reason:', reason)
161+
162+
// Track CLI error for unhandled rejection.
163+
const error = reason instanceof Error ? reason : new Error(String(reason))
164+
await trackCliError(process.argv, cliStartTime, error, 1)
165+
166+
// Finalize telemetry before exit.
167+
await finalizeTelemetry()
168+
169+
// eslint-disable-next-line n/no-process-exit
170+
process.exit(1)
171+
})

src/commands/npm/cmd-npm.mts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import { commonFlags, outputFlags } from '../../flags.mts'
1212
import { filterFlags } from '../../utils/cmd.mts'
1313
import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
1414
import { getFlagApiRequirementsOutput } from '../../utils/output-formatting.mts'
15+
import {
16+
trackSubprocessExit,
17+
trackSubprocessStart,
18+
} from '../../utils/telemetry/integration.mts'
1519

1620
import type {
1721
CliCommandConfig,
@@ -86,20 +90,29 @@ async function run(
8690
const argsToForward = filterFlags(argv, { ...commonFlags, ...outputFlags }, [
8791
FLAG_JSON,
8892
])
93+
94+
// Track subprocess start.
95+
const subprocessStartTime = await trackSubprocessStart(NPM)
96+
8997
const { spawnPromise } = await shadowNpmBin(argsToForward, {
9098
stdio: 'inherit',
9199
})
92100

101+
// Handle exit codes and signals using event-based pattern.
93102
// See https://nodejs.org/api/child_process.html#event-exit.
94103
spawnPromise.process.on(
95104
'exit',
96-
(code: string | null, signalName: NodeJS.Signals | null) => {
97-
if (signalName) {
98-
process.kill(process.pid, signalName)
99-
} else if (typeof code === 'number') {
100-
// eslint-disable-next-line n/no-process-exit
101-
process.exit(code)
102-
}
105+
(code: number | null, signalName: NodeJS.Signals | null) => {
106+
// Track subprocess exit and flush telemetry before exiting.
107+
// Use .then() to ensure telemetry completes before process.exit().
108+
void trackSubprocessExit(NPM, subprocessStartTime, code).then(() => {
109+
if (signalName) {
110+
process.kill(process.pid, signalName)
111+
} else if (typeof code === 'number') {
112+
// eslint-disable-next-line n/no-process-exit
113+
process.exit(code)
114+
}
115+
})
103116
},
104117
)
105118

src/commands/npx/cmd-npx.mts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import constants, { FLAG_DRY_RUN, FLAG_HELP, NPX } from '../../constants.mts'
66
import { commonFlags } from '../../flags.mts'
77
import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
88
import { getFlagApiRequirementsOutput } from '../../utils/output-formatting.mts'
9+
import {
10+
trackSubprocessExit,
11+
trackSubprocessStart,
12+
} from '../../utils/telemetry/integration.mts'
913

1014
import type {
1115
CliCommandConfig,
@@ -74,18 +78,26 @@ async function run(
7478

7579
process.exitCode = 1
7680

81+
// Track subprocess start.
82+
const subprocessStartTime = await trackSubprocessStart(NPX)
83+
7784
const { spawnPromise } = await shadowNpxBin(argv, { stdio: 'inherit' })
7885

86+
// Handle exit codes and signals using event-based pattern.
7987
// See https://nodejs.org/api/child_process.html#event-exit.
8088
spawnPromise.process.on(
8189
'exit',
82-
(code: string | null, signalName: NodeJS.Signals | null) => {
83-
if (signalName) {
84-
process.kill(process.pid, signalName)
85-
} else if (typeof code === 'number') {
86-
// eslint-disable-next-line n/no-process-exit
87-
process.exit(code)
88-
}
90+
(code: number | null, signalName: NodeJS.Signals | null) => {
91+
// Track subprocess exit and flush telemetry before exiting.
92+
// Use .then() to ensure telemetry completes before process.exit().
93+
void trackSubprocessExit(NPX, subprocessStartTime, code).then(() => {
94+
if (signalName) {
95+
process.kill(process.pid, signalName)
96+
} else if (typeof code === 'number') {
97+
// eslint-disable-next-line n/no-process-exit
98+
process.exit(code)
99+
}
100+
})
89101
},
90102
)
91103

src/commands/pnpm/cmd-pnpm.mts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { commonFlags } from '../../flags.mts'
77
import { filterFlags } from '../../utils/cmd.mts'
88
import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
99
import { getFlagApiRequirementsOutput } from '../../utils/output-formatting.mts'
10+
import {
11+
trackSubprocessExit,
12+
trackSubprocessStart,
13+
} from '../../utils/telemetry/integration.mts'
1014

1115
import type {
1216
CliCommandConfig,
@@ -81,10 +85,30 @@ async function run(
8185
// Filter Socket flags from argv.
8286
const filteredArgv = filterFlags(argv, config.flags)
8387

88+
// Track subprocess start.
89+
const subprocessStartTime = await trackSubprocessStart(PNPM)
90+
8491
const { spawnPromise } = await shadowPnpmBin(filteredArgv, {
8592
stdio: 'inherit',
8693
})
8794

95+
// Handle exit codes and signals using event-based pattern.
96+
// See https://nodejs.org/api/child_process.html#event-exit.
97+
spawnPromise.process.on(
98+
'exit',
99+
(code: number | null, signalName: NodeJS.Signals | null) => {
100+
// Track subprocess exit and flush telemetry before exiting.
101+
// Use .then() to ensure telemetry completes before process.exit().
102+
void trackSubprocessExit(PNPM, subprocessStartTime, code).then(() => {
103+
if (signalName) {
104+
process.kill(process.pid, signalName)
105+
} else if (typeof code === 'number') {
106+
// eslint-disable-next-line n/no-process-exit
107+
process.exit(code)
108+
}
109+
})
110+
},
111+
)
112+
88113
await spawnPromise
89-
process.exitCode = 0
90114
}

src/commands/yarn/cmd-yarn.mts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { commonFlags } from '../../flags.mts'
77
import { filterFlags } from '../../utils/cmd.mts'
88
import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
99
import { getFlagApiRequirementsOutput } from '../../utils/output-formatting.mts'
10+
import {
11+
trackSubprocessExit,
12+
trackSubprocessStart,
13+
} from '../../utils/telemetry/integration.mts'
1014

1115
import type {
1216
CliCommandConfig,
@@ -81,10 +85,32 @@ async function run(
8185
// Filter Socket flags from argv.
8286
const filteredArgv = filterFlags(argv, config.flags)
8387

88+
// Track subprocess start.
89+
const subprocessStartTime = await trackSubprocessStart(YARN)
90+
8491
const { spawnPromise } = await shadowYarnBin(filteredArgv, {
8592
stdio: 'inherit',
8693
})
8794

95+
// Handle exit codes and signals using event-based pattern.
96+
// See https://nodejs.org/api/child_process.html#event-exit.
97+
spawnPromise.process.on(
98+
'exit',
99+
(code: number | null, signalName: NodeJS.Signals | null) => {
100+
// Track subprocess exit and flush telemetry before exiting.
101+
// Use .then() to ensure telemetry completes before process.exit().
102+
void trackSubprocessExit(YARN, subprocessStartTime, code).then(() => {
103+
if (signalName) {
104+
process.kill(process.pid, signalName)
105+
} else if (typeof code === 'number') {
106+
// eslint-disable-next-line n/no-process-exit
107+
process.exit(code)
108+
}
109+
})
110+
},
111+
)
112+
88113
await spawnPromise
114+
89115
process.exitCode = 0
90116
}

0 commit comments

Comments
 (0)