From ed99ea42aae2cce24f23283075c18f33e309f465 Mon Sep 17 00:00:00 2001 From: Thomas Horner <3452618+potion-cellar@users.noreply.github.com> Date: Sun, 16 Apr 2023 00:00:15 -0600 Subject: [PATCH] fix: better cli reporting of missing/invalid config file (#354) --- packages/schema/src/cli/config.ts | 31 ++++++++++++------------ packages/schema/src/cli/index.ts | 7 ++---- packages/schema/tests/cli/config.test.ts | 26 ++++++++++++-------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/schema/src/cli/config.ts b/packages/schema/src/cli/config.ts index a9ac8a2b3..78cd14002 100644 --- a/packages/schema/src/cli/config.ts +++ b/packages/schema/src/cli/config.ts @@ -1,6 +1,6 @@ import { GUARD_FIELD_NAME, TRANSACTION_FIELD_NAME } from '@zenstackhq/sdk'; import fs from 'fs'; -import z from 'zod'; +import z, { ZodError } from 'zod'; import { fromZodError } from 'zod-validation-error'; import { CliError } from './cli-error'; @@ -20,21 +20,20 @@ export let config: ConfigType = schema.parse({}); * @returns */ export function loadConfig(filename: string) { - if (!fs.existsSync(filename)) { - return; - } - - let content: unknown; try { - content = JSON.parse(fs.readFileSync(filename, 'utf-8')); - } catch { - throw new CliError(`Config is not a valid JSON file: ${filename}`); + const fileData = fs.readFileSync(filename, `utf-8`); + const content = JSON.parse(fileData); + config = schema.parse(content); + } catch (err: any) { + if (err?.code === `ENOENT`) { + throw new CliError(`Config file could not be found: ${filename}`); + } + if (err instanceof SyntaxError) { + throw new CliError(`Config is not a valid JSON file: ${filename}`); + } + if (err instanceof ZodError) { + throw new CliError(`Config file ${filename} is not valid: ${fromZodError(err)}`); + } + throw new CliError(`Error loading config: ${filename}`); } - - const parsed = schema.safeParse(content); - if (!parsed.success) { - throw new CliError(`Config file ${filename} is not valid: ${fromZodError(parsed.error)}`); - } - - config = parsed.data; } diff --git a/packages/schema/src/cli/index.ts b/packages/schema/src/cli/index.ts index f9f402d16..a3b511340 100644 --- a/packages/schema/src/cli/index.ts +++ b/packages/schema/src/cli/index.ts @@ -139,16 +139,13 @@ export function createProgram() { // make sure config is loaded before actions run program.hook('preAction', async (_, actionCommand) => { let configFile: string | undefined = actionCommand.opts().config; + if (!configFile && fs.existsSync(DEFAULT_CONFIG_FILE)) { configFile = DEFAULT_CONFIG_FILE; } if (configFile) { - if (fs.existsSync(configFile)) { - loadConfig(configFile); - } else { - throw new CliError(`Config file ${configFile} not found`); - } + loadConfig(configFile); } }); diff --git a/packages/schema/tests/cli/config.test.ts b/packages/schema/tests/cli/config.test.ts index 9259d1c42..3fa522f56 100644 --- a/packages/schema/tests/cli/config.test.ts +++ b/packages/schema/tests/cli/config.test.ts @@ -18,6 +18,8 @@ describe('CLI Config Tests', () => { projDir = r.name; console.log(`Project dir: ${projDir}`); process.chdir(projDir); + + fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); }); afterEach(() => { @@ -26,7 +28,6 @@ describe('CLI Config Tests', () => { }); it('invalid default config', async () => { - fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); fs.writeFileSync('zenstack.config.json', JSON.stringify({ abc: 'def' })); const program = createProgram(); @@ -36,7 +37,6 @@ describe('CLI Config Tests', () => { }); it('valid default config empty', async () => { - fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); fs.writeFileSync('zenstack.config.json', JSON.stringify({})); const program = createProgram(); @@ -50,7 +50,6 @@ describe('CLI Config Tests', () => { }); it('valid default config non-empty', async () => { - fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); fs.writeFileSync( 'zenstack.config.json', JSON.stringify({ guardFieldName: 'myGuardField', transactionFieldName: 'myTransactionField' }) @@ -66,16 +65,24 @@ describe('CLI Config Tests', () => { expect(config.transactionFieldName).toBe('myTransactionField'); }); - it('config not found', async () => { - fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); + it('custom config file does not exist', async () => { const program = createProgram(); + const configFile = `my.config.json`; await expect( - program.parseAsync(['init', '--tag', 'latest', '--config', 'my.config.json'], { from: 'user' }) - ).rejects.toBeInstanceOf(CliError); + program.parseAsync(['init', '--tag', 'latest', '--config', configFile], { from: 'user' }) + ).rejects.toThrow(/Config file could not be found/i); + }); + + it('custom config file is not json', async () => { + const program = createProgram(); + const configFile = `my.config.json`; + fs.writeFileSync(configFile, ` 😬 😬 😬`); + await expect( + program.parseAsync(['init', '--tag', 'latest', '--config', configFile], { from: 'user' }) + ).rejects.toThrow(/Config is not a valid JSON file/i); }); it('valid custom config file', async () => { - fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); fs.writeFileSync('my.config.json', JSON.stringify({ guardFieldName: 'myGuardField' })); const program = createProgram(); await program.parseAsync(['init', '--tag', 'latest', '--config', 'my.config.json'], { from: 'user' }); @@ -88,11 +95,10 @@ describe('CLI Config Tests', () => { }); it('invalid custom config file', async () => { - fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); fs.writeFileSync('my.config.json', JSON.stringify({ abc: 'def' })); const program = createProgram(); await expect( program.parseAsync(['init', '--tag', 'latest', '--config', 'my.config.json'], { from: 'user' }) - ).rejects.toBeInstanceOf(CliError); + ).rejects.toThrow(/Config file my.config.json is not valid/i); }); });