Skip to content

Commit 21f6f15

Browse files
Merge pull request #224 from salesforcecli/er/addApiNameFlag
fix: add --api-name flag to generate authoring-bundle command
2 parents 3023651 + f19c444 commit 21f6f15

File tree

5 files changed

+74
-17
lines changed

5 files changed

+74
-17
lines changed

command-snapshot.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"command": "agent:generate:authoring-bundle",
5757
"flagAliases": [],
5858
"flagChars": ["d", "f", "n", "o"],
59-
"flags": ["api-version", "flags-dir", "json", "name", "output-dir", "spec", "target-org"],
59+
"flags": ["api-name", "api-version", "flags-dir", "json", "name", "output-dir", "spec", "target-org"],
6060
"plugin": "@salesforce/plugin-agent"
6161
},
6262
{

messages/agent.generate.authoring-bundle.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ Directory where the authoring bundle files will be generated.
1616

1717
# flags.name.summary
1818

19-
Name (label) of the authoring bundle. If not provided, you will be prompted for it.
19+
Name (label) of the authoring bundle.
2020

21-
# flags.name.prompt
21+
# flags.api-name.summary
2222

23-
Name (label) of the authoring bundle
23+
API name of the new authoring bundle; if not specified, the API name is derived from the authoring bundle name (label); the API name must not exist in the org.
24+
25+
# flags.api-name.prompt
26+
27+
API name of the new authoring bundle
2428

2529
# examples
2630

src/commands/agent/generate/authoring-bundle.ts

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { join } from 'node:path';
18-
import { mkdirSync, writeFileSync, readFileSync } from 'node:fs';
17+
import { join, resolve } from 'node:path';
18+
import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
1919
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
20-
import { Messages, SfError } from '@salesforce/core';
20+
import { generateApiName, Messages, SfError } from '@salesforce/core';
2121
import { Agent, AgentJobSpec } from '@salesforce/agents';
2222
import YAML from 'yaml';
23-
import { FlaggablePrompt, promptForFlag } from '../../../flags.js';
23+
import { input as inquirerInput } from '@inquirer/prompts';
24+
import { theme } from '../../../inquirer-theme.js';
25+
import { FlaggablePrompt, promptForFlag, promptForYamlFile } from '../../../flags.js';
2426

2527
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
2628
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.generate.authoring-bundle');
@@ -39,6 +41,9 @@ export default class AgentGenerateAuthoringBundle extends SfCommand<AgentGenerat
3941

4042
public static readonly flags = {
4143
'target-org': Flags.requiredOrg(),
44+
'api-name': Flags.string({
45+
summary: messages.getMessage('flags.api-name.summary'),
46+
}),
4247
'api-version': Flags.orgApiVersion(),
4348
spec: Flags.file({
4449
summary: messages.getMessage('flags.spec.summary'),
@@ -58,13 +63,36 @@ export default class AgentGenerateAuthoringBundle extends SfCommand<AgentGenerat
5863
private static readonly FLAGGABLE_PROMPTS = {
5964
name: {
6065
message: messages.getMessage('flags.name.summary'),
61-
promptMessage: messages.getMessage('flags.name.prompt'),
62-
validate: (d: string): boolean | string => d.length > 0 || 'Name cannot be empty',
66+
validate: (d: string): boolean | string =>
67+
d.trim().length > 0 || 'Name cannot be empty or contain only whitespace',
6368
required: true,
6469
},
70+
'api-name': {
71+
message: messages.getMessage('flags.api-name.summary'),
72+
promptMessage: messages.getMessage('flags.api-name.prompt'),
73+
validate: (d: string): boolean | string => {
74+
if (d.length === 0) {
75+
return true;
76+
}
77+
if (d.length > 80) {
78+
return 'API name cannot be over 80 characters.';
79+
}
80+
const regex = /^[A-Za-z][A-Za-z0-9_]*[A-Za-z0-9]+$/;
81+
if (!regex.test(d)) {
82+
return 'Invalid API name.';
83+
}
84+
return true;
85+
},
86+
},
6587
spec: {
6688
message: messages.getMessage('flags.spec.summary'),
67-
validate: (d: string): boolean | string => d.length > 0 || 'Spec file path cannot be empty',
89+
validate: (d: string): boolean | string => {
90+
const specPath = resolve(d);
91+
if (!existsSync(specPath)) {
92+
return 'Please enter an existing agent spec (yaml) file';
93+
}
94+
return true;
95+
},
6896
required: true,
6997
},
7098
} satisfies Record<string, FlaggablePrompt>;
@@ -74,12 +102,25 @@ export default class AgentGenerateAuthoringBundle extends SfCommand<AgentGenerat
74102
const { 'output-dir': outputDir, 'target-org': targetOrg } = flags;
75103

76104
// If we don't have a spec yet, prompt for it
77-
const spec = flags['spec'] ?? (await promptForFlag(AgentGenerateAuthoringBundle.FLAGGABLE_PROMPTS['spec']));
105+
const spec = flags.spec ?? (await promptForYamlFile(AgentGenerateAuthoringBundle.FLAGGABLE_PROMPTS['spec']));
78106

79107
// If we don't have a name yet, prompt for it
80-
const name = (
81-
flags['name'] ?? (await promptForFlag(AgentGenerateAuthoringBundle.FLAGGABLE_PROMPTS['name']))
82-
).replaceAll(' ', '_');
108+
const name = flags['name'] ?? (await promptForFlag(AgentGenerateAuthoringBundle.FLAGGABLE_PROMPTS['name']));
109+
110+
// If we don't have an api name yet, prompt for it
111+
let bundleApiName = flags['api-name'];
112+
if (!bundleApiName) {
113+
bundleApiName = generateApiName(name);
114+
const promptedValue = await inquirerInput({
115+
message: messages.getMessage('flags.api-name.prompt'),
116+
validate: AgentGenerateAuthoringBundle.FLAGGABLE_PROMPTS['api-name'].validate,
117+
default: bundleApiName,
118+
theme,
119+
});
120+
if (promptedValue?.length) {
121+
bundleApiName = promptedValue;
122+
}
123+
}
83124

84125
try {
85126
// Get default output directory if not specified

src/flags.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ export function makeFlags<T extends Record<string, FlaggablePrompt>>(flaggablePr
9191
) as FlagsOfPrompts<T>;
9292
}
9393

94+
export async function getHiddenDirs(projectRoot?: string): Promise<string[]> {
95+
const rootDir = projectRoot ?? process.cwd();
96+
97+
try {
98+
const files = await readdir(rootDir, { withFileTypes: true });
99+
return files.filter((file) => file.isDirectory() && file.name.startsWith('.')).map((file) => file.name);
100+
} catch (error) {
101+
return [];
102+
}
103+
}
104+
94105
export async function traverseForFiles(dir: string, suffixes: string[], excludeDirs?: string[]): Promise<string[]> {
95106
const files = await readdir(dir, { withFileTypes: true });
96107
const results: string[] = [];
@@ -141,7 +152,8 @@ export const promptForAiEvaluationDefinitionApiName = async (
141152
};
142153

143154
export const promptForYamlFile = async (flagDef: FlaggablePrompt): Promise<string> => {
144-
const yamlFiles = await traverseForFiles(process.cwd(), ['.yml', '.yaml'], ['node_modules']);
155+
const hiddenDirs = await getHiddenDirs();
156+
const yamlFiles = await traverseForFiles(process.cwd(), ['.yml', '.yaml'], ['node_modules', ...hiddenDirs]);
145157
return autocomplete({
146158
message: flagDef.message,
147159
// eslint-disable-next-line @typescript-eslint/require-await

test/nuts/agent.generate.authoring-bundle.nut.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe.skip('agent generate authoring-bundle NUTs', () => {
5656
execCmd(specCommand, { ensureExitCode: 0 });
5757

5858
// Now generate the authoring bundle
59-
const command = `agent generate authoring-bundle --spec ${specPath} --name ${bundleName} --target-org ${username} --json`;
59+
const command = `agent generate authoring-bundle --spec ${specPath} --name ${bundleName} --api-name ${bundleName} --target-org ${username} --json`;
6060
const result = execCmd<AgentGenerateAuthoringBundleResult>(command, { ensureExitCode: 0 }).jsonOutput?.result;
6161

6262
expect(result).to.be.ok;

0 commit comments

Comments
 (0)