Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0e61cc3
feat(yeoman-ui): implement generator progress notifications
korotkovao Jul 28, 2026
47510da
fix(yeoman-ui): add .js extensions to test imports for ESM
korotkovao Jul 29, 2026
480aefd
fix(yeoman-ui): add .js extension to external package import
korotkovao Jul 29, 2026
814dd4f
chore(yeoman-ui): remove debug console.log statements
korotkovao Jul 29, 2026
65f14e5
test(yeoman-ui): add comprehensive tests for project name feature
korotkovao Jul 29, 2026
f08d0ce
fix(yeoman-ui): remove problematic loggerWrapperMock from test setup
korotkovao Jul 29, 2026
7fa25a5
fix(yeoman-ui): restore logger initialization in test setup
korotkovao Jul 29, 2026
a53cea8
fix(yeoman-ui): fix lodash import for ESM compatibility
korotkovao Jul 29, 2026
040b8c7
fix(yeoman-ui): fix fs mock conflicts and remove incorrect tests
korotkovao Jul 29, 2026
5b4b8e1
fix(yeoman-ui): replace all fsMock.expects with sandbox.stub
korotkovao Jul 29, 2026
a7a1a56
fix(yeoman-ui): fix ES module mocking and improve test coverage
korotkovao Jul 29, 2026
add6245
test(yeoman-ui): add tests for edge cases to improve coverage
korotkovao Jul 29, 2026
3bf95cd
style(yeoman-ui): fix prettier formatting in test file
korotkovao Jul 29, 2026
cf568f2
chore(yeoman-ui): add clarifying comment
korotkovao Jul 29, 2026
b60e499
perf(yeoman-ui): reduce finalizing delay from 1000ms to 100ms
korotkovao Jul 29, 2026
fb39b26
fix(yeoman-ui): remove setTimeout to fix CI test timeout
korotkovao Jul 29, 2026
595127e
refactor(yeoman-ui): simplify doGeneratorDone return
korotkovao Jul 29, 2026
1044f04
test(yeoman-ui): add test to improve coverage to 92.02%
korotkovao Jul 29, 2026
85e9502
fix(yeoman-ui): convert Thenable to Promise<void> in doGeneratorDone
korotkovao Jul 29, 2026
41ee0c7
fix(yeoman-ui): add explicit return type annotation to then callback
korotkovao Jul 29, 2026
3adb1cf
fix(yeoman-ui): use type assertion instead of Promise wrapper
korotkovao Jul 29, 2026
02e31f2
fix(yeoman-ui): change return type to Thenable<any> to match implemen…
korotkovao Jul 29, 2026
885319f
fix(yeoman-ui): update YouiEvents interface to return Thenable<any>
korotkovao Jul 29, 2026
46622df
fix(yeoman-ui): stub fs.writeFileSync in tests to prevent CI failures
korotkovao Jul 29, 2026
c5206df
fix(yeoman-ui): stub WorkspaceFile methods to prevent CI filesystem e…
korotkovao Jul 29, 2026
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
122 changes: 103 additions & 19 deletions projects/yeoman-ui/packages/backend/src/vscode-youi-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { getFileSchemeWorkspaceFolders } from "./utils/workspaceFolders.js";

const { isEmpty, isNil, set } = lodash;

// App Wizard wrapper that delegates to VSCodeYouiEvents
class YoUiAppWizard extends AppWizard {
constructor(private readonly events: VSCodeYouiEvents) {
super();
Expand Down Expand Up @@ -61,6 +62,8 @@ export class VSCodeYouiEvents implements YouiEvents {
private webviewPanel: WebviewPanel;
private readonly messages: any;
private resolveFunc: any;
private progressReporter: any; // Store progress reporter to update it
private currentProjectName: string | undefined; // Store project name for success message
public output: GeneratorOutput;
private readonly logger: IChildLogger;
private readonly appWizard: AppWizard;
Expand Down Expand Up @@ -94,21 +97,63 @@ export class VSCodeYouiEvents implements YouiEvents {
selectedWorkspace: string,
type: string,
targetFolderPath?: string
): void {
): Thenable<any> {
// Show "Finalising..." before closing
if (this.progressReporter) {
this.progressReporter.report({ message: "Finalising..." });
}

this.resolveInstallingProgress();
set(this.webviewPanel, Constants.GENERATOR_COMPLETED, success);
this.doClose();
void this.showDoneMessage(
return this.showDoneMessage(
success,
message,
selectedWorkspace,
type,
targetFolderPath
targetFolderPath,
true // Skip resolving progress since we already did it
);
}

public doGeneratorInstall(): void {
public doGeneratorInstall(projectName?: string): void {
this.doClose();
this.showInstallMessage();
this.showInstallMessage(projectName);
}

public async doGeneratorProgress(
projectName: string | undefined,
phase: "writing" | "install" | "end"
): Promise<void> {
// Map phases to user-friendly messages
const phaseMessages = {
writing: "Creating project files...",
install: "Installing dependencies...",
end: "Finalising...",
};

const message = phaseMessages[phase];

// If this is the first phase (writing), initialize the notification with the message
if (phase === "writing") {
this.doClose();
this.showInstallMessage(projectName, message);

// Wait for the progress reporter to be initialized
await new Promise((resolve) => setTimeout(resolve, 50));
} else {
if (this.progressReporter) {
// Artificial delay for "install" phase to ensure "Creating project files..." is visible for 2 seconds
if (phase === "install") {
await new Promise((resolve) => setTimeout(resolve, 2000));
}

// Give VS Code time to render the previous state before updating
await new Promise((resolve) => setTimeout(resolve, 10));
// Don't use increment to get a continuous spinner instead of a stuck progress bar
this.progressReporter.report({ message });
}
}
}

public getAppWizard(): AppWizard {
Expand Down Expand Up @@ -182,16 +227,36 @@ export class VSCodeYouiEvents implements YouiEvents {
}
}

private showInstallMessage(): void {
private showInstallMessage(
projectName?: string,
initialMessage: string = "Preparing..."
): void {
// Store project name for later use in success message
this.currentProjectName = projectName;

// Use "Generating {projectName}" as the title
const title = projectName
? `Generating ${projectName}`
: "Application Generator";

void vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: "Installing dependencies...",
title: title,
cancellable: false,
},
async () => {
async (progress) => {
// Store the progress reporter so we can update it
this.progressReporter = progress;
progress.report({ message: initialMessage });

// Keep the notification open until generation completes
await new Promise((resolve) => {
this.resolveFunc = resolve;
});

// Clean up the progress reporter
this.progressReporter = null;
}
);
}
Expand All @@ -207,9 +272,12 @@ export class VSCodeYouiEvents implements YouiEvents {
errorMmessage: string,
selectedWorkspace: string,
type: string,
targetFolderPath?: string
targetFolderPath?: string,
skipResolve: boolean = false
): Thenable<any> {
this.resolveInstallingProgress();
if (!skipResolve) {
this.resolveInstallingProgress();
}

if (success) {
if (!isNil(targetFolderPath)) {
Expand Down Expand Up @@ -319,17 +387,33 @@ export class VSCodeYouiEvents implements YouiEvents {
selectedWorkspace: string,
type: string
): string {
let successInfoMessage: string = this.messages.artifact_generated_files;
// Default message with project name if available
let successInfoMessage: string = this.currentProjectName
? `Project ${this.currentProjectName} has been generated.`
: this.messages.artifact_generated_files;

if (type === "project") {
if (selectedWorkspace === this.messages.open_in_a_new_workspace) {
successInfoMessage =
this.messages.artifact_generated_project_open_in_a_new_workspace;
} else if (selectedWorkspace === this.messages.add_to_workspace) {
successInfoMessage =
this.messages.artifact_generated_project_add_to_workspace;
// For project type, use project name and add workspace-specific detail
if (this.currentProjectName) {
if (selectedWorkspace === this.messages.open_in_a_new_workspace) {
successInfoMessage = `Project ${this.currentProjectName} has been generated. The project will be opened in a new workspace.`;
} else if (selectedWorkspace === this.messages.add_to_workspace) {
successInfoMessage = `Project ${this.currentProjectName} has been generated. The project has been added to workspace.`;
} else {
successInfoMessage = `Project ${this.currentProjectName} has been generated.`;
}
} else {
successInfoMessage =
this.messages.artifact_generated_project_saved_for_future;
// Fallback to original messages if no project name
if (selectedWorkspace === this.messages.open_in_a_new_workspace) {
successInfoMessage =
this.messages.artifact_generated_project_open_in_a_new_workspace;
} else if (selectedWorkspace === this.messages.add_to_workspace) {
successInfoMessage =
this.messages.artifact_generated_project_add_to_workspace;
} else {
successInfoMessage =
this.messages.artifact_generated_project_saved_for_future;
}
}
} else if (type === "module") {
successInfoMessage = this.messages.artifact_generated_module;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,28 @@ export class ServerYouiEvents implements YouiEvents {
selectedWorkspace: string,
type: string,
targetPath = ""
): void {
void this.rpc.invoke("generatorDone", [
): Promise<void> {
return this.rpc.invoke("generatorDone", [
suceeded,
message,
selectedWorkspace,
type,
targetPath,
]);
]) as Promise<void>;
}

public doGeneratorInstall(): void {
void this.rpc.invoke("generatorInstall");
}

public async doGeneratorProgress(
projectName: string | undefined,
phase: "writing" | "install" | "end"
): Promise<void> {
// WebSocket implementation - invoke RPC method with progress info
await this.rpc.invoke("generatorProgress", [projectName, phase]);
}

public showProgress(): void {
void this.rpc.invoke("showProgress");
}
Expand Down
32 changes: 29 additions & 3 deletions projects/yeoman-ui/packages/backend/src/yeomanui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ export class YeomanUI {
);
AnalyticsWrapper.updateGeneratorEnded(generatorName);
// when targetFolderPath is undefined and no files are generated, send type = '' to get the empty toast message
this.youiEvents.doGeneratorDone(
void this.youiEvents.doGeneratorDone(
true,
message,
selectedWorkspace,
Expand All @@ -570,7 +570,7 @@ export class YeomanUI {
const messagePrefix = `${generatorName} generator failed`;
const errorMsg = error?.message || error;
this.logError(error, messagePrefix);
this.youiEvents.doGeneratorDone(
void this.youiEvents.doGeneratorDone(
false,
`${messagePrefix} - ${errorMsg}`,
"",
Expand All @@ -582,8 +582,34 @@ export class YeomanUI {
}

private onGenInstall(gen: any) {
// Extract project name
const getProjectName = () => {
return (
_.get(gen, "state.project.name") ||
_.get(gen, "options.projectName") ||
_.get(gen, "answers.projectName") ||
_.get(gen, "answers.app.name") ||
_.get(gen, "props.projectName") ||
_.get(gen, "props.app.name")
);
};

// Listen to writing phase
gen.on("method:writing", () => {
const projectName = getProjectName();
void this.youiEvents.doGeneratorProgress(projectName, "writing");
});

// Listen to install phase
gen.on("method:install", () => {
this.youiEvents.doGeneratorInstall();
const projectName = getProjectName();
void this.youiEvents.doGeneratorProgress(projectName, "install");
});

// Listen to end phase
gen.on("method:end", () => {
const projectName = getProjectName();
void this.youiEvents.doGeneratorProgress(projectName, "end");
});
}

Expand Down
2 changes: 1 addition & 1 deletion projects/yeoman-ui/packages/backend/src/youi-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class YouiAdapter {
try {
return await cb(result); // eslint-disable-line @typescript-eslint/await-thenable
} catch (err) {
this.youiEvents.doGeneratorDone(
void this.youiEvents.doGeneratorDone(
false,
get(err, "message", "Template Wizard detected an error"),
"",
Expand Down
8 changes: 6 additions & 2 deletions projects/yeoman-ui/packages/backend/src/youi-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ export interface YouiEvents {
selectedWorkspace: string,
type: string,
targetFolderPath?: string
): void;
doGeneratorInstall(): void;
): Thenable<any>;
doGeneratorInstall(projectName?: string): void;
doGeneratorProgress(
projectName: string | undefined,
phase: "writing" | "install" | "end"
): Promise<void>;
showProgress(message?: string): void;
getAppWizard(): AppWizard;
executeCommand(id: string, ...args: any[]): Thenable<any>;
Expand Down
Loading
Loading