From f5c0fb2bc05a1d76ae413bf98ebef3b14c9ecf34 Mon Sep 17 00:00:00 2001 From: InSync Date: Tue, 21 Apr 2026 01:14:12 +0000 Subject: [PATCH 1/7] feat(core): Support ${configDir} --- .changeset/cute-files-pump.md | 5 +++++ packages/core/src/config.test.ts | 20 ++++++++++++++++++++ packages/core/src/config.ts | 10 ++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 .changeset/cute-files-pump.md diff --git a/.changeset/cute-files-pump.md b/.changeset/cute-files-pump.md new file mode 100644 index 00000000..0d0b8e1f --- /dev/null +++ b/.changeset/cute-files-pump.md @@ -0,0 +1,5 @@ +--- +'@css-modules-kit/core': minor +--- + +Support ${configDir} diff --git a/packages/core/src/config.test.ts b/packages/core/src/config.test.ts index 05deaa18..042766c6 100644 --- a/packages/core/src/config.test.ts +++ b/packages/core/src/config.test.ts @@ -378,4 +378,24 @@ describe('readConfigFile', () => { ]); }); }); + describe('configDir template variable', () => { + test('correctly resolves configDir', async () => { + const iff = await createIFF({ + 'apps/app/tsconfig.json': dedent` + { + "extends": ["../../tsconfig.a.json"] + } + `, + 'tsconfig.a.json': dedent` + { + "include": ["./types", "\${configDir}/src"], + "exclude": ["./dist", "\${configDir}/dist"] + } + `, + }); + const result = readConfigFile(iff.join('apps/app')); + expect(result.includes).toEqual([iff.join('types'), iff.join('apps/app/src')]); + expect(result.excludes).toEqual([iff.join('dist'), iff.join('apps/app/dist')]); + }); + }); }); diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 1e381852..ed2c6faf 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -256,11 +256,17 @@ export function readConfigFile(project: string): CMKConfig { } const basePath = dirname(configFileName); + + // https://github.com/microsoft/TypeScript/blob/55423abe4d029017f19b6e4c32097591994836b4/src/compiler/commandLineParser.ts#L3299-L3328 + function resolvePath(path: string) { + return join(basePath, path.replace(/^\$\{configDir}/i, './')); + } + return { // If `include` is not specified, fallback to the default include spec。 // ref: https://github.com/microsoft/TypeScript/blob/caf1aee269d1660b4d2a8b555c2d602c97cb28d7/src/compiler/commandLineParser.ts#L3102 - includes: (parsedTsConfig.config.includes ?? [DEFAULT_INCLUDE_SPEC]).map((i) => join(basePath, i)), - excludes: (parsedTsConfig.config.excludes ?? []).map((e) => join(basePath, e)), + includes: (parsedTsConfig.config.includes ?? [DEFAULT_INCLUDE_SPEC]).map(resolvePath), + excludes: (parsedTsConfig.config.excludes ?? []).map(resolvePath), dtsOutDir: join(basePath, parsedTsConfig.config.dtsOutDir ?? 'generated'), arbitraryExtensions: parsedTsConfig.config.arbitraryExtensions ?? false, namedExports: parsedTsConfig.config.namedExports ?? false, From 315c4e99b4c75ce86fd1bf530697292206c22616 Mon Sep 17 00:00:00 2001 From: mizdra Date: Tue, 21 Apr 2026 23:31:43 +0900 Subject: [PATCH 2/7] refactor: refactor the code - Use the same function names as in TypeScript - To prevent the function from being redefined every time `readConfigFile` is executed --- packages/core/src/config.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index ed2c6faf..b9e69e50 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -229,6 +229,11 @@ function parseTsConfigFile(fileName: string) { }; } +// https://github.com/microsoft/TypeScript/blob/55423abe4d029017f19b6e4c32097591994836b4/src/compiler/commandLineParser.ts#L3299-L3328 +function getSubstitutedPath(path: string, basePath: string) { + return join(basePath, path.replace(/^\$\{configDir}/i, './')); +} + /** * Reads the `tsconfig.json` file and returns the normalized config. * Even if the `tsconfig.json` file contains syntax or semantic errors, @@ -257,16 +262,13 @@ export function readConfigFile(project: string): CMKConfig { const basePath = dirname(configFileName); - // https://github.com/microsoft/TypeScript/blob/55423abe4d029017f19b6e4c32097591994836b4/src/compiler/commandLineParser.ts#L3299-L3328 - function resolvePath(path: string) { - return join(basePath, path.replace(/^\$\{configDir}/i, './')); - } - return { // If `include` is not specified, fallback to the default include spec。 // ref: https://github.com/microsoft/TypeScript/blob/caf1aee269d1660b4d2a8b555c2d602c97cb28d7/src/compiler/commandLineParser.ts#L3102 - includes: (parsedTsConfig.config.includes ?? [DEFAULT_INCLUDE_SPEC]).map(resolvePath), - excludes: (parsedTsConfig.config.excludes ?? []).map(resolvePath), + includes: (parsedTsConfig.config.includes ?? [DEFAULT_INCLUDE_SPEC]).map((path) => + getSubstitutedPath(path, basePath), + ), + excludes: (parsedTsConfig.config.excludes ?? []).map((path) => getSubstitutedPath(path, basePath)), dtsOutDir: join(basePath, parsedTsConfig.config.dtsOutDir ?? 'generated'), arbitraryExtensions: parsedTsConfig.config.arbitraryExtensions ?? false, namedExports: parsedTsConfig.config.namedExports ?? false, From 38a4318f4e87f18fa18ea1e1a37fdf09e29fb1cc Mon Sep 17 00:00:00 2001 From: mizdra Date: Tue, 21 Apr 2026 23:36:43 +0900 Subject: [PATCH 3/7] feat: support `${configDir}` in `cmkOptions.dtsOutDir` - Users can also specify a path for `cmkOptions.dtsOutDir`. They would likely expect `${configDir}` to be supported there as well. --- packages/core/src/config.test.ts | 6 +++++- packages/core/src/config.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/core/src/config.test.ts b/packages/core/src/config.test.ts index 042766c6..0c7cb8fa 100644 --- a/packages/core/src/config.test.ts +++ b/packages/core/src/config.test.ts @@ -389,13 +389,17 @@ describe('readConfigFile', () => { 'tsconfig.a.json': dedent` { "include": ["./types", "\${configDir}/src"], - "exclude": ["./dist", "\${configDir}/dist"] + "exclude": ["./dist", "\${configDir}/dist"], + "cmkOptions": { + "dtsOutDir": "\${configDir}/generated" + } } `, }); const result = readConfigFile(iff.join('apps/app')); expect(result.includes).toEqual([iff.join('types'), iff.join('apps/app/src')]); expect(result.excludes).toEqual([iff.join('dist'), iff.join('apps/app/dist')]); + expect(result.dtsOutDir).toBe(iff.join('apps/app/generated')); }); }); }); diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index b9e69e50..0986497a 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -269,7 +269,7 @@ export function readConfigFile(project: string): CMKConfig { getSubstitutedPath(path, basePath), ), excludes: (parsedTsConfig.config.excludes ?? []).map((path) => getSubstitutedPath(path, basePath)), - dtsOutDir: join(basePath, parsedTsConfig.config.dtsOutDir ?? 'generated'), + dtsOutDir: getSubstitutedPath(parsedTsConfig.config.dtsOutDir ?? 'generated', basePath), arbitraryExtensions: parsedTsConfig.config.arbitraryExtensions ?? false, namedExports: parsedTsConfig.config.namedExports ?? false, prioritizeNamedImports: parsedTsConfig.config.prioritizeNamedImports ?? false, From 447b8974ab39c71fd6920aef5a171bd3adbaad36 Mon Sep 17 00:00:00 2001 From: mizdra Date: Wed, 22 Apr 2026 00:01:31 +0900 Subject: [PATCH 4/7] test: refactor test --- packages/core/src/config.test.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/core/src/config.test.ts b/packages/core/src/config.test.ts index 0c7cb8fa..d6725e62 100644 --- a/packages/core/src/config.test.ts +++ b/packages/core/src/config.test.ts @@ -379,27 +379,28 @@ describe('readConfigFile', () => { }); }); describe('configDir template variable', () => { - test('correctly resolves configDir', async () => { + // oxlint-disable-next-line no-template-curly-in-string + test('resolve ${configDir} with the entry tsconfig directory', async () => { const iff = await createIFF({ - 'apps/app/tsconfig.json': dedent` - { - "extends": ["../../tsconfig.a.json"] - } - `, - 'tsconfig.a.json': dedent` + 'tsconfig.base.json': dedent` { - "include": ["./types", "\${configDir}/src"], - "exclude": ["./dist", "\${configDir}/dist"], + "include": ["\${configDir}/src"], + "exclude": ["\${configDir}/dist"], "cmkOptions": { "dtsOutDir": "\${configDir}/generated" } } `, + 'app/tsconfig.json': dedent` + { + "extends": "../tsconfig.base.json", + } + `, }); - const result = readConfigFile(iff.join('apps/app')); - expect(result.includes).toEqual([iff.join('types'), iff.join('apps/app/src')]); - expect(result.excludes).toEqual([iff.join('dist'), iff.join('apps/app/dist')]); - expect(result.dtsOutDir).toBe(iff.join('apps/app/generated')); + const result = readConfigFile(iff.join('app')); + expect(result.includes).toEqual([iff.join('app/src')]); + expect(result.excludes).toEqual([iff.join('app/dist')]); + expect(result.dtsOutDir).toBe(iff.join('app/generated')); }); }); }); From 2641a993ab4fe4de3fb6e7e5f5d5ac980ebf8811 Mon Sep 17 00:00:00 2001 From: mizdra Date: Wed, 22 Apr 2026 00:08:45 +0900 Subject: [PATCH 5/7] test: add test cases --- packages/core/src/config.test.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/core/src/config.test.ts b/packages/core/src/config.test.ts index d6725e62..08596f97 100644 --- a/packages/core/src/config.test.ts +++ b/packages/core/src/config.test.ts @@ -402,5 +402,30 @@ describe('readConfigFile', () => { expect(result.excludes).toEqual([iff.join('app/dist')]); expect(result.dtsOutDir).toBe(iff.join('app/generated')); }); + // oxlint-disable-next-line no-template-curly-in-string + test('does not replace ${configDir} if it is not at the start of the path', async () => { + const iff = await createIFF({ + 'tsconfig.json': dedent` + { + "include": ["./\${configDir}/src"] + } + `, + }); + const result = readConfigFile(iff.rootDir); + // oxlint-disable-next-line no-template-curly-in-string + expect(result.includes).toEqual([iff.join('${configDir}/src')]); + }); + // oxlint-disable-next-line no-template-curly-in-string + test('replaces ${configDir} case-insensitively', async () => { + const iff = await createIFF({ + 'tsconfig.json': dedent` + { + "include": ["\${CONFIGDIR}/src"] + } + `, + }); + const result = readConfigFile(iff.rootDir); + expect(result.includes).toEqual([iff.join('src')]); + }); }); }); From 04a2af49aec544a8ef27c71e9bce17409ff54869 Mon Sep 17 00:00:00 2001 From: mizdra Date: Wed, 22 Apr 2026 00:16:10 +0900 Subject: [PATCH 6/7] test: add failed test --- packages/core/src/config.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/core/src/config.test.ts b/packages/core/src/config.test.ts index 08596f97..f8c7d0e4 100644 --- a/packages/core/src/config.test.ts +++ b/packages/core/src/config.test.ts @@ -268,6 +268,29 @@ describe('readConfigFile', () => { }), ); }); + // FIXME + test.fails('resolves relative paths against the defining tsconfig directory', async () => { + const iff = await createIFF({ + 'tsconfig.base.json': dedent` + { + "include": ["src"], + "exclude": ["dist"], + "cmkOptions": { + "dtsOutDir": "generated" + } + } + `, + 'app/tsconfig.json': dedent` + { + "extends": "../tsconfig.base.json" + } + `, + }); + const result = readConfigFile(iff.join('app')); + expect(result.includes).toEqual([iff.join('src')]); + expect(result.excludes).toEqual([iff.join('dist')]); + expect(result.dtsOutDir).toBe(iff.join('generated')); + }); }); describe('diagnostics', () => { test('returns diagnostics and a config object with error values excluded if config file has semantic errors', async () => { From 8f28ecc127f9a38c6e02d5775b6da1231107da60 Mon Sep 17 00:00:00 2001 From: mizdra Date: Wed, 22 Apr 2026 00:18:18 +0900 Subject: [PATCH 7/7] chore: fix changeset --- .changeset/cute-files-pump.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cute-files-pump.md b/.changeset/cute-files-pump.md index 0d0b8e1f..e7570cd0 100644 --- a/.changeset/cute-files-pump.md +++ b/.changeset/cute-files-pump.md @@ -2,4 +2,4 @@ '@css-modules-kit/core': minor --- -Support ${configDir} +feat(core): support `${configDir}` in tsconfig options