diff --git a/package-lock.json b/package-lock.json index 96e04f05c..618af877d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5971,6 +5971,7 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -24328,7 +24329,6 @@ "license": "MIT", "dependencies": { "@readme/api-core": "file:../core", - "chalk": "^5.3.0", "ci-info": "^4.0.0", "commander": "^14.0.0", "emphasize": "^7.0.0", @@ -24340,7 +24340,6 @@ "lodash-es": "^4.17.21", "oas": "^27.0.0", "oas-normalize": "^14.0.0", - "ora": "^8.0.1", "preferred-pm": "^4.0.0", "prompts": "^2.4.2", "semver": "^7.3.8", @@ -24666,12 +24665,14 @@ "dependencies": { "@readme/oas-to-har": "^26.0.0", "caseless": "^0.12.0", + "chalk": "^5.4.1", "datauri": "^4.1.0", "fetch-har": "^11.0.1", "json-schema-to-ts": "^3.0.0", "json-schema-traverse": "^1.0.0", "lodash.merge": "^4.6.2", "oas": "^27.0.0", + "ora": "^8.0.1", "remove-undefined-objects": "^6.0.0" }, "devDependencies": { diff --git a/packages/api/src/bin.ts b/packages/api/src/bin.ts index 679842c18..3a68f8340 100644 --- a/packages/api/src/bin.ts +++ b/packages/api/src/bin.ts @@ -1,8 +1,8 @@ +import { logger } from '@readme/api-core/logger'; import { Command } from 'commander'; import updateNotifier from 'update-notifier'; import commands from './commands/index.js'; -import logger from './logger.js'; import * as pkg from './packageInfo.js'; // eslint-disable-line readme/no-wildcard-imports updateNotifier({ pkg: { name: pkg.PACKAGE_NAME, version: pkg.PACKAGE_VERSION } }).notify(); diff --git a/packages/api/src/codegen/languages/typescript/index.ts b/packages/api/src/codegen/languages/typescript/index.ts index 2b0c8d288..100fb3f22 100644 --- a/packages/api/src/codegen/languages/typescript/index.ts +++ b/packages/api/src/codegen/languages/typescript/index.ts @@ -19,6 +19,7 @@ import type { JsonObject, PackageJson, TsConfigJson } from 'type-fest'; import path from 'node:path'; +import { logger } from '@readme/api-core/logger'; import corePkg from '@readme/api-core/package.json' with { type: 'json' }; import { execa } from 'execa'; import { getLicense } from 'license'; @@ -28,7 +29,6 @@ import semver from 'semver'; import { IndentationText, Project, QuoteKind, ScriptTarget, VariableDeclarationKind } from 'ts-morph'; import { buildCodeSnippetForOperation, getSuggestedOperation } from '../../../lib/suggestedOperations.js'; -import logger from '../../../logger.js'; import { PACKAGE_VERSION } from '../../../packageInfo.js'; import Storage from '../../../storage.js'; import CodeGenerator from '../../codegenerator.js'; @@ -433,6 +433,52 @@ sdk.server('https://eu.api.example.com/v14');`), }, ], }, + { + name: 'debug', + parameters: [], + returnType: 'SDK', + statements: writer => { + writer.writeLine('this.core.setDebugMode(true);'); + writer.writeLine('const self = this;'); + writer.writeLine('return new Proxy(this, {'); + writer.writeLine('get(target: SDK, prop: keyof SDK) {'); + writer.writeLine('if (typeof target[prop] === "function" && prop !== \'debug\') {'); + writer.writeLine('return async(...args: unknown[]) => {'); + writer.writeLine('try {'); + writer.writeLine('return await (target[prop] as Function).apply(target, args);'); + writer.writeLine('} catch (err) {'); + writer.writeLine('throw err;'); + writer.writeLine('} finally {'); + writer.writeLine('self.core.setDebugMode(false);'); + writer.writeLine('}'); + writer.writeLine('}'); + writer.writeLine('}'); + writer.writeLine('return Reflect.get(target, prop);'); + writer.writeLine('},'); + writer.writeLine('});'); + return writer; + }, + docs: [ + { + description: writer => + writer.writeLine( + wordWrap(`Enables debug mode for SDK operations. Debug mode captures additional internal information such as request/response payloads and timing, which may assist in troubleshooting issues during development. + +This method can be used in two modes: + +- **Global mode**: Calls \`sdk.debug();\` and enables debug logging for all subsequent operations. +- **Chained mode**: Calls \`sdk.debug().operation();\` and enables debug logging only for the single operation invoked. Debug mode is automatically turned off afterward. + +@example Global debug mode +sdk.debug(); +sdk.getPets(); + +@example Chained debug mode (single operation) +sdk.debug().getPets();`), + ), + }, + ], + }, ]); // Add all available operation ID accessors into the SDK. diff --git a/packages/api/src/commands/install.ts b/packages/api/src/commands/install.ts index 6c7bf17fd..eb2e04e10 100644 --- a/packages/api/src/commands/install.ts +++ b/packages/api/src/commands/install.ts @@ -1,5 +1,6 @@ import type { SupportedLanguage } from '../codegen/factory.js'; +import { logger, oraOptions } from '@readme/api-core/logger'; import chalk from 'chalk'; import { Command, Option } from 'commander'; import { createEmphasize, common } from 'emphasize'; @@ -11,7 +12,6 @@ import uslug from 'uslug'; import { SupportedLanguages, codegenFactory } from '../codegen/factory.js'; import Fetcher from '../fetcher.js'; import promptTerminal from '../lib/prompt.js'; -import logger, { oraOptions } from '../logger.js'; import Storage from '../storage.js'; const { highlight } = createEmphasize(common); diff --git a/packages/api/src/commands/list.ts b/packages/api/src/commands/list.ts index aa24b0bc9..40e54e8c9 100644 --- a/packages/api/src/commands/list.ts +++ b/packages/api/src/commands/list.ts @@ -1,8 +1,8 @@ +import { logger } from '@readme/api-core/logger'; import chalk from 'chalk'; import { Command } from 'commander'; import { SupportedLanguages } from '../codegen/factory.js'; -import logger from '../logger.js'; import Storage from '../storage.js'; const cmd = new Command(); diff --git a/packages/api/src/commands/uninstall.ts b/packages/api/src/commands/uninstall.ts index 08eddf273..80745771a 100644 --- a/packages/api/src/commands/uninstall.ts +++ b/packages/api/src/commands/uninstall.ts @@ -1,12 +1,12 @@ import path from 'node:path'; +import { logger, oraOptions } from '@readme/api-core/logger'; import chalk from 'chalk'; import { Command, Option } from 'commander'; import ora from 'ora'; import { SupportedLanguages, uninstallerFactory } from '../codegen/factory.js'; import promptTerminal from '../lib/prompt.js'; -import logger, { oraOptions } from '../logger.js'; import Storage from '../storage.js'; interface Options { diff --git a/packages/core/package.json b/packages/core/package.json index c12bfac15..47d2214b2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -21,6 +21,10 @@ "require": "./dist/types.d.cjs", "import": "./dist/types.d.js" }, + "./logger": { + "require": "./dist/logger.cjs", + "import": "./dist/logger.js" + }, "./package.json": "./package.json" }, "main": "dist/index.cjs", @@ -51,12 +55,14 @@ "dependencies": { "@readme/oas-to-har": "^26.0.0", "caseless": "^0.12.0", + "chalk": "^5.4.1", "datauri": "^4.1.0", "fetch-har": "^11.0.1", "json-schema-to-ts": "^3.0.0", "json-schema-traverse": "^1.0.0", "lodash.merge": "^4.6.2", "oas": "^27.0.0", + "ora": "^8.0.1", "remove-undefined-objects": "^6.0.0" }, "devDependencies": { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index dbf6ac6b5..61f7bb099 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -10,6 +10,7 @@ import Oas from 'oas'; import FetchError from './errors/fetchError.js'; import { parseResponse, prepareAuth, prepareParams, prepareServer } from './lib/index.js'; +import { logger } from './logger.js'; export default class APICore { spec!: Oas; @@ -27,6 +28,8 @@ export default class APICore { private userAgent!: string; + private debugMode: boolean = false; + constructor(definition?: OASDocument | Record, userAgent?: string) { if (definition) this.spec = Oas.init(definition); if (userAgent) this.userAgent = userAgent; @@ -56,6 +59,11 @@ export default class APICore { return this; } + setDebugMode(debugMode: boolean) { + this.debugMode = debugMode; + return this; + } + async fetch( path: string, method: HttpMethods, @@ -96,6 +104,10 @@ export default class APICore { const har = this.getHARForRequest(operation, data, prepareAuth(this.auth, operation)); + if (this.debugMode) { + logger(`[DEBUG] HAR: ${JSON.stringify(har, null, 2)}`); + } + let timeoutSignal: NodeJS.Timeout; const init: RequestInit = {}; if (this.config.timeout) { diff --git a/packages/api/src/logger.ts b/packages/core/src/logger.ts similarity index 86% rename from packages/api/src/logger.ts rename to packages/core/src/logger.ts index 128c0f0d1..195a186a5 100644 --- a/packages/api/src/logger.ts +++ b/packages/core/src/logger.ts @@ -3,7 +3,7 @@ import type { Options as OraOptions } from 'ora'; import chalk from 'chalk'; -export default function logger(log: string, error?: boolean) { +export function logger(log: string, error?: boolean) { if (error) { console.error(chalk.red(log)); } else { diff --git a/packages/core/tsup.config.ts b/packages/core/tsup.config.ts index ac904e727..6dc891414 100644 --- a/packages/core/tsup.config.ts +++ b/packages/core/tsup.config.ts @@ -10,7 +10,7 @@ export default defineConfig((options: Options) => ({ ...options, ...config, - entry: ['src/errors/fetchError.ts', 'src/lib/index.ts', 'src/index.ts', 'src/types.ts'], + entry: ['src/errors/fetchError.ts', 'src/lib/index.ts', 'src/index.ts', 'src/types.ts', 'src/logger.ts'], noExternal: [ // These dependencies are ESM-only but because we're building for ESM **and** CJS we can't diff --git a/packages/test-utils/sdks/alby/src/sdk.ts b/packages/test-utils/sdks/alby/src/sdk.ts index 74bd718af..d47e135a9 100644 --- a/packages/test-utils/sdks/alby/src/sdk.ts +++ b/packages/test-utils/sdks/alby/src/sdk.ts @@ -72,6 +72,47 @@ export default class SDK { this.core.setServer(url, variables); } + /** + * Enables debug mode for SDK operations. Debug mode captures additional internal + * information such as request/response payloads and timing, which may assist in + * troubleshooting issues during development. + * + * This method can be used in two modes: + * + * - **Global mode**: Calls `sdk.debug();` and enables debug logging for all subsequent + * operations. + * - **Chained mode**: Calls `sdk.debug().operation();` and enables debug logging only for + * the single operation invoked. Debug mode is automatically turned off afterward. + * + * @example Global debug mode + * sdk.debug(); + * sdk.getPets(); + * + * @example Chained debug mode (single operation) + * sdk.debug().getPets(); + * + */ + debug(): SDK { + this.core.setDebugMode(true); + const self = this; + return new Proxy(this, { + get(target: SDK, prop: keyof SDK) { + if (typeof target[prop] === "function" && prop !== 'debug') { + return async (...args: unknown[]) => { + try { + return await (target[prop] as Function).apply(target, args); + } catch (err) { + throw err; + } finally { + self.core.setDebugMode(false); + } + } + } + return Reflect.get(target, prop); + }, + }); + } + /** * List all applications for the specified account ID. * diff --git a/packages/test-utils/sdks/metrotransit/src/sdk.ts b/packages/test-utils/sdks/metrotransit/src/sdk.ts index d5f6d5708..c5fcfcd88 100644 --- a/packages/test-utils/sdks/metrotransit/src/sdk.ts +++ b/packages/test-utils/sdks/metrotransit/src/sdk.ts @@ -72,6 +72,47 @@ export default class SDK { this.core.setServer(url, variables); } + /** + * Enables debug mode for SDK operations. Debug mode captures additional internal + * information such as request/response payloads and timing, which may assist in + * troubleshooting issues during development. + * + * This method can be used in two modes: + * + * - **Global mode**: Calls `sdk.debug();` and enables debug logging for all subsequent + * operations. + * - **Chained mode**: Calls `sdk.debug().operation();` and enables debug logging only for + * the single operation invoked. Debug mode is automatically turned off afterward. + * + * @example Global debug mode + * sdk.debug(); + * sdk.getPets(); + * + * @example Chained debug mode (single operation) + * sdk.debug().getPets(); + * + */ + debug(): SDK { + this.core.setDebugMode(true); + const self = this; + return new Proxy(this, { + get(target: SDK, prop: keyof SDK) { + if (typeof target[prop] === "function" && prop !== 'debug') { + return async (...args: unknown[]) => { + try { + return await (target[prop] as Function).apply(target, args); + } catch (err) { + throw err; + } finally { + self.core.setDebugMode(false); + } + } + } + return Reflect.get(target, prop); + }, + }); + } + /** @throws FetchError<400, types.ProblemDetails> Bad Request */ getNextripAgencies(): Promise> { return this.core.fetch('/nextrip/agencies', 'get'); diff --git a/packages/test-utils/sdks/operationid-quirks/src/sdk.ts b/packages/test-utils/sdks/operationid-quirks/src/sdk.ts index 606d56d37..aaa7824fc 100644 --- a/packages/test-utils/sdks/operationid-quirks/src/sdk.ts +++ b/packages/test-utils/sdks/operationid-quirks/src/sdk.ts @@ -71,6 +71,47 @@ export default class SDK { this.core.setServer(url, variables); } + /** + * Enables debug mode for SDK operations. Debug mode captures additional internal + * information such as request/response payloads and timing, which may assist in + * troubleshooting issues during development. + * + * This method can be used in two modes: + * + * - **Global mode**: Calls `sdk.debug();` and enables debug logging for all subsequent + * operations. + * - **Chained mode**: Calls `sdk.debug().operation();` and enables debug logging only for + * the single operation invoked. Debug mode is automatically turned off afterward. + * + * @example Global debug mode + * sdk.debug(); + * sdk.getPets(); + * + * @example Chained debug mode (single operation) + * sdk.debug().getPets(); + * + */ + debug(): SDK { + this.core.setDebugMode(true); + const self = this; + return new Proxy(this, { + get(target: SDK, prop: keyof SDK) { + if (typeof target[prop] === "function" && prop !== 'debug') { + return async (...args: unknown[]) => { + try { + return await (target[prop] as Function).apply(target, args); + } catch (err) { + throw err; + } finally { + self.core.setDebugMode(false); + } + } + } + return Reflect.get(target, prop); + }, + }); + } + /** * This mess of a string is intentionally nasty so we can be sure that we're not including * anything that wouldn't look right as an operationID for a potential method accessor in diff --git a/packages/test-utils/sdks/optional-payload/src/sdk.ts b/packages/test-utils/sdks/optional-payload/src/sdk.ts index 26cdac678..71519ba0d 100644 --- a/packages/test-utils/sdks/optional-payload/src/sdk.ts +++ b/packages/test-utils/sdks/optional-payload/src/sdk.ts @@ -72,6 +72,47 @@ export default class SDK { this.core.setServer(url, variables); } + /** + * Enables debug mode for SDK operations. Debug mode captures additional internal + * information such as request/response payloads and timing, which may assist in + * troubleshooting issues during development. + * + * This method can be used in two modes: + * + * - **Global mode**: Calls `sdk.debug();` and enables debug logging for all subsequent + * operations. + * - **Chained mode**: Calls `sdk.debug().operation();` and enables debug logging only for + * the single operation invoked. Debug mode is automatically turned off afterward. + * + * @example Global debug mode + * sdk.debug(); + * sdk.getPets(); + * + * @example Chained debug mode (single operation) + * sdk.debug().getPets(); + * + */ + debug(): SDK { + this.core.setDebugMode(true); + const self = this; + return new Proxy(this, { + get(target: SDK, prop: keyof SDK) { + if (typeof target[prop] === "function" && prop !== 'debug') { + return async (...args: unknown[]) => { + try { + return await (target[prop] as Function).apply(target, args); + } catch (err) { + throw err; + } finally { + self.core.setDebugMode(false); + } + } + } + return Reflect.get(target, prop); + }, + }); + } + /** * Updates a pet in the store with form data * diff --git a/packages/test-utils/sdks/petstore/src/sdk.ts b/packages/test-utils/sdks/petstore/src/sdk.ts index 40ba0e160..154a83ad1 100644 --- a/packages/test-utils/sdks/petstore/src/sdk.ts +++ b/packages/test-utils/sdks/petstore/src/sdk.ts @@ -72,6 +72,47 @@ export default class SDK { this.core.setServer(url, variables); } + /** + * Enables debug mode for SDK operations. Debug mode captures additional internal + * information such as request/response payloads and timing, which may assist in + * troubleshooting issues during development. + * + * This method can be used in two modes: + * + * - **Global mode**: Calls `sdk.debug();` and enables debug logging for all subsequent + * operations. + * - **Chained mode**: Calls `sdk.debug().operation();` and enables debug logging only for + * the single operation invoked. Debug mode is automatically turned off afterward. + * + * @example Global debug mode + * sdk.debug(); + * sdk.getPets(); + * + * @example Chained debug mode (single operation) + * sdk.debug().getPets(); + * + */ + debug(): SDK { + this.core.setDebugMode(true); + const self = this; + return new Proxy(this, { + get(target: SDK, prop: keyof SDK) { + if (typeof target[prop] === "function" && prop !== 'debug') { + return async (...args: unknown[]) => { + try { + return await (target[prop] as Function).apply(target, args); + } catch (err) { + throw err; + } finally { + self.core.setDebugMode(false); + } + } + } + return Reflect.get(target, prop); + }, + }); + } + /** * Add a new pet to the store * diff --git a/packages/test-utils/sdks/readme/src/sdk.ts b/packages/test-utils/sdks/readme/src/sdk.ts index 6f5f740fd..558d2319b 100644 --- a/packages/test-utils/sdks/readme/src/sdk.ts +++ b/packages/test-utils/sdks/readme/src/sdk.ts @@ -72,6 +72,47 @@ export default class SDK { this.core.setServer(url, variables); } + /** + * Enables debug mode for SDK operations. Debug mode captures additional internal + * information such as request/response payloads and timing, which may assist in + * troubleshooting issues during development. + * + * This method can be used in two modes: + * + * - **Global mode**: Calls `sdk.debug();` and enables debug logging for all subsequent + * operations. + * - **Chained mode**: Calls `sdk.debug().operation();` and enables debug logging only for + * the single operation invoked. Debug mode is automatically turned off afterward. + * + * @example Global debug mode + * sdk.debug(); + * sdk.getPets(); + * + * @example Chained debug mode (single operation) + * sdk.debug().getPets(); + * + */ + debug(): SDK { + this.core.setDebugMode(true); + const self = this; + return new Proxy(this, { + get(target: SDK, prop: keyof SDK) { + if (typeof target[prop] === "function" && prop !== 'debug') { + return async (...args: unknown[]) => { + try { + return await (target[prop] as Function).apply(target, args); + } catch (err) { + throw err; + } finally { + self.core.setDebugMode(false); + } + } + } + return Reflect.get(target, prop); + }, + }); + } + /** * Returns project data for the API key. * diff --git a/packages/test-utils/sdks/response-title-quirks/src/sdk.ts b/packages/test-utils/sdks/response-title-quirks/src/sdk.ts index 0e73ab1eb..596f5b1f8 100644 --- a/packages/test-utils/sdks/response-title-quirks/src/sdk.ts +++ b/packages/test-utils/sdks/response-title-quirks/src/sdk.ts @@ -72,6 +72,47 @@ export default class SDK { this.core.setServer(url, variables); } + /** + * Enables debug mode for SDK operations. Debug mode captures additional internal + * information such as request/response payloads and timing, which may assist in + * troubleshooting issues during development. + * + * This method can be used in two modes: + * + * - **Global mode**: Calls `sdk.debug();` and enables debug logging for all subsequent + * operations. + * - **Chained mode**: Calls `sdk.debug().operation();` and enables debug logging only for + * the single operation invoked. Debug mode is automatically turned off afterward. + * + * @example Global debug mode + * sdk.debug(); + * sdk.getPets(); + * + * @example Chained debug mode (single operation) + * sdk.debug().getPets(); + * + */ + debug(): SDK { + this.core.setDebugMode(true); + const self = this; + return new Proxy(this, { + get(target: SDK, prop: keyof SDK) { + if (typeof target[prop] === "function" && prop !== 'debug') { + return async (...args: unknown[]) => { + try { + return await (target[prop] as Function).apply(target, args); + } catch (err) { + throw err; + } finally { + self.core.setDebugMode(false); + } + } + } + return Reflect.get(target, prop); + }, + }); + } + getAnything(metadata: types.GetAnythingMetadataParam): Promise, types.GetAnythingResponse2XX>> { return this.core.fetch('/anything', 'get', metadata); } diff --git a/packages/test-utils/sdks/simple/src/sdk.ts b/packages/test-utils/sdks/simple/src/sdk.ts index 2c45382ad..8e54a135b 100644 --- a/packages/test-utils/sdks/simple/src/sdk.ts +++ b/packages/test-utils/sdks/simple/src/sdk.ts @@ -72,6 +72,47 @@ export default class SDK { this.core.setServer(url, variables); } + /** + * Enables debug mode for SDK operations. Debug mode captures additional internal + * information such as request/response payloads and timing, which may assist in + * troubleshooting issues during development. + * + * This method can be used in two modes: + * + * - **Global mode**: Calls `sdk.debug();` and enables debug logging for all subsequent + * operations. + * - **Chained mode**: Calls `sdk.debug().operation();` and enables debug logging only for + * the single operation invoked. Debug mode is automatically turned off afterward. + * + * @example Global debug mode + * sdk.debug(); + * sdk.getPets(); + * + * @example Chained debug mode (single operation) + * sdk.debug().getPets(); + * + */ + debug(): SDK { + this.core.setDebugMode(true); + const self = this; + return new Proxy(this, { + get(target: SDK, prop: keyof SDK) { + if (typeof target[prop] === "function" && prop !== 'debug') { + return async (...args: unknown[]) => { + try { + return await (target[prop] as Function).apply(target, args); + } catch (err) { + throw err; + } finally { + self.core.setDebugMode(false); + } + } + } + return Reflect.get(target, prop); + }, + }); + } + /** * Multiple status values can be provided with comma separated strings * diff --git a/packages/test-utils/sdks/star-trek/src/sdk.ts b/packages/test-utils/sdks/star-trek/src/sdk.ts index e2cea8df9..ce8f4fcbd 100644 --- a/packages/test-utils/sdks/star-trek/src/sdk.ts +++ b/packages/test-utils/sdks/star-trek/src/sdk.ts @@ -72,6 +72,47 @@ export default class SDK { this.core.setServer(url, variables); } + /** + * Enables debug mode for SDK operations. Debug mode captures additional internal + * information such as request/response payloads and timing, which may assist in + * troubleshooting issues during development. + * + * This method can be used in two modes: + * + * - **Global mode**: Calls `sdk.debug();` and enables debug logging for all subsequent + * operations. + * - **Chained mode**: Calls `sdk.debug().operation();` and enables debug logging only for + * the single operation invoked. Debug mode is automatically turned off afterward. + * + * @example Global debug mode + * sdk.debug(); + * sdk.getPets(); + * + * @example Chained debug mode (single operation) + * sdk.debug().getPets(); + * + */ + debug(): SDK { + this.core.setDebugMode(true); + const self = this; + return new Proxy(this, { + get(target: SDK, prop: keyof SDK) { + if (typeof target[prop] === "function" && prop !== 'debug') { + return async (...args: unknown[]) => { + try { + return await (target[prop] as Function).apply(target, args); + } catch (err) { + throw err; + } finally { + self.core.setDebugMode(false); + } + } + } + return Reflect.get(target, prop); + }, + }); + } + /** * Retrival of a single animal *