Skip to content

Commit f53a52f

Browse files
authored
Merge 58aab21 into 3e4d1ec
2 parents 3e4d1ec + 58aab21 commit f53a52f

File tree

7 files changed

+281
-79
lines changed

7 files changed

+281
-79
lines changed

.github/workflows/ci.yml

+8-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
steps:
1818
- uses: actions/checkout@v2
1919
- name: Use Node.js ${{ matrix.NODE_VERSION }}
20-
uses: actions/setup-node@v1
20+
uses: actions/setup-node@v2
2121
with:
2222
node-version: ${{ matrix.node-version }}
2323
- name: Cache Node.js modules
@@ -29,8 +29,10 @@ jobs:
2929
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-
3030
- name: Install dependencies
3131
run: npm ci
32-
- name: CI Self-Check
32+
- name: CI Environments Check
3333
run: npm run ci:check
34+
- name: CI Node Engine Check
35+
run: npm run ci:checkNodeEngine
3436
check-changelog:
3537
name: Changelog
3638
timeout-minutes: 5
@@ -45,7 +47,7 @@ jobs:
4547
steps:
4648
- uses: actions/checkout@v2
4749
- name: Use Node.js ${{ matrix.NODE_VERSION }}
48-
uses: actions/setup-node@v1
50+
uses: actions/setup-node@v2
4951
with:
5052
node-version: ${{ matrix.node-version }}
5153
- name: Cache Node.js modules
@@ -65,7 +67,7 @@ jobs:
6567
steps:
6668
- uses: actions/checkout@v2
6769
- name: Use Node.js ${{ matrix.NODE_VERSION }}
68-
uses: actions/setup-node@v1
70+
uses: actions/setup-node@v2
6971
with:
7072
node-version: ${{ matrix.node-version }}
7173
- name: Cache Node.js modules
@@ -159,7 +161,7 @@ jobs:
159161
steps:
160162
- uses: actions/checkout@v2
161163
- name: Use Node.js ${{ matrix.NODE_VERSION }}
162-
uses: actions/setup-node@v1
164+
uses: actions/setup-node@v2
163165
with:
164166
node-version: ${{ matrix.NODE_VERSION }}
165167
- name: Cache Node.js modules
@@ -219,7 +221,7 @@ jobs:
219221
steps:
220222
- uses: actions/checkout@v2
221223
- name: Use Node.js ${{ matrix.NODE_VERSION }}
222-
uses: actions/setup-node@v1
224+
uses: actions/setup-node@v2
223225
with:
224226
node-version: ${{ matrix.NODE_VERSION }}
225227
- name: Cache Node.js modules

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ ___
104104
- 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)
105105
- 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)
106106
- 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)
107+
- ci: add node engine version check (Manuel Trezza) [#7574](https://github.com/parse-community/parse-server/pull/7574)
107108

108109
### Notable Changes
109110
- Added Parse Server Security Check to report weak security settings (Manuel Trezza, dblythy) [#7247](https://github.com/parse-community/parse-server/issues/7247)
File renamed without changes.
File renamed without changes.

ci/nodeEngineCheck.js

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
const core = require('@actions/core');
2+
const semver = require('semver');
3+
const fs = require('fs').promises;
4+
const path = require('path');
5+
6+
/**
7+
* This checks whether any package dependency requires a minimum node engine
8+
* version higher than the host package.
9+
*/
10+
class NodeEngineCheck {
11+
12+
/**
13+
* The constructor.
14+
* @param {Object} config The config.
15+
* @param {String} config.nodeModulesPath The path to the node_modules directory.
16+
* @param {String} config.packageJsonPath The path to the parent package.json file.
17+
*/
18+
constructor(config) {
19+
const {
20+
nodeModulesPath,
21+
packageJsonPath,
22+
} = config;
23+
24+
// Ensure required params are set
25+
if ([
26+
nodeModulesPath,
27+
packageJsonPath,
28+
].includes(undefined)) {
29+
throw 'invalid configuration';
30+
}
31+
32+
this.nodeModulesPath = nodeModulesPath;
33+
this.packageJsonPath = packageJsonPath;
34+
}
35+
36+
/**
37+
* Returns an array of `package.json` files under the given path and subdirectories.
38+
* @param {String} [basePath] The base path for recursive directory search.
39+
*/
40+
async getPackageFiles(basePath = this.nodeModulesPath) {
41+
try {
42+
// Declare file list
43+
const files = []
44+
45+
// Get files
46+
const dirents = await fs.readdir(basePath, { withFileTypes: true });
47+
const validFiles = dirents.filter(d => d.name.toLowerCase() == 'package.json').map(d => path.join(basePath, d.name));
48+
files.push(...validFiles);
49+
50+
// For each directory entry
51+
for (const dirent of dirents) {
52+
if (dirent.isDirectory()) {
53+
const subFiles = await this.getPackageFiles(path.join(basePath, dirent.name));
54+
files.push(...subFiles);
55+
}
56+
}
57+
return files;
58+
} catch (e) {
59+
throw `Failed to get package.json files in ${this.nodeModulesPath} with error: ${e}`;
60+
}
61+
}
62+
63+
/**
64+
* Extracts and returns the node engine versions of the given package.json
65+
* files.
66+
* @param {String[]} files The package.json files.
67+
* @param {Boolean} clean Is true if packages with undefined node versions
68+
* should be removed from the results.
69+
* @returns {Object[]} A list of results.
70+
*/
71+
async getNodeVersion({ files, clean = false }) {
72+
73+
// Declare response
74+
let response = [];
75+
76+
// For each file
77+
for (const file of files) {
78+
79+
// Get node version
80+
const contentString = await fs.readFile(file, 'utf-8');
81+
const contentJson = JSON.parse(contentString);
82+
const version = ((contentJson || {}).engines || {}).node;
83+
84+
// Add response
85+
response.push({
86+
file: file,
87+
nodeVersion: version
88+
});
89+
}
90+
91+
// If results should be cleaned by removing undefined node versions
92+
if (clean) {
93+
response = response.filter(r => r.nodeVersion !== undefined);
94+
}
95+
return response;
96+
}
97+
98+
/**
99+
* Returns the highest semver definition that satisfies all versions
100+
* in the given list.
101+
* @param {String[]} versions The list of semver version ranges.
102+
* @param {String} baseVersion The base version of which higher versions should be
103+
* determined; as a version (1.2.3), not a range (>=1.2.3).
104+
* @returns {String} The highest semver version.
105+
*/
106+
getHigherVersions({ versions, baseVersion }) {
107+
// Add min satisfying node versions
108+
const minVersions = versions.map(v => {
109+
v.nodeMinVersion = semver.minVersion(v.nodeVersion)
110+
return v;
111+
});
112+
113+
// Sort by min version
114+
const sortedMinVersions = minVersions.sort((v1, v2) => semver.compare(v1.nodeMinVersion, v2.nodeMinVersion));
115+
116+
// Filter by higher versions
117+
const higherVersions = sortedMinVersions.filter(v => semver.gt(v.nodeMinVersion, baseVersion));
118+
// console.log(`getHigherVersions: ${JSON.stringify(higherVersions)}`);
119+
return higherVersions;
120+
}
121+
122+
/**
123+
* Returns the node version of the parent package.
124+
* @return {Object} The parent package info.
125+
*/
126+
async getParentVersion() {
127+
// Get parent package.json version
128+
const version = await this.getNodeVersion({ files: [ this.packageJsonPath ], clean: true });
129+
// console.log(`getParentVersion: ${JSON.stringify(version)}`);
130+
return version[0];
131+
}
132+
}
133+
134+
async function check() {
135+
// Define paths
136+
const nodeModulesPath = path.join(__dirname, '../node_modules');
137+
const packageJsonPath = path.join(__dirname, '../package.json');
138+
139+
// Create check
140+
const check = new NodeEngineCheck({
141+
nodeModulesPath,
142+
packageJsonPath,
143+
});
144+
145+
// Get package node version of parent package
146+
const parentVersion = await check.getParentVersion();
147+
148+
// If parent node version could not be determined
149+
if (parentVersion === undefined) {
150+
core.setFailed(`Failed to determine node engine version of parent package at ${this.packageJsonPath}`);
151+
return;
152+
}
153+
154+
// Determine parent min version
155+
const parentMinVersion = semver.minVersion(parentVersion.nodeVersion);
156+
157+
// Get package.json files
158+
const files = await check.getPackageFiles();
159+
core.info(`Checking the minimum node version requirement of ${files.length} dependencies`);
160+
161+
// Get node versions
162+
const versions = await check.getNodeVersion({ files, clean: true });
163+
164+
// Get are dependencies that require a higher node version than the parent package
165+
const higherVersions = check.getHigherVersions({ versions, baseVersion: parentMinVersion });
166+
167+
// Get highest version
168+
const highestVersion = higherVersions.map(v => v.nodeMinVersion).pop();
169+
170+
// If there are higher versions
171+
if (higherVersions.length > 0) {
172+
console.log(`\nThere are ${higherVersions.length} dependencies that require a higher node engine version than the parent package (${parentVersion.nodeVersion}):`);
173+
174+
// For each dependency
175+
for (const higherVersion of higherVersions) {
176+
177+
// Get package name
178+
const _package = higherVersion.file.split('node_modules/').pop().replace('/package.json', '');
179+
console.log(`- ${_package} requires at least node ${higherVersion.nodeMinVersion} (${higherVersion.nodeVersion})`);
180+
}
181+
console.log('');
182+
core.setFailed(`❌ Upgrade the node engine version in package.json to at least '${highestVersion}' to satisfy the dependencies.`);
183+
console.log('');
184+
return;
185+
}
186+
187+
console.log(`✅ All dependencies satisfy the node version requirement of the parent package (${parentVersion.nodeVersion}).`);
188+
}
189+
190+
check();

0 commit comments

Comments
 (0)