Skip to content

Commit d3a9053

Browse files
committed
Add JavaScript version
Based in .NET version
1 parent d6e666c commit d3a9053

File tree

6 files changed

+209
-0
lines changed

6 files changed

+209
-0
lines changed

JS/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

JS/package-lock.json

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

JS/package.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "verify-node-modules",
3+
"version": "1.0.0",
4+
"description": "Check whether node_modules/ contains what it needs",
5+
"main": "verify-node-modules.js",
6+
"scripts": {
7+
"tsc": "tsc",
8+
"test": "node verify-node-modules.js"
9+
},
10+
"author": "",
11+
"license": "MIT",
12+
"devDependencies": {
13+
"@types/node": "^14.0.27",
14+
"typescript": "^3.9.7"
15+
}
16+
}

JS/tsconfig.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2017",
4+
"module": "commonjs",
5+
"allowJs": true,
6+
"checkJs": true,
7+
"noEmit": true,
8+
"strict": true,
9+
"moduleResolution": "node",
10+
"types": ["node"],
11+
"esModuleInterop": true,
12+
"skipLibCheck": true,
13+
"forceConsistentCasingInFileNames": true
14+
}
15+
}

JS/types.d.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
interface DependencyDictionary {
2+
[name: string]: PackageLockDependency;
3+
}
4+
5+
export interface PackageJson {
6+
readonly version: string;
7+
}
8+
9+
export interface PackageLockDependency {
10+
readonly version: string;
11+
readonly dependencies?: DependencyDictionary;
12+
}
13+
14+
export interface PackageLockRoot {
15+
readonly dependencies: DependencyDictionary;
16+
}
17+
18+
export interface DependencyError {
19+
readonly packageName: string;
20+
readonly expectedVersion: string;
21+
readonly actualVersion: string;
22+
}

JS/verify-node-modules.js

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
let checkedDependencies = 0;
5+
6+
async function main() {
7+
const projectPath = process.cwd();
8+
const packageLockPath = path.join(projectPath, "package-lock.json");
9+
10+
if (!fs.existsSync(packageLockPath)) {
11+
console.error("'package-lock.json' not found in current directory.");
12+
return -1;
13+
}
14+
15+
/** @type import('./types').PackageLockRoot */
16+
const packageLock = await loadJsonFile(packageLockPath);
17+
18+
const startTime = process.hrtime();
19+
20+
const nodeModulesPath = path.join(projectPath, "node_modules");
21+
const dependencyNames = Object.keys(packageLock.dependencies);
22+
const tasks = dependencyNames.map((n) =>
23+
getDependencyErrors(n, packageLock.dependencies[n], nodeModulesPath)
24+
);
25+
26+
let exitCode = 0;
27+
try {
28+
let nestedDependencyErrors = await Promise.all(tasks);
29+
30+
/** @type import('./types').DependencyError[] */
31+
let flattenedErrors = [];
32+
33+
for (const nestedErrors of nestedDependencyErrors) {
34+
flattenedErrors.push(...nestedErrors);
35+
}
36+
37+
for (let dependencyError of flattenedErrors) {
38+
exitCode++;
39+
40+
console.error(
41+
"Version mismatch for package '%s'! Wanted '%s' but got '%s'!",
42+
dependencyError.packageName,
43+
dependencyError.expectedVersion,
44+
dependencyError.actualVersion
45+
);
46+
}
47+
} catch (e) {
48+
console.error(e);
49+
exitCode = -2;
50+
}
51+
52+
const timeToFinish = process.hrtime(startTime);
53+
54+
console.log(
55+
"Checked %d dependencies in %s",
56+
checkedDependencies,
57+
`${timeToFinish[0]}.${timeToFinish[1].toFixed(0).padStart(9, "0")}s`
58+
);
59+
60+
return exitCode;
61+
}
62+
63+
/**
64+
* @param packageName {string}
65+
* @param dependency {import('./types').PackageLockDependency}
66+
* @param nodeModulesPath {string}
67+
*/
68+
async function getDependencyErrors(packageName, dependency, nodeModulesPath) {
69+
let packagePath = path.resolve(path.join(nodeModulesPath, packageName));
70+
let packageJsonPath = path.join(packagePath, "package.json");
71+
72+
checkedDependencies++;
73+
74+
if (!fs.existsSync(packageJsonPath)) {
75+
/** @type import('./types').DependencyError */
76+
const error = {
77+
packageName,
78+
expectedVersion: dependency.version,
79+
actualVersion: "None",
80+
};
81+
82+
return [error];
83+
}
84+
85+
/** @type import('./types').PackageJson */
86+
let packageJson = await loadJsonFile(packageJsonPath);
87+
88+
/** @type import('./types').DependencyError[] */
89+
let errors = [];
90+
91+
if (packageJson.version != dependency.version) {
92+
errors.push({
93+
packageName,
94+
expectedVersion: dependency.version,
95+
actualVersion: packageJson.version,
96+
});
97+
}
98+
99+
let nestedNodeModulesPath = path.join(packagePath, "node_modules");
100+
101+
if (dependency.dependencies) {
102+
const nestedDependencies = dependency.dependencies;
103+
let nestedDependencyNames = Object.keys(nestedDependencies);
104+
let tasks = nestedDependencyNames.map((n) =>
105+
getDependencyErrors(n, nestedDependencies[n], nestedNodeModulesPath)
106+
);
107+
108+
let nestedDependencyErrors = await Promise.all(tasks);
109+
110+
for (const nestedErrors of nestedDependencyErrors) {
111+
errors.push(...nestedErrors);
112+
}
113+
}
114+
115+
return errors;
116+
}
117+
118+
/**
119+
*
120+
* @param {string} path
121+
*/
122+
function loadJsonFile(path) {
123+
return new Promise((resolve, reject) => {
124+
fs.readFile(path, { encoding: "utf-8" }, (err, data) => {
125+
if (err) {
126+
reject(err);
127+
} else {
128+
const obj = JSON.parse(data);
129+
resolve(obj);
130+
}
131+
});
132+
});
133+
}
134+
135+
main().then((exitCode) => process.exit(exitCode));

0 commit comments

Comments
 (0)