Skip to content

Commit

Permalink
feat: enhance the diversification of configuration file reading
Browse files Browse the repository at this point in the history
- enhance configuration reading so that it can read from .ctirc, package.json, and tsconfig.json.
- enhance visibility of the source of configuration from .ctirc, package.json, or tsconfig.json.
  • Loading branch information
imjuni committed Aug 3, 2024
1 parent 5962b76 commit 653ae3e
Show file tree
Hide file tree
Showing 17 changed files with 504 additions and 195 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@types/minimist": "^1.2.4",
"@types/node": "^20.8.7",
"@types/source-map-support": "^0.5.9",
"@types/type-check": "^0.3.30",
"@types/yargs": "^17.0.32",
"@typescript-eslint/eslint-plugin": "^7.6.0",
"@typescript-eslint/parser": "^7.6.0",
Expand Down Expand Up @@ -143,6 +144,7 @@
"ts-morph": "^23.0.0",
"ts-pattern": "^5.0.5",
"tslib": "^2.6.2",
"type-check": "^0.4.0",
"type-fest": "^4.15.0",
"yaml": "^2.4.1",
"yargs": "^17.7.2"
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

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

18 changes: 18 additions & 0 deletions src/configs/__tests__/get.config.value.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getConfigObject } from '#/configs/getConfigObject';
import { getConfigValue } from '#/configs/getConfigValue';
import { describe, expect, it } from 'vitest';

Expand Down Expand Up @@ -28,3 +29,20 @@ describe('getConfigValue', () => {
expect(r02).toBeUndefined();
});
});

describe('getConfigObject', () => {
it('key more then one', () => {
const obj = getConfigObject({ a: 'a', b: 'b', c: 'c' }, 'a', 'c');
expect(obj).toMatchObject({ a: 'a', c: 'c' });
});

it('empty keys', () => {
const obj = getConfigObject({ a: 'a', b: 'b', c: 'c' }, 'z');
expect(obj).toBeUndefined();
});

it('hit two keys, but undefined value', () => {
const obj = getConfigObject({ a: 'a', b: 'b', c: 'c', d: undefined, e: undefined }, 'd', 'e');
expect(obj).toBeUndefined();
});
});
9 changes: 8 additions & 1 deletion src/configs/castConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import type { TCommandRemoveOptions } from '#/configs/interfaces/TCommandRemoveO
export function castConfig(
command: CE_CTIX_COMMAND,
config: unknown,
paths: { config?: string; tsconfig?: string },
paths: {
from: 'none' | '.ctirc' | 'tsconfig.json' | 'package.json';
config?: string;
tsconfig?: string;
},
): TCommandBuildArgvOptions | TCommandRemoveOptions | IProjectOptions {
switch (command) {
case CE_CTIX_COMMAND.BUILD_COMMAND:
return {
...(config as TCommandBuildArgvOptions),
from: paths.from,
p: paths.tsconfig,
project: paths.tsconfig,
c: paths.config,
Expand All @@ -21,6 +26,7 @@ export function castConfig(
case CE_CTIX_COMMAND.REMOVE_COMMAND:
return {
...(config as TCommandRemoveOptions),
from: paths.from,
p: paths.tsconfig,
project: paths.tsconfig,
c: paths.config,
Expand All @@ -29,6 +35,7 @@ export function castConfig(

default:
return {
from: paths.from,
p: paths.tsconfig,
project: paths.tsconfig,
c: paths.config,
Expand Down
17 changes: 17 additions & 0 deletions src/configs/getConfigObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function getConfigObject(
argv: Record<string, unknown>,
...keywordArgs: string[]
): Record<string, unknown> | undefined {
const keywords = [...keywordArgs];
const keys = keywords.filter((keyword) => keyword in argv && argv[keyword] != null);

if (keys.length <= 0) {
return undefined;
}

const aggregated = keys.reduce<Record<string, unknown>>((obj, key) => {
return { ...obj, [key]: argv[key] };
}, {});

return aggregated;
}
165 changes: 104 additions & 61 deletions src/configs/loadConfig.ts
Original file line number Diff line number Diff line change
@@ -1,103 +1,146 @@
import { Spinner } from '#/cli/ux/Spinner';
import { castConfig } from '#/configs/castConfig';
import { CE_CTIX_DEFAULT_VALUE } from '#/configs/const-enum/CE_CTIX_DEFAULT_VALUE';
import { getConfigObject } from '#/configs/getConfigObject';
import { getConfigValue } from '#/configs/getConfigValue';
import type { IProjectOptions } from '#/configs/interfaces/IProjectOptions';
import type { TCommandBuildArgvOptions } from '#/configs/interfaces/TCommandBuildArgvOptions';
import type { TCommandRemoveOptions } from '#/configs/interfaces/TCommandRemoveOptions';
import { getCommand } from '#/configs/modules/getCommand';
import { readJsonConfig } from '#/configs/modules/json/readJsonConfig';
import { parseConfig } from '#/configs/parseConfig';
import { getConfigFilePath } from '#/modules/path/getConfigFilePath';
import { posixJoin } from '#/modules/path/modules/posixJoin';
import { getConfigFilePath } from '#/configs/modules/getConfigFilePath';
import { readConfigFromFile } from '#/configs/modules/readConfigFromFile';
import { readConfigFromPackageJson } from '#/configs/modules/readConfigFromPackageJson';
import { readConfigFromTsconfigJson } from '#/configs/modules/readConfigFromTsconfigJson';
import { getCheckedValue } from '#/modules/values/getCheckedValue';
import consola from 'consola';
import findUp from 'find-up';
import minimist from 'minimist';
import { isError } from 'my-easy-fp';
import fs from 'node:fs';
import type { PackageJson, TsConfigJson } from 'type-fest';

export async function loadConfig(): Promise<
TCommandBuildArgvOptions | TCommandRemoveOptions | IProjectOptions
> {
try {
const configValueKeys = [
'force-yes',
'y',
'remove-backup',
'export-filename',
'f',
'output',
'o',
'skip-empty-dir',
'start-from',
'project',
'p',
'mode',
'use-semicolon',
'use-banner',
'quote',
'q',
'directive',
'file-ext',
'overwrite',
'w',
'backup',
'generation-style',
'include-files',
'exclude-files',
'config',
'c',
'spinner-stream',
'progress-stream',
'reasoner-stream',
];
const argv = minimist(process.argv.slice(2));

const prjectPath = getConfigValue(argv, 'p', 'project');
const tsconfigPath =
prjectPath != null
? findUp.sync(prjectPath)
: findUp.sync(CE_CTIX_DEFAULT_VALUE.TSCONFIG_FILENAME);

const configFilePath = getConfigFilePath(argv, tsconfigPath);
// const configFilePath = getConfigFilePath(argv, tsconfigPath);
const command = getCommand(argv._);

if (tsconfigPath == null) {
Spinner.it.fail('Cannot found tsconfig.json file!');
throw new Error('Cannot found tsconfig.json file!');
}
const configFilePath = await getConfigFilePath(
CE_CTIX_DEFAULT_VALUE.CONFIG_FILENAME,
getConfigValue(argv, 'c', 'config'),
);

const tsconfigFilePath = await getConfigFilePath(
CE_CTIX_DEFAULT_VALUE.TSCONFIG_FILENAME,
getConfigValue(argv, 'p', 'project'),
);

const configFileEither =
configFilePath != null ? await readConfigFromFile(configFilePath) : undefined;

// case 1. using .ctirc
if (configFilePath != null) {
const parsed =
configFilePath != null ? parseConfig(await fs.promises.readFile(configFilePath)) : {};
if (configFileEither != null && configFileEither.type === 'pass') {
const projectFilePath =
getCheckedValue<string>('String', getConfigValue(argv, 'p', 'project')) ??
getCheckedValue<string>('String', configFileEither.pass.p) ??
getCheckedValue<string>('String', configFileEither.pass.project) ??
tsconfigFilePath;

const config = castConfig(command, parsed, {
config: configFilePath,
tsconfig: tsconfigPath,
});
const config = castConfig(
command,
{
...configFileEither.pass,
...getConfigObject(argv, ...configValueKeys),
},
{
from: '.ctirc',
config: configFilePath,
tsconfig: projectFilePath,
},
);

Spinner.it.fail("load configuration from '.ctirc'");
return config;
}

// case 2. using tsconfig.json
const tsconfigParsed = await readJsonConfig<TsConfigJson>(tsconfigPath);
if (
configFilePath == null &&
tsconfigParsed != null &&
'ctix' in tsconfigParsed &&
tsconfigParsed.ctix != null &&
typeof tsconfigParsed.ctix === 'object' &&
Object.keys(tsconfigParsed.ctix).length > 0
) {
const config = castConfig(command, tsconfigParsed.ctix, {
config: configFilePath,
tsconfig: tsconfigPath,
});
const tsconfigEither =
tsconfigFilePath != null ? await readConfigFromTsconfigJson(tsconfigFilePath) : undefined;

if (tsconfigEither != null && tsconfigEither.type === 'pass') {
const config = castConfig(
command,
{
...tsconfigEither.pass,
...getConfigObject(argv, ...configValueKeys),
},
{
from: 'tsconfig.json',
config: configFilePath,
tsconfig: tsconfigFilePath,
},
);

Spinner.it.fail("load configuration from 'tsconfig.json'");
return config;
}

// case 3. using package.json
const packageJsonParsed = await readJsonConfig<PackageJson>(
posixJoin(process.cwd(), 'package.json'),
);
if (
configFilePath == null &&
packageJsonParsed != null &&
'ctix' in packageJsonParsed &&
packageJsonParsed.ctix != null &&
typeof packageJsonParsed.ctix === 'object' &&
Object.keys(packageJsonParsed.ctix).length > 0
) {
const config = castConfig(command, packageJsonParsed.ctix, {
config: configFilePath,
tsconfig: tsconfigPath,
});
const packageJsonEither = await readConfigFromPackageJson();

if (packageJsonEither.type === 'pass') {
const config = castConfig(
command,
{
...packageJsonEither.pass,
...getConfigObject(argv, ...configValueKeys),
},
{
from: 'package.json',
config: configFilePath,
tsconfig: tsconfigFilePath,
},
);

Spinner.it.fail("load configuration from 'package.json'");
return config;
}

// case 4. in case of a read failure from .ctirc, tsconfig.json, or package.json
const config = castConfig(
command,
{},
{
...getConfigObject(argv, ...configValueKeys),
},
{
from: 'none',
config: configFilePath,
tsconfig: tsconfigPath,
tsconfig: tsconfigFilePath,
},
);

Expand Down
Loading

0 comments on commit 653ae3e

Please sign in to comment.