Skip to content

Commit 2483a93

Browse files
committed
feat: adds resolveFilepath to @app-config/node for resolving $exec files relatively
1 parent dcaae97 commit 2483a93

File tree

7 files changed

+56
-16
lines changed

7 files changed

+56
-16
lines changed

app-config-exec/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"dependencies": {
3333
"@app-config/core": "^2.2.0",
3434
"@app-config/extension-utils": "^2.2.0",
35+
"@app-config/node": "^2.2.0",
3536
"@app-config/utils": "^2.2.0"
3637
},
3738
"devDependencies": {

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { defaultEnvExtensions, defaultExtensions, loadUnvalidatedConfig } from '@app-config/main';
22
import { isWindows } from '@app-config/utils';
3+
import { withTempFiles } from '@app-config/test-utils';
4+
import { FileSource } from '@app-config/node';
35
import execParsingExtension from '.';
46

57
const defaultOptions = {
@@ -129,4 +131,31 @@ describe('execParsingExtension', () => {
129131

130132
await expect(action()).rejects.toThrow();
131133
});
134+
135+
it('reads from command as root level string', async () => {
136+
process.env.APP_CONFIG = JSON.stringify({
137+
$exec: 'echo test123',
138+
});
139+
140+
const { fullConfig } = await loadUnvalidatedConfig(defaultOptions);
141+
142+
expect(fullConfig).toEqual('test123');
143+
});
144+
145+
it('loads file relative to app-config', () =>
146+
withTempFiles(
147+
{
148+
'config.yml': `
149+
$exec: node ./foo.js
150+
`,
151+
'foo.js': `
152+
console.log("foo bar");
153+
`,
154+
},
155+
async (inDir) => {
156+
const source = new FileSource(inDir('config.yml'));
157+
158+
expect(await source.readToJSON([execParsingExtension()])).toEqual('foo bar');
159+
},
160+
));
132161
});

app-config-exec/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Json } from '@app-config/utils';
22
import { ParsingExtension, parseRawString, guessFileType, AppConfigError } from '@app-config/core';
33
import { forKey, validateOptions } from '@app-config/extension-utils';
4+
import { resolveFilepath } from '@app-config/node';
45
import { exec } from 'child_process';
6+
import { dirname } from 'path';
57
import { promisify } from 'util';
68

79
const execAsync = promisify(exec);
@@ -30,7 +32,7 @@ function execParsingExtension(): ParsingExtension {
3032
.addBoolean('parseOutput', {}, false)
3133
.addBoolean('trimWhitespace', {}, false),
3234
),
33-
(value) => async (parse) => {
35+
(value) => async (parse, _, context) => {
3436
let options;
3537

3638
if (typeof value === 'string') {
@@ -47,7 +49,8 @@ function execParsingExtension(): ParsingExtension {
4749
} = options;
4850

4951
try {
50-
const { stdout, stderr } = await execAsync(command);
52+
const dir = resolveFilepath(context, '.');
53+
const { stdout, stderr } = await execAsync(command, { cwd: dir });
5154

5255
if (failOnStderr && stderr) {
5356
throw new ExecError(`$exec command "${command}" produced stderr: ${stderr}`);

app-config-exec/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
{ "path": "../app-config-main" },
1212
{ "path": "../app-config-utils" },
1313
{ "path": "../app-config-core" },
14+
{ "path": "../app-config-node" },
1415
{ "path": "../app-config-extension-utils" }
1516
]
1617
}

app-config-extensions/src/index.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import {
2020
currentEnvironment,
2121
defaultAliases,
22+
resolveFilepath,
2223
EnvironmentAliases,
2324
FileSource,
2425
} from '@app-config/node';
@@ -343,18 +344,7 @@ function fileReferenceDirective(keyName: string, meta: ParsedValueMetadata): Par
343344
},
344345
(value) => async (_, __, context, extensions) => {
345346
const retrieveFile = async (filepath: string, subselector?: string, isOptional = false) => {
346-
let resolvedPath = filepath;
347-
348-
// resolve filepaths that are relative to the current FileSource
349-
if (!isAbsolute(filepath) && context instanceof FileSource) {
350-
resolvedPath = join(dirname(context.filePath), filepath);
351-
352-
if (resolve(context.filePath) === resolvedPath) {
353-
throw new AppConfigError(
354-
`A ${keyName} directive resolved to it's own file (${resolvedPath}). Please use $extendsSelf instead.`,
355-
);
356-
}
357-
}
347+
const resolvedPath = resolveFilepath(context, filepath);
358348

359349
logger.verbose(`Loading file for ${keyName}: ${resolvedPath}`);
360350

app-config-node/src/file-source.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { resolve } from 'path';
1+
import { isAbsolute, join, dirname, resolve } from 'path';
22
import { readFile, pathExists } from 'fs-extra';
33
import {
44
filePathAssumedType,
55
ConfigSource,
66
FileType,
77
ParsedValue,
88
ParsingExtension,
9+
AppConfigError,
910
NotFoundError,
1011
} from '@app-config/core';
1112
import { logger } from '@app-config/logging';
@@ -95,3 +96,18 @@ export class FlexibleFileSource extends ConfigSource {
9596
return source.read(extensions);
9697
}
9798
}
99+
100+
export function resolveFilepath(context: ConfigSource, filepath: string) {
101+
let resolvedPath = filepath;
102+
103+
// resolve filepaths that are relative to the current FileSource
104+
if (!isAbsolute(filepath) && context instanceof FileSource) {
105+
resolvedPath = join(dirname(context.filePath), filepath);
106+
107+
if (resolve(context.filePath) === resolvedPath) {
108+
throw new AppConfigError(`An extension tried to resolve to it's own file (${resolvedPath}).`);
109+
}
110+
}
111+
112+
return resolvedPath;
113+
}

app-config-node/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { FileSource, FlexibleFileSource } from './file-source';
1+
export { FileSource, FlexibleFileSource, resolveFilepath } from './file-source';
22
export { currentEnvironment, defaultAliases, EnvironmentAliases } from './environment';
33
export { EnvironmentSource } from './environment-source';
44
export { promptUser, promptUserWithRetry, consumeStdin } from './prompts';

0 commit comments

Comments
 (0)