Skip to content
Draft
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
18 changes: 18 additions & 0 deletions .changeset/wave-review-cleanup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@salesforce/b2c-tooling-sdk': patch
'@salesforce/b2c-cli': patch
'@salesforce/mrt-utilities': patch
---

Hardens long-running operations and atomic config writes:

- dw.json mutations (`addInstance`, `removeInstance`, `setActiveInstance`) now go through a per-path async serializer and write via temp-file + rename, so concurrent CLI invocations within the same process can no longer interleave reads and writes.
- The session file written by stateful auth removes an exists-then-mkdir TOCTOU and cleans up orphan tmp files on rename failure.
- Cartridge deploy/download progress intervals are wrapped in a `withProgress` helper that always tears down on exception.
- `b2c jobs run` no longer silently treats a body-read failure as "not the JobAlreadyRunning case" during 400 detection.
- MRT proxy `onError` no longer crashes when upstream begins streaming before erroring (`headersSent` guard).
- Sandbox CLI commands now route output through `this.log` so `--json` mode and test output silencing work as documented; a lint rule prevents regression.
- Shared ANSI palette consolidated in `@salesforce/b2c-tooling-sdk/cli` (now also exporting standalone `RED`/`GREEN`/`YELLOW`/`CYAN`/`MAGENTA`/`GRAY`); the script-debugger REPL and the `cap pull`/`cap tasks` commands consume it instead of redefining literal-ESC palettes.
- HTTP error paths in `code:deploy`, `code:download`, and OAuth client_credentials no longer lose the underlying status when `response.text()` itself rejects mid-body.
- MRT bundle `loadServerConfig` now surfaces real errors from `config.server.js` instead of silently falling back to defaults; scaffold registry surfaces a warning for non-ENOENT manifest read errors instead of dropping them silently.
- Six newly-added ecdn detail commands (`firewall:get/create/update`, `rate-limit:get/create/update`) use the SDK's `printFieldsBlock` helper, matching the `bm`/`am` detail commands.
11 changes: 11 additions & 0 deletions packages/b2c-cli/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,15 @@ export default [
'import/no-unresolved': 'off',
},
},
{
// Commands must route output through oclif (this.log/this.error/ux.stdout) so that
// --json mode, log redirection, and test stdout silencing work. Bare console.* breaks
// these contracts. The prophet IDE script is exempt because it serializes JS source
// that runs outside the CLI process.
files: ['src/commands/**/*.ts'],
ignores: ['src/commands/setup/ide/prophet.ts'],
rules: {
'no-console': 'error',
},
},
];
9 changes: 2 additions & 7 deletions packages/b2c-cli/src/commands/cap/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import path from 'node:path';
import {Args, Flags} from '@oclif/core';
import {JobCommand} from '@salesforce/b2c-tooling-sdk/cli';
import {JobCommand, ANSI} from '@salesforce/b2c-tooling-sdk/cli';
import {
listInstalledApps,
pullCommerceApps,
Expand Down Expand Up @@ -125,12 +125,7 @@ export default class CapPull extends JobCommand<typeof CapPull> {
});

if (!this.jsonEnabled()) {
const bold = '';
const dim = '';
const cyan = '';
const yellow = '';
const red = '';
const reset = '';
const {BOLD: bold, DIM: dim, CYAN: cyan, YELLOW: yellow, RED: red, RESET: reset} = ANSI;

for (const app of result.pulled) {
const relativePath = path.relative(process.cwd(), app.extractedPath);
Expand Down
7 changes: 2 additions & 5 deletions packages/b2c-cli/src/commands/cap/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {Args, Flags} from '@oclif/core';
import {JobCommand} from '@salesforce/b2c-tooling-sdk/cli';
import {JobCommand, ANSI} from '@salesforce/b2c-tooling-sdk/cli';
import {listInstalledApps, type CommerceFeatureState} from '@salesforce/b2c-tooling-sdk/operations/cap';
import {t, withDocs} from '../../i18n/index.js';

Expand Down Expand Up @@ -107,10 +107,7 @@ export default class CapTasks extends JobCommand<typeof CapTasks> {
);

if (!this.jsonEnabled()) {
const bold = '';
const dim = '';
const cyan = '';
const reset = '';
const {BOLD: bold, DIM: dim, CYAN: cyan, RESET: reset} = ANSI;

for (const task of tasks) {
process.stdout.write(`\n ${bold}${task.taskNumber}. ${task.name}${reset}\n`);
Expand Down
24 changes: 11 additions & 13 deletions packages/b2c-cli/src/commands/ecdn/firewall/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {Flags} from '@oclif/core';
import cliui from 'cliui';
import {printFieldsBlock} from '@salesforce/b2c-tooling-sdk/cli';
import type {CdnZonesComponents} from '@salesforce/b2c-tooling-sdk/clients';
import {EcdnZoneCommand, formatApiError} from '../../../utils/ecdn/index.js';
import {t, withDocs} from '../../../i18n/index.js';
Expand Down Expand Up @@ -125,18 +125,16 @@ export default class EcdnFirewallCreate extends EcdnZoneCommand<typeof EcdnFirew
return output;
}

const ui = cliui({width: process.stdout.columns || 80});
const labelWidth = 18;

ui.div('');
ui.div({text: t('commands.ecdn.firewall.create.success', 'Custom firewall rule created successfully!')});
ui.div('');
ui.div({text: 'Rule ID:', width: labelWidth}, {text: rule.ruleId});
ui.div({text: 'Description:', width: labelWidth}, {text: rule.description});
ui.div({text: 'Actions:', width: labelWidth}, {text: rule.actions?.join(', ') ?? '-'});
ui.div({text: 'Enabled:', width: labelWidth}, {text: rule.enabled ? 'yes' : 'no'});

this.log(ui.toString());
printFieldsBlock(
t('commands.ecdn.firewall.create.success', 'Custom firewall rule created successfully!'),
[
['Rule ID', rule.ruleId],
['Description', rule.description],
['Actions', rule.actions?.join(', ') ?? '-'],
['Enabled', rule.enabled ? 'yes' : 'no'],
],
{labelWidth: 18},
);

return output;
}
Expand Down
28 changes: 13 additions & 15 deletions packages/b2c-cli/src/commands/ecdn/firewall/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {Flags} from '@oclif/core';
import cliui from 'cliui';
import {printFieldsBlock} from '@salesforce/b2c-tooling-sdk/cli';
import type {CdnZonesComponents} from '@salesforce/b2c-tooling-sdk/clients';
import {EcdnZoneCommand, formatApiError} from '../../../utils/ecdn/index.js';
import {t, withDocs} from '../../../i18n/index.js';
Expand Down Expand Up @@ -77,20 +77,18 @@ export default class EcdnFirewallGet extends EcdnZoneCommand<typeof EcdnFirewall
return output;
}

const ui = cliui({width: process.stdout.columns || 80});
const labelWidth = 18;

ui.div('');
ui.div({text: t('commands.ecdn.firewall.get.success', 'Custom firewall rule details:')});
ui.div('');
ui.div({text: 'Rule ID:', width: labelWidth}, {text: rule.ruleId});
ui.div({text: 'Description:', width: labelWidth}, {text: rule.description});
ui.div({text: 'Expression:', width: labelWidth}, {text: rule.expression});
ui.div({text: 'Actions:', width: labelWidth}, {text: rule.actions?.join(', ') ?? '-'});
ui.div({text: 'Enabled:', width: labelWidth}, {text: rule.enabled ? 'yes' : 'no'});
ui.div({text: 'Last Updated:', width: labelWidth}, {text: rule.lastUpdated});

this.log(ui.toString());
printFieldsBlock(
t('commands.ecdn.firewall.get.success', 'Custom firewall rule details:'),
[
['Rule ID', rule.ruleId],
['Description', rule.description],
['Expression', rule.expression],
['Actions', rule.actions?.join(', ') ?? '-'],
['Enabled', rule.enabled ? 'yes' : 'no'],
['Last Updated', rule.lastUpdated],
],
{labelWidth: 18},
);

return output;
}
Expand Down
24 changes: 11 additions & 13 deletions packages/b2c-cli/src/commands/ecdn/firewall/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {Flags} from '@oclif/core';
import cliui from 'cliui';
import {printFieldsBlock} from '@salesforce/b2c-tooling-sdk/cli';
import type {CdnZonesComponents} from '@salesforce/b2c-tooling-sdk/clients';
import {EcdnZoneCommand, formatApiError} from '../../../utils/ecdn/index.js';
import {t, withDocs} from '../../../i18n/index.js';
Expand Down Expand Up @@ -129,18 +129,16 @@ export default class EcdnFirewallUpdate extends EcdnZoneCommand<typeof EcdnFirew
return output;
}

const ui = cliui({width: process.stdout.columns || 80});
const labelWidth = 18;

ui.div('');
ui.div({text: t('commands.ecdn.firewall.update.success', 'Custom firewall rule updated successfully!')});
ui.div('');
ui.div({text: 'Rule ID:', width: labelWidth}, {text: rule.ruleId});
ui.div({text: 'Description:', width: labelWidth}, {text: rule.description});
ui.div({text: 'Actions:', width: labelWidth}, {text: rule.actions?.join(', ') ?? '-'});
ui.div({text: 'Enabled:', width: labelWidth}, {text: rule.enabled ? 'yes' : 'no'});

this.log(ui.toString());
printFieldsBlock(
t('commands.ecdn.firewall.update.success', 'Custom firewall rule updated successfully!'),
[
['Rule ID', rule.ruleId],
['Description', rule.description],
['Actions', rule.actions?.join(', ') ?? '-'],
['Enabled', rule.enabled ? 'yes' : 'no'],
],
{labelWidth: 18},
);

return output;
}
Expand Down
28 changes: 13 additions & 15 deletions packages/b2c-cli/src/commands/ecdn/rate-limit/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {Flags} from '@oclif/core';
import cliui from 'cliui';
import {printFieldsBlock} from '@salesforce/b2c-tooling-sdk/cli';
import type {CdnZonesComponents} from '@salesforce/b2c-tooling-sdk/clients';
import {EcdnZoneCommand, formatApiError} from '../../../utils/ecdn/index.js';
import {t, withDocs} from '../../../i18n/index.js';
Expand Down Expand Up @@ -189,20 +189,18 @@ export default class EcdnRateLimitCreate extends EcdnZoneCommand<typeof EcdnRate
return output;
}

const ui = cliui({width: process.stdout.columns || 80});
const labelWidth = 22;

ui.div('');
ui.div({text: t('commands.ecdn.rate-limit.create.success', 'Rate limiting rule created successfully!')});
ui.div('');
ui.div({text: 'Rule ID:', width: labelWidth}, {text: rule.ruleId});
ui.div({text: 'Description:', width: labelWidth}, {text: rule.description});
ui.div({text: 'Action:', width: labelWidth}, {text: rule.action});
ui.div({text: 'Period (seconds):', width: labelWidth}, {text: String(rule.period)});
ui.div({text: 'Requests Per Period:', width: labelWidth}, {text: String(rule.requestsPerPeriod)});
ui.div({text: 'Enabled:', width: labelWidth}, {text: rule.enabled ? 'yes' : 'no'});

this.log(ui.toString());
printFieldsBlock(
t('commands.ecdn.rate-limit.create.success', 'Rate limiting rule created successfully!'),
[
['Rule ID', rule.ruleId],
['Description', rule.description],
['Action', rule.action],
['Period (seconds)', String(rule.period)],
['Requests Per Period', String(rule.requestsPerPeriod)],
['Enabled', rule.enabled ? 'yes' : 'no'],
],
{labelWidth: 22},
);

return output;
}
Expand Down
42 changes: 18 additions & 24 deletions packages/b2c-cli/src/commands/ecdn/rate-limit/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {Flags} from '@oclif/core';
import cliui from 'cliui';
import {printFieldsBlock} from '@salesforce/b2c-tooling-sdk/cli';
import type {CdnZonesComponents} from '@salesforce/b2c-tooling-sdk/clients';
import {EcdnZoneCommand, formatApiError} from '../../../utils/ecdn/index.js';
import {t, withDocs} from '../../../i18n/index.js';
Expand Down Expand Up @@ -77,29 +77,23 @@ export default class EcdnRateLimitGet extends EcdnZoneCommand<typeof EcdnRateLim
return output;
}

const ui = cliui({width: process.stdout.columns || 80});
const labelWidth = 22;

ui.div('');
ui.div({text: t('commands.ecdn.rate-limit.get.success', 'Rate limiting rule details:')});
ui.div('');
ui.div({text: 'Rule ID:', width: labelWidth}, {text: rule.ruleId});
ui.div({text: 'Description:', width: labelWidth}, {text: rule.description});
ui.div({text: 'Expression:', width: labelWidth}, {text: rule.expression});
ui.div({text: 'Action:', width: labelWidth}, {text: rule.action});
ui.div({text: 'Period (seconds):', width: labelWidth}, {text: String(rule.period)});
ui.div({text: 'Requests Per Period:', width: labelWidth}, {text: String(rule.requestsPerPeriod)});
ui.div({text: 'Mitigation Timeout:', width: labelWidth}, {text: String(rule.mitigationTimeout)});
ui.div({text: 'Enabled:', width: labelWidth}, {text: rule.enabled ? 'yes' : 'no'});
ui.div({text: 'Characteristics:', width: labelWidth}, {text: rule.characteristics.join(', ')});

if (rule.countingExpression) {
ui.div({text: 'Counting Expression:', width: labelWidth}, {text: rule.countingExpression});
}

ui.div({text: 'Last Updated:', width: labelWidth}, {text: rule.lastUpdated});

this.log(ui.toString());
printFieldsBlock(
t('commands.ecdn.rate-limit.get.success', 'Rate limiting rule details:'),
[
['Rule ID', rule.ruleId],
['Description', rule.description],
['Expression', rule.expression],
['Action', rule.action],
['Period (seconds)', String(rule.period)],
['Requests Per Period', String(rule.requestsPerPeriod)],
['Mitigation Timeout', String(rule.mitigationTimeout)],
['Enabled', rule.enabled ? 'yes' : 'no'],
['Characteristics', rule.characteristics.join(', ')],
['Counting Expression', rule.countingExpression ?? null],
['Last Updated', rule.lastUpdated],
],
{labelWidth: 22},
);

return output;
}
Expand Down
28 changes: 13 additions & 15 deletions packages/b2c-cli/src/commands/ecdn/rate-limit/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
import {Flags} from '@oclif/core';
import cliui from 'cliui';
import {printFieldsBlock} from '@salesforce/b2c-tooling-sdk/cli';
import type {CdnZonesComponents} from '@salesforce/b2c-tooling-sdk/clients';
import {EcdnZoneCommand, formatApiError} from '../../../utils/ecdn/index.js';
import {t, withDocs} from '../../../i18n/index.js';
Expand Down Expand Up @@ -80,7 +80,7 @@
}),
};

async run(): Promise<UpdateOutput> {

Check warning on line 83 in packages/b2c-cli/src/commands/ecdn/rate-limit/update.ts

View workflow job for this annotation

GitHub Actions / test-windows (24.15.0)

Async method 'run' has a complexity of 24. Maximum allowed is 20

Check warning on line 83 in packages/b2c-cli/src/commands/ecdn/rate-limit/update.ts

View workflow job for this annotation

GitHub Actions / test (24.x)

Async method 'run' has a complexity of 24. Maximum allowed is 20

Check warning on line 83 in packages/b2c-cli/src/commands/ecdn/rate-limit/update.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

Async method 'run' has a complexity of 24. Maximum allowed is 20

Check warning on line 83 in packages/b2c-cli/src/commands/ecdn/rate-limit/update.ts

View workflow job for this annotation

GitHub Actions / test-windows (22.x)

Async method 'run' has a complexity of 24. Maximum allowed is 20
this.requireOAuthCredentials();

const zoneId = await this.resolveZoneId();
Expand Down Expand Up @@ -230,20 +230,18 @@
return output;
}

const ui = cliui({width: process.stdout.columns || 80});
const labelWidth = 22;

ui.div('');
ui.div({text: t('commands.ecdn.rate-limit.update.success', 'Rate limiting rule updated successfully!')});
ui.div('');
ui.div({text: 'Rule ID:', width: labelWidth}, {text: rule.ruleId});
ui.div({text: 'Description:', width: labelWidth}, {text: rule.description});
ui.div({text: 'Action:', width: labelWidth}, {text: rule.action});
ui.div({text: 'Period (seconds):', width: labelWidth}, {text: String(rule.period)});
ui.div({text: 'Requests Per Period:', width: labelWidth}, {text: String(rule.requestsPerPeriod)});
ui.div({text: 'Enabled:', width: labelWidth}, {text: rule.enabled ? 'yes' : 'no'});

this.log(ui.toString());
printFieldsBlock(
t('commands.ecdn.rate-limit.update.success', 'Rate limiting rule updated successfully!'),
[
['Rule ID', rule.ruleId],
['Description', rule.description],
['Action', rule.action],
['Period (seconds)', String(rule.period)],
['Requests Per Period', String(rule.requestsPerPeriod)],
['Enabled', rule.enabled ? 'yes' : 'no'],
],
{labelWidth: 22},
);

return output;
}
Expand Down
16 changes: 8 additions & 8 deletions packages/b2c-cli/src/commands/sandbox/ips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,24 +84,24 @@ export default class SandboxIps extends OdsCommand<typeof SandboxIps> {
private printIps(system: SystemInfoSpec, realm?: string): void {
const context = realm ? t('commands.sandbox.ips.realmLabel', ' (for realm {{realm}})', {realm}) : '';

console.log(t('commands.sandbox.ips.inboundHeader', 'Inbound IP addresses{{context}}:', {context}));
this.log(t('commands.sandbox.ips.inboundHeader', 'Inbound IP addresses{{context}}:', {context}));
for (const ip of system.inboundIps ?? []) {
console.log(` - ${ip}`);
this.log(` - ${ip}`);
}

console.log();
this.log('');

console.log(t('commands.sandbox.ips.outboundHeader', 'Outbound IP addresses{{context}}:', {context}));
this.log(t('commands.sandbox.ips.outboundHeader', 'Outbound IP addresses{{context}}:', {context}));
for (const ip of system.outboundIps ?? []) {
console.log(` - ${ip}`);
this.log(` - ${ip}`);
}

if (system.sandboxIps && system.sandboxIps.length > 0) {
console.log();
this.log('');

console.log(t('commands.sandbox.ips.sandboxHeader', 'Sandbox IP addresses{{context}}:', {context}));
this.log(t('commands.sandbox.ips.sandboxHeader', 'Sandbox IP addresses{{context}}:', {context}));
for (const ip of system.sandboxIps) {
console.log(` - ${ip}`);
this.log(` - ${ip}`);
}
}
}
Expand Down
Loading
Loading