From 70ef403580f52e33adc40b6b451e937b8489c73e Mon Sep 17 00:00:00 2001 From: Ryan Wilson-Perkin Date: Fri, 22 Sep 2023 16:33:29 -0400 Subject: [PATCH] Add support for alternative config files with cosmiconfig (#178) Fixes #118 Introduces `cosmiconfig` as requested in #118 in order to support loading the config from alternative file formats like: ``` .unimportedrc.js .unimportedrc.yml package.json > "unimported" key ``` I'm using `cosmiconfigSync` utilities instead of the async version, despite them being available since the `getConfig` is an async method. When we use the async equivalent, we hit a segfault in Node as a result of `cosmiconfig` trying to call a dynamic import on the file within a Jest context that doesn't allow it to. Patched in a recent version of Node, and once the test infra can require the latest version it should be fine to switch to the async version. See https://github.com/nodejs/node/issues/35889 and https://github.com/jestjs/jest/issues/11438 I haven't made any efforts to change the `update` function to write updates to the loaded files, as this would be difficult/impossible to update something like a .js or .yml (if using features like anchors) in a meaningful way. A few notes on the other changes included: 1. `cosmiconfig` pulls in a newer version of TypeScript which was incompatible with the version of `@types/node` we used, updated it 2. One of the tests produced invalid JSON to a config file, which failed silently before and passed the test, but now fails loudly when `cosmiconfig` tries to read and parse the JSON. Updated it to be valid to fulfill the spirit of the test. --------- Co-authored-by: Stephan Meijer --- package-lock.json | 92 ++++++++++++++++++++++++++++++++++++++------ package.json | 1 + src/__tests__/cli.ts | 64 +++++++++++++++++++++++++++++- src/config.ts | 6 +-- 4 files changed, 147 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6f58857..653a3c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@typescript-eslint/parser": "^5.27.1", "@typescript-eslint/typescript-estree": "^5.27.1", "chalk": "^4.1.0", + "cosmiconfig": "^8.3.6", "debug": "^4.3.2", "file-entry-cache": "^6.0.1", "flow-remove-types": "2.156.0", @@ -1281,9 +1282,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "14.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", + "version": "14.18.62", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.62.tgz", + "integrity": "sha512-53Fhb08qfKwSNCIUtysIqw0ye+v1d5QCdL2kl8liKQFlOZTAo+nEYr/FztzMaHBFwB5H0ugF0PF0gmtojaNNiQ==", "dev": true }, "node_modules/@types/normalize-package-data": { @@ -2540,6 +2541,47 @@ "node": ">=0.10.0" } }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -8227,9 +8269,9 @@ } }, "node_modules/typescript": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz", - "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9647,9 +9689,9 @@ "dev": true }, "@types/node": { - "version": "14.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", + "version": "14.18.62", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.62.tgz", + "integrity": "sha512-53Fhb08qfKwSNCIUtysIqw0ye+v1d5QCdL2kl8liKQFlOZTAo+nEYr/FztzMaHBFwB5H0ugF0PF0gmtojaNNiQ==", "dev": true }, "@types/normalize-package-data": { @@ -10536,6 +10578,32 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + } + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -14823,9 +14891,9 @@ } }, "typescript": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz", - "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==" + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" }, "undefsafe": { "version": "2.0.5", diff --git a/package.json b/package.json index ae97772..98a4e78 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@typescript-eslint/parser": "^5.27.1", "@typescript-eslint/typescript-estree": "^5.27.1", "chalk": "^4.1.0", + "cosmiconfig": "^8.3.6", "debug": "^4.3.2", "file-entry-cache": "^6.0.1", "flow-remove-types": "2.156.0", diff --git a/src/__tests__/cli.ts b/src/__tests__/cli.ts index 46557d2..00e1a9a 100644 --- a/src/__tests__/cli.ts +++ b/src/__tests__/cli.ts @@ -181,6 +181,68 @@ cases( exitCode: 1, stdout: /1 unimported files.*bar.js/s, }, + { + name: 'should support JSON config', + files: [ + { name: 'package.json', content: '{}' }, + { + name: '.unimportedrc.json', + content: '{ "entry": ["entry.js"] }', + }, + { name: 'entry.js', content: `import foo from './foo';` }, + { name: 'foo.js', content: '' }, + { name: 'bar.js', content: '' }, + ], + exitCode: 1, + stdout: /1 unimported files.*bar.js/s, + }, + { + name: 'should support JS config', + files: [ + { name: 'package.json', content: '{}' }, + { + name: '.unimportedrc.js', + content: 'module.exports = { entry: ["entry.js"] }', + }, + { name: 'entry.js', content: `import foo from './foo';` }, + { name: 'foo.js', content: '' }, + { name: 'bar.js', content: '' }, + ], + exitCode: 1, + stdout: /1 unimported files.*bar.js/s, + }, + { + name: 'should support YML config', + files: [ + { name: 'package.json', content: '{}' }, + { + name: '.unimportedrc.yml', + content: ` +entry: + - entry.js + `, + }, + { name: 'entry.js', content: `import foo from './foo';` }, + { name: 'foo.js', content: '' }, + { name: 'bar.js', content: '' }, + ], + exitCode: 1, + stdout: /1 unimported files.*bar.js/s, + }, + { + name: 'should support package.json config', + files: [ + { + name: 'package.json', + content: '{ "unimported": { "entry": ["entry.js"] } }', + }, + { name: 'entry.js', content: `import foo from './foo';` }, + { name: 'foo.js', content: '' }, + { name: 'bar.js', content: '' }, + ], + exitCode: 1, + stdout: /1 unimported files.*bar.js/s, + }, { name: 'should identify unresolved imports', files: [ @@ -893,7 +955,7 @@ export default promise { name: 'helpers/index.ts', content: '' }, { name: '.unimportedrc.json', - content: '{ "pathTransforms": { "(\\..+)\\.js$": "$1.ts" } }', + content: '{ "pathTransforms": { "(..+).js$": "$1.ts" } }', }, ], exitCode: 0, diff --git a/src/config.ts b/src/config.ts index 50058c6..e7b3527 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,3 +1,4 @@ +import { cosmiconfigSync } from 'cosmiconfig'; import { ProcessedResult } from './process'; import { readJson, writeJson } from './fs'; import { CliArguments, Context, PackageJson } from './index'; @@ -137,9 +138,8 @@ export async function getConfig(args?: CliArguments): Promise { return cachedConfig; } - const configFile = await readJson>( - args?.config || CONFIG_FILE, - ); + const cosmiconfigResult = cosmiconfigSync('unimported').search(); + const configFile = cosmiconfigResult?.config as Partial; const unimportedPkg = await readPkgUp({ cwd: __dirname });