Skip to content
Draft
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
77 changes: 77 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@
}
],
"commands": [
{
"command": "magento-toolbox.generate",
"title": "Generate…",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.generateModule",
"title": "Generate Module",
Expand Down Expand Up @@ -296,6 +301,46 @@
"title": "Generate Cron Job",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.generateCliCommand",
"title": "Generate CLI Command",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.generateProductEavAttributePatch",
"title": "Generate Product EAV Attribute Patch",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.generateCategoryEavAttributePatch",
"title": "Generate Category EAV Attribute Patch",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.generateCustomerEavAttributePatch",
"title": "Generate Customer EAV Attribute Patch",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.generateSystemConfig",
"title": "Generate System Configuration",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.generateGraphqlResolver",
"title": "Generate GraphQL Resolver",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.generateCronGroup",
"title": "Generate Cron Group",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.generateController",
"title": "Generate Controller",
"category": "Magento Toolbox"
},
{
"command": "magento-toolbox.jumpToModule",
"title": "Jump to Module",
Expand Down Expand Up @@ -437,6 +482,38 @@
{
"command": "magento-toolbox.generateCronJob",
"when": "resourcePath =~ /app\\/code\\/.+\\/.+/i"
},
{
"command": "magento-toolbox.generateCliCommand",
"when": "resourcePath =~ /app\\/code\\/.+\\/.+/i"
},
{
"command": "magento-toolbox.generateProductEavAttributePatch",
"when": "resourcePath =~ /app\\/code\\/.+\\/.+/i"
},
{
"command": "magento-toolbox.generateCategoryEavAttributePatch",
"when": "resourcePath =~ /app\\/code\\/.+\\/.+/i"
},
{
"command": "magento-toolbox.generateCustomerEavAttributePatch",
"when": "resourcePath =~ /app\\/code\\/.+\\/.+/i"
},
{
"command": "magento-toolbox.generateSystemConfig",
"when": "resourcePath =~ /app\\/code\\/.+\\/.+/i"
},
{
"command": "magento-toolbox.generateGraphqlResolver",
"when": "resourcePath =~ /app\\/code\\/.+\\/.+/i"
},
{
"command": "magento-toolbox.generateCronGroup",
"when": "resourcePath =~ /app\\/code\\/.+\\/.+/i"
},
{
"command": "magento-toolbox.generateController",
"when": "resourcePath =~ /app\\/code\\/.+\\/.+/i"
}
]
}
Expand Down
Binary file modified resources/logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
132 changes: 132 additions & 0 deletions src/command/AbstractWizardCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Uri, window, workspace, WorkspaceFolder } from 'vscode';
import { Command } from './Command';
import Common from 'util/Common';
import FileGenerator from 'generator/FileGenerator';
import FileGeneratorManager from 'generator/FileGeneratorManager';
import FileSystem from 'util/FileSystem';
import IndexManager from 'indexer/IndexManager';
import ModuleIndexer from 'indexer/module/ModuleIndexer';
import WizzardClosedError from 'webview/error/WizzardClosedError';
import { GeneratorWizard } from 'webview/GeneratorWizard';
import { PreviewResult } from 'types/webview';
import CommandAbortError from './CommandAbortError';

export type OpenStrategy = 'first' | 'last' | 'all' | 'none';

export abstract class AbstractWizardCommand<TData> extends Command {
protected abstract showWizard(
contextModule: string | undefined,
uri: Uri | undefined,
args: unknown[]
): Promise<TData>;

protected abstract buildGenerators(data: TData): FileGenerator[];

protected openStrategy(): OpenStrategy {
return 'first';
}

public async execute(uri?: Uri, ...args: unknown[]): Promise<void> {
const contextModule = this.resolveContextModule(uri);

let data: TData;
try {
data = await this.showWizard(contextModule, uri, args);
} catch (error) {
if (error instanceof WizzardClosedError || error instanceof CommandAbortError) {
return;
}
throw error;
}

const workspaceFolder = this.requireWorkspaceFolder();
if (!workspaceFolder) {
return;
}

const manager = new FileGeneratorManager(this.buildGenerators(data));
await manager.generate(workspaceFolder.uri);
await manager.writeFiles();
await manager.refreshIndex(workspaceFolder);
this.openFiles(manager);
}

protected resolveContextModule(uri: Uri | undefined): string | undefined {
const contextUri = uri ?? window.activeTextEditor?.document.uri;
if (!contextUri) {
return undefined;
}

const moduleIndex = IndexManager.getIndexData(ModuleIndexer.KEY);
if (!moduleIndex) {
return undefined;
}

return moduleIndex.getModuleByUri(contextUri)?.name;
}

protected requireWorkspaceFolder(): WorkspaceFolder | undefined {
const folder = Common.getActiveWorkspaceFolder();
if (!folder) {
window.showErrorMessage('No active workspace folder');
return undefined;
}
return folder;
}

protected openFiles(manager: FileGeneratorManager): void {
switch (this.openStrategy()) {
case 'all':
manager.openAllFiles();
return;
case 'last':
manager.openLastFile();
return;
case 'none':
return;
case 'first':
default:
manager.openFirstFile();
}
}

/**
* Attach the live-preview handler to a freshly-constructed wizard. Commands
* should call this on any wizard before invoking show().
*/
protected attachPreview<W extends GeneratorWizard>(wizard: W): W {
wizard.setPreviewHandler(formData => this.buildPreview(formData as TData));
return wizard;
}

/**
* Dry-run the generators for the given form data and return a list of
* files that would be created or modified. Catches generator errors (e.g.
* partial input) and returns an empty list so the webview can show a neutral
* state until the form is complete enough to generate.
*/
protected async buildPreview(data: TData): Promise<PreviewResult> {
const workspaceFolder = Common.getActiveWorkspaceFolder();
if (!workspaceFolder) {
return { files: [] };
}

try {
const manager = new FileGeneratorManager(this.buildGenerators(data));
const generated = await manager.generate(workspaceFolder.uri);

const files = await Promise.all(
generated.map(async file => ({
path: workspace.asRelativePath(file.uri),
action: ((await FileSystem.fileExists(file.uri)) ? 'modify' : 'create') as
| 'create'
| 'modify',
}))
);

return { files };
} catch (error) {
return { files: [], error: error instanceof Error ? error.message : String(error) };
}
}
}
11 changes: 11 additions & 0 deletions src/command/CommandAbortError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Thrown from inside a command's showWizard() to cleanly abort before any files
* are generated. The base command swallows it silently — callers should show
* their own error message before throwing.
*/
export default class CommandAbortError extends Error {
constructor(message?: string) {
super(message ?? 'Command aborted');
this.name = 'CommandAbortError';
}
}
58 changes: 0 additions & 58 deletions src/command/GenerateBlockCommand.ts

This file was deleted.

22 changes: 22 additions & 0 deletions src/command/GenerateCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as vscode from 'vscode';
import { Command } from './Command';
import { pickGenerator } from 'util/GeneratorPicker';

/**
* Top-level entry point that opens a QuickPick listing every Magento Toolbox
* generator command and runs the one the user picks. Serves both as a
* standalone command palette shortcut and as the backing command for the
* "Switch generator…" button inside any open wizard.
*/
export default class GenerateCommand extends Command {
constructor() {
super('magento-toolbox.generate');
}

public async execute(): Promise<void> {
const picked = await pickGenerator();
if (picked) {
await vscode.commands.executeCommand(picked);
}
}
}
Loading
Loading