Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion packages/b2c-cli/.mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"reporter": "spec",
"timeout": 60000,
"node-option": [
"import=tsx"
"import=tsx",
"conditions=development"
]
}
29 changes: 27 additions & 2 deletions packages/b2c-cli/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import headerPlugin from 'eslint-plugin-header';
import path from 'node:path';
import {fileURLToPath} from 'node:url';

import {copyrightHeader, sharedRules, oclifRules, prettierPlugin} from '../../eslint.config.mjs';
import {copyrightHeader, sharedRules, oclifRules, chaiTestRules, prettierPlugin} from '../../eslint.config.mjs';

const gitignorePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '.gitignore');
headerPlugin.rules.header.meta.schema = false;
Expand All @@ -19,7 +19,7 @@ export default [
// node_modules must be explicitly ignored because the .gitignore pattern only covers
// packages/b2c-cli/node_modules, not the monorepo root node_modules
{
ignores: ['**/node_modules/**', 'test/functional/fixtures/**/*.js'],
ignores: ['**/node_modules/**', 'test/functional/fixtures/**/*.js', '**/node_modules/marked-terminal/**'],
},
includeIgnoreFile(gitignorePath),
...oclif,
Expand All @@ -39,4 +39,29 @@ export default [
...oclifRules,
},
},
{
files: ['test/**/*.ts'],
rules: {
...chaiTestRules,
// Tests use stubbing patterns that intentionally return undefined
'unicorn/no-useless-undefined': 'off',
// Some tests use void 0 to satisfy TS stub typings; allow it in tests
'no-void': 'off',
// Command tests frequently use `any` to avoid over-typing oclif command internals
'@typescript-eslint/no-explicit-any': 'off',
// Helper functions in tests are commonly declared within suites for clarity
'unicorn/consistent-function-scoping': 'off',
// Sinon default import is intentional and idiomatic in tests
'import/no-named-as-default-member': 'off',
// import/namespace behaves inconsistently across environments when parsing CJS modules like marked-terminal
'import/namespace': 'off',
},
},
{
files: ['src/commands/docs/**/*.ts'],
rules: {
// marked-terminal is CJS and breaks import/namespace static analysis
'import/namespace': 'off',
},
},
];
1 change: 1 addition & 0 deletions packages/b2c-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"oclif": "^4",
"prettier": "^3.6.2",
"shx": "^0.3.3",
"sinon": "^21.0.1",
"tsx": "^4.20.6",
"typescript": "^5"
},
Expand Down
9 changes: 7 additions & 2 deletions packages/b2c-cli/src/commands/code/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export default class CodeDelete extends InstanceCommand<typeof CodeDelete> {
}),
};

protected operations = {
confirm,
deleteCodeVersion: async (codeVersion: string) => deleteCodeVersion(this.instance, codeVersion),
};

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

Expand All @@ -59,7 +64,7 @@ export default class CodeDelete extends InstanceCommand<typeof CodeDelete> {

// Confirm deletion unless --force is used
if (!this.flags.force) {
const confirmed = await confirm(
const confirmed = await this.operations.confirm(
t(
'commands.code.delete.confirm',
'Are you sure you want to delete code version "{{codeVersion}}" on {{hostname}}? (y/n)',
Expand All @@ -80,7 +85,7 @@ export default class CodeDelete extends InstanceCommand<typeof CodeDelete> {
}),
);

await deleteCodeVersion(this.instance, codeVersion);
await this.operations.deleteCodeVersion(codeVersion);
this.log(t('commands.code.delete.deleted', 'Code version {{codeVersion}} deleted successfully', {codeVersion}));
}
}
17 changes: 13 additions & 4 deletions packages/b2c-cli/src/commands/code/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
}),
};

protected operations = {
uploadCartridges: async (cartridges: Parameters<typeof uploadCartridges>[1]) =>
uploadCartridges(this.instance, cartridges),
deleteCartridges: async (cartridges: Parameters<typeof deleteCartridges>[1]) =>
deleteCartridges(this.instance, cartridges),
getActiveCodeVersion: async () => getActiveCodeVersion(this.instance),
reloadCodeVersion: async (codeVersion: string) => reloadCodeVersion(this.instance, codeVersion),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern of duplicating the type of the function here is brittle. We should just use the name verbatim, i.e. uploadCartridges so type inference can work. If we're using the operations pattern it should just be a simple object of the operations.

There's no need to capture this.instance here we can pass that in the code deploy.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve updated operations to use the SDK functions (no wrapper lambdas or copied parameter types) and now pass this.instance at the call sites.

};

async run(): Promise<DeployResult> {
this.requireWebDavCredentials();
this.requireOAuthCredentials();
Expand All @@ -59,7 +68,7 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
this.warn(
t('commands.code.deploy.noCodeVersion', 'No code version specified, discovering active code version...'),
);
const activeVersion = await getActiveCodeVersion(this.instance);
const activeVersion = await this.operations.getActiveCodeVersion();
if (!activeVersion?.id) {
this.error(
t('commands.code.deploy.noActiveVersion', 'No active code version found. Specify one with --code-version.'),
Expand Down Expand Up @@ -119,17 +128,17 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
try {
// Optionally delete existing cartridges first
if (this.flags.delete) {
await deleteCartridges(this.instance, cartridges);
await this.operations.deleteCartridges(cartridges);
}

// Upload cartridges
await uploadCartridges(this.instance, cartridges);
await this.operations.uploadCartridges(cartridges);

// Optionally reload code version
let reloaded = false;
if (this.flags.reload) {
try {
await reloadCodeVersion(this.instance, version);
await this.operations.reloadCodeVersion(version);
reloaded = true;
} catch (error) {
this.logger?.debug(`Could not reload code version: ${error instanceof Error ? error.message : error}`);
Expand Down
29 changes: 17 additions & 12 deletions packages/b2c-cli/src/commands/code/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ export default class CodeWatch extends CartridgeCommand<typeof CodeWatch> {
...CartridgeCommand.cartridgeFlags,
};

protected operations = {
watchCartridges: async () =>
watchCartridges(this.instance, this.cartridgePath, {
...this.cartridgeOptions,
onUpload: (files) => {
this.log(t('commands.code.watch.uploaded', '[UPLOAD] {{count}} file(s)', {count: files.length}));
},
onDelete: (files) => {
this.log(t('commands.code.watch.deleted', '[DELETE] {{count}} file(s)', {count: files.length}));
},
onError: (error) => {
this.warn(t('commands.code.watch.error', 'Error: {{message}}', {message: error.message}));
},
}),
};

async run(): Promise<void> {
this.requireWebDavCredentials();
this.requireOAuthCredentials();
Expand All @@ -41,18 +57,7 @@ export default class CodeWatch extends CartridgeCommand<typeof CodeWatch> {
}

try {
const result = await watchCartridges(this.instance, this.cartridgePath, {
...this.cartridgeOptions,
onUpload: (files) => {
this.log(t('commands.code.watch.uploaded', '[UPLOAD] {{count}} file(s)', {count: files.length}));
},
onDelete: (files) => {
this.log(t('commands.code.watch.deleted', '[DELETE] {{count}} file(s)', {count: files.length}));
},
onError: (error) => {
this.warn(t('commands.code.watch.error', 'Error: {{message}}', {message: error.message}));
},
});
const result = await this.operations.watchCartridges();

this.log(
t('commands.code.watch.watching', 'Watching {{count}} cartridge(s)...', {count: result.cartridges.length}),
Expand Down
6 changes: 5 additions & 1 deletion packages/b2c-cli/src/commands/docs/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export default class DocsDownload extends InstanceCommand<typeof DocsDownload> {
}),
};

protected operations = {
downloadDocs: async (input: Parameters<typeof downloadDocs>[1]) => downloadDocs(this.instance, input),
};

async run(): Promise<DownloadDocsResult> {
this.requireServer();
this.requireWebDavCredentials();
Expand All @@ -50,7 +54,7 @@ export default class DocsDownload extends InstanceCommand<typeof DocsDownload> {
}),
);

const result = await downloadDocs(this.instance, {
const result = await this.operations.downloadDocs({
outputDir,
keepArchive,
});
Expand Down
8 changes: 6 additions & 2 deletions packages/b2c-cli/src/commands/docs/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import {Args, Flags} from '@oclif/core';
import {marked} from 'marked';
// eslint-disable-next-line import/namespace

import {markedTerminal} from 'marked-terminal';
import {BaseCommand} from '@salesforce/b2c-tooling-sdk/cli';
import {readDocByQuery, type DocEntry} from '@salesforce/b2c-tooling-sdk/operations/docs';
Expand Down Expand Up @@ -57,11 +57,15 @@ export default class DocsRead extends BaseCommand<typeof DocsRead> {
}),
};

protected operations = {
readDocByQuery,
};

async run(): Promise<ReadDocsResult> {
const {query} = this.args;
const {raw} = this.flags;

const result = readDocByQuery(query);
const result = this.operations.readDocByQuery(query);

if (!result) {
this.error(t('commands.docs.read.notFound', 'No documentation found matching: {{query}}', {query}), {
Expand Down
9 changes: 7 additions & 2 deletions packages/b2c-cli/src/commands/docs/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,18 @@ export default class DocsSchema extends BaseCommand<typeof DocsSchema> {
}),
};

protected operations = {
listSchemas,
readSchemaByQuery,
};

async run(): Promise<ListResult | SchemaResult> {
const {query} = this.args;
const {list} = this.flags;

// List mode
if (list) {
const entries = listSchemas();
const entries = this.operations.listSchemas();

if (this.jsonEnabled()) {
return {entries};
Expand All @@ -72,7 +77,7 @@ export default class DocsSchema extends BaseCommand<typeof DocsSchema> {
this.error(t('commands.docs.schema.queryRequired', 'Schema name is required. Use --list to see all schemas.'));
}

const result = readSchemaByQuery(query);
const result = this.operations.readSchemaByQuery(query);

if (!result) {
this.error(t('commands.docs.schema.notFound', 'No schema found matching: {{query}}', {query}), {
Expand Down
9 changes: 7 additions & 2 deletions packages/b2c-cli/src/commands/docs/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,18 @@ export default class DocsSearch extends BaseCommand<typeof DocsSearch> {
}),
};

protected operations = {
listDocs,
searchDocs,
};

async run(): Promise<ListDocsResponse | SearchDocsResponse> {
const {query} = this.args;
const {limit, list} = this.flags;

// List mode
if (list) {
const entries = listDocs();
const entries = this.operations.listDocs();

if (this.jsonEnabled()) {
return {entries};
Expand Down Expand Up @@ -109,7 +114,7 @@ export default class DocsSearch extends BaseCommand<typeof DocsSearch> {
);
}

const results = searchDocs(query, limit);
const results = this.operations.searchDocs(query, limit);

const response: SearchDocsResponse = {
query,
Expand Down
12 changes: 10 additions & 2 deletions packages/b2c-cli/src/commands/job/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ export default class JobExport extends JobCommand<typeof JobExport> {
}),
};

protected operations = {
siteArchiveExportToPath: async (
dataUnits: Parameters<typeof siteArchiveExportToPath>[1],
output: Parameters<typeof siteArchiveExportToPath>[2],
options: Parameters<typeof siteArchiveExportToPath>[3],
) => siteArchiveExportToPath(this.instance, dataUnits, output, options),
};

async run(): Promise<SiteArchiveExportResult & {localPath?: string}> {
this.requireOAuthCredentials();
this.requireWebDavCredentials();
Expand All @@ -114,7 +122,7 @@ export default class JobExport extends JobCommand<typeof JobExport> {
'no-download': noDownload,
'zip-only': zipOnly,
timeout,
'show-log': showLog,
'show-log': showLog = true,
} = this.flags;

const hostname = this.resolvedConfig.values.hostname!;
Expand Down Expand Up @@ -173,7 +181,7 @@ export default class JobExport extends JobCommand<typeof JobExport> {
this.log(t('commands.job.export.dataUnits', 'Data units: {{dataUnits}}', {dataUnits: JSON.stringify(dataUnits)}));

try {
const result = await siteArchiveExportToPath(this.instance, dataUnits, output, {
const result = await this.operations.siteArchiveExportToPath(dataUnits, output, {
keepArchive: keepArchive || noDownload,
extractZip: !zipOnly,
waitOptions: {
Expand Down
11 changes: 9 additions & 2 deletions packages/b2c-cli/src/commands/job/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,19 @@ export default class JobImport extends JobCommand<typeof JobImport> {
}),
};

protected operations = {
siteArchiveImport: async (
target: Parameters<typeof siteArchiveImport>[1],
options: Parameters<typeof siteArchiveImport>[2],
) => siteArchiveImport(this.instance, target, options),
};

async run(): Promise<SiteArchiveImportResult> {
this.requireOAuthCredentials();
this.requireWebDavCredentials();

const {target} = this.args;
const {'keep-archive': keepArchive, remote, timeout, 'show-log': showLog} = this.flags;
const {'keep-archive': keepArchive, remote, timeout, 'show-log': showLog = true} = this.flags;

const hostname = this.resolvedConfig.values.hostname!;

Expand Down Expand Up @@ -107,7 +114,7 @@ export default class JobImport extends JobCommand<typeof JobImport> {
try {
const importTarget = remote ? {remoteFilename: target} : target;

const result = await siteArchiveImport(this.instance, importTarget, {
const result = await this.operations.siteArchiveImport(importTarget, {
keepArchive,
waitOptions: {
timeout: timeout ? timeout * 1000 : undefined,
Expand Down
11 changes: 9 additions & 2 deletions packages/b2c-cli/src/commands/job/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ export default class JobRun extends JobCommand<typeof JobRun> {
}),
};

protected operations = {
executeJob: async (jobId: string, options: Parameters<typeof executeJob>[2]) =>
executeJob(this.instance, jobId, options),
waitForJob: async (jobId: string, executionId: string, options: Parameters<typeof waitForJob>[3]) =>
waitForJob(this.instance, jobId, executionId, options),
};

async run(): Promise<JobExecution> {
this.requireOAuthCredentials();

Expand Down Expand Up @@ -106,7 +113,7 @@ export default class JobRun extends JobCommand<typeof JobRun> {

let execution: JobExecution;
try {
execution = await executeJob(this.instance, jobId, {
execution = await this.operations.executeJob(jobId, {
parameters: rawBody ? undefined : parameters,
body: rawBody,
waitForRunning: !noWaitRunning,
Expand Down Expand Up @@ -213,7 +220,7 @@ export default class JobRun extends JobCommand<typeof JobRun> {
this.log(t('commands.job.run.waiting', 'Waiting for job to complete...'));

try {
const execution = await waitForJob(this.instance, jobId, executionId, {
const execution = await this.operations.waitForJob(jobId, executionId, {
timeout: timeout ? timeout * 1000 : undefined,
onProgress: (exec, elapsed) => {
if (!this.jsonEnabled()) {
Expand Down
Loading