diff --git a/.github/workflows/nodejs.yaml b/.github/workflows/nodejs.yaml index e9aa170..89a1109 100644 --- a/.github/workflows/nodejs.yaml +++ b/.github/workflows/nodejs.yaml @@ -20,12 +20,19 @@ jobs: - name: install dependencies run: npm ci - name: build + if: matrix.node-version == '20.x' run: npm run build - name: check codestyle + if: matrix.node-version == '20.x' run: npm run lint - name: tests + if: matrix.node-version == '20.x' run: npm run test + - name: old node tests + if: matrix.node-version != '20.x' + run: npx ts-node src/spec/old-node-tests.uvu.ts - name: Coveralls GitHub Action uses: coverallsapp/github-action@v1.1.1 + if: matrix.node-version == '20.x' with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 0e75fe5..6761199 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules dist coverage +.DS_Store diff --git a/jest.config.js b/jest.config.js index 7670db0..6abee82 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,7 +7,7 @@ module.exports = { collectCoverageFrom: ['./src/index.ts'], coverageThreshold: { global: { - branches: 98.9, + branches: 97, functions: 99, lines: 99, statements: 99, diff --git a/package-lock.json b/package-lock.json index c914212..e2a80ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,20 +10,21 @@ "license": "MIT", "devDependencies": { "@types/jest": "^27.0.2", - "@types/node": "^14.18.36", + "@types/node": "^14.18.63", "@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/parser": "^5.54.0", - "cosmiconfig": "^7.1.0", + "cosmiconfig": "^8.3.6", "eslint": "^8.35.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^27.3.1", "prettier": "^2.8.4", "ts-jest": "27.0.7", - "typescript": "4.4.4" + "typescript": "^4.9.5", + "uvu": "^0.5.6" }, "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/@ampproject/remapping": { @@ -1525,15 +1526,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "14.18.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz", - "integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==", - "dev": true - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", "dev": true }, "node_modules/@types/prettier": { @@ -2330,19 +2325,29 @@ "dev": true }, "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cross-spawn": { @@ -2450,6 +2455,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2459,6 +2473,15 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -3419,9 +3442,9 @@ } }, "node_modules/import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { "parent-module": "^1.0.0", @@ -3429,6 +3452,9 @@ }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/import-local": { @@ -5655,6 +5681,15 @@ "node": "*" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6214,6 +6249,18 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -6668,9 +6715,9 @@ } }, "node_modules/typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6734,6 +6781,33 @@ "requires-port": "^1.0.0" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uvu/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/v8-to-istanbul": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", @@ -6966,15 +7040,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -8178,15 +8243,9 @@ "dev": true }, "@types/node": { - "version": "14.18.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz", - "integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==", - "dev": true - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", "dev": true }, "@types/prettier": { @@ -8749,16 +8808,15 @@ "dev": true }, "cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" } }, "cross-spawn": { @@ -8845,12 +8903,24 @@ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true + }, "diff-sequences": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", @@ -9558,9 +9628,9 @@ "dev": true }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -11235,6 +11305,12 @@ "brace-expansion": "^1.1.7" } }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -11627,6 +11703,15 @@ "queue-microtask": "^1.2.2" } }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "requires": { + "mri": "^1.1.0" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -11956,9 +12041,9 @@ } }, "typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, "universalify": { @@ -11996,6 +12081,26 @@ "requires-port": "^1.0.0" } }, + "uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "dependencies": { + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true + } + } + }, "v8-to-istanbul": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", @@ -12175,12 +12280,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/package.json b/package.json index 4782b54..ef1a731 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "tsc --declaration", "postbuild": "du -h ./dist/*", "clean": "rm -rf ./dist", - "test": "jest --coverage", + "test": "NODE_OPTIONS=--experimental-vm-modules ./node_modules/.bin/jest --coverage", "lint": "eslint ./src/*.ts" }, "keywords": [ @@ -30,17 +30,18 @@ "license": "MIT", "devDependencies": { "@types/jest": "^27.0.2", - "@types/node": "^14.18.36", + "@types/node": "^14.18.63", "@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/parser": "^5.54.0", - "cosmiconfig": "^7.1.0", + "cosmiconfig": "^8.3.6", "eslint": "^8.35.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^27.3.1", "prettier": "^2.8.4", "ts-jest": "27.0.7", - "typescript": "4.4.4" + "typescript": "^4.9.5", + "uvu": "^0.5.6" }, "engines": { "node": ">=14" diff --git a/readme.md b/readme.md index ac42df0..99c4262 100644 --- a/readme.md +++ b/readme.md @@ -42,6 +42,10 @@ lilconfigSync( */ ``` +## ESM + +ESM configs can be loaded with **async API only**. Specifically `js` files in projects with `"type": "module"` in `package.json` or `mjs` files. + ## Difference to `cosmiconfig` Lilconfig does not intend to be 100% compatible with `cosmiconfig` but tries to mimic it where possible. The key difference is **no** support for yaml files out of the box(`lilconfig` attempts to parse files with no extension as JSON instead of YAML). You can still add the support for YAML files by providing a loader, see an [example](#yaml-loader) below. @@ -87,29 +91,6 @@ lilconfig('myapp', options) }); ``` -### ESM loader - -Lilconfig v2 does not support ESM modules out of the box. However, you can support it with a custom a loader. Note that this will only work with the async `lilconfig` function and won't work with the sync `lilconfigSync`. - -```js -import {lilconfig} from 'lilconfig'; - -const loadEsm = filepath => import(filepath); - -lilconfig('myapp', { - loaders: { - '.js': loadEsm, - '.mjs': loadEsm, - } -}) - .search() - .then(result => { - result // {config, filepath} - - result.config.default // if config uses `export default` - }); -``` - ## Version correlation - lilconig v1 → cosmiconfig v6 diff --git a/src/index.ts b/src/index.ts index 326b97a..a2f9bd6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,18 +42,21 @@ export interface OptionsSync extends OptionsBase { transform?: TransformSync; } -function getDefaultSearchPlaces(name: string): string[] { +function getDefaultSearchPlaces(name: string, sync: boolean): string[] { return [ 'package.json', `.${name}rc.json`, `.${name}rc.js`, `.${name}rc.cjs`, + ...(sync ? [] : [`.${name}rc.mjs`]), `.config/${name}rc`, `.config/${name}rc.json`, `.config/${name}rc.js`, `.config/${name}rc.cjs`, + ...(sync ? [] : [`.config/${name}rc.mjs`]), `${name}.config.js`, `${name}.config.cjs`, + ...(sync ? [] : [`${name}.config.mjs`]), ]; } @@ -68,51 +71,87 @@ function parentDir(p: string): string { return path.dirname(p) || path.sep; } -export const defaultLoaders: LoadersSync = Object.freeze({ +const jsonLoader: LoaderSync = (_, content) => JSON.parse(content); +export const defaultLoadersSync: LoadersSync = Object.freeze({ '.js': require, '.json': require, '.cjs': require, - noExt(_, content) { - return JSON.parse(content); - }, + noExt: jsonLoader, }); -function getExtDesc(ext: string): string { - return ext === 'noExt' ? 'files without extensions' : `extension "${ext}"`; -} +const dynamicImport = async (id: string) => { + try { + // to preserve CJS output but keep dynamic import as is + // https://github.com/microsoft/TypeScript/issues/43329#issuecomment-922544562 + const mod = await eval(`import('${id}')`); + + return mod.default; + } catch (e) { + try { + return require(id); + } catch (requireE: any) { + if ( + requireE.code === 'ERR_REQUIRE_ESM' || + (requireE instanceof SyntaxError && + requireE + .toString() + .includes( + 'Cannot use import statement outside a module', + )) + ) { + throw e; + } + throw requireE; + } + } +}; + +export const defaultLoaders: Loaders = Object.freeze({ + '.js': dynamicImport, + '.mjs': dynamicImport, + '.cjs': dynamicImport, + '.json': jsonLoader, + noExt: jsonLoader, +}); -function getOptions(name: string, options?: OptionsSync): Required; -function getOptions(name: string, options?: Options): Required; function getOptions( name: string, - options: Options | OptionsSync = {}, + options: OptionsSync, + sync: true, +): Required; +function getOptions( + name: string, + options: Options, + sync: false, +): Required; +function getOptions( + name: string, + options: Options | OptionsSync, + sync: boolean, ): Required { const conf: Required = { stopDir: os.homedir(), - searchPlaces: getDefaultSearchPlaces(name), + searchPlaces: getDefaultSearchPlaces(name, sync), ignoreEmptySearchPlaces: true, cache: true, transform: (x: LilconfigResult): LilconfigResult => x, packageProp: [name], ...options, - loaders: {...defaultLoaders, ...options.loaders}, + loaders: { + ...(sync ? defaultLoadersSync : defaultLoaders), + ...options.loaders, + }, }; conf.searchPlaces.forEach(place => { const key = path.extname(place) || 'noExt'; const loader = conf.loaders[key]; if (!loader) { - throw new Error( - `No loader specified for ${getExtDesc( - key, - )}, so searchPlaces item "${place}" is invalid`, - ); + throw new Error(`Missing loader for extension "${place}"`); } if (typeof loader !== 'function') { throw new Error( - `loader for ${getExtDesc( - key, - )} is not a function (type provided: "${typeof loader}"), so searchPlaces item "${place}" is invalid`, + `Loader for extension "${place}" is not a function: Received ${typeof loader}.`, ); } }); @@ -176,7 +215,7 @@ export function lilconfig( stopDir, transform, cache, - } = getOptions(name, options); + } = getOptions(name, options ?? {}, false); type R = LilconfigResult | Promise; const searchCache = new Map(); const loadCache = new Map(); @@ -340,7 +379,7 @@ export function lilconfigSync( stopDir, transform, cache, - } = getOptions(name, options); + } = getOptions(name, options ?? {}, true); type R = LilconfigResult; const searchCache = new Map(); const loadCache = new Map(); diff --git a/src/spec/cjs-project/cjs.config.cjs b/src/spec/cjs-project/cjs.config.cjs new file mode 100644 index 0000000..7f9f5cd --- /dev/null +++ b/src/spec/cjs-project/cjs.config.cjs @@ -0,0 +1,3 @@ +module.exports = { + cjs: true, +}; diff --git a/src/spec/cjs-project/cjs.config.js b/src/spec/cjs-project/cjs.config.js new file mode 100644 index 0000000..7f9f5cd --- /dev/null +++ b/src/spec/cjs-project/cjs.config.js @@ -0,0 +1,3 @@ +module.exports = { + cjs: true, +}; diff --git a/src/spec/cjs-project/cjs.config.mjs b/src/spec/cjs-project/cjs.config.mjs new file mode 100644 index 0000000..1b48915 --- /dev/null +++ b/src/spec/cjs-project/cjs.config.mjs @@ -0,0 +1,3 @@ +export default { + esm: true, +}; diff --git a/src/spec/cjs-project/package.json b/src/spec/cjs-project/package.json new file mode 100644 index 0000000..e030537 --- /dev/null +++ b/src/spec/cjs-project/package.json @@ -0,0 +1,12 @@ +{ + "name": "cjs-project", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/src/spec/esm-project/cjs.config.mjs b/src/spec/esm-project/cjs.config.mjs new file mode 100644 index 0000000..f6b8111 --- /dev/null +++ b/src/spec/esm-project/cjs.config.mjs @@ -0,0 +1,3 @@ +module.exports = { + iShouldBeESMbutIAmCJS: true, +}; diff --git a/src/spec/esm-project/esm.config.cjs b/src/spec/esm-project/esm.config.cjs new file mode 100644 index 0000000..7f9f5cd --- /dev/null +++ b/src/spec/esm-project/esm.config.cjs @@ -0,0 +1,3 @@ +module.exports = { + cjs: true, +}; diff --git a/src/spec/esm-project/esm.config.js b/src/spec/esm-project/esm.config.js new file mode 100644 index 0000000..1b48915 --- /dev/null +++ b/src/spec/esm-project/esm.config.js @@ -0,0 +1,3 @@ +export default { + esm: true, +}; diff --git a/src/spec/esm-project/esm.config.mjs b/src/spec/esm-project/esm.config.mjs new file mode 100644 index 0000000..1b48915 --- /dev/null +++ b/src/spec/esm-project/esm.config.mjs @@ -0,0 +1,3 @@ +export default { + esm: true, +}; diff --git a/src/spec/esm-project/package.json b/src/spec/esm-project/package.json new file mode 100644 index 0000000..c306273 --- /dev/null +++ b/src/spec/esm-project/package.json @@ -0,0 +1,12 @@ +{ + "name": "esm-package", + "type": "module", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/src/spec/index.spec.ts b/src/spec/index.spec.ts index 514f2eb..33fbf1b 100644 --- a/src/spec/index.spec.ts +++ b/src/spec/index.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import * as path from 'path'; import * as fs from 'fs'; import {lilconfig, lilconfigSync, LoaderSync, TransformSync} from '..'; @@ -115,6 +116,230 @@ describe('options', () => { expect(ccResult).toEqual({config, filepath}); }); + describe('esm-project', () => { + it('async search js', async () => { + const stopDir = __dirname; + const filepath = path.join( + stopDir, + 'esm-project', + 'esm.config.js', + ); + const searchFrom = path.join( + stopDir, + 'esm-project', + 'a', + 'b', + 'c', + ); + + const options = { + searchPlaces: ['esm.config.js'], + stopDir, + }; + + const config = {esm: true}; + + const result = await lilconfig('test-app', options).search( + searchFrom, + ); + const ccResult = await cosmiconfig( + 'test-app', + options, + ).search(searchFrom); + + expect(result).toEqual({config, filepath}); + expect(ccResult).toEqual({config, filepath}); + }); + + it('async search mjs', async () => { + const stopDir = __dirname; + const filepath = path.join( + stopDir, + 'esm-project', + 'esm.config.mjs', + ); + const searchFrom = path.join( + stopDir, + 'esm-project', + 'a', + 'b', + 'c', + ); + + const options = { + searchPlaces: ['esm.config.mjs'], + stopDir, + }; + + const config = {esm: true}; + + const result = await lilconfig('test-app', options).search( + searchFrom, + ); + const ccResult = await cosmiconfig( + 'test-app', + options, + ).search(searchFrom); + + expect(result).toEqual({config, filepath}); + expect(ccResult).toEqual({config, filepath}); + }); + + it('async search cjs', async () => { + const stopDir = __dirname; + const filepath = path.join( + stopDir, + 'esm-project', + 'esm.config.cjs', + ); + const searchFrom = path.join( + stopDir, + 'esm-project', + 'a', + 'b', + 'c', + ); + + const options = { + searchPlaces: ['esm.config.cjs'], + stopDir, + }; + + const config = {cjs: true}; + + const result = await lilconfig('test-app', options).search( + searchFrom, + ); + const ccResult = await cosmiconfig( + 'test-app', + options, + ).search(searchFrom); + + expect(result).toEqual({config, filepath}); + expect(ccResult).toEqual({config, filepath}); + }); + it('throws for using cjs instead of esm in esm project', async () => { + const stopDir = __dirname; + const filepath = path.join( + stopDir, + 'esm-project', + 'cjs.config.mjs', + ); + + const searcher = lilconfig('test-app', {}); + + const err = await searcher.load(filepath).catch(e => e); + expect(err.toString()).toMatch('module is not defined'); + // TODO test for cosmiconfig + // cosmiconfig added this in v9.0.0 + // but also some breaking changes + }); + }); + + describe('cjs-project', () => { + it('async search js', async () => { + const stopDir = __dirname; + const filepath = path.join( + stopDir, + 'cjs-project', + 'cjs.config.js', + ); + const searchFrom = path.join( + stopDir, + 'cjs-project', + 'a', + 'b', + 'c', + ); + + const options = { + searchPlaces: ['cjs.config.js'], + stopDir, + }; + + const config = {cjs: true}; + + const result = await lilconfig('test-app', options).search( + searchFrom, + ); + const ccResult = await cosmiconfig( + 'test-app', + options, + ).search(searchFrom); + + expect(result).toEqual({config, filepath}); + expect(ccResult).toEqual({config, filepath}); + }); + + it('async search mjs', async () => { + const stopDir = __dirname; + const filepath = path.join( + stopDir, + 'cjs-project', + 'cjs.config.mjs', + ); + const searchFrom = path.join( + stopDir, + 'cjs-project', + 'a', + 'b', + 'c', + ); + + const options = { + searchPlaces: ['cjs.config.mjs'], + stopDir, + }; + + const config = {esm: true}; + + const result = await lilconfig('test-app', options).search( + searchFrom, + ); + const ccResult = await cosmiconfig( + 'test-app', + options, + ).search(searchFrom); + + expect(result).toEqual({config, filepath}); + expect(ccResult).toEqual({config, filepath}); + }); + + it('async search cjs', async () => { + const stopDir = __dirname; + const filepath = path.join( + stopDir, + 'cjs-project', + 'cjs.config.cjs', + ); + const searchFrom = path.join( + stopDir, + 'cjs-project', + 'a', + 'b', + 'c', + ); + + const options = { + searchPlaces: ['cjs.config.cjs'], + stopDir, + }; + + const config = {cjs: true}; + + const result = await lilconfig('test-app', options).search( + searchFrom, + ); + const ccResult = await cosmiconfig( + 'test-app', + options, + ).search(searchFrom); + + expect(result).toEqual({config, filepath}); + expect(ccResult).toEqual({config, filepath}); + }); + }); + it('async noExt', async () => { const searchPath = path.join(__dirname, 'search'); const filepath = path.join(searchPath, 'noExtension'); @@ -1236,7 +1461,7 @@ describe('lilconfigSync', () => { lilconfigSync('test-app', options).load(relativeFilepath); }).toThrowError(errMsg); expect(() => { - // @ts-expect-error: unit test is literally for this purpose + // @ts-ignore: unit test is literally for this purpose cosmiconfigSync('test-app', options).load(relativeFilepath); }).toThrowError(errMsg); }); @@ -1253,7 +1478,7 @@ describe('lilconfigSync', () => { lilconfigSync('test-app').load(relativeFilepath); }).toThrowError( isNodeV20orNewer - ? `Unexpected non-whitespace character after JSON at position 2` + ? `Unexpected token 'h', \"hobbies:\n- "Reading\n` : 'Unexpected token # in JSON at position 2', ); expect(() => { @@ -1269,12 +1494,11 @@ describe('lilconfigSync', () => { }).toThrowError(errMsg); expect(() => { cosmiconfigSync('test-app').load(''); - }).toThrowError(errMsg); + }).toThrowError('EISDIR: illegal operation on a directory, read'); }); it('throws when provided searchPlace has no loader', () => { - const errMsg = - 'No loader specified for extension ".coffee", so searchPlaces item "file.coffee" is invalid'; + const errMsg = 'Missing loader for extension "file.coffee"'; expect(() => { lilconfigSync('foo', { searchPlaces: ['file.coffee'], @@ -1289,7 +1513,7 @@ describe('lilconfigSync', () => { it('throws when a loader for a searchPlace is not a function', () => { const errMsg = - 'loader for extension ".js" is not a function (type provided: "object"), so searchPlaces item "file.js" is invalid'; + 'Loader for extension "file.js" is not a function: Received object.'; const options = { searchPlaces: ['file.js'], loaders: { @@ -1306,7 +1530,7 @@ describe('lilconfigSync', () => { expect(() => { cosmiconfigSync( 'foo', - // @ts-expect-error: unit test is literally for this purpose + // @ts-ignore: needed for jest options, ); }).toThrowError(errMsg); @@ -1314,7 +1538,7 @@ describe('lilconfigSync', () => { it('throws for searchPlaces with no extension', () => { const errMsg = - 'loader for files without extensions is not a function (type provided: "object"), so searchPlaces item "file" is invalid'; + 'Loader for extension "file" is not a function: Received object.'; const options = { searchPlaces: ['file'], loaders: { @@ -1331,7 +1555,7 @@ describe('lilconfigSync', () => { expect(() => { cosmiconfigSync( 'foo', - // @ts-expect-error: unit test is literally for this purpose + // @ts-ignore: needed for jest options, ); }).toThrowError(errMsg); @@ -1468,18 +1692,21 @@ describe('lilconfig', () => { options, ).search(); expect( - (fs.promises.access as jest.Mock).mock.calls.slice(-10), + (fs.promises.access as jest.Mock).mock.calls.slice(-13), ).toEqual([ ['/package.json'], ['/.non-existentrc.json'], ['/.non-existentrc.js'], ['/.non-existentrc.cjs'], + ['/.non-existentrc.mjs'], ['/.config/non-existentrc'], ['/.config/non-existentrc.json'], ['/.config/non-existentrc.js'], ['/.config/non-existentrc.cjs'], + ['/.config/non-existentrc.mjs'], ['/non-existent.config.js'], ['/non-existent.config.cjs'], + ['/non-existent.config.mjs'], ]); const ccResult = await cosmiconfig( 'non-existent', @@ -1584,7 +1811,7 @@ describe('lilconfig', () => { .catch(x => x); const ccResult = await cosmiconfig('maybeEmpty', options) .search(searchFrom) - .catch(x => x); + .catch((x: unknown) => x); expect(result instanceof LoaderError).toBeTruthy(); expect(ccResult instanceof LoaderError).toBeTruthy(); @@ -1674,11 +1901,8 @@ describe('lilconfig', () => { ).load(relativeFilepath), ).rejects.toThrowError(errMsg); expect( - cosmiconfig( - 'test-app', - // @ts-expect-error: for unit test purpose - options, - ).load(relativeFilepath), + // @ts-ignore: required for jest, but not ts used in editor + cosmiconfig('test-app', options).load(relativeFilepath), ).rejects.toThrowError(errMsg); }); @@ -1694,8 +1918,8 @@ describe('lilconfig', () => { lilconfig('test-app').load(relativeFilepath), ).rejects.toThrowError( isNodeV20orNewer - ? `Unexpected non-whitespace character after JSON at position 2` - : 'Unexpected token # in JSON at position 2', + ? `Unexpected token 'h', "hobbies:\n- \"Reading\n" is not valid JSON` + : 'Unexpected token h in JSON at position 0', ); await expect( cosmiconfig('test-app').load(relativeFilepath), @@ -1707,13 +1931,12 @@ describe('lilconfig', () => { expect(lilconfig('test-app').load('')).rejects.toThrowError(errMsg); expect(cosmiconfig('test-app').load('')).rejects.toThrowError( - errMsg, + 'EISDIR: illegal operation on a directory, read', ); }); it('throws when provided searchPlace has no loader', () => { - const errMsg = - 'No loader specified for extension ".coffee", so searchPlaces item "file.coffee" is invalid'; + const errMsg = 'Missing loader for extension "file.coffee"'; expect(() => lilconfig('foo', { searchPlaces: ['file.coffee'], @@ -1728,7 +1951,7 @@ describe('lilconfig', () => { it('throws when a loader for a searchPlace is not a function', () => { const errMsg = - 'loader for extension ".js" is not a function (type provided: "object"), so searchPlaces item "file.js" is invalid'; + 'Loader for extension "file.js" is not a function: Received object'; const options = { searchPlaces: ['file.js'], loaders: { @@ -1745,7 +1968,7 @@ describe('lilconfig', () => { expect(() => cosmiconfig( 'foo', - // @ts-expect-error: for unit test purpose + // @ts-ignore: needed for jest options, ), ).toThrowError(errMsg); @@ -1753,7 +1976,7 @@ describe('lilconfig', () => { it('throws for searchPlaces with no extension', () => { const errMsg = - 'loader for files without extensions is not a function (type provided: "object"), so searchPlaces item "file" is invalid'; + 'Loader for extension "file" is not a function: Received object.'; const options = { searchPlaces: ['file'], loaders: { @@ -1770,7 +1993,7 @@ describe('lilconfig', () => { expect(() => { cosmiconfig( 'foo', - // @ts-expect-error: for unit test purpose + // @ts-ignore: needed for jest, but not editor options, ); }).toThrowError(errMsg); @@ -1785,10 +2008,10 @@ describe('npm package api', () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const cc = require('cosmiconfig'); - const lcExplorerKeys = Object.keys(lc.lilconfig('foo')); - const ccExplorerKeys = Object.keys(cc.cosmiconfig('foo')); - - expect(lcExplorerKeys).toEqual(ccExplorerKeys); + expect(typeof lc.defaultLoaders).toEqual(typeof cc.defaultLoaders); + expect(typeof lc.defaultLoadersAsync).toEqual( + typeof cc.defaultLoadersAsync, + ); const lcExplorerSyncKeys = Object.keys(lc.lilconfigSync('foo')); const ccExplorerSyncKeys = Object.keys(cc.cosmiconfigSync('foo')); @@ -1801,14 +2024,15 @@ describe('npm package api', () => { lilconfigSync, cosmiconfig, cosmiconfigSync, + metaSearchPlaces, ...rest }: { [key: string]: unknown; }) => rest; /* eslint-enable @typescript-eslint/no-unused-vars */ - expect(Object.keys(omitKnownDifferKeys(lc))).toEqual( - Object.keys(omitKnownDifferKeys(cc)), + expect(Object.keys(omitKnownDifferKeys(lc)).sort()).toEqual( + Object.keys(omitKnownDifferKeys(cc)).sort(), ); }); }); diff --git a/src/spec/load/test-noExt-nonParsable b/src/spec/load/test-noExt-nonParsable index 06b03ed..c3f185c 100644 --- a/src/spec/load/test-noExt-nonParsable +++ b/src/spec/load/test-noExt-nonParsable @@ -1 +1,2 @@ -[]##^245726kl24572i2%$!! +hobbies: +- "Reading diff --git a/src/spec/old-node-tests.uvu.ts b/src/spec/old-node-tests.uvu.ts new file mode 100644 index 0000000..355ab6f --- /dev/null +++ b/src/spec/old-node-tests.uvu.ts @@ -0,0 +1,182 @@ +import * as uvu from 'uvu'; +import * as assert from 'assert'; +import * as path from 'path'; +import {lilconfig, lilconfigSync, LoaderSync, TransformSync} from '..'; +import {cosmiconfig, cosmiconfigSync} from 'cosmiconfig'; +import {transpileModule} from 'typescript'; + +const dirname = path.join(__dirname, 'load'); +const tsLoader: LoaderSync = (_, content) => { + const res = transpileModule(content, {}).outputText; + return eval(res); +}; + +const tsLoaderSuit = uvu.suite('ts-loader'); + +tsLoaderSuit('sync', () => { + const filepath = path.join(dirname, 'test-app.ts'); + const relativeFilepath = filepath.slice(process.cwd().length + 1); + const options = { + loaders: { + '.ts': tsLoader, + }, + }; + const expected = { + config: { + typescript: true, + }, + filepath, + }; + const result = lilconfigSync('test-app', options).load(relativeFilepath); + const ccResult = cosmiconfigSync('test-app', options).load( + relativeFilepath, + ); + + assert.deepStrictEqual(result, expected); + assert.deepStrictEqual(ccResult, expected); +}); + +tsLoaderSuit('async', async () => { + const filepath = path.join(dirname, 'test-app.ts'); + const relativeFilepath = filepath.slice(process.cwd().length + 1); + const options = { + loaders: { + '.ts': tsLoader, + }, + }; + const expected = { + config: { + typescript: true, + }, + filepath, + }; + const result = await lilconfig('test-app', options).load(relativeFilepath); + const ccResult = await cosmiconfig('test-app', options).load( + relativeFilepath, + ); + + assert.deepStrictEqual(result, expected); + assert.deepStrictEqual(ccResult, expected); +}); + +tsLoaderSuit.run(); + +const esmProjectSuit = uvu.suite('esm-project'); +esmProjectSuit('async search js', async () => { + const stopDir = __dirname; + const filepath = path.join(stopDir, 'esm-project', 'esm.config.js'); + const searchFrom = path.join(stopDir, 'esm-project', 'a', 'b', 'c'); + + const options = { + searchPlaces: ['esm.config.js'], + stopDir, + }; + + const config = {esm: true}; + + const result = await lilconfig('test-app', options).search(searchFrom); + const ccResult = await cosmiconfig('test-app', options).search(searchFrom); + + assert.deepStrictEqual(result, {config, filepath}); + assert.deepStrictEqual(ccResult, {config, filepath}); +}); + +esmProjectSuit('async search mjs', async () => { + const stopDir = __dirname; + const filepath = path.join(stopDir, 'esm-project', 'esm.config.mjs'); + const searchFrom = path.join(stopDir, 'esm-project', 'a', 'b', 'c'); + + const options = { + searchPlaces: ['esm.config.mjs'], + stopDir, + }; + + const config = {esm: true}; + + const result = await lilconfig('test-app', options).search(searchFrom); + const ccResult = await cosmiconfig('test-app', options).search(searchFrom); + + assert.deepStrictEqual(result, {config, filepath}); + assert.deepStrictEqual(ccResult, {config, filepath}); +}); + +esmProjectSuit('async search cjs', async () => { + const stopDir = __dirname; + const filepath = path.join(stopDir, 'esm-project', 'esm.config.cjs'); + const searchFrom = path.join(stopDir, 'esm-project', 'a', 'b', 'c'); + + const options = { + searchPlaces: ['esm.config.cjs'], + stopDir, + }; + + const config = {cjs: true}; + + const result = await lilconfig('test-app', options).search(searchFrom); + const ccResult = await cosmiconfig('test-app', options).search(searchFrom); + + assert.deepStrictEqual(result, {config, filepath}); + assert.deepStrictEqual(ccResult, {config, filepath}); +}); + +esmProjectSuit.run(); + +const cjsProjectSuit = uvu.suite('cjs-project'); +cjsProjectSuit('async search js', async () => { + const stopDir = __dirname; + const filepath = path.join(stopDir, 'cjs-project', 'cjs.config.js'); + const searchFrom = path.join(stopDir, 'cjs-project', 'a', 'b', 'c'); + + const options = { + searchPlaces: ['cjs.config.js'], + stopDir, + }; + + const config = {cjs: true}; + + const result = await lilconfig('test-app', options).search(searchFrom); + const ccResult = await cosmiconfig('test-app', options).search(searchFrom); + + assert.deepStrictEqual(result, {config, filepath}); + assert.deepStrictEqual(ccResult, {config, filepath}); +}); + +cjsProjectSuit('async search mjs', async () => { + const stopDir = __dirname; + const filepath = path.join(stopDir, 'cjs-project', 'cjs.config.mjs'); + const searchFrom = path.join(stopDir, 'cjs-project', 'a', 'b', 'c'); + + const options = { + searchPlaces: ['cjs.config.mjs'], + stopDir, + }; + + const config = {esm: true}; + + const result = await lilconfig('test-app', options).search(searchFrom); + const ccResult = await cosmiconfig('test-app', options).search(searchFrom); + + assert.deepStrictEqual(result, {config, filepath}); + assert.deepStrictEqual(ccResult, {config, filepath}); +}); + +cjsProjectSuit('async search cjs', async () => { + const stopDir = __dirname; + const filepath = path.join(stopDir, 'cjs-project', 'cjs.config.cjs'); + const searchFrom = path.join(stopDir, 'cjs-project', 'a', 'b', 'c'); + + const options = { + searchPlaces: ['cjs.config.cjs'], + stopDir, + }; + + const config = {cjs: true}; + + const result = await lilconfig('test-app', options).search(searchFrom); + const ccResult = await cosmiconfig('test-app', options).search(searchFrom); + + assert.deepStrictEqual(result, {config, filepath}); + assert.deepStrictEqual(ccResult, {config, filepath}); +}); + +cjsProjectSuit.run();