From ef8302cebf39b96f8531678cc89f1bf04dd2ca96 Mon Sep 17 00:00:00 2001 From: Charles Lavery Date: Tue, 25 Nov 2025 17:33:20 -0500 Subject: [PATCH 1/4] adding pino logging and spec requirements --- packages/b2c-tooling/package.json | 15 ++- packages/b2c-tooling/src/cli/base-command.ts | 52 +++++----- packages/b2c-tooling/src/index.ts | 9 +- packages/b2c-tooling/src/logging/index.ts | 9 ++ packages/b2c-tooling/src/logging/logger.ts | 54 +++++++++++ packages/b2c-tooling/src/logging/redaction.ts | 26 +++++ packages/b2c-tooling/src/logging/types.ts | 32 +++++++ pnpm-lock.yaml | 94 ++++++++++++++++++- typedoc.json | 2 +- 9 files changed, 265 insertions(+), 28 deletions(-) create mode 100644 packages/b2c-tooling/src/logging/index.ts create mode 100644 packages/b2c-tooling/src/logging/logger.ts create mode 100644 packages/b2c-tooling/src/logging/redaction.ts create mode 100644 packages/b2c-tooling/src/logging/types.ts diff --git a/packages/b2c-tooling/package.json b/packages/b2c-tooling/package.json index f791dc96..d42f6bff 100644 --- a/packages/b2c-tooling/package.json +++ b/packages/b2c-tooling/package.json @@ -92,6 +92,16 @@ "types": "./dist/cjs/cli/index.d.ts", "default": "./dist/cjs/cli/index.js" } + }, + "./logging": { + "import": { + "types": "./dist/esm/logging/index.d.ts", + "default": "./dist/esm/logging/index.js" + }, + "require": { + "types": "./dist/cjs/logging/index.d.ts", + "default": "./dist/cjs/logging/index.js" + } } }, "main": "./dist/cjs/index.js", @@ -116,7 +126,7 @@ "@oclif/core": "^4", "@oclif/prettier-config": "^0.2.1", "@salesforce/dev-config": "^4.3.2", - "@types/node": "^18", + "@types/node": "^18.19.130", "eslint": "^9", "eslint-config-prettier": "^10", "eslint-plugin-prettier": "^5.5.4", @@ -137,6 +147,7 @@ "node": ">=18.0.0" }, "dependencies": { - "i18next": "^25.6.3" + "i18next": "^25.6.3", + "pino": "^10.1.0" } } diff --git a/packages/b2c-tooling/src/cli/base-command.ts b/packages/b2c-tooling/src/cli/base-command.ts index 865f8116..6287c223 100644 --- a/packages/b2c-tooling/src/cli/base-command.ts +++ b/packages/b2c-tooling/src/cli/base-command.ts @@ -1,30 +1,30 @@ import {Command, Flags, Interfaces} from '@oclif/core'; import {loadConfig, ResolvedConfig, LoadConfigOptions} from './config.js'; -import {setLogger, consoleLogger} from '../logger.js'; import {setLanguage} from '../i18n/index.js'; +import {configureLogger, getLogger, type LogLevel, type Logger} from '../logging/index.js'; export type Flags = Interfaces.InferredFlags<(typeof BaseCommand)['baseFlags'] & T['flags']>; export type Args = Interfaces.InferredArgs; +const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'silent'] as const; + /** * Base command class for B2C CLI tools. - * Provides minimal common flags: debug, config file path, instance selection. - * - * All flags support environment variables with SFCC_ prefix. - * - * For commands that need authentication, extend one of: - * - OAuthCommand: For platform operations requiring OAuth (ODS, etc.) - * - InstanceCommand: For B2C instance operations (sites, code, etc.) - * - MrtCommand: For Managed Runtime operations */ export abstract class BaseCommand extends Command { static baseFlags = { + 'log-level': Flags.option({ + description: 'Set logging verbosity level', + env: 'SFCC_LOG_LEVEL', + options: LOG_LEVELS, + helpGroup: 'LOGGING', + })(), debug: Flags.boolean({ char: 'D', - description: 'Enable debug logging', + description: 'Enable debug logging (shorthand for --log-level debug)', env: 'SFCC_DEBUG', default: false, - helpGroup: 'GLOBAL', + helpGroup: 'LOGGING', }), lang: Flags.string({ char: 'L', @@ -47,6 +47,7 @@ export abstract class BaseCommand extends Command { protected flags!: Flags; protected args!: Args; protected resolvedConfig!: ResolvedConfig; + protected logger!: Logger; public async init(): Promise { await super.init(); @@ -61,25 +62,32 @@ export abstract class BaseCommand extends Command { this.flags = flags as Flags; this.args = args as Args; - // Set language first so all messages are localized - // Flag takes precedence (env var is handled by i18n module at import time) if (this.flags.lang) { setLanguage(this.flags.lang); } - if (this.flags.debug) { - setLogger(consoleLogger); + this.configureLogging(); + this.resolvedConfig = this.loadConfiguration(); + } + + protected configureLogging(): void { + let level: LogLevel = 'info'; + + if (this.flags['log-level']) { + level = this.flags['log-level'] as LogLevel; + } else if (this.flags.debug) { + level = 'debug'; } - // Load config - subclasses will augment with their specific flags - this.resolvedConfig = this.loadConfiguration(); + configureLogger({ + level, + destination: process.stderr, + baseContext: {command: this.id}, + }); + + this.logger = getLogger(); } - /** - * Load configuration from flags and dw.json. - * Environment variables are handled by OCLIF's flag parsing. - * Subclasses should override to add their specific flag mappings. - */ protected loadConfiguration(): ResolvedConfig { const options: LoadConfigOptions = { instance: this.flags.instance, diff --git a/packages/b2c-tooling/src/index.ts b/packages/b2c-tooling/src/index.ts index 5a2c6167..e2a997a3 100644 --- a/packages/b2c-tooling/src/index.ts +++ b/packages/b2c-tooling/src/index.ts @@ -1,5 +1,10 @@ -// Logger -export {Logger, noopLogger, consoleLogger, setLogger, getLogger} from './logger.js'; +// Logging +export {createLogger, configureLogger, getLogger, resetLogger, createSilentLogger} from './logging/index.js'; +export type {Logger, LoggerOptions, LogLevel, LogContext} from './logging/index.js'; + +// Legacy logger exports (deprecated - use logging module instead) +export {noopLogger, consoleLogger, setLogger, getLogger as getLegacyLogger} from './logger.js'; +export type {Logger as LegacyLogger} from './logger.js'; // i18n export {t, setLanguage, getLanguage, getI18nInstance, registerTranslations, B2C_NAMESPACE} from './i18n/index.js'; diff --git a/packages/b2c-tooling/src/logging/index.ts b/packages/b2c-tooling/src/logging/index.ts new file mode 100644 index 00000000..41ee5ffe --- /dev/null +++ b/packages/b2c-tooling/src/logging/index.ts @@ -0,0 +1,9 @@ +/** + * Structured logging using pino with JSON lines output. + * + * @module logging + */ + +export type {Logger, LoggerOptions, LogLevel, LogContext} from './types.js'; +export {createLogger, configureLogger, getLogger, resetLogger, createSilentLogger} from './logger.js'; +export {isRedactionDisabled} from './redaction.js'; diff --git a/packages/b2c-tooling/src/logging/logger.ts b/packages/b2c-tooling/src/logging/logger.ts new file mode 100644 index 00000000..feaa93dc --- /dev/null +++ b/packages/b2c-tooling/src/logging/logger.ts @@ -0,0 +1,54 @@ +/** + * Logger using pino with JSON lines output. + */ + +import pino from 'pino'; +import type {Logger, LoggerOptions} from './types.js'; +import {getRedactPaths, isRedactionDisabled} from './redaction.js'; + +let globalLogger: Logger | null = null; +let globalOptions: LoggerOptions = {level: 'info'}; + +function createPinoLogger(options: LoggerOptions): Logger { + const level = options.level ?? 'info'; + const dest = options.destination ?? process.stderr; + + const pinoOptions: pino.LoggerOptions = { + level, + base: options.baseContext ?? {}, + }; + + if (options.redact !== false && !isRedactionDisabled()) { + pinoOptions.redact = { + paths: getRedactPaths(), + censor: '[REDACTED]', + }; + } + + return pino(pinoOptions, pino.destination({dest, sync: true})) as unknown as Logger; +} + +export function createLogger(options: LoggerOptions = {}): Logger { + return createPinoLogger({...globalOptions, ...options}); +} + +export function configureLogger(options: LoggerOptions): void { + globalOptions = {...globalOptions, ...options}; + globalLogger = createPinoLogger(globalOptions); +} + +export function getLogger(): Logger { + if (!globalLogger) { + globalLogger = createPinoLogger(globalOptions); + } + return globalLogger; +} + +export function resetLogger(): void { + globalLogger = null; + globalOptions = {level: 'info'}; +} + +export function createSilentLogger(): Logger { + return createLogger({level: 'silent'}); +} diff --git a/packages/b2c-tooling/src/logging/redaction.ts b/packages/b2c-tooling/src/logging/redaction.ts new file mode 100644 index 00000000..2c7f43b0 --- /dev/null +++ b/packages/b2c-tooling/src/logging/redaction.ts @@ -0,0 +1,26 @@ +/** + * Redaction configuration for pino. + */ + +const REDACT_FIELDS = [ + 'password', + 'client_secret', + 'clientSecret', + 'access_token', + 'accessToken', + 'refresh_token', + 'refreshToken', + 'api_key', + 'apiKey', + 'token', + 'secret', + 'authorization', +]; + +export function isRedactionDisabled(): boolean { + return process.env.SFCC_REDACT_SECRETS === 'false'; +} + +export function getRedactPaths(): string[] { + return REDACT_FIELDS.flatMap((field) => [field, `*.${field}`]); +} diff --git a/packages/b2c-tooling/src/logging/types.ts b/packages/b2c-tooling/src/logging/types.ts new file mode 100644 index 00000000..7d742ef0 --- /dev/null +++ b/packages/b2c-tooling/src/logging/types.ts @@ -0,0 +1,32 @@ +/** + * Logging types. + */ + +export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent'; + +export interface LogContext { + [key: string]: unknown; +} + +export interface LoggerOptions { + level?: LogLevel; + destination?: NodeJS.WritableStream; + baseContext?: LogContext; + redact?: boolean; +} + +export interface Logger { + trace(message: string, context?: LogContext): void; + trace(context: LogContext, message: string): void; + debug(message: string, context?: LogContext): void; + debug(context: LogContext, message: string): void; + info(message: string, context?: LogContext): void; + info(context: LogContext, message: string): void; + warn(message: string, context?: LogContext): void; + warn(context: LogContext, message: string): void; + error(message: string, context?: LogContext): void; + error(context: LogContext, message: string): void; + fatal(message: string, context?: LogContext): void; + fatal(context: LogContext, message: string): void; + child(context: LogContext): Logger; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3afc54f..ffdd51b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: i18next: specifier: ^25.6.3 version: 25.6.3(typescript@5.9.3) + pino: + specifier: ^10.1.0 + version: 10.1.0 devDependencies: '@eslint/compat': specifier: ^1 @@ -110,7 +113,7 @@ importers: specifier: ^4.3.2 version: 4.3.2 '@types/node': - specifier: ^18 + specifier: ^18.19.130 version: 18.19.130 eslint: specifier: ^9 @@ -1035,6 +1038,9 @@ packages: peerDependencies: '@oclif/core': '>= 3.0.0' + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -1852,6 +1858,10 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -3239,6 +3249,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3340,6 +3354,16 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@10.1.0: + resolution: {integrity: sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==} + hasBin: true + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -3372,6 +3396,9 @@ packages: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -3389,6 +3416,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -3411,6 +3441,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} @@ -3505,6 +3539,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -3586,6 +3624,9 @@ packages: snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + sort-object-keys@1.1.3: resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} @@ -3619,6 +3660,10 @@ packages: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} @@ -3691,6 +3736,9 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + tiny-jsonc@1.0.2: resolution: {integrity: sha512-f5QDAfLq6zIVSyCZQZhhyl0QS6MvAyTxgz4X4x3+EoCktNWEYJ6PeoEA97fyb98njpBNNi88ybpD7m+BDFXaCw==} @@ -5216,6 +5264,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@pinojs/redact@0.4.0': {} + '@pkgr/core@0.2.9': {} '@pnpm/config.env-replace@1.1.0': {} @@ -6149,6 +6199,8 @@ snapshots: async@3.2.6: {} + atomic-sleep@1.0.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -7709,6 +7761,8 @@ snapshots: - aws-crt - supports-color + on-exit-leak-free@2.1.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -7811,6 +7865,26 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@10.1.0: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + pluralize@8.0.0: {} possible-typed-array-names@1.1.0: {} @@ -7833,6 +7907,8 @@ snapshots: proc-log@4.2.0: {} + process-warning@5.0.0: {} + property-information@7.1.0: {} proto-list@1.2.4: {} @@ -7843,6 +7919,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + quick-lru@5.1.1: {} rambda@7.5.0: {} @@ -7868,6 +7946,8 @@ snapshots: dependencies: picomatch: 2.3.1 + real-require@0.2.0: {} + rechoir@0.6.2: dependencies: resolve: 1.22.11 @@ -7991,6 +8071,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} search-insights@2.17.3: {} @@ -8096,6 +8178,10 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + sort-object-keys@1.1.3: {} sort-package-json@2.15.1: @@ -8134,6 +8220,8 @@ snapshots: speakingurl@14.0.1: {} + split2@4.2.0: {} + stable-hash@0.0.5: {} stop-iteration-iterator@1.1.0: @@ -8211,6 +8299,10 @@ snapshots: tapable@2.3.0: {} + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + tiny-jsonc@1.0.2: {} tinyglobby@0.2.15: diff --git a/typedoc.json b/typedoc.json index 4fab6722..ba70fc92 100644 --- a/typedoc.json +++ b/typedoc.json @@ -8,7 +8,7 @@ "./packages/b2c-tooling/src/operations/jobs/index.ts", "./packages/b2c-tooling/src/operations/sites/index.ts", "./packages/b2c-tooling/src/i18n/index.ts", - "./packages/b2c-tooling/src/logger.ts" + "./packages/b2c-tooling/src/logging/index.ts" ], "out": "./docs/api", "plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"], From a6b83c5041165091a1a5a7bb3b4874cbedf1f9ef Mon Sep 17 00:00:00 2001 From: Charles Lavery Date: Tue, 25 Nov 2025 18:40:48 -0500 Subject: [PATCH 2/4] logging patterns --- docs/cli/index.md | 4 + docs/cli/logging.md | 123 ++++++++++++++++++ packages/b2c-cli/src/commands/_test/index.ts | 45 +++++++ packages/b2c-cli/src/commands/hello/index.ts | 25 ---- packages/b2c-cli/src/commands/hello/world.ts | 16 --- packages/b2c-tooling/package.json | 3 +- packages/b2c-tooling/src/cli/base-command.ts | 60 ++++++++- packages/b2c-tooling/src/logging/index.ts | 112 +++++++++++++++- packages/b2c-tooling/src/logging/logger.ts | 30 ++++- packages/b2c-tooling/src/logging/redaction.ts | 4 - packages/b2c-tooling/src/logging/types.ts | 10 +- pnpm-lock.yaml | 80 ++++++++++++ 12 files changed, 457 insertions(+), 55 deletions(-) create mode 100644 docs/cli/logging.md create mode 100644 packages/b2c-cli/src/commands/_test/index.ts delete mode 100644 packages/b2c-cli/src/commands/hello/index.ts delete mode 100644 packages/b2c-cli/src/commands/hello/world.ts diff --git a/docs/cli/index.md b/docs/cli/index.md index 2128d11d..3e34ff12 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -30,6 +30,10 @@ These flags are available on all commands that interact with B2C instances: - [Sandbox Commands](./sandbox) - Create and manage sandboxes - [MRT Commands](./mrt) - Manage Managed Runtime environments +## Configuration + +- [Logging](./logging) - Log levels, output formats, and environment variables + ## Getting Help Get help for any command: diff --git a/docs/cli/logging.md b/docs/cli/logging.md new file mode 100644 index 00000000..2b96e107 --- /dev/null +++ b/docs/cli/logging.md @@ -0,0 +1,123 @@ +# Logging + +The CLI uses [pino](https://github.com/pinojs/pino) for structured logging with pretty-printed output by default. + +## Output Modes + +### Pretty Print (Default) + +Human-readable output with colors, suitable for interactive terminal use: + +``` +[18:31:58] INFO: Deploying cartridges... +[18:31:59] INFO: Upload complete +``` + +### JSON Lines (`--json`) + +Machine-readable output for scripting, CI/CD pipelines, and log aggregation: + +```bash +b2c code deploy --json +``` + +```json +{"level":"info","time":1764113529411,"command":"code deploy","msg":"Deploying cartridges..."} +{"level":"info","time":1764113529412,"command":"code deploy","msg":"Upload complete"} +``` + +## Log Levels + +Control verbosity with `--log-level` or `-D` for debug: + +| Level | Description | +|-------|-------------| +| `trace` | Maximum verbosity | +| `debug` | Detailed operational info | +| `info` | Normal messages (default) | +| `warn` | Warnings | +| `error` | Errors only | +| `silent` | No output | + +```bash +b2c code deploy --log-level debug +b2c code deploy -D # shorthand for --log-level debug +``` + +In debug/trace mode, context objects are displayed: + +``` +[18:31:58] INFO: Upload complete + command: "code deploy" + file: "cartridge.zip" + bytes: 45678 + duration: 1234 +``` + +## Flags + +| Flag | Environment Variable | Description | +|------|---------------------|-------------| +| `--log-level` | `SFCC_LOG_LEVEL` | Set log verbosity | +| `-D, --debug` | `SFCC_DEBUG` | Enable debug logging | +| `--json` | | Output JSON lines | + +## Environment Variables + +| Variable | Description | +|----------|-------------| +| `SFCC_LOG_LEVEL` | Log level (trace, debug, info, warn, error, silent) | +| `SFCC_DEBUG` | Enable debug logging | +| `SFCC_LOG_TO_STDOUT` | Send logs to stdout instead of stderr | +| `SFCC_LOG_COLORIZE` | Force colors on/off | +| `SFCC_REDACT_SECRETS` | Set to `false` to disable secret redaction | +| `NO_COLOR` | Industry standard to disable colors | + +## Output Streams + +By default, logs go to **stderr** so that command output (data, IDs, etc.) can be piped cleanly: + +```bash +# Logs go to stderr, JSON output goes to stdout +b2c sites list --json 2>/dev/null | jq '.sites[0].id' +``` + +To send logs to stdout instead: + +```bash +SFCC_LOG_TO_STDOUT=1 b2c code deploy +``` + +## Secret Redaction + +Sensitive fields are automatically redacted from log output: + +- `password`, `secret`, `token` +- `client_secret`, `access_token`, `refresh_token` +- `api_key`, `authorization` + +``` +[18:31:58] INFO: Authenticating + client_id: "my-client" + client_secret: "[REDACTED]" +``` + +To disable redaction (for debugging): + +```bash +SFCC_REDACT_SECRETS=false b2c code deploy --debug +``` + +## CI/CD Usage + +For CI/CD pipelines, use JSON output and disable colors: + +```bash +NO_COLOR=1 b2c code deploy --json 2>&1 | tee deploy.log +``` + +Or explicitly set the log level: + +```bash +SFCC_LOG_LEVEL=info b2c code deploy --json +``` diff --git a/packages/b2c-cli/src/commands/_test/index.ts b/packages/b2c-cli/src/commands/_test/index.ts new file mode 100644 index 00000000..8eda9ed6 --- /dev/null +++ b/packages/b2c-cli/src/commands/_test/index.ts @@ -0,0 +1,45 @@ +import {BaseCommand} from '@salesforce/b2c-tooling/cli'; + +export default class Test extends BaseCommand { + static description = 'Test logging output'; + static hidden = true; + + async run(): Promise { + // Test this.log() which now uses pino + this.log('Using this.log() - goes through pino'); + this.warn('Using this.warn() - goes through pino'); + + // Test logger directly at different levels + this.logger.trace('Trace level message'); + this.logger.debug('Debug level message'); + this.logger.info('Info level message'); + this.logger.error('Error level message'); + + // Context (visible in debug mode) + this.logger.info({operation: 'test', duration: 123}, 'Message with context'); + + this.logger.debug( + { + file: 'cartridge.zip', + bytes: 45_678, + instance: 'dev01.sandbox.us01.dx.commercecloud.salesforce.com', + }, + 'Debug with multiple context fields', + ); + + // Test redaction + this.logger.info( + { + username: 'testuser', + password: 'secret123', + client_secret: 'abc123xyz', // eslint-disable-line camelcase + token: 'Bearer eyJhbGciOiJIUzI1NiJ9.test', + }, + 'This should have redacted fields', + ); + + // Child logger + const childLogger = this.logger.child({operation: 'upload'}); + childLogger.info('Message from child logger'); + } +} diff --git a/packages/b2c-cli/src/commands/hello/index.ts b/packages/b2c-cli/src/commands/hello/index.ts deleted file mode 100644 index 899058a3..00000000 --- a/packages/b2c-cli/src/commands/hello/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {Args, Command, Flags} from '@oclif/core'; - -export default class Hello extends Command { - static args = { - person: Args.string({description: 'Person to say hello to', required: true}), - }; - - static description = 'Say hello'; - - static examples = [ - `<%= config.bin %> <%= command.id %> friend --from oclif -hello friend from oclif! -`, - ]; - - static flags = { - from: Flags.string({char: 'f', description: 'Who is saying hello', required: true}), - }; - - async run(): Promise { - const {args, flags} = await this.parse(Hello); - - this.log(`hello ${args.person} from ${flags.from}!`); - } -} diff --git a/packages/b2c-cli/src/commands/hello/world.ts b/packages/b2c-cli/src/commands/hello/world.ts deleted file mode 100644 index 534d685b..00000000 --- a/packages/b2c-cli/src/commands/hello/world.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {Command} from '@oclif/core'; - -export default class World extends Command { - static args = {}; - static description = 'Say hello world'; - static examples = [ - `<%= config.bin %> <%= command.id %> -hello world! (./src/commands/hello/world.ts) -`, - ]; - static flags = {}; - - async run(): Promise { - this.log('hello world! (./src/commands/hello/world.ts)'); - } -} diff --git a/packages/b2c-tooling/package.json b/packages/b2c-tooling/package.json index d42f6bff..a2ba116d 100644 --- a/packages/b2c-tooling/package.json +++ b/packages/b2c-tooling/package.json @@ -148,6 +148,7 @@ }, "dependencies": { "i18next": "^25.6.3", - "pino": "^10.1.0" + "pino": "^10.1.0", + "pino-pretty": "^13.1.2" } } diff --git a/packages/b2c-tooling/src/cli/base-command.ts b/packages/b2c-tooling/src/cli/base-command.ts index 6287c223..c0e5ca38 100644 --- a/packages/b2c-tooling/src/cli/base-command.ts +++ b/packages/b2c-tooling/src/cli/base-command.ts @@ -10,6 +10,12 @@ const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'silent'] as cons /** * Base command class for B2C CLI tools. + * + * Environment variables for logging: + * - SFCC_LOG_TO_STDOUT: Send logs to stdout instead of stderr + * - SFCC_LOG_COLORIZE: Force colors on/off (default: auto-detect TTY) + * - SFCC_REDACT_SECRETS: Set to 'false' to disable secret redaction + * - NO_COLOR: Industry standard to disable colors */ export abstract class BaseCommand extends Command { static baseFlags = { @@ -26,6 +32,11 @@ export abstract class BaseCommand extends Command { default: false, helpGroup: 'LOGGING', }), + json: Flags.boolean({ + description: 'Output logs as JSON lines', + default: false, + helpGroup: 'LOGGING', + }), lang: Flags.string({ char: 'L', description: 'Language for messages (e.g., en, de). Also respects LANGUAGE env var.', @@ -70,6 +81,26 @@ export abstract class BaseCommand extends Command { this.resolvedConfig = this.loadConfiguration(); } + /** + * Determine colorize setting based on env vars and TTY. + * Priority: NO_COLOR > SFCC_LOG_COLORIZE > TTY detection + */ + private shouldColorize(): boolean { + // NO_COLOR is the industry standard + if (process.env.NO_COLOR !== undefined) { + return false; + } + + // Explicit override + const colorizeEnv = process.env.SFCC_LOG_COLORIZE; + if (colorizeEnv !== undefined) { + return colorizeEnv !== 'false' && colorizeEnv !== '0'; + } + + // Default: colorize if stderr is a TTY + return process.stderr.isTTY ?? false; + } + protected configureLogging(): void { let level: LogLevel = 'info'; @@ -79,15 +110,42 @@ export abstract class BaseCommand extends Command { level = 'debug'; } + // Default to stderr (fd 2), allow override to stdout (fd 1) + const fd = process.env.SFCC_LOG_TO_STDOUT ? 1 : 2; + + // Redaction: default true, can be disabled + const redact = process.env.SFCC_REDACT_SECRETS !== 'false'; + configureLogger({ level, - destination: process.stderr, + fd, baseContext: {command: this.id}, + json: this.flags.json, + colorize: this.shouldColorize(), + redact, }); this.logger = getLogger(); } + /** + * Override oclif's log() to use pino. + */ + log(message?: string, ...args: unknown[]): void { + if (message !== undefined) { + this.logger.info(args.length > 0 ? `${message} ${args.join(' ')}` : message); + } + } + + /** + * Override oclif's warn() to use pino. + */ + warn(input: string | Error): string | Error { + const message = input instanceof Error ? input.message : input; + this.logger.warn(message); + return input; + } + protected loadConfiguration(): ResolvedConfig { const options: LoadConfigOptions = { instance: this.flags.instance, diff --git a/packages/b2c-tooling/src/logging/index.ts b/packages/b2c-tooling/src/logging/index.ts index 41ee5ffe..4337a7fa 100644 --- a/packages/b2c-tooling/src/logging/index.ts +++ b/packages/b2c-tooling/src/logging/index.ts @@ -1,9 +1,117 @@ /** - * Structured logging using pino with JSON lines output. + * Structured logging module using [pino](https://github.com/pinojs/pino). + * + * This module provides a logging interface for library consumers. It does **not** + * read environment variables directly—that is the responsibility of the CLI layer + * or the consuming application. + * + * ## Basic Usage + * + * ```typescript + * import { createLogger } from '@salesforce/b2c-tooling/logging'; + * + * const logger = createLogger({ level: 'debug' }); + * logger.info('Hello world'); + * logger.debug({ file: 'data.json' }, 'Processing file'); + * ``` + * + * ## Configuration Options + * + * | Option | Type | Default | Description | + * |--------|------|---------|-------------| + * | `level` | `LogLevel` | `'info'` | Minimum log level | + * | `fd` | `number` | `2` | File descriptor (1=stdout, 2=stderr) | + * | `json` | `boolean` | `false` | Output JSON lines instead of pretty print | + * | `colorize` | `boolean` | `true` | Enable colors in pretty print mode | + * | `redact` | `boolean` | `true` | Redact sensitive fields | + * | `baseContext` | `object` | `{}` | Context included in all log entries | + * + * ## Output Modes + * + * **Pretty print** (default): Human-readable with colors + * ``` + * [18:31:58] INFO: Hello world + * ``` + * + * **JSON lines** (`json: true`): Machine-readable for log aggregation + * ```json + * {"level":"info","time":1764113529411,"msg":"Hello world"} + * ``` + * + * ## Log Levels + * + * From most to least verbose: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent` + * + * ## Context and Child Loggers + * + * Add structured context to log entries: + * + * ```typescript + * // Inline context + * logger.info({ operation: 'deploy', file: 'app.zip' }, 'Starting deployment'); + * + * // Child logger with bound context + * const deployLogger = logger.child({ operation: 'deploy' }); + * deployLogger.info('Step 1 complete'); // Includes operation: 'deploy' + * deployLogger.info('Step 2 complete'); + * ``` + * + * ## Secret Redaction + * + * Sensitive fields are automatically redacted: + * + * ```typescript + * logger.info({ username: 'admin', password: 'secret' }, 'Auth attempt'); + * // Output: { username: 'admin', password: '[REDACTED]' } + * ``` + * + * Redacted fields: `password`, `secret`, `token`, `client_secret`, `access_token`, + * `refresh_token`, `api_key`, `authorization` + * + * ## Global Logger + * + * For CLI applications, use the global logger pattern: + * + * ```typescript + * import { configureLogger, getLogger } from '@salesforce/b2c-tooling/logging'; + * + * // Configure once at startup + * configureLogger({ + * level: 'debug', + * json: process.env.CI === 'true', + * colorize: process.stdout.isTTY, + * }); + * + * // Get anywhere in your app + * const logger = getLogger(); + * logger.info('Ready'); + * ``` + * + * ## Library vs CLI Usage + * + * **Library code** should accept a logger as a parameter: + * + * ```typescript + * import type { Logger } from '@salesforce/b2c-tooling/logging'; + * + * export function myOperation(options: { logger?: Logger }) { + * const log = options.logger ?? console; + * log.info('Starting operation'); + * } + * ``` + * + * **CLI code** reads environment variables and passes the configured logger: + * + * ```typescript + * const logger = createLogger({ + * level: process.env.LOG_LEVEL ?? 'info', + * json: process.env.LOG_JSON === 'true', + * }); + * myOperation({ logger }); + * ``` * * @module logging */ export type {Logger, LoggerOptions, LogLevel, LogContext} from './types.js'; export {createLogger, configureLogger, getLogger, resetLogger, createSilentLogger} from './logger.js'; -export {isRedactionDisabled} from './redaction.js'; diff --git a/packages/b2c-tooling/src/logging/logger.ts b/packages/b2c-tooling/src/logging/logger.ts index feaa93dc..306249cb 100644 --- a/packages/b2c-tooling/src/logging/logger.ts +++ b/packages/b2c-tooling/src/logging/logger.ts @@ -1,31 +1,51 @@ /** - * Logger using pino with JSON lines output. + * Logger using pino with pretty printing by default. */ import pino from 'pino'; +import pretty from 'pino-pretty'; import type {Logger, LoggerOptions} from './types.js'; -import {getRedactPaths, isRedactionDisabled} from './redaction.js'; +import {getRedactPaths} from './redaction.js'; let globalLogger: Logger | null = null; let globalOptions: LoggerOptions = {level: 'info'}; function createPinoLogger(options: LoggerOptions): Logger { const level = options.level ?? 'info'; - const dest = options.destination ?? process.stderr; + const fd = options.fd ?? 2; // Default to stderr + const colorize = options.colorize ?? true; const pinoOptions: pino.LoggerOptions = { level, base: options.baseContext ?? {}, + formatters: { + level: (label) => ({level: label}), + }, }; - if (options.redact !== false && !isRedactionDisabled()) { + if (options.redact !== false) { pinoOptions.redact = { paths: getRedactPaths(), censor: '[REDACTED]', }; } - return pino(pinoOptions, pino.destination({dest, sync: true})) as unknown as Logger; + // JSON output + if (options.json) { + return pino(pinoOptions, pino.destination({fd, sync: true})) as unknown as Logger; + } + + // Pretty print (default) + const isVerbose = level === 'debug' || level === 'trace'; + const prettyStream = pretty({ + destination: fd, + sync: true, + colorize, + ignore: 'pid,hostname', + hideObject: !isVerbose, + }); + + return pino(pinoOptions, prettyStream) as unknown as Logger; } export function createLogger(options: LoggerOptions = {}): Logger { diff --git a/packages/b2c-tooling/src/logging/redaction.ts b/packages/b2c-tooling/src/logging/redaction.ts index 2c7f43b0..cb3f486d 100644 --- a/packages/b2c-tooling/src/logging/redaction.ts +++ b/packages/b2c-tooling/src/logging/redaction.ts @@ -17,10 +17,6 @@ const REDACT_FIELDS = [ 'authorization', ]; -export function isRedactionDisabled(): boolean { - return process.env.SFCC_REDACT_SECRETS === 'false'; -} - export function getRedactPaths(): string[] { return REDACT_FIELDS.flatMap((field) => [field, `*.${field}`]); } diff --git a/packages/b2c-tooling/src/logging/types.ts b/packages/b2c-tooling/src/logging/types.ts index 7d742ef0..21f66fc5 100644 --- a/packages/b2c-tooling/src/logging/types.ts +++ b/packages/b2c-tooling/src/logging/types.ts @@ -9,10 +9,18 @@ export interface LogContext { } export interface LoggerOptions { + /** Log level. Default: 'info' */ level?: LogLevel; - destination?: NodeJS.WritableStream; + /** File descriptor to write to (1=stdout, 2=stderr). Default: 2 */ + fd?: number; + /** Base context included in all log entries */ baseContext?: LogContext; + /** Enable secret redaction. Default: true */ redact?: boolean; + /** Output JSON lines instead of pretty print. Default: false */ + json?: boolean; + /** Enable colors in pretty print mode. Default: true */ + colorize?: boolean; } export interface Logger { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ffdd51b4..e8ec81bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: pino: specifier: ^10.1.0 version: 10.1.0 + pino-pretty: + specifier: ^13.1.2 + version: 13.1.2 devDependencies: '@eslint/compat': specifier: ^1 @@ -2009,6 +2012,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -2058,6 +2064,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -2146,6 +2155,9 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -2423,6 +2435,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + fast-copy@3.0.2: + resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2442,6 +2457,9 @@ packages: fast-levenshtein@3.0.0: resolution: {integrity: sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-xml-parser@5.2.5: resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} hasBin: true @@ -2669,6 +2687,9 @@ packages: header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -2909,6 +2930,10 @@ packages: engines: {node: '>=10'} hasBin: true + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3357,6 +3382,10 @@ packages: pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + pino-pretty@13.1.2: + resolution: {integrity: sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ==} + hasBin: true + pino-std-serializers@7.0.0: resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} @@ -3405,6 +3434,9 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -3549,6 +3581,9 @@ packages: search-insights@2.17.3: resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -3706,6 +3741,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strnum@2.1.1: resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} @@ -6372,6 +6411,8 @@ snapshots: color-name@1.1.4: {} + colorette@2.0.20: {} + comma-separated-tokens@2.0.3: {} comment-parser@1.4.1: {} @@ -6427,6 +6468,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + dateformat@4.6.3: {} + debug@3.2.7: dependencies: ms: 2.1.3 @@ -6500,6 +6543,10 @@ snapshots: emoji-regex@8.0.0: {} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -6982,6 +7029,8 @@ snapshots: esutils@2.0.3: {} + fast-copy@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -7002,6 +7051,8 @@ snapshots: dependencies: fastest-levenshtein: 1.0.16 + fast-safe-stringify@2.1.1: {} + fast-xml-parser@5.2.5: dependencies: strnum: 2.1.1 @@ -7240,6 +7291,8 @@ snapshots: capital-case: 1.0.4 tslib: 2.8.1 + help-me@5.0.0: {} + hookable@5.5.3: {} hosted-git-info@2.8.9: {} @@ -7462,6 +7515,8 @@ snapshots: filelist: 1.0.4 picocolors: 1.1.1 + joycon@3.1.1: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -7869,6 +7924,22 @@ snapshots: dependencies: split2: 4.2.0 + pino-pretty@13.1.2: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 3.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pump: 3.0.3 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.0 + strip-json-comments: 5.0.3 + pino-std-serializers@7.0.0: {} pino@10.1.0: @@ -7913,6 +7984,11 @@ snapshots: proto-list@1.2.4: {} + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode.js@2.3.1: {} punycode@2.3.1: {} @@ -8077,6 +8153,8 @@ snapshots: search-insights@2.17.3: {} + secure-json-parse@4.1.0: {} + semver@5.7.2: {} semver@6.3.1: {} @@ -8275,6 +8353,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + strnum@2.1.1: {} superjson@2.2.5: From 15d016d292ed154bf4f162df3a28b5c11a8978c5 Mon Sep 17 00:00:00 2001 From: Charles Lavery Date: Tue, 25 Nov 2025 19:28:42 -0500 Subject: [PATCH 3/4] execution conditions; typescript module syntax --- AGENTS.md | 5 ++++ README.md | 2 ++ packages/b2c-cli/bin/dev.js | 2 +- packages/b2c-cli/src/commands/_test/index.ts | 2 +- packages/b2c-cli/src/i18n/index.ts | 2 +- packages/b2c-cli/src/index.ts | 3 +- packages/b2c-cli/tsconfig.json | 3 +- packages/b2c-tooling/package.json | 9 ++++++ packages/b2c-tooling/src/auth/api-key.ts | 2 +- packages/b2c-tooling/src/auth/basic.ts | 2 +- packages/b2c-tooling/src/auth/index.ts | 5 ++-- packages/b2c-tooling/src/auth/oauth.ts | 2 +- packages/b2c-tooling/src/cli/base-command.ts | 5 ++-- packages/b2c-tooling/src/cli/index.ts | 6 ++-- .../b2c-tooling/src/cli/instance-command.ts | 5 ++-- packages/b2c-tooling/src/cli/mrt-command.ts | 8 +++-- packages/b2c-tooling/src/cli/oauth-command.ts | 5 ++-- packages/b2c-tooling/src/i18n/index.ts | 2 +- packages/b2c-tooling/src/index.ts | 24 +++++++-------- packages/b2c-tooling/src/instance/index.ts | 2 +- packages/b2c-tooling/src/logging/logger.ts | 29 +++++++++++++++++-- packages/b2c-tooling/src/logging/redaction.ts | 22 -------------- .../b2c-tooling/src/operations/jobs/index.ts | 3 +- packages/b2c-tooling/src/platform/index.ts | 6 ++-- packages/b2c-tooling/src/platform/mrt.ts | 2 +- packages/b2c-tooling/src/platform/ods.ts | 2 +- packages/b2c-tooling/tsconfig.cjs.json | 3 +- packages/b2c-tooling/tsconfig.json | 3 +- 28 files changed, 96 insertions(+), 70 deletions(-) delete mode 100644 packages/b2c-tooling/src/logging/redaction.ts diff --git a/AGENTS.md b/AGENTS.md index ee9cb58e..20922479 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,14 @@ +- this is a monorepo project; packages: + - ./packages/b2c-cli - the command line interface built with oclif + - ./packages/b2c-tooling - the SDK/library for B2C Commerce operations; support the CLI and can be used standalone + ## Setup/Packaging - use `pnpm` over `npm` for package management - the `pnpm run test` commands also run the linter after tests - use `pnpm run -r format` (or individually in packages) to format code with prettier +- use `exports` field in package.json files to define public API surface for packages; use `development` field for nodejs --conditions for development ergonomics (packages/b2c-cli/bin/dev.js will use this condition) ## Documentation diff --git a/README.md b/README.md index efe31f45..8fed5072 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ - Separate CLI and SDK - Logging + - redaction - Localization Support +- supply chain security ## CLI diff --git a/packages/b2c-cli/bin/dev.js b/packages/b2c-cli/bin/dev.js index a372dda5..b6a1f1e8 100755 --- a/packages/b2c-cli/bin/dev.js +++ b/packages/b2c-cli/bin/dev.js @@ -1,4 +1,4 @@ -#!/usr/bin/env -S node --import tsx +#!/usr/bin/env -S node --conditions development --import tsx import {execute} from '@oclif/core'; diff --git a/packages/b2c-cli/src/commands/_test/index.ts b/packages/b2c-cli/src/commands/_test/index.ts index 8eda9ed6..7fe0eddd 100644 --- a/packages/b2c-cli/src/commands/_test/index.ts +++ b/packages/b2c-cli/src/commands/_test/index.ts @@ -33,7 +33,7 @@ export default class Test extends BaseCommand { username: 'testuser', password: 'secret123', client_secret: 'abc123xyz', // eslint-disable-line camelcase - token: 'Bearer eyJhbGciOiJIUzI1NiJ9.test', + accessToken: 'eyJhbGciOiJIUzI1NiJ9.test', }, 'This should have redacted fields', ); diff --git a/packages/b2c-cli/src/i18n/index.ts b/packages/b2c-cli/src/i18n/index.ts index f2e2a818..9f363128 100644 --- a/packages/b2c-cli/src/i18n/index.ts +++ b/packages/b2c-cli/src/i18n/index.ts @@ -9,7 +9,7 @@ * this.log(t('commands.sites.list.fetching', 'Fetching sites from {{hostname}}...', { hostname })) */ -import {registerTranslations, t as toolingT, TOptions} from '@salesforce/b2c-tooling'; +import {registerTranslations, t as toolingT, type TOptions} from '@salesforce/b2c-tooling'; import {locales} from './locales/index.js'; /** The namespace used by b2c-cli messages */ diff --git a/packages/b2c-cli/src/index.ts b/packages/b2c-cli/src/index.ts index dd5a59b2..39576ff3 100644 --- a/packages/b2c-cli/src/index.ts +++ b/packages/b2c-cli/src/index.ts @@ -10,6 +10,5 @@ export { // Config utilities loadConfig, findDwJson, - ResolvedConfig, - LoadConfigOptions, } from '@salesforce/b2c-tooling/cli'; +export type {ResolvedConfig, LoadConfigOptions} from '@salesforce/b2c-tooling/cli'; diff --git a/packages/b2c-cli/tsconfig.json b/packages/b2c-cli/tsconfig.json index 10c376d8..d033f7c3 100644 --- a/packages/b2c-cli/tsconfig.json +++ b/packages/b2c-cli/tsconfig.json @@ -5,7 +5,8 @@ "rootDir": "src", "skipLibCheck": true, "esModuleInterop": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "verbatimModuleSyntax": true }, "include": ["./src/**/*"] } diff --git a/packages/b2c-tooling/package.json b/packages/b2c-tooling/package.json index a2ba116d..ee8f2c08 100644 --- a/packages/b2c-tooling/package.json +++ b/packages/b2c-tooling/package.json @@ -14,6 +14,7 @@ "type": "module", "exports": { ".": { + "development": "./src/index.ts", "import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" @@ -24,6 +25,7 @@ } }, "./auth": { + "development": "./src/auth/index.ts", "import": { "types": "./dist/esm/auth/index.d.ts", "default": "./dist/esm/auth/index.js" @@ -34,6 +36,7 @@ } }, "./instance": { + "development": "./src/instance/index.ts", "import": { "types": "./dist/esm/instance/index.d.ts", "default": "./dist/esm/instance/index.js" @@ -44,6 +47,7 @@ } }, "./platform": { + "development": "./src/platform/index.ts", "import": { "types": "./dist/esm/platform/index.d.ts", "default": "./dist/esm/platform/index.js" @@ -54,6 +58,7 @@ } }, "./operations/code": { + "development": "./src/operations/code/index.ts", "import": { "types": "./dist/esm/operations/code/index.d.ts", "default": "./dist/esm/operations/code/index.js" @@ -64,6 +69,7 @@ } }, "./operations/jobs": { + "development": "./src/operations/jobs/index.ts", "import": { "types": "./dist/esm/operations/jobs/index.d.ts", "default": "./dist/esm/operations/jobs/index.js" @@ -74,6 +80,7 @@ } }, "./operations/sites": { + "development": "./src/operations/sites/index.ts", "import": { "types": "./dist/esm/operations/sites/index.d.ts", "default": "./dist/esm/operations/sites/index.js" @@ -84,6 +91,7 @@ } }, "./cli": { + "development": "./src/cli/index.ts", "import": { "types": "./dist/esm/cli/index.d.ts", "default": "./dist/esm/cli/index.js" @@ -94,6 +102,7 @@ } }, "./logging": { + "development": "./src/logging/index.ts", "import": { "types": "./dist/esm/logging/index.d.ts", "default": "./dist/esm/logging/index.js" diff --git a/packages/b2c-tooling/src/auth/api-key.ts b/packages/b2c-tooling/src/auth/api-key.ts index 25fca54e..6cf81f75 100644 --- a/packages/b2c-tooling/src/auth/api-key.ts +++ b/packages/b2c-tooling/src/auth/api-key.ts @@ -1,4 +1,4 @@ -import {AuthStrategy} from './types.js'; +import type {AuthStrategy} from './types.js'; export class ApiKeyStrategy implements AuthStrategy { constructor( diff --git a/packages/b2c-tooling/src/auth/basic.ts b/packages/b2c-tooling/src/auth/basic.ts index 2476c43e..e9fdd527 100644 --- a/packages/b2c-tooling/src/auth/basic.ts +++ b/packages/b2c-tooling/src/auth/basic.ts @@ -1,4 +1,4 @@ -import {AuthStrategy} from './types.js'; +import type {AuthStrategy} from './types.js'; export class BasicAuthStrategy implements AuthStrategy { private encoded: string; diff --git a/packages/b2c-tooling/src/auth/index.ts b/packages/b2c-tooling/src/auth/index.ts index c6a2036f..50a0998a 100644 --- a/packages/b2c-tooling/src/auth/index.ts +++ b/packages/b2c-tooling/src/auth/index.ts @@ -30,7 +30,8 @@ * * @module auth */ -export {AuthStrategy, AccessTokenResponse, DecodedJWT} from './types.js'; +export type {AuthStrategy, AccessTokenResponse, DecodedJWT} from './types.js'; export {BasicAuthStrategy} from './basic.js'; -export {OAuthStrategy, OAuthConfig, decodeJWT} from './oauth.js'; +export {OAuthStrategy, decodeJWT} from './oauth.js'; +export type {OAuthConfig} from './oauth.js'; export {ApiKeyStrategy} from './api-key.js'; diff --git a/packages/b2c-tooling/src/auth/oauth.ts b/packages/b2c-tooling/src/auth/oauth.ts index ad5d1611..09bbe38e 100644 --- a/packages/b2c-tooling/src/auth/oauth.ts +++ b/packages/b2c-tooling/src/auth/oauth.ts @@ -1,4 +1,4 @@ -import {AuthStrategy, AccessTokenResponse, DecodedJWT} from './types.js'; +import type {AuthStrategy, AccessTokenResponse, DecodedJWT} from './types.js'; import {getLogger} from '../logger.js'; const DEFAULT_ACCOUNT_MANAGER_HOST = 'account.demandware.com'; diff --git a/packages/b2c-tooling/src/cli/base-command.ts b/packages/b2c-tooling/src/cli/base-command.ts index c0e5ca38..97eb78e6 100644 --- a/packages/b2c-tooling/src/cli/base-command.ts +++ b/packages/b2c-tooling/src/cli/base-command.ts @@ -1,5 +1,6 @@ -import {Command, Flags, Interfaces} from '@oclif/core'; -import {loadConfig, ResolvedConfig, LoadConfigOptions} from './config.js'; +import {Command, Flags, type Interfaces} from '@oclif/core'; +import {loadConfig} from './config.js'; +import type {ResolvedConfig, LoadConfigOptions} from './config.js'; import {setLanguage} from '../i18n/index.js'; import {configureLogger, getLogger, type LogLevel, type Logger} from '../logging/index.js'; diff --git a/packages/b2c-tooling/src/cli/index.ts b/packages/b2c-tooling/src/cli/index.ts index 6edd8838..026bcef8 100644 --- a/packages/b2c-tooling/src/cli/index.ts +++ b/packages/b2c-tooling/src/cli/index.ts @@ -1,8 +1,10 @@ // Base command classes -export {BaseCommand, Flags, Args} from './base-command.js'; +export {BaseCommand} from './base-command.js'; +export type {Flags, Args} from './base-command.js'; export {OAuthCommand} from './oauth-command.js'; export {InstanceCommand} from './instance-command.js'; export {MrtCommand} from './mrt-command.js'; // Config utilities -export {loadConfig, findDwJson, ResolvedConfig, LoadConfigOptions} from './config.js'; +export {loadConfig, findDwJson} from './config.js'; +export type {ResolvedConfig, LoadConfigOptions} from './config.js'; diff --git a/packages/b2c-tooling/src/cli/instance-command.ts b/packages/b2c-tooling/src/cli/instance-command.ts index 15b74412..2e3bedb9 100644 --- a/packages/b2c-tooling/src/cli/instance-command.ts +++ b/packages/b2c-tooling/src/cli/instance-command.ts @@ -1,7 +1,8 @@ import {Command, Flags} from '@oclif/core'; import {OAuthCommand} from './oauth-command.js'; -import {loadConfig, ResolvedConfig, LoadConfigOptions} from './config.js'; -import {AuthStrategy} from '../auth/types.js'; +import {loadConfig} from './config.js'; +import type {ResolvedConfig, LoadConfigOptions} from './config.js'; +import type {AuthStrategy} from '../auth/types.js'; import {BasicAuthStrategy} from '../auth/basic.js'; import {OAuthStrategy} from '../auth/oauth.js'; import {B2CInstance} from '../instance/index.js'; diff --git a/packages/b2c-tooling/src/cli/mrt-command.ts b/packages/b2c-tooling/src/cli/mrt-command.ts index ea7ecb4e..53b25296 100644 --- a/packages/b2c-tooling/src/cli/mrt-command.ts +++ b/packages/b2c-tooling/src/cli/mrt-command.ts @@ -1,9 +1,11 @@ import {Command, Flags} from '@oclif/core'; import {BaseCommand} from './base-command.js'; -import {loadConfig, ResolvedConfig, LoadConfigOptions} from './config.js'; -import {AuthStrategy} from '../auth/types.js'; +import {loadConfig} from './config.js'; +import type {ResolvedConfig, LoadConfigOptions} from './config.js'; +import type {AuthStrategy} from '../auth/types.js'; import {ApiKeyStrategy} from '../auth/api-key.js'; -import {MrtClient, MrtProject} from '../platform/mrt.js'; +import {MrtClient} from '../platform/mrt.js'; +import type {MrtProject} from '../platform/mrt.js'; import {t} from '../i18n/index.js'; /** diff --git a/packages/b2c-tooling/src/cli/oauth-command.ts b/packages/b2c-tooling/src/cli/oauth-command.ts index ad20223e..4a4de29d 100644 --- a/packages/b2c-tooling/src/cli/oauth-command.ts +++ b/packages/b2c-tooling/src/cli/oauth-command.ts @@ -1,7 +1,8 @@ import {Command, Flags} from '@oclif/core'; import {BaseCommand} from './base-command.js'; -import {loadConfig, ResolvedConfig, LoadConfigOptions} from './config.js'; -import {AuthStrategy} from '../auth/types.js'; +import {loadConfig} from './config.js'; +import type {ResolvedConfig, LoadConfigOptions} from './config.js'; +import type {AuthStrategy} from '../auth/types.js'; import {OAuthStrategy} from '../auth/oauth.js'; import {t} from '../i18n/index.js'; diff --git a/packages/b2c-tooling/src/i18n/index.ts b/packages/b2c-tooling/src/i18n/index.ts index 1be9aef3..11162948 100644 --- a/packages/b2c-tooling/src/i18n/index.ts +++ b/packages/b2c-tooling/src/i18n/index.ts @@ -53,7 +53,7 @@ * * @module i18n */ -import i18next, {TOptions, Resource} from 'i18next'; +import i18next, {type TOptions, type Resource} from 'i18next'; // Re-export TOptions for consumers export type {TOptions} from 'i18next'; diff --git a/packages/b2c-tooling/src/index.ts b/packages/b2c-tooling/src/index.ts index e2a997a3..522ecb5b 100644 --- a/packages/b2c-tooling/src/index.ts +++ b/packages/b2c-tooling/src/index.ts @@ -11,28 +11,24 @@ export {t, setLanguage, getLanguage, getI18nInstance, registerTranslations, B2C_ export type {TOptions} from './i18n/index.js'; // Auth Layer - Strategies -export { - AuthStrategy, - AccessTokenResponse, - DecodedJWT, - BasicAuthStrategy, - OAuthStrategy, - OAuthConfig, - ApiKeyStrategy, - decodeJWT, -} from './auth/index.js'; +export {BasicAuthStrategy, OAuthStrategy, ApiKeyStrategy, decodeJWT} from './auth/index.js'; +export type {AuthStrategy, AccessTokenResponse, DecodedJWT, OAuthConfig} from './auth/index.js'; // Context Layer - Instance -export {B2CInstance, InstanceConfig} from './instance/index.js'; +export {B2CInstance} from './instance/index.js'; +export type {InstanceConfig} from './instance/index.js'; // Context Layer - Platform -export {MrtClient, MrtProject, OdsClient, OdsConfig} from './platform/index.js'; +export {MrtClient, OdsClient} from './platform/index.js'; +export type {MrtProject, OdsConfig} from './platform/index.js'; // Operations - Code export {uploadCartridges, activateCodeVersion} from './operations/code/index.js'; // Operations - Jobs -export {runJob, getJobStatus, JobExecutionResult} from './operations/jobs/index.js'; +export {runJob, getJobStatus} from './operations/jobs/index.js'; +export type {JobExecutionResult} from './operations/jobs/index.js'; // Operations - Sites -export {listSites, getSite, Site} from './operations/sites/index.js'; +export {listSites, getSite} from './operations/sites/index.js'; +export type {Site} from './operations/sites/index.js'; diff --git a/packages/b2c-tooling/src/instance/index.ts b/packages/b2c-tooling/src/instance/index.ts index 61ad390c..dc34ad3e 100644 --- a/packages/b2c-tooling/src/instance/index.ts +++ b/packages/b2c-tooling/src/instance/index.ts @@ -27,7 +27,7 @@ * * @module instance */ -import {AuthStrategy} from '../auth/types.js'; +import type {AuthStrategy} from '../auth/types.js'; export interface InstanceConfig { hostname: string; diff --git a/packages/b2c-tooling/src/logging/logger.ts b/packages/b2c-tooling/src/logging/logger.ts index 306249cb..25cf0bb3 100644 --- a/packages/b2c-tooling/src/logging/logger.ts +++ b/packages/b2c-tooling/src/logging/logger.ts @@ -5,7 +5,30 @@ import pino from 'pino'; import pretty from 'pino-pretty'; import type {Logger, LoggerOptions} from './types.js'; -import {getRedactPaths} from './redaction.js'; + +const REDACT_FIELDS = [ + 'password', + 'client_secret', + 'clientSecret', + 'access_token', + 'accessToken', + 'refresh_token', + 'refreshToken', + 'api_key', + 'apiKey', + 'token', + 'secret', + 'authorization', +]; + +const REDACT_PATHS = REDACT_FIELDS.flatMap((field) => [field, `*.${field}`]); + +function censor(value: unknown): string { + if (typeof value === 'string' && value.length > 10) { + return `${value.slice(0, 4)}REDACTED`; + } + return 'REDACTED'; +} let globalLogger: Logger | null = null; let globalOptions: LoggerOptions = {level: 'info'}; @@ -25,8 +48,8 @@ function createPinoLogger(options: LoggerOptions): Logger { if (options.redact !== false) { pinoOptions.redact = { - paths: getRedactPaths(), - censor: '[REDACTED]', + paths: REDACT_PATHS, + censor, }; } diff --git a/packages/b2c-tooling/src/logging/redaction.ts b/packages/b2c-tooling/src/logging/redaction.ts deleted file mode 100644 index cb3f486d..00000000 --- a/packages/b2c-tooling/src/logging/redaction.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Redaction configuration for pino. - */ - -const REDACT_FIELDS = [ - 'password', - 'client_secret', - 'clientSecret', - 'access_token', - 'accessToken', - 'refresh_token', - 'refreshToken', - 'api_key', - 'apiKey', - 'token', - 'secret', - 'authorization', -]; - -export function getRedactPaths(): string[] { - return REDACT_FIELDS.flatMap((field) => [field, `*.${field}`]); -} diff --git a/packages/b2c-tooling/src/operations/jobs/index.ts b/packages/b2c-tooling/src/operations/jobs/index.ts index 5b38d535..324f9161 100644 --- a/packages/b2c-tooling/src/operations/jobs/index.ts +++ b/packages/b2c-tooling/src/operations/jobs/index.ts @@ -41,4 +41,5 @@ * * @module operations/jobs */ -export {runJob, getJobStatus, JobExecutionResult} from './run.js'; +export {runJob, getJobStatus} from './run.js'; +export type {JobExecutionResult} from './run.js'; diff --git a/packages/b2c-tooling/src/platform/index.ts b/packages/b2c-tooling/src/platform/index.ts index 05b359d2..1cc57525 100644 --- a/packages/b2c-tooling/src/platform/index.ts +++ b/packages/b2c-tooling/src/platform/index.ts @@ -32,5 +32,7 @@ * * @module platform */ -export {MrtClient, MrtProject} from './mrt.js'; -export {OdsClient, OdsConfig} from './ods.js'; +export {MrtClient} from './mrt.js'; +export type {MrtProject} from './mrt.js'; +export {OdsClient} from './ods.js'; +export type {OdsConfig} from './ods.js'; diff --git a/packages/b2c-tooling/src/platform/mrt.ts b/packages/b2c-tooling/src/platform/mrt.ts index 95097cb9..f8eb65ba 100644 --- a/packages/b2c-tooling/src/platform/mrt.ts +++ b/packages/b2c-tooling/src/platform/mrt.ts @@ -1,4 +1,4 @@ -import {AuthStrategy} from '../auth/types.js'; +import type {AuthStrategy} from '../auth/types.js'; export interface MrtProject { org: string; diff --git a/packages/b2c-tooling/src/platform/ods.ts b/packages/b2c-tooling/src/platform/ods.ts index 4eaee515..faca3c14 100644 --- a/packages/b2c-tooling/src/platform/ods.ts +++ b/packages/b2c-tooling/src/platform/ods.ts @@ -1,4 +1,4 @@ -import {AuthStrategy} from '../auth/types.js'; +import type {AuthStrategy} from '../auth/types.js'; export interface OdsConfig { region?: string; diff --git a/packages/b2c-tooling/tsconfig.cjs.json b/packages/b2c-tooling/tsconfig.cjs.json index a77f3445..1d4664e1 100644 --- a/packages/b2c-tooling/tsconfig.cjs.json +++ b/packages/b2c-tooling/tsconfig.cjs.json @@ -4,6 +4,7 @@ "module": "CommonJS", "moduleResolution": "node", "outDir": "dist/cjs", - "rootDir": "src" + "rootDir": "src", + "verbatimModuleSyntax": false } } diff --git a/packages/b2c-tooling/tsconfig.json b/packages/b2c-tooling/tsconfig.json index ee2902f8..2ff87a3b 100644 --- a/packages/b2c-tooling/tsconfig.json +++ b/packages/b2c-tooling/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "skipLibCheck": true, "esModuleInterop": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "verbatimModuleSyntax": true }, "include": ["src/**/*"] } From 5203f3956ccb0b208c5ca68edb3afff8861199c6 Mon Sep 17 00:00:00 2001 From: Charles Lavery Date: Tue, 25 Nov 2025 19:33:38 -0500 Subject: [PATCH 4/4] test fix --- packages/b2c-cli/test/commands/_test/index.test.ts | 9 +++++++++ packages/b2c-cli/test/commands/hello/index.test.ts | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 packages/b2c-cli/test/commands/_test/index.test.ts delete mode 100644 packages/b2c-cli/test/commands/hello/index.test.ts diff --git a/packages/b2c-cli/test/commands/_test/index.test.ts b/packages/b2c-cli/test/commands/_test/index.test.ts new file mode 100644 index 00000000..1faec0ad --- /dev/null +++ b/packages/b2c-cli/test/commands/_test/index.test.ts @@ -0,0 +1,9 @@ +import {runCommand} from '@oclif/test'; +import {expect} from 'chai'; + +describe('_test', () => { + it('runs the smoke test command without errors', async () => { + const {error} = await runCommand('_test'); + expect(error).to.be.undefined; + }); +}); diff --git a/packages/b2c-cli/test/commands/hello/index.test.ts b/packages/b2c-cli/test/commands/hello/index.test.ts deleted file mode 100644 index 432cc127..00000000 --- a/packages/b2c-cli/test/commands/hello/index.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {runCommand} from '@oclif/test'; -import {expect} from 'chai'; - -describe('hello', () => { - it('runs hello', async () => { - const {stdout} = await runCommand('hello bob --from jane'); - expect(stdout).to.contain('hello bob from jane!'); - }); -});