Skip to content
Merged
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
23 changes: 23 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,26 @@
- when logging use the logger instance from `@salesforce/b2c-tooling/logger` package
- CLI commands have access to this logger via `this.log` method from oclif Command class
- CLI commands can write directly to stdout/stderr if their primary purpose is to output or stream data

## Table Output

When rendering tabular data in CLI commands, use the shared `TableRenderer` utility from `@salesforce/b2c-tooling/cli`:

```typescript
import { createTable, type ColumnDef } from '@salesforce/b2c-tooling/cli';

// Define columns with header and getter function
const COLUMNS: Record<string, ColumnDef<MyDataType>> = {
id: { header: 'ID', get: (item) => item.id },
name: { header: 'Name', get: (item) => item.name },
status: { header: 'Status', get: (item) => item.status },
};

// Render the table
createTable(COLUMNS).render(data, ['id', 'name', 'status']);
```

Features:
- Dynamic column widths based on content
- Supports `extended` flag on columns for optional fields
- Use `TableRenderer` class directly for column validation helpers (e.g., `--columns` flag support)
64 changes: 27 additions & 37 deletions packages/b2c-cli/src/commands/code/list.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
import {ux} from '@oclif/core';
import cliui from 'cliui';
import {InstanceCommand} from '@salesforce/b2c-tooling/cli';
import {InstanceCommand, createTable, type ColumnDef} from '@salesforce/b2c-tooling/cli';
import {listCodeVersions, type CodeVersion, type CodeVersionResult} from '@salesforce/b2c-tooling/operations/code';
import {t} from '../../i18n/index.js';

const COLUMNS: Record<string, ColumnDef<CodeVersion>> = {
id: {
header: 'ID',
get: (v) => v.id || '-',
},
active: {
header: 'Active',
get: (v) => (v.active ? 'Yes' : 'No'),
},
rollback: {
header: 'Rollback',
get: (v) => (v.rollback ? 'Yes' : 'No'),
},
lastModified: {
header: 'Last Modified',
get: (v) => (v.last_modification_time ? new Date(v.last_modification_time).toLocaleString() : '-'),
},
cartridges: {
header: 'Cartridges',
get: (v) => String(v.cartridges?.length ?? 0),
},
};

const DEFAULT_COLUMNS = ['id', 'active', 'rollback', 'lastModified', 'cartridges'];

export default class CodeList extends InstanceCommand<typeof CodeList> {
static description = t('commands.code.list.description', 'List code versions on a B2C Commerce instance');

Expand Down Expand Up @@ -41,42 +65,8 @@ export default class CodeList extends InstanceCommand<typeof CodeList> {
return result;
}

this.printVersionsTable(versions);
createTable(COLUMNS).render(versions, DEFAULT_COLUMNS);

return result;
}

private printVersionsTable(versions: CodeVersion[]): void {
const ui = cliui({width: process.stdout.columns || 80});

// Header
ui.div(
{text: 'ID', width: 25, padding: [0, 2, 0, 0]},
{text: 'Active', width: 10, padding: [0, 2, 0, 0]},
{text: 'Rollback', width: 10, padding: [0, 2, 0, 0]},
{text: 'Last Modified', width: 25, padding: [0, 2, 0, 0]},
{text: 'Cartridges', padding: [0, 0, 0, 0]},
);

// Separator
ui.div({text: '─'.repeat(80), padding: [0, 0, 0, 0]});

// Rows
for (const version of versions) {
const lastModified = version.last_modification_time
? new Date(version.last_modification_time).toLocaleString()
: '-';
const cartridgeCount = version.cartridges?.length ?? 0;

ui.div(
{text: version.id || '', width: 25, padding: [0, 2, 0, 0]},
{text: version.active ? 'Yes' : 'No', width: 10, padding: [0, 2, 0, 0]},
{text: version.rollback ? 'Yes' : 'No', width: 10, padding: [0, 2, 0, 0]},
{text: lastModified, width: 25, padding: [0, 2, 0, 0]},
{text: String(cartridgeCount), padding: [0, 0, 0, 0]},
);
}

ux.stdout(ui.toString());
}
}
72 changes: 3 additions & 69 deletions packages/b2c-cli/src/commands/job/search.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import {Flags, ux} from '@oclif/core';
import cliui from 'cliui';
import {InstanceCommand} from '@salesforce/b2c-tooling/cli';
import {InstanceCommand, createTable, type ColumnDef} from '@salesforce/b2c-tooling/cli';
import {
searchJobExecutions,
type JobExecutionSearchResult,
type JobExecution,
} from '@salesforce/b2c-tooling/operations/jobs';
import {t} from '../../i18n/index.js';

/**
* Column definition for table output.
*/
interface ColumnDef {
header: string;
get: (e: JobExecution) => string;
}

/**
* Available columns for job execution list output.
*/
const COLUMNS: Record<string, ColumnDef> = {
const COLUMNS: Record<string, ColumnDef<JobExecution>> = {
id: {
header: 'Execution ID',
get: (e) => e.id ?? '-',
Expand Down Expand Up @@ -124,62 +112,8 @@ export default class JobSearch extends InstanceCommand<typeof JobSearch> {
}),
);

this.printExecutionsTable(results.hits);
createTable(COLUMNS).render(results.hits, DEFAULT_COLUMNS);

return results;
}

/**
* Calculate dynamic column widths based on content.
*/
private calculateColumnWidths(executions: JobExecution[], columnKeys: string[]): Map<string, number> {
const widths = new Map<string, number>();
const padding = 2;

for (const key of columnKeys) {
const col = COLUMNS[key];
let maxWidth = col.header.length;

for (const exec of executions) {
const value = col.get(exec);
maxWidth = Math.max(maxWidth, value.length);
}

widths.set(key, maxWidth + padding);
}

return widths;
}

private printExecutionsTable(executions: JobExecution[]): void {
const termWidth = process.stdout.columns || 120;
const ui = cliui({width: termWidth});
const columnKeys = DEFAULT_COLUMNS;

const widths = this.calculateColumnWidths(executions, columnKeys);

// Header
const headerCols = columnKeys.map((key) => ({
text: COLUMNS[key].header,
width: widths.get(key),
padding: [0, 1, 0, 0] as [number, number, number, number],
}));
ui.div(...headerCols);

// Separator
const totalWidth = [...widths.values()].reduce((sum, w) => sum + w, 0);
ui.div({text: '─'.repeat(Math.min(totalWidth, termWidth)), padding: [0, 0, 0, 0]});

// Rows
for (const exec of executions) {
const rowCols = columnKeys.map((key) => ({
text: COLUMNS[key].get(exec),
width: widths.get(key),
padding: [0, 1, 0, 0] as [number, number, number, number],
}));
ui.div(...rowCols);
}

ux.stdout(ui.toString());
}
}
68 changes: 68 additions & 0 deletions packages/b2c-cli/src/commands/mrt/env-var/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {Args, Flags} from '@oclif/core';
import {MrtCommand} from '@salesforce/b2c-tooling/cli';
import {deleteEnvVar} from '@salesforce/b2c-tooling/operations/mrt';
import {t} from '../../../i18n/index.js';

/**
* Delete an environment variable from an MRT project environment.
*/
export default class MrtEnvVarDelete extends MrtCommand<typeof MrtEnvVarDelete> {
static args = {
key: Args.string({
description: 'Environment variable name',
required: true,
}),
};

static description = t(
'commands.mrt.envVar.delete.description',
'Delete an environment variable from a Managed Runtime environment',
);

static enableJsonFlag = true;

static examples = [
'<%= config.bin %> <%= command.id %> MY_VAR --project acme-storefront --environment production',
'<%= config.bin %> <%= command.id %> OLD_API_KEY -p my-project -e staging',
];

static flags = {
...MrtCommand.baseFlags,
project: Flags.string({
char: 'p',
description: 'MRT project slug',
required: true,
}),
environment: Flags.string({
char: 'e',
description: 'Target environment (e.g., staging, production)',
required: true,
}),
};

async run(): Promise<{key: string; project: string; environment: string}> {
this.requireMrtCredentials();

const {key} = this.args;
const {project, environment} = this.flags;

await deleteEnvVar(
{
projectSlug: project,
environment,
key,
},
this.getMrtAuth(),
);

this.log(
t('commands.mrt.envVar.delete.success', 'Deleted {{key}} from {{project}}/{{environment}}', {
key,
project,
environment,
}),
);

return {key, project, environment};
}
}
88 changes: 88 additions & 0 deletions packages/b2c-cli/src/commands/mrt/env-var/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {Flags} from '@oclif/core';
import {MrtCommand, createTable, type ColumnDef} from '@salesforce/b2c-tooling/cli';
import {listEnvVars, type ListEnvVarsResult, type EnvironmentVariable} from '@salesforce/b2c-tooling/operations/mrt';
import {t} from '../../../i18n/index.js';

const COLUMNS: Record<string, ColumnDef<EnvironmentVariable>> = {
name: {
header: 'Name',
get: (v) => v.name,
},
value: {
header: 'Value',
get: (v) => v.value,
},
status: {
header: 'Status',
get: (v) => v.publishingStatusDescription,
},
updated: {
header: 'Updated',
get: (v) => (v.updatedAt ? new Date(v.updatedAt).toLocaleString() : '-'),
},
};

const DEFAULT_COLUMNS = ['name', 'value', 'status', 'updated'];

/**
* List environment variables on an MRT project environment.
*/
export default class MrtEnvVarList extends MrtCommand<typeof MrtEnvVarList> {
static description = t(
'commands.mrt.envVar.list.description',
'List environment variables on a Managed Runtime environment',
);

static enableJsonFlag = true;

static examples = [
'<%= config.bin %> <%= command.id %> --project acme-storefront --environment production',
'<%= config.bin %> <%= command.id %> -p my-project -e staging',
'<%= config.bin %> <%= command.id %> -p my-project -e production --json',
];

static flags = {
...MrtCommand.baseFlags,
project: Flags.string({
char: 'p',
description: 'MRT project slug',
required: true,
}),
environment: Flags.string({
char: 'e',
description: 'Target environment (e.g., staging, production)',
required: true,
}),
};

async run(): Promise<ListEnvVarsResult> {
this.requireMrtCredentials();

const {project, environment} = this.flags;

this.log(
t('commands.mrt.envVar.list.fetching', 'Listing env vars for {{project}}/{{environment}}...', {
project,
environment,
}),
);

const result = await listEnvVars(
{
projectSlug: project,
environment,
},
this.getMrtAuth(),
);

if (!this.jsonEnabled()) {
if (result.variables.length === 0) {
this.log(t('commands.mrt.envVar.list.empty', 'No environment variables found.'));
} else {
createTable(COLUMNS).render(result.variables, DEFAULT_COLUMNS);
}
}

return result;
}
}
Loading