Skip to content

Commit ed99ea4

Browse files
fix: better cli reporting of missing/invalid config file (#354)
1 parent 8284988 commit ed99ea4

File tree

3 files changed

+33
-31
lines changed

3 files changed

+33
-31
lines changed

packages/schema/src/cli/config.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { GUARD_FIELD_NAME, TRANSACTION_FIELD_NAME } from '@zenstackhq/sdk';
22
import fs from 'fs';
3-
import z from 'zod';
3+
import z, { ZodError } from 'zod';
44
import { fromZodError } from 'zod-validation-error';
55
import { CliError } from './cli-error';
66

@@ -20,21 +20,20 @@ export let config: ConfigType = schema.parse({});
2020
* @returns
2121
*/
2222
export function loadConfig(filename: string) {
23-
if (!fs.existsSync(filename)) {
24-
return;
25-
}
26-
27-
let content: unknown;
2823
try {
29-
content = JSON.parse(fs.readFileSync(filename, 'utf-8'));
30-
} catch {
31-
throw new CliError(`Config is not a valid JSON file: ${filename}`);
24+
const fileData = fs.readFileSync(filename, `utf-8`);
25+
const content = JSON.parse(fileData);
26+
config = schema.parse(content);
27+
} catch (err: any) {
28+
if (err?.code === `ENOENT`) {
29+
throw new CliError(`Config file could not be found: ${filename}`);
30+
}
31+
if (err instanceof SyntaxError) {
32+
throw new CliError(`Config is not a valid JSON file: ${filename}`);
33+
}
34+
if (err instanceof ZodError) {
35+
throw new CliError(`Config file ${filename} is not valid: ${fromZodError(err)}`);
36+
}
37+
throw new CliError(`Error loading config: ${filename}`);
3238
}
33-
34-
const parsed = schema.safeParse(content);
35-
if (!parsed.success) {
36-
throw new CliError(`Config file ${filename} is not valid: ${fromZodError(parsed.error)}`);
37-
}
38-
39-
config = parsed.data;
4039
}

packages/schema/src/cli/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,13 @@ export function createProgram() {
139139
// make sure config is loaded before actions run
140140
program.hook('preAction', async (_, actionCommand) => {
141141
let configFile: string | undefined = actionCommand.opts().config;
142+
142143
if (!configFile && fs.existsSync(DEFAULT_CONFIG_FILE)) {
143144
configFile = DEFAULT_CONFIG_FILE;
144145
}
145146

146147
if (configFile) {
147-
if (fs.existsSync(configFile)) {
148-
loadConfig(configFile);
149-
} else {
150-
throw new CliError(`Config file ${configFile} not found`);
151-
}
148+
loadConfig(configFile);
152149
}
153150
});
154151

packages/schema/tests/cli/config.test.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ describe('CLI Config Tests', () => {
1818
projDir = r.name;
1919
console.log(`Project dir: ${projDir}`);
2020
process.chdir(projDir);
21+
22+
fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' }));
2123
});
2224

2325
afterEach(() => {
@@ -26,7 +28,6 @@ describe('CLI Config Tests', () => {
2628
});
2729

2830
it('invalid default config', async () => {
29-
fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' }));
3031
fs.writeFileSync('zenstack.config.json', JSON.stringify({ abc: 'def' }));
3132

3233
const program = createProgram();
@@ -36,7 +37,6 @@ describe('CLI Config Tests', () => {
3637
});
3738

3839
it('valid default config empty', async () => {
39-
fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' }));
4040
fs.writeFileSync('zenstack.config.json', JSON.stringify({}));
4141

4242
const program = createProgram();
@@ -50,7 +50,6 @@ describe('CLI Config Tests', () => {
5050
});
5151

5252
it('valid default config non-empty', async () => {
53-
fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' }));
5453
fs.writeFileSync(
5554
'zenstack.config.json',
5655
JSON.stringify({ guardFieldName: 'myGuardField', transactionFieldName: 'myTransactionField' })
@@ -66,16 +65,24 @@ describe('CLI Config Tests', () => {
6665
expect(config.transactionFieldName).toBe('myTransactionField');
6766
});
6867

69-
it('config not found', async () => {
70-
fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' }));
68+
it('custom config file does not exist', async () => {
7169
const program = createProgram();
70+
const configFile = `my.config.json`;
7271
await expect(
73-
program.parseAsync(['init', '--tag', 'latest', '--config', 'my.config.json'], { from: 'user' })
74-
).rejects.toBeInstanceOf(CliError);
72+
program.parseAsync(['init', '--tag', 'latest', '--config', configFile], { from: 'user' })
73+
).rejects.toThrow(/Config file could not be found/i);
74+
});
75+
76+
it('custom config file is not json', async () => {
77+
const program = createProgram();
78+
const configFile = `my.config.json`;
79+
fs.writeFileSync(configFile, ` 😬 😬 😬`);
80+
await expect(
81+
program.parseAsync(['init', '--tag', 'latest', '--config', configFile], { from: 'user' })
82+
).rejects.toThrow(/Config is not a valid JSON file/i);
7583
});
7684

7785
it('valid custom config file', async () => {
78-
fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' }));
7986
fs.writeFileSync('my.config.json', JSON.stringify({ guardFieldName: 'myGuardField' }));
8087
const program = createProgram();
8188
await program.parseAsync(['init', '--tag', 'latest', '--config', 'my.config.json'], { from: 'user' });
@@ -88,11 +95,10 @@ describe('CLI Config Tests', () => {
8895
});
8996

9097
it('invalid custom config file', async () => {
91-
fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' }));
9298
fs.writeFileSync('my.config.json', JSON.stringify({ abc: 'def' }));
9399
const program = createProgram();
94100
await expect(
95101
program.parseAsync(['init', '--tag', 'latest', '--config', 'my.config.json'], { from: 'user' })
96-
).rejects.toBeInstanceOf(CliError);
102+
).rejects.toThrow(/Config file my.config.json is not valid/i);
97103
});
98104
});

0 commit comments

Comments
 (0)