diff --git a/.changeset/calm-ads-burn.md b/.changeset/calm-ads-burn.md new file mode 100644 index 0000000000..411356fc22 --- /dev/null +++ b/.changeset/calm-ads-burn.md @@ -0,0 +1,12 @@ +--- +"@evmts/bundler": patch +"@evmts/bun-plugin": patch +"@evmts/config": patch +"@evmts/esbuild-plugin": patch +"@evmts/rollup-plugin": patch +"@evmts/rspack-plugin": patch +"@evmts/vite-plugin": patch +"@evmts/webpack-plugin": patch +--- + +Made @evmts/config loading async diff --git a/bundlers/bun/src/index.spec.ts b/bundlers/bun/src/index.spec.ts index fcdd002a31..c5879cf0f0 100644 --- a/bundlers/bun/src/index.spec.ts +++ b/bundlers/bun/src/index.spec.ts @@ -1,13 +1,13 @@ import { evmtsBunPlugin } from '.' import { file } from './bunFile' import { bundler } from '@evmts/bundler' -import { loadConfig } from '@evmts/config' +import { loadConfigAsync } from '@evmts/config' import { exists, readFile } from 'fs/promises' import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('@evmts/config', async () => ({ ...((await vi.importActual('@evmts/config')) as {}), - loadConfig: vi.fn(), + loadConfigAsync: vi.fn(), })) vi.mock('@evmts/bundler', () => ({ @@ -29,7 +29,7 @@ const mockFile = file as Mock const mockExists = exists as Mock const mockBundler = bundler as Mock -const mockLoadConfig = loadConfig as Mock +const mockLoadConfig = loadConfigAsync as Mock mockBundler.mockReturnValue({ resolveEsmModule: vi.fn(), }) @@ -80,7 +80,7 @@ describe('evmtsBunPlugin', () => { onResolve: vi.fn(), config: {} as any, } - plugin.setup(mockBuild) + await plugin.setup(mockBuild) const [onLoadFilter, onLoadFn] = mockBuild.onLoad.mock.lastCall ?? [] @@ -121,7 +121,7 @@ describe('evmtsBunPlugin', () => { onResolve: vi.fn(), config: {} as any, } - plugin.setup(mockBuild) + await plugin.setup(mockBuild) const [onLoadFilter, onLoadFn] = mockBuild.onLoad.mock.lastCall ?? [] @@ -150,7 +150,7 @@ describe('evmtsBunPlugin', () => { onLoad: vi.fn(), config: {} as any, } - plugin.setup(mockBuild) + await plugin.setup(mockBuild) const [_, onResolveFn] = mockBuild.onResolve.mock.calls[0] const resolved = onResolveFn({ @@ -168,7 +168,7 @@ describe('evmtsBunPlugin', () => { onLoad: vi.fn(), config: {} as any, } - plugin.setup(mockBuild) + await plugin.setup(mockBuild) const [_, onResolveFn] = mockBuild.onResolve.mock.calls[0] @@ -192,7 +192,7 @@ describe('evmtsBunPlugin', () => { onResolve: vi.fn(), config: {} as any, } - plugin.setup(mockBuild) + await plugin.setup(mockBuild) const [_, onLoadFn] = mockBuild.onLoad.mock.lastCall ?? [] @@ -214,7 +214,7 @@ describe('evmtsBunPlugin', () => { onResolve: vi.fn(), config: {} as any, } - plugin.setup(mockBuild) + await plugin.setup(mockBuild) const [_, onLoadFn] = mockBuild.onLoad.mock.lastCall ?? [] @@ -237,7 +237,7 @@ describe('evmtsBunPlugin', () => { onResolve: vi.fn(), config: {} as any, } - plugin.setup(mockBuild) + await plugin.setup(mockBuild) const [_, onLoadFn] = mockBuild.onLoad.mock.lastCall ?? [] diff --git a/bundlers/bun/src/index.ts b/bundlers/bun/src/index.ts index 3489b2b4c5..79248e2c24 100644 --- a/bundlers/bun/src/index.ts +++ b/bundlers/bun/src/index.ts @@ -1,6 +1,6 @@ import { bunFileAccesObject } from './bunFileAccessObject' import { bundler } from '@evmts/bundler' -import { loadConfig } from '@evmts/config' +import { loadConfigAsync } from '@evmts/config' import type { BunPlugin } from 'bun' type EvmtsBunPluginOptions = {} @@ -8,11 +8,15 @@ type EvmtsBunPluginOptions = {} type EvmtsBunPlugin = (options?: EvmtsBunPluginOptions) => BunPlugin export const evmtsBunPlugin: EvmtsBunPlugin = () => { - const config = loadConfig(process.cwd()) - const moduleResolver = bundler(config, console, bunFileAccesObject) return { name: '@evmts/esbuild-plugin', - setup(build) { + async setup(build) { + const config = await loadConfigAsync( + process.cwd(), + console, + bunFileAccesObject.exists, + ) + const moduleResolver = bundler(config, console, bunFileAccesObject) /** * @evmts/core is used to construct the evmts modules for solidity files * sometimes the solidity file might exist in the node_modules folder diff --git a/bundlers/bundler/package.json b/bundlers/bundler/package.json index e537b25916..13cb151d56 100644 --- a/bundlers/bundler/package.json +++ b/bundlers/bundler/package.json @@ -31,10 +31,6 @@ "src" ], "scripts": { - "test": "vitest --coverage", - "test:coverage": "vitest run --coverage", - "test:run": "vitest run", - "test:ui": "vitest --ui", "build": "nx run-many --targets=build:dist,build:types --projects=@evmts/bundler ", "build:dist": "tsup", "build:types": "tsc --emitDeclarationOnly", @@ -43,7 +39,11 @@ "format:check": "rome format .", "lint": "rome check . --apply-unsafe", "lint:check": "rome check . --verbose", - "package:up": "pnpm up --latest" + "package:up": "pnpm up --latest", + "test": "vitest --coverage", + "test:coverage": "vitest run --coverage", + "test:run": "vitest run", + "test:ui": "vitest --ui" }, "dependencies": { "@evmts/config": "workspace:^", diff --git a/bundlers/bundler/src/unplugin.spec.ts b/bundlers/bundler/src/unplugin.spec.ts index 8a6f423493..8c25455165 100644 --- a/bundlers/bundler/src/unplugin.spec.ts +++ b/bundlers/bundler/src/unplugin.spec.ts @@ -1,7 +1,7 @@ import * as packageJson from '../package.json' import { bundler } from './bundler' import { unpluginFn } from './unplugin' -import { loadConfig } from '@evmts/config' +import { loadConfigAsync } from '@evmts/config' import { existsSync } from 'fs' import { createRequire } from 'module' import type { UnpluginBuildContext, UnpluginContext } from 'unplugin' @@ -22,7 +22,7 @@ vi.mock('module', async () => ({ vi.mock('@evmts/config', async () => ({ ...((await vi.importActual('@evmts/config')) as {}), - loadConfig: vi.fn(), + loadConfigAsync: vi.fn(), })) vi.mock('./bundler', () => ({ bundler: vi.fn(), @@ -36,7 +36,7 @@ vi.mock('fs', async () => ({ const mockExistsSync = existsSync as Mock const mockBundler = bundler as Mock -const mockLoadConfig = loadConfig as Mock +const mockLoadConfig = loadConfigAsync as Mock mockBundler.mockReturnValue({ resolveEsmModule: vi.fn(), }) @@ -79,7 +79,7 @@ describe('unpluginFn', () => { // call buildstart with mockPlugin as this await plugin.buildStart?.call(mockPlugin) - expect(loadConfig).toHaveBeenCalledWith(mockCwd) + expect(loadConfigAsync).toHaveBeenCalledWith(mockCwd) expect((bundler as Mock).mock.lastCall).toMatchInlineSnapshot(` [ { diff --git a/bundlers/bundler/src/unplugin.ts b/bundlers/bundler/src/unplugin.ts index 6c288b0a56..3e59053b08 100644 --- a/bundlers/bundler/src/unplugin.ts +++ b/bundlers/bundler/src/unplugin.ts @@ -1,7 +1,7 @@ import * as packageJson from '../package.json' import { bundler } from './bundler' import type { FileAccessObject } from './types' -import { type ResolvedConfig, loadConfig } from '@evmts/config' +import { type ResolvedConfig, loadConfigAsync } from '@evmts/config' import { existsSync, readFileSync } from 'fs' import { readFile } from 'fs/promises' import { createRequire } from 'module' @@ -55,7 +55,7 @@ export const unpluginFn: UnpluginFactory< name: '@evmts/rollup-plugin', version: packageJson.version, async buildStart() { - config = loadConfig(process.cwd()) + config = await loadConfigAsync(process.cwd()) moduleResolver = bundler(config, console, fao) this.addWatchFile('./tsconfig.json') }, diff --git a/bundlers/bundler/src/utils/resolvePromise.spec.ts b/bundlers/bundler/src/utils/resolvePromise.spec.ts index b5bd70631a..da9b340855 100644 --- a/bundlers/bundler/src/utils/resolvePromise.spec.ts +++ b/bundlers/bundler/src/utils/resolvePromise.spec.ts @@ -4,51 +4,51 @@ import fs from 'fs' import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest' const fao: FileAccessObject = { - existsSync: (filePath: string) => fs.existsSync(filePath), - readFile: (filePath: string, encoding: string) => - Promise.resolve( - fs.readFileSync(filePath, { encoding: encoding as 'utf8' }), - ), - readFileSync: (filePath: string) => fs.readFileSync(filePath, 'utf8'), + existsSync: (filePath: string) => fs.existsSync(filePath), + readFile: (filePath: string, encoding: string) => + Promise.resolve( + fs.readFileSync(filePath, { encoding: encoding as 'utf8' }), + ), + readFileSync: (filePath: string) => fs.readFileSync(filePath, 'utf8'), } let logger: Logger = { - error: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - log: vi.fn(), + error: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + log: vi.fn(), } describe('resolvePromise', () => { - beforeEach(() => { - logger = { - error: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - log: vi.fn(), - } - }) + beforeEach(() => { + logger = { + error: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + log: vi.fn(), + } + }) - it('should resolve a file path in the base directory', async () => { - const resolvedPath = await resolvePromise( - './resolvePromise.spec.ts', - __dirname, - fao, - logger, - ) - expect( - resolvedPath.endsWith( - 'evmts-monorepo/bundlers/bundler/src/utils/resolvePromise.spec.ts', - ), - ).toBe(true) - }) + it('should resolve a file path in the base directory', async () => { + const resolvedPath = await resolvePromise( + './resolvePromise.spec.ts', + __dirname, + fao, + logger, + ) + expect( + resolvedPath.endsWith( + 'evmts-monorepo/bundlers/bundler/src/utils/resolvePromise.spec.ts', + ), + ).toBe(true) + }) - it('should handle readFile throwing', async () => { - fao.readFile = () => Promise.reject('readFile error') - await expect( - resolvePromise('./resolvePromise.spec.tst', './src/utils', fao, logger), - ).rejects.toThrowErrorMatchingInlineSnapshot('"readFile error"') - expect((logger.error as Mock).mock.calls).toMatchInlineSnapshot(` + it('should handle readFile throwing', async () => { + fao.readFile = () => Promise.reject('readFile error') + await expect( + resolvePromise('./resolvePromise.spec.tst', './src/utils', fao, logger), + ).rejects.toThrowErrorMatchingInlineSnapshot('"readFile error"') + expect((logger.error as Mock).mock.calls).toMatchInlineSnapshot(` [ [ "readFile error", @@ -88,16 +88,16 @@ describe('resolvePromise', () => { ], ] `) - }) + }) - it('should throw an error for non-existent file', async () => { - fao.existsSync = () => false - await expect( - resolvePromise('./resolvePromise.spec.tst', './src/utils', fao, logger), - ).rejects.toThrowErrorMatchingInlineSnapshot( - "\"Cannot find module './resolvePromise.spec.tst' from './src/utils'\"", - ) - expect((logger.error as Mock).mock.calls).toMatchInlineSnapshot(` + it('should throw an error for non-existent file', async () => { + fao.existsSync = () => false + await expect( + resolvePromise('./resolvePromise.spec.tst', './src/utils', fao, logger), + ).rejects.toThrowErrorMatchingInlineSnapshot( + "\"Cannot find module './resolvePromise.spec.tst' from './src/utils'\"", + ) + expect((logger.error as Mock).mock.calls).toMatchInlineSnapshot(` [ [ [Error: Cannot find module './resolvePromise.spec.tst' from './src/utils'], @@ -107,19 +107,21 @@ describe('resolvePromise', () => { ], ] `) - }) + }) - it('should throw an error if existsSync throws', () => { - fao.existsSync = () => { - throw new Error('existsSync error') - } - expect( - resolvePromise('./resolvePromise.spec.ts', './src/utils', fao, logger), - ).rejects.toThrowErrorMatchingInlineSnapshot('"existsSync error"') - expect((logger.error as Mock).mock.calls[0].slice(0, 2)).toMatchInlineSnapshot(` + it('should throw an error if existsSync throws', () => { + fao.existsSync = () => { + throw new Error('existsSync error') + } + expect( + resolvePromise('./resolvePromise.spec.ts', './src/utils', fao, logger), + ).rejects.toThrowErrorMatchingInlineSnapshot('"existsSync error"') + expect( + (logger.error as Mock).mock.calls[0].slice(0, 2), + ).toMatchInlineSnapshot(` [ [Error: existsSync error], ] `) - }) + }) }) diff --git a/config/package.json b/config/package.json index b6a35734c5..bd5f02c24a 100644 --- a/config/package.json +++ b/config/package.json @@ -24,7 +24,7 @@ "type": "module", "main": "dist/index.cjs", "module": "dist/index.js", - "types": "types/index.d.ts", + "types": "types/src/index.d.ts", "files": [ "dist", "types", diff --git a/config/src/fileExists.spec.ts b/config/src/fileExists.spec.ts new file mode 100644 index 0000000000..dddfa922ce --- /dev/null +++ b/config/src/fileExists.spec.ts @@ -0,0 +1,34 @@ +import { fileExists } from './fileExists' +import { constants } from 'fs' +import { access } from 'fs/promises' +import { type Mock, afterEach, describe, expect, it, vi } from 'vitest' + +vi.mock('fs/promises') + +describe('fileExists', () => { + afterEach(() => { + vi.clearAllMocks() + }) + + const accessMock = access as Mock + it('should return true if the file exists', async () => { + accessMock.mockResolvedValueOnce(undefined) + const result = await fileExists('path-to-existing-file.txt') + expect(result).toBe(true) + expect(access).toHaveBeenCalledWith( + 'path-to-existing-file.txt', + constants.F_OK, + ) + }) + + it('should return false if the file does not exist', async () => { + const mockError = new Error('File does not exist') + accessMock.mockRejectedValueOnce(mockError) + const result = await fileExists('path-to-non-existing-file.txt') + expect(result).toBe(false) + expect(access).toHaveBeenCalledWith( + 'path-to-non-existing-file.txt', + constants.F_OK, + ) + }) +}) diff --git a/config/src/fileExists.ts b/config/src/fileExists.ts new file mode 100644 index 0000000000..c12ecd69be --- /dev/null +++ b/config/src/fileExists.ts @@ -0,0 +1,13 @@ +import { constants } from 'fs' +import { access } from 'fs/promises' + +export const fileExists = async (path: string) => { + try { + // TODO not the most robust check for existence here + await access(path, constants.F_OK) + return true + } catch (e) { + // TODO should be inspecting the error here + return false + } +} diff --git a/config/src/index.ts b/config/src/index.ts index 1a31225f32..6329d219f7 100644 --- a/config/src/index.ts +++ b/config/src/index.ts @@ -1,3 +1,5 @@ export * from './Config' export * from './loadConfig' +export * from './loadConfigAsync' export * from './defineConfig' +export const huh = 'huh' diff --git a/config/src/loadConfigAsync.spec.ts b/config/src/loadConfigAsync.spec.ts new file mode 100644 index 0000000000..51117558c6 --- /dev/null +++ b/config/src/loadConfigAsync.spec.ts @@ -0,0 +1,287 @@ +import { fileExists } from './fileExists' +import { loadConfigAsync } from './loadConfigAsync' +import { readFile } from 'fs/promises' +import path from 'path' +import { + type MockedFunction, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest' + +const validConfig = JSON.stringify({ + compilerOptions: { + plugins: [ + { + name: '@evmts/ts-plugin', + compiler: { + solcVersion: '0.9.0', + libs: ['path/to/libs'], + }, + localContracts: { + contracts: [ + { + name: 'WagmiMintExample', + addresses: { + '1': '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + '5': '0x1df10ec981ac5871240be4a94f250dd238b77901', + '10': '0x1df10ec981ac5871240be4a94f250dd238b77901', + }, + }, + ], + }, + }, + ], + }, +}) + +vi.mock('fs/promises', () => ({ + ...vi.importActual('fs/promises'), + readFile: vi.fn(), +})) + +vi.mock('./fileExists', () => ({ + fileExists: vi.fn(), +})) + +describe(loadConfigAsync.name, () => { + beforeEach(() => { + vi.resetAllMocks() + }) + + it('should successfully load the configuration from tsconfig.json', async () => { + const mockFileExists = fileExists as MockedFunction + const mockReadFile = readFile as MockedFunction + + mockReadFile.mockResolvedValue(validConfig) + + const mockConfigFilePath = '/mock/path' + mockFileExists.mockResolvedValue(true) + + const config = await loadConfigAsync(mockConfigFilePath) + + expect(config).toMatchInlineSnapshot(` + { + "compiler": { + "foundryProject": false, + "libs": [ + "path/to/libs", + ], + "remappings": {}, + "solcVersion": "0.9.0", + }, + "externalContracts": { + "apiKeys": { + "etherscan": {}, + }, + "contracts": [], + "out": "externalContracts", + }, + "localContracts": { + "contracts": [ + { + "addresses": { + "1": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "10": "0x1df10ec981ac5871240be4a94f250dd238b77901", + "5": "0x1df10ec981ac5871240be4a94f250dd238b77901", + }, + "name": "WagmiMintExample", + }, + ], + }, + } + `) + }) + + it('should throw an error if no config file exists', async () => { + const mockFileExists = fileExists as MockedFunction + const mockReadFile = readFile as MockedFunction + + mockFileExists.mockResolvedValue(false) // Neither jsconfig.json nor tsconfig.json exist. + mockReadFile.mockRejectedValue(new Error('File not found')) + + const mockConfigFilePath = '/mock/path' + + // Assert that the function throws an error when the file doesn't exist. + await expect(loadConfigAsync(mockConfigFilePath)).rejects.toThrow( + `Failed to read the file at ${path.join( + mockConfigFilePath, + 'tsconfig.json', + )}. Make sure the file exists and is accessible.`, + ) + }) + + it('should throw an error if the config file contains invalid JSON', async () => { + const mockFileExists = fileExists as MockedFunction + const mockReadFile = readFile as MockedFunction + + mockFileExists.mockResolvedValue(true) // Assume a jsconfig.json exists for this test. + mockReadFile.mockResolvedValue('{ invalid: JSON }') // Invalid JSON. + + const mockConfigFilePath = '/mock/path' + + // Assert that the function throws an error for invalid JSON. + await expect(loadConfigAsync(mockConfigFilePath)).rejects.toThrow( + `tsconfig.json at ${path.join( + mockConfigFilePath, + 'tsconfig.json', + )} is not valid json`, + ) + }) + it('should throw an error if compilerOptions is missing from the config file', async () => { + const mockFileExists = fileExists as MockedFunction + const mockReadFile = readFile as MockedFunction + + // A config without compilerOptions + const invalidConfig = JSON.stringify({ + notCompilerOptions: {}, + }) + + mockFileExists.mockResolvedValue(true) // Assume a jsconfig.json exists for this test. + mockReadFile.mockResolvedValue(invalidConfig) + + const mockConfigFilePath = '/mock/path' + + // Assert that the function throws an error when compilerOptions is missing. + await expect( + loadConfigAsync(mockConfigFilePath), + ).rejects.toThrowErrorMatchingInlineSnapshot( + '"tsconfig.json at /mock/path/tsconfig.json is not valid json"', + ) + }) + + it('should warn and return default config when @evmts/ts-plugin is not found', async () => { + const mockFileExists = fileExists as MockedFunction + const mockReadFile = readFile as MockedFunction + + // A config with compilerOptions but without @evmts/ts-plugin in plugins + const configWithoutPlugin = JSON.stringify({ + compilerOptions: { + plugins: [{ name: 'some-other-plugin' }], + }, + }) + + mockFileExists.mockResolvedValue(true) // Assume a jsconfig.json exists for this test. + mockReadFile.mockResolvedValue(configWithoutPlugin) + + const mockConfigFilePath = '/mock/path' + const mockLogger = { + warn: vi.fn(), + error: vi.fn(), + } + + const config = await loadConfigAsync(mockConfigFilePath, mockLogger) + + expect(mockLogger.warn).toHaveBeenCalledWith( + 'No Evmts plugin found in tsconfig.json. Using the default config', + ) + + expect(config).toMatchInlineSnapshot(` + { + "compiler": { + "foundryProject": false, + "libs": [], + "remappings": {}, + "solcVersion": [Function], + }, + "externalContracts": { + "apiKeys": { + "etherscan": {}, + }, + "contracts": [], + "out": "externalContracts", + }, + "localContracts": { + "contracts": [], + }, + } + `) + }) + + it('should append baseUrl to the libs when compilerOptions.baseUrl is set', async () => { + const mockFileExists = fileExists as MockedFunction + const mockReadFile = readFile as MockedFunction + + // Config with compilerOptions.baseUrl set + const configWithBaseUrl = JSON.stringify({ + compilerOptions: { + plugins: [ + { + name: '@evmts/ts-plugin', + compiler: { + solcVersion: '0.9.0', + libs: ['path/to/libs'], + }, + }, + ], + baseUrl: 'base/url', + }, + }) + + mockFileExists.mockResolvedValue(true) // Assume a jsconfig.json exists for this test. + mockReadFile.mockResolvedValue(configWithBaseUrl) + + const mockConfigFilePath = '/mock/path' + + const config = await loadConfigAsync(mockConfigFilePath) + + // Assert that the baseUrl has been appended to the libs + expect(config.compiler.libs).toContain('path/to/libs') + expect(config.compiler.libs).toContain( + path.join(mockConfigFilePath, 'base/url'), + ) + }) + + it('should attempt to load from tsconfig.json when jsconfig.json does not exist', async () => { + const mockFileExists = fileExists as MockedFunction + const mockReadFile = readFile as MockedFunction + + mockFileExists.mockResolvedValue(false) // jsconfig.json does not exist. + mockReadFile.mockResolvedValue(validConfig) // Provide the earlier defined validConfig for the tsconfig.json. + + const mockConfigFilePath = '/mock/path' + + const config = await loadConfigAsync(mockConfigFilePath) + + // Assert that the readFile was called with tsconfig.json + expect(mockReadFile).toHaveBeenCalledWith( + path.join(mockConfigFilePath, 'tsconfig.json'), + 'utf8', + ) + + // Assert that the config matches expected + expect(config).toMatchInlineSnapshot(` + { + "compiler": { + "foundryProject": false, + "libs": [ + "path/to/libs", + ], + "remappings": {}, + "solcVersion": "0.9.0", + }, + "externalContracts": { + "apiKeys": { + "etherscan": {}, + }, + "contracts": [], + "out": "externalContracts", + }, + "localContracts": { + "contracts": [ + { + "addresses": { + "1": "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + "10": "0x1df10ec981ac5871240be4a94f250dd238b77901", + "5": "0x1df10ec981ac5871240be4a94f250dd238b77901", + }, + "name": "WagmiMintExample", + }, + ], + }, + } + `) + }) +}) diff --git a/config/src/loadConfigAsync.ts b/config/src/loadConfigAsync.ts new file mode 100644 index 0000000000..2021191e78 --- /dev/null +++ b/config/src/loadConfigAsync.ts @@ -0,0 +1,78 @@ +import { type EvmtsConfig, type ResolvedConfig } from './Config' +import { defineConfig } from './defineConfig' +import { fileExists as defaultFileExists } from './fileExists' +import { readFile } from 'fs/promises' +import { parse } from 'jsonc-parser' +import * as path from 'path' + +export type LoadConfigAsync = ( + configFilePath: string, + logger?: Pick, + fileExists?: typeof defaultFileExists, +) => Promise + +export const loadConfigAsync: LoadConfigAsync = async ( + configFilePath, + logger = console, + fileExists = defaultFileExists, +) => { + /** + * evmts.config.ts currently doesn't work for ts-plugin because it is not syncronous + * for now load config will load from tsconfig instead until fixed + */ + const tsConfigPath = path.join(configFilePath, 'tsconfig.json') + const jsConfigPath = path.join(configFilePath, 'jsconfig.json') + let configStr + try { + configStr = (await fileExists(jsConfigPath)) + ? await readFile(jsConfigPath, 'utf8') + : await readFile(tsConfigPath, 'utf8') + } catch (error) { + logger.error(error) + throw new Error( + `Failed to read the file at ${tsConfigPath}. Make sure the file exists and is accessible.`, + ) + } + let configJson: { + compilerOptions: { + plugins?: Array<{ name: '@evmts/ts-plugin' } & EvmtsConfig> + baseUrl?: string + } + } + try { + configJson = parse(configStr) + if (!configJson.compilerOptions) { + throw new Error('No compilerOptions found failed to parse tsconfig.json') + } + } catch (e) { + logger.error(e) + throw new Error(`tsconfig.json at ${tsConfigPath} is not valid json`) + } + + let config: EvmtsConfig | undefined = + configJson?.compilerOptions?.plugins?.find( + (plugin) => plugin.name === '@evmts/ts-plugin', + ) + + if (!config) { + logger.warn( + 'No Evmts plugin found in tsconfig.json. Using the default config', + ) + config = {} + } + + if (config && configJson.compilerOptions.baseUrl) { + config = { + ...config, + compiler: { + ...config.compiler, + libs: [ + ...(config.compiler?.libs ?? []), + path.join(configFilePath, configJson.compilerOptions.baseUrl), + ], + }, + } + } + + return defineConfig(() => config ?? {}).configFn(configFilePath) +} diff --git a/config/tsconfig.json b/config/tsconfig.json index e0ca1234e4..4aaf1c719e 100644 --- a/config/tsconfig.json +++ b/config/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "@evmts/tsconfig/base.json", "compilerOptions": { - "rootDir": "src", "composite": true, "outDir": "types", "skipLibCheck": true @@ -10,4 +9,4 @@ "src", "src/**/*.json" ] -} \ No newline at end of file +} diff --git a/config/vitest.config.ts b/config/vitest.config.ts index ccbcd9a166..e329181f17 100644 --- a/config/vitest.config.ts +++ b/config/vitest.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ lines: 100, statements: 100, functions: 100, - branches: 95.34, + branches: 94.23, thresholdAutoUpdate: true, }, },