diff --git a/messages/agent.generate.authoring-bundle.md b/messages/agent.generate.authoring-bundle.md index 13f2764..f98ff2f 100644 --- a/messages/agent.generate.authoring-bundle.md +++ b/messages/agent.generate.authoring-bundle.md @@ -139,3 +139,15 @@ Canceled authoring bundle generation. # warning.noSpecDir No agent spec directory found at %s. + +# error.jsonRequiresName + +When using --json, you must also specify --name. + +# error.jsonRequiresSpecOrNoSpec + +When using --json, you must also specify either --spec or --no-spec. + +# error.jsonAabExists + +An authoring bundle with the API name "%s" already exists in the project. Use --force-overwrite to overwrite it or specify a different authoring bundle using the --api-name flag. diff --git a/messages/agent.test.run.md b/messages/agent.test.run.md index 65ed2f4..09adc43 100644 --- a/messages/agent.test.run.md +++ b/messages/agent.test.run.md @@ -35,3 +35,7 @@ Number of minutes to wait for the command to complete and display results to the - Start an agent test and write the JSON-formatted results into a directory called "test-results": <%= config.bin %> <%= command.id %> --api-name Resort_Manager_Test --wait 10 --output-dir ./test-results --result-format json + +# error.missingRequiredFlags + +When using --json, you must also specify the required flag(s): %s. diff --git a/src/commands/agent/generate/authoring-bundle.ts b/src/commands/agent/generate/authoring-bundle.ts index 8f97722..1fba5f8 100644 --- a/src/commands/agent/generate/authoring-bundle.ts +++ b/src/commands/agent/generate/authoring-bundle.ts @@ -106,6 +106,19 @@ async function resolveUniqueBundle( return resolveUniqueBundle(baseOutputDir, false); } +function resolveNameAndApiNameForJson( + flags: { name: string; 'api-name'?: string; 'force-overwrite'?: boolean }, + baseOutputDir: string +): { name: string; bundleApiName: string } { + const name = flags.name; + const bundleApiName = flags['api-name'] ?? generateApiName(name); + const bundleDir = join(baseOutputDir, 'aiAuthoringBundles', bundleApiName); + if (existsSync(bundleDir) && !flags['force-overwrite']) { + throw messages.createError('error.jsonAabExists', [bundleApiName]); + } + return { name, bundleApiName }; +} + export default class AgentGenerateAuthoringBundle extends SfCommand { public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); @@ -138,6 +151,7 @@ export default class AgentGenerateAuthoringBundle extends SfCommand { const { flags } = await this.parse(AgentGenerateAuthoringBundle); const { 'output-dir': outputDir } = flags; @@ -146,6 +160,16 @@ export default class AgentGenerateAuthoringBundle extends SfCommand undefined, --spec => path, missing => wizard prompts let spec: string | undefined; if (flags['no-spec']) { @@ -207,19 +231,26 @@ export default class AgentGenerateAuthoringBundle extends SfCommand { const { flags } = await this.parse(AgentPublishAuthoringBundle); + + if (this.jsonEnabled() && !flags['api-name']) { + throw messages.createError('error.missingRequiredFlags', ['api-name']); + } + // If api-name is not provided, prompt user to select an .agent file from the project and extract the API name from it const aabName = flags['api-name'] ?? diff --git a/src/commands/agent/test/run.ts b/src/commands/agent/test/run.ts index 91e482d..9376965 100644 --- a/src/commands/agent/test/run.ts +++ b/src/commands/agent/test/run.ts @@ -84,6 +84,11 @@ export default class AgentTestRun extends SfCommand { public async run(): Promise { const { flags } = await this.parse(AgentTestRun); const connection = flags['target-org'].getConnection(flags['api-version']); + + if (this.jsonEnabled() && !flags['api-name']) { + throw messages.createError('error.missingRequiredFlags', ['api-name']); + } + const apiName = flags['api-name'] ?? (await promptForAiEvaluationDefinitionApiName(FLAGGABLE_PROMPTS['api-name'], connection)); diff --git a/src/commands/agent/validate/authoring-bundle.ts b/src/commands/agent/validate/authoring-bundle.ts index c402eef..823d5fd 100644 --- a/src/commands/agent/validate/authoring-bundle.ts +++ b/src/commands/agent/validate/authoring-bundle.ts @@ -63,6 +63,11 @@ export default class AgentValidateAuthoringBundle extends SfCommand { const { flags } = await this.parse(AgentValidateAuthoringBundle); + + if (this.jsonEnabled() && !flags['api-name']) { + throw messages.createError('error.missingRequiredFlags', ['api-name']); + } + // If api-name is not provided, prompt user to select an .agent file from the project and extract the API name from it const aabName = flags['api-name'] ?? diff --git a/test/commands/agent/generate/authoring-bundle.test.ts b/test/commands/agent/generate/authoring-bundle.test.ts index b5dfc41..0db9c8b 100644 --- a/test/commands/agent/generate/authoring-bundle.test.ts +++ b/test/commands/agent/generate/authoring-bundle.test.ts @@ -459,4 +459,116 @@ describe('agent generate authoring-bundle', () => { expect(result.agentPath).to.include('BrandNewAgent.agent'); }); }); + + describe('when --json is used', () => { + it('should throw when --name is not specified', async () => { + try { + await AgentGenerateAuthoringBundle.run([ + '--json', + '--no-spec', + '--api-name', + 'MyAgent', + '--target-org', + 'test@org.com', + ]); + expect.fail('Expected error'); + } catch (error) { + expect((error as Error).message).to.include('you must specify --name'); + } + }); + + it('should throw when neither --spec nor --no-spec is specified', async () => { + try { + await AgentGenerateAuthoringBundle.run([ + '--json', + '--name', + 'My Agent', + '--api-name', + 'MyAgent', + '--target-org', + 'test@org.com', + ]); + expect.fail('Expected error'); + } catch (error) { + expect((error as Error).message).to.include('you must specify either --spec or --no-spec'); + } + }); + + it('should throw when existing AAB matches api-name and --force-overwrite is not set', async () => { + const EXISTING_BUNDLE_API_NAME = 'Willie_Resort_Manager'; + try { + await AgentGenerateAuthoringBundle.run([ + '--json', + '--no-spec', + '--name', + 'Willie Resort Manager', + '--api-name', + EXISTING_BUNDLE_API_NAME, + '--target-org', + 'test@org.com', + ]); + expect.fail('Expected error'); + } catch (error) { + expect((error as Error).message).to.include(EXISTING_BUNDLE_API_NAME); + expect((error as Error).message).to.include('--force-overwrite'); + } + }); + + it('should use generateApiName(name) as api-name when --api-name is omitted and not prompt', async () => { + const result = await AgentGenerateAuthoringBundle.run([ + '--json', + '--no-spec', + '--name', + 'My Custom Agent', + '--target-org', + 'test@org.com', + ]); + + expect(inputStub.called).to.be.false; + const expectedApiName = generateApiName('My Custom Agent'); + expect(result.outputDir).to.include(expectedApiName); + expect(createAuthoringBundleStub.calledOnce).to.be.true; + const callArgs = createAuthoringBundleStub.firstCall.args[0] as CreateAuthoringBundleArgs; + expect(callArgs.bundleApiName).to.equal(expectedApiName); + expect(callArgs.agentSpec.name).to.equal('My Custom Agent'); + }); + + it('should succeed with --json when all required flags provided and no existing AAB', async () => { + const result = await AgentGenerateAuthoringBundle.run([ + '--json', + '--no-spec', + '--name', + 'Json Agent', + '--api-name', + 'JsonAgent', + '--target-org', + 'test@org.com', + ]); + + expect(selectStub.called).to.be.false; + expect(inputStub.called).to.be.false; + expect(yesNoOrCancelStub.called).to.be.false; + expect(result.agentPath).to.include('JsonAgent.agent'); + expect(createAuthoringBundleStub.calledOnce).to.be.true; + }); + + it('should succeed with --json and --force-overwrite when existing AAB matches', async () => { + const EXISTING_BUNDLE_API_NAME = 'Willie_Resort_Manager'; + const result = await AgentGenerateAuthoringBundle.run([ + '--json', + '--no-spec', + '--name', + 'Willie Resort Manager', + '--api-name', + EXISTING_BUNDLE_API_NAME, + '--force-overwrite', + '--target-org', + 'test@org.com', + ]); + + expect(yesNoOrCancelStub.called).to.be.false; + expect(createAuthoringBundleStub.calledOnce).to.be.true; + expect(result.agentPath).to.include(`${EXISTING_BUNDLE_API_NAME}.agent`); + }); + }); });