diff --git a/cli/src/utils.ts b/cli/src/utils.ts index 45eedfb8dd..974b081452 100644 --- a/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -305,7 +305,10 @@ function readPackageJson(f: string = findRoot(cwd()), file: string | undefined = ? `Package.json at ${packageJsonPath} does not exist` : `No package.json found at ${packageJsonPath}. Run this command from your project root (the folder that contains package.json), or pass --package-json to point at it (for example in a monorepo).` log.error(message) - throw new Error(message) + // Expected user error, not a crash: throw a CliUserError so error tracking + // skips it. Keep the thrown message constant (the path goes in context) so + // it fingerprints one issue instead of one per absolute path. + throw new CliUserError('package.json not found. Run this command from your project root, or pass --package-json .', { packageJsonPath }) } const packageJson = readFileSync(packageJsonPath) return JSON.parse(packageJson as any) @@ -498,7 +501,9 @@ export async function getAllPackagesDependencies(f: string = findRoot(cwd()), fi if (!existsSync(file)) { const message = `Package.json at ${file} does not exist` log.error(message) - throw new Error(message) + // Expected user error, not a crash: constant thrown message, path in + // context, so error tracking skips it and does not fingerprint per path. + throw new CliUserError('package.json not found. Run this command from your project root, or pass --package-json .', { packageJsonPath: file }) } } } @@ -573,7 +578,9 @@ export async function getDeclaredPackageVersionMap(f: string = findRoot(cwd()), if (!existsSync(file)) { const message = `Package.json at ${file} does not exist` log.error(message) - throw new Error(message) + // Expected user error, not a crash: constant thrown message, path in + // context, so error tracking skips it and does not fingerprint per path. + throw new CliUserError('package.json not found. Run this command from your project root, or pass --package-json .', { packageJsonPath: file }) } } } diff --git a/cli/test/test-package-json-guard.mjs b/cli/test/test-package-json-guard.mjs index 370efd918a..ae60c0552b 100644 --- a/cli/test/test-package-json-guard.mjs +++ b/cli/test/test-package-json-guard.mjs @@ -23,17 +23,21 @@ function makeDir(name) { // Regression: running `capgo init` (and doctor) from a directory without a // package.json used to escape as a raw Node ENOENT stack trace. readPackageJson -// must now guard the default path and surface an actionable message. -await test('getBundleVersion throws a helpful message when package.json is missing', async () => { +// must now guard the default path and throw a CliUserError with a constant, +// actionable message, so error tracking skips it and does not open one issue +// per absolute path. +await test('getBundleVersion throws a CliUserError when package.json is missing', async () => { const dir = makeDir('missing-pkg') try { assert.throws( () => getBundleVersion(dir), (error) => { assert.ok(!(error instanceof Error && 'code' in error && error.code === 'ENOENT'), 'should not surface a raw ENOENT') - assert.match(error.message, /No package\.json found at/) - assert.match(error.message, /project root/) - assert.match(error.message, /--package-json/) + assert.equal(error.name, 'CliUserError') + // Constant, actionable message (no interpolated path) so error tracking + // fingerprints one issue; the path travels in context instead. + assert.equal(error.message, 'package.json not found. Run this command from your project root, or pass --package-json .') + assert.match(error.context.packageJsonPath, /package\.json$/) return true }, ) @@ -43,14 +47,16 @@ await test('getBundleVersion throws a helpful message when package.json is missi } }) -await test('getPackageScripts throws the explicit-path message when a supplied package.json is missing', async () => { +await test('getPackageScripts throws a CliUserError with the path in context for an explicit package.json', async () => { const dir = makeDir('missing-explicit-pkg') try { const missing = join(dir, 'package.json') assert.throws( () => getPackageScripts(undefined, missing), (error) => { - assert.match(error.message, /Package\.json at .*package\.json does not exist/) + assert.equal(error.name, 'CliUserError') + assert.equal(error.message, 'package.json not found. Run this command from your project root, or pass --package-json .') + assert.equal(error.context.packageJsonPath, missing) return true }, )