2020-08-04 05:14:13 +00:00
|
|
|
import { InvalidArgumentError, NotFoundError, NotImplementedError } from '@apextoaster/js-utils';
|
2020-06-30 13:08:03 +00:00
|
|
|
import { SAFE_SCHEMA, safeLoad, Schema, Type as YamlType } from 'js-yaml';
|
2019-11-13 14:01:51 +00:00
|
|
|
|
2020-08-04 05:14:13 +00:00
|
|
|
export interface ReaderOptions {
|
2020-07-29 13:23:37 +00:00
|
|
|
encoding: string;
|
|
|
|
}
|
|
|
|
|
2020-08-04 05:14:13 +00:00
|
|
|
export type IncludeReader = (path: string, options: ReaderOptions) => string;
|
2020-07-09 11:40:08 +00:00
|
|
|
|
2020-08-04 05:14:13 +00:00
|
|
|
export interface IncludeOptions {
|
2020-07-01 00:34:02 +00:00
|
|
|
exists: (path: string) => boolean;
|
2020-08-04 05:14:13 +00:00
|
|
|
join: (...path: Array<string>) => string;
|
2020-07-09 11:40:08 +00:00
|
|
|
read: IncludeReader;
|
2020-07-01 00:34:02 +00:00
|
|
|
resolve: (path: string) => string;
|
2020-06-30 13:08:03 +00:00
|
|
|
schema: Schema;
|
|
|
|
}
|
|
|
|
|
2020-06-29 23:56:08 +00:00
|
|
|
/**
|
|
|
|
* The schema to be used for included files. This is necessary to work around circular dependency errors.
|
2020-08-01 15:57:05 +00:00
|
|
|
*
|
|
|
|
* @public
|
2020-06-29 23:56:08 +00:00
|
|
|
*/
|
2020-08-04 05:14:13 +00:00
|
|
|
export const includeOptions: IncludeOptions = {
|
2020-06-30 13:08:03 +00:00
|
|
|
exists: (path: string) => false,
|
2020-08-04 05:14:13 +00:00
|
|
|
join: (...path: Array<string>) => {
|
|
|
|
throw new NotImplementedError('join stub');
|
|
|
|
},
|
|
|
|
read: (path: string, encoding: ReaderOptions) => {
|
|
|
|
throw new NotImplementedError('read stub');
|
2020-06-30 13:08:03 +00:00
|
|
|
},
|
|
|
|
resolve: (path: string) => {
|
2020-08-04 05:14:13 +00:00
|
|
|
throw new NotImplementedError('resolve stub');
|
2020-06-30 13:08:03 +00:00
|
|
|
},
|
2019-11-13 14:01:51 +00:00
|
|
|
schema: SAFE_SCHEMA,
|
|
|
|
};
|
|
|
|
|
2020-08-01 15:57:05 +00:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2019-11-13 14:01:51 +00:00
|
|
|
export const includeType = new YamlType('!include', {
|
|
|
|
kind: 'scalar',
|
|
|
|
resolve(path: string) {
|
|
|
|
try {
|
|
|
|
const canonical = resolvePath(path);
|
|
|
|
// throws in node 11+
|
2020-08-04 05:14:13 +00:00
|
|
|
if (includeOptions.exists(canonical)) {
|
2019-11-13 14:01:51 +00:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
throw new NotFoundError('included file does not exist');
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
throw new NotFoundError('included file does not exist', err);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
construct(path: string): unknown {
|
|
|
|
try {
|
2020-08-04 05:14:13 +00:00
|
|
|
return safeLoad(includeOptions.read(resolvePath(path), {
|
2019-11-13 14:01:51 +00:00
|
|
|
encoding: 'utf-8',
|
|
|
|
}), {
|
2020-08-04 05:14:13 +00:00
|
|
|
schema: includeOptions.schema,
|
2019-11-13 14:01:51 +00:00
|
|
|
});
|
|
|
|
} catch (err) {
|
2019-11-13 14:20:34 +00:00
|
|
|
throw new InvalidArgumentError('error including file', err);
|
2019-11-13 14:01:51 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2020-08-04 05:14:13 +00:00
|
|
|
/**
|
|
|
|
* @todo take root parameter instead of __dirname
|
|
|
|
*/
|
2019-11-13 14:01:51 +00:00
|
|
|
export function resolvePath(path: string): string {
|
|
|
|
if (path[0] === '.') {
|
2020-08-04 05:14:13 +00:00
|
|
|
return includeOptions.resolve(includeOptions.join(__dirname, path));
|
2019-11-13 14:01:51 +00:00
|
|
|
} else {
|
2020-08-04 05:14:13 +00:00
|
|
|
return includeOptions.resolve(path);
|
2019-11-13 14:01:51 +00:00
|
|
|
}
|
|
|
|
}
|