feat: added disable config watching flag
Signed-off-by: Antonio Musolino <antoniomusolino007@gmail.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/config-loader': patch
|
||||
'@backstage/backend-app-api': patch
|
||||
---
|
||||
|
||||
Added `watch` option to configuration loaders that can be used to disable file watching by setting it to `false`.
|
||||
@@ -190,6 +190,7 @@ export function loadBackendConfig(options: {
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
argv: string[];
|
||||
additionalConfigs?: AppConfig[];
|
||||
watch?: boolean;
|
||||
}): Promise<{
|
||||
config: Config;
|
||||
}>;
|
||||
|
||||
@@ -72,6 +72,7 @@ export async function loadBackendConfig(options: {
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
argv: string[];
|
||||
additionalConfigs?: AppConfig[];
|
||||
watch?: boolean;
|
||||
}): Promise<{ config: Config }> {
|
||||
const args = parseArgs(options.argv);
|
||||
|
||||
@@ -89,30 +90,35 @@ export async function loadBackendConfig(options: {
|
||||
configRoot: paths.targetRoot,
|
||||
configTargets: configTargets,
|
||||
remote: options.remote,
|
||||
watch: {
|
||||
onChange(newConfigs) {
|
||||
console.info(
|
||||
`Reloaded config from ${newConfigs.map(c => c.context).join(', ')}`,
|
||||
);
|
||||
const configsToMerge = [...newConfigs];
|
||||
if (options.additionalConfigs) {
|
||||
configsToMerge.push(...options.additionalConfigs);
|
||||
}
|
||||
config.setConfig(ConfigReader.fromConfigs(configsToMerge));
|
||||
},
|
||||
stopSignal: new Promise(resolve => {
|
||||
if (currentCancelFunc) {
|
||||
currentCancelFunc();
|
||||
}
|
||||
currentCancelFunc = resolve;
|
||||
watch:
|
||||
options.watch ?? true
|
||||
? {
|
||||
onChange(newConfigs) {
|
||||
console.info(
|
||||
`Reloaded config from ${newConfigs
|
||||
.map(c => c.context)
|
||||
.join(', ')}`,
|
||||
);
|
||||
const configsToMerge = [...newConfigs];
|
||||
if (options.additionalConfigs) {
|
||||
configsToMerge.push(...options.additionalConfigs);
|
||||
}
|
||||
config.setConfig(ConfigReader.fromConfigs(configsToMerge));
|
||||
},
|
||||
stopSignal: new Promise(resolve => {
|
||||
if (currentCancelFunc) {
|
||||
currentCancelFunc();
|
||||
}
|
||||
currentCancelFunc = resolve;
|
||||
|
||||
// TODO(Rugvip): We keep this here for now to avoid breaking the old system
|
||||
// since this is re-used in backend-common
|
||||
if (module.hot) {
|
||||
module.hot.addDisposeHandler(resolve);
|
||||
}
|
||||
}),
|
||||
},
|
||||
// TODO(Rugvip): We keep this here for now to avoid breaking the old system
|
||||
// since this is re-used in backend-common
|
||||
if (module.hot) {
|
||||
module.hot.addDisposeHandler(resolve);
|
||||
}
|
||||
}),
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
console.info(
|
||||
`Loaded config from ${appConfigs.map(c => c.context).join(', ')}`,
|
||||
|
||||
@@ -552,6 +552,7 @@ export function loadBackendConfig(options: {
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
additionalConfigs?: AppConfig[];
|
||||
argv: string[];
|
||||
watch?: boolean;
|
||||
}): Promise<Config>;
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -36,6 +36,7 @@ export async function loadBackendConfig(options: {
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
additionalConfigs?: AppConfig[];
|
||||
argv: string[];
|
||||
watch?: boolean;
|
||||
}): Promise<Config> {
|
||||
const secretEnumerator = await createConfigSecretEnumerator({
|
||||
logger: options.logger,
|
||||
|
||||
@@ -27,6 +27,8 @@ export interface BaseConfigSourcesOptions {
|
||||
rootDir?: string;
|
||||
// (undocumented)
|
||||
substitutionFunc?: EnvFunc;
|
||||
// (undocumented)
|
||||
watch?: boolean;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -142,6 +144,7 @@ export class FileConfigSource implements ConfigSource {
|
||||
export interface FileConfigSourceOptions {
|
||||
path: string;
|
||||
substitutionFunc?: EnvFunc;
|
||||
watch?: boolean;
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
|
||||
@@ -107,6 +107,7 @@ export async function loadConfig(
|
||||
remote: options.remote && {
|
||||
reloadInterval: { seconds: options.remote.reloadIntervalSeconds },
|
||||
},
|
||||
watch: Boolean(options.watch),
|
||||
rootDir: options.configRoot,
|
||||
argv: options.configTargets.flatMap(t => [
|
||||
'--config',
|
||||
|
||||
@@ -71,6 +71,7 @@ export interface ClosableConfig extends Config {
|
||||
* @public
|
||||
*/
|
||||
export interface BaseConfigSourcesOptions {
|
||||
watch?: boolean;
|
||||
rootDir?: string;
|
||||
remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;
|
||||
substitutionFunc?: SubstitutionFunc;
|
||||
@@ -159,6 +160,7 @@ export class ConfigSources {
|
||||
});
|
||||
}
|
||||
return FileConfigSource.create({
|
||||
watch: options.watch,
|
||||
path: arg.target,
|
||||
substitutionFunc: options.substitutionFunc,
|
||||
});
|
||||
@@ -170,6 +172,7 @@ export class ConfigSources {
|
||||
|
||||
argSources.push(
|
||||
FileConfigSource.create({
|
||||
watch: options.watch,
|
||||
path: defaultPath,
|
||||
substitutionFunc: options.substitutionFunc,
|
||||
}),
|
||||
@@ -177,6 +180,7 @@ export class ConfigSources {
|
||||
if (fs.pathExistsSync(localPath)) {
|
||||
argSources.push(
|
||||
FileConfigSource.create({
|
||||
watch: options.watch,
|
||||
path: localPath,
|
||||
substitutionFunc: options.substitutionFunc,
|
||||
}),
|
||||
|
||||
@@ -39,6 +39,11 @@ export interface FileConfigSourceOptions {
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* Enable watching file
|
||||
*/
|
||||
watch?: boolean;
|
||||
|
||||
/**
|
||||
* A substitution function to use instead of the default environment substitution.
|
||||
*/
|
||||
@@ -89,10 +94,12 @@ export class FileConfigSource implements ConfigSource {
|
||||
|
||||
readonly #path: string;
|
||||
readonly #substitutionFunc?: SubstitutionFunc;
|
||||
readonly #watch?: boolean;
|
||||
|
||||
private constructor(options: FileConfigSourceOptions) {
|
||||
this.#path = options.path;
|
||||
this.#substitutionFunc = options.substitutionFunc;
|
||||
this.#watch = options.watch ?? true;
|
||||
}
|
||||
|
||||
// Work is duplicated across each read, in practice that should not
|
||||
@@ -104,20 +111,27 @@ export class FileConfigSource implements ConfigSource {
|
||||
const signal = options?.signal;
|
||||
const configFileName = basename(this.#path);
|
||||
|
||||
// Keep track of watched paths, since this is simpler than resetting the watcher
|
||||
const watchedPaths = new Array<string>();
|
||||
const watcher = chokidar.watch(this.#path, {
|
||||
usePolling: process.env.NODE_ENV === 'test',
|
||||
});
|
||||
let watchedPaths: Array<string> | null = null;
|
||||
let watcher: FSWatcher | null = null;
|
||||
|
||||
if (this.#watch) {
|
||||
// Keep track of watched paths, since this is simpler than resetting the watcher
|
||||
watchedPaths = new Array<string>();
|
||||
watcher = chokidar.watch(this.#path, {
|
||||
usePolling: process.env.NODE_ENV === 'test',
|
||||
});
|
||||
}
|
||||
|
||||
const dir = dirname(this.#path);
|
||||
const transformer = createConfigTransformer({
|
||||
substitutionFunc: this.#substitutionFunc,
|
||||
readFile: async path => {
|
||||
const fullPath = resolvePath(dir, path);
|
||||
// Any files discovered while reading this config should be watched too
|
||||
watcher.add(fullPath);
|
||||
watchedPaths.push(fullPath);
|
||||
if (watcher && watchedPaths) {
|
||||
// Any files discovered while reading this config should be watched too
|
||||
watcher.add(fullPath);
|
||||
watchedPaths.push(fullPath);
|
||||
}
|
||||
|
||||
const data = await readFile(fullPath);
|
||||
if (data === undefined) {
|
||||
@@ -131,12 +145,15 @@ export class FileConfigSource implements ConfigSource {
|
||||
|
||||
// This is the entry point for reading the file, called initially and on change
|
||||
const readConfigFile = async (): Promise<ConfigSourceData[]> => {
|
||||
// We clear the watched files every time we initiate a new read
|
||||
watcher.unwatch(watchedPaths);
|
||||
watchedPaths.length = 0;
|
||||
if (watcher && watchedPaths) {
|
||||
// We clear the watched files every time we initiate a new read
|
||||
watcher.unwatch(watchedPaths);
|
||||
watchedPaths.length = 0;
|
||||
|
||||
watcher.add(this.#path);
|
||||
watchedPaths.push(this.#path);
|
||||
}
|
||||
|
||||
watcher.add(this.#path);
|
||||
watchedPaths.push(this.#path);
|
||||
const content = await readFile(this.#path);
|
||||
if (content === undefined) {
|
||||
throw new NotFoundError(`Config file "${this.#path}" does not exist`);
|
||||
@@ -157,18 +174,20 @@ export class FileConfigSource implements ConfigSource {
|
||||
|
||||
const onAbort = () => {
|
||||
signal?.removeEventListener('abort', onAbort);
|
||||
watcher.close();
|
||||
if (watcher) watcher.close();
|
||||
};
|
||||
signal?.addEventListener('abort', onAbort);
|
||||
|
||||
yield { configs: await readConfigFile() };
|
||||
|
||||
for (;;) {
|
||||
const event = await this.#waitForEvent(watcher, signal);
|
||||
if (event === 'abort') {
|
||||
return;
|
||||
if (watcher) {
|
||||
for (;;) {
|
||||
const event = await this.#waitForEvent(watcher, signal);
|
||||
if (event === 'abort') {
|
||||
return;
|
||||
}
|
||||
yield { configs: await readConfigFile() };
|
||||
}
|
||||
yield { configs: await readConfigFile() };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user