diff --git a/packages/cli-kit/src/public/node/environments.test.ts b/packages/cli-kit/src/public/node/environments.test.ts index e646a8a80ca..332ef269d9c 100644 --- a/packages/cli-kit/src/public/node/environments.test.ts +++ b/packages/cli-kit/src/public/node/environments.test.ts @@ -1,5 +1,7 @@ import * as environments from './environments.js' import {encodeToml as tomlEncode} from './toml/codec.js' +import {TomlFileError} from './toml/toml-file.js' +import {shouldReportErrorAsUnexpected} from './error.js' import {inTemporaryDirectory, writeFile} from './fs.js' import {joinPath} from './path.js' import {mockAndCaptureOutput} from './testing/output.js' @@ -83,6 +85,21 @@ describe('loading environments', async () => { }) }) + test('malformed environment files fail as expected user errors', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const filePath = joinPath(tmpDir, fileName) + await writeFile(filePath, 'environments = [invalid') + + // When + const error = await environments.loadEnvironment('environment1', fileName, {from: tmpDir}).catch((err) => err) + + // Then + expect(error).toBeInstanceOf(TomlFileError) + expect(shouldReportErrorAsUnexpected(error)).toBe(false) + }) + }) + test('suppresses warning when no environments file exists with silent option', async () => { await inTemporaryDirectory(async (tmpDir) => { // Given diff --git a/packages/cli-kit/src/public/node/toml/toml-file.test.ts b/packages/cli-kit/src/public/node/toml/toml-file.test.ts index 2edcf00fb11..9e507c60df0 100644 --- a/packages/cli-kit/src/public/node/toml/toml-file.test.ts +++ b/packages/cli-kit/src/public/node/toml/toml-file.test.ts @@ -1,4 +1,5 @@ import {TomlFile, TomlFileError} from './toml-file.js' +import {shouldReportErrorAsUnexpected} from '../error.js' import {writeFile, readFile, inTemporaryDirectory} from '../fs.js' import {joinPath} from '../path.js' import {describe, expect, test} from 'vitest' @@ -38,6 +39,18 @@ describe('TomlFile', () => { }) }) + test('classifies invalid TOML as an expected user error', async () => { + await inTemporaryDirectory(async (dir) => { + const path = joinPath(dir, 'bad.toml') + await writeFile(path, 'name = [invalid') + + const error = await TomlFile.read(path).catch((err: unknown) => err) + + expect(error).toBeInstanceOf(TomlFileError) + expect(shouldReportErrorAsUnexpected(error)).toBe(false) + }) + }) + test('throws TomlFileError if file does not exist', async () => { await expect(TomlFile.read('/nonexistent/path/test.toml')).rejects.toThrow(TomlFileError) }) diff --git a/packages/cli-kit/src/public/node/toml/toml-file.ts b/packages/cli-kit/src/public/node/toml/toml-file.ts index 2100fd4ea45..a2ee81b3236 100644 --- a/packages/cli-kit/src/public/node/toml/toml-file.ts +++ b/packages/cli-kit/src/public/node/toml/toml-file.ts @@ -1,4 +1,5 @@ import {JsonMapType, decodeToml, encodeToml} from './codec.js' +import {AbortError} from '../error.js' import {fileExists, readFile, writeFile} from '../fs.js' import {updateTomlValues} from '@shopify/toml-patch' @@ -6,9 +7,9 @@ type TomlPatchValue = string | number | boolean | undefined | (string | number | /** * An error on a TOML file — missing or malformed. - * Extends Error so it can be thrown. Carries path and a clean message suitable for JSON output. + * Carries path and a clean message suitable for JSON output. */ -export class TomlFileError extends Error { +export class TomlFileError extends AbortError { readonly path: string constructor(path: string, message: string) {