diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 050739c404..f8e93f6475 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.NODE_VERSION }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - name: Cache Node.js modules @@ -29,8 +29,10 @@ jobs: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}- - name: Install dependencies run: npm ci - - name: CI Self-Check + - name: CI Environments Check run: npm run ci:check + - name: CI Node Engine Check + run: npm run ci:checkNodeEngine check-changelog: name: Changelog timeout-minutes: 5 @@ -45,7 +47,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.NODE_VERSION }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - name: Cache Node.js modules @@ -65,7 +67,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.NODE_VERSION }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - name: Cache Node.js modules @@ -159,7 +161,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.NODE_VERSION }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.NODE_VERSION }} - name: Cache Node.js modules @@ -219,7 +221,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.NODE_VERSION }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: ${{ matrix.NODE_VERSION }} - name: Cache Node.js modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ff5620f9..3e1d355b7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ ___ - Remove support for Node 10 which has reached its End-of-Life date (Manuel Trezza) [#7314](https://github.com/parse-community/parse-server/pull/7314) - Remove S3 Files Adapter from Parse Server, instead install separately as `@parse/s3-files-adapter` (Manuel Trezza) [#7324](https://github.com/parse-community/parse-server/pull/7324) - Remove Session field `restricted`; the field was a code artifact from a feature that never existed in Open Source Parse Server; if you have been using this field for custom purposes, consider that for new Parse Server installations the field does not exist anymore in the schema, and for existing installations the field default value `false` will not be set anymore when creating a new session (Manuel Trezza) [#7543](https://github.com/parse-community/parse-server/pull/7543) +- ci: add node engine version check (Manuel Trezza) [#7574](https://github.com/parse-community/parse-server/pull/7574) ### Notable Changes - Added Parse Server Security Check to report weak security settings (Manuel Trezza, dblythy) [#7247](https://github.com/parse-community/parse-server/issues/7247) diff --git a/resources/ci/CiVersionCheck.js b/ci/CiVersionCheck.js similarity index 100% rename from resources/ci/CiVersionCheck.js rename to ci/CiVersionCheck.js diff --git a/resources/ci/ciCheck.js b/ci/ciCheck.js similarity index 100% rename from resources/ci/ciCheck.js rename to ci/ciCheck.js diff --git a/ci/nodeEngineCheck.js b/ci/nodeEngineCheck.js new file mode 100644 index 0000000000..da68f314b1 --- /dev/null +++ b/ci/nodeEngineCheck.js @@ -0,0 +1,190 @@ +const core = require('@actions/core'); +const semver = require('semver'); +const fs = require('fs').promises; +const path = require('path'); + +/** + * This checks whether any package dependency requires a minimum node engine + * version higher than the host package. + */ +class NodeEngineCheck { + + /** + * The constructor. + * @param {Object} config The config. + * @param {String} config.nodeModulesPath The path to the node_modules directory. + * @param {String} config.packageJsonPath The path to the parent package.json file. + */ + constructor(config) { + const { + nodeModulesPath, + packageJsonPath, + } = config; + + // Ensure required params are set + if ([ + nodeModulesPath, + packageJsonPath, + ].includes(undefined)) { + throw 'invalid configuration'; + } + + this.nodeModulesPath = nodeModulesPath; + this.packageJsonPath = packageJsonPath; + } + + /** + * Returns an array of `package.json` files under the given path and subdirectories. + * @param {String} [basePath] The base path for recursive directory search. + */ + async getPackageFiles(basePath = this.nodeModulesPath) { + try { + // Declare file list + const files = [] + + // Get files + const dirents = await fs.readdir(basePath, { withFileTypes: true }); + const validFiles = dirents.filter(d => d.name.toLowerCase() == 'package.json').map(d => path.join(basePath, d.name)); + files.push(...validFiles); + + // For each directory entry + for (const dirent of dirents) { + if (dirent.isDirectory()) { + const subFiles = await this.getPackageFiles(path.join(basePath, dirent.name)); + files.push(...subFiles); + } + } + return files; + } catch (e) { + throw `Failed to get package.json files in ${this.nodeModulesPath} with error: ${e}`; + } + } + + /** + * Extracts and returns the node engine versions of the given package.json + * files. + * @param {String[]} files The package.json files. + * @param {Boolean} clean Is true if packages with undefined node versions + * should be removed from the results. + * @returns {Object[]} A list of results. + */ + async getNodeVersion({ files, clean = false }) { + + // Declare response + let response = []; + + // For each file + for (const file of files) { + + // Get node version + const contentString = await fs.readFile(file, 'utf-8'); + const contentJson = JSON.parse(contentString); + const version = ((contentJson || {}).engines || {}).node; + + // Add response + response.push({ + file: file, + nodeVersion: version + }); + } + + // If results should be cleaned by removing undefined node versions + if (clean) { + response = response.filter(r => r.nodeVersion !== undefined); + } + return response; + } + + /** + * Returns the highest semver definition that satisfies all versions + * in the given list. + * @param {String[]} versions The list of semver version ranges. + * @param {String} baseVersion The base version of which higher versions should be + * determined; as a version (1.2.3), not a range (>=1.2.3). + * @returns {String} The highest semver version. + */ + getHigherVersions({ versions, baseVersion }) { + // Add min satisfying node versions + const minVersions = versions.map(v => { + v.nodeMinVersion = semver.minVersion(v.nodeVersion) + return v; + }); + + // Sort by min version + const sortedMinVersions = minVersions.sort((v1, v2) => semver.compare(v1.nodeMinVersion, v2.nodeMinVersion)); + + // Filter by higher versions + const higherVersions = sortedMinVersions.filter(v => semver.gt(v.nodeMinVersion, baseVersion)); + // console.log(`getHigherVersions: ${JSON.stringify(higherVersions)}`); + return higherVersions; + } + + /** + * Returns the node version of the parent package. + * @return {Object} The parent package info. + */ + async getParentVersion() { + // Get parent package.json version + const version = await this.getNodeVersion({ files: [ this.packageJsonPath ], clean: true }); + // console.log(`getParentVersion: ${JSON.stringify(version)}`); + return version[0]; + } +} + +async function check() { + // Define paths + const nodeModulesPath = path.join(__dirname, '../node_modules'); + const packageJsonPath = path.join(__dirname, '../package.json'); + + // Create check + const check = new NodeEngineCheck({ + nodeModulesPath, + packageJsonPath, + }); + + // Get package node version of parent package + const parentVersion = await check.getParentVersion(); + + // If parent node version could not be determined + if (parentVersion === undefined) { + core.setFailed(`Failed to determine node engine version of parent package at ${this.packageJsonPath}`); + return; + } + + // Determine parent min version + const parentMinVersion = semver.minVersion(parentVersion.nodeVersion); + + // Get package.json files + const files = await check.getPackageFiles(); + core.info(`Checking the minimum node version requirement of ${files.length} dependencies`); + + // Get node versions + const versions = await check.getNodeVersion({ files, clean: true }); + + // Get are dependencies that require a higher node version than the parent package + const higherVersions = check.getHigherVersions({ versions, baseVersion: parentMinVersion }); + + // Get highest version + const highestVersion = higherVersions.map(v => v.nodeMinVersion).pop(); + + // If there are higher versions + if (higherVersions.length > 0) { + console.log(`\nThere are ${higherVersions.length} dependencies that require a higher node engine version than the parent package (${parentVersion.nodeVersion}):`); + + // For each dependency + for (const higherVersion of higherVersions) { + + // Get package name + const _package = higherVersion.file.split('node_modules/').pop().replace('/package.json', ''); + console.log(`- ${_package} requires at least node ${higherVersion.nodeMinVersion} (${higherVersion.nodeVersion})`); + } + console.log(''); + core.setFailed(`❌ Upgrade the node engine version in package.json to at least '${highestVersion}' to satisfy the dependencies.`); + console.log(''); + return; + } + + console.log(`✅ All dependencies satisfy the node version requirement of the parent package (${parentVersion.nodeVersion}).`); +} + +check(); diff --git a/package-lock.json b/package-lock.json index c87569b4d4..d0567d9917 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1991,19 +1991,19 @@ "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" }, "@typescript-eslint/types": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.2.tgz", - "integrity": "sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.0.tgz", + "integrity": "sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.2.tgz", - "integrity": "sha512-TJ0/hEnYxapYn9SGn3dCnETO0r+MjaxtlWZ2xU+EvytF0g4CqTpZL48SqSNn2hXsPolnewF30pdzR9a5Lj3DNg==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz", + "integrity": "sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.29.2", - "@typescript-eslint/visitor-keys": "4.29.2", + "@typescript-eslint/types": "4.31.0", + "@typescript-eslint/visitor-keys": "4.31.0", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -2047,12 +2047,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.29.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz", - "integrity": "sha512-bDgJLQ86oWHJoZ1ai4TZdgXzJxsea3Ee9u9wsTAvjChdj2WLcVsgWYAPeY7RQMn16tKrlQaBnpKv7KBfs4EQag==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz", + "integrity": "sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w==", "dev": true, "requires": { - "@typescript-eslint/types": "4.29.2", + "@typescript-eslint/types": "4.31.0", "eslint-visitor-keys": "^2.0.0" }, "dependencies": { @@ -4473,27 +4473,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "precinct": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/precinct/-/precinct-8.1.0.tgz", - "integrity": "sha512-oeZBR9IdER42Ef6Rz11z1oOUqicsI5J1Qffj6tYghKLhxN2UnHy7uE1axxNr0VZRevPK2HWkROk36uXrbJwHFA==", - "dev": true, - "requires": { - "commander": "^2.20.3", - "debug": "^4.3.1", - "detective-amd": "^3.0.1", - "detective-cjs": "^3.1.1", - "detective-es6": "^2.2.0", - "detective-less": "^1.0.2", - "detective-postcss": "^4.0.0", - "detective-sass": "^3.0.1", - "detective-scss": "^2.0.1", - "detective-stylus": "^1.0.0", - "detective-typescript": "^7.0.0", - "module-definition": "^3.3.1", - "node-source-walk": "^4.2.0" - } } } }, @@ -4567,17 +4546,23 @@ } }, "detective-postcss": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-4.0.0.tgz", - "integrity": "sha512-Fwc/g9VcrowODIAeKRWZfVA/EufxYL7XfuqJQFroBKGikKX83d2G7NFw6kDlSYGG3LNQIyVa+eWv1mqre+v4+A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-5.0.0.tgz", + "integrity": "sha512-IBmim4GTEmZJDBOAoNFBskzNryTmYpBq+CQGghKnSGkoGWascE8iEo98yA+ZM4N5slwGjCr/NxCm+Kzg+q3tZg==", "dev": true, "requires": { - "debug": "^4.1.1", + "debug": "^4.3.1", "is-url": "^1.2.4", - "postcss": "^8.1.7", - "postcss-values-parser": "^2.0.1" + "postcss": "^8.2.13", + "postcss-values-parser": "^5.0.0" }, "dependencies": { + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -4592,6 +4577,17 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "postcss-values-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-5.0.0.tgz", + "integrity": "sha512-2viDDjMMrt21W2izbeiJxl3kFuD/+asgB0CBwPEgSyhCmBnDIa/y+pLaoyX+q3I3DHH0oPPL3cgjVTQvlS1Maw==", + "dev": true, + "requires": { + "color-name": "^1.1.4", + "is-url-superb": "^4.0.0", + "quote-unquote": "^1.0.0" + } } } }, @@ -5769,9 +5765,9 @@ "integrity": "sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag==" }, "fastq": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz", - "integrity": "sha512-HOnr8Mc60eNYl1gzwp6r5RoUyAn5/glBolUzP/Ez6IFVPMPirxn/9phgL6zhOtaTy7ISwPvQ+wT+hfcRZh/bzw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", + "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -7425,6 +7421,12 @@ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", "dev": true }, + "is-url-superb": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", + "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8686,30 +8688,30 @@ "dev": true }, "madge": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/madge/-/madge-4.0.2.tgz", - "integrity": "sha512-l5bnA2dvyk0azLKDbOTCI+wDZ6nB007PhvPdmiYlPmqwVi49JPbhQrH/t4u8E6Akp3gwji1GZuA+v/F5q6yoWQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/madge/-/madge-5.0.1.tgz", + "integrity": "sha512-krmSWL9Hkgub74bOjnjWRoFPAJvPwSG6Dbta06qhWOq6X/n/FPzO3ESZvbFYVIvG2g4UHXvCJN1b+RZLaSs9nA==", "dev": true, "requires": { - "chalk": "^4.1.0", - "commander": "^6.2.1", + "chalk": "^4.1.1", + "commander": "^7.2.0", "commondir": "^1.0.1", - "debug": "^4.0.1", - "dependency-tree": "^8.0.0", - "detective-amd": "^3.0.1", + "debug": "^4.3.1", + "dependency-tree": "^8.1.1", + "detective-amd": "^3.1.0", "detective-cjs": "^3.1.1", - "detective-es6": "^2.1.0", + "detective-es6": "^2.2.0", "detective-less": "^1.0.2", - "detective-postcss": "^4.0.0", + "detective-postcss": "^5.0.0", "detective-sass": "^3.0.1", "detective-scss": "^2.0.1", "detective-stylus": "^1.0.0", "detective-typescript": "^7.0.0", "graphviz": "0.0.9", - "ora": "^5.1.0", + "ora": "^5.4.1", "pluralize": "^8.0.0", - "precinct": "^7.0.0", - "pretty-ms": "^7.0.0", + "precinct": "^8.1.0", + "pretty-ms": "^7.0.1", "rc": "^1.2.7", "typescript": "^3.9.5", "walkdir": "^0.4.1" @@ -8750,9 +8752,9 @@ "dev": true }, "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true }, "debug": { @@ -10321,9 +10323,9 @@ } }, "precinct": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/precinct/-/precinct-7.1.0.tgz", - "integrity": "sha512-I1RkW5PX51/q6Xl39//D7x9NgaKNGHpR5DCNaoxP/b2+KbzzXDNhauJUMV17KSYkJA41CSpwYUPRtRoNxbshWA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/precinct/-/precinct-8.1.0.tgz", + "integrity": "sha512-oeZBR9IdER42Ef6Rz11z1oOUqicsI5J1Qffj6tYghKLhxN2UnHy7uE1axxNr0VZRevPK2HWkROk36uXrbJwHFA==", "dev": true, "requires": { "commander": "^2.20.3", @@ -10336,7 +10338,7 @@ "detective-sass": "^3.0.1", "detective-scss": "^2.0.1", "detective-stylus": "^1.0.0", - "detective-typescript": "^6.0.0", + "detective-typescript": "^7.0.0", "module-definition": "^3.3.1", "node-source-walk": "^4.2.0" }, @@ -10356,16 +10358,16 @@ "ms": "2.1.2" } }, - "detective-typescript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-6.0.0.tgz", - "integrity": "sha512-vTidcSDK3QostdbrH2Rwf9FhvrgJ4oIaVw5jbolgruTejexk6nNa9DShGpuS8CFVDb1IP86jct5BaZt1wSxpkA==", + "detective-postcss": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-4.0.0.tgz", + "integrity": "sha512-Fwc/g9VcrowODIAeKRWZfVA/EufxYL7XfuqJQFroBKGikKX83d2G7NFw6kDlSYGG3LNQIyVa+eWv1mqre+v4+A==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "^4.8.2", - "ast-module-types": "^2.7.1", - "node-source-walk": "^4.2.0", - "typescript": "^3.9.7" + "debug": "^4.1.1", + "is-url": "^1.2.4", + "postcss": "^8.1.7", + "postcss-values-parser": "^2.0.1" } }, "ms": { @@ -10547,6 +10549,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "quote-unquote": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz", + "integrity": "sha1-Z6mncUjv/q+BpNQoQEpxC6qsigs=", + "dev": true + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", diff --git a/package.json b/package.json index 38635d0bac..6ca0e7fa9e 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "jsdoc": "3.6.3", "jsdoc-babel": "0.5.0", "lint-staged": "10.2.3", - "madge": "4.0.2", + "madge": "5.0.1", "mock-files-adapter": "file:spec/dependencies/mock-files-adapter", "mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter", "mongodb-runner": "4.8.1", @@ -101,7 +101,8 @@ "yaml": "1.10.0" }, "scripts": { - "ci:check": "node ./resources/ci/ciCheck.js", + "ci:check": "node ./ci/ciCheck.js", + "ci:checkNodeEngine": "node ./ci/nodeEngineCheck.js", "definitions": "node ./resources/buildConfigDefinitions.js && prettier --write 'src/Options/*.js'", "docs": "jsdoc -c ./jsdoc-conf.json", "lint": "flow && eslint --cache ./", @@ -127,7 +128,7 @@ "madge:circular": "node_modules/.bin/madge ./src --circular" }, "engines": { - "node": ">= 8" + "node": ">=12.20.0" }, "bin": { "parse-server": "bin/parse-server"