Skip to content

Commit c982a61

Browse files
committed
Workaround package-lock issues
1 parent 9227948 commit c982a61

File tree

3 files changed

+104
-24
lines changed

3 files changed

+104
-24
lines changed

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"preinstall": "cd packages/blerf && npm install && npm run build",
1010
"build": "blerf build",
1111
"test": "blerf build && blerf test",
12-
"pack": "blerf pack:publish",
13-
"deploy": "blerf pack:deploy"
12+
"pack": "blerf build && blerf pack:publish",
13+
"deploy": "blerf build && blerf pack:deploy"
1414
}
1515
}

packages/blerf/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "blerf",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"description": "Blerf monorepo/solution/project management tool. Handle dependencies and run build tasks when source files change. Pack for publish, pack for deployment.",
55
"author": "andersnm",
66
"license": "MIT",

packages/blerf/src/commands/build.ts

+101-21
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as childProcess from 'child_process';
44
import { PackageEnumerator, PackagesType } from "../packageEnumerator";
55
const glob = require('fast-glob');
66
const semver = require('semver');
7+
const stringifyPackage = require("stringify-package");
78

89
interface IBuildStep {
910
srcPath?: string|string[];
@@ -17,7 +18,7 @@ export class BuildEnumerator extends PackageEnumerator {
1718
}
1819

1920
protected async processPackage(packagePath: string, packageJson: any, packages: PackagesType): Promise<void> {
20-
if (this.needsNpmInstall(packagePath, packageJson)) {
21+
if (this.needsNpmInstall(packagePath, packageJson, packages)) {
2122
console.log("blerf: installing " + packageJson.name);
2223
childProcess.execSync("npm install", {stdio: 'inherit', cwd: packagePath});
2324
} else {
@@ -90,7 +91,71 @@ export class BuildEnumerator extends PackageEnumerator {
9091
}
9192
}
9293

93-
private needsNpmInstall(packagePath: string, packageJson: any): boolean {
94+
private needsNpmInstall(packagePath: string, packageJson: any, packages: PackagesType): boolean {
95+
96+
// Workaround errors during npm install with file: references in package-lock.json, f.ex this error message:
97+
// verbose stack Error: ENOENT: no such file or directory, rename 'd:\Code\festtest\lib-b\node_modules\.staging\lib-a-3f8fcb24\node_modules\@babel\code-frame' -> 'd:\Code\festtest\lib-b\node_modules\.staging\@babel\code-frame-7800f1dd'
98+
// This occurs when installing a project which depends on a file:-based library, and dependencies were removed from the library.
99+
// And this error message:
100+
// npm ERR! Cannot read property 'match' of undefined
101+
// This also occurs when installing a project which depends on a file:-based library, and there were changes in the library dependencies.
102+
// Haven't been able to reproduce this. There were bogus entries for dependencies of a file:-based reference without version:
103+
// "version": "file:../project",
104+
// ...
105+
// "dependencies": {
106+
// ...
107+
// "@babel/core": {
108+
// "bundled": true
109+
// },
110+
111+
// To resolve both of these, check for changes in the referenced package.json,
112+
// remove such file:-based entries in the parent package-lock and run npm install.
113+
114+
let packageLockJson: any;
115+
try {
116+
packageLockJson = this.readPackageJson(path.join(packagePath, "package-lock.json"));
117+
} catch (e) {
118+
packageLockJson = null;
119+
}
120+
121+
let savePackageLockJson = false;
122+
if (packageLockJson && packageLockJson.dependencies) {
123+
for (const dependencyName of Object.keys(packageLockJson.dependencies)) {
124+
125+
const dependencyInfo = packageLockJson.dependencies[dependencyName];
126+
if (typeof dependencyInfo.version !== "string") {
127+
console.log("blerf: recovering from npm error scenario: invalid version. package-lock.json has been modified.");
128+
savePackageLockJson = true;
129+
delete packageLockJson.dependencies[dependencyName];
130+
continue;
131+
}
132+
133+
if (dependencyInfo.version.startsWith("file:")) {
134+
const dependencyPackageInfo = packages[dependencyName];
135+
if (!dependencyPackageInfo) {
136+
continue;
137+
}
138+
139+
const dependencyPackageJson = dependencyPackageInfo.packageJson;
140+
141+
// Get sub-toplevel dependencies of file:-based dependency from package-lock
142+
const dependencyToplevelDependencies = this.getTopLevelDependencies(dependencyInfo.dependencies);
143+
144+
if (!this.hasAllDependencies(dependencyPackageJson, dependencyToplevelDependencies)) {
145+
console.log("blerf: recovering from npm error scenario: file:-based dependency mismatch. package-lock.json has been modified.");
146+
delete packageLockJson.dependencies[dependencyName];
147+
savePackageLockJson = true;
148+
continue;
149+
}
150+
}
151+
}
152+
}
153+
154+
if (savePackageLockJson) {
155+
fs.writeFileSync(path.join(packagePath, "package-lock.json"), stringifyPackage(packageLockJson), 'utf8');
156+
return true;
157+
}
158+
94159
if (packageJson.dependencies) {
95160
if (this.needsNpmInstallDependencies(packageJson.dependencies, packagePath)) {
96161
return true;
@@ -104,33 +169,48 @@ export class BuildEnumerator extends PackageEnumerator {
104169
}
105170

106171
// Compare package.json with package-lock.json if anything was removed
107-
let packageLockJson: any;
108-
try {
109-
packageLockJson = this.readPackageJson(path.join(packagePath, "package-lock.json"));
110-
} catch (e) {
111-
packageLockJson = null;
172+
if (packageLockJson && packageLockJson.dependencies) {
173+
const topLevelDependencies = this.getTopLevelDependencies(packageLockJson.dependencies);
174+
if (!this.hasAllDependencies(packageJson, topLevelDependencies)) {
175+
console.log("blerf: requires install: top level dependency in package-lock.json missing from package.json")
176+
return true;
177+
}
112178
}
113179

114-
if (packageLockJson && packageLockJson.dependencies) {
115-
const nonTopLevelNames: string[] = [];
180+
return false;
181+
}
182+
183+
private hasAllDependencies(packageJson: any, dependencyNames: string[]) {
184+
for (const dependencyName of dependencyNames) {
185+
if (!this.hasDependency(packageJson, dependencyName)) {
186+
console.log("MISSING" + dependencyName)
187+
return false;
188+
}
189+
}
116190

117-
this.scanNonTopLevelDependencies(packageLockJson.dependencies, nonTopLevelNames);
191+
return true;
192+
}
118193

119-
for (const dependencyName of Object.keys(packageLockJson.dependencies)) {
120-
if (nonTopLevelNames.indexOf(dependencyName) !== -1) {
121-
continue;
122-
}
194+
private hasDependency(packageJson: any, dependencyName: string) {
195+
const isInDependencies = packageJson.dependencies && !!packageJson.dependencies[dependencyName];
196+
const isInDevDependencies = packageJson.devDependencies && !!packageJson.devDependencies[dependencyName];
197+
return isInDependencies || isInDevDependencies;
198+
}
123199

124-
const isInDependencies = packageJson.dependencies && !!packageJson.dependencies[dependencyName];
125-
const isInDevDependencies = packageJson.devDependencies && !!packageJson.devDependencies[dependencyName];
126-
if (!isInDependencies && !isInDevDependencies) {
127-
console.log("blerf: top level dependency " + dependencyName + " not in package.json")
128-
return true;
129-
}
200+
private getTopLevelDependencies(dependencies: any): string[] {
201+
const nonTopLevelNames: string[] = [];
202+
this.scanNonTopLevelDependencies(dependencies, nonTopLevelNames);
203+
204+
const result: string[] = [];
205+
for (const dependencyName of Object.keys(dependencies)) {
206+
if (nonTopLevelNames.indexOf(dependencyName) !== -1) {
207+
continue;
130208
}
209+
210+
result.push(dependencyName);
131211
}
132212

133-
return false;
213+
return result;
134214
}
135215

136216
private scanNonTopLevelDependencies(dependencies: any, nonTopLevelNames: string[]) {

0 commit comments

Comments
 (0)