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
28,933 changes: 28,933 additions & 0 deletions data-api.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions data.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dw.json
3 changes: 2 additions & 1 deletion packages/b2c-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"@oclif/core": "^4",
"@oclif/plugin-help": "^6",
"@oclif/plugin-plugins": "^5",
"@salesforce/b2c-tooling": "workspace:*"
"@salesforce/b2c-tooling": "workspace:*",
"cliui": "^9.0.1"
},
"devDependencies": {
"@eslint/compat": "^1",
Expand Down
4 changes: 1 addition & 3 deletions packages/b2c-cli/src/commands/code/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ export default class Deploy extends InstanceCommand<typeof Deploy> {
];

async run(): Promise<void> {
this.requireServer();
this.requireCodeVersion();
this.requireWebDavCredentials();

const instance = this.createWebDavInstance();
const path = this.args.cartridgePath;
const hostname = this.resolvedConfig.hostname!;
const version = this.resolvedConfig.codeVersion!;
Expand All @@ -34,7 +32,7 @@ export default class Deploy extends InstanceCommand<typeof Deploy> {
this.log(t('commands.code.deploy.codeVersion', 'Code Version: {{version}}', {version}));

try {
await uploadCartridges(instance, path);
await uploadCartridges(this.instance, path);
this.log(t('commands.code.deploy.complete', 'Deployment complete'));
} catch (error) {
if (error instanceof Error) {
Expand Down
112 changes: 61 additions & 51 deletions packages/b2c-cli/src/commands/sites/list.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,82 @@
import {ux} from '@oclif/core';
import cliui from 'cliui';
import {InstanceCommand} from '@salesforce/b2c-tooling/cli';
import type {OcapiComponents} from '@salesforce/b2c-tooling';
import {t} from '../../i18n/index.js';

interface SitesResponse {
_v: string;
count: number;
data: Array<{
id: string;
display_name?: {default?: string};
status?: string;
}>;
total: number;
}
type Sites = OcapiComponents['schemas']['sites'];
type Site = OcapiComponents['schemas']['site'];

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

static enableJsonFlag = true;

static examples = [
'<%= config.bin %> <%= command.id %>',
'<%= config.bin %> <%= command.id %> --server my-sandbox.demandware.net',
'<%= config.bin %> <%= command.id %> --json',
];

async run(): Promise<void> {
this.requireServer();
async run(): Promise<Sites> {
this.requireOAuthCredentials();

const instance = this.createApiInstance();
const hostname = this.resolvedConfig.hostname!;

this.log(t('commands.sites.list.fetching', 'Fetching sites from {{hostname}}...', {hostname}));

try {
const response = await instance.ocapiDataRequest('sites?select=(**)');

if (!response.ok) {
const errorText = await response.text();
this.error(
t('commands.sites.list.fetchFailed', 'Failed to fetch sites: {{status}} {{statusText}}\n{{error}}', {
status: response.status,
statusText: response.statusText,
error: errorText,
}),
);
}

const data = (await response.json()) as SitesResponse;

if (data.count === 0) {
this.log(t('commands.sites.list.noSites', 'No sites found.'));
return;
}

this.log('');
this.log(t('commands.sites.list.foundSites', 'Found {{count}} site(s):', {count: data.count}));
this.log('');

for (const site of data.data) {
const displayName = site.display_name?.default || site.id;
const status = site.status || 'unknown';
this.log(` ${site.id}`);
this.log(` ${t('commands.sites.list.displayName', 'Display Name: {{name}}', {name: displayName})}`);
this.log(` ${t('commands.sites.list.status', 'Status: {{status}}', {status})}`);
this.log('');
}
} catch (error) {
if (error instanceof Error) {
this.error(t('commands.sites.list.error', 'Failed to fetch sites: {{message}}', {message: error.message}));
}
throw error;
// eslint-disable-next-line new-cap
const {data, error} = await this.instance.ocapi.GET('/sites', {
params: {query: {select: '(**)'}},
});

if (error) {
this.error(t('commands.sites.list.error', 'Failed to fetch sites: {{message}}', {message: String(error)}));
}

const sites = data as Sites;

// In JSON mode, just return the data - oclif handles output to stdout
if (this.jsonEnabled()) {
return sites;
}

// Human-readable table output to stdout
if (!sites || sites.count === 0) {
ux.stdout(t('commands.sites.list.noSites', 'No sites found.'));
return sites;
}

this.printSitesTable(sites.data ?? []);

return sites;
}

private printSitesTable(sites: Site[]): void {
const ui = cliui({width: process.stdout.columns || 80});

// Header
ui.div(
{text: 'ID', width: 30, padding: [0, 2, 0, 0]},
{text: 'Display Name', width: 30, padding: [0, 2, 0, 0]},
{text: 'Status', padding: [0, 0, 0, 0]},
);

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

// Rows
for (const site of sites) {
const displayName = site.display_name?.default || site.id || '';
const status = site.storefront_status || 'unknown';

ui.div(
{text: site.id || '', width: 30, padding: [0, 2, 0, 0]},
{text: displayName, width: 30, padding: [0, 2, 0, 0]},
{text: status, padding: [0, 0, 0, 0]},
);
}

ux.stdout(ui.toString());
}
}
24 changes: 24 additions & 0 deletions packages/b2c-cli/src/types/cliui.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
declare module 'cliui' {
interface Column {
text: string;
width?: number;
align?: 'center' | 'left' | 'right';
padding?: [number, number, number, number];
border?: boolean;
}

interface UIOptions {
width?: number;
wrap?: boolean;
}

interface UI {
div(...columns: (Column | string)[]): void;
span(...columns: (Column | string)[]): void;
resetOutput(): void;
toString(): string;
}

function cliui(options?: UIOptions): UI;
export default cliui;
}
1 change: 1 addition & 0 deletions packages/b2c-tooling/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.generated.ts
3 changes: 3 additions & 0 deletions packages/b2c-tooling/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const gitignorePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)),

export default [
includeIgnoreFile(gitignorePath),
{
ignores: ['**/*.generated.ts'],
},
...tseslint.configs.recommended,
prettierPlugin,
{
Expand Down
8 changes: 6 additions & 2 deletions packages/b2c-tooling/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,12 @@
"module": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts",
"files": [
"dist"
"dist",
"specs"
],
"scripts": {
"build": "pnpm run build:esm && pnpm run build:cjs",
"generate:types": "openapi-typescript specs/data-api.json -o src/clients/ocapi.generated.ts",
"build": "pnpm run generate:types && pnpm run build:esm && pnpm run build:cjs",
"build:esm": "tsc -p tsconfig.esm.json",
"build:cjs": "tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
"clean": "shx rm -rf dist",
Expand All @@ -139,6 +141,7 @@
"eslint": "^9",
"eslint-config-prettier": "^10",
"eslint-plugin-prettier": "^5.5.4",
"openapi-typescript": "^7.10.1",
"prettier": "^3.6.2",
"shx": "^0.3.3",
"typescript": "^5",
Expand All @@ -157,6 +160,7 @@
},
"dependencies": {
"i18next": "^25.6.3",
"openapi-fetch": "^0.15.0",
"pino": "^10.1.0",
"pino-pretty": "^13.1.2"
}
Expand Down
Loading