Răsfoiți Sursa

feat(cli): Remove default behavior and require explicit service and naming in add commands - WIP

HouseinIsProgramming 7 luni în urmă
părinte
comite
f6a416e766

+ 21 - 12
packages/cli/src/commands/add/add-operations.ts

@@ -26,6 +26,12 @@ export interface AddOperationOptions {
     uiExtensions?: string | boolean;
     /** Specify the path to a custom Vendure config file */
     config?: string;
+    /** Name for the job queue (used with jobQueue) */
+    name?: string;
+    /** Name for the query (used with apiExtension) */
+    queryName?: string;
+    /** Name for the mutation (used with apiExtension) */
+    mutationName?: string;
 }
 
 export interface AddOperationResult {
@@ -67,10 +73,11 @@ export async function performAddOperation(options: AddOperationOptions): Promise
         }
         if (options.jobQueue) {
             const pluginName = typeof options.jobQueue === 'string' ? options.jobQueue : undefined;
-            await addJobQueueCommand.run({ 
-                isNonInteractive: true, 
+            await addJobQueueCommand.run({
+                isNonInteractive: true,
                 config: options.config,
-                pluginName 
+                pluginName,
+                name: options.name
             });
             return {
                 success: true,
@@ -79,10 +86,10 @@ export async function performAddOperation(options: AddOperationOptions): Promise
         }
         if (options.codegen) {
             const pluginName = typeof options.codegen === 'string' ? options.codegen : undefined;
-            await addCodegenCommand.run({ 
-                isNonInteractive: true, 
+            await addCodegenCommand.run({
+                isNonInteractive: true,
                 config: options.config,
-                pluginName 
+                pluginName
             });
             return {
                 success: true,
@@ -91,10 +98,12 @@ export async function performAddOperation(options: AddOperationOptions): Promise
         }
         if (options.apiExtension) {
             const pluginName = typeof options.apiExtension === 'string' ? options.apiExtension : undefined;
-            await addApiExtensionCommand.run({ 
-                isNonInteractive: true, 
+            await addApiExtensionCommand.run({
+                isNonInteractive: true,
                 config: options.config,
-                pluginName 
+                pluginName,
+                queryName: options.queryName,
+                mutationName: options.mutationName
             });
             return {
                 success: true,
@@ -103,10 +112,10 @@ export async function performAddOperation(options: AddOperationOptions): Promise
         }
         if (options.uiExtensions) {
             const pluginName = typeof options.uiExtensions === 'string' ? options.uiExtensions : undefined;
-            await addUiExtensionsCommand.run({ 
-                isNonInteractive: true, 
+            await addUiExtensionsCommand.run({
+                isNonInteractive: true,
                 config: options.config,
-                pluginName 
+                pluginName
             });
             return {
                 success: true,

+ 56 - 47
packages/cli/src/commands/add/api-extension/add-api-extension.ts

@@ -55,90 +55,99 @@ async function addApiExtension(
 
     // Detect non-interactive mode
     const isNonInteractive = options?.isNonInteractive === true;
-    
+
     let plugin: VendurePluginRef | undefined = providedVendurePlugin;
-    
+
     // If a plugin name was provided, try to find it
     if (!plugin && options?.pluginName) {
         const pluginClasses = getPluginClasses(project);
         const foundPlugin = pluginClasses.find(p => p.getName() === options.pluginName);
-        
+
         if (!foundPlugin) {
             // List available plugins if the specified one wasn't found
             const availablePlugins = pluginClasses.map(p => p.getName()).filter(Boolean);
             throw new Error(
                 `Plugin "${options.pluginName}" not found. Available plugins:\n` +
-                availablePlugins.map(name => `  - ${name}`).join('\n')
+                availablePlugins.map(name => `  - ${name as string}`).join('\n')
             );
         }
-        
+
         plugin = new VendurePluginRef(foundPlugin);
     }
-    
-    // In non-interactive mode, we need all required values
+
+    // In non-interactive mode, we need all required values upfront
     if (isNonInteractive) {
         if (!plugin) {
             throw new Error('Plugin must be specified when running in non-interactive mode');
         }
-        // Don't require query/mutation names - we'll use defaults
+        // Require names to be specified explicitly
+        if (!options?.queryName && !options?.mutationName) {
+            throw new Error(
+                'At least one of queryName or mutationName must be specified in non-interactive mode.\n' +
+                'Usage: npx vendure add -a <PluginName> --queryName <name> --mutationName <name>'
+            );
+        }
     }
 
-    plugin = plugin ?? (await selectPlugin(project, cancelledMessage));
-    
-    // In non-interactive mode, we cannot prompt for service selection
+    // In non-interactive mode, we cannot prompt for plugin selection
     if (isNonInteractive && !plugin) {
-        throw new Error('Cannot select service in non-interactive mode - plugin must be specified');
+        throw new Error('Cannot select plugin in non-interactive mode - plugin must be specified');
     }
-    
-    let serviceRef: ServiceRef | undefined;
-    
+
+    plugin = plugin ?? (await selectPlugin(project, cancelledMessage));
+
     if (isNonInteractive) {
-        // In non-interactive mode, find existing services or create a new one
-        const existingServices = getServices(project).filter(sr => {
-            return sr.classDeclaration
-                .getSourceFile()
-                .getDirectoryPath()
-                .includes(plugin.getSourceFile().getDirectoryPath());
+        // In non-interactive mode, require explicit service specification
+        throw new Error(
+            'Service selection is not supported in non-interactive mode.\n' +
+            'Please first create a service using: npx vendure add -s <ServiceName>\n' +
+            'Then add the API extension interactively.'
+        );
+    }
+
+    const services = getServices(project).filter(sr => {
+        return sr.classDeclaration
+            .getSourceFile()
+            .getDirectoryPath()
+            .includes(plugin.getSourceFile().getDirectoryPath());
+    });
+
+    let serviceRef: ServiceRef | undefined;
+    let serviceEntityRef: EntityRef | undefined;
+
+    const scaffoldSpinner = spinner();
+
+    if (services.length === 0) {
+        log.info('No services found in the selected plugin. Let\'s create one first!');
+        const result = await addServiceCommand.run({
+            plugin,
         });
-        
-        if (existingServices.length > 0) {
-            // Use the first available service
-            serviceRef = existingServices[0];
-            log.info(`Using existing service: ${serviceRef.name}`);
-        } else {
-            // Create a new service automatically
-            log.info('No existing services found, creating a new service...');
-            const result = await addServiceCommand.run({ 
-                type: 'basic', 
-                plugin,
-                serviceName: 'GeneratedService',
-                isNonInteractive: true
-            });
-            serviceRef = result.serviceRef;
-        }
+        serviceRef = result.serviceRef;
     } else {
-        // Interactive mode - let user choose
-        serviceRef = await selectServiceRef(project, plugin, false);
+        serviceRef = await selectServiceRef(project, plugin);
     }
 
     if (!serviceRef) {
-        throw new Error('Service is required for API extension');
+        cancel(cancelledMessage);
+        process.exit(0);
     }
 
-    const serviceEntityRef = serviceRef.crudEntityRef;
     const modifiedSourceFiles: SourceFile[] = [];
-    let resolver: ClassDeclaration | undefined;
-    let apiExtensions: VariableDeclaration | undefined;
 
-    const scaffoldSpinner = spinner();
+    if (serviceRef.crudEntityRef) {
+        serviceEntityRef = serviceRef.crudEntityRef;
+    }
+
+    let resolver: ClassDeclaration;
+    let apiExtensions: VariableDeclaration | undefined;
 
     let queryName = '';
     let mutationName = '';
     if (!serviceEntityRef) {
         if (isNonInteractive) {
-            // Use defaults in non-interactive mode
-            queryName = options?.queryName || 'customQuery';
-            mutationName = options?.mutationName || 'customMutation';
+            // Use provided values - we already validated at least one exists
+            queryName = options?.queryName || '';
+            mutationName = options?.mutationName || '';
         } else {
             const queryNameResult = options?.queryName ?? await text({
                 message: 'Enter a name for the new query',

+ 9 - 9
packages/cli/src/commands/add/codegen/add-codegen.ts

@@ -35,26 +35,26 @@ async function addCodegen(options?: AddCodegenOptions): Promise<CliCommandReturn
 
     // Detect non-interactive mode
     const isNonInteractive = options?.isNonInteractive === true;
-    
+
     let plugin: VendurePluginRef | undefined = providedVendurePlugin;
-    
+
     // If a plugin name was provided, try to find it
     if (!plugin && options?.pluginName) {
         const pluginClasses = getPluginClasses(project);
         const foundPlugin = pluginClasses.find(p => p.getName() === options.pluginName);
-        
+
         if (!foundPlugin) {
             // List available plugins if the specified one wasn't found
             const availablePlugins = pluginClasses.map(p => p.getName()).filter(Boolean);
             throw new Error(
                 `Plugin "${options.pluginName}" not found. Available plugins:\n` +
-                availablePlugins.map(name => `  - ${name}`).join('\n')
+                availablePlugins.map(name => `  - ${name as string}`).join('\n')
             );
         }
-        
+
         plugin = new VendurePluginRef(foundPlugin);
     }
-    
+
     // In non-interactive mode, we need a plugin specified
     if (isNonInteractive && !plugin) {
         throw new Error('Plugin must be specified when running in non-interactive mode');
@@ -108,10 +108,10 @@ async function addCodegen(options?: AddCodegenOptions): Promise<CliCommandReturn
     if (!rootDir) {
         throw new Error('Could not find the root directory of the project');
     }
-    for (const plugin of plugins) {
+    for (const pluginRef of plugins) {
         const relativePluginPath = getRelativeImportPath({
             from: rootDir,
-            to: plugin.classDeclaration.getSourceFile(),
+            to: pluginRef.classDeclaration.getSourceFile(),
         });
         const generatedTypesPath = `${path.dirname(relativePluginPath)}/gql/generated.ts`;
         codegenFile.addEntryToGeneratesObject({
@@ -120,7 +120,7 @@ async function addCodegen(options?: AddCodegenOptions): Promise<CliCommandReturn
             initializer: `{ plugins: ['typescript'] }`,
         });
 
-        if (plugin.hasUiExtensions()) {
+        if (pluginRef.hasUiExtensions()) {
             const uiExtensionsPath = `${path.dirname(relativePluginPath)}/ui`;
             codegenFile.addEntryToGeneratesObject({
                 name: `'${uiExtensionsPath}/gql/'`,

+ 24 - 35
packages/cli/src/commands/add/job-queue/add-job-queue.ts

@@ -34,67 +34,56 @@ async function addJobQueue(
 
     // Detect non-interactive mode
     const isNonInteractive = options?.isNonInteractive === true;
-    
+
     let plugin: VendurePluginRef | undefined = providedVendurePlugin;
-    
+
     // If a plugin name was provided, try to find it
     if (!plugin && options?.pluginName) {
         const pluginClasses = getPluginClasses(project);
         const foundPlugin = pluginClasses.find(p => p.getName() === options.pluginName);
-        
+
         if (!foundPlugin) {
             // List available plugins if the specified one wasn't found
             const availablePlugins = pluginClasses.map(p => p.getName()).filter(Boolean);
             throw new Error(
                 `Plugin "${options.pluginName}" not found. Available plugins:\n` +
-                availablePlugins.map(name => `  - ${name}`).join('\n')
+                availablePlugins.map(name => `  - ${name as string}`).join('\n')
             );
         }
-        
+
         plugin = new VendurePluginRef(foundPlugin);
     }
-    
+
     // In non-interactive mode, we need all required values upfront
     if (isNonInteractive) {
         if (!plugin) {
             throw new Error('Plugin must be specified when running in non-interactive mode');
         }
-        // Don't require name - we'll use a default
+        // Require name to be specified explicitly
+        if (!options?.name) {
+            throw new Error(
+                'Job queue name must be specified in non-interactive mode.\n' +
+                'Usage: npx vendure add -j <PluginName> --name <job-queue-name>'
+            );
+        }
     }
 
     plugin = plugin ?? (await selectPlugin(project, cancelledMessage));
-    
+
     // In non-interactive mode, we cannot prompt for service selection
     if (isNonInteractive && !plugin) {
         throw new Error('Cannot select service in non-interactive mode - plugin must be specified');
     }
-    
+
     let serviceRef: ServiceRef | undefined;
-    
+
     if (isNonInteractive) {
-        // In non-interactive mode, find existing services or create a new one
-        const existingServices = getServices(project).filter(sr => {
-            return sr.classDeclaration
-                .getSourceFile()
-                .getDirectoryPath()
-                .includes(plugin.getSourceFile().getDirectoryPath());
-        });
-        
-        if (existingServices.length > 0) {
-            // Use the first available service
-            serviceRef = existingServices[0];
-            log.info(`Using existing service: ${serviceRef.name}`);
-        } else {
-            // Create a new service automatically
-            log.info('No existing services found, creating a new service...');
-            const result = await addServiceCommand.run({ 
-                type: 'basic', 
-                plugin,
-                serviceName: 'GeneratedService',
-                isNonInteractive: true
-            });
-            serviceRef = result.serviceRef;
-        }
+        // In non-interactive mode, require explicit service specification
+        throw new Error(
+            'Service selection is not supported in non-interactive mode.\n' +
+            'Please first create a service using: npx vendure add -s <ServiceName>\n' +
+            'Then add the job queue interactively.'
+        );
     } else {
         // Interactive mode - let user choose
         serviceRef = await selectServiceRef(project, plugin);
@@ -104,7 +93,7 @@ async function addJobQueue(
         throw new Error('Service is required for job queue');
     }
 
-    const jobQueueName = options?.name ?? (isNonInteractive ? 'my-job-queue' : await text({
+    const jobQueueName = options?.name ?? await text({
         message: 'What is the name of the job queue?',
         initialValue: 'my-background-task',
         validate: input => {
@@ -112,7 +101,7 @@ async function addJobQueue(
                 return 'The job queue name must be lowercase and contain only letters, numbers and dashes';
             }
         },
-    }));
+    });
 
     if (!isNonInteractive && isCancel(jobQueueName)) {
         cancel(cancelledMessage);

+ 6 - 6
packages/cli/src/commands/add/ui-extensions/add-ui-extensions.ts

@@ -32,26 +32,26 @@ async function addUiExtensions(options?: AddUiExtensionsOptions): Promise<CliCom
 
     // Detect non-interactive mode
     const isNonInteractive = options?.isNonInteractive === true;
-    
+
     let vendurePlugin: VendurePluginRef | undefined = providedVendurePlugin;
-    
+
     // If a plugin name was provided, try to find it
     if (!vendurePlugin && options?.pluginName) {
         const pluginClasses = getPluginClasses(project);
         const foundPlugin = pluginClasses.find(p => p.getName() === options.pluginName);
-        
+
         if (!foundPlugin) {
             // List available plugins if the specified one wasn't found
             const availablePlugins = pluginClasses.map(p => p.getName()).filter(Boolean);
             throw new Error(
                 `Plugin "${options.pluginName}" not found. Available plugins:\n` +
-                availablePlugins.map(name => `  - ${name}`).join('\n')
+                availablePlugins.map(name => `  - ${name as string}`).join('\n')
             );
         }
-        
+
         vendurePlugin = new VendurePluginRef(foundPlugin);
     }
-    
+
     // In non-interactive mode, we need a plugin specified
     if (isNonInteractive && !vendurePlugin) {
         throw new Error('Plugin must be specified when running in non-interactive mode');

+ 15 - 0
packages/cli/src/commands/command-declarations.ts

@@ -34,6 +34,11 @@ export const cliCommands: CliCommandDefinition[] = [
                 description: 'Add job-queue support to the specified plugin',
                 required: false,
             },
+            {
+                long: '--name <n>',
+                description: 'Name for the job queue (required with -j)',
+                required: false,
+            },
             {
                 short: '-c',
                 long: '--codegen [plugin]',
@@ -46,6 +51,16 @@ export const cliCommands: CliCommandDefinition[] = [
                 description: 'Add an API extension scaffold to the specified plugin',
                 required: false,
             },
+            {
+                long: '--queryName <n>',
+                description: 'Name for the query (used with -a)',
+                required: false,
+            },
+            {
+                long: '--mutationName <n>',
+                description: 'Name for the mutation (used with -a)',
+                required: false,
+            },
             {
                 short: '-u',
                 long: '--ui-extensions [plugin]',