From c982a61cbed296505a1d6553f56e965837a0f5e6 Mon Sep 17 00:00:00 2001 From: andersnm Date: Sat, 20 Apr 2019 16:34:55 +0200 Subject: [PATCH] Workaround package-lock issues --- package.json | 4 +- packages/blerf/package.json | 2 +- packages/blerf/src/commands/build.ts | 122 ++++++++++++++++++++++----- 3 files changed, 104 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 06ff8d9..a89245d 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/packages/blerf/package.json b/packages/blerf/package.json index fd6321f..b53f62a 100644 --- a/packages/blerf/package.json +++ b/packages/blerf/package.json @@ -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", diff --git a/packages/blerf/src/commands/build.ts b/packages/blerf/src/commands/build.ts index c664485..45a0372 100644 --- a/packages/blerf/src/commands/build.ts +++ b/packages/blerf/src/commands/build.ts @@ -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[]; @@ -17,7 +18,7 @@ export class BuildEnumerator extends PackageEnumerator { } protected async processPackage(packagePath: string, packageJson: any, packages: PackagesType): Promise { - 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 { @@ -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; @@ -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[]) {