Skip to content

Commit

Permalink
feat(ng-dev): support performing configuration assertions in the getC…
Browse files Browse the repository at this point in the history
…onfig function (#204)

Allow the getConfig function take in a list of assertions functions which will assert against the
retrieved config object.  Errors from the assertions will be thrown as expected.  The returned config
object will has the asserted typings.

This will allow retrieving the config object already typed, which provides a convenience for cases
where getConfig is called on intialization.

PR Close #204
  • Loading branch information
josephperrott authored and devversion committed Sep 9, 2021
1 parent c5da4aa commit f631e36
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 12 deletions.
16 changes: 13 additions & 3 deletions github-actions/slash-commands/main.js

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions ng-dev/caretaker/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {info} from 'console';
import {Arguments, Argv} from 'yargs';
import {getConfig} from '../utils/config';
import {assertValidGithubConfig, getConfig} from '../utils/config';
import {CheckModule} from './check/cli';
import {assertValidCaretakerConfig} from './config';
import {HandoffModule} from './handoff/cli';
Expand All @@ -22,9 +22,8 @@ export function buildCaretakerParser(yargs: Argv) {
}

function caretakerCommandCanRun(argv: Arguments) {
const config = getConfig();
try {
assertValidCaretakerConfig(config);
getConfig([assertValidCaretakerConfig, assertValidGithubConfig]);
} catch {
info('The `caretaker` command is not enabled in this repository.');
info(` To enable it, provide a caretaker config in the repository's .ng-dev/ directory`);
Expand Down
17 changes: 17 additions & 0 deletions ng-dev/utils/assertion-typings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
type UnionToIntersection<U> = (U extends unknown ? (union: U) => void : never) extends (
intersection: infer I,
) => void
? I
: never;

type AssertedType<A> = A extends AssertionFn<infer T> ? T : never;
type AllAssertedTypes<A extends readonly AssertionFn<unknown>[]> = {
[K in keyof A]: AssertedType<A[K]>;
};
type ExtractValuesAsUnionFromObject<T> = T[keyof T & number];

export type Assertions<A extends readonly AssertionFn<unknown>[]> = UnionToIntersection<
ExtractValuesAsUnionFromObject<AllAssertedTypes<A>>
>;
export type AssertionFn<T> = (value: Partial<T>) => asserts value is T;
export type MultipleAssertions = AssertionFn<unknown>[];
21 changes: 18 additions & 3 deletions ng-dev/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {existsSync} from 'fs';
import {dirname, join} from 'path';
import {Assertions, MultipleAssertions} from './assertion-typings';

import {debug, error} from './console';
import {GitClient} from './git/git-client';
Expand Down Expand Up @@ -61,16 +62,30 @@ export function setConfig(config: {}) {
* copy if it is defined.
*/
export function getConfig(): {};
export function getConfig(baseDir?: string): {};
export function getConfig(baseDir?: string): {} {
export function getConfig(baseDir: string): {};
export function getConfig<A extends MultipleAssertions>(assertions: A): Assertions<A>;
export function getConfig(baseDirOrAssertions?: unknown) {
let baseDir: string;
if (typeof baseDirOrAssertions === 'string') {
baseDir = baseDirOrAssertions;
} else {
baseDir = GitClient.get().baseDir;
}

// If the global config is not defined, load it from the file system.
if (cachedConfig === null) {
baseDir = baseDir || GitClient.get().baseDir;
// The full path to the configuration file.
const configPath = join(baseDir, CONFIG_FILE_PATH);
// Read the configuration and validate it before caching it for the future.
cachedConfig = readConfigFile(configPath);
}

if (Array.isArray(baseDirOrAssertions)) {
for (const assertion of baseDirOrAssertions) {
assertion(cachedConfig);
}
}

// Return a clone of the cached global config to ensure that a new instance of the config
// is returned each time, preventing unexpected effects of modifications to the config object.
return {...cachedConfig};
Expand Down
16 changes: 13 additions & 3 deletions tools/local-actions/changelog/main.js

Large diffs are not rendered by default.

0 comments on commit f631e36

Please sign in to comment.