Skip to content

ci: add node engine check #7574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ba3cf52
add issue bot for prs
mtrezza Aug 27, 2021
2af6350
Update CHANGELOG.md
mtrezza Aug 27, 2021
d7a34ce
Update issue-bot.yml
mtrezza Aug 27, 2021
c2f2481
Merge remote-tracking branch 'upstream/master'
mtrezza Aug 27, 2021
60537fe
Merge remote-tracking branch 'upstream/master'
mtrezza Aug 31, 2021
2e69bc7
Merge remote-tracking branch 'upstream/master'
mtrezza Sep 2, 2021
ac221a1
Merge remote-tracking branch 'upstream/master'
mtrezza Sep 2, 2021
23e78ae
Merge remote-tracking branch 'upstream/master'
mtrezza Sep 2, 2021
ab2d9ed
Merge remote-tracking branch 'upstream/master'
mtrezza Sep 3, 2021
59c4214
Merge remote-tracking branch 'upstream/master'
mtrezza Sep 4, 2021
ebefede
Merge remote-tracking branch 'upstream/master'
mtrezza Sep 7, 2021
81165cc
Merge remote-tracking branch 'upstream/master'
mtrezza Sep 7, 2021
a26006e
Merge remote-tracking branch 'upstream/master'
mtrezza Sep 7, 2021
3a3d4fa
Merge remote-tracking branch 'upstream/master'
mtrezza Sep 7, 2021
7a46fa4
replace node 15 with node 16
mtrezza Sep 8, 2021
7193da4
Update CHANGELOG.md
mtrezza Sep 8, 2021
d5afd4d
use node 16 as default node version
mtrezza Sep 8, 2021
bceaf0a
ignore node 15 in ci self-check
mtrezza Sep 8, 2021
db934e1
bumped madge for node deprecation DEP0148
mtrezza Sep 8, 2021
5145939
ci: add node engine check
mtrezza Sep 9, 2021
b1a6726
lint
mtrezza Sep 9, 2021
5d7378d
bump node engine
mtrezza Sep 10, 2021
cb5c23c
Update ci.yml
mtrezza Sep 10, 2021
01ce427
revert unnecessary changes
mtrezza Sep 14, 2021
66d924b
Update CHANGELOG.md
mtrezza Sep 14, 2021
d3f8d41
Merge branch 'master' into ci-add-node-engine-check
mtrezza Sep 14, 2021
58aab21
Update ci.yml
mtrezza Sep 14, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
File renamed without changes.
File renamed without changes.
190 changes: 190 additions & 0 deletions ci/nodeEngineCheck.js
Original file line number Diff line number Diff line change
@@ -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();
Loading