From 6fa4c0c8d5afecfef8a8c1de18b1f6620edc006c Mon Sep 17 00:00:00 2001 From: ViktorSlavov Date: Mon, 1 Jun 2020 13:36:59 +0300 Subject: [PATCH] fix(package-manager): manually update pckgJSON instead of letting npm do it, #751 Co-authored-by: Damyan Petev --- packages/core/packages/PackageManager.ts | 27 +++++++++++++++++------ packages/core/util/Util.ts | 10 +++++++++ spec/unit/packageManager-spec.ts | 28 ++++++++++++++++-------- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/packages/core/packages/PackageManager.ts b/packages/core/packages/PackageManager.ts index 40e738639..bcd759f25 100644 --- a/packages/core/packages/PackageManager.ts +++ b/packages/core/packages/PackageManager.ts @@ -1,14 +1,21 @@ import { exec, spawnSync } from "child_process"; import * as path from "path"; import { TemplateManager } from "../../cli/lib/TemplateManager"; -import { Config, ProjectTemplate } from "../types"; -import { ProjectConfig, Util } from "../util"; +import { Config, FS_TOKEN, IFileSystem, ProjectTemplate } from "../types"; +import { App, ProjectConfig, Util } from "../util"; import componentsConfig = require("./components"); export class PackageManager { private static ossPackage: string = "ignite-ui"; private static fullPackage: string = "@infragistics/ignite-ui-full"; + private static get fs(): IFileSystem { + return App.container.get(FS_TOKEN); + } + + private static get jsonPath(): string { + return path.join(process.cwd(), "package.json"); + } private static installQueue: Array> = []; @@ -153,11 +160,17 @@ export class PackageManager { } public static async queuePackage(packageName: string, verbose = false) { - const command = this.getInstallCommand(this.getManager(), packageName); - const packName = packageName.split(/@[^\/]+$/)[0]; - if (this.getPackageJSON().dependencies[packName] || this.installQueue.find(x => x["packageName"] === packName)) { + const command = this.getInstallCommand(this.getManager(), packageName).replace("--save", "--no-save"); + const [ packName, version ] = packageName.split(/@(?=[^\/]+$)/); + const packageJSON = this.getPackageJSON(); + if (!packageJSON.dependencies) { + packageJSON.dependencies = {}; + } + if (packageJSON.dependencies[packName] || this.installQueue.find(x => x["packageName"] === packName)) { return; } + packageJSON.dependencies[packName] = version; + this.fs.writeFile(this.jsonPath, Util.formatPackageJson(packageJSON)); // D.P. Concurrent install runs should be supported // https://github.com/npm/npm/issues/5948 // https://github.com/npm/npm/issues/2500 @@ -236,8 +249,8 @@ export class PackageManager { } protected static getPackageJSON(): { "dependencies": { [x: string]: string } } { - const filePath = path.join(process.cwd(), "package.json"); - return require(filePath); + const content = this.fs.readFile(this.jsonPath); + return JSON.parse(content); } private static getInstallCommand(managerCommand: string, packageName: string): string { diff --git a/packages/core/util/Util.ts b/packages/core/util/Util.ts index b5c401036..bd01bd13f 100644 --- a/packages/core/util/Util.ts +++ b/packages/core/util/Util.ts @@ -176,6 +176,16 @@ export class Util { } } + public static formatPackageJson(json: { dependencies: { [key: string]: string } }, sort = true): string { + if (sort) { + json.dependencies = + Object.keys(json.dependencies) + .sort() + .reduce((result, key) => (result[key] = json.dependencies[key]) && result, {}); + } + return JSON.stringify(json, null, 2) + "\n"; + } + public static version(filePath?: string): string { const configuration = require(filePath || "../package.json"); return configuration.version; diff --git a/spec/unit/packageManager-spec.ts b/spec/unit/packageManager-spec.ts index 3f99fff6d..94c6b57a8 100644 --- a/spec/unit/packageManager-spec.ts +++ b/spec/unit/packageManager-spec.ts @@ -1,4 +1,4 @@ -import { Config, PackageManager, ProjectConfig, Util } from "@igniteui/cli-core"; +import { App, Config, IFileSystem, PackageManager, ProjectConfig, Util } from "@igniteui/cli-core"; import * as cp from "child_process"; import * as path from "path"; import { resetSpy } from "../helpers/utils"; @@ -31,11 +31,12 @@ describe("Unit - Package Manager", () => { spyOn(ProjectConfig, "setConfig"); spyOn(PackageManager, "addPackage").and.returnValue(true); spyOn(path, "join").and.returnValue("fakemodule.json"); - spyOn(require("module"), "_load").and.callFake((modulePath: string) => { - if (modulePath === "fakemodule.json") { - return mockRequire; - } - }); + const mockFs: Partial = { + readFile: jasmine.createSpy().and.returnValue(JSON.stringify(mockRequire)), + writeFile: jasmine.createSpy() + }; + // should ignore already installed + spyOn(App.container, "get").and.returnValue(mockFs); spyOn(Util, "execSync").and.callFake((cmd: string, opts) => { if (cmd.includes("whoami")) { throw new Error(""); @@ -365,7 +366,7 @@ describe("Unit - Package Manager", () => { expect(Util.log).toHaveBeenCalledTimes(0); expect(cp.exec).toHaveBeenCalledTimes(1); expect(cp.exec).toHaveBeenCalledWith( - `npm install test-pack --quiet --save`, {}, jasmine.any(Function)); + `npm install test-pack --quiet --no-save`, {}, jasmine.any(Function)); done(); }); @@ -375,8 +376,12 @@ describe("Unit - Package Manager", () => { "test-pack": "19.2" } }; + const mockFs: Partial = { + readFile: jasmine.createSpy().and.returnValue(JSON.stringify(mockRequire)), + writeFile: jasmine.createSpy() + }; // should ignore already installed - spyOn(require("module"), "_load").and.returnValue(mockRequire); + spyOn(App.container, "get").and.returnValue(mockFs); spyOn(Util, "log"); const execSpy = spyOn(cp, "exec"); PackageManager.queuePackage("test-pack"); @@ -396,8 +401,13 @@ describe("Unit - Package Manager", () => { dependencies: {} }; // should ignore already installed - spyOn(require("module"), "_load").and.returnValue(mockRequire); + const mockFs: Partial = { + readFile: jasmine.createSpy().and.returnValue(JSON.stringify(mockRequire)), + writeFile: jasmine.createSpy() + }; + // spyOn(require("module"), "_load").and.returnValue(mockRequire); spyOn(Util, "log"); + spyOn(App.container, "get").and.returnValue(mockFs); const execSpy = spyOn(cp, "exec").and.callFake((cmd, opts, callback) => { setTimeout(() => callback(null, [1], [2]), 20); });