|
1 | | -import { test, expect, vi } from "vitest"; |
| 1 | +import { test, expect, vi, afterEach } from "vitest"; |
2 | 2 | import { createRequire } from "node:module"; |
| 3 | +import fs from "node:fs"; |
| 4 | +import os from "node:os"; |
| 5 | +import path from "node:path"; |
3 | 6 | import { RuleConfigSeverity, UserConfig } from "@commitlint/types"; |
4 | 7 |
|
5 | | -import resolveExtends, { ResolveExtendsContext } from "./index.js"; |
| 8 | +import resolveExtends, { ResolveExtendsContext, resolveFrom } from "./index.js"; |
6 | 9 |
|
7 | 10 | const require = createRequire(import.meta.url); |
8 | 11 |
|
@@ -628,3 +631,73 @@ test("should correctly merge nested configs", async () => { |
628 | 631 |
|
629 | 632 | expect(actual).toEqual(expected); |
630 | 633 | }); |
| 634 | + |
| 635 | +const scaffoldedRoots: string[] = []; |
| 636 | + |
| 637 | +afterEach(() => { |
| 638 | + for (const root of scaffoldedRoots.splice(0)) { |
| 639 | + fs.rmSync(root, { recursive: true, force: true }); |
| 640 | + } |
| 641 | +}); |
| 642 | + |
| 643 | +// Create a throwaway `<root>/node_modules/<pkg>` layout and return <root>. |
| 644 | +const scaffoldModule = ( |
| 645 | + pkg: string, |
| 646 | + manifest: Record<string, unknown>, |
| 647 | + files: Record<string, string>, |
| 648 | +): string => { |
| 649 | + const root = fs.mkdtempSync(path.join(os.tmpdir(), "resolve-from-")); |
| 650 | + scaffoldedRoots.push(root); |
| 651 | + const pkgDir = path.join(root, "node_modules", pkg); |
| 652 | + fs.mkdirSync(pkgDir, { recursive: true }); |
| 653 | + fs.writeFileSync(path.join(pkgDir, "package.json"), JSON.stringify(manifest)); |
| 654 | + for (const [relative, content] of Object.entries(files)) { |
| 655 | + const file = path.join(pkgDir, relative); |
| 656 | + fs.mkdirSync(path.dirname(file), { recursive: true }); |
| 657 | + fs.writeFileSync(file, content); |
| 658 | + } |
| 659 | + return root; |
| 660 | +}; |
| 661 | + |
| 662 | +// CommonJS resolvers cannot read an `exports` map that only declares the `import` |
| 663 | +// condition (no require/default/main), so resolveFrom must fall back to reading the |
| 664 | +// manifest itself. See conventional-changelog-angular@>=9 / conventionalcommits@>=10. |
| 665 | +test("resolveFrom resolves a pure-ESM package exposing only the import condition", () => { |
| 666 | + const root = scaffoldModule( |
| 667 | + "esm-only-preset", |
| 668 | + { |
| 669 | + name: "esm-only-preset", |
| 670 | + type: "module", |
| 671 | + exports: { types: "./index.d.ts", import: "./index.js" }, |
| 672 | + }, |
| 673 | + { "index.js": "export default {};" }, |
| 674 | + ); |
| 675 | + |
| 676 | + expect(resolveFrom("esm-only-preset", root)).toBe( |
| 677 | + path.join(root, "node_modules", "esm-only-preset", "index.js"), |
| 678 | + ); |
| 679 | +}); |
| 680 | + |
| 681 | +test("resolveFrom resolves a subpath of a pure-ESM package", () => { |
| 682 | + const root = scaffoldModule( |
| 683 | + "esm-only-preset", |
| 684 | + { name: "esm-only-preset", type: "module", exports: { import: "./index.js" } }, |
| 685 | + { "index.js": "export default {};", "feature.js": "export default {};" }, |
| 686 | + ); |
| 687 | + |
| 688 | + expect(resolveFrom("esm-only-preset/feature", root)).toBe( |
| 689 | + path.join(root, "node_modules", "esm-only-preset", "feature.js"), |
| 690 | + ); |
| 691 | +}); |
| 692 | + |
| 693 | +test("resolveFrom falls back to main when the exports map has no runtime condition", () => { |
| 694 | + const root = scaffoldModule( |
| 695 | + "types-only-preset", |
| 696 | + { name: "types-only-preset", exports: { types: "./index.d.ts" }, main: "./main.js" }, |
| 697 | + { "main.js": "module.exports = {};" }, |
| 698 | + ); |
| 699 | + |
| 700 | + expect(resolveFrom("types-only-preset", root)).toBe( |
| 701 | + path.join(root, "node_modules", "types-only-preset", "main.js"), |
| 702 | + ); |
| 703 | +}); |
0 commit comments