Skip to content

Commit

Permalink
refactor(betterer 🔧): allow separate config of cwd and basepath (#1231)
Browse files Browse the repository at this point in the history
Fixes #1099, #1129
  • Loading branch information
phenomnomnominal authored Oct 23, 2024
1 parent f434c2a commit d229df0
Show file tree
Hide file tree
Showing 44 changed files with 619 additions and 408 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"customise",
"Deserialiser",
"docgen",
"esque",
"GOLDENS",
"hasher",
"initialisation",
Expand Down
22 changes: 11 additions & 11 deletions goldens/api/betterer.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const betterer: {
export type BettererAPI = typeof betterer;

// @public
export interface BettererConfig extends BettererConfigFS, BettererConfigReporter, BettererConfigContext, BettererConfigWatcher {
export interface BettererConfig extends BettererConfigFS, BettererConfigReporter, BettererConfigContext {
}

// @public
Expand All @@ -45,12 +45,16 @@ export type BettererConfigFilters = ReadonlyArray<RegExp>;

// @public
export interface BettererConfigFS {
basePath: string;
cache: boolean;
cachePath: string;
configPaths: BettererConfigPaths;
cwd: string;
ignores: BettererConfigIgnores;
repoPath: string;
resultsPath: string;
versionControlPath: string;
versionControlPath: string | null;
watch: boolean;
}

// @public
Expand All @@ -60,19 +64,13 @@ export type BettererConfigIgnores = ReadonlyArray<string>;
export type BettererConfigIncludes = ReadonlyArray<string>;

// @public
export type BettererConfigPaths = ReadonlyArray<string>;
export type BettererConfigPaths = readonly [string, ...Array<string>];

// @public
export interface BettererConfigReporter {
logo: boolean;
}

// @public
export interface BettererConfigWatcher {
ignores: BettererConfigIgnores;
watch: boolean;
}

// @public
export interface BettererContext {
readonly config: BettererConfig;
Expand Down Expand Up @@ -159,7 +157,7 @@ export type BettererFilePath = string;
export type BettererFilePaths = ReadonlyArray<BettererFilePath>;

// @public
export type BettererFilePatterns = ReadonlyArray<RegExp | ReadonlyArray<RegExp>>;
export type BettererFilePatterns = ReadonlyArray<RegExp | string | ReadonlyArray<RegExp | string>>;

// @public
export interface BettererFileResolver {
Expand Down Expand Up @@ -232,10 +230,12 @@ export type BettererOptionsFilters = Array<string | RegExp> | string | RegExp;

// @public
export interface BettererOptionsFS {
basePath?: string;
cache?: boolean;
cachePath?: string;
configPaths?: BettererOptionsPaths;
cwd?: string;
repoPath?: string;
resultsPath?: string;
}

Expand Down Expand Up @@ -395,7 +395,7 @@ export class BettererResolverTest<DeserialisedType = unknown, SerialisedType = D
constructor(options: BettererTestOptions<DeserialisedType, SerialisedType, DiffType>);
exclude(...excludePatterns: BettererFilePatterns): this;
include(...includePatterns: BettererFileGlobs): this;
get resolver(): BettererFileResolver;
readonly resolver: BettererFileResolver;
}

// @public
Expand Down
36 changes: 36 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@microsoft/api-extractor": "^7.47.7",
"@phenomnomnominal/commitlint-plugin": "^1.1.1",
"@types/eslint": "^9.6.1",
"@types/ignore-walk": "^4.0.3",
"@types/node": "^18.19.50",
"@vitest/ui": "^2.0.5",
"eslint": "^9.9.1",
Expand Down
1 change: 1 addition & 0 deletions packages/betterer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"core-js": "^3.38.1",
"djb2a": "^2.0.0",
"fast-memoize": "^2.5.2",
"ignore-walk": "^7.0.0",
"lines-and-columns": "^2.0.3",
"minimatch": "^5.0.1",
"prettier": "^3.2.5",
Expand Down
3 changes: 1 addition & 2 deletions packages/betterer/src/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { BettererOptionsContext, BettererOptionsMode, BettererOptionsModeWatch } from '../context/index.js';
import type { BettererOptionsFS } from '../fs/index.js';
import type { BettererOptionsFS, BettererOptionsWatcher } from '../fs/index.js';
import type { BettererOptionsReporter } from '../reporters/index.js';
import type { BettererOptionsWatcher } from '../runner/index.js';
import type { betterer } from './betterer.js';

/**
Expand Down
1 change: 1 addition & 0 deletions packages/betterer/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export type { BettererConfig, BettererOptionsOverride } from './types.js';
export { toArray, toRegExps } from './cast.js';
export {
validateBool,
validateDirectory,
validateFilePath,
validateString,
validateStringArray,
Expand Down
9 changes: 2 additions & 7 deletions packages/betterer/src/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import type { BettererConfigContext, BettererOptionsContextOverride } from '../context/index.js';
import type { BettererConfigFS } from '../fs/index.js';
import type { BettererConfigFS, BettererOptionsWatcherOverride } from '../fs/index.js';
import type { BettererConfigReporter, BettererOptionsReporterOverride } from '../reporters/index.js';
import type { BettererConfigWatcher, BettererOptionsWatcherOverride } from '../runner/index.js';

/**
* @public Full validated config object for **Betterer**.
*/
export interface BettererConfig
extends BettererConfigFS,
BettererConfigReporter,
BettererConfigContext,
BettererConfigWatcher {}
export interface BettererConfig extends BettererConfigFS, BettererConfigReporter, BettererConfigContext {}

/**
* @public Options for when you override the config via the {@link @betterer/betterer#BettererContext.options | `BettererContext.options()` API}.
Expand Down
11 changes: 10 additions & 1 deletion packages/betterer/src/config/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import assert from 'node:assert';

import { BettererError, invariantΔ } from '@betterer/errors';

import { read } from '../fs/index.js';
import { read, readdir } from '../fs/index.js';
import { isBoolean, isNumber, isRegExp, isString, isUndefined } from '../utils.js';

function getKeyValue(config: object): [string, unknown] {
Expand Down Expand Up @@ -33,6 +33,15 @@ export async function validateFilePath<Config extends object>(config: Config): P
return config;
}

export async function validateDirectory<Config extends object>(config: Config): Promise<Config> {
const [key, value] = getKeyValue(config);
validate(
value == null || (isString(value) && (await readdir(value)) !== null),
`"${key}" must be a path to a directory. ${received(value)}`
);
return config;
}

function validateNumber<Config extends object>(config: Config): Config {
const [key, value] = getKeyValue(config);
validate(isNumber(value), `"${key}" must be a number. ${received(value)}`);
Expand Down
8 changes: 4 additions & 4 deletions packages/betterer/src/context/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { BettererContext, BettererContextSummary } from './types.js';

import { overrideContextConfig } from '../context/index.js';
import { overrideReporterConfig } from '../reporters/index.js';
import { overrideWatchConfig } from '../runner/index.js';
import { overrideWatchConfig } from '../fs/index.js';
import { BettererSuiteΩ } from '../suite/index.js';
import { getGlobals } from '../globals.js';
import { BettererContextSummaryΩ } from './context-summary.js';
Expand Down Expand Up @@ -51,11 +51,11 @@ export class BettererContextΩ implements BettererContext {
specifiedFilePaths = [];
}

const { config, reporter, resolvers, results, versionControl } = getGlobals();
const { config, fs, reporter, resolvers, results } = getGlobals();

await versionControl.api.sync();
await fs.api.sync();

const resolver = resolvers.cwd as BettererFileResolverΩ;
const resolver = resolvers.base as BettererFileResolverΩ;

const { includes, excludes } = config;
resolver.include(...includes);
Expand Down
69 changes: 59 additions & 10 deletions packages/betterer/src/fs/config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import type { BettererConfigFS, BettererOptionsFS } from './types.js';
import type { BettererConfigContext } from './../context/index.js';
import type {
BettererConfigFS,
BettererConfigPaths,
BettererOptionsFS,
BettererOptionsWatcher,
BettererOptionsWatcherOverride
} from './types.js';

import { promises as fs } from 'node:fs';
import path from 'node:path';

import { BettererError } from '@betterer/errors';
import { BettererError, invariantΔ } from '@betterer/errors';

import { toArray, validateBool, validateFilePath, validateString, validateStringArray } from '../config/index.js';
import {
toArray,
validateBool,
validateDirectory,
validateFilePath,
validateString,
validateStringArray
} from '../config/index.js';
import { getGlobals } from '../globals.js';

const BETTERER_CACHE = './.betterer.cache';
const BETTERER_RESULTS = './.betterer.results';
const BETTERER_TS = './.betterer.ts';

export async function createFSConfig(options: BettererOptionsFS): Promise<BettererConfigFS> {
export async function createFSConfig(
configContext: BettererConfigContext,
options: BettererOptionsFS,
optionsWatcher: BettererOptionsWatcher
): Promise<BettererConfigFS> {
const cache = (!!options.cachePath || options.cache) ?? false;
const cachePath = options.cachePath ?? BETTERER_CACHE;

Expand All @@ -20,31 +39,58 @@ export async function createFSConfig(options: BettererOptionsFS): Promise<Better
validateStringArray({ configPaths });
const validatedConfigPaths = await validateConfigPaths(cwd, configPaths);

const ignores = toArray<string>(optionsWatcher.ignores);
const watch = optionsWatcher.watch ?? false;

const resultsPath = options.resultsPath ?? BETTERER_RESULTS;
const [configPath] = validatedConfigPaths;

let basePath = options.basePath;
if (basePath) {
await validateDirectory({ basePath: path.resolve(cwd, basePath) });
} else {
basePath = path.dirname(configPath);
}
const repoPath = options.repoPath ?? basePath;

validateString({ cwd });
validateBool({ cache });
validateStringArray({ cachePath });
validateStringArray({ resultsPath });

const gitRoot = await validateGitRepo(cwd);
const gitPath = configContext.precommit ? await validateGitRepo(repoPath) : null;

validateStringArray({ ignores });
validateBool({ watch });

return {
basePath,
cache,
cachePath: path.resolve(cwd, cachePath),
cwd,
configPaths: validatedConfigPaths,
ignores,
repoPath,
resultsPath: path.resolve(cwd, resultsPath),
versionControlPath: path.dirname(gitRoot)
versionControlPath: gitPath ?? null,
watch
};
}

export function overrideWatchConfig(optionsOverride: BettererOptionsWatcherOverride): void {
if (optionsOverride.ignores) {
const { config } = getGlobals();
validateStringArray({ ignores: optionsOverride.ignores });
config.ignores = toArray<string>(optionsOverride.ignores);
}
}

const JS_EXTENSIONS = ['.js', '.cjs', '.mjs'];
const TS_EXTENSIONS = ['.ts', '.tsx', '.cts', '.ctsx', '.mtx', '.mtsx'];
const IMPORT_EXTENSIONS = [...JS_EXTENSIONS, ...TS_EXTENSIONS];

async function validateConfigPaths(cwd: string, configPaths: Array<string>): Promise<Array<string>> {
return await Promise.all(
async function validateConfigPaths(cwd: string, configPaths: Array<string>): Promise<BettererConfigPaths> {
const validatedConfigPaths = await Promise.all(
configPaths.map(async (configPath) => {
const absoluteConfigPath = path.resolve(cwd, configPath);
const { dir, name, ext } = path.parse(absoluteConfigPath);
Expand All @@ -71,6 +117,9 @@ async function validateConfigPaths(cwd: string, configPaths: Array<string>): Pro
}
})
);
const [first, ...rest] = validatedConfigPaths.filter(Boolean);
invariantΔ(first, 'the existence of at least one config path should have been validated!');
return [first, ...rest];
}

async function validateGitRepo(cwd: string): Promise<string> {
Expand All @@ -79,10 +128,10 @@ async function validateGitRepo(cwd: string): Promise<string> {
try {
const gitPath = path.join(dir, '.git');
await fs.access(gitPath);
return gitPath;
return dir;
} catch {
dir = path.join(dir, '..');
}
}
throw new BettererError('.git directory not found. Betterer must be used within a git repository.');
throw new BettererError('.git directory not found. Precommit mode only works within a git repository.');
}
Loading

0 comments on commit d229df0

Please sign in to comment.