From bcf427c5380119ef1fd248b8eb7d6b2c1cbb2ac0 Mon Sep 17 00:00:00 2001 From: PIERLUIGI VITI Date: Tue, 6 Oct 2026 18:11:30 +0200 Subject: [PATCH 1/2] feat(core): a base command shared by the plugins (CLCommand) What the plugins that call the Core API repeated in their own base command, in one place: - the organization, domain and access token flags the CLI fills in from the current application (organizationFlag, domainFlag, accessTokenFlag for the plugins that change them); - the plugin update check, skipped with --blind, --silent or --quiet; - the application kind check (static applicationKinds); - quitting an interactive prompt; - clientOptions(), the options of the SDK client: cli-core stays independent of the SDK, the plugin creates the client; - handleApiError(): the 401 (login suggestion), 404 (missing resource) and other API errors. @oclif/core becomes a peer dependency, so the base command extends the plugin's own Command class. BREAKING CHANGE: @oclif/core is a peer dependency of cli-core. Co-Authored-By: Claude Opus 5.5 --- packages/core/package.json | 5 +- packages/core/src/base.ts | 168 +++++++++++++++++++++++++++ packages/core/src/index.ts | 3 +- packages/core/test/unit/base.test.ts | 86 ++++++++++++++ pnpm-lock.yaml | 6 +- 5 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 packages/core/src/base.ts create mode 100644 packages/core/test/unit/base.test.ts diff --git a/packages/core/package.json b/packages/core/package.json index 277cd21ce..620f7b94e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,6 +35,7 @@ }, "devDependencies": { "@commercelayer/cli-test-utils": "workspace:*", + "@oclif/core": "catalog:", "@types/chai": "catalog:", "@types/jsonwebtoken": "^9.0.10", "@types/mocha": "catalog:", @@ -51,11 +52,13 @@ }, "dependencies": { "@commercelayer/js-auth": "catalog:", - "@oclif/core": "catalog:", "chalk": "catalog:", "jsonwebtoken": "^9.0.3", "update-notifier-cjs": "catalog:" }, + "peerDependencies": { + "@oclif/core": "^5.1.2" + }, "publishConfig": { "access": "public" }, diff --git a/packages/core/src/base.ts b/packages/core/src/base.ts new file mode 100644 index 000000000..a735bdb13 --- /dev/null +++ b/packages/core/src/base.ts @@ -0,0 +1,168 @@ +/** + * The base command of the CLI plugins that call the Core API: the flags the + * CLI fills in from the current application, the plugin update check, the + * application kind check, the SDK client options and the common API errors. + * + * Plugins extend it with their own base command; whatever is specific to a + * plugin stays there. It doesn't depend on the SDK: the plugin creates the + * client from clientOptions(). + */ +import { Command, Flags, type Interfaces } from '@oclif/core' +import * as clColor from './color' +import * as clOutput from './output' +import * as clToken from './token' +import * as clUpdate from './update' +import * as clUtil from './util' + + +type CommandError = Interfaces.CommandError + +/** What a plugin may change of the base flags */ +export type BaseFlagOptions = { + char?: Interfaces.AlphabetLowercase | Interfaces.AlphabetUppercase + description?: string + hidden?: boolean + dependsOn?: string[] +} + + +/** The organization slug, filled in by the CLI from the current application (or CL_CLI_ORGANIZATION) */ +export const organizationFlag = (options: BaseFlagOptions = {}) => Flags.string({ + char: 'o', + description: 'the slug of your organization', + required: true, + env: 'CL_CLI_ORGANIZATION', + hidden: true, + ...options, +}) + +/** The API domain, filled in by the CLI from the current application (or CL_CLI_DOMAIN) */ +export const domainFlag = (options: BaseFlagOptions = {}) => Flags.string({ + char: 'd', + required: false, + hidden: true, + dependsOn: ['organization'], + env: 'CL_CLI_DOMAIN', + ...options, +}) + +/** The access token, filled in by the CLI from the current application (or CL_CLI_ACCESS_TOKEN) */ +export const accessTokenFlag = (options: BaseFlagOptions = {}) => Flags.string({ + hidden: true, + required: true, + env: 'CL_CLI_ACCESS_TOKEN', + ...options, +}) + + +/** The options of a Core API client (SDK), from the command flags */ +export type ClientOptions = { + organization: string + domain?: string + accessToken: string + userAgent: string +} + +/** What handleApiError needs of an SDK ApiError */ +export type ApiErrorLike = { + status?: number + errors?: unknown + first: () => { title?: string; detail?: string } | undefined +} + +export type ApiErrorOptions = { + /** The resource type in the messages (e.g. 'tag'): the 404 message names it, the 401 suggestion uses its plural */ + resource?: string + /** The ID of the resource the request was about, for the 404 message */ + id?: string + /** How the ID is called in the 404 message (default: 'id') */ + idLabel?: string + /** The command flags, for the output format of the other errors */ + flags?: any +} + + +const QUIET_FLAGS = ['--blind', '--silent', '--quiet'] + + +export abstract class CLCommand extends Command { + + static baseFlags = { + organization: organizationFlag(), + domain: domainFlag(), + accessToken: accessTokenFlag(), + } + + /** The application kinds the commands accept, checked on the access token before they run (all when undefined) */ + static applicationKinds?: readonly string[] + + + async init(): Promise { + + // Check for plugin updates, only in visible mode + const pjson = this.ctor.plugin?.pjson + if (pjson && !this.argv.some(a => QUIET_FLAGS.includes(a))) clUpdate.checkUpdate(pjson as unknown as clUpdate.Package) + + // Application kind check, on the access token the CLI passes + const kinds = (this.ctor as typeof CLCommand).applicationKinds + const atFlag = this.argv.find(a => a.startsWith('--accessToken=')) + if (kinds && atFlag) this.checkApplication(atFlag.substring(atFlag.indexOf('=') + 1), kinds) + + return await super.init() + + } + + + async catch(error: CommandError): Promise { + // The user quit an interactive prompt + if (error.message?.includes('quit')) this.exit() + else return await super.catch(error) + } + + + /** Checks that the access token belongs to an application of one of the given kinds */ + protected checkApplication(accessToken: string, kinds: readonly string[]): boolean { + + let info: clToken.AccessTokenInfo + try { + info = clToken.decodeAccessToken(accessToken) + } catch { + this.error('Invalid access token provided') + } + + if (!kinds.includes(info.application.kind)) + this.error(`Invalid application kind: ${clColor.msg.error(info.application.kind)}. Application kind must be one of the following: ${clColor.cyanBright(kinds.join(', '))}`) + + return true + + } + + + /** The options of the Core API client: organization, domain and access token from the flags, the CLI user agent */ + protected clientOptions(flags: { organization?: string; domain?: string; accessToken?: string }): ClientOptions { + return { + organization: flags.organization || '', + domain: flags.domain, + accessToken: flags.accessToken || '', + userAgent: clUtil.userAgent(this.config), + } + } + + + /** + * Stops with the message of a Core API error: an unauthorized request + * suggests to log in, a missing resource names it, any other error is + * printed as the API returned it. + */ + protected handleApiError(error: ApiErrorLike, { resource, id, idLabel = 'id', flags }: ApiErrorOptions = {}): never { + if (error.status === 401) { + const err = error.first() + this.error(clColor.msg.error(`${err?.title}: ${err?.detail}`), { + suggestions: [`Execute login to get access to the organization's ${resource ? `${resource}s` : 'resources'}`], + }) + } + if ((error.status === 404) && resource) this.error(`Unable to find ${resource}${id ? ` with ${idLabel} ${clColor.msg.error(id)}` : ''}`) + this.error(clOutput.formatError(error, flags)) + } + +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 81c1672f8..5ff89b433 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,8 @@ export * as clApi from './api' - export * as clApplication from './application' +export type { ApiErrorLike, ApiErrorOptions, BaseFlagOptions, ClientOptions } from './base' +export { accessTokenFlag, CLCommand, domainFlag, organizationFlag } from './base' export * as clCommand from './command' export { default as clConfig } from './config' diff --git a/packages/core/test/unit/base.test.ts b/packages/core/test/unit/base.test.ts new file mode 100644 index 000000000..3f4ed6d44 --- /dev/null +++ b/packages/core/test/unit/base.test.ts @@ -0,0 +1,86 @@ +import { accessToken } from '@commercelayer/cli-test-utils' +import { expect } from 'chai' +import { CLCommand } from '../../src/base' + +class TestCommand extends CLCommand { + static applicationKinds = ['integration'] + async run(): Promise {} +} + +const config = { name: '@commercelayer/cli-plugin-test', version: '1.2.3' } as any +const command = () => new TestCommand([], config) as any + +/** The message of the error a call stops with */ +const stopsWith = (fn: () => unknown): string => { + try { + fn() + } catch (error: any) { + return error.message + } + throw new Error('expected an error') +} + +const apiError = (status: number, errors = [{ title: 'Unauthorized', detail: 'Invalid token' }]) => ({ status, errors, first: () => errors[0] }) + +describe('CLCommand', () => { + it('has the flags the CLI fills in from the current application', () => { + const { organization, domain, accessToken } = CLCommand.baseFlags + expect(organization).to.include({ char: 'o', required: true, hidden: true, env: 'CL_CLI_ORGANIZATION' }) + expect(domain).to.include({ char: 'd', required: false, hidden: true, env: 'CL_CLI_DOMAIN' }) + expect(accessToken).to.include({ required: true, hidden: true, env: 'CL_CLI_ACCESS_TOKEN' }) + }) + + it('builds the client options from the flags, with the CLI user agent', () => { + const options = command().clientOptions({ organization: 'org', domain: 'commercelayer.co', accessToken: 'token' }) + expect(options).to.deep.equal({ organization: 'org', domain: 'commercelayer.co', accessToken: 'token', userAgent: 'CLI-test/1.2.3' }) + }) + + describe('checkApplication', () => { + it('accepts a token of the given kinds', () => { + expect(command().checkApplication(accessToken({ kind: 'integration' }), ['integration'])).to.equal(true) + }) + it('refuses a token of another kind', () => { + expect(stopsWith(() => command().checkApplication(accessToken({ kind: 'sales_channel' }), ['integration']))).to.match( + /Invalid application kind: .*sales_channel.*integration/, + ) + }) + it('refuses an invalid token', () => { + expect(stopsWith(() => command().checkApplication('not-a-token', ['integration']))).to.equal('Invalid access token provided') + }) + }) + + describe('init', () => { + it('checks the application kind of the access token passed by the CLI', async () => { + const cmd = new TestCommand(['--accessToken=' + accessToken({ kind: 'sales_channel' }), '--blind'], config) as any + const message = await cmd.init().then( + () => '', + (error: Error) => error.message, + ) + expect(message).to.match(/Invalid application kind/) + }) + }) + + describe('handleApiError', () => { + it('suggests to log in on an unauthorized request', () => { + try { + command().handleApiError(apiError(401), { resource: 'tag' }) + } catch (error: any) { + expect(error.message).to.contain('Unauthorized: Invalid token') + expect(error.suggestions).to.deep.equal(["Execute login to get access to the organization's tags"]) + return + } + throw new Error('expected an error') + }) + it('names a missing resource', () => { + expect(stopsWith(() => command().handleApiError(apiError(404), { resource: 'tag', id: 'abc', idLabel: 'ID or name' }))).to.match( + /^Unable to find tag with ID or name .*abc/, + ) + expect(stopsWith(() => command().handleApiError(apiError(404), { resource: 'webhook' }))).to.equal('Unable to find webhook') + }) + it('prints any other error as the API returned it', () => { + const errors = [{ title: 'Unprocessable', detail: 'name is invalid' }] + expect(stopsWith(() => command().handleApiError(apiError(422, errors)))).to.contain('name is invalid') + expect(stopsWith(() => command().handleApiError(apiError(404, errors)))).to.contain('name is invalid') + }) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2c47e13f..9de344c3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -386,9 +386,6 @@ importers: '@commercelayer/js-auth': specifier: 'catalog:' version: 8.0.1 - '@oclif/core': - specifier: 'catalog:' - version: 5.1.2 chalk: specifier: 'catalog:' version: 6.0.1 @@ -402,6 +399,9 @@ importers: '@commercelayer/cli-test-utils': specifier: workspace:* version: link:../test-utils + '@oclif/core': + specifier: 'catalog:' + version: 5.1.2 '@types/chai': specifier: 'catalog:' version: 5.2.3 From 4bcbc2369e1a1c603857a600bd2a29e6b6cd6de2 Mon Sep 17 00:00:00 2001 From: PIERLUIGI VITI Date: Fri, 2 Oct 2026 16:16:24 +0200 Subject: [PATCH 2/2] refactor: the plugins extend CLCommand checkout, cleanups, exports, imports, links, metrics, microstore, orders, resources, tags, triggers and webhooks extend the base command of cli-core instead of repeating its flags, init, application check, client options and API errors; what is specific to a plugin stays in its base command. The command surfaces are unchanged (oclif manifests compared before and after). Also: - the update check is skipped with --blind, --silent or --quiet in all of them, not only in some; - cleanups: the API errors named exports instead of cleanups; - resources:count tested a --doc flag it doesn't have. Co-Authored-By: Claude Opus 5.5 --- plugins/checkout/src/base.ts | 68 ++---------- plugins/cleanups/src/base.ts | 85 ++------------- plugins/exports/src/base.ts | 98 ++--------------- plugins/imports/src/base.ts | 92 ++-------------- plugins/links/src/base.ts | 66 ++---------- plugins/metrics/src/base.ts | 50 ++------- plugins/microstore/src/base.ts | 63 ++--------- plugins/orders/src/base.ts | 51 ++------- plugins/resources/src/base.ts | 68 ++---------- .../resources/src/commands/resources/count.ts | 2 +- plugins/tags/src/base.ts | 100 ++---------------- plugins/triggers/src/base.ts | 37 +------ plugins/webhooks/src/base.ts | 68 ++---------- 13 files changed, 99 insertions(+), 749 deletions(-) diff --git a/plugins/checkout/src/base.ts b/plugins/checkout/src/base.ts index 3c0e55840..d0dcd4cb5 100644 --- a/plugins/checkout/src/base.ts +++ b/plugins/checkout/src/base.ts @@ -1,58 +1,27 @@ -import { clColor, clOutput, clToken, clUpdate, clUtil } from '@commercelayer/cli-core' +import { accessTokenFlag, CLCommand, clColor, clToken } from '@commercelayer/cli-core' import commercelayer, { type CommerceLayerClient, CommerceLayerStatic } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, Flags } from '@oclif/core' +import { Args, Flags } from '@oclif/core' type CommandError = Interfaces.CommandError -const pkg: clUpdate.Package = require('../package.json') - - const REQUIRED_APP_KIND = 'sales_channel' -export default abstract class extends Command { +export default abstract class extends CLCommand { static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN' - }), - accessToken: Flags.string({ + ...CLCommand.baseFlags, + accessToken: accessTokenFlag({ char: 'a', description: 'custom access token to use instead of the one used for login', hidden: false, - required: true, - env: 'CL_CLI_ACCESS_TOKEN', - dependsOn: ['organization'] + dependsOn: ['organization'], }), open: Flags.boolean({ description: 'open checkout URL in default browser' }) - /*, - link: Flags.boolean({ - char: 'l', - description: 'generate short link' - }) - */ - } - - - // INIT (override) - async init(): Promise { - clUpdate.checkUpdate(pkg) - return super.init() } @@ -62,31 +31,13 @@ export default abstract class extends Command { protected async handleError(error: CommandError, flags?: any): Promise { - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.msg.error(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s resources'] }, - ) - } else this.error(clOutput.formatError(error, flags)) - } else return super.catch(error) + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { flags }) + else return super.catch(error) } protected commercelayerInit(flags: any): CommerceLayerClient { - - const organization = flags.organization - const domain = flags.domain - const accessToken = flags.accessToken - const userAgent = clUtil.userAgent(this.config) - - return commercelayer({ - organization, - domain, - accessToken, - userAgent - }) - + return commercelayer(this.clientOptions(flags)) } @@ -109,5 +60,4 @@ export default abstract class extends Command { } - export { Args, Flags } diff --git a/plugins/cleanups/src/base.ts b/plugins/cleanups/src/base.ts index 3553f273c..2f0c4db75 100644 --- a/plugins/cleanups/src/base.ts +++ b/plugins/cleanups/src/base.ts @@ -1,51 +1,21 @@ -import { clColor, clFilter, clOutput, clToken, clUpdate, clUtil, type KeyValRel, type KeyValString } from '@commercelayer/cli-core' +import { CLCommand, clColor, clFilter, type KeyValRel, type KeyValString } from '@commercelayer/cli-core' import * as cliux from '@commercelayer/cli-ux' import commercelayer, { type CommerceLayerClient, CommerceLayerStatic } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, Flags } from '@oclif/core' +import { Args, Flags } from '@oclif/core' type CommandError = Interfaces.CommandError -const pkg: clUpdate.Package = require('../package.json') - - -export default abstract class extends Command { +export default abstract class extends CLCommand { + // oclif collects the static properties of a command up to the first class + // without any (cacheCommand): without its own, the manifest would miss them static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true, - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN', - }), - accessToken: Flags.string({ - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN', - }), - } - - - - // INIT (override) - async init(): Promise { - // Check for plugin updates only if in visible mode - if (!this.argv.includes('--blind') && !this.argv.includes('--silent') && !this.argv.includes('--quiet')) clUpdate.checkUpdate(pkg) - return await super.init() + ...CLCommand.baseFlags } - - // -- CUSTOM METHODS -- // @@ -97,35 +67,8 @@ export default abstract class extends Command { } - protected checkApplication(accessToken: string, kinds: string[]): boolean { - - const info = clToken.decodeAccessToken(accessToken) - - if (info === null) this.error('Invalid access token provided') - else - if (!kinds.includes(info.application.kind)) - this.error(`Invalid application kind: ${clColor.msg.error(info.application.kind)}. Application kind must be one of the following: ${clColor.cyanBright(kinds.join(', '))}`) - - return true - - } - - protected commercelayerInit(flags: any): CommerceLayerClient { - - const organization = flags.organization - const domain = flags.domain - const accessToken = flags.accessToken - - const userAgent = clUtil.userAgent(this.config) - - return commercelayer({ - organization, - domain, - accessToken, - userAgent - }) - + return commercelayer(this.clientOptions(flags)) } @@ -142,21 +85,11 @@ export default abstract class extends Command { protected handleError(error: CommandError, flags?: any, id?: string): void { - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.msg.error(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s exports'] }, - ) - } else - if (error.status === 404) { - this.error(`Unable to find export${id ? ` with id ${clColor.msg.error(id)}` : ''}`) - } else this.error(clOutput.formatOutput(error, flags)) - } else throw error + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { resource: 'cleanup', id, flags }) + else throw error } } - export { Args, cliux, Flags } diff --git a/plugins/exports/src/base.ts b/plugins/exports/src/base.ts index 83c5dd7a0..4dc7b65ee 100644 --- a/plugins/exports/src/base.ts +++ b/plugins/exports/src/base.ts @@ -3,21 +3,18 @@ import { rename } from 'node:fs/promises' import { join } from 'node:path' import { gunzipSync, type InputType } from 'node:zlib' import type { ApiMode, KeyValRel, KeyValString } from '@commercelayer/cli-core' -import { clApi, clColor, clFilter, clOutput, clToken, clUpdate, clUtil } from '@commercelayer/cli-core' +import { CLCommand, clApi, clColor, clFilter, clOutput, clToken, clUtil } from '@commercelayer/cli-core' import * as cliux from '@commercelayer/cli-ux' import type { CommerceLayerClient, Export, ResourceTypeLock } from '@commercelayer/sdk' import commercelayer, { CommerceLayerStatic } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, Flags } from '@oclif/core' +import { Args, Flags } from '@oclif/core' import axios from 'axios' import notifier from 'node-notifier' type CommandError = Interfaces.CommandError -const pkg: clUpdate.Package = require('../package.json') - - export const encoding = 'utf-8' @@ -46,28 +43,12 @@ export const computeDelay = (): number => { } -export default abstract class BaseCommand extends Command { +export default abstract class BaseCommand extends CLCommand { + // oclif collects the static properties of a command up to the first class + // without any (cacheCommand): without its own, the manifest would miss them static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN' - }), - accessToken: Flags.string({ - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN' - }) + ...CLCommand.baseFlags } @@ -75,55 +56,10 @@ export default abstract class BaseCommand extends Command { protected cl!: CommerceLayerClient - - // INIT (override) - async init(): Promise { - // Check for plugin updates only if in visible mode - if (!this.argv.includes('--blind') && !this.argv.includes('--silent') && !this.argv.includes('--quiet')) clUpdate.checkUpdate(pkg) - return await super.init() - } - - - async catch(error: CommandError): Promise { - if (error.message?.includes('quit')) this.exit() - else return super.catch(error) - } - - - - protected checkApplication(accessToken: string, kinds: string[]): boolean { - - const info = clToken.decodeAccessToken(accessToken) - - if (info === null) this.error('Invalid access token provided') - else - if (!kinds.includes(info.application.kind)) - this.error(`Invalid application kind: ${clColor.msg.error(info.application.kind)}. Application kind must be one of the following: ${clColor.cyanBright(kinds.join(', '))}`) - - return true - - } - - protected commercelayerInit(flags: any): CommerceLayerClient { - - const organization = flags.organization - const domain = flags.domain - const accessToken: string = flags.accessToken - - const userAgent = clUtil.userAgent(this.config) - - this.environment = clToken.getTokenEnvironment(accessToken) - - this.cl = commercelayer({ - organization, - domain, - accessToken, - userAgent - }) - + this.environment = clToken.getTokenEnvironment(flags.accessToken as string) + this.cl = commercelayer(this.clientOptions(flags)) return this.cl - } @@ -139,24 +75,14 @@ export default abstract class BaseCommand extends Command { } - protected handleError(error: CommandError, _flags?: any, id?: string): void { - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.msg.error(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s exports'] }, - ) - } else - if (error.status === 404) { - this.error(`Unable to find export${id ? ` with id ${clColor.msg.error(id)}` : ''}`) - } else this.error(clOutput.formatError(error)) - } else throw error + protected handleError(error: CommandError, flags?: any, id?: string): void { + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { resource: 'export', id, flags }) + else throw error } } - export abstract class ExportCommand extends BaseCommand { static baseFlags = { @@ -275,7 +201,6 @@ export abstract class ExportCommand extends BaseCommand { } - protected includeFlag(flag: string[] | undefined, relationships?: KeyValRel, force?: boolean): string[] { const values: string[] = [] @@ -363,7 +288,6 @@ export abstract class ExportCommand extends BaseCommand { } - export { Args, Flags } export type ExportFormat = 'json' | 'csv' diff --git a/plugins/imports/src/base.ts b/plugins/imports/src/base.ts index 63f24b049..ccb70a93d 100644 --- a/plugins/imports/src/base.ts +++ b/plugins/imports/src/base.ts @@ -1,77 +1,26 @@ -import { type ApiMode, clColor, clOutput, clToken, clUpdate, clUtil } from '@commercelayer/cli-core' +import { type ApiMode, CLCommand, clColor, clToken } from '@commercelayer/cli-core' import * as cliux from '@commercelayer/cli-ux' import commercelayer, { type CommerceLayerClient, CommerceLayerStatic } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, Flags } from '@oclif/core' +import { Args, Flags } from '@oclif/core' type CommandError = Interfaces.CommandError -const pkg: clUpdate.Package = require('../package.json') - - -export default abstract class extends Command { +export default abstract class extends CLCommand { static enableJsonFlag = false - static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true, - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN', - }), - accessToken: Flags.string({ - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN', - }), - } - protected environment: ApiMode = 'test' - - - // INIT (override) - async init(): Promise { - // Check for plugin updates only if in visible mode - if (!this.argv.includes('--blind') && !this.argv.includes('--silent') && !this.argv.includes('--quiet')) clUpdate.checkUpdate(pkg) - return super.init() - } - - - async catch(error: CommandError): Promise { - if (error.message?.includes('quit')) this.exit() - else return super.catch(error) - } - - protected handleError(error: CommandError, flags?: any, id?: string): never { - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.msg.error(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s imports'] }, - ) - } else - if (error.status === 404) { - this.error(`Unable to find import${id ? ` with id ${clColor.msg.error(id)}` : ''}`) - } else this.error(clOutput.formatOutput(error, flags)) - } else throw error + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { resource: 'import', id, flags }) + else throw error } - protected importStatus(status?: string): string { if (!status) return '' switch (status.toLowerCase()) { @@ -84,36 +33,9 @@ export default abstract class extends Command { } - protected checkApplication(accessToken: string, kinds: string[]): boolean { - - const info = clToken.decodeAccessToken(accessToken) - - if (info === null) this.error('Invalid access token provided') - else - if (!kinds.includes(info.application.kind)) - this.error(`Invalid application kind: ${clColor.msg.error(info.application.kind)}. Application kind must be one of the following: ${clColor.cyanBright(kinds.join(', '))}`) - - return true - - } - - protected commercelayerInit(flags: any): CommerceLayerClient { - - const organization = flags.organization - const domain = flags.domain - const accessToken: string = flags.accessToken - const userAgent = clUtil.userAgent(this.config) - - this.environment = clToken.getTokenEnvironment(accessToken) - - return commercelayer({ - organization, - domain, - accessToken, - userAgent - }) - + this.environment = clToken.getTokenEnvironment(flags.accessToken as string) + return commercelayer(this.clientOptions(flags)) } } diff --git a/plugins/links/src/base.ts b/plugins/links/src/base.ts index 5c2739fd4..375521051 100644 --- a/plugins/links/src/base.ts +++ b/plugins/links/src/base.ts @@ -1,41 +1,20 @@ -import { clColor, clConfig, clOutput, clText, clUpdate, clUtil } from '@commercelayer/cli-core' +import { accessTokenFlag, CLCommand, clColor, clConfig, clText } from '@commercelayer/cli-core' import commercelayer, { type CommerceLayerClient, CommerceLayerStatic, type LinkCreate } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, ux as cliux, Flags } from '@oclif/core' +import { Args, ux as cliux, Flags } from '@oclif/core' import { DOC_DATE_TIME_STRING_FORMAT, fillUTCDate } from './util' type CommandError = Interfaces.CommandError - -const pkg: clUpdate.Package = require('../package.json') - - - -export abstract class BaseCommand extends Command { +export abstract class BaseCommand extends CLCommand { static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN' - }), - accessToken: Flags.string({ + ...CLCommand.baseFlags, + accessToken: accessTokenFlag({ char: 'a', description: 'custom access token to use instead of the one used for login', - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN', - dependsOn: ['organization'] + dependsOn: ['organization'], }) } @@ -43,44 +22,20 @@ export abstract class BaseCommand extends Command { protected cl!: CommerceLayerClient - // INIT (override) - async init(): Promise { - clUpdate.checkUpdate(pkg) - return await super.init() - } - - async catch(error: any): Promise { return await this.handleError(error) } protected async handleError(error: any, flags?: any): Promise { - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.msg.error(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s resources'] } - ) - } else this.error(clOutput.formatError(error, flags)) - } else return await super.catch(error as CommandError) + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { flags }) + else return await super.catch(error as CommandError) } protected commercelayerInit(flags: any): CommerceLayerClient { - - const organization = flags.organization - const domain = flags.domain - const accessToken = flags.accessToken - const userAgent = clUtil.userAgent(this.config) - - return this.cl = commercelayer({ - organization, - domain, - accessToken, - userAgent - }) - + this.cl = commercelayer(this.clientOptions(flags)) + return this.cl } @@ -286,5 +241,4 @@ export abstract class BaseIdCommand extends BaseCommand { } - export { Args, cliux, Flags } diff --git a/plugins/metrics/src/base.ts b/plugins/metrics/src/base.ts index 5744c5f6c..4d31898f5 100644 --- a/plugins/metrics/src/base.ts +++ b/plugins/metrics/src/base.ts @@ -1,69 +1,36 @@ -import { clColor, clConfig, clOutput, clToken, clUpdate } from '@commercelayer/cli-core' +import { accessTokenFlag, CLCommand, clColor, clConfig, clOutput, clToken } from '@commercelayer/cli-core' import * as cliux from '@commercelayer/cli-ux' import { CommerceLayerStatic } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, Flags } from '@oclif/core' +import { Args, Flags } from '@oclif/core' import { type MetricsFilter, type MetricsQueryBreakdown, type MetricsQueryBreakdownResponse, operators, resources } from './common' type CommandError = Interfaces.CommandError - -const pkg: clUpdate.Package = require('../package.json') - - const REQUIRED_APP_KIND = clConfig.metrics.applications -export abstract class BaseCommand extends Command { +export abstract class BaseCommand extends CLCommand { static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN' - }), - accessToken: Flags.string({ + ...CLCommand.baseFlags, + accessToken: accessTokenFlag({ char: 'a', description: 'custom access token to use instead of the one used for login', - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN', - dependsOn: ['organization'] + dependsOn: ['organization'], }) } - // INIT (override) - async init(): Promise { - clUpdate.checkUpdate(pkg) - return await super.init() - } - - async catch(error: any): Promise { return await this.handleError(error) } protected async handleError(error: any, flags?: any): Promise { - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.msg.error(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s resources'] } - ) - } else this.error(clOutput.formatError(error, flags)) - } else return await super.catch(error as CommandError) + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { flags }) + else return await super.catch(error as CommandError) } /* @@ -205,7 +172,6 @@ export abstract class BaseBreakdownCommand extends BaseResourceCommand { } - protected breakdownFlag(flag?: string): MetricsQueryBreakdown | undefined { let breakdown: MetricsQueryBreakdown | undefined diff --git a/plugins/microstore/src/base.ts b/plugins/microstore/src/base.ts index 69ef9e42d..ee342b96b 100644 --- a/plugins/microstore/src/base.ts +++ b/plugins/microstore/src/base.ts @@ -1,41 +1,24 @@ -import { clColor, clOutput, clToken, clUpdate, clUtil } from '@commercelayer/cli-core' +import { accessTokenFlag, CLCommand, clColor, clToken, domainFlag, organizationFlag } from '@commercelayer/cli-core' import commercelayer, { type CommerceLayerClient, CommerceLayerStatic } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Command, Flags } from '@oclif/core' +import { Flags } from '@oclif/core' type CommandError = Interfaces.CommandError -const pkg: clUpdate.Package = require('../package.json') - - const REQUIRED_APP_KIND = 'sales_channel' -export default abstract class extends Command { +export default abstract class extends CLCommand { static flags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN' - }), - accessToken: Flags.string({ + organization: organizationFlag(), + domain: domainFlag(), + accessToken: accessTokenFlag({ char: 'a', description: 'custom access token to use instead of the one used for login', hidden: false, - required: true, - env: 'CL_CLI_ACCESS_TOKEN', - dependsOn: ['organization'] + dependsOn: ['organization'], }), open: Flags.boolean({ description: 'open microstore URL in default browser' @@ -76,44 +59,19 @@ export default abstract class extends Command { } - // INIT (override) - async init(): Promise { - clUpdate.checkUpdate(pkg) - return super.init() - } - - async catch(error: any): Promise { return this.handleError(error) } protected async handleError(error: any, flags?: any): Promise { - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.msg.error(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s resources'] }, - ) - } else this.error(clOutput.formatError(error, flags)) - } else return super.catch(error as CommandError) + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { flags }) + else return super.catch(error as CommandError) } protected commercelayerInit(flags: any): CommerceLayerClient { - - const organization = flags.organization - const domain = flags.domain - const accessToken = flags.accessToken - const userAgent = clUtil.userAgent(this.config) - - return commercelayer({ - organization, - domain, - accessToken, - userAgent - }) - + return commercelayer(this.clientOptions(flags)) } @@ -144,5 +102,4 @@ export default abstract class extends Command { } - export { Flags } diff --git a/plugins/orders/src/base.ts b/plugins/orders/src/base.ts index 4551b8192..d0d1b90d1 100644 --- a/plugins/orders/src/base.ts +++ b/plugins/orders/src/base.ts @@ -1,38 +1,17 @@ -import { clColor, clOutput, clUpdate } from '@commercelayer/cli-core' +import { CLCommand, clColor, clOutput } from '@commercelayer/cli-core' import { CommerceLayerStatic, type Order } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, Flags } from '@oclif/core' +import { Args, Flags } from '@oclif/core' import exec from './exec' import type { ActionType } from './triggers' type CommandError = Interfaces.CommandError -const pkg: clUpdate.Package = require('../package.json') - - -export default abstract class extends Command { +export default abstract class extends CLCommand { static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN' - }), - accessToken: Flags.string({ - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN' - }), + ...CLCommand.baseFlags, print: Flags.boolean({ char: 'p', description: 'print out the modified order' @@ -55,13 +34,6 @@ export default abstract class extends Command { } - // INIT (override) - async init(): Promise { - clUpdate.checkUpdate(pkg) - return await super.init() - } - - async catch(error: CommandError): Promise { if (error.message?.includes('quit')) this.exit() else this.handleError(error as Error) @@ -72,18 +44,8 @@ export default abstract class extends Command { if (error.message?.match(/Missing \d required args?:\nid/)) this.error(`Missing the required unique ${clColor.style.error('id')} of the order`) else - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.msg.error(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s orders'] }, - ) - } else - if (error.status === 404) { - const id = (error as any).id || '' - this.error(`Unable to find order${id ? ` with id ${clColor.msg.error(id)}` : ''}`) - } else this.error(clOutput.formatError(error, flags)) - } else throw error + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { resource: 'order', id: (error as any).id || '', flags }) + else throw error } @@ -103,5 +65,4 @@ export default abstract class extends Command { } - export { Flags } diff --git a/plugins/resources/src/base.ts b/plugins/resources/src/base.ts index 97a1a0834..e6a021b66 100644 --- a/plugins/resources/src/base.ts +++ b/plugins/resources/src/base.ts @@ -1,11 +1,11 @@ import { existsSync } from 'node:fs' import type { KeyVal, KeyValArray, KeyValObj, KeyValRel, KeyValSort, KeyValString, ResAttributes } from '@commercelayer/cli-core' -import { clColor, clCommand, clConfig, clFilter, clText, clToken, clUpdate, clUtil } from '@commercelayer/cli-core' +import { accessTokenFlag, CLCommand, clColor, clCommand, clConfig, clFilter, clText, clUtil, domainFlag, organizationFlag } from '@commercelayer/cli-core' import * as cliux from '@commercelayer/cli-ux' import type { CommerceLayerClient, QueryParams, QueryParamsRetrieve, ResourceId, ResourceType, ResourceTypeLock } from '@commercelayer/sdk' import commercelayer, { CommerceLayerStatic } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, type Config, Flags } from '@oclif/core' +import { Args, type Config, Flags } from '@oclif/core' import { aliasExists, type CommandParams, checkAlias, loadCommandData, type ResourceOperation, saveCommandData } from './commands' import { exportCsv } from './csv' import { availableLanguages, buildCommand, getLanguageArg, languageInfo, promptLanguage, type RequestData } from './lang' @@ -16,53 +16,23 @@ import { type ApiResource, findResource } from './util/resources' type CommandError = Interfaces.CommandError - -const pkg: clUpdate.Package = require('../package.json') - - export const FLAG_SAVE_PARAMS = 'save-args' export const FLAG_LOAD_PARAMS = 'load-args' -export abstract class BaseCommand extends Command { +export abstract class BaseCommand extends CLCommand { static flags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN' - }), - accessToken: Flags.string({ - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN' - }) + organization: organizationFlag(), + domain: domainFlag(), + accessToken: accessTokenFlag(), } protected initCommerceLayer(flags: any, ...options: any[]): CommerceLayerClient { - - const organization = flags.organization - const domain = flags.domain - const accessToken = flags.accessToken - const userAgent = clUtil.userAgent(this.config) - - - const cl = commercelayer({ organization, domain, accessToken, userAgent, ...options }) - + const cl = commercelayer({ ...this.clientOptions(flags), ...options }) if ('cl' in this) this.cl = cl - return cl - } @@ -245,14 +215,6 @@ export abstract class BaseQueryCommand extends BaseCommand { } - // INIT (override) - async init(): Promise { - // Check for plugin updates only if in visible mode - if (!this.argv.includes('--blind') && !this.argv.includes('--silent') && !this.argv.includes('--quiet')) clUpdate.checkUpdate(pkg) - return await super.init() - } - - // CATCH (override) async catch(error: any): Promise { if (error.message?.match(/Missing \d required args?:\nresource/)) @@ -264,7 +226,6 @@ export abstract class BaseQueryCommand extends BaseCommand { } - // -- CUSTOM METHODS -- // protected checkTag(resource: string): boolean { @@ -723,20 +684,6 @@ export abstract class BaseQueryCommand extends BaseCommand { } - protected checkApplication(accessToken: string, kinds: string[]): boolean { - - const info = clToken.decodeAccessToken(accessToken) - - if (info === null) this.error('Invalid access token provided') - else - if (!kinds.includes(info.application.kind)) - this.error(`Invalid application kind: ${clColor.msg.error(info.application.kind)}. Application kind must be one of the following: ${clColor.cyanBright(kinds.join(', '))}`) - - return true - - } - - protected checkAlias(alias: string, resource: string, operation: ResourceOperation, config: Config): void { let ok = false try { @@ -813,5 +760,4 @@ export default abstract class extends BaseQueryCommand { } - export { Args, cliux, Flags } diff --git a/plugins/resources/src/commands/resources/count.ts b/plugins/resources/src/commands/resources/count.ts index 35230bc3c..b182c4579 100644 --- a/plugins/resources/src/commands/resources/count.ts +++ b/plugins/resources/src/commands/resources/count.ts @@ -52,7 +52,7 @@ export default class ResourcesCount extends BaseCommand { const humanized = clApi.humanizeResource(resource.type) this.log() - if (!flags.doc) cliux.action.start(`Counting ${humanized}`) + cliux.action.start(`Counting ${humanized}`) const res = await resSdk.list({ filters /* , pageNumber: 1, pageSize: 1 */}) as ListResponse diff --git a/plugins/tags/src/base.ts b/plugins/tags/src/base.ts index 2a54db187..2eb0096ef 100644 --- a/plugins/tags/src/base.ts +++ b/plugins/tags/src/base.ts @@ -1,120 +1,36 @@ /* eslint-disable @typescript-eslint/unbound-method */ -import { clColor, clConfig, clOutput, clToken, clUpdate, clUtil } from '@commercelayer/cli-core' +import { CLCommand, clColor, clConfig } from '@commercelayer/cli-core' import * as cliux from '@commercelayer/cli-ux' import type { CommerceLayerClient, ListResponse, Tag, TaggableResource, TaggableResourceType } from '@commercelayer/sdk' import commercelayer, { CommerceLayerStatic } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, Flags } from '@oclif/core' +import { Args, Flags } from '@oclif/core' type CommandError = Interfaces.CommandError -const pkg: clUpdate.Package = require('../package.json') +export default abstract class BaseCommand extends CLCommand { -export default abstract class BaseCommand extends Command { - - static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true, - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN', - }), - accessToken: Flags.string({ - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN', - }) - } + static applicationKinds = ['integration'] protected cl!: CommerceLayerClient - // INIT (override) - async init(): Promise { - - // Check for plugin updates only if in visible mode - if (!this.argv.includes('--blind') && !this.argv.includes('--silent') && !this.argv.includes('--quiet')) clUpdate.checkUpdate(pkg) - - // Application check - const atFlag = this.argv.find(a => a.startsWith('--accessToken=')) - if (atFlag) { - const accessToken = atFlag?.substring(atFlag.indexOf('=') + 1) - this.checkApplication(accessToken, ['integration'/* , 'cli' */]) - } - - return await super.init() - - } - - - async catch(error: CommandError): Promise { - if (error.message?.includes('quit')) this.exit() - else return super.catch(error) - } - - - - protected checkApplication(accessToken: string, kinds: string[]): boolean { - - const info = clToken.decodeAccessToken(accessToken) - - if (info === null) this.error('Invalid access token provided') - else - if (!kinds.includes(info.application.kind)) - this.error(`Invalid application kind: ${clColor.msg.error(info.application.kind)}. Application kind must be one of the following: ${clColor.cyanBright(kinds.join(', '))}`) - - return true - - } - - protected commercelayerInit(flags: any): CommerceLayerClient { - - const organization = flags.organization - const domain = flags.domain - const accessToken = flags.accessToken - - const userAgent = clUtil.userAgent(this.config) - - this.cl = commercelayer({ - organization, - domain, - accessToken, - userAgent - }) - + this.cl = commercelayer(this.clientOptions(flags)) return this.cl - } - protected handleError(error: CommandError, _flags?: any, id?: string): void { - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.msg.error(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s tags'] }, - ) - } else - if (error.status === 404) { - this.error(`Unable to find tag${id ? ` with ID or name ${clColor.msg.error(id)}` : ''}`) - } else this.error(clOutput.formatError(error)) - } else throw error + protected handleError(error: CommandError, flags?: any, id?: string): void { + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { resource: 'tag', id, idLabel: 'ID or name', flags }) + else throw error } diff --git a/plugins/triggers/src/base.ts b/plugins/triggers/src/base.ts index 5b96034c5..7a48c81a9 100644 --- a/plugins/triggers/src/base.ts +++ b/plugins/triggers/src/base.ts @@ -1,38 +1,17 @@ -import { clApi, clColor, clOutput, clUpdate } from '@commercelayer/cli-core' +import { CLCommand, clApi, clColor, clOutput } from '@commercelayer/cli-core' import { CommerceLayerStatic, type Resource } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, type Errors, Flags } from '@oclif/core' +import { Args, type Errors, Flags } from '@oclif/core' import exec from './exec' type CLIError = InstanceType type CommandError = Interfaces.CommandError -const pkg: clUpdate.Package = require('../package.json') - - -export default abstract class extends Command { +export default abstract class extends CLCommand { static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true, - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN', - }), - accessToken: Flags.string({ - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN', - }), + ...CLCommand.baseFlags, print: Flags.boolean({ char: 'p', description: 'print out the modified resource', @@ -55,13 +34,6 @@ export default abstract class extends Command { } - // INIT (override) - async init(): Promise { - clUpdate.checkUpdate(pkg) - return await super.init() - } - - async catch(error: CLIError): Promise { this.handleError(error) } @@ -116,5 +88,4 @@ export default abstract class extends Command { } - export { Flags } diff --git a/plugins/webhooks/src/base.ts b/plugins/webhooks/src/base.ts index b8f2915db..571f86798 100644 --- a/plugins/webhooks/src/base.ts +++ b/plugins/webhooks/src/base.ts @@ -1,45 +1,18 @@ -import { clColor, clOutput, clUpdate, clUtil } from '@commercelayer/cli-core' +import { CLCommand, clColor } from '@commercelayer/cli-core' import * as cliux from '@commercelayer/cli-ux' import commercelayer, { type CommerceLayerClient, CommerceLayerStatic } from '@commercelayer/sdk' import type { Interfaces } from '@oclif/core' -import { Args, Command, Flags } from '@oclif/core' +import { Args, Flags } from '@oclif/core' type CommandError = Interfaces.CommandError -const pkg: clUpdate.Package = require('../package.json') - - -export abstract class BaseCommand extends Command { +export abstract class BaseCommand extends CLCommand { + // oclif collects the static properties of a command up to the first class + // without any (cacheCommand): without its own, the manifest would miss them static baseFlags = { - organization: Flags.string({ - char: 'o', - description: 'the slug of your organization', - required: true, - env: 'CL_CLI_ORGANIZATION', - hidden: true - }), - domain: Flags.string({ - char: 'd', - required: false, - hidden: true, - dependsOn: ['organization'], - env: 'CL_CLI_DOMAIN' - }), - accessToken: Flags.string({ - hidden: true, - required: true, - env: 'CL_CLI_ACCESS_TOKEN' - }) - } - - - - // INIT (override) - async init(): Promise { - clUpdate.checkUpdate(pkg) - return await super.init() + ...CLCommand.baseFlags } @@ -49,35 +22,13 @@ export abstract class BaseCommand extends Command { protected handleError(error: CommandError, flags?: any, id?: string): void { - if (CommerceLayerStatic.isApiError(error)) { - if (error.status === 401) { - const err = error.first() - this.error(clColor.bg.red(`${err.title}: ${err.detail}`), - { suggestions: ['Execute login to get access to the organization\'s webhooks'] }, - ) - } else - if (error.status === 404) { - this.error(`Unable to find webhook${id ? ` with id ${clColor.msg.error(id)}` : ''}`) - } else this.error(clOutput.formatError(error, flags)) - } else throw error + if (CommerceLayerStatic.isApiError(error)) this.handleApiError(error, { resource: 'webhook', id, flags }) + else throw error } protected commercelayerInit(flags: any): CommerceLayerClient { - - const organization = flags.organization - const domain = flags.domain - const accessToken = flags.accessToken - - const userAgent = clUtil.userAgent(this.config) - - return commercelayer({ - organization, - domain, - accessToken, - userAgent - }) - + return commercelayer(this.clientOptions(flags)) } } @@ -98,5 +49,4 @@ export abstract class BaseIdCommand extends BaseCommand { } - export { Args, cliux, Flags }