Skip to content

Commit 37f4a8c

Browse files
committed
fix(cli): align utils/ miscellaneous error messages with 4-ingredient strategy
Final PR in the error-message series. Covers everything not already touched by #1255-#1259: utils/basics, utils/config, utils/fs, utils/git, utils/npm, utils/promise, utils/terminal, and the flags module at the root of the CLI tree. Sources: - flags.mts: 2 throws (--max-old-space-size, --max-semi-space-size) — name the flag, show the offending value, suggest a concrete megabyte value. - utils/config.mts: 1 throw (SOCKET_CLI_CONFIG base64 decode) — explains the replacement-character symptom and how to re-encode. - utils/basics/vfs-extract.mts: 4 throws (SEA VFS extraction for Python + security tools) — name the missing paths, the exit codes, and point at the "rebuild the SEA binary" fix. - utils/promise/queue.mts: 1 throw (PromiseQueue concurrency guard) — show the offending value and suggest 4/8. - utils/npm/spec.mts: 1 throw (PURL conversion) — show the input, state what a valid npm spec looks like. - utils/git/operations.mts: 1 throw (git-not-on-PATH) — point at install and the local-path env-var override. - utils/git/gitlab-provider.mts: 2 throws (no token, PR creation after retries) — name the token scope, the retry count, the repo/head refs. - utils/fs/path-resolve.mts: 1 throw (npm path-walk iteration cap) — name the start path, current directory, and what usually causes the cycle (symlinks). - utils/terminal/iocraft.mts: 1 throw (native-module load failure) — show the underlying error and the offending platform/arch triple. Skipped (already informative): - github-provider.mts pass-through errors (forward inner CResult cause/message) - gitlab-provider.mts try/catch wrappers that call formatErrorWithDetail (inner error has context) - 'process.exit called' sentinel throws in npm/pnpm/yarn/with- subcommands paths (test harness re-raise markers, not user-facing) Tests updated: - test/unit/utils/promise/queue.test.mts (2 assertions) - test/unit/utils/npm/spec.test.mts (2 assertions) - test/unit/utils/git/gitlab-provider.test.mts (3 assertions) Full suite (343 files / 5225 tests) passes. Completes the series: #1255 (commands/) → #1256 (utils/dlx/) → #1257 (utils/update + utils/command/) → #1258 (env/ + constants/) → #1259 (test/) → this.
1 parent fc5591f commit 37f4a8c

12 files changed

Lines changed: 35 additions & 22 deletions

File tree

packages/cli/src/flags.mts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ function getRawSpaceSizeFlags(): RawSpaceSizeFlags {
6464
// Validate numeric flags (should be guaranteed by meow type='number', but defensive).
6565
if (Number.isNaN(maxOldSpaceSize) || maxOldSpaceSize < 0) {
6666
throw new Error(
67-
`Invalid value for --max-old-space-size: ${cli.flags['maxOldSpaceSize']}`,
67+
`--max-old-space-size must be a non-negative integer in megabytes (saw: "${cli.flags['maxOldSpaceSize']}"); pass a whole number like --max-old-space-size=4096 for 4GB`,
6868
)
6969
}
7070
if (Number.isNaN(maxSemiSpaceSize) || maxSemiSpaceSize < 0) {
7171
throw new Error(
72-
`Invalid value for --max-semi-space-size: ${cli.flags['maxSemiSpaceSize']}`,
72+
`--max-semi-space-size must be a non-negative integer in megabytes (saw: "${cli.flags['maxSemiSpaceSize']}"); pass a whole number like --max-semi-space-size=128`,
7373
)
7474
}
7575

packages/cli/src/utils/basics/vfs-extract.mts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export async function extractBasicsTools(
176176
const missingTools = tools.filter(t => !extractedPaths[t])
177177
if (missingTools.length) {
178178
throw new Error(
179-
`Failed to extract all basics tools. Missing: ${missingTools.join(', ')}`,
179+
`socket-basics VFS extraction returned ${Object.keys(extractedPaths).length}/${tools.length} tools (missing: ${missingTools.join(', ')}); the SEA bundle is incomplete — rebuild with all basics tools included`,
180180
)
181181
}
182182

@@ -186,7 +186,9 @@ export async function extractBasicsTools(
186186
const pythonExe = isPlatWin ? 'python3.exe' : 'python3'
187187
const pythonDir = extractedPaths['python']
188188
if (!pythonDir) {
189-
throw new Error('Python extraction path not found')
189+
throw new Error(
190+
`extractedPaths.python is undefined after VFS extraction (expected a directory path); the basics SEA bundle is missing Python — rebuild the SEA binary`,
191+
)
190192
}
191193
const pythonPath = normalizePath(path.join(pythonDir, 'bin', pythonExe))
192194

@@ -197,7 +199,7 @@ export async function extractBasicsTools(
197199

198200
if (!validateResult || validateResult.code !== 0) {
199201
throw new Error(
200-
`Python validation failed: ${validateResult?.stderr || 'Unable to execute Python'}`,
202+
`extracted Python at ${pythonPath} failed to run with exit code ${validateResult?.code ?? 'null'} (stderr: ${validateResult?.stderr || '<none>'}); the extracted binary may be corrupt or missing a shared lib — rebuild the SEA binary`,
201203
)
202204
}
203205

@@ -218,7 +220,7 @@ export async function extractBasicsTools(
218220

219221
if (!toolValidateResult || toolValidateResult.code !== 0) {
220222
throw new Error(
221-
`${tool} validation failed: ${toolValidateResult?.stderr || `Unable to execute ${tool}`}`,
223+
`extracted ${tool} at ${toolPath} failed to run with exit code ${toolValidateResult?.code ?? 'null'} (stderr: ${toolValidateResult?.stderr || '<none>'}); the extracted binary may be corrupt or missing a shared lib — rebuild the SEA binary`,
222224
)
223225
}
224226

packages/cli/src/utils/config.mts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ function getConfigValues(retryCount = 0): LocalConfig {
140140
const decoded = Buffer.from(rawString, 'base64').toString('utf8')
141141
// Check for invalid UTF-8 sequences (replacement character).
142142
if (decoded.includes('\ufffd')) {
143-
throw new Error('Invalid UTF-8 in base64-encoded config')
143+
throw new Error(
144+
`SOCKET_CLI_CONFIG contains invalid UTF-8 after base64-decode (replacement-character in output); the env var may have been truncated or double-encoded — re-export it with \`echo '{...}' | base64\``,
145+
)
144146
}
145147
const parsed = JSON.parse(decoded)
146148
// Only copy supported config keys to prevent prototype pollution.

packages/cli/src/utils/fs/path-resolve.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function findNpmDirPathSync(npmBinPath: string): string | undefined {
5555
while (true) {
5656
if (iterations >= MAX_ITERATIONS) {
5757
throw new Error(
58-
`path traversal exceeded maximum iterations of ${MAX_ITERATIONS}`,
58+
`npm path resolution walked ${MAX_ITERATIONS} directories without finding lib/node_modules/npm starting from "${npmBinPath}" (current: "${thePath}"); check for a circular symlink or corrupt node install`,
5959
)
6060
}
6161
iterations += 1

packages/cli/src/utils/git/gitlab-provider.mts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class GitLabProvider implements PrProvider {
9797
}
9898

9999
throw new Error(
100-
`Failed to create merge request after ${retries} attempts: ${owner}/${repo}#${head}`,
100+
`GitLab API rejected createMergeRequest for ${owner}/${repo} (head="${head}") after ${retries} retries with exponential backoff; check GL_TOKEN permissions (needs api scope), the target branch exists, and GitLab is reachable`,
101101
)
102102
}
103103

@@ -326,6 +326,6 @@ function getGitLabToken(): string {
326326
}
327327

328328
throw new Error(
329-
'GitLab token not found. Set GITLAB_TOKEN environment variable.',
329+
`GitLab access requires a token but process.env.GITLAB_TOKEN is not set; create a personal access token with the \`api\` scope at https://gitlab.com/-/user_settings/personal_access_tokens and export GITLAB_TOKEN=<token>`,
330330
)
331331
}

packages/cli/src/utils/git/operations.mts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ async function getGitPath(): Promise<string> {
5555
if (!_gitPath) {
5656
const result = await whichReal('git', { nothrow: true })
5757
if (!result || Array.isArray(result)) {
58-
throw new Error('git not found in PATH')
58+
throw new Error(
59+
`git executable not found on PATH (whichReal returned ${Array.isArray(result) ? 'multiple matches' : 'null'}); install git and ensure it is on PATH, or set SOCKET_CLI_GIT_PATH to point at a specific binary`,
60+
)
5961
}
6062
_gitPath = result
6163
}

packages/cli/src/utils/npm/spec.mts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ export function safeNpmSpecToPurl(pkgSpec: string): string | undefined {
184184
export function npmSpecToPurl(pkgSpec: string): string {
185185
const purl = safeNpmSpecToPurl(pkgSpec)
186186
if (!purl) {
187-
throw new Error(`Failed to convert ${NPM} spec to PURL: ${pkgSpec}`)
187+
throw new Error(
188+
`cannot convert npm spec "${pkgSpec}" to PURL (safeNpmSpecToPurl returned null); valid npm specs look like "lodash@4.17.21" or "@scope/pkg@^1.0.0" — check the spec for typos or unsupported forms`,
189+
)
188190
}
189191
return purl
190192
}

packages/cli/src/utils/promise/queue.mts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export class PromiseQueue {
2727
this.maxQueueLength = maxQueueLength
2828
}
2929
if (maxConcurrency < 1) {
30-
throw new Error('maxConcurrency must be at least 1')
30+
throw new Error(
31+
`PromiseQueue maxConcurrency must be >= 1 (saw: ${maxConcurrency}); pass a positive integer like 4 or 8`,
32+
)
3133
}
3234
}
3335

packages/cli/src/utils/terminal/iocraft.mts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ function getIocraft(): typeof iocraft {
2727
iocraftInstance = loaded.default || loaded
2828
} catch (e) {
2929
throw new Error(
30-
`Failed to load iocraft native module: ${e}\n` +
31-
`Make sure @socketaddon/iocraft is installed and your platform is supported.`,
30+
`could not load @socketaddon/iocraft native module (${(e as Error).message}); reinstall socket-cli to pull the prebuilt for your platform, or check that your platform (${process.platform}-${process.arch}) has a published prebuilt`,
3231
)
3332
}
3433
}

packages/cli/test/unit/utils/git/gitlab-provider.test.mts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('git/gitlab-provider', () => {
7272
it('throws error when no token available', () => {
7373
delete process.env['GITLAB_TOKEN']
7474
expect(() => new GitLabProvider()).toThrow(
75-
'GitLab token not found. Set GITLAB_TOKEN environment variable.',
75+
/GitLab access requires a token but process\.env\.GITLAB_TOKEN is not set/,
7676
)
7777
})
7878
})
@@ -193,7 +193,9 @@ describe('git/gitlab-provider', () => {
193193
retries: 2,
194194
title: 'Test',
195195
}),
196-
).rejects.toThrow('Failed to create merge request after 2 attempts')
196+
).rejects.toThrow(
197+
/GitLab API rejected createMergeRequest for owner\/repo .*after 2 retries/,
198+
)
197199
})
198200

199201
it('does not retry on 400 errors', async () => {
@@ -212,7 +214,9 @@ describe('git/gitlab-provider', () => {
212214
retries: 3,
213215
title: 'Test',
214216
}),
215-
).rejects.toThrow('Failed to create merge request after 3 attempts')
217+
).rejects.toThrow(
218+
/GitLab API rejected createMergeRequest for owner\/repo .*after 3 retries/,
219+
)
216220

217221
expect(mockCreate).toHaveBeenCalledTimes(1)
218222
})

0 commit comments

Comments
 (0)