Skip to content

Commit

Permalink
Workaround package-lock issues
Browse files Browse the repository at this point in the history
  • Loading branch information
andersnm committed Apr 20, 2019
1 parent 9227948 commit c982a61
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 24 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"preinstall": "cd packages/blerf && npm install && npm run build",
"build": "blerf build",
"test": "blerf build && blerf test",
"pack": "blerf pack:publish",
"deploy": "blerf pack:deploy"
"pack": "blerf build && blerf pack:publish",
"deploy": "blerf build && blerf pack:deploy"
}
}
2 changes: 1 addition & 1 deletion packages/blerf/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "blerf",
"version": "0.0.1",
"version": "0.0.2",
"description": "Blerf monorepo/solution/project management tool. Handle dependencies and run build tasks when source files change. Pack for publish, pack for deployment.",
"author": "andersnm",
"license": "MIT",
Expand Down
122 changes: 101 additions & 21 deletions packages/blerf/src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as childProcess from 'child_process';
import { PackageEnumerator, PackagesType } from "../packageEnumerator";
const glob = require('fast-glob');
const semver = require('semver');
const stringifyPackage = require("stringify-package");

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

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

private needsNpmInstall(packagePath: string, packageJson: any): boolean {
private needsNpmInstall(packagePath: string, packageJson: any, packages: PackagesType): boolean {

// Workaround errors during npm install with file: references in package-lock.json, f.ex this error message:
// 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'
// This occurs when installing a project which depends on a file:-based library, and dependencies were removed from the library.
// And this error message:
// npm ERR! Cannot read property 'match' of undefined
// This also occurs when installing a project which depends on a file:-based library, and there were changes in the library dependencies.
// Haven't been able to reproduce this. There were bogus entries for dependencies of a file:-based reference without version:
// "version": "file:../project",
// ...
// "dependencies": {
// ...
// "@babel/core": {
// "bundled": true
// },

// To resolve both of these, check for changes in the referenced package.json,
// remove such file:-based entries in the parent package-lock and run npm install.

let packageLockJson: any;
try {
packageLockJson = this.readPackageJson(path.join(packagePath, "package-lock.json"));
} catch (e) {
packageLockJson = null;
}

let savePackageLockJson = false;
if (packageLockJson && packageLockJson.dependencies) {
for (const dependencyName of Object.keys(packageLockJson.dependencies)) {

const dependencyInfo = packageLockJson.dependencies[dependencyName];
if (typeof dependencyInfo.version !== "string") {
console.log("blerf: recovering from npm error scenario: invalid version. package-lock.json has been modified.");
savePackageLockJson = true;
delete packageLockJson.dependencies[dependencyName];
continue;
}

if (dependencyInfo.version.startsWith("file:")) {
const dependencyPackageInfo = packages[dependencyName];
if (!dependencyPackageInfo) {
continue;
}

const dependencyPackageJson = dependencyPackageInfo.packageJson;

// Get sub-toplevel dependencies of file:-based dependency from package-lock
const dependencyToplevelDependencies = this.getTopLevelDependencies(dependencyInfo.dependencies);

if (!this.hasAllDependencies(dependencyPackageJson, dependencyToplevelDependencies)) {
console.log("blerf: recovering from npm error scenario: file:-based dependency mismatch. package-lock.json has been modified.");
delete packageLockJson.dependencies[dependencyName];
savePackageLockJson = true;
continue;
}
}
}
}

if (savePackageLockJson) {
fs.writeFileSync(path.join(packagePath, "package-lock.json"), stringifyPackage(packageLockJson), 'utf8');
return true;
}

if (packageJson.dependencies) {
if (this.needsNpmInstallDependencies(packageJson.dependencies, packagePath)) {
return true;
Expand All @@ -104,33 +169,48 @@ export class BuildEnumerator extends PackageEnumerator {
}

// Compare package.json with package-lock.json if anything was removed
let packageLockJson: any;
try {
packageLockJson = this.readPackageJson(path.join(packagePath, "package-lock.json"));
} catch (e) {
packageLockJson = null;
if (packageLockJson && packageLockJson.dependencies) {
const topLevelDependencies = this.getTopLevelDependencies(packageLockJson.dependencies);
if (!this.hasAllDependencies(packageJson, topLevelDependencies)) {
console.log("blerf: requires install: top level dependency in package-lock.json missing from package.json")
return true;
}
}

if (packageLockJson && packageLockJson.dependencies) {
const nonTopLevelNames: string[] = [];
return false;
}

private hasAllDependencies(packageJson: any, dependencyNames: string[]) {
for (const dependencyName of dependencyNames) {
if (!this.hasDependency(packageJson, dependencyName)) {
console.log("MISSING" + dependencyName)
return false;
}
}

this.scanNonTopLevelDependencies(packageLockJson.dependencies, nonTopLevelNames);
return true;
}

for (const dependencyName of Object.keys(packageLockJson.dependencies)) {
if (nonTopLevelNames.indexOf(dependencyName) !== -1) {
continue;
}
private hasDependency(packageJson: any, dependencyName: string) {
const isInDependencies = packageJson.dependencies && !!packageJson.dependencies[dependencyName];
const isInDevDependencies = packageJson.devDependencies && !!packageJson.devDependencies[dependencyName];
return isInDependencies || isInDevDependencies;
}

const isInDependencies = packageJson.dependencies && !!packageJson.dependencies[dependencyName];
const isInDevDependencies = packageJson.devDependencies && !!packageJson.devDependencies[dependencyName];
if (!isInDependencies && !isInDevDependencies) {
console.log("blerf: top level dependency " + dependencyName + " not in package.json")
return true;
}
private getTopLevelDependencies(dependencies: any): string[] {
const nonTopLevelNames: string[] = [];
this.scanNonTopLevelDependencies(dependencies, nonTopLevelNames);

const result: string[] = [];
for (const dependencyName of Object.keys(dependencies)) {
if (nonTopLevelNames.indexOf(dependencyName) !== -1) {
continue;
}

result.push(dependencyName);
}

return false;
return result;
}

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

0 comments on commit c982a61

Please sign in to comment.