diff --git a/.github/workflows/bump-packages.yaml b/.github/workflows/bump-packages.yaml index 16f81e0c..333eaa27 100644 --- a/.github/workflows/bump-packages.yaml +++ b/.github/workflows/bump-packages.yaml @@ -33,7 +33,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: "npm" - name: Install npm diff --git a/.github/workflows/check-test.yaml b/.github/workflows/check-test.yaml index 849ae1f4..bac6abb3 100644 --- a/.github/workflows/check-test.yaml +++ b/.github/workflows/check-test.yaml @@ -56,7 +56,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: "npm" - name: Use python@3.11 diff --git a/.github/workflows/publish-packages.yaml b/.github/workflows/publish-packages.yaml index 4ec48081..ccf35606 100644 --- a/.github/workflows/publish-packages.yaml +++ b/.github/workflows/publish-packages.yaml @@ -40,7 +40,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x cache: "npm" - name: Install npm diff --git a/package-lock.json b/package-lock.json index 3426513e..b9fc2ac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8747,6 +8747,220 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.0.tgz", + "integrity": "sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509-attr": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-cms/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.0.tgz", + "integrity": "sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.0.tgz", + "integrity": "sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.0.tgz", + "integrity": "sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-pkcs8": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.0.tgz", + "integrity": "sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.0.tgz", + "integrity": "sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-pfx": "^2.6.0", + "@peculiar/asn1-pkcs8": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509-attr": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.0.tgz", + "integrity": "sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.0.tgz", + "integrity": "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.0.tgz", + "integrity": "sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/asn1-x509/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@peculiar/x509": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.2.tgz", + "integrity": "sha512-r2w1Hg6pODDs0zfAKHkSS5HLkOLSeburtcgwvlLLWWCixw+MmW3U6kD5ddyvc2Y2YdbGuVwCF2S2ASoU1cFAag==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@peculiar/x509/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@pkgjs/nv": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@pkgjs/nv/-/nv-0.2.1.tgz", @@ -11648,6 +11862,26 @@ "safer-buffer": "~2.1.0" } }, + "node_modules/asn1js": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", + "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/asn1js/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", @@ -13559,31 +13793,6 @@ "node": ">=18" } }, - "node_modules/data-urls/node_modules/tr46": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz", - "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -19449,18 +19658,6 @@ "node": ">=6" } }, - "node_modules/jsdom/node_modules/tr46": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz", - "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/jsdom/node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -19469,19 +19666,6 @@ "node": ">= 4.0.0" } }, - "node_modules/jsdom/node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -25512,6 +25696,30 @@ "node": ">=6" } }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvtsutils/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -26263,6 +26471,12 @@ "node": ">=8" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -28551,14 +28765,15 @@ } }, "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/traverse": { @@ -28715,6 +28930,18 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/tuf-js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", @@ -29386,15 +29613,16 @@ } }, "node_modules/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/which": { @@ -30990,9 +31218,10 @@ "@mongodb-js/mongodb-downloader": "^1.0.0", "@mongodb-js/oidc-mock-provider": "^0.12.0", "@mongodb-js/saslprep": "^1.3.2", + "@peculiar/x509": "^1.14.2", "debug": "^4.4.0", - "mongodb": "^6.9.0", - "mongodb-connection-string-url": "^3.0.0", + "mongodb": "^6.9.0 || ^7.0.0", + "mongodb-connection-string-url": "^3.0.0 || ^7.0.0", "yargs": "^17.7.2" }, "bin": { @@ -31020,6 +31249,24 @@ "typescript": "^5.0.4" } }, + "packages/mongodb-runner/node_modules/@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "packages/mongodb-runner/node_modules/bson": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz", + "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0" + } + }, "packages/mongodb-runner/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -31033,6 +31280,65 @@ "node": ">=12" } }, + "packages/mongodb-runner/node_modules/mongodb": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", + "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.0.0", + "mongodb-connection-string-url": "^7.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "packages/mongodb-runner/node_modules/mongodb-connection-string-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz", + "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" + } + }, "packages/mongodb-runner/node_modules/typescript": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", @@ -40396,6 +40702,217 @@ "node-gyp-build": "^4.3.0" } }, + "@peculiar/asn1-cms": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.0.tgz", + "integrity": "sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==", + "requires": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509-attr": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/asn1-csr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.0.tgz", + "integrity": "sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==", + "requires": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/asn1-ecc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.0.tgz", + "integrity": "sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==", + "requires": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/asn1-pfx": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.0.tgz", + "integrity": "sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==", + "requires": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-pkcs8": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/asn1-pkcs8": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.0.tgz", + "integrity": "sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==", + "requires": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/asn1-pkcs9": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.0.tgz", + "integrity": "sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==", + "requires": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-pfx": "^2.6.0", + "@peculiar/asn1-pkcs8": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509-attr": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/asn1-rsa": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.0.tgz", + "integrity": "sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==", + "requires": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "requires": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/asn1-x509": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.0.tgz", + "integrity": "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==", + "requires": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/asn1-x509-attr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.0.tgz", + "integrity": "sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==", + "requires": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "@peculiar/x509": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.2.tgz", + "integrity": "sha512-r2w1Hg6pODDs0zfAKHkSS5HLkOLSeburtcgwvlLLWWCixw+MmW3U6kD5ddyvc2Y2YdbGuVwCF2S2ASoU1cFAag==", + "requires": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, "@pkgjs/nv": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@pkgjs/nv/-/nv-0.2.1.tgz", @@ -42764,6 +43281,23 @@ "safer-buffer": "~2.1.0" } }, + "asn1js": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", + "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==", + "requires": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, "assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", @@ -44182,25 +44716,6 @@ "requires": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" - }, - "dependencies": { - "tr46": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz", - "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==", - "requires": { - "punycode": "^2.3.1" - } - }, - "whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "requires": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - } - } } }, "data-view-buffer": { @@ -48461,27 +48976,10 @@ "url-parse": "^1.5.3" } }, - "tr46": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz", - "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==", - "requires": { - "punycode": "^2.3.1" - } - }, "universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" - }, - "whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "requires": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - } } } }, @@ -51053,6 +51551,7 @@ "@mongodb-js/prettier-config-devtools": "^1.0.2", "@mongodb-js/saslprep": "^1.3.2", "@mongodb-js/tsconfig-devtools": "^1.0.4", + "@peculiar/x509": "^1.14.2", "@types/chai": "^4.2.21", "@types/debug": "^4.1.8", "@types/mocha": "^9.1.1", @@ -51065,8 +51564,8 @@ "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.3", "mocha": "^8.4.0", - "mongodb": "^6.9.0", - "mongodb-connection-string-url": "^3.0.0", + "mongodb": "^6.9.0 || ^7.0.0", + "mongodb-connection-string-url": "^3.0.0 || ^7.0.0", "nyc": "^15.1.0", "prettier": "^3.5.3", "sinon": "^9.2.3", @@ -51074,6 +51573,19 @@ "yargs": "^17.7.2" }, "dependencies": { + "@types/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", + "requires": { + "@types/webidl-conversions": "*" + } + }, + "bson": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz", + "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==" + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -51084,6 +51596,25 @@ "wrap-ansi": "^7.0.0" } }, + "mongodb": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", + "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", + "requires": { + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.0.0", + "mongodb-connection-string-url": "^7.0.0" + } + }, + "mongodb-connection-string-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz", + "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==", + "requires": { + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + } + }, "typescript": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", @@ -53339,6 +53870,26 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, + "pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "requires": { + "tslib": "^2.8.1" + }, + "dependencies": { + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + } + } + }, + "pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==" + }, "qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -53884,6 +54435,11 @@ "strip-indent": "^3.0.0" } }, + "reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + }, "reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -55605,11 +56161,11 @@ } }, "tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "requires": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" } }, "traverse": { @@ -55719,6 +56275,14 @@ "tslib": "^1.8.1" } }, + "tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "requires": { + "tslib": "^1.9.3" + } + }, "tuf-js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", @@ -56200,11 +56764,11 @@ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" }, "whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "requires": { - "tr46": "^4.1.1", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, diff --git a/packages/mongodb-runner/package.json b/packages/mongodb-runner/package.json index 5025c251..9c78c72c 100644 --- a/packages/mongodb-runner/package.json +++ b/packages/mongodb-runner/package.json @@ -59,9 +59,10 @@ "@mongodb-js/mongodb-downloader": "^1.0.0", "@mongodb-js/oidc-mock-provider": "^0.12.0", "@mongodb-js/saslprep": "^1.3.2", + "@peculiar/x509": "^1.14.2", "debug": "^4.4.0", - "mongodb": "^6.9.0", - "mongodb-connection-string-url": "^3.0.0", + "mongodb": "^6.9.0 || ^7.0.0", + "mongodb-connection-string-url": "^3.0.0 || ^7.0.0", "yargs": "^17.7.2" }, "devDependencies": { diff --git a/packages/mongodb-runner/src/cli.ts b/packages/mongodb-runner/src/cli.ts index a63472dd..f497a60d 100644 --- a/packages/mongodb-runner/src/cli.ts +++ b/packages/mongodb-runner/src/cli.ts @@ -74,7 +74,10 @@ import type { MongoClientOptions } from 'mongodb'; type: 'string', describe: 'Configure OIDC authentication on the server', }) + .config() + .env('MONGODB_RUNNER') .option('debug', { type: 'boolean', describe: 'Enable debug output' }) + .option('verbose', { type: 'boolean', describe: 'Enable verbose output' }) .command('start', 'Start a MongoDB instance') .command('stop', 'Stop a MongoDB instance') .command('prune', 'Clean up metadata for any dead MongoDB instances') @@ -86,9 +89,12 @@ import type { MongoClientOptions } from 'mongodb'; .demandCommand(1, 'A command needs to be provided') .help().argv; const [command, ...args] = argv._.map(String); - if (argv.debug) { + if (argv.debug || argv.verbose) { createDebug.enable('mongodb-runner'); } + if (argv.verbose) { + createDebug.enable('mongodb-runner:*'); + } if (argv.oidc && process.platform !== 'linux') { console.warn( diff --git a/packages/mongodb-runner/src/index.ts b/packages/mongodb-runner/src/index.ts index 1bc50d23..d59500c9 100644 --- a/packages/mongodb-runner/src/index.ts +++ b/packages/mongodb-runner/src/index.ts @@ -7,6 +7,11 @@ export { MongoCluster, type MongoClusterEvents, MongoClusterOptions, + MongoDBUserDoc, + RSMemberOptions as MongoClusterRSMemberOptions, + RSOptions as MongoClusterRSOptions, + CommonOptions as MongoClusterCommonOptions, + ShardedOptions as MongoClusterShardedOptions, } from './mongocluster'; export type { LogEntry } from './mongologreader'; export type { ConnectionString } from 'mongodb-connection-string-url'; diff --git a/packages/mongodb-runner/src/mongocluster.spec.ts b/packages/mongodb-runner/src/mongocluster.spec.ts index 9f1717bc..1634dc8b 100644 --- a/packages/mongodb-runner/src/mongocluster.spec.ts +++ b/packages/mongodb-runner/src/mongocluster.spec.ts @@ -6,6 +6,7 @@ import os from 'os'; import createDebug from 'debug'; import sinon from 'sinon'; import type { LogEntry } from './mongologreader'; +import type { MongoClientOptions } from 'mongodb'; if (process.env.CI) { createDebug.enable('mongodb-runner,mongodb-downloader'); @@ -17,6 +18,12 @@ const twoIfNotWindowsCI = // These are from the testing/certificates directory of mongosh const SERVER_KEY = 'server.bundle.pem'; const CA_CERT = 'ca.crt'; +const SHORT_TIMEOUTS: Partial = { + serverSelectionTimeoutMS: 500, + connectTimeoutMS: 500, + socketTimeoutMS: 500, + timeoutMS: 500, +}; describe('MongoCluster', function () { this.timeout(1_000_000); // Downloading Windows binaries can take a very long time... @@ -50,7 +57,7 @@ describe('MongoCluster', function () { try { cluster = await MongoCluster.start({ - version: '6.x', + version: '8.x', topology: 'standalone', tmpDir, downloadDir: customDownloadDir, @@ -65,7 +72,7 @@ describe('MongoCluster', function () { expect(MongoCluster['downloadMongoDb']).to.have.been.calledWith( customDownloadDir, - '6.x', + '8.x', { platform: 'linux', arch: 'x64', @@ -88,9 +95,24 @@ describe('MongoCluster', function () { expect(ok).to.equal(1); }); - it('can spawn a 6.x standalone mongod on a pre-specified port', async function () { + it('can spawn a 8.x standalone mongod', async function () { cluster = await MongoCluster.start({ - version: '6.x', + version: '8.x', + topology: 'standalone', + tmpDir, + }); + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.serverVersion).to.match(/^8\./); + expect(cluster.serverVariant).to.equal('community'); + const { ok } = await cluster.withClient(async (client) => { + return await client.db('admin').command({ ping: 1 }); + }); + expect(ok).to.equal(1); + }); + + it('can spawn a 8.x standalone mongod on a pre-specified port', async function () { + cluster = await MongoCluster.start({ + version: '8.x', topology: 'standalone', tmpDir, args: ['--port', '50079'], @@ -98,40 +120,42 @@ describe('MongoCluster', function () { expect(cluster.connectionString).to.include(`:50079`); }); - it('can spawn a 6.x replset', async function () { + it('can spawn a 8.x replset', async function () { cluster = await MongoCluster.start({ - version: '6.x', + version: '8.x', topology: 'replset', tmpDir, }); expect(cluster.connectionString).to.be.a('string'); - expect(cluster.serverVersion).to.match(/^6\./); + expect(cluster.serverVersion).to.match(/^8\./); const hello = await cluster.withClient(async (client) => { return await client.db('admin').command({ hello: 1 }); }); expect(+hello.passives.length + +hello.hosts.length).to.equal(3); }); - it('can spawn a 6.x replset with specific number of arbiters and secondaries', async function () { + it('can spawn a 8.x replset with specific number of arbiters and secondaries', async function () { cluster = await MongoCluster.start({ - version: '6.x', + version: '8.x', topology: 'replset', tmpDir, secondaries: 3, arbiters: 1, }); expect(cluster.connectionString).to.be.a('string'); - expect(cluster.serverVersion).to.match(/^6\./); + expect(cluster.serverVersion).to.match(/^8\./); const hello = await cluster.withClient(async (client) => { return await client.db('admin').command({ hello: 1 }); }); - expect(+hello.passives.length + +hello.hosts.length).to.equal(5); + expect(hello.hosts).to.have.lengthOf(1); + expect(hello.passives).to.have.lengthOf(3); + expect(hello.arbiters).to.have.lengthOf(1); }); - it('can spawn a 6.x sharded cluster', async function () { + it('can spawn a 8.x sharded cluster', async function () { const logDir = path.join(tmpDir, `sharded-logs`); cluster = await MongoCluster.start({ - version: '6.x', + version: '8.x', topology: 'sharded', tmpDir, shards: 2, @@ -139,7 +163,7 @@ describe('MongoCluster', function () { logDir, }); expect(cluster.connectionString).to.be.a('string'); - expect(cluster.serverVersion).to.match(/^6\./); + expect(cluster.serverVersion).to.match(/^8\./); const hello = await cluster.withClient(async (client) => { return await client.db('admin').command({ hello: 1 }); }); @@ -154,23 +178,107 @@ describe('MongoCluster', function () { ).to.equal(1); }); - it('can spawn a 6.x standalone mongod with TLS enabled and get build info', async function () { - cluster = await MongoCluster.start({ - version: '6.x', - topology: 'standalone', - tmpDir, - args: [ - '--tlsMode', - 'requireTLS', - '--tlsCertificateKeyFile', - path.resolve(__dirname, '..', 'test', 'fixtures', SERVER_KEY), - '--tlsCAFile', - path.resolve(__dirname, '..', 'test', 'fixtures', CA_CERT), - ], + context('TLS', function () { + it('can spawn a 8.x standalone mongod with TLS enabled and get build info (automatically added client key)', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'standalone', + tmpDir, + args: [ + '--tlsMode', + 'requireTLS', + '--tlsCertificateKeyFile', + path.resolve(__dirname, '..', 'test', 'fixtures', SERVER_KEY), + '--tlsCAFile', + path.resolve(__dirname, '..', 'test', 'fixtures', CA_CERT), + ], + }); + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.connectionString).to.include( + 'tls=true&tlsCertificateKeyFile=', + ); + expect(cluster.serverVersion).to.match(/^8\./); + expect(cluster.serverVariant).to.equal('community'); + + await cluster.withClient(async (client) => { + expect( + path.basename(client.options.tlsCertificateKeyFile ?? ''), + ).to.include('mongodb-runner-client-'); + const buildInfo = await client.db('admin').command({ buildInfo: 1 }); + expect(buildInfo.version).to.be.a('string'); + }); + }); + + it('can spawn a 8.x standalone mongod with TLS enabled and get build info (no extra client key)', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'standalone', + tmpDir, + tlsAddClientKey: false, + internalClientOptions: { ...SHORT_TIMEOUTS }, + args: [ + '--tlsMode', + 'requireTLS', + '--tlsCertificateKeyFile', + path.resolve(__dirname, '..', 'test', 'fixtures', SERVER_KEY), + '--tlsCAFile', + path.resolve(__dirname, '..', 'test', 'fixtures', CA_CERT), + ], + }); + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.connectionString).not.to.include('tls='); + expect(cluster.connectionString).not.to.include('tlsCertificateKeyFile='); + expect(cluster.serverVersion).to.match(/^8\./); + expect(cluster.serverVariant).to.equal('community'); + + try { + await cluster.withClient(() => {}, { ...SHORT_TIMEOUTS }); + expect.fail('missed exception'); + } catch (err: any) { + expect(err.name).to.equal('MongoServerSelectionError'); + } + }); + + it('can spawn a 8.x standalone mongod with TLS enabled and get build info (explicit client cert)', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'standalone', + tmpDir, + internalClientOptions: { + tls: true, + tlsCAFile: path.resolve(__dirname, '..', 'test', 'fixtures', CA_CERT), + tlsCertificateKeyFile: path.resolve( + __dirname, + '..', + 'test', + 'fixtures', + SERVER_KEY, + ), + tlsAllowInvalidCertificates: true, + }, + args: [ + '--tlsMode', + 'requireTLS', + '--tlsCertificateKeyFile', + path.resolve(__dirname, '..', 'test', 'fixtures', SERVER_KEY), + '--tlsCAFile', + path.resolve(__dirname, '..', 'test', 'fixtures', CA_CERT), + ], + }); + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.connectionString).to.include('tls=true&tlsCAFile='); + expect(cluster.connectionString).to.include('tlsCertificateKeyFile='); + expect(cluster.serverVersion).to.match(/^8\./); + expect(cluster.serverVariant).to.equal('community'); + + await cluster.withClient(async (client) => { + expect( + path.basename(client.options.tlsCertificateKeyFile ?? ''), + ).to.equal(SERVER_KEY); + const buildInfo = await client.db('admin').command({ buildInfo: 1 }); + expect(buildInfo.version).to.be.a('string'); + }); }); - expect(cluster.connectionString).to.be.a('string'); - expect(cluster.serverVersion).to.match(/^6\./); - expect(cluster.serverVariant).to.equal('community'); }); context('on Linux', function () { @@ -181,12 +289,12 @@ describe('MongoCluster', function () { // This is the easiest way to ensure that MongoServer can handle the // pre-4.4 log format (because in the devtools-shared CI, we only // test ubuntu-latest). - it('can spawn a 4.0.x replset using docker', async function () { + it('can spawn a 4.2.x replset using docker', async function () { cluster = await MongoCluster.start({ - version: '4.0.x', + version: '4.2.x', topology: 'replset', tmpDir, - docker: 'mongo:4.0', + docker: 'mongo:4.2', downloadOptions: { distro: 'ubuntu1604', }, @@ -199,12 +307,12 @@ describe('MongoCluster', function () { expect(+hello.passives.length + +hello.hosts.length).to.equal(3); }); - it('can spawn a 4.0.x sharded env using docker', async function () { + it('can spawn a 4.2.x sharded env using docker', async function () { cluster = await MongoCluster.start({ - version: '4.0.x', + version: '4.2.x', topology: 'sharded', tmpDir, - docker: 'mongo:4.0', + docker: 'mongo:4.2', shards: 1, secondaries: 0, downloadOptions: { @@ -219,9 +327,9 @@ describe('MongoCluster', function () { expect(hello.msg).to.equal('isdbgrid'); }); - it('can spawn a 4.0.x standalone mongod with TLS enabled and get build info', async function () { + it('can spawn a 4.2.x standalone mongod with TLS enabled and get build info', async function () { cluster = await MongoCluster.start({ - version: '4.0.x', + version: '4.2.x', topology: 'standalone', tmpDir, args: [ @@ -234,7 +342,7 @@ describe('MongoCluster', function () { ], docker: [ `--volume=${path.resolve(__dirname, '..')}:/projectroot:ro`, - 'mongo:4.0', + 'mongo:4.2', ], downloadOptions: { distro: 'ubuntu1604', @@ -287,7 +395,7 @@ describe('MongoCluster', function () { it('can serialize and deserialize sharded cluster info', async function () { cluster = await MongoCluster.start({ - version: '6.x', + version: '8.x', topology: 'sharded', tmpDir, secondaries: 0, @@ -302,7 +410,7 @@ describe('MongoCluster', function () { it('can let callers listen for server log events', async function () { cluster = await MongoCluster.start({ - version: '6.x', + version: '8.x', topology: 'replset', tmpDir, secondaries: 1, @@ -343,4 +451,182 @@ describe('MongoCluster', function () { baddoc: 1, }); }); + + it('can pass custom arguments for replica set members', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'replset', + tmpDir, + rsMembers: [ + { args: ['--setParameter', 'cursorTimeoutMillis=60000'] }, + { args: ['--setParameter', 'cursorTimeoutMillis=50000'] }, + ], + }); + + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.serverVersion).to.match(/^8\./); + const hello = await cluster.withClient(async (client) => { + return await client.db('admin').command({ hello: 1 }); + }); + expect(hello.hosts).to.have.lengthOf(1); + expect(hello.passives).to.have.lengthOf(1); + + const servers = cluster['servers']; + expect(servers).to.have.lengthOf(2); + const values = await Promise.all( + servers.map((srv) => + srv.withClient(async (client) => { + return await Promise.all([ + client + .db('admin') + .command({ getParameter: 1, cursorTimeoutMillis: 1 }), + client.db('admin').command({ hello: 1 }), + ]); + }), + ), + ); + + expect( + values.map((v) => [v[0].cursorTimeoutMillis, v[1].isWritablePrimary]), + ).to.deep.equal([ + [60000, true], + [50000, false], + ]); + }); + + it('can pass custom arguments for shards', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'sharded', + tmpDir, + secondaries: 0, + shards: [ + { args: ['--setParameter', 'cursorTimeoutMillis=60000'] }, + { args: ['--setParameter', 'cursorTimeoutMillis=50000'] }, + ], + }); + + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.serverVersion).to.match(/^8\./); + + const shards = cluster['shards']; + expect(shards).to.have.lengthOf(2); + const values = await Promise.all( + shards.map((srv) => + srv.withClient(async (client) => { + return await Promise.all([ + client + .db('admin') + .command({ getParameter: 1, cursorTimeoutMillis: 1 }), + client.db('admin').command({ hello: 1 }), + ]); + }), + ), + ); + + expect( + values.map((v) => [ + v[0].cursorTimeoutMillis, + v[1].setName === values[0][1].setName, + ]), + ).to.deep.equal([ + [60000, true], + [50000, false], + ]); + }); + + it('can pass custom arguments for mongoses', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'sharded', + tmpDir, + secondaries: 0, + mongosArgs: [ + ['--setParameter', 'cursorTimeoutMillis=60000'], + ['--setParameter', 'cursorTimeoutMillis=50000'], + ], + }); + + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.serverVersion).to.match(/^8\./); + + const mongoses = cluster['servers']; + expect(mongoses).to.have.lengthOf(2); + const values = await Promise.all( + mongoses.map((srv, i) => + srv.withClient(async (client) => { + return await Promise.all([ + client + .db('admin') + .command({ getParameter: 1, cursorTimeoutMillis: 1 }), + client.db('admin').command({ hello: 1 }), + // Ensure that the mongos announces itself to the cluster + client.db('test').collection(`test${i}`).insertOne({ dummy: 1 }), + ]); + }), + ), + ); + + const processIdForMongos = (v: any) => + v[1].topologyVersion.processId.toHexString(); + expect( + values.map((v) => [ + v[0].cursorTimeoutMillis, + v[1].msg, + processIdForMongos(v) === processIdForMongos(values[0]), + ]), + ).to.deep.equal([ + [60000, 'isdbgrid', true], + [50000, 'isdbgrid', false], + ]); + + await eventually(async () => { + const mongosList = await cluster.withClient( + async (client) => + await client.db('config').collection('mongos').find().toArray(), + ); + expect(mongosList).to.have.lengthOf(2); + }); + }); + + it('can add authentication options and verify them after serialization', async function () { + cluster = await MongoCluster.start({ + version: '8.x', + topology: 'sharded', + tmpDir, + secondaries: 1, + shards: 1, + users: [ + { + username: 'testuser', + password: 'testpass', + roles: [{ role: 'readWriteAnyDatabase', db: 'admin' }], + }, + ], + mongosArgs: [[], []], + }); + expect(cluster.connectionString).to.be.a('string'); + expect(cluster.serverVersion).to.match(/^8\./); + expect(cluster.connectionString).to.include('testuser:testpass@'); + await cluster.withClient(async (client) => { + const result = await client + .db('test') + .collection('test') + .insertOne({ foo: 42 }); + expect(result.insertedId).to.exist; + }); + + cluster = await MongoCluster.deserialize(cluster.serialize()); + expect(cluster.connectionString).to.include('testuser:testpass@'); + const [doc, status] = await cluster.withClient(async (client) => { + return Promise.all([ + client.db('test').collection('test').findOne({ foo: 42 }), + client.db('admin').command({ connectionStatus: 1 }), + ] as const); + }); + expect(doc?.foo).to.equal(42); + expect(status.authInfo.authenticatedUsers).to.deep.equal([ + { user: 'testuser', db: 'admin' }, + ]); + }); }); diff --git a/packages/mongodb-runner/src/mongocluster.ts b/packages/mongodb-runner/src/mongocluster.ts index 9e75119c..2afc9715 100644 --- a/packages/mongodb-runner/src/mongocluster.ts +++ b/packages/mongodb-runner/src/mongocluster.ts @@ -3,27 +3,146 @@ import { MongoServer } from './mongoserver'; import { ConnectionString } from 'mongodb-connection-string-url'; import type { DownloadOptions } from '@mongodb-js/mongodb-downloader'; import { downloadMongoDb } from '@mongodb-js/mongodb-downloader'; -import type { MongoClientOptions } from 'mongodb'; +import type { + Document, + MongoClientOptions, + TagSet, + WriteConcernSettings, +} from 'mongodb'; import { MongoClient } from 'mongodb'; -import { sleep, range, uuid, debug } from './util'; +import { + sleep, + range, + uuid, + debug, + jsonClone, + debugVerbose, + makeConnectionString, +} from './util'; import { OIDCMockProviderProcess } from './oidc'; import { EventEmitter } from 'events'; +import assert from 'assert'; +import { handleTLSClientKeyOptions } from './tls-helpers'; -export interface MongoClusterOptions - extends Pick< - MongoServerOptions, - 'logDir' | 'tmpDir' | 'args' | 'binDir' | 'docker' - > { - topology: 'standalone' | 'replset' | 'sharded'; - arbiters?: number; - secondaries?: number; - shards?: number; - version?: string; +/** + * Description of a MongoDB user account that will be created in a test cluster. + */ +export interface MongoDBUserDoc { + /** + * SCRAM-SHA-256 username. + */ + username: string; + /** + * SCRAM-SHA-256 password. + */ + password: string; + /** + * Additional metadata for a given user. + */ + customData?: Document; + /** + * Roles to assign to the user. + */ + roles: ({ role: string; db?: string } | string)[]; + /** + * Additional fields may be included as per the `createUser` command. + */ + [key: string]: unknown; +} + +/** Describe the individual members of a replica set */ +export interface RSMemberOptions { + /** + * Tags to assign to the member, in the format expected by the Node.js driver. + */ + tags?: TagSet; + /** + * Priority of the member. If none is specified, one member will be given priority 1 + * and all others priority 0. The mongodb-runner package assumes that the highest priority + * member will become primary. + */ + priority?: number; + /** + * Additional arguments for the member. + */ + args?: string[]; + /** + * Whether the member is an arbiter. + */ + arbiterOnly?: boolean; +} + +/** + * Shared options for all cluster topologies. + */ +export interface CommonOptions { + /** + * Directory where server binaries will be downloaded and stored. + */ downloadDir?: string; + /** + * Various options to control the download of MongoDB binaries. + */ downloadOptions?: DownloadOptions; + + /** + * OIDC mock provider command line (e.g. '--port=0' or full path to binary). + * If provided, an OIDC mock provider will be started alongside the cluster, + * and the necessary parameters to connect to it will be added to the + * cluster's mongod/mongos processes. + */ oidc?: string; + + /** + * MongoDB server version to download and use (e.g. '6.0.3', '8.x-enterprise', 'latest-alpha', etc.) + */ + version?: string; + /** + * User accounts to create after starting the cluster. + */ + users?: MongoDBUserDoc[]; + + /** + * Whether to automatically add an additional TLS client certificate key file + * to the cluster nodes based on whether TLS configuration was detected. + * + * Adding this is required in order for authentication to work when TLS is enabled. + */ + tlsAddClientKey?: boolean; + + /** + * Topology of the cluster. + */ + topology: 'standalone' | 'replset' | 'sharded'; } +/** + * Options specific to replica set clusters. + */ +export type RSOptions = { + /** Number of arbiters to create (default: 0) */ + arbiters?: number; + /** Number of secondary nodes to create (default: 2) */ + secondaries?: number; + /** Explicitly specify replica set members. If set, `arbiters` and `secondaries` will be ignored. */ + rsMembers?: RSMemberOptions[]; +}; + +export type ShardedOptions = { + /** Arguments to pass to each mongos instance. */ + mongosArgs?: string[][]; + /** Number of shards to create or explicit shard configurations. */ + shards?: number | Partial[]; +}; + +export type MongoClusterOptions = Pick< + MongoServerOptions, + 'logDir' | 'tmpDir' | 'args' | 'binDir' | 'docker' | 'internalClientOptions' +> & + CommonOptions & + RSOptions & + ShardedOptions; + export type MongoClusterEvents = { [k in keyof MongoServerEvents]: [serverUUID: string, ...MongoServerEvents[k]]; } & { @@ -31,12 +150,120 @@ export type MongoClusterEvents = { removeListener: [keyof MongoClusterEvents]; }; +function removePortArg([...args]: string[]): string[] { + let portArgIndex = -1; + if ((portArgIndex = args.indexOf('--port')) !== -1) { + args.splice(portArgIndex + 1, 1); + } else if ( + (portArgIndex = args.findIndex((arg) => arg.startsWith('--port='))) !== -1 + ) { + args.splice(portArgIndex, 1); + } + return args; +} + +function hasPortArg(args: string[] | undefined): boolean { + if (!args) return false; + return ( + args.includes('--port') || args.some((arg) => arg.startsWith('--port=')) + ); +} + +function processRSMembers(options: MongoClusterOptions): { + rsMembers: RSMemberOptions[]; + replSetName: string; +} { + const { + secondaries = 2, + arbiters = 0, + args: [...args] = [], + rsMembers, + } = options; + + let replSetName: string; + if (!args.includes('--replSet')) { + replSetName = `replSet-${uuid()}`; + args.push('--replSet', replSetName); + } else { + replSetName = args[args.indexOf('--replSet') + 1]; + } + + const primaryArgs: string[] = [...args]; + const secondaryArgs = [...removePortArg(args), '--port', '0']; + + if (rsMembers) { + const primary = rsMembers.find((m) => + rsMembers.every((m2) => m.priority ?? 0 >= (m2.priority ?? 0)), + ); + return { + rsMembers: rsMembers.map((m) => ({ + ...m, + args: [ + ...(m.args ?? []), + ...(hasPortArg(m.args) + ? args + : m === primary + ? primaryArgs + : secondaryArgs), + ], + })), + replSetName, + }; + } + + return { + rsMembers: [ + { priority: 1, args: primaryArgs }, + ...range(secondaries).map(() => ({ priority: 0, args: secondaryArgs })), + ...range(arbiters).map(() => ({ + priority: 0, + arbiterOnly: true, + args: secondaryArgs, + })), + ], + replSetName, + }; +} + +function processShardOptions(options: MongoClusterOptions): { + shards: Partial[]; + mongosArgs: string[][]; +} { + const shards: Partial[] = + typeof options.shards === 'number' || !options.shards + ? range((options.shards ?? 3) + 1).map( + () => ({}) as Partial, + ) + : options.shards; + const { mongosArgs = [[]], args = [] } = options; + return { + shards: shards.map(({ args = [], ...perShardOpts }, i) => ({ + ...perShardOpts, + args: [ + ...removePortArg(args), + ...args, + ...(args.includes('--configsvr') || args.includes('--shardsvr') + ? [] + : i === 0 + ? ['--configsvr'] + : ['--shardsvr']), + ], + })), + mongosArgs: mongosArgs.map((perMongosArgs, i) => [ + ...(i === 0 && !hasPortArg(perMongosArgs) ? args : removePortArg(args)), + ...perMongosArgs, + ]), + }; +} + export class MongoCluster extends EventEmitter { private topology: MongoClusterOptions['topology'] = 'standalone'; private replSetName?: string; private servers: MongoServer[] = []; // mongod/mongos private shards: MongoCluster[] = []; // replsets private oidcMockProviderProcess?: OIDCMockProviderProcess; + private defaultConnectionOptions: Partial = {}; + private users: MongoDBUserDoc[] = []; private constructor() { super(); @@ -75,17 +302,24 @@ export class MongoCluster extends EventEmitter { servers: this.servers.map((srv) => srv.serialize()), shards: this.shards.map((shard) => shard.serialize()), oidcMockProviderProcess: this.oidcMockProviderProcess?.serialize(), + defaultConnectionOptions: jsonClone(this.defaultConnectionOptions ?? {}), + users: jsonClone(this.users), }; } isClosed(): boolean { - return this.servers.length === 0 && this.shards.length === 0; + // Return true if and only if there are no running sub-clusters/servers + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for (const _ of this.children()) return true; + return true; } static async deserialize(serialized: any): Promise { const cluster = new MongoCluster(); cluster.topology = serialized.topology; cluster.replSetName = serialized.replSetName; + cluster.defaultConnectionOptions = serialized.defaultConnectionOptions; + cluster.users = serialized.users; cluster.servers = await Promise.all( serialized.servers.map((srv: any) => MongoServer.deserialize(srv)), ); @@ -103,13 +337,11 @@ export class MongoCluster extends EventEmitter { } get connectionString(): string { - const cs = new ConnectionString(`mongodb://${this.hostport}/`); - if (this.replSetName) - cs.typedSearchParams().set( - 'replicaSet', - this.replSetName, - ); - return cs.toString(); + return makeConnectionString( + this.hostport, + this.replSetName, + this.defaultConnectionOptions, + ); } get oidcIssuer(): string | undefined { @@ -131,8 +363,12 @@ export class MongoCluster extends EventEmitter { static async start({ ...options }: MongoClusterOptions): Promise { + options = { ...options, ...(await handleTLSClientKeyOptions(options)) }; + const cluster = new MongoCluster(); cluster.topology = options.topology; + cluster.users = options.users ?? []; + cluster.defaultConnectionOptions = { ...options.internalClientOptions }; if (!options.binDir) { options.binDir = await this.downloadMongoDb( options.downloadDir ?? options.tmpDir, @@ -174,61 +410,49 @@ export class MongoCluster extends EventEmitter { }), ); } else if (options.topology === 'replset') { - const { secondaries = 2, arbiters = 0 } = options; - - const args = [...(options.args ?? [])]; - let replSetName: string; - if (!args.includes('--replSet')) { - replSetName = `replSet-${uuid()}`; - args.push('--replSet', replSetName); - } else { - replSetName = args[args.indexOf('--replSet') + 1]; - } + const { rsMembers, replSetName } = processRSMembers(options); - const primaryArgs = [...args]; - debug('Starting primary', primaryArgs); - const primary = await MongoServer.start({ - ...options, - args: primaryArgs, - binary: 'mongod', + debug('Starting replica set nodes', { + replSetName, + secondaries: rsMembers.filter((m) => !m.arbiterOnly).length - 1, + arbiters: rsMembers.filter((m) => m.arbiterOnly).length, }); - cluster.servers.push(primary); - - if (args.includes('--port')) { - args.splice(args.indexOf('--port') + 1, 1, '0'); - } + const primaryIndex = rsMembers.findIndex((m) => + rsMembers.every((m2) => m.priority ?? 0 >= (m2.priority ?? 0)), + ); + assert.notStrictEqual(primaryIndex, -1); - debug('Starting secondaries and arbiters', { - secondaries, - arbiters, - args, - }); - cluster.servers.push( - ...(await Promise.all( - range(secondaries + arbiters).map(() => - MongoServer.start({ + const nodes = await Promise.all( + rsMembers.map(async (member) => { + return [ + await MongoServer.start({ ...options, - args, + args: member.args, binary: 'mongod', }), - ), - )), + member, + ] as const; + }), ); + cluster.servers.push(...nodes.map(([srv]) => srv)); + const primary = cluster.servers[primaryIndex]; await primary.withClient(async (client) => { debug('Running rs.initiate'); const rsConf = { _id: replSetName, - configsvr: args.includes('--configsvr'), - members: cluster.servers.map((srv, i) => { + configsvr: rsMembers.some((m) => m.args?.includes('--configsvr')), + members: nodes.map(([srv, member], i) => { return { _id: i, host: srv.hostport, - arbiterOnly: i > 1 + secondaries, - priority: i === 0 ? 1 : 0, + arbiterOnly: member.arbiterOnly ?? false, + priority: member.priority ?? (i === primaryIndex ? 1 : 0), + tags: member.tags || {}, }; }), }; + debugVerbose('replSetInitiate:', rsConf); await client.db('admin').command({ replSetInitiate: rsConf, }); @@ -240,7 +464,11 @@ export class MongoCluster extends EventEmitter { if ( status.members.some((member: any) => member.stateStr === 'PRIMARY') ) { - debug('rs.status indicated primary for replset', status.set); + debug( + 'rs.status indicated primary for replset', + status.set, + status.members, + ); cluster.replSetName = status.set; break; } @@ -249,35 +477,43 @@ export class MongoCluster extends EventEmitter { } }); } else if (options.topology === 'sharded') { - const { shards = 3 } = options; - const shardArgs = [...(options.args ?? [])]; - if (shardArgs.includes('--port')) { - shardArgs.splice(shardArgs.indexOf('--port') + 1, 1, '0'); - } - - debug('starting config server and shard servers', shardArgs); - const [configsvr, ...shardsvrs] = await Promise.all( - range(shards + 1).map((i) => - MongoCluster.start({ + const { shards, mongosArgs } = processShardOptions(options); + debug('starting config server and shard servers', shards); + const allShards = await Promise.all( + shards.map(async (s) => { + const isConfig = s.args?.includes('--configsvr'); + const cluster = await MongoCluster.start({ ...options, - args: [...shardArgs, i > 0 ? '--shardsvr' : '--configsvr'], + ...s, topology: 'replset', - }), - ), + users: isConfig ? undefined : options.users, // users go on the mongos/config server only for the config set + }); + return [cluster, isConfig] as const; + }), ); + const configsvr = allShards.find(([, isConfig]) => isConfig)![0]; + const shardsvrs = allShards + .filter(([, isConfig]) => !isConfig) + .map(([shard]) => shard); cluster.shards.push(configsvr, ...shardsvrs); - debug('starting mongos'); - const mongos = await MongoServer.start({ - ...options, - binary: 'mongos', - args: [ - ...(options.args ?? []), - '--configdb', - `${configsvr.replSetName!}/${configsvr.hostport}`, - ], - }); - cluster.servers.push(mongos); + const mongosServers: MongoServer[] = await Promise.all( + mongosArgs.map(async (args) => { + debug('starting mongos'); + return await MongoServer.start({ + ...options, + binary: 'mongos', + args: [ + ...(options.args ?? []), + ...args, + '--configdb', + `${configsvr.replSetName!}/${configsvr.hostport}`, + ], + }); + }), + ); + cluster.servers.push(...mongosServers); + const mongos = mongosServers[0]; await mongos.withClient(async (client) => { for (const shard of shardsvrs) { const shardSpec = `${shard.replSetName!}/${shard.hostport}`; @@ -289,13 +525,51 @@ export class MongoCluster extends EventEmitter { debug('added shards'); }); } + + await cluster.addAuthIfNeeded(); return cluster; } + private *children(): Iterable { + yield* this.servers; + yield* this.shards; + } + + async addAuthIfNeeded(): Promise { + if (!this.users?.length) return; + // Sleep to give time for a possible replset election to settle. + await sleep(1000); + await this.withClient(async (client) => { + const admin = client.db('admin'); + for (const user of this.users) { + const { username, password, ...rest } = user; + debug('adding new user', { username, ...rest }, this.connectionString); + await admin.command({ createUser: username, pwd: password, ...rest }); + } + }); + await this.updateDefaultConnectionOptions({ + auth: this.users[0], + }); + } + + async updateDefaultConnectionOptions( + options: Partial, + ): Promise { + await Promise.all( + [...this.children()].map(async (child) => + child.updateDefaultConnectionOptions(options), + ), + ); + this.defaultConnectionOptions = { + ...this.defaultConnectionOptions, + ...options, + }; + } + async close(): Promise { await Promise.all( - [...this.servers, ...this.shards, this.oidcMockProviderProcess].map( - (closable) => closable?.close(), + [...this.children(), this.oidcMockProviderProcess].map((closable) => + closable?.close(), ), ); this.servers = []; @@ -306,10 +580,11 @@ export class MongoCluster extends EventEmitter { fn: Fn, clientOptions: MongoClientOptions = {}, ): Promise> { - const client = await MongoClient.connect( - this.connectionString, - clientOptions, - ); + const client = await MongoClient.connect(this.connectionString, { + ...this.getFullWriteConcernOptions(), + ...this.defaultConnectionOptions, + ...clientOptions, + }); try { return await fn(client); } finally { @@ -318,10 +593,35 @@ export class MongoCluster extends EventEmitter { } ref(): void { - for (const child of [...this.servers, ...this.shards]) child.ref(); + for (const child of this.children()) child.ref(); } unref(): void { - for (const child of [...this.servers, ...this.shards]) child.unref(); + for (const child of this.children()) child.unref(); + } + + // Return maximal write concern options based on topology + getFullWriteConcernOptions(): { writeConcern?: WriteConcernSettings } { + switch (this.topology) { + case 'standalone': + return {}; + case 'replset': + return { writeConcern: { w: this.servers.length, j: true } }; + case 'sharded': + return { + writeConcern: { + w: Math.min( + ...this.shards + .map((s) => s.getFullWriteConcernOptions().writeConcern?.w) + .filter((w) => typeof w === 'number'), + ), + j: true, + }, + }; + default: + throw new Error( + `Not implemented for topology ${this.topology as string}`, + ); + } } } diff --git a/packages/mongodb-runner/src/mongoserver.ts b/packages/mongodb-runner/src/mongoserver.ts index 2f7b0643..7e7fab1c 100644 --- a/packages/mongodb-runner/src/mongoserver.ts +++ b/packages/mongodb-runner/src/mongoserver.ts @@ -13,15 +13,33 @@ import type { Document, MongoClientOptions } from 'mongodb'; import { MongoClient } from 'mongodb'; import path from 'path'; import { EventEmitter, once } from 'events'; -import { uuid, debug, pick, debugVerbose } from './util'; +import { + uuid, + debug, + pick, + debugVerbose, + jsonClone, + makeConnectionString, +} from './util'; +/** + * Options for starting a MongoDB server process. + */ export interface MongoServerOptions { + /** Directory where server binaries are located. */ binDir?: string; - binary: string; // 'mongod', 'mongos', etc. - tmpDir: string; // Stores e.g. database contents - logDir?: string; // If set, pipe log file output through here. - args?: string[]; // May or may not contain --port - docker?: string | string[]; // Image or docker options + /** The MongoDB binary to run, e.g., 'mongod', 'mongos', etc. */ + binary: string; + /** Directory for temporary files, e.g., database contents */ + tmpDir: string; + /** If set, log file output will be piped through here. */ + logDir?: string; + /** Arguments to pass to the MongoDB binary. May or may not contain --port */ + args?: string[]; + /** Docker image or options to run the MongoDB binary in a container. */ + docker?: string | string[]; + /** Internal options for the MongoDB client used by this server instance. */ + internalClientOptions?: Partial; } interface SerializedServerProperties { @@ -29,6 +47,7 @@ interface SerializedServerProperties { pid?: number; port?: number; dbPath?: string; + defaultConnectionOptions?: Partial; startTime: string; hasInsertedMetadataCollEntry: boolean; } @@ -47,6 +66,7 @@ export class MongoServer extends EventEmitter { private closing = false; private startTime = new Date().toISOString(); private hasInsertedMetadataCollEntry = false; + private defaultConnectionOptions?: Partial; get id(): string { return this.uuid; @@ -65,6 +85,7 @@ export class MongoServer extends EventEmitter { dbPath: this.dbPath, startTime: this.startTime, hasInsertedMetadataCollEntry: this.hasInsertedMetadataCollEntry, + defaultConnectionOptions: jsonClone(this.defaultConnectionOptions ?? {}), }; } @@ -74,6 +95,7 @@ export class MongoServer extends EventEmitter { const srv = new MongoServer(); srv.uuid = serialized._id; srv.port = serialized.port; + srv.defaultConnectionOptions = serialized.defaultConnectionOptions; srv.closing = !!(await srv._populateBuildInfo('restore-check')); if (!srv.closing) { srv.pid = serialized.pid; @@ -143,7 +165,7 @@ export class MongoServer extends EventEmitter { ...options }: MongoServerOptions): Promise { const srv = new MongoServer(); - + srv.defaultConnectionOptions = { ...options.internalClientOptions }; if (!options.docker) { const dbPath = path.join(options.tmpDir, `db-${srv.uuid}`); await fs.mkdir(dbPath, { recursive: true }); @@ -276,8 +298,32 @@ export class MongoServer extends EventEmitter { return srv; } + async updateDefaultConnectionOptions( + options: Partial, + ): Promise { + let buildInfoError: Error | null = null; + for (let attempts = 0; attempts < 10; attempts++) { + buildInfoError = await this._populateBuildInfo('restore-check', { + ...options, + }); + if (!buildInfoError) break; + debug( + 'failed to get buildInfo when setting new options', + buildInfoError, + options, + this.connectionString, + ); + } + if (buildInfoError) throw buildInfoError; + this.defaultConnectionOptions = { + ...this.defaultConnectionOptions, + ...options, + }; + } + async close(): Promise { this.closing = true; + debug('in close', 'pid', this.pid); if (this.childProcess) { debug('closing running process', this.childProcess.pid); if ( @@ -321,8 +367,17 @@ export class MongoServer extends EventEmitter { .collection< Omit >('mongodbrunner'); + // mongos hosts require a bit of special treatment because they do not have + // local storage of their own, so we store the metadata in the config database, + // which may be accessed by multiple mongos instances. debug('ensuring metadata collection entry', insertedInfo, { isMongoS }); if (mode === 'insert-new') { + const existingEntry = await runnerColl.findOne(); + if (!isMongoS && existingEntry) { + throw new Error( + `Unexpected mongodbrunner entry when creating new server: ${JSON.stringify(existingEntry)}`, + ); + } await runnerColl.insertOne(insertedInfo); debug('inserted metadata collection entry', insertedInfo); this.hasInsertedMetadataCollEntry = true; @@ -333,7 +388,9 @@ export class MongoServer extends EventEmitter { ); return; } - const match = await runnerColl.findOne(); + const match = await runnerColl.findOne( + isMongoS ? { _id: this.uuid } : {}, + ); debug('read metadata collection entry', insertedInfo, match); if (!match) { throw new Error( @@ -350,10 +407,11 @@ export class MongoServer extends EventEmitter { private async _populateBuildInfo( mode: 'insert-new' | 'restore-check', + clientOpts?: Partial, ): Promise { try { // directConnection + retryWrites let us write to `local` db on secondaries - const clientOpts = { retryWrites: false }; + clientOpts = { retryWrites: false, ...clientOpts }; this.buildInfo = await this.withClient(async (client) => { // Insert the metadata entry, except if we're a freshly started mongos // (which does not have its own storage to persist) @@ -372,12 +430,21 @@ export class MongoServer extends EventEmitter { return null; } + get connectionString(): string { + return makeConnectionString( + this.hostport, + undefined, + this.defaultConnectionOptions, + ); + } + async withClient any>( fn: Fn, clientOptions: MongoClientOptions = {}, ): Promise> { - const client = await MongoClient.connect(`mongodb://${this.hostport}/`, { + const client = await MongoClient.connect(this.connectionString, { directConnection: true, + ...this.defaultConnectionOptions, ...clientOptions, }); try { diff --git a/packages/mongodb-runner/src/tls-helpers.ts b/packages/mongodb-runner/src/tls-helpers.ts new file mode 100644 index 00000000..dd19985c --- /dev/null +++ b/packages/mongodb-runner/src/tls-helpers.ts @@ -0,0 +1,111 @@ +import * as x509 from '@peculiar/x509'; +import { webcrypto } from 'crypto'; +import { uuid } from './util'; +import path from 'path'; +import { writeFile, readFile } from 'fs/promises'; +import type { MongoClientOptions } from 'mongodb'; +x509.cryptoProvider.set(webcrypto as typeof crypto); + +export interface TLSClientOptions { + tlsAddClientKey?: boolean; + args?: string[]; + tmpDir: string; + internalClientOptions?: Partial; +} + +export async function handleTLSClientKeyOptions({ + tlsAddClientKey, + args: [...args] = [], + tmpDir, + internalClientOptions = {}, +}: TLSClientOptions): Promise> { + const existingTLSCAOptionIndex = args.findIndex((arg) => + arg.match(/^--tls(Cluster)?CAFile(=|$)/), + ); + + if (tlsAddClientKey === false) return {}; + if (tlsAddClientKey !== true && existingTLSCAOptionIndex === -1) return {}; + if (tlsAddClientKey !== true && internalClientOptions.tlsCertificateKeyFile) + return {}; + + const alg: RsaHashedKeyGenParams = { + name: 'RSASSA-PKCS1-v1_5', + hash: 'SHA-256', + publicExponent: new Uint8Array([1, 0, 1]), + modulusLength: 2048, + }; + const now = Date.now(); + const keys = await webcrypto.subtle.generateKey(alg, true, [ + 'sign', + 'verify', + ]); + const cert = await x509.X509CertificateGenerator.createSelfSigned({ + name: 'O=MongoDB, OU=MongoDBRunnerCA, CN=MongoDBRunnerCA', + notBefore: new Date(now - 1000 * 60 * 60), + notAfter: new Date(now + 1000 * 60 * 60 * 24 * 365 * 10), + signingAlgorithm: alg, + keys, + extensions: [ + await x509.SubjectKeyIdentifierExtension.create(keys.publicKey), + ], + }); + const clientPEMContent = Buffer.from(cert.toString('pem') + '\n'); + + const existingTLSCAOptionHasValue = + args[existingTLSCAOptionIndex].includes('='); + const existingTLSCAOption = args[existingTLSCAOptionIndex].split('=')[0]; + const existingTLSCAOptionValue = existingTLSCAOptionHasValue + ? args[existingTLSCAOptionIndex].split('=')[1] + : args[existingTLSCAOptionIndex + 1]; + + const id = uuid(); + const clientPEM = path.join(tmpDir, `mongodb-runner-client-${id}.pem`); + const caPEM = path.join(tmpDir, `mongodb-runner-ca-${id}.pem`); + + await Promise.all([ + (async () => { + await writeFile( + clientPEM, + Buffer.concat([ + clientPEMContent, + Buffer.from( + pkcs8ToPEM( + await webcrypto.subtle.exportKey('pkcs8', keys.privateKey), + ), + ), + ]), + ); + })(), + (async () => { + await writeFile( + caPEM, + Buffer.concat([ + clientPEMContent, + await readFile(existingTLSCAOptionValue), + ]), + ); + })(), + ]); + + args.splice( + existingTLSCAOptionIndex, + existingTLSCAOptionHasValue ? 1 : 2, + `${existingTLSCAOption}=${caPEM}`, + ); + + return { + args, + tlsAddClientKey: false, + internalClientOptions: { + tlsCertificateKeyFile: clientPEM, + tlsAllowInvalidCertificates: true, + tls: true, + ...internalClientOptions, + }, + }; +} + +function pkcs8ToPEM(pkcs8Buffer: ArrayBuffer): string { + const b64 = Buffer.from(pkcs8Buffer).toString('base64'); + return `-----BEGIN PRIVATE KEY-----\n${b64.match(/.{1,64}/g)?.join('\n') || ''}\n-----END PRIVATE KEY-----\n`; +} diff --git a/packages/mongodb-runner/src/util.ts b/packages/mongodb-runner/src/util.ts index 57507397..55618657 100644 --- a/packages/mongodb-runner/src/util.ts +++ b/packages/mongodb-runner/src/util.ts @@ -1,5 +1,7 @@ +import type { MongoClientOptions } from 'mongodb'; import { BSON } from 'mongodb'; import createDebug from 'debug'; +import { ConnectionString } from 'mongodb-connection-string-url'; export const debug = createDebug('mongodb-runner'); export const debugVerbose = debug.extend('verbose'); @@ -42,3 +44,74 @@ export function pick( } return ret as Pick; } + +export function jsonClone(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} + +export function makeConnectionString( + hostport: string, + replSetName?: string, + defaultConnectionOptions: Partial = {}, +): string { + const cs = new ConnectionString(`mongodb://${hostport}/`); + if (replSetName) { + cs.typedSearchParams().set('replicaSet', replSetName); + } + if (defaultConnectionOptions.tls) { + cs.typedSearchParams().set('tls', 'true'); + } + if (defaultConnectionOptions.tlsCAFile) { + cs.typedSearchParams().set( + 'tlsCAFile', + defaultConnectionOptions.tlsCAFile, + ); + } + if (defaultConnectionOptions.tlsCertificateKeyFile) { + cs.typedSearchParams().set( + 'tlsCertificateKeyFile', + defaultConnectionOptions.tlsCertificateKeyFile, + ); + } + if (defaultConnectionOptions.tlsCertificateKeyFilePassword) { + cs.typedSearchParams().set( + 'tlsCertificateKeyFilePassword', + defaultConnectionOptions.tlsCertificateKeyFilePassword, + ); + } + if (defaultConnectionOptions.tlsAllowInvalidCertificates) { + cs.typedSearchParams().set( + 'tlsAllowInvalidCertificates', + 'true', + ); + } + if (defaultConnectionOptions.auth?.username) { + cs.username = defaultConnectionOptions.auth.username; + } + if (defaultConnectionOptions.auth?.password) { + cs.password = defaultConnectionOptions.auth.password; + } + return cs.toString(); +} + +export async function eventually( + fn: () => Promise | void, + { + intervalMs = 100, + timeoutMs = 5000, + }: { intervalMs?: number; timeoutMs?: number } = {}, +): Promise { + const startTime = Date.now(); + // eslint-disable-next-line no-constant-condition + while (true) { + try { + await fn(); + } catch (err) { + if (Date.now() - startTime > timeoutMs) { + throw err; + } else { + await sleep(intervalMs); + } + } + } +}