Skip to content

Commit 7be696d

Browse files
committed
feat: uses extension-utils
1 parent 73707d8 commit 7be696d

File tree

2 files changed

+62
-64
lines changed

2 files changed

+62
-64
lines changed

app-config-exec/src/index.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ describe('execParsingExtension', () => {
106106
await expect(action()).rejects.toThrow();
107107
});
108108

109+
it('fails if options dont include command', async () => {
110+
process.env.APP_CONFIG = JSON.stringify({
111+
$exec: {},
112+
});
113+
114+
await expect(loadUnvalidatedConfig(defaultOptions)).rejects.toThrow();
115+
});
116+
109117
it('invalid command fails', async () => {
110118
process.env.APP_CONFIG = JSON.stringify({
111119
$exec: { command: 'non-existing-command' },

app-config-exec/src/index.ts

Lines changed: 54 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Json } from '@app-config/utils';
2-
import { ParsingExtension, parseRawString, guessFileType } from '@app-config/core';
2+
import { ParsingExtension, parseRawString, guessFileType, AppConfigError } from '@app-config/core';
3+
import { forKey, validateOptions } from '@app-config/extension-utils';
34
import { exec } from 'child_process';
45
import { promisify } from 'util';
56

@@ -12,83 +13,72 @@ export interface Options {
1213
trimWhitespace: boolean;
1314
}
1415

15-
class ExecError extends Error {
16+
class ExecError extends AppConfigError {
1617
name = 'ExecError';
1718
}
1819

19-
function parseOptions(parsed: Json): Options {
20-
if (typeof parsed === 'string') {
21-
return { command: parsed, failOnStderr: false, parseOutput: false, trimWhitespace: true };
22-
}
23-
24-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
25-
const { command, failOnStderr = false, parseOutput = false, trimWhitespace = true } = parsed;
26-
27-
if (typeof command !== 'string') {
28-
throw new ExecError('$exec requires a "command" option');
29-
}
30-
31-
if (typeof failOnStderr !== 'boolean') {
32-
throw new ExecError('$exec "failOnStderr" option must be a boolean');
33-
}
34-
35-
if (typeof parseOutput !== 'boolean') {
36-
throw new ExecError('$exec "parseString" option must be a boolean');
37-
}
38-
39-
if (typeof trimWhitespace !== 'boolean') {
40-
throw new ExecError('$exec "trimWhitespace" option must be a boolean');
41-
}
42-
43-
return { command, failOnStderr, parseOutput, trimWhitespace };
44-
}
45-
46-
throw new ExecError('$exec must be a string, or object with options');
47-
}
48-
4920
function execParsingExtension(): ParsingExtension {
50-
return (value, [_, objectKey]) => {
51-
if (objectKey !== '$exec') {
52-
return false;
53-
}
21+
return forKey(
22+
'$exec',
23+
validateOptions(
24+
(SchemaBuilder) =>
25+
SchemaBuilder.oneOf(
26+
SchemaBuilder.stringSchema(),
27+
SchemaBuilder.emptySchema()
28+
.addString('command')
29+
.addBoolean('failOnStderr', {}, false)
30+
.addBoolean('parseOutput', {}, false)
31+
.addBoolean('trimWhitespace', {}, false),
32+
),
33+
(value) => async (parse) => {
34+
let options;
35+
36+
if (typeof value === 'string') {
37+
options = { command: value };
38+
} else {
39+
options = value;
40+
}
5441

55-
return async (parse) => {
56-
const parsed = await parse(value).then((v) => v.toJSON());
42+
const {
43+
command,
44+
failOnStderr = false,
45+
parseOutput = false,
46+
trimWhitespace = true,
47+
} = options;
5748

58-
const { command, failOnStderr, parseOutput, trimWhitespace } = parseOptions(parsed);
49+
try {
50+
const { stdout, stderr } = await execAsync(command);
5951

60-
try {
61-
const { stdout, stderr } = await execAsync(command);
52+
if (failOnStderr && stderr) {
53+
throw new ExecError(`$exec command "${command}" produced stderr: ${stderr}`);
54+
}
6255

63-
if (failOnStderr && stderr) {
64-
throw new ExecError(`$exec command "${command}" produced stderr: ${stderr}`);
65-
}
56+
let result: Json = stdout;
6657

67-
let result: Json = stdout;
58+
if (trimWhitespace) {
59+
result = stdout.trim();
60+
}
6861

69-
if (trimWhitespace) {
70-
result = stdout.trim();
71-
}
62+
if (parseOutput) {
63+
const fileType = await guessFileType(stdout);
64+
result = await parseRawString(stdout, fileType);
65+
}
7266

73-
if (parseOutput) {
74-
const fileType = await guessFileType(stdout);
75-
result = await parseRawString(stdout, fileType);
76-
}
67+
return parse(result, { shouldFlatten: true });
68+
} catch (err: unknown) {
69+
if (!isError(err)) {
70+
throw err;
71+
}
7772

78-
return parse(result, { shouldFlatten: true });
79-
} catch (err: unknown) {
80-
if (!isError(err)) {
81-
throw err;
82-
}
73+
if (err instanceof ExecError) {
74+
throw err;
75+
}
8376

84-
if (err instanceof ExecError) {
85-
throw err;
77+
throw new ExecError(`$exec command "${command}" failed with error:\n${err.message}`);
8678
}
87-
88-
throw new ExecError(`$exec command "${command}" failed with error:\n${err.message}`);
89-
}
90-
};
91-
};
79+
},
80+
),
81+
);
9282
}
9383

9484
/**

0 commit comments

Comments
 (0)