diff --git a/src/install/index.ts b/src/install/index.ts index c9d2f12..52fad85 100644 --- a/src/install/index.ts +++ b/src/install/index.ts @@ -10,8 +10,6 @@ import * as os from "os"; import { logger } from "../log"; import { requireNpmV7 } from "../npm"; -// TODO: remove nodecg-io directory if initial installation was aborted? - export const installModule: CommandModule = { command: "install", describe: "installs nodecg-io to your local nodecg installation", diff --git a/src/install/production.ts b/src/install/production.ts index ccdb11b..f1c7b04 100644 --- a/src/install/production.ts +++ b/src/install/production.ts @@ -7,6 +7,7 @@ import { buildNpmPackagePath, downloadNpmPackage, createNpmSymlinks, + getSubPackages, } from "../npm"; import { directoryExists, ensureDirectory } from "../fsUtils"; import { logger } from "../log"; @@ -53,14 +54,25 @@ export function diffPackages( requested: NpmPackage[], current: NpmPackage[], ): { pkgInstall: NpmPackage[]; pkgRemove: NpmPackage[] } { + // currently installed but not requested exactly anymore + const pkgRemove = current.filter((a) => !requested.find((b) => isPackageEquals(a, b))); + + // requested and not already exactly installed (e.g. version change) + const pkgInstall = requested.filter((a) => !current.find((b) => isPackageEquals(a, b))); + + // Gets sub-packages of packages in pkgInstall that might not be in there. + // E.g. core got upgraded => nodecg-io-core will be removed and reinstalled + // nodecg-io-dashboard will also be removed because it is in nodecg-io-core and + // contained in the directory of the core package. This ensures that the dashboard will + // also be reinstalled, even though it got no upgrade. + const installAdditional = pkgInstall.map((pkg) => getSubPackages(requested, pkg)).flat(); + return { - pkgInstall: requested.filter((a) => !current.find((b) => isPackageEquals(a, b))), // requested and not already exactly installed (e.g. version change) - pkgRemove: current.filter((a) => !requested.find((b) => isPackageEquals(a, b))), // currently installed but not requested exactly anymore + pkgRemove, + pkgInstall: [...new Set(pkgInstall.concat(installAdditional))], }; } -// TODO: handle when e.g. core upgrades and removes nodecg-io-core directory. Need to re-download dashboard because it got deleted (or don't delete it). - /** * Removes a list of packages from a production nodecg-io install. * @param pkgs the packages that should be removed diff --git a/src/install/prompt.ts b/src/install/prompt.ts index 04c1468..0c1d563 100644 --- a/src/install/prompt.ts +++ b/src/install/prompt.ts @@ -151,7 +151,7 @@ function getPackagePath(pkgName: string) { async function getPackageVersion(pkgName: string, minorVersion: string) { const version = await getHighestPatchVersion(pkgName, minorVersion); // if patch part could be found out we will use .0 as it should always exist if the minor version also does. - return version ?? `${version}.0`; + return version ?? `${minorVersion}.0`; } function getPackageSymlinks(pkgName: string) { diff --git a/src/npm.ts b/src/npm.ts index ee6ce96..c24a847 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -191,6 +191,16 @@ export async function removeNpmPackage(pkg: NpmPackage, nodecgIODir: string): Pr await removeDirectory(buildNpmPackagePath(pkg, nodecgIODir)); } +/** + * Returns you all packages that are in a sub-path of rootPkg. + * This is helpful if you have reinstalled rootPkg and now also need to reinstall all packages + * that were in the directory of rootPkg because they also got removed in {@link removeNpmPackage}. + * @returns the packages that are contained in rootPkg + */ +export function getSubPackages(allPackages: NpmPackage[], rootPkg: NpmPackage): NpmPackage[] { + return allPackages.filter((pkg) => pkg !== rootPkg && pkg.path.startsWith(rootPkg.path)); +} + /** * Gets version of the installed npm by running "npm --version". * @returns the npm version or undefined if npm is not installed/not in $PATH. diff --git a/test/install/production.ts b/test/install/production.ts index 3429ef6..2b8bc31 100644 --- a/test/install/production.ts +++ b/test/install/production.ts @@ -1,6 +1,6 @@ import { vol } from "memfs"; import * as path from "path"; -import { corePkg, nodecgIODir, twitchChatPkg, validProdInstall } from "../testUtils"; +import { corePkg, dashboardPkg, nodecgIODir, twitchChatPkg, validProdInstall } from "../testUtils"; import { diffPackages, installPackages, removePackages, validateInstall } from "../../src/install/production"; import * as installation from "../../src/installation"; import * as fsUtils from "../../src/fsUtils"; @@ -45,6 +45,13 @@ describe("diffPackages", () => { expect(pkgRemove).toStrictEqual([]); expect(pkgInstall).toStrictEqual([]); }); + + test("should install dashboard (sub pkg) if upgrading core", async () => { + // dashboard not modified, but should still be installed because it is in core and core gets upgraded + const { pkgInstall, pkgRemove } = diffPackages([corePkg2, dashboardPkg], [corePkg, dashboardPkg]); + expect(pkgRemove).toStrictEqual([corePkg]); + expect(pkgInstall).toStrictEqual([corePkg2, dashboardPkg]); + }); }); describe("removePackages", () => { diff --git a/test/install/prompt.ts b/test/install/prompt.ts index e7a3c70..e687557 100644 --- a/test/install/prompt.ts +++ b/test/install/prompt.ts @@ -25,7 +25,7 @@ describe("getCompatibleVersions", () => { }); describe("buildPackageList", () => { - const ver = "0.1.0"; + const ver = "0.1"; const testSvcName = "testSvc"; const testSvcPkgName = `nodecg-io-${testSvcName}`; const mock = jest.spyOn(npm, "getHighestPatchVersion").mockResolvedValue("0.1.1"); diff --git a/test/npm/pkgManagement.ts b/test/npm/pkgManagement.ts index 794026a..0785eb8 100644 --- a/test/npm/pkgManagement.ts +++ b/test/npm/pkgManagement.ts @@ -1,6 +1,6 @@ import { createFsFromVolume, vol } from "memfs"; -import { createNpmSymlinks, removeNpmPackage, runNpmInstall } from "../../src/npm"; -import { tempDir, corePkg, fsRoot } from "../testUtils"; +import { createNpmSymlinks, getSubPackages, removeNpmPackage, runNpmInstall } from "../../src/npm"; +import { tempDir, corePkg, fsRoot, twitchChatPkg, dashboardPkg } from "../testUtils"; import * as fsUtils from "../../src/fsUtils"; import * as path from "path"; @@ -45,3 +45,13 @@ describe("removeNpmPackage", () => { expect(spy).toHaveBeenCalled(); }); }); + +describe("getSubPackages", () => { + test("should return empty list if no packages are inside the passed package", async () => { + expect(getSubPackages([corePkg, twitchChatPkg], corePkg)).toStrictEqual([]); + }); + + test("should return dashboard to be inside core", async () => { + expect(getSubPackages([corePkg, dashboardPkg], corePkg)).toStrictEqual([dashboardPkg]); + }); +}); diff --git a/test/testUtils.ts b/test/testUtils.ts index f1587dd..007d712 100644 --- a/test/testUtils.ts +++ b/test/testUtils.ts @@ -1,5 +1,5 @@ import * as temp from "temp"; -import { corePackage } from "../src/install/nodecgIOVersions"; +import { corePackage, dashboardPackage, dashboardPath } from "../src/install/nodecgIOVersions"; import * as path from "path"; import { DevelopmentInstallation, ProductionInstallation } from "../src/installation"; @@ -21,6 +21,11 @@ export const twitchChatPkg = { path: "nodecg-io-twitch-chat", version: "0.1.0", }; +export const dashboardPkg = { + name: dashboardPackage, + path: dashboardPath, + version: "0.1.0", +}; export const nodecgExampleConfig = { bundles: { paths: ["some-custom-bundle-dir"],