Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions packages/cli/lib/commands/upgrade.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { App, BaseTemplateManager, detectFrameworkFromPackageJson, GoogleAnalytics, ProjectConfig, TEMPLATE_MANAGER,
type ProjectTemplate, Util } from "@igniteui/cli-core";
import { App, BaseTemplateManager, detectFrameworkFromPackageJson, GoogleAnalytics, ProjectConfig, resolveUpgradeableProject, TEMPLATE_MANAGER,
Util } from "@igniteui/cli-core";
import { PositionalArgs, UpgradeCommandType } from "./types";
import { ArgumentsCamelCase } from "yargs";

Expand Down Expand Up @@ -60,12 +60,10 @@ const command: UpgradeCommandType = {
if (projectType === "igx-ts" || projectType === "igr-ts" || projectType === "igc-ts") {
const templateManager = App.container.get<BaseTemplateManager>(TEMPLATE_MANAGER);
const projectLibrary = templateManager.getProjectLibrary(framework, projectType);
let project: ProjectTemplate;
if (!config.project?.projectTemplate || !projectLibrary.hasProject(config.project.projectTemplate)) {
// in case project template is missing from the config we provide backward.
project = projectLibrary.getProject(projectLibrary.projectIds[0]);
} else {
project = projectLibrary.getProject(config.project.projectTemplate);
const project = resolveUpgradeableProject(projectLibrary, config.project?.projectTemplate);
if (!project) {
Util.error("No valid Ignite UI project template found to upgrade packages.", "red");
return;
}
const success = await project.upgradeIgniteUIPackages(process.cwd(), "");
if (success && !argv.skipInstall) {
Expand Down
14 changes: 6 additions & 8 deletions packages/core/packages/PackageManager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { exec } from "child_process";
import * as path from "path";
import { BaseTemplateManager } from "../templates";
import { Config, FS_TOKEN, IFileSystem, ProjectTemplate } from "../types";
import { App, ProjectConfig, Util } from "../util";
import { Config, FS_TOKEN, IFileSystem } from "../types";
import { App, ProjectConfig, resolveUpgradeableProject, Util } from "../util";

import componentsConfig = require("./components");

Expand Down Expand Up @@ -69,14 +69,12 @@ export class PackageManager {
const projectLibrary = templateManager.getProjectLibrary(config.project.framework, config.project.projectType);
if (projectLibrary) {
// TODO multiple projects?
let project: ProjectTemplate;
if (!config.project.projectTemplate) {
// in case project template is missing from the config we provide backward.
project = projectLibrary.getProject(projectLibrary.projectIds[0]);
const project = resolveUpgradeableProject(projectLibrary, config.project.projectTemplate);
if (!project) {
Util.warn("No valid Ignite UI project template found; skipping upgrade step.", "yellow");
} else {
project = projectLibrary.getProject(config.project.projectTemplate);
await project.upgradeIgniteUIPackages(process.cwd(), `./node_modules/${this.fullPackage}/en`);
}
Comment thread
Copilot marked this conversation as resolved.
await project.upgradeIgniteUIPackages(process.cwd(), `./node_modules/${this.fullPackage}/en`);
}
}
} else {
Expand Down
42 changes: 42 additions & 0 deletions packages/core/util/ProjectTemplateResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ProjectLibrary, ProjectTemplate } from "../types";

/** Returns true if the given project template is safe to use for upgrade operations,
* i.e. it's not hidden/partial-only and actually implements `upgradeIgniteUIPackages`. */
function isUpgradeableProject(project: ProjectTemplate): boolean {
return !!project && !project.isHidden && typeof project.upgradeIgniteUIPackages === "function";
}

/**
* Resolves the project template to use for Ignite UI package upgrade operations.
*
* Resolution order:
* 1. The configured `projectTemplate` id, if it exists in the library and is a valid,
* non-hidden project that implements `upgradeIgniteUIPackages`.
* 2. The first project in `library.projectIds` (declared order) that is valid,
* non-hidden, and implements `upgradeIgniteUIPackages`.
*
* This avoids blindly falling back to `projectIds[0]`, which can select hidden/partial
* project templates (e.g. `ai-config`) that don't support upgrading.
*
* @returns the resolved project template, or `null` if none could be found.
*/
export function resolveUpgradeableProject(
library: ProjectLibrary,
configuredTemplateId?: string
): ProjectTemplate | null {
if (configuredTemplateId && library.hasProject(configuredTemplateId)) {
const configuredProject = library.getProject(configuredTemplateId);
if (isUpgradeableProject(configuredProject)) {
return configuredProject;
}
}

for (const id of library.projectIds) {
const project = library.getProject(id);
if (isUpgradeableProject(project)) {
return project;
}
Comment thread
ivanvpetrov marked this conversation as resolved.
}

return null;
}
1 change: 1 addition & 0 deletions packages/core/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './mcp-config';
export * from './Util';
export * from './DotnetTemplateManager';
export * from './ProjectConfig';
export * from './ProjectTemplateResolver';
export * from './Schematics';
export * from './App';
export * from './FileSystem';
Expand Down
13 changes: 5 additions & 8 deletions packages/ng-schematics/src/upgrade-packages/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Rule, SchematicContext, Tree } from "@angular-devkit/schematics";
import { Rule, SchematicContext, SchematicsException, Tree } from "@angular-devkit/schematics";
import { NodePackageInstallTask } from "@angular-devkit/schematics/tasks";
import { App, GoogleAnalytics, ProjectConfig, ProjectTemplate } from "@igniteui/cli-core";
import { App, GoogleAnalytics, ProjectConfig, resolveUpgradeableProject } from "@igniteui/cli-core";
import { defer } from "rxjs";
import { SchematicsTemplateManager } from "../SchematicsTemplateManager";
import { setVirtual } from "../utils/NgFileSystem";
Expand All @@ -19,12 +19,9 @@ export default function(options: UpgradeOptions): Rule {
const templateManager = new SchematicsTemplateManager();
const config = ProjectConfig.getConfig();
const library = templateManager.getProjectLibrary('angular', config.project?.projectType || 'igx-ts');
let project: ProjectTemplate;
if (!config.project?.projectTemplate || !library.hasProject(config.project?.projectTemplate)) {
// in case project template is missing from the config we provide backward.
project = library.getProject(library.projectIds[0]);
} else {
project = library.getProject(config.project.projectTemplate);
const project = resolveUpgradeableProject(library, config.project?.projectTemplate);
if (!project) {
throw new SchematicsException("No valid Ignite UI project template found to upgrade packages.");
}
setVirtual(tree);
return defer(async () => {
Expand Down
87 changes: 87 additions & 0 deletions spec/unit/ProjectTemplateResolver-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { ProjectLibrary, ProjectTemplate, resolveUpgradeableProject } from "@igniteui/cli-core";

describe("Unit - resolveUpgradeableProject", () => {

function mockProject(id: string, options: { isHidden?: boolean; upgradeable?: boolean } = {}): ProjectTemplate {
return {
id,
isHidden: !!options.isHidden,
upgradeIgniteUIPackages: options.upgradeable === false ? undefined : jasmine.createSpy().and.returnValue(Promise.resolve(true))
} as unknown as ProjectTemplate;
}

function mockLibrary(projects: { [id: string]: ProjectTemplate }, projectIds: string[]): ProjectLibrary {
return {
projectIds,
hasProject: jasmine.createSpy().and.callFake((id: string) => projectIds.indexOf(id) > -1),
getProject: jasmine.createSpy().and.callFake((id: string) => projects[id] || null)
} as unknown as ProjectLibrary;
}

it("returns the configured project template when it exists and is upgradeable", () => {
const emptyProject = mockProject("empty");
const library = mockLibrary({ empty: emptyProject }, ["ai-config", "empty"]);

const result = resolveUpgradeableProject(library, "empty");

expect(result).toBe(emptyProject);
expect(library.getProject).toHaveBeenCalledWith("empty");
});

it("skips the configured project template when it is hidden and falls back to the first upgradeable project", () => {
const hiddenConfigured = mockProject("hidden-configured", { isHidden: true });
const emptyProject = mockProject("empty");
const library = mockLibrary(
{ "hidden-configured": hiddenConfigured, empty: emptyProject },
["ai-config", "empty"]
);

const result = resolveUpgradeableProject(library, "hidden-configured");

expect(result).toBe(emptyProject);
});

it("skips hidden projects like ai-config and falls back to the first non-hidden upgradeable project", () => {
const aiConfig = mockProject("ai-config", { isHidden: true });
const base = mockProject("base", { isHidden: true });
const empty = mockProject("empty");
const library = mockLibrary(
{ "ai-config": aiConfig, base, empty },
["ai-config", "base", "empty"]
);

const result = resolveUpgradeableProject(library, "invalid-configured-template");

expect(result).toBe(empty);
});

it("skips projects that don't implement upgradeIgniteUIPackages", () => {
const notUpgradeable = mockProject("not-upgradeable", { upgradeable: false });
const empty = mockProject("empty");
const library = mockLibrary(
{ "not-upgradeable": notUpgradeable, empty },
["not-upgradeable", "empty"]
);

const result = resolveUpgradeableProject(library);

expect(result).toBe(empty);
});

it("returns null when the configured template is invalid and no project in the library is upgradeable", () => {
const aiConfig = mockProject("ai-config", { isHidden: true });
const library = mockLibrary({ "ai-config": aiConfig }, ["ai-config"]);

const result = resolveUpgradeableProject(library, "invalid-configured-template");

expect(result).toBeNull();
});

it("returns null when the library has no projects at all", () => {
const library = mockLibrary({}, []);

const result = resolveUpgradeableProject(library);

expect(result).toBeNull();
});
});