Skip to content

Commit

Permalink
fix: better cli reporting of missing/invalid config file (#354)
Browse files Browse the repository at this point in the history
  • Loading branch information
potion-cellar committed Apr 16, 2023
1 parent 8284988 commit ed99ea4
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 31 deletions.
31 changes: 15 additions & 16 deletions packages/schema/src/cli/config.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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;
}
7 changes: 2 additions & 5 deletions packages/schema/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});

Expand Down
26 changes: 16 additions & 10 deletions packages/schema/tests/cli/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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' })
Expand All @@ -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' });
Expand All @@ -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);
});
});

0 comments on commit ed99ea4

Please sign in to comment.