From b7f614e9ec3ce70f776b1b49dbbd6222a1095941 Mon Sep 17 00:00:00 2001 From: Daniel Del Core Date: Thu, 22 Jun 2023 17:44:21 +1000 Subject: [PATCH] checks remote module entrypoints for config files --- .changeset/hot-cars-leave.md | 6 ++++ packages/cli/src/list.spec.ts | 37 +++++++++++++++++++ packages/fetcher/src/index.spec.ts | 58 ++++++++++++++++++++++++++++++ packages/fetcher/src/index.ts | 18 ++++++++++ 4 files changed, 119 insertions(+) create mode 100644 .changeset/hot-cars-leave.md diff --git a/.changeset/hot-cars-leave.md b/.changeset/hot-cars-leave.md new file mode 100644 index 000000000..70694d73d --- /dev/null +++ b/.changeset/hot-cars-leave.md @@ -0,0 +1,6 @@ +--- +'@codeshift/fetcher': patch +'@codeshift/cli': patch +--- + +Correctly checks remote pacakge entrypoints for configs (as opposed to just checking for codeshift.config.js files) diff --git a/packages/cli/src/list.spec.ts b/packages/cli/src/list.spec.ts index 3c61ae673..28f6170c8 100644 --- a/packages/cli/src/list.spec.ts +++ b/packages/cli/src/list.spec.ts @@ -24,6 +24,7 @@ describe('list', () => { })), })); }); + afterEach(() => { jest.resetAllMocks(); }); @@ -37,6 +38,12 @@ describe('list', () => { ├─ transforms | ├─ 18.0.0 | └─ 19.0.0 +└─ presets + └─ sort-imports +${chalk.bold('foobar')} +├─ transforms +| ├─ 18.0.0 +| └─ 19.0.0 └─ presets └─ sort-imports`); expect(console.warn).not.toHaveBeenCalled(); @@ -51,6 +58,12 @@ describe('list', () => { ├─ transforms | ├─ 18.0.0 | └─ 19.0.0 +└─ presets + └─ sort-imports +${chalk.bold('@foo/bar')} +├─ transforms +| ├─ 18.0.0 +| └─ 19.0.0 └─ presets └─ sort-imports`); expect(console.warn).not.toHaveBeenCalled(); @@ -65,12 +78,24 @@ describe('list', () => { ├─ transforms | ├─ 18.0.0 | └─ 19.0.0 +└─ presets + └─ sort-imports +${chalk.bold('bar')} +├─ transforms +| ├─ 18.0.0 +| └─ 19.0.0 └─ presets └─ sort-imports ${chalk.bold('@codeshift/mod-foo__bar')} ├─ transforms | ├─ 18.0.0 | └─ 19.0.0 +└─ presets + └─ sort-imports +${chalk.bold('@foo/bar')} +├─ transforms +| ├─ 18.0.0 +| └─ 19.0.0 └─ presets └─ sort-imports`); expect(console.warn).not.toHaveBeenCalled(); @@ -136,12 +161,24 @@ ${chalk.bold('@codeshift/mod-foo__bar')} ├─ transforms | ├─ 18.0.0 | └─ 19.0.0 +└─ presets + └─ sort-imports +${chalk.bold('found1')} +├─ transforms +| ├─ 18.0.0 +| └─ 19.0.0 └─ presets └─ sort-imports ${chalk.bold('@codeshift/mod-found2')} ├─ transforms | ├─ 18.0.0 | └─ 19.0.0 +└─ presets + └─ sort-imports +${chalk.bold('found2')} +├─ transforms +| ├─ 18.0.0 +| └─ 19.0.0 └─ presets └─ sort-imports`); diff --git a/packages/fetcher/src/index.spec.ts b/packages/fetcher/src/index.spec.ts index 90e312018..c8c506463 100644 --- a/packages/fetcher/src/index.spec.ts +++ b/packages/fetcher/src/index.spec.ts @@ -137,6 +137,10 @@ describe('fetcher', () => { it('correctly fetches package and returns a config', async () => { const mockPackageManager = { install: jest.fn(), + require: jest + .fn() + .mockReturnValueOnce({}) // fail the entrypoint config check + .mockReturnValueOnce(mockConfig), getInfo: jest.fn().mockReturnValue({ location: mockBasePath, }), @@ -166,6 +170,60 @@ describe('fetcher', () => { ).rejects.toEqual('Import error'); }); + it('correctly fetches package and returns an entrypoint-based config', async () => { + const mockPackageManager = { + install: jest.fn(), + require: jest.fn().mockReturnValueOnce(mockConfig), + getInfo: jest.fn().mockReturnValue({ + location: mockBasePath + '/index.js', + }), + }; + + const { config, filePath } = await fetchRemotePackage( + 'fake-package', + mockPackageManager as unknown as PluginManager, + ); + + expect(config).toEqual(mockConfig); + expect(filePath).toEqual(mockBasePath + '/index.js'); + }); + + it('throws if entrypoint-based config does not contain a valid config (and there are no config files available elsewhere)', async () => { + const mockPackageManager = { + install: jest.fn(), + require: jest.fn().mockReturnValueOnce({}), + getInfo: jest.fn().mockReturnValue({ + location: mockBasePath + '/index.js', + }), + }; + + (globby as unknown as jest.Mock).mockImplementation(() => + Promise.resolve([]), + ); + + const res = await fetchRemotePackage( + 'fake-package', + mockPackageManager as unknown as PluginManager, + ); + + expect(res).toBeUndefined(); + }); + + it('should throw if fetching fails', async () => { + const mockPackageManager = { + install: jest.fn().mockRejectedValue('Import error'), + }; + + expect.assertions(1); + + await expect( + fetchRemotePackage( + 'fake-package', + mockPackageManager as unknown as PluginManager, + ), + ).rejects.toEqual('Import error'); + }); + it('should throw if package source cannot be retrieved', async () => { const mockPackageManager = { install: jest.fn(), diff --git a/packages/fetcher/src/index.ts b/packages/fetcher/src/index.ts index 24c7490b6..7e27ed6fc 100644 --- a/packages/fetcher/src/index.ts +++ b/packages/fetcher/src/index.ts @@ -35,6 +35,9 @@ export async function fetchConfig(filePath: string): Promise { export async function fetchConfigs(filePath: string): Promise { const matchedPaths = await globby([ + path.join(filePath, 'hypermod.config.(js|ts|tsx)'), + path.join(filePath, 'src', 'hypermod.config.(js|ts|tsx)'), + path.join(filePath, 'codemods', 'hypermod.config.(js|ts|tsx)'), path.join(filePath, 'codeshift.config.(js|ts|tsx)'), path.join(filePath, 'src', 'codeshift.config.(js|ts|tsx)'), path.join(filePath, 'codemods', 'codeshift.config.(js|ts|tsx)'), @@ -101,5 +104,20 @@ export async function fetchRemotePackage( ); } + // Search main entrypoint for transform/presets from the default import + try { + const pkg = packageManager.require(packageName); + const configExport = resolveConfigExport(pkg); + + if (configExport.transforms || configExport.presets) { + return { + filePath: info.location, + config: resolveConfigExport(pkg), + }; + } + } catch (e) { + // Swallow this error + } + return await fetchConfig(info.location); }