From 1079f6db53d22453bd13590958fb5b5cb105e9a6 Mon Sep 17 00:00:00 2001 From: Eli <88557639+lishaduck@users.noreply.github.com> Date: Tue, 16 Jul 2024 21:12:35 -0500 Subject: [PATCH 1/9] style: node: imports --- package-lock.json | 13 +++++++------ package.json | 1 + src/__tests__/config-service.test.ts | 2 +- src/__tests__/package-json-file-service.test.ts | 6 +++--- src/cli.ts | 2 +- src/config-service.ts | 2 +- src/globber.ts | 2 +- src/package-json-file-service.ts | 2 +- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 748a094..0a94f40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "devDependencies": { "@types/glob": "^8.1.0", "@types/jest": "^29.5.12", + "@types/node": "~16.18.102", "@types/npm-registry-fetch": "^8.0.7", "@types/semver": "^7.5.8", "@typescript-eslint/eslint-plugin": "^7.15.0", @@ -1536,9 +1537,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "14.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", - "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==", + "version": "16.18.102", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.102.tgz", + "integrity": "sha512-eSe2YwGCcRjqPidxfm20IAq02krERWcIIJW4FNPkU0zQLbc4L9pvhsmB0p6UJecjEf0j/E2ERHsKq7madvthKw==", "dev": true }, "node_modules/@types/node-fetch": { @@ -7447,9 +7448,9 @@ "dev": true }, "@types/node": { - "version": "14.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", - "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==", + "version": "16.18.102", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.102.tgz", + "integrity": "sha512-eSe2YwGCcRjqPidxfm20IAq02krERWcIIJW4FNPkU0zQLbc4L9pvhsmB0p6UJecjEf0j/E2ERHsKq7madvthKw==", "dev": true }, "@types/node-fetch": { diff --git a/package.json b/package.json index 54e1b23..4a32104 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "devDependencies": { "@types/glob": "^8.1.0", "@types/jest": "^29.5.12", + "@types/node": "~16.18.102", "@types/npm-registry-fetch": "^8.0.7", "@types/semver": "^7.5.8", "@typescript-eslint/eslint-plugin": "^7.15.0", diff --git a/src/__tests__/config-service.test.ts b/src/__tests__/config-service.test.ts index a57dee0..e5e26c7 100644 --- a/src/__tests__/config-service.test.ts +++ b/src/__tests__/config-service.test.ts @@ -1,4 +1,4 @@ -import path from 'path' +import * as path from 'node:path' import { createConfigService } from '../config-service' const testDirectory = path.resolve(__dirname, 'fixtures') diff --git a/src/__tests__/package-json-file-service.test.ts b/src/__tests__/package-json-file-service.test.ts index c6533b2..93f9fc5 100644 --- a/src/__tests__/package-json-file-service.test.ts +++ b/src/__tests__/package-json-file-service.test.ts @@ -1,7 +1,7 @@ import { createPackageJSONFileService } from '../package-json-file-service' -import * as os from 'os' -import * as path from 'path' -import * as fs from 'fs' +import * as os from 'node:os' +import * as path from 'node:path' +import * as fs from 'node:fs' import { promisify } from '../util' const writeFileAsync = promisify(fs.writeFile) diff --git a/src/cli.ts b/src/cli.ts index bcbea7e..9d1e7b5 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,6 @@ import { createContainer, InjectionMode, asFunction } from 'awilix' import chalk from 'chalk' -import * as path from 'path' +import * as path from 'node:path' import * as C from './cli-util' import { ITypeSyncer, IPackageTypingDescriptor, ISyncedFile } from './types' import { createTypeSyncer } from './type-syncer' diff --git a/src/config-service.ts b/src/config-service.ts index eab5782..c4e3723 100644 --- a/src/config-service.ts +++ b/src/config-service.ts @@ -1,4 +1,4 @@ -import path from 'path' +import * as path from 'node:path' import { cosmiconfig } from 'cosmiconfig' import { IConfigService, diff --git a/src/globber.ts b/src/globber.ts index 4b74d1b..89eb408 100644 --- a/src/globber.ts +++ b/src/globber.ts @@ -1,5 +1,5 @@ import { glob } from 'glob' -import * as path from 'path' +import * as path from 'node:path' import { uniq } from './util' /** diff --git a/src/package-json-file-service.ts b/src/package-json-file-service.ts index 3977e44..66f98c4 100644 --- a/src/package-json-file-service.ts +++ b/src/package-json-file-service.ts @@ -1,5 +1,5 @@ import { IPackageJSONService, IPackageFile } from './types' -import * as fs from 'fs' +import * as fs from 'node:fs' import { promisify } from './util' import detectIndent from 'detect-indent' From 7324560fb360fadeaf54b52be3c392ec944b5c6e Mon Sep 17 00:00:00 2001 From: Eli <88557639+lishaduck@users.noreply.github.com> Date: Tue, 16 Jul 2024 21:12:49 -0500 Subject: [PATCH 2/9] feat: support pnpm monorepos --- .typesyncrc.yaml | 2 +- package-lock.json | 221 +++++++++--------------------- package.json | 3 +- src/__tests__/type-syncer.test.ts | 6 + src/package-json-file-service.ts | 19 ++- src/type-syncer.ts | 11 +- src/types.ts | 20 ++- src/util.ts | 4 +- 8 files changed, 122 insertions(+), 164 deletions(-) diff --git a/.typesyncrc.yaml b/.typesyncrc.yaml index 4b7aa3b..b7e8bc3 100644 --- a/.typesyncrc.yaml +++ b/.typesyncrc.yaml @@ -1,5 +1,5 @@ -# These packages are dev tools and we don't need their typings. ignorePackages: + # These packages are dev tools and we don't need their typings. - nodemon - prettier - rimraf diff --git a/package-lock.json b/package-lock.json index 0a94f40..2b49bbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "cosmiconfig": "^9.0.0", "detect-indent": "^6.0.0", "glob": "^10.4.2", + "js-yaml": "^4.1.0", "npm-registry-fetch": "^17.1.0", "ora": "^5.1.0", "semver": "^7.6.2" @@ -22,8 +23,8 @@ "typesync": "bin/typesync" }, "devDependencies": { - "@types/glob": "^8.1.0", "@types/jest": "^29.5.12", + "@types/js-yaml": "~4.0.9 || ~4.1.0", "@types/node": "~16.18.102", "@types/npm-registry-fetch": "^8.0.7", "@types/semver": "^7.5.8", @@ -756,12 +757,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/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==", - "dev": true - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -777,18 +772,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/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==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -955,6 +938,28 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -1476,16 +1481,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dev": true, - "dependencies": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1530,10 +1525,10 @@ "pretty-format": "^29.0.0" } }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true }, "node_modules/@types/node": { @@ -1956,13 +1951,9 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } + "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/array-union": { "version": "2.1.0", @@ -2465,22 +2456,6 @@ } } }, - "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-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2787,12 +2762,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/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==", - "dev": true - }, "node_modules/eslint/node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2860,18 +2829,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/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==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4387,13 +4344,11 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -5667,7 +5622,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "node_modules/ssri": { @@ -6836,12 +6791,6 @@ "strip-json-comments": "^3.1.1" }, "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==", - "dev": true - }, "globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -6851,15 +6800,6 @@ "type-fest": "^0.20.2" } }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -6968,6 +6908,25 @@ "resolve-from": "^5.0.0" }, "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -7388,16 +7347,6 @@ "@babel/types": "^7.20.7" } }, - "@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dev": true, - "requires": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, "@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -7441,10 +7390,10 @@ "pretty-format": "^29.0.0" } }, - "@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true }, "@types/node": { @@ -7727,13 +7676,9 @@ } }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-union": { "version": "2.1.0", @@ -8088,21 +8033,6 @@ "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.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-jest": { @@ -8303,12 +8233,6 @@ "text-table": "^0.2.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==", - "dev": true - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -8352,15 +8276,6 @@ "type-fest": "^0.20.2" } }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -9508,13 +9423,11 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "jsbn": { @@ -10436,7 +10349,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, "ssri": { diff --git a/package.json b/package.json index 4a32104..33bec9a 100644 --- a/package.json +++ b/package.json @@ -52,13 +52,14 @@ "cosmiconfig": "^9.0.0", "detect-indent": "^6.0.0", "glob": "^10.4.2", + "js-yaml": "^4.1.0", "npm-registry-fetch": "^17.1.0", "ora": "^5.1.0", "semver": "^7.6.2" }, "devDependencies": { - "@types/glob": "^8.1.0", "@types/jest": "^29.5.12", + "@types/js-yaml": "~4.0.9 || ~4.1.0", "@types/node": "~16.18.102", "@types/npm-registry-fetch": "^8.0.7", "@types/semver": "^7.5.8", diff --git a/src/__tests__/type-syncer.test.ts b/src/__tests__/type-syncer.test.ts index 6f6e247..c3379b8 100644 --- a/src/__tests__/type-syncer.test.ts +++ b/src/__tests__/type-syncer.test.ts @@ -143,6 +143,12 @@ function buildSyncer() { } }), writePackageFile: jest.fn(() => Promise.resolve()), + readPnpmWorkspaceFile: jest.fn( + async () => + ({ + hasWorkspacesConfig: false, + }) as const, + ), } const globber: IGlobber = { diff --git a/src/package-json-file-service.ts b/src/package-json-file-service.ts index 66f98c4..d42d751 100644 --- a/src/package-json-file-service.ts +++ b/src/package-json-file-service.ts @@ -1,7 +1,12 @@ -import { IPackageJSONService, IPackageFile } from './types' +import { + IPackageJSONService, + IPackageFile, + type IYarnPnpmWorkspacesConfig, +} from './types' import * as fs from 'node:fs' import { promisify } from './util' import detectIndent from 'detect-indent' +import yaml from 'js-yaml' const statAsync = promisify(fs.stat) const readFileAsync = promisify(fs.readFile) @@ -26,6 +31,18 @@ export function createPackageJSONFileService(): IPackageJSONService { ) await writeFileAsync(filePath, data + (trailingNewline ? '\n' : '')) }, + readPnpmWorkspaceFile: async (filePath) => { + try { + const contents = await readFileContents(filePath) + + return { + hasWorkspacesConfig: true, + contents: yaml.load(contents) as IYarnPnpmWorkspacesConfig, + } + } catch (err) { + return { hasWorkspacesConfig: false } + } + }, } } diff --git a/src/type-syncer.ts b/src/type-syncer.ts index 9611c8e..c89e93c 100644 --- a/src/type-syncer.ts +++ b/src/type-syncer.ts @@ -25,6 +25,7 @@ import { } from './util' import { IGlobber } from './globber' import { getClosestMatchingVersion } from './versioning' +import * as path from 'node:path' /** * Creates a type syncer. @@ -51,9 +52,14 @@ export function createTypeSyncer( filePath: string, flags: ICLIArguments['flags'], ): Promise { + const pnpmWorkspaceFilename = path.relative( + path.dirname(filePath), + 'pnpm-workspace.yaml', + ) const dryRun = !!flags.dry - const [file, syncOpts] = await Promise.all([ + const [file, pnpmWorkspaces, syncOpts] = await Promise.all([ packageJSONService.readPackageFile(filePath), + packageJSONService.readPnpmWorkspaceFile(pnpmWorkspaceFilename), configService.readConfig(filePath, flags), ]) @@ -61,6 +67,9 @@ export function createTypeSyncer( [ ...ensureWorkspacesArray(file.packages), ...ensureWorkspacesArray(file.workspaces), + ...(pnpmWorkspaces.hasWorkspacesConfig === true + ? ensureWorkspacesArray(pnpmWorkspaces.contents) + : []), ].map(globber.globPackageFiles), ) .then(flatten) diff --git a/src/types.ts b/src/types.ts index c70d8bc..b89ac76 100644 --- a/src/types.ts +++ b/src/types.ts @@ -56,6 +56,16 @@ export interface IPackageJSONService { * Writes the JSON to the specified file. */ writePackageFile(filePath: string, fileContents: IPackageFile): Promise + + /** + * Reads and parses YAML from the specified file. Path is relative to the current working directory. + */ + readPnpmWorkspaceFile( + filePath: string, + ): Promise< + | { hasWorkspacesConfig: true; contents: IYarnPnpmWorkspacesConfig } + | { hasWorkspacesConfig: false } + > } /** @@ -86,8 +96,8 @@ export interface IPackageFile { peerDependencies?: IDependenciesSection optionalDependencies?: IDependenciesSection packages?: IWorkspacesSection - workspaces?: IWorkspacesSection | IYarnWorkspacesConfig - [key: string]: any + workspaces?: IWorkspacesSection | IYarnPnpmWorkspacesConfig + [key: string]: unknown } /** @@ -104,9 +114,11 @@ export type IWorkspacesSection = Array /** * Yarn is a special snowflake. + * So is PNPM. */ -export interface IYarnWorkspacesConfig { - packages: IWorkspacesSection +export interface IYarnPnpmWorkspacesConfig { + packages: IWorkspacesSection & 'yarn' + [key: string]: unknown } /** diff --git a/src/util.ts b/src/util.ts index 60af0a1..738faec 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,4 @@ -import { IWorkspacesSection, IYarnWorkspacesConfig } from './types' +import { IWorkspacesSection, IYarnPnpmWorkspacesConfig } from './types' /** * Returns unique items. @@ -182,7 +182,7 @@ export function memoizeAsync( * @param data */ export function ensureWorkspacesArray( - data?: IWorkspacesSection | IYarnWorkspacesConfig, + data?: IWorkspacesSection | IYarnPnpmWorkspacesConfig, ): IWorkspacesSection { if (!data) { return [] From f064c5937f98208cca30a74df7b82a42bd25d7ed Mon Sep 17 00:00:00 2001 From: Eli <88557639+lishaduck@users.noreply.github.com> Date: Wed, 17 Jul 2024 20:13:00 -0500 Subject: [PATCH 3/9] refactor: general changes A bunch of stuff that was (and wasn't) needed for abstracting workspaces. --- src/__tests__/config-service.test.ts | 7 +-- .../package-json-file-service.test.ts | 23 ++++---- src/__tests__/type-syncer.test.ts | 26 ++++----- src/__tests__/util.test.ts | 53 +++++-------------- src/__tests__/versioning.test.ts | 2 +- src/cli-util.ts | 2 +- src/cli.ts | 14 +++-- src/config-service.ts | 6 +-- src/fs-utils.ts | 25 +++++++++ src/globber.ts | 19 ++++--- src/index.ts | 4 +- src/package-json-file-service.ts | 44 +++------------ src/package-source.ts | 2 +- src/type-syncer.ts | 48 ++++++++--------- src/types.ts | 2 +- src/util.ts | 46 +--------------- src/versioning.ts | 2 +- 17 files changed, 132 insertions(+), 193 deletions(-) create mode 100644 src/fs-utils.ts diff --git a/src/__tests__/config-service.test.ts b/src/__tests__/config-service.test.ts index e5e26c7..fcc2519 100644 --- a/src/__tests__/config-service.test.ts +++ b/src/__tests__/config-service.test.ts @@ -1,5 +1,6 @@ import * as path from 'node:path' import { createConfigService } from '../config-service' +import { IDependencySection } from '../types' const testDirectory = path.resolve(__dirname, 'fixtures') @@ -11,16 +12,16 @@ describe('config service', () => { it('should load ".typesyncrc"', async () => { const config = await subject.readConfig(filepath, {}) - expect(config.ignoreDeps).toEqual(['dev']) + expect(config.ignoreDeps).toEqual([IDependencySection.dev]) expect(config.ignorePackages).toEqual(['package1']) }) it('should read from cli args', async () => { const config = await subject.readConfig(filepath, { - ignoredeps: 'dev', + ignoredeps: IDependencySection.dev, ignorepackages: 'package1', }) - expect(config.ignoreDeps).toEqual(['dev']) + expect(config.ignoreDeps).toEqual([IDependencySection.dev]) expect(config.ignorePackages).toEqual(['package1']) }) diff --git a/src/__tests__/package-json-file-service.test.ts b/src/__tests__/package-json-file-service.test.ts index 93f9fc5..7981cfe 100644 --- a/src/__tests__/package-json-file-service.test.ts +++ b/src/__tests__/package-json-file-service.test.ts @@ -1,12 +1,7 @@ import { createPackageJSONFileService } from '../package-json-file-service' import * as os from 'node:os' import * as path from 'node:path' -import * as fs from 'node:fs' -import { promisify } from '../util' - -const writeFileAsync = promisify(fs.writeFile) -const readFileAsync = promisify(fs.readFile) -const unlinkAsync = promisify(fs.unlink) +import * as fsp from 'node:fs/promises' describe('package json file service', () => { const subject = createPackageJSONFileService() @@ -60,8 +55,8 @@ describe('package json file service', () => { ]) const [noNewlineContent, withNewlineContent] = await Promise.all([ - readFileAsync(noNewline).then((x) => x.toString()), - readFileAsync(withNewline).then((x) => x.toString()), + fsp.readFile(noNewline).then((x) => x.toString()), + fsp.readFile(withNewline).then((x) => x.toString()), ]) expect(noNewlineContent[noNewlineContent.length - 1]).not.toBe('\n') @@ -71,15 +66,15 @@ describe('package json file service', () => { it('does not fail when writing to an empty file', async () => { const file = path.join(os.tmpdir(), `package-${Math.random()}.json`) - await writeFileAsync(file, '') + await fsp.writeFile(file, '') await subject.writePackageFile(file, { name: 'test' }) }) }) }) -function _writeFixture(withTrailingNewline = false): Promise { +async function _writeFixture(withTrailingNewline = false): Promise { const file = path.join(os.tmpdir(), `package-${Math.random()}.json`) - return writeFileAsync( + await fsp.writeFile( file, JSON.stringify( { @@ -91,9 +86,11 @@ function _writeFixture(withTrailingNewline = false): Promise { null, 2, ) + (withTrailingNewline ? '\n' : ''), - ).then(() => file) + ) + + return file } function cleanup(...files: string[]): Promise { - return Promise.all(files.map((f) => unlinkAsync(f))) + return Promise.all(files.map((f) => fsp.unlink(f))) } diff --git a/src/__tests__/type-syncer.test.ts b/src/__tests__/type-syncer.test.ts index c3379b8..505bf6c 100644 --- a/src/__tests__/type-syncer.test.ts +++ b/src/__tests__/type-syncer.test.ts @@ -1,14 +1,14 @@ +import type { IGlobber } from '../globber' +import { createTypeSyncer } from '../type-syncer' import { - IPackageJSONService, - IPackageTypingDescriptor, - IPackageFile, - IPackageSource, - IPackageInfo, - IConfigService, - ISyncOptions, + type IConfigService, + IDependencySection, + type IPackageFile, + type IPackageInfo, + type IPackageJSONService, + type IPackageSource, + type IPackageTypingDescriptor, } from '../types' -import { createTypeSyncer } from '../type-syncer' -import { IGlobber } from '../globber' const descriptors: IPackageTypingDescriptor[] = [ { @@ -201,7 +201,7 @@ function buildSyncer() { return {} case 'package-ignore-dev.json': case 'package-ignore-dev-synced.json': - return { ignoreDeps: ['dev'] } as ISyncOptions + return { ignoreDeps: [IDependencySection.dev] } case 'package-ignore-package1.json': return { ignorePackages: ['package1'] } default: @@ -321,11 +321,13 @@ describe('type syncer', () => { it('does not write packages if options.dry is specified', async () => { const { syncer, packageService } = buildSyncer() await syncer.sync('package.json', { dry: true }) - expect(packageService.writePackageFile as jest.Mock).not.toBeCalled() + expect( + packageService.writePackageFile as jest.Mock, + ).not.toHaveBeenCalled() }) it('does not detect diff when already synced', async () => { - const { syncer, packageService } = buildSyncer() + const { syncer } = buildSyncer() const { syncedFiles } = await syncer.sync( 'package-ignore-dev-synced.json', {}, diff --git a/src/__tests__/util.test.ts b/src/__tests__/util.test.ts index d9ddb30..4aabef8 100644 --- a/src/__tests__/util.test.ts +++ b/src/__tests__/util.test.ts @@ -1,14 +1,13 @@ import { - uniq, + ensureWorkspacesArray, filterMap, - shrinkObject, + memoizeAsync, mergeObjects, orderObject, - promisify, - memoizeAsync, - ensureWorkspacesArray, - untyped, + shrinkObject, typed, + uniq, + untyped, } from '../util' describe('util', () => { @@ -55,39 +54,6 @@ describe('util', () => { }) }) - describe('promisify', () => { - it('resolves when the callback is successful', async () => { - const original = (arg1: any, arg2: any, cb: Function) => - cb(null, arg1 + arg2) - const promisified = promisify(original) - const result = await promisified(2, 2) - expect(result).toBe(4) - }) - - it('resolves when the callback is invoked with a single bool argument', async () => { - const original = (arg1: any, cb: Function) => cb(arg1) - const promisified = promisify(original) - const result = await promisified(true) - expect(result).toBe(true) - }) - - it('rejects when the callback is not successful', async () => { - const original = (arg1: any, arg2: any, cb: Function) => - cb(new Error('oh shit'), null) - const promisified = promisify(original) - const err = await promisified(2, 2).catch((err) => err) - expect(err.message).toBe('oh shit') - }) - - it('rejects when the callback is not successful even if only passed 1 param', async () => { - const original = (arg1: any, arg2: any, cb: Function) => - cb(new Error('oh shit')) - const promisified = promisify(original) - const err = await promisified(2, 2).catch((err) => err) - expect(err.message).toBe('oh shit') - }) - }) - describe('memoizeAsync', () => { it('memoizes promises', async () => { let i = 0 @@ -127,6 +93,15 @@ describe('util', () => { 'lol', ]) }) + it("handles Yarn's weird format", () => { + expect(ensureWorkspacesArray({ packages: [] })).toEqual([]) + }) + it('handles an array of globs', () => { + expect(ensureWorkspacesArray(['packages/*'])).toEqual(['packages/*']) + }) + it('handles no workspaces', () => { + expect(ensureWorkspacesArray(undefined)).toEqual([]) + }) }) describe('typed', () => { diff --git a/src/__tests__/versioning.test.ts b/src/__tests__/versioning.test.ts index 616bd11..cec6d04 100644 --- a/src/__tests__/versioning.test.ts +++ b/src/__tests__/versioning.test.ts @@ -1,4 +1,4 @@ -import { IPackageVersionInfo } from '../types' +import type { IPackageVersionInfo } from '../types' import { getClosestMatchingVersion } from '../versioning' describe('getClosestMatchingVersion', () => { diff --git a/src/cli-util.ts b/src/cli-util.ts index 7f51a51..3095338 100644 --- a/src/cli-util.ts +++ b/src/cli-util.ts @@ -1,6 +1,6 @@ import chalk from 'chalk' -import { ICLIArguments } from './types' import ora from 'ora' +import type { ICLIArguments } from './types' /** * Like regular console.log, but better. diff --git a/src/cli.ts b/src/cli.ts index 9d1e7b5..c0ee3d1 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,13 +1,17 @@ -import { createContainer, InjectionMode, asFunction } from 'awilix' -import chalk from 'chalk' import * as path from 'node:path' +import { asFunction, createContainer, InjectionMode } from 'awilix' +import chalk from 'chalk' import * as C from './cli-util' -import { ITypeSyncer, IPackageTypingDescriptor, ISyncedFile } from './types' -import { createTypeSyncer } from './type-syncer' -import { createPackageJSONFileService } from './package-json-file-service' import { createConfigService } from './config-service' import { createGlobber } from './globber' +import { createPackageJSONFileService } from './package-json-file-service' import { createPackageSource } from './package-source' +import { createTypeSyncer } from './type-syncer' +import type { + IPackageTypingDescriptor, + ISyncedFile, + ITypeSyncer, +} from './types' /** * Starts the TypeSync CLI. diff --git a/src/config-service.ts b/src/config-service.ts index c4e3723..b2a6eae 100644 --- a/src/config-service.ts +++ b/src/config-service.ts @@ -1,10 +1,10 @@ import * as path from 'node:path' import { cosmiconfig } from 'cosmiconfig' import { - IConfigService, - ISyncOptions, + type ICLIArguments, + type IConfigService, IDependencySection, - ICLIArguments, + type ISyncOptions, } from './types' import { shrinkObject } from './util' diff --git a/src/fs-utils.ts b/src/fs-utils.ts new file mode 100644 index 0000000..4dc666e --- /dev/null +++ b/src/fs-utils.ts @@ -0,0 +1,25 @@ +import { readFile, stat } from 'node:fs/promises' + +export async function readFileContents(filePath: string) { + await assertFile(filePath) + return readFile(filePath, 'utf-8') +} + +async function assertFile(filePath: string) { + if (!(await existsAsync(filePath))) { + throw new Error(`${filePath} does not exist.`) + } +} + +async function existsAsync(filePath: string): Promise { + return stat(filePath) + .then(() => true) + .catch((err) => { + /* istanbul ignore else */ + if (err.code === 'ENOENT') { + return false + } + /* istanbul ignore next */ + throw err + }) +} diff --git a/src/globber.ts b/src/globber.ts index 89eb408..68fbeec 100644 --- a/src/globber.ts +++ b/src/globber.ts @@ -1,5 +1,5 @@ -import { glob } from 'glob' import * as path from 'node:path' +import { glob } from 'glob' import { uniq } from './util' /** @@ -7,11 +7,11 @@ import { uniq } from './util' */ export interface IGlobber { /** - * Globs for package.json files. + * Globs for `package.json` files. * - * @param pattern + * @param root */ - globPackageFiles(pattern: string): Promise> + globPackageFiles(root: string): Promise> } /** @@ -19,10 +19,15 @@ export interface IGlobber { */ export function createGlobber() { return { - globPackageFiles(pattern: string) { - return glob(path.join(pattern, 'package.json'), { + async globPackageFiles( + root: string, + file = 'package.json', + ): Promise> { + const source = await glob(path.join(root, file), { ignore: '**/node_modules/**', - }).then(uniq) + }) + + return uniq(source) }, } } diff --git a/src/index.ts b/src/index.ts index 5cf456e..e53d8e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export * from './type-syncer' export * from './config-service' -export * from './types' export * from './package-source' +export * from './type-syncer' +export * from './types' diff --git a/src/package-json-file-service.ts b/src/package-json-file-service.ts index d42d751..0e09846 100644 --- a/src/package-json-file-service.ts +++ b/src/package-json-file-service.ts @@ -1,16 +1,12 @@ -import { - IPackageJSONService, - IPackageFile, - type IYarnPnpmWorkspacesConfig, -} from './types' -import * as fs from 'node:fs' -import { promisify } from './util' +import * as fsp from 'node:fs/promises' import detectIndent from 'detect-indent' import yaml from 'js-yaml' - -const statAsync = promisify(fs.stat) -const readFileAsync = promisify(fs.readFile) -const writeFileAsync = promisify(fs.writeFile) +import { readFileContents } from './fs-utils' +import type { + IPackageFile, + IPackageJSONService, + IYarnPnpmWorkspacesConfig, +} from './types' export function createPackageJSONFileService(): IPackageJSONService { return { @@ -29,7 +25,7 @@ export function createPackageJSONFileService(): IPackageJSONService { null, indent /* istanbul ignore next */ || ' ', ) - await writeFileAsync(filePath, data + (trailingNewline ? '\n' : '')) + await fsp.writeFile(filePath, data + (trailingNewline ? '\n' : '')) }, readPnpmWorkspaceFile: async (filePath) => { try { @@ -45,27 +41,3 @@ export function createPackageJSONFileService(): IPackageJSONService { }, } } - -async function readFileContents(filePath: string) { - await assertFile(filePath) - return readFileAsync(filePath, 'utf-8').then((x: Buffer) => x.toString()) -} - -async function assertFile(filePath: string) { - if (!(await existsAsync(filePath))) { - throw new Error(`${filePath} does not exist.`) - } -} - -async function existsAsync(filePath: string): Promise { - return statAsync(filePath) - .then(() => true) - .catch((err) => { - /* istanbul ignore else */ - if (err.code === 'ENOENT') { - return false - } - /* istanbul ignore next */ - throw err - }) -} diff --git a/src/package-source.ts b/src/package-source.ts index 9f46543..803535d 100644 --- a/src/package-source.ts +++ b/src/package-source.ts @@ -1,6 +1,6 @@ -import { IPackageSource, IPackageVersionInfo } from './types' import fetch from 'npm-registry-fetch' import { compare } from 'semver' +import type { IPackageSource, IPackageVersionInfo } from './types' /** * Creates a package source. diff --git a/src/type-syncer.ts b/src/type-syncer.ts index c89e93c..bf4e46b 100644 --- a/src/type-syncer.ts +++ b/src/type-syncer.ts @@ -1,31 +1,30 @@ +import * as path from 'node:path' +import type { IGlobber } from './globber' import { - ITypeSyncer, - IPackageJSONService, - IPackageTypingDescriptor, - IPackageFile, - ISyncOptions, - IDependenciesSection, - IPackageVersion, - ISyncResult, - ISyncedFile, - IPackageSource, - IConfigService, + type ICLIArguments, + type IConfigService, + type IDependenciesSection, IDependencySection, - ICLIArguments, + type IPackageFile, + type IPackageJSONService, + type IPackageSource, + type IPackageTypingDescriptor, + type IPackageVersion, + type ISyncOptions, + type ISyncResult, + type ISyncedFile, + type ITypeSyncer, } from './types' import { + ensureWorkspacesArray, filterMap, + memoizeAsync, mergeObjects, - typed, orderObject, + typed, uniq, - flatten, - memoizeAsync, - ensureWorkspacesArray, } from './util' -import { IGlobber } from './globber' import { getClosestMatchingVersion } from './versioning' -import * as path from 'node:path' /** * Creates a type syncer. @@ -72,7 +71,7 @@ export function createTypeSyncer( : []), ].map(globber.globPackageFiles), ) - .then(flatten) + .then((arr) => arr.flat()) .then(uniq) const syncedFiles: Array = await Promise.all([ @@ -103,19 +102,20 @@ export function createTypeSyncer( const packageFile = file || (await packageJSONService.readPackageFile(filePath)) - const allLocalPackages = flatten( - Object.values(IDependencySection).map((dep) => { + const allLocalPackages = Object.values(IDependencySection) + .map((dep) => { const section = getDependenciesBySection(packageFile, dep) const ignoredSection = ignoreDeps?.includes(dep) return getPackagesFromSection(section, ignoredSection, ignorePackages) - }), - ) + }) + .flat() const allPackageNames = uniq(allLocalPackages.map((p) => p.name)) const potentiallyUntypedPackages = getPotentiallyUntypedPackages(allPackageNames) + // This is pushed to in the inner `map`, because packages that have DT-typings // *as well* as internal typings should be excluded. - const used: Array[0]> = [] + const used: ReturnType = [] const devDepsToAdd = await Promise.all( potentiallyUntypedPackages.map(async (t) => { // Fetch the code package from the source. diff --git a/src/types.ts b/src/types.ts index b89ac76..fc44937 100644 --- a/src/types.ts +++ b/src/types.ts @@ -117,7 +117,7 @@ export type IWorkspacesSection = Array * So is PNPM. */ export interface IYarnPnpmWorkspacesConfig { - packages: IWorkspacesSection & 'yarn' + packages: IWorkspacesSection [key: string]: unknown } diff --git a/src/util.ts b/src/util.ts index 738faec..8fe7450 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,4 @@ -import { IWorkspacesSection, IYarnPnpmWorkspacesConfig } from './types' +import type { IWorkspacesSection, IYarnPnpmWorkspacesConfig } from './types' /** * Returns unique items. @@ -6,15 +6,7 @@ import { IWorkspacesSection, IYarnPnpmWorkspacesConfig } from './types' * @param source The source to filter */ export function uniq(source: Array): Array { - const seen: T[] = [] - for (const s of source) { - if (seen.includes(s)) { - continue - } - seen.push(s) - } - - return seen + return [...new Set(source)] } /** @@ -111,40 +103,6 @@ export function orderObject< return result as T } -/** - * Promisifies the specified function. - * - * @param fn - */ -export function promisify(fn: (...args: any[]) => void) { - return function promisified(...args: any[]) { - return new Promise((resolve, reject) => { - fn(...args, function callback(err: any, result: any) { - // Edge case with `fs.exists`. - if (arguments.length === 1 && typeof err === 'boolean') { - return resolve(err) - } - return !err ? resolve(result) : reject(err) - }) - }) - } -} - -/** - * Flattens a 2-dimensional array. - * - * @param source - */ -export function flatten(source: Array>): Array { - const result: Array = [] - for (const items of source) { - for (const item of items) { - result.push(item) - } - } - return result -} - /** * Async memoize. * diff --git a/src/versioning.ts b/src/versioning.ts index c961430..350716c 100644 --- a/src/versioning.ts +++ b/src/versioning.ts @@ -1,5 +1,5 @@ -import { IPackageVersionInfo } from './types' import { parse } from 'semver' +import type { IPackageVersionInfo } from './types' /** * Gets the closest matching package version info. From 7baedc912da0a2efc769deded8ed2e31429a319b Mon Sep 17 00:00:00 2001 From: Eli <88557639+lishaduck@users.noreply.github.com> Date: Wed, 17 Jul 2024 20:13:36 -0500 Subject: [PATCH 4/9] feat: rough workspace resolver It's not yet the most efficient, but it passes the test suite! --- src/__tests__/type-syncer.test.ts | 53 +++++++++++++---- src/cli.ts | 6 +- src/package-json-file-service.ts | 19 +------ src/type-syncer.ts | 23 ++------ src/types.ts | 95 +++++++++++++++++++++++++------ src/util.ts | 6 +- src/workspace-resolver.ts | 67 ++++++++++++++++++++++ 7 files changed, 199 insertions(+), 70 deletions(-) create mode 100644 src/workspace-resolver.ts diff --git a/src/__tests__/type-syncer.test.ts b/src/__tests__/type-syncer.test.ts index 505bf6c..7a66538 100644 --- a/src/__tests__/type-syncer.test.ts +++ b/src/__tests__/type-syncer.test.ts @@ -8,6 +8,8 @@ import { type IPackageJSONService, type IPackageSource, type IPackageTypingDescriptor, + type IWorkspaceResolverService, + type IWorkspacesArray, } from '../types' const descriptors: IPackageTypingDescriptor[] = [ @@ -69,8 +71,12 @@ const descriptors: IPackageTypingDescriptor[] = [ }, ] +interface ITestPackageFile extends IPackageFile { + workspaces?: IWorkspacesArray +} + function buildSyncer() { - const rootPackageFile: IPackageFile = { + const rootPackageFile: ITestPackageFile = { name: 'consumer', dependencies: { package1: '^1.0.0', @@ -97,7 +103,7 @@ function buildSyncer() { } // synced package file with ignoreDeps: dev - const syncedPackageFile: IPackageFile = { + const syncedPackageFile: ITestPackageFile = { ...rootPackageFile, devDependencies: { '@types/package1': '^1.0.0', @@ -111,14 +117,14 @@ function buildSyncer() { }, } - const package1File: IPackageFile = { + const package1File: ITestPackageFile = { name: 'package-1', dependencies: { package1: '^1.0.0', }, } - const package2File: IPackageFile = { + const package2File: ITestPackageFile = { name: 'package-1', dependencies: { package3: '^1.0.0', @@ -139,16 +145,39 @@ function buildSyncer() { case 'packages/package-2/package.json': return package2File default: - throw new Error('What?!') + throw new Error(`Who?! ${filepath}`) } }), writePackageFile: jest.fn(() => Promise.resolve()), - readPnpmWorkspaceFile: jest.fn( - async () => - ({ - hasWorkspacesConfig: false, - }) as const, - ), + } + + const workspaceResolverService: IWorkspaceResolverService = { + getWorkspaces: jest.fn(async (root, globber) => { + let workspaces: IWorkspacesArray | undefined + + switch (root) { + case '.': { + workspaces = rootPackageFile.workspaces + break + } + case 'packages/package-1/': { + workspaces = package1File.workspaces + break + } + case 'packages/package-2/': { + workspaces = package2File.workspaces + break + } + default: + throw new Error('What?!') + } + + workspaces ??= [] + const globPromises = workspaces.map((w) => globber.globPackageFiles(w)) + const globbed = await Promise.all(globPromises) + + return globbed.flat() + }), } const globber: IGlobber = { @@ -217,6 +246,7 @@ function buildSyncer() { configService, syncer: createTypeSyncer( packageService, + workspaceResolverService, packageSource, configService, globber, @@ -243,6 +273,7 @@ describe('type syncer', () => { package4: '^1.0.0', package5: '^1.0.0', }) + expect(result.syncedFiles).toHaveLength(3) expect(result.syncedFiles[0].filePath).toEqual('package.json') diff --git a/src/cli.ts b/src/cli.ts index c0ee3d1..be4876f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -12,6 +12,7 @@ import type { ISyncedFile, ITypeSyncer, } from './types' +import { createWorkspaceResolverService } from './workspace-resolver' /** * Starts the TypeSync CLI. @@ -23,6 +24,9 @@ export async function startCli() { injectionMode: InjectionMode.CLASSIC, }).register({ packageJSONService: asFunction(createPackageJSONFileService).singleton(), + workspaceResolverService: asFunction( + createWorkspaceResolverService, + ).singleton(), packageSource: asFunction(createPackageSource).singleton(), configService: asFunction(createConfigService).singleton(), globber: asFunction(createGlobber).singleton(), @@ -79,7 +83,7 @@ async function run(syncer: ITypeSyncer) { ? `No new typings to add, looks like you're all synced up!` : flags.dry ? chalk`${totals.newTypings.toString()} new typings can be added.\n\n${syncedFilesOutput}\n\n✨ Run {green typesync} again without the {gray --dry} flag to update your {gray package.json}.` - : chalk`${totals.newTypings.toString()} new typings added.\n\n${syncedFilesOutput}\n\n✨ Go ahead and run {green npm install} or {green yarn} to install the packages that were added.`, + : chalk`${totals.newTypings.toString()} new typings added.\n\n${syncedFilesOutput}\n\n✨ Go ahead and run {green npm install}, {green yarn}, or {green pnpm i} to install the packages that were added.`, ) } diff --git a/src/package-json-file-service.ts b/src/package-json-file-service.ts index 0e09846..862d8e6 100644 --- a/src/package-json-file-service.ts +++ b/src/package-json-file-service.ts @@ -1,12 +1,7 @@ import * as fsp from 'node:fs/promises' import detectIndent from 'detect-indent' -import yaml from 'js-yaml' import { readFileContents } from './fs-utils' -import type { - IPackageFile, - IPackageJSONService, - IYarnPnpmWorkspacesConfig, -} from './types' +import { IPackageJSONService, IPackageFile } from './types' export function createPackageJSONFileService(): IPackageJSONService { return { @@ -27,17 +22,5 @@ export function createPackageJSONFileService(): IPackageJSONService { ) await fsp.writeFile(filePath, data + (trailingNewline ? '\n' : '')) }, - readPnpmWorkspaceFile: async (filePath) => { - try { - const contents = await readFileContents(filePath) - - return { - hasWorkspacesConfig: true, - contents: yaml.load(contents) as IYarnPnpmWorkspacesConfig, - } - } catch (err) { - return { hasWorkspacesConfig: false } - } - }, } } diff --git a/src/type-syncer.ts b/src/type-syncer.ts index bf4e46b..2e8291f 100644 --- a/src/type-syncer.ts +++ b/src/type-syncer.ts @@ -14,9 +14,9 @@ import { type ISyncResult, type ISyncedFile, type ITypeSyncer, + type IWorkspaceResolverService, } from './types' import { - ensureWorkspacesArray, filterMap, memoizeAsync, mergeObjects, @@ -34,6 +34,7 @@ import { getClosestMatchingVersion } from './versioning' */ export function createTypeSyncer( packageJSONService: IPackageJSONService, + workspaceResolverService: IWorkspaceResolverService, packageSource: IPackageSource, configService: IConfigService, globber: IGlobber, @@ -51,29 +52,13 @@ export function createTypeSyncer( filePath: string, flags: ICLIArguments['flags'], ): Promise { - const pnpmWorkspaceFilename = path.relative( - path.dirname(filePath), - 'pnpm-workspace.yaml', - ) const dryRun = !!flags.dry - const [file, pnpmWorkspaces, syncOpts] = await Promise.all([ + const [file, subPackages, syncOpts] = await Promise.all([ packageJSONService.readPackageFile(filePath), - packageJSONService.readPnpmWorkspaceFile(pnpmWorkspaceFilename), + workspaceResolverService.getWorkspaces(path.dirname(filePath), globber), configService.readConfig(filePath, flags), ]) - const subPackages = await Promise.all( - [ - ...ensureWorkspacesArray(file.packages), - ...ensureWorkspacesArray(file.workspaces), - ...(pnpmWorkspaces.hasWorkspacesConfig === true - ? ensureWorkspacesArray(pnpmWorkspaces.contents) - : []), - ].map(globber.globPackageFiles), - ) - .then((arr) => arr.flat()) - .then(uniq) - const syncedFiles: Array = await Promise.all([ syncFile(filePath, file, syncOpts, dryRun), ...subPackages.map((p) => syncFile(p, null, syncOpts, dryRun)), diff --git a/src/types.ts b/src/types.ts index fc44937..2e7fa09 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import type { IGlobber } from './globber' + /** * The guts of the program. */ @@ -56,16 +58,6 @@ export interface IPackageJSONService { * Writes the JSON to the specified file. */ writePackageFile(filePath: string, fileContents: IPackageFile): Promise - - /** - * Reads and parses YAML from the specified file. Path is relative to the current working directory. - */ - readPnpmWorkspaceFile( - filePath: string, - ): Promise< - | { hasWorkspacesConfig: true; contents: IYarnPnpmWorkspacesConfig } - | { hasWorkspacesConfig: false } - > } /** @@ -95,8 +87,7 @@ export interface IPackageFile { devDependencies?: IDependenciesSection peerDependencies?: IDependenciesSection optionalDependencies?: IDependenciesSection - packages?: IWorkspacesSection - workspaces?: IWorkspacesSection | IYarnPnpmWorkspacesConfig + workspaces?: IWorkspacesSection [key: string]: unknown } @@ -108,17 +99,85 @@ export interface IDependenciesSection { } /** - * Section in package.json representing workspaces (yarn/lerna). + * @example + * ```json + * "workspaces": [ + * "packages/*", + * ] + * ``` + */ +export type IWorkspacesArray = Array + +/** + * @example + * ```yaml + * projects: + * - 'packages/*' + * ``` + */ +export type IWorkspacesObject = { + packages: IWorkspacesArray +} + +/** + * @see {@link IWorkspacesArray} */ -export type IWorkspacesSection = Array +type NpmWorkspacesConfig = IWorkspacesArray /** * Yarn is a special snowflake. - * So is PNPM. + * + * @example + * ```json + * "workspaces": { + * "packages": [ + * "packages/*", + * ], + * "nohoist": [] + * } + * ``` */ -export interface IYarnPnpmWorkspacesConfig { - packages: IWorkspacesSection - [key: string]: unknown +type YarnWorkspacesConfig = + | IWorkspacesArray + | (IWorkspacesObject & { nohoist?: string[] }) + +/** + * The contents of a `pnpm-workspace.yaml` file. + * + * @example + * ```yaml + * packages: + * - 'packages/*' + * ``` + */ +export type PnpmWorkspacesConfig = IWorkspacesObject + +/** + * @see {@link IWorkspacesArray} + */ +type BunWorkspacesConfig = IWorkspacesArray + +/** + * Section in `package.json` representing workspaces. + */ +export type IWorkspacesSection = + | NpmWorkspacesConfig + | YarnWorkspacesConfig + | BunWorkspacesConfig + +/** + * Files service. + */ +export interface IWorkspaceResolverService { + /** + * Reads, parses, and normalizes a workspaces configuration from the following files, in this order: + * - `package.json` `workspaces` field, as an array of globs. + * - `package.json` `workspaces` field, as an object with a `projects` field, which is an array of globs. + * - `pnpm-workspace.yaml` `packages` field, as an array of globs. + * + * Path is relative to the current working directory. + */ + getWorkspaces(root: string, globber: IGlobber): Promise } /** diff --git a/src/util.ts b/src/util.ts index 8fe7450..ca88d88 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,4 @@ -import type { IWorkspacesSection, IYarnPnpmWorkspacesConfig } from './types' +import type { IWorkspacesSection, IWorkspacesArray } from './types' /** * Returns unique items. @@ -140,8 +140,8 @@ export function memoizeAsync( * @param data */ export function ensureWorkspacesArray( - data?: IWorkspacesSection | IYarnPnpmWorkspacesConfig, -): IWorkspacesSection { + data?: IWorkspacesSection, +): IWorkspacesArray { if (!data) { return [] } diff --git a/src/workspace-resolver.ts b/src/workspace-resolver.ts new file mode 100644 index 0000000..d502b6a --- /dev/null +++ b/src/workspace-resolver.ts @@ -0,0 +1,67 @@ +import path from 'node:path' +import yaml from 'js-yaml' +import { readFileContents } from './fs-utils' +import type { + IPackageFile, + IWorkspaceResolverService, + IWorkspacesArray, + IWorkspacesSection, + PnpmWorkspacesConfig, +} from './types' +import { ensureWorkspacesArray, uniq } from './util' + +export function createWorkspaceResolverService(): IWorkspaceResolverService { + return { + getWorkspaces: async (root, globber) => { + const workspaces = await getWorkspaces(root) + const workspacesArray = ensureWorkspacesArray(workspaces) + + const manifests = await Promise.all( + workspacesArray.map( + async (workspace) => await globber.globPackageFiles(workspace), + ), + ) + + return uniq(manifests.flat()) + }, + } +} + +async function getWorkspaces( + root: string, +): Promise { + const packageJsonWorkspaces = await getPackageJsonWorkspaces(root) + if (packageJsonWorkspaces !== undefined) { + return packageJsonWorkspaces + } + + return await getPnpmWorkspaces(root) +} + +async function getPackageJsonWorkspaces( + root: string, +): Promise { + try { + const filePath = path.relative(root, 'package.json') + const contents = await readFileContents(filePath) // TODO: Don't do this twice. + const packageJson = JSON.parse(contents) as IPackageFile + + return packageJson.workspaces + } catch { + return undefined + } +} + +async function getPnpmWorkspaces( + root: string, +): Promise { + try { + const filePath = path.relative(root, 'pnpm-workspace.yaml') + const contents = await readFileContents(filePath) + const pnpmWorkspaces = yaml.load(contents) as PnpmWorkspacesConfig + + return pnpmWorkspaces.packages + } catch { + return undefined + } +} From e391d7f60546fdba84fcd8b74037201b63a59fe5 Mon Sep 17 00:00:00 2001 From: Eli <88557639+lishaduck@users.noreply.github.com> Date: Wed, 17 Jul 2024 21:23:56 -0500 Subject: [PATCH 5/9] refactor: move interfaces out of `types.ts` This makes jump-to-definition much more useful. --- src/__tests__/type-syncer.test.ts | 13 +-- src/__tests__/versioning.test.ts | 6 +- src/config-service.ts | 14 ++- src/package-json-file-service.ts | 16 +++- src/package-source.ts | 24 ++++- src/type-syncer.ts | 8 +- src/types.ts | 141 +----------------------------- src/util.ts | 2 +- src/versioning.ts | 9 +- src/workspace-resolver.ts | 92 +++++++++++++++++-- 10 files changed, 161 insertions(+), 164 deletions(-) diff --git a/src/__tests__/type-syncer.test.ts b/src/__tests__/type-syncer.test.ts index 7a66538..c2d5ecc 100644 --- a/src/__tests__/type-syncer.test.ts +++ b/src/__tests__/type-syncer.test.ts @@ -1,16 +1,17 @@ +import type { IConfigService } from '../config-service' import type { IGlobber } from '../globber' +import type { IPackageJSONService } from '../package-json-file-service' +import type { IPackageSource, IPackageInfo } from '../package-source' import { createTypeSyncer } from '../type-syncer' import { - type IConfigService, IDependencySection, type IPackageFile, - type IPackageInfo, - type IPackageJSONService, - type IPackageSource, type IPackageTypingDescriptor, - type IWorkspaceResolverService, - type IWorkspacesArray, } from '../types' +import type { + IWorkspacesArray, + IWorkspaceResolverService, +} from '../workspace-resolver' const descriptors: IPackageTypingDescriptor[] = [ { diff --git a/src/__tests__/versioning.test.ts b/src/__tests__/versioning.test.ts index cec6d04..116363d 100644 --- a/src/__tests__/versioning.test.ts +++ b/src/__tests__/versioning.test.ts @@ -1,5 +1,7 @@ -import type { IPackageVersionInfo } from '../types' -import { getClosestMatchingVersion } from '../versioning' +import { + getClosestMatchingVersion, + type IPackageVersionInfo, +} from '../versioning' describe('getClosestMatchingVersion', () => { it('returns the closest matching version', () => { diff --git a/src/config-service.ts b/src/config-service.ts index b2a6eae..714bb55 100644 --- a/src/config-service.ts +++ b/src/config-service.ts @@ -2,12 +2,24 @@ import * as path from 'node:path' import { cosmiconfig } from 'cosmiconfig' import { type ICLIArguments, - type IConfigService, IDependencySection, type ISyncOptions, } from './types' import { shrinkObject } from './util' +/** + * Config Service. + */ +export interface IConfigService { + /** + * Get typesync config. + */ + readConfig( + filePath: string, + flags: ICLIArguments['flags'], + ): Promise +} + const explorer = cosmiconfig('typesync') export function createConfigService(): IConfigService { diff --git a/src/package-json-file-service.ts b/src/package-json-file-service.ts index 862d8e6..927e3ee 100644 --- a/src/package-json-file-service.ts +++ b/src/package-json-file-service.ts @@ -1,7 +1,21 @@ import * as fsp from 'node:fs/promises' import detectIndent from 'detect-indent' import { readFileContents } from './fs-utils' -import { IPackageJSONService, IPackageFile } from './types' +import { IPackageFile } from './types' + +/** + * File service. + */ +export interface IPackageJSONService { + /** + * Reads and parses JSON from the specified file. Path is relative to the current working directory. + */ + readPackageFile(filePath: string): Promise + /** + * Writes the JSON to the specified file. + */ + writePackageFile(filePath: string, fileContents: IPackageFile): Promise +} export function createPackageJSONFileService(): IPackageJSONService { return { diff --git a/src/package-source.ts b/src/package-source.ts index 803535d..9f8b88a 100644 --- a/src/package-source.ts +++ b/src/package-source.ts @@ -1,6 +1,28 @@ import fetch from 'npm-registry-fetch' import { compare } from 'semver' -import type { IPackageSource, IPackageVersionInfo } from './types' +import type { IPackageVersionInfo } from './versioning' + +/** + * Fetches info about a package. + */ +export interface IPackageSource { + /** + * Fetches package info from an external source. + * + * @param packageName + */ + fetch(packageName: string): Promise +} + +/** + * Interface for the Package Info structure. + */ +export interface IPackageInfo { + name: string + latestVersion: string + deprecated: boolean + versions: Array +} /** * Creates a package source. diff --git a/src/type-syncer.ts b/src/type-syncer.ts index 2e8291f..770f68f 100644 --- a/src/type-syncer.ts +++ b/src/type-syncer.ts @@ -1,20 +1,19 @@ import * as path from 'node:path' +import type { IConfigService } from './config-service' import type { IGlobber } from './globber' +import type { IPackageJSONService } from './package-json-file-service' +import type { IPackageSource } from './package-source' import { type ICLIArguments, - type IConfigService, type IDependenciesSection, IDependencySection, type IPackageFile, - type IPackageJSONService, - type IPackageSource, type IPackageTypingDescriptor, type IPackageVersion, type ISyncOptions, type ISyncResult, type ISyncedFile, type ITypeSyncer, - type IWorkspaceResolverService, } from './types' import { filterMap, @@ -25,6 +24,7 @@ import { uniq, } from './util' import { getClosestMatchingVersion } from './versioning' +import type { IWorkspaceResolverService } from './workspace-resolver' /** * Creates a type syncer. diff --git a/src/types.ts b/src/types.ts index 2e7fa09..96b3c7b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import type { IGlobber } from './globber' +import type { IWorkspacesSection } from './workspace-resolver' /** * The guts of the program. @@ -21,63 +21,6 @@ export interface ISyncOptions { ignorePackages?: string[] } -/** - * Config Service. - */ -export interface IConfigService { - /** - * Get typesync config. - */ - readConfig( - filePath: string, - flags: ICLIArguments['flags'], - ): Promise -} - -/** - * Fetches info about a package. - */ -export interface IPackageSource { - /** - * Fetches package info from an external source. - * - * @param packageName - */ - fetch(packageName: string): Promise -} - -/** - * File service. - */ -export interface IPackageJSONService { - /** - * Reads and parses JSON from the specified file. Path is relative to the current working directory. - */ - readPackageFile(filePath: string): Promise - /** - * Writes the JSON to the specified file. - */ - writePackageFile(filePath: string, fileContents: IPackageFile): Promise -} - -/** - * Interface for the Package Info structure. - */ -export interface IPackageInfo { - name: string - latestVersion: string - deprecated: boolean - versions: Array -} - -/** - * Version descriptor for versions returned in remote package info. - */ -export interface IPackageVersionInfo { - version: string - containsInternalTypings: boolean -} - /** * Package.json file. */ @@ -98,88 +41,6 @@ export interface IDependenciesSection { [packageName: string]: string } -/** - * @example - * ```json - * "workspaces": [ - * "packages/*", - * ] - * ``` - */ -export type IWorkspacesArray = Array - -/** - * @example - * ```yaml - * projects: - * - 'packages/*' - * ``` - */ -export type IWorkspacesObject = { - packages: IWorkspacesArray -} - -/** - * @see {@link IWorkspacesArray} - */ -type NpmWorkspacesConfig = IWorkspacesArray - -/** - * Yarn is a special snowflake. - * - * @example - * ```json - * "workspaces": { - * "packages": [ - * "packages/*", - * ], - * "nohoist": [] - * } - * ``` - */ -type YarnWorkspacesConfig = - | IWorkspacesArray - | (IWorkspacesObject & { nohoist?: string[] }) - -/** - * The contents of a `pnpm-workspace.yaml` file. - * - * @example - * ```yaml - * packages: - * - 'packages/*' - * ``` - */ -export type PnpmWorkspacesConfig = IWorkspacesObject - -/** - * @see {@link IWorkspacesArray} - */ -type BunWorkspacesConfig = IWorkspacesArray - -/** - * Section in `package.json` representing workspaces. - */ -export type IWorkspacesSection = - | NpmWorkspacesConfig - | YarnWorkspacesConfig - | BunWorkspacesConfig - -/** - * Files service. - */ -export interface IWorkspaceResolverService { - /** - * Reads, parses, and normalizes a workspaces configuration from the following files, in this order: - * - `package.json` `workspaces` field, as an array of globs. - * - `package.json` `workspaces` field, as an object with a `projects` field, which is an array of globs. - * - `pnpm-workspace.yaml` `packages` field, as an array of globs. - * - * Path is relative to the current working directory. - */ - getWorkspaces(root: string, globber: IGlobber): Promise -} - /** * Package + version record, collected from the {"package": "^1.2.3"} sections. */ diff --git a/src/util.ts b/src/util.ts index ca88d88..f5c2ec0 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,4 @@ -import type { IWorkspacesSection, IWorkspacesArray } from './types' +import type { IWorkspacesArray, IWorkspacesSection } from './workspace-resolver' /** * Returns unique items. diff --git a/src/versioning.ts b/src/versioning.ts index 350716c..e1fc3c4 100644 --- a/src/versioning.ts +++ b/src/versioning.ts @@ -1,5 +1,12 @@ import { parse } from 'semver' -import type { IPackageVersionInfo } from './types' + +/** + * Version descriptor for versions returned in remote package info. + */ +export interface IPackageVersionInfo { + version: string + containsInternalTypings: boolean +} /** * Gets the closest matching package version info. diff --git a/src/workspace-resolver.ts b/src/workspace-resolver.ts index d502b6a..2b2a13e 100644 --- a/src/workspace-resolver.ts +++ b/src/workspace-resolver.ts @@ -1,14 +1,92 @@ import path from 'node:path' import yaml from 'js-yaml' import { readFileContents } from './fs-utils' -import type { - IPackageFile, - IWorkspaceResolverService, - IWorkspacesArray, - IWorkspacesSection, - PnpmWorkspacesConfig, -} from './types' +import { IGlobber } from './globber' import { ensureWorkspacesArray, uniq } from './util' +import type { IPackageFile } from './types' + +/** + * Service for fetching monorepo workspaces in a standardized format in package-manager-agnostic way. + * It is used to allow syncing all types in a workspace when run from the root of a monorepo. + */ +export interface IWorkspaceResolverService { + /** + * Reads, parses, and normalizes a workspaces configuration from the following files, in this order: + * - `package.json` `workspaces` field, as an array of globs. + * - `package.json` `workspaces` field, as an object with a `projects` field, which is an array of globs. + * - `pnpm-workspace.yaml` `packages` field, as an array of globs. + * + * Path is relative to the current working directory. + */ + getWorkspaces(root: string, globber: IGlobber): Promise +} + +/** + * @example + * ```json + * "workspaces": [ + * "packages/*", + * ] + * ``` + */ +export type IWorkspacesArray = Array + +/** + * @example + * ```yaml + * projects: + * - 'packages/*' + * ``` + */ +export type IWorkspacesObject = { + packages: IWorkspacesArray +} + +/** + * @see {@link IWorkspacesArray} + */ +type NpmWorkspacesConfig = IWorkspacesArray + +/** + * Yarn is a special snowflake. + * + * @example + * ```json + * "workspaces": { + * "packages": [ + * "packages/*", + * ], + * "nohoist": [] + * } + * ``` + */ +type YarnWorkspacesConfig = + | IWorkspacesArray + | (IWorkspacesObject & { nohoist?: string[] }) + +/** + * The contents of a `pnpm-workspace.yaml` file. + * + * @example + * ```yaml + * packages: + * - 'packages/*' + * ``` + */ +export type PnpmWorkspacesConfig = IWorkspacesObject + +/** + * @see {@link IWorkspacesArray} + */ +type BunWorkspacesConfig = IWorkspacesArray + +/** + * Section in `package.json` representing workspaces. + */ +export type IWorkspacesSection = + | NpmWorkspacesConfig + | YarnWorkspacesConfig + | BunWorkspacesConfig export function createWorkspaceResolverService(): IWorkspaceResolverService { return { From eaa7e8b497855281a2d66c73a2dcec7345517d0b Mon Sep 17 00:00:00 2001 From: Eli <88557639+lishaduck@users.noreply.github.com> Date: Wed, 17 Jul 2024 21:58:43 -0500 Subject: [PATCH 6/9] style: changes I tried to enabled recommended-type-checked, but somehow ended up just making the arrays consistent. --- .eslintrc.cjs | 2 ++ .../package-json-file-service.test.ts | 4 ++-- src/__tests__/type-syncer.test.ts | 2 +- src/__tests__/versioning.test.ts | 2 +- src/cli.ts | 6 +++--- src/config-service.ts | 2 +- src/fs-utils.ts | 21 ++++++++++--------- src/package-json-file-service.ts | 4 +--- src/package-source.ts | 4 ++-- src/type-syncer.ts | 7 ++++--- src/types.ts | 10 ++++----- src/util.ts | 17 ++++++++------- src/versioning.ts | 4 ++-- src/workspace-resolver.ts | 4 ++-- 14 files changed, 45 insertions(+), 44 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 58e7202..47919d5 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -4,6 +4,7 @@ module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/stylistic-type-checked', 'prettier', ], overrides: [ @@ -25,6 +26,7 @@ module.exports = { 'error', { allow: ['/package\\.json$'] }, ], + '@typescript-eslint/array-type': ['error', { default: 'generic' }], }, }, // Tests diff --git a/src/__tests__/package-json-file-service.test.ts b/src/__tests__/package-json-file-service.test.ts index 7981cfe..10225a6 100644 --- a/src/__tests__/package-json-file-service.test.ts +++ b/src/__tests__/package-json-file-service.test.ts @@ -91,6 +91,6 @@ async function _writeFixture(withTrailingNewline = false): Promise { return file } -function cleanup(...files: string[]): Promise { - return Promise.all(files.map((f) => fsp.unlink(f))) +async function cleanup(...files: Array): Promise { + await Promise.all(files.map((f) => fsp.unlink(f))) } diff --git a/src/__tests__/type-syncer.test.ts b/src/__tests__/type-syncer.test.ts index c2d5ecc..51a2ac9 100644 --- a/src/__tests__/type-syncer.test.ts +++ b/src/__tests__/type-syncer.test.ts @@ -13,7 +13,7 @@ import type { IWorkspaceResolverService, } from '../workspace-resolver' -const descriptors: IPackageTypingDescriptor[] = [ +const descriptors: Array = [ { typingsName: 'package1', codePackageName: 'package1', diff --git a/src/__tests__/versioning.test.ts b/src/__tests__/versioning.test.ts index 116363d..237c84b 100644 --- a/src/__tests__/versioning.test.ts +++ b/src/__tests__/versioning.test.ts @@ -5,7 +5,7 @@ import { describe('getClosestMatchingVersion', () => { it('returns the closest matching version', () => { - const inputVersions: IPackageVersionInfo[] = [ + const inputVersions: Array = [ { containsInternalTypings: false, version: '1.16.0', diff --git a/src/cli.ts b/src/cli.ts index be4876f..91c3857 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -33,8 +33,8 @@ export async function startCli() { typeSyncer: asFunction(createTypeSyncer), }) await run(container.resolve('typeSyncer')) - } catch (err: any) { - C.error(err) + } catch (err) { + C.error(err as any) process.exitCode = 1 } } @@ -109,7 +109,7 @@ function renderSyncedFile(file: ISyncedFile) { : chalk`{green.bold (${file.newTypings.length.toString()} new typings added)}` const dirName = path.basename(path.dirname(path.resolve(file.filePath))) - const title = chalk`📦 ${file.package.name || dirName} {gray.italic — ${ + const title = chalk`📦 ${file.package.name ?? dirName} {gray.italic — ${ file.filePath }} ${badge}` diff --git a/src/config-service.ts b/src/config-service.ts index 714bb55..87dc36c 100644 --- a/src/config-service.ts +++ b/src/config-service.ts @@ -40,7 +40,7 @@ function readCliConfig(flags: ICLIArguments['flags']): ISyncOptions { const readValues = ( key: string, validator?: (value: string) => boolean, - ): T[] | undefined => { + ): Array | undefined => { const values = flags[key] return typeof values === 'string' ? values diff --git a/src/fs-utils.ts b/src/fs-utils.ts index 4dc666e..8dbda87 100644 --- a/src/fs-utils.ts +++ b/src/fs-utils.ts @@ -12,14 +12,15 @@ async function assertFile(filePath: string) { } async function existsAsync(filePath: string): Promise { - return stat(filePath) - .then(() => true) - .catch((err) => { - /* istanbul ignore else */ - if (err.code === 'ENOENT') { - return false - } - /* istanbul ignore next */ - throw err - }) + try { + await stat(filePath) + return true + } catch (err) { + /* istanbul ignore else */ + if ((err as { code: string }).code === 'ENOENT') { + return false + } + /* istanbul ignore next */ + throw err + } } diff --git a/src/package-json-file-service.ts b/src/package-json-file-service.ts index 927e3ee..41a49f8 100644 --- a/src/package-json-file-service.ts +++ b/src/package-json-file-service.ts @@ -26,9 +26,7 @@ export function createPackageJSONFileService(): IPackageJSONService { writePackageFile: async (filePath, fileContent) => { const contents = await readFileContents(filePath) const { indent } = detectIndent(contents) - const trailingNewline = contents.length - ? contents[contents.length - 1] === '\n' - : false + const trailingNewline = contents.length ? contents.endsWith('\n') : false const data = JSON.stringify( fileContent, null, diff --git a/src/package-source.ts b/src/package-source.ts index 9f8b88a..342eb7a 100644 --- a/src/package-source.ts +++ b/src/package-source.ts @@ -11,7 +11,7 @@ export interface IPackageSource { * * @param packageName */ - fetch(packageName: string): Promise + fetch(this: void, packageName: string): Promise } /** @@ -33,7 +33,7 @@ export function createPackageSource(): IPackageSource { * Fetches info about a package, or `null` if not found. */ fetch: async (name) => { - const response = await fetch(encodeURI(name)).catch((err: any) => { + const response = await fetch(encodeURI(name)).catch((err) => { if (err.statusCode === 404) { return null } diff --git a/src/type-syncer.ts b/src/type-syncer.ts index 770f68f..561fa0c 100644 --- a/src/type-syncer.ts +++ b/src/type-syncer.ts @@ -86,7 +86,7 @@ export function createTypeSyncer( const { ignoreDeps, ignorePackages } = opts const packageFile = - file || (await packageJSONService.readPackageFile(filePath)) + file ?? (await packageJSONService.readPackageFile(filePath)) const allLocalPackages = Object.values(IDependencySection) .map((dep) => { const section = getDependenciesBySection(packageFile, dep) @@ -241,14 +241,15 @@ function getPackageScope(packageName: string): [string, string] | null { function getPackagesFromSection( section: IDependenciesSection, ignoredSection?: boolean, - ignorePackages?: string[], -): IPackageVersion[] { + ignorePackages?: Array, +): Array { return filterMap(Object.keys(section), (name) => { const isTyping = name.startsWith('@types/') // Never ignore `@types` packages. if (!isTyping) { // If it's not a `@types` package, check whether the section or package is ignored. + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- We want to check for false as well. if (ignoredSection || ignorePackages?.includes(name)) { return false } diff --git a/src/types.ts b/src/types.ts index 96b3c7b..30324f4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,11 +14,11 @@ export interface ISyncOptions { /** * Ignore certain deps. */ - ignoreDeps?: IDependencySection[] + ignoreDeps?: Array /** * Ignore certain packages. */ - ignorePackages?: string[] + ignorePackages?: Array } /** @@ -37,9 +37,7 @@ export interface IPackageFile { /** * Section in package.json representing dependencies. */ -export interface IDependenciesSection { - [packageName: string]: string -} +export type IDependenciesSection = Record /** * Package + version record, collected from the {"package": "^1.2.3"} sections. @@ -107,6 +105,6 @@ export enum IDependencySection { * CLI arguments. */ export interface ICLIArguments { - flags: { [key: string]: boolean | string | undefined } + flags: Record args: Array } diff --git a/src/util.ts b/src/util.ts index f5c2ec0..e1f3735 100644 --- a/src/util.ts +++ b/src/util.ts @@ -53,7 +53,7 @@ export function shrinkObject(source: T): Required { * @param source An array of objects to merge. */ export function mergeObjects(source: Array): T { - return source.reduce((accum: any, next: any) => ({ ...accum, ...next }), {}) + return source.reduce((accum, next) => ({ ...accum, ...next }), {} as T) } /** @@ -91,13 +91,14 @@ export function untyped(name: string): string { * Orders an object. * @param source */ -export function orderObject< - T extends Record, ->(source: T, comparer?: (a: string, b: string) => number): T { +export function orderObject, U>( + source: T, + comparer?: (a: string, b: string) => number, +): T { const keys = Object.keys(source).sort(comparer) - const result: any = {} + const result: Record = {} for (const key of keys) { - result[key] = (source as any)[key] + result[key] = source[key] } return result as T @@ -108,10 +109,10 @@ export function orderObject< * * @param fn */ -export function memoizeAsync( +export function memoizeAsync, V, W>( fn: (...args: U) => Promise, ) { - const cache = new Map>() + const cache = new Map>() async function run(...args: U): Promise { try { diff --git a/src/versioning.ts b/src/versioning.ts index e1fc3c4..8ff523e 100644 --- a/src/versioning.ts +++ b/src/versioning.ts @@ -15,7 +15,7 @@ export interface IPackageVersionInfo { * @param version */ export function getClosestMatchingVersion( - availableVersions: IPackageVersionInfo[], + availableVersions: Array, version: string, ) { const parsedVersion = parseVersion(version) @@ -40,7 +40,7 @@ export function getClosestMatchingVersion( return true }) - return bestMatch || availableVersions[0] + return bestMatch ?? availableVersions[0] } /** diff --git a/src/workspace-resolver.ts b/src/workspace-resolver.ts index 2b2a13e..30da8b9 100644 --- a/src/workspace-resolver.ts +++ b/src/workspace-resolver.ts @@ -38,7 +38,7 @@ export type IWorkspacesArray = Array * - 'packages/*' * ``` */ -export type IWorkspacesObject = { +export interface IWorkspacesObject { packages: IWorkspacesArray } @@ -62,7 +62,7 @@ type NpmWorkspacesConfig = IWorkspacesArray */ type YarnWorkspacesConfig = | IWorkspacesArray - | (IWorkspacesObject & { nohoist?: string[] }) + | (IWorkspacesObject & { nohoist?: Array }) /** * The contents of a `pnpm-workspace.yaml` file. From 5a462a5836b7fd0563f58f29ae5deba73b129744 Mon Sep 17 00:00:00 2001 From: Eli <88557639+lishaduck@users.noreply.github.com> Date: Thu, 18 Jul 2024 12:39:33 -0500 Subject: [PATCH 7/9] perf: improve the efficiency of the workspace resolver --- src/__tests__/type-syncer.test.ts | 2 +- src/type-syncer.ts | 25 ++++++++++++++++++++++--- src/workspace-resolver.ts | 27 +++++++++------------------ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/__tests__/type-syncer.test.ts b/src/__tests__/type-syncer.test.ts index 51a2ac9..716c096 100644 --- a/src/__tests__/type-syncer.test.ts +++ b/src/__tests__/type-syncer.test.ts @@ -153,7 +153,7 @@ function buildSyncer() { } const workspaceResolverService: IWorkspaceResolverService = { - getWorkspaces: jest.fn(async (root, globber) => { + getWorkspaces: jest.fn(async (_, root, globber) => { let workspaces: IWorkspacesArray | undefined switch (root) { diff --git a/src/type-syncer.ts b/src/type-syncer.ts index 561fa0c..06b8154 100644 --- a/src/type-syncer.ts +++ b/src/type-syncer.ts @@ -53,9 +53,8 @@ export function createTypeSyncer( flags: ICLIArguments['flags'], ): Promise { const dryRun = !!flags.dry - const [file, subPackages, syncOpts] = await Promise.all([ - packageJSONService.readPackageFile(filePath), - workspaceResolverService.getWorkspaces(path.dirname(filePath), globber), + const [{ file, subPackages }, syncOpts] = await Promise.all([ + getManifests(filePath, globber), configService.readConfig(filePath, flags), ]) @@ -69,6 +68,26 @@ export function createTypeSyncer( } } + /** + * Get the `package.json` files and sub-packages. + * + * @param filePath + * @param globber + */ + async function getManifests(filePath: string, globber: IGlobber) { + const file = await packageJSONService.readPackageFile(filePath) + const subPackages = await workspaceResolverService.getWorkspaces( + file, + path.dirname(filePath), + globber, + ) + + return { + file, + subPackages, + } + } + /** * Syncs a single file. * diff --git a/src/workspace-resolver.ts b/src/workspace-resolver.ts index 30da8b9..7f54078 100644 --- a/src/workspace-resolver.ts +++ b/src/workspace-resolver.ts @@ -18,7 +18,11 @@ export interface IWorkspaceResolverService { * * Path is relative to the current working directory. */ - getWorkspaces(root: string, globber: IGlobber): Promise + getWorkspaces( + packageJson: IPackageFile, + root: string, + globber: IGlobber, + ): Promise } /** @@ -90,8 +94,8 @@ export type IWorkspacesSection = export function createWorkspaceResolverService(): IWorkspaceResolverService { return { - getWorkspaces: async (root, globber) => { - const workspaces = await getWorkspaces(root) + getWorkspaces: async (packageJson, root, globber) => { + const workspaces = await getWorkspaces(packageJson, root) const workspacesArray = ensureWorkspacesArray(workspaces) const manifests = await Promise.all( @@ -106,9 +110,10 @@ export function createWorkspaceResolverService(): IWorkspaceResolverService { } async function getWorkspaces( + packageJson: IPackageFile, root: string, ): Promise { - const packageJsonWorkspaces = await getPackageJsonWorkspaces(root) + const packageJsonWorkspaces = packageJson.workspaces if (packageJsonWorkspaces !== undefined) { return packageJsonWorkspaces } @@ -116,20 +121,6 @@ async function getWorkspaces( return await getPnpmWorkspaces(root) } -async function getPackageJsonWorkspaces( - root: string, -): Promise { - try { - const filePath = path.relative(root, 'package.json') - const contents = await readFileContents(filePath) // TODO: Don't do this twice. - const packageJson = JSON.parse(contents) as IPackageFile - - return packageJson.workspaces - } catch { - return undefined - } -} - async function getPnpmWorkspaces( root: string, ): Promise { From c69636bbc0c0d98818cfcae15638fd6f099fe66a Mon Sep 17 00:00:00 2001 From: Eli <88557639+lishaduck@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:28:36 -0500 Subject: [PATCH 8/9] chore: add tests --- src/__tests__/workspace-resolver.test.ts | 82 ++++++++++++++++++++++++ src/workspace-resolver.ts | 56 ++++++++-------- 2 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 src/__tests__/workspace-resolver.test.ts diff --git a/src/__tests__/workspace-resolver.test.ts b/src/__tests__/workspace-resolver.test.ts new file mode 100644 index 0000000..f3088d1 --- /dev/null +++ b/src/__tests__/workspace-resolver.test.ts @@ -0,0 +1,82 @@ +import type { IGlobber } from '../globber' +import { + createWorkspaceResolverService, + type BunWorkspacesConfig, + type NpmWorkspacesConfig, + type PnpmWorkspacesConfig, + type YarnWorkspacesConfig, +} from '../workspace-resolver' + +describe('workspace resolver', () => { + const globber: IGlobber = { + globPackageFiles: jest.fn(async (_root: string) => { + return ['packages/package1', 'packages/package2'] + }), + } + + describe('getWorkspaces', () => { + describe('returns workspaces for all package managers', () => { + const subject = createWorkspaceResolverService({ + readFileContents: jest.fn(async (_filePath: string) => { + return JSON.stringify({ + packages: ['packages/*'], + } satisfies PnpmWorkspacesConfig) + }), + }) + + it.each([ + { + pm: 'npm', + files: { + 'package.json': { + workspaces: ['packages/*'] satisfies NpmWorkspacesConfig, + }, + }, + }, + { + pm: 'yarn', + files: { + 'package.json': { + workspaces: { + packages: ['packages/*'], + nohoist: [], + } satisfies YarnWorkspacesConfig, + }, + }, + }, + { + pm: 'pnpm', + files: { + 'package.json': {}, + }, + }, + { + pm: 'bun', + files: { + 'package.json': { + workspaces: ['packages/*'] satisfies BunWorkspacesConfig, + }, + }, + }, + ])(`returns $pm workspaces`, async ({ pm, files }) => { + const workspaces = await subject.getWorkspaces( + files['package.json'], + '/', + globber, + ) + + expect(workspaces).toEqual(['packages/package1', 'packages/package2']) + }) + + it('returns an empty list if no workspaces are found', async () => { + const subject = createWorkspaceResolverService({ + readFileContents: jest.fn(async (_filePath: string) => { + throw new Error('Nothing here, move along.') + }), + }) + + expect(await subject.getWorkspaces({}, '/', globber)).toEqual([]) + }) + }) + }) +}) diff --git a/src/workspace-resolver.ts b/src/workspace-resolver.ts index 7f54078..b81a0a9 100644 --- a/src/workspace-resolver.ts +++ b/src/workspace-resolver.ts @@ -1,6 +1,6 @@ import path from 'node:path' import yaml from 'js-yaml' -import { readFileContents } from './fs-utils' +import * as fsUtils from './fs-utils' import { IGlobber } from './globber' import { ensureWorkspacesArray, uniq } from './util' import type { IPackageFile } from './types' @@ -49,7 +49,7 @@ export interface IWorkspacesObject { /** * @see {@link IWorkspacesArray} */ -type NpmWorkspacesConfig = IWorkspacesArray +export type NpmWorkspacesConfig = IWorkspacesArray /** * Yarn is a special snowflake. @@ -64,7 +64,7 @@ type NpmWorkspacesConfig = IWorkspacesArray * } * ``` */ -type YarnWorkspacesConfig = +export type YarnWorkspacesConfig = | IWorkspacesArray | (IWorkspacesObject & { nohoist?: Array }) @@ -82,7 +82,7 @@ export type PnpmWorkspacesConfig = IWorkspacesObject /** * @see {@link IWorkspacesArray} */ -type BunWorkspacesConfig = IWorkspacesArray +export type BunWorkspacesConfig = IWorkspacesArray /** * Section in `package.json` representing workspaces. @@ -92,7 +92,11 @@ export type IWorkspacesSection = | YarnWorkspacesConfig | BunWorkspacesConfig -export function createWorkspaceResolverService(): IWorkspaceResolverService { +export function createWorkspaceResolverService({ + readFileContents, +}: { + readFileContents: typeof fsUtils.readFileContents +} = fsUtils): IWorkspaceResolverService { return { getWorkspaces: async (packageJson, root, globber) => { const workspaces = await getWorkspaces(packageJson, root) @@ -107,30 +111,30 @@ export function createWorkspaceResolverService(): IWorkspaceResolverService { return uniq(manifests.flat()) }, } -} -async function getWorkspaces( - packageJson: IPackageFile, - root: string, -): Promise { - const packageJsonWorkspaces = packageJson.workspaces - if (packageJsonWorkspaces !== undefined) { - return packageJsonWorkspaces - } + async function getWorkspaces( + packageJson: IPackageFile, + root: string, + ): Promise { + const packageJsonWorkspaces = packageJson.workspaces + if (packageJsonWorkspaces !== undefined) { + return packageJsonWorkspaces + } - return await getPnpmWorkspaces(root) -} + return await getPnpmWorkspaces(root) + } -async function getPnpmWorkspaces( - root: string, -): Promise { - try { - const filePath = path.relative(root, 'pnpm-workspace.yaml') - const contents = await readFileContents(filePath) - const pnpmWorkspaces = yaml.load(contents) as PnpmWorkspacesConfig + async function getPnpmWorkspaces( + root: string, + ): Promise { + try { + const filePath = path.relative(root, 'pnpm-workspace.yaml') + const contents = await readFileContents(filePath) + const pnpmWorkspaces = yaml.load(contents) as PnpmWorkspacesConfig - return pnpmWorkspaces.packages - } catch { - return undefined + return pnpmWorkspaces.packages + } catch { + return undefined + } } } From 18605181c3d5dfc689658947b3ec316c72ab3a73 Mon Sep 17 00:00:00 2001 From: Eli <88557639+lishaduck@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:45:44 -0500 Subject: [PATCH 9/9] fix: coverage --- src/cli.ts | 5 +++-- src/workspace-resolver.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 91c3857..d1405a3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,6 +13,7 @@ import type { ITypeSyncer, } from './types' import { createWorkspaceResolverService } from './workspace-resolver' +import * as fsUtils from './fs-utils' /** * Starts the TypeSync CLI. @@ -24,8 +25,8 @@ export async function startCli() { injectionMode: InjectionMode.CLASSIC, }).register({ packageJSONService: asFunction(createPackageJSONFileService).singleton(), - workspaceResolverService: asFunction( - createWorkspaceResolverService, + workspaceResolverService: asFunction(() => + createWorkspaceResolverService(fsUtils), ).singleton(), packageSource: asFunction(createPackageSource).singleton(), configService: asFunction(createConfigService).singleton(), diff --git a/src/workspace-resolver.ts b/src/workspace-resolver.ts index b81a0a9..282732d 100644 --- a/src/workspace-resolver.ts +++ b/src/workspace-resolver.ts @@ -1,6 +1,6 @@ import path from 'node:path' import yaml from 'js-yaml' -import * as fsUtils from './fs-utils' +import type * as fsUtils from './fs-utils' import { IGlobber } from './globber' import { ensureWorkspacesArray, uniq } from './util' import type { IPackageFile } from './types' @@ -96,7 +96,7 @@ export function createWorkspaceResolverService({ readFileContents, }: { readFileContents: typeof fsUtils.readFileContents -} = fsUtils): IWorkspaceResolverService { +}): IWorkspaceResolverService { return { getWorkspaces: async (packageJson, root, globber) => { const workspaces = await getWorkspaces(packageJson, root)