From 327815b42f0e8a11bc4ff83b6a9dc512e57bf1d2 Mon Sep 17 00:00:00 2001 From: rosie yohannan Date: Tue, 14 Oct 2025 12:36:19 +0100 Subject: [PATCH 1/4] remove snippet enricher with our own snippet generation scrips using Kong httpsnippet package --- gulp.d/tasks/build-api-docs.js | 160 ++++----- package-lock.json | 598 +++---------------------------- package.json | 2 +- scripts/generate-api-snippets.js | 509 ++++++++++++++++++++++++++ 4 files changed, 642 insertions(+), 627 deletions(-) create mode 100644 scripts/generate-api-snippets.js diff --git a/gulp.d/tasks/build-api-docs.js b/gulp.d/tasks/build-api-docs.js index be331b299e..665c29595b 100644 --- a/gulp.d/tasks/build-api-docs.js +++ b/gulp.d/tasks/build-api-docs.js @@ -102,22 +102,23 @@ function buildApiV1(callback) { /** * Build API v2 Documentation * - * STRATEGY: Sophisticated pipeline matching original CircleCI build process + * STRATEGY: Sophisticated pipeline for generating API documentation with code samples * * PIPELINE STEPS: * 1. Fetch live OpenAPI spec from CircleCI API - * 2. Prepare spec (placeholder for future code sample enrichment) - * 3. Apply JSON patches (customizations/corrections) - * 4. Bundle and optimize (remove unused components) + * 2. Bundle and resolve all $ref pointers (using Redocly) + * 3. Generate code samples with httpsnippet (for cURL, Node.js, Python, Go, Ruby) + * 4. Apply JSON patches - CURRENTLY DISABLED for simplicity * 5. Lint for quality assurance * 6. Generate final HTML with Redocly - * 7. Cleanup temporary files + * 7. Copy logo assets + * 8. Cleanup temporary files * - * WHY COMPLEX: + * WHY THIS ORDER: + * - Bundling first resolves $ref pointers, making parameters accessible + * - Code samples need resolved parameters to generate valid snippets + * - Linting catches issues before final HTML generation * - Live API ensures docs are always current - * - Patches allow customization without modifying source - * - Bundling optimizes file size and structure - * - Linting catches issues before publication */ function buildApiV2(callback) { console.log('Building API v2 documentation with full pipeline...') @@ -139,94 +140,87 @@ function buildApiV2(callback) { } console.log('āœ… OpenAPI spec fetched') - // STEP 2: Prepare spec for processing - // Add code samples using snippet-enricher-cli - console.log('šŸ“ Adding code samples to OpenAPI spec...') - exec('cd build/temp-api-v2 && ../../node_modules/.bin/snippet-enricher-cli --targets="node_request,python_python3,go_native,shell_curl" --input=openapi.json > openapi-with-examples.json', (err, stdout, stderr) => { + // STEP 2: Bundle and dereference all $ref pointers + // This resolves ALL references so snippet generation can access parameter definitions + console.log('šŸ“¦ Bundling and dereferencing API spec...') + exec('npx @redocly/cli bundle build/temp-api-v2/openapi.json --dereferenced --output build/temp-api-v2/openapi-bundled.json', (err, stdout, stderr) => { if (err) { - console.error('āŒ Failed to add code samples:', err) - console.log('ā„¹ļø Falling back to unprocessed spec...') - // Fallback: copy unprocessed file if snippet enricher fails - fs.copyFileSync('build/temp-api-v2/openapi.json', 'build/temp-api-v2/openapi-with-examples.json') - } else { - console.log('āœ… Code samples added to OpenAPI spec') + console.error('āŒ Failed to bundle API docs:', err) + return callback(err) } + console.log('āœ… API docs bundled and all references resolved') + + // STEP 3: Add code samples using our custom script with Kong's httpsnippet + // This runs AFTER bundling so all $ref pointers are resolved + console.log('šŸ“ Adding code samples to OpenAPI spec...') + exec('node scripts/generate-api-snippets.js build/temp-api-v2/openapi-bundled.json build/temp-api-v2/openapi-with-examples.json', (err, stdout, stderr) => { + if (err) { + console.error('āŒ Failed to add code samples:', err) + if (stderr) console.error(stderr) + console.log('ā„¹ļø Falling back to bundled spec without examples...') + // Fallback: copy bundled file if snippet generation fails + fs.copyFileSync('build/temp-api-v2/openapi-bundled.json', 'build/temp-api-v2/openapi-with-examples.json') + } else { + console.log('āœ… Code samples added to OpenAPI spec') + if (stdout) console.log(stdout) + } + + // STEP 4: Apply JSON patches (COMMENTED OUT - keeping simple for now) + // Allows customizing the API spec without modifying the source + // Uncomment this section when you need to apply custom patches + // console.log('šŸ”§ Applying JSON patches...') + // applyJsonPatches(() => { - // STEP 3: Apply JSON patches - // Allows customizing the API spec without modifying the source - // Patches can fix errors, add descriptions, or customize for documentation - console.log('šŸ”§ Applying JSON patches...') - applyJsonPatches(() => { + // For now, skip patching and use the spec with examples as final + console.log('ā„¹ļø Skipping JSON patches (disabled for simplicity)') + fs.copyFileSync('build/temp-api-v2/openapi-with-examples.json', 'build/temp-api-v2/openapi-final.json') - // STEP 4: Bundle and remove unused components - // Optimizes the spec by removing unreferenced schemas, reducing file size - console.log('šŸ“¦ Bundling API docs and removing unused components...') - exec('npx @redocly/cli bundle build/temp-api-v2/openapi-patched.json --remove-unused-components --output build/temp-api-v2/openapi-final.json', (err, stdout, stderr) => { + // STEP 5: Lint API docs + // Quality check to catch issues before generating final docs + console.log('šŸ” Linting API docs...') + exec('npx @redocly/cli lint build/temp-api-v2/openapi-final.json', (err, stdout, stderr) => { if (err) { - console.error('āŒ Failed to bundle API docs:', err) - return callback(err) + console.warn('āš ļø Linting warnings found, but continuing build...') + console.log(stdout) + } else { + console.log('āœ… API docs linting passed') } - console.log('āœ… API docs bundled') - // STEP 5: Lint API docs - // Quality check to catch issues before generating final docs - // Warnings don't stop the build, but errors would - console.log('šŸ” Linting API docs...') - exec('npx @redocly/cli lint build/temp-api-v2/openapi-final.json', (err, stdout, stderr) => { - if (err) { - console.warn('āš ļø Linting warnings found, but continuing build...') - console.log(stdout) - } else { - console.log('āœ… API docs linting passed') - } + // STEP 6: Build final HTML documentation + // Redocly transforms the OpenAPI spec into beautiful, interactive docs + console.log('šŸ—ļø Building docs with Redocly CLI...') - // STEP 6: Build final HTML documentation - // Redocly transforms the OpenAPI spec into beautiful, interactive docs - console.log('šŸ—ļø Building docs with Redocly CLI...') + const buildCommand = [ + 'npx @redocly/cli build-docs build/temp-api-v2/openapi-final.json', + '--output build/api/v2/index.html', + '--config redocly.yaml', + '--template custom-template.hbs', + '--title "CircleCI API v2 Documentation"', + '--disableGoogleFont=false', + ].join(' ') - // Build options for enhanced customization (all free options): - // --title: Custom page title - // --theme.openapi.disableSearch: Disable search (if needed) - // --theme.openapi.hideDownloadButton: Hide download button - // --template: Custom template (requires template file) - // --options.maxDisplayedEnumValues: Limit enum display + exec(buildCommand, (err, stdout, stderr) => { + if (err) { + console.error('āŒ Failed to build API docs:', err) + return callback(err) + } - const buildCommand = [ - 'npx @redocly/cli build-docs build/temp-api-v2/openapi-final.json', - '--output build/api/v2/index.html', - '--config redocly.yaml', - '--template custom-template.hbs', - '--title "CircleCI API v2 Documentation"', - '--disableGoogleFont=false', - // Additional options for free version: - // '--theme.openapi.hideDownloadButton=true', - // '--theme.openapi.disableSearch=true', - // '--theme.openapi.nativeScrollbars=true' - ].join(' ') + console.log('āœ… API v2 docs built successfully') - exec(buildCommand, (err, stdout, stderr) => { + // STEP 7: Copy logo file for template + console.log('šŸ“‹ Copying logo file...') + exec('cp logo.svg build/api/v2/', (err) => { if (err) { - console.error('āŒ Failed to build API docs:', err) - return callback(err) + console.warn('āš ļø Warning: Could not copy logo file:', err.message) + } else { + console.log('āœ… Logo file copied successfully') } - console.log('āœ… API v2 docs built successfully') - - // STEP 7: Copy logo file for template - console.log('šŸ“‹ Copying logo file...') - exec('cp logo.svg build/api/v2/', (err) => { - if (err) { - console.warn('āš ļø Warning: Could not copy logo file:', err.message) - } else { - console.log('āœ… Logo file copied successfully') - } - - // STEP 8: Cleanup temporary files - // Remove intermediate files to keep build directory clean - exec('rm -rf build/temp-api-v2', () => { - console.log('šŸŽ‰ API v2 documentation build completed!') - callback() - }) + // STEP 8: Cleanup temporary files + // Remove intermediate files to keep build directory clean + exec('rm -rf build/temp-api-v2', () => { + console.log('šŸŽ‰ API v2 documentation build completed!') + callback() }) }) }) diff --git a/package-lock.json b/package-lock.json index b7dd4f1702..92d1b5d7aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@redocly/cli": "^2.0.0", "browser-sync": "^3.0.4", "gulp": "^5.0.0", - "snippet-enricher-cli": "0.0.8" + "httpsnippet": "^3.0.9" } }, "node_modules/@algolia/abtesting": { @@ -812,7 +812,6 @@ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -892,7 +891,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "dev": true, - "peer": true, "engines": { "node": ">=8.0.0" } @@ -1592,7 +1590,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2119,15 +2116,6 @@ "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "dev": true }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -2504,15 +2492,6 @@ "ms": "2.0.0" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decko": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", @@ -2972,28 +2951,6 @@ "dev": true, "license": "MIT" }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -3004,21 +2961,6 @@ "node": ">= 0.6" } }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -3127,12 +3069,6 @@ "node": ">=8.6.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, "node_modules/fast-levenshtein": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", @@ -3261,19 +3197,6 @@ "node": ">= 0.8" } }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/findup-sync": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", @@ -3456,49 +3379,6 @@ "node": ">=10.13.0" } }, - "node_modules/fs-readfile-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-2.0.1.tgz", - "integrity": "sha512-7+P9eOOMnkIOmtxrBWTzWOBQlE7Nz/cBx9EYTX5hm8DzmZ/Fj9YWeUY2O9G+Q8YblScd1hyEkcmNcZMDj5U8Ug==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/fs-writefile-promise": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fs-writefile-promise/-/fs-writefile-promise-1.0.3.tgz", - "integrity": "sha512-yI+wDwj0FsgX7tyIQJR+EP60R64evMSixtGb9AzGWjJVKlF5tCet95KomfqGBg/aIAG1Dhd6wjCOQe5HbX/qLA==", - "dev": true, - "dependencies": { - "mkdirp-promise": "^1.0.0", - "pinkie-promise": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/fs-writefile-promise/node_modules/pinkie": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz", - "integrity": "sha512-VFVaU1ysKakao68ktZm76PIdOhvEfoNNRaGkyLln9Os7r0/MCxqHjHyBM7dT3pgTiBybqiPtpqKfpENwdBp50Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fs-writefile-promise/node_modules/pinkie-promise": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz", - "integrity": "sha512-5mvtVNse2Ml9zpFKkWBpGsTPwm3DKhs+c95prO/F6E7d6DN0FPqxs6LONpLNpyD7Iheb7QN4BbUoKJgo+DnkQA==", - "dev": true, - "dependencies": { - "pinkie": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3872,71 +3752,12 @@ "uglify-js": "^3.1.4" } }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/har-validator/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/har-validator/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "node_modules/har-validator-compiled": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/har-validator-compiled/-/har-validator-compiled-1.0.0.tgz", + "integrity": "sha512-dher7nFSx+Ef6OoqVveLClh8itAR3vd8Qx70Lh/hEgP1iGeARAolbci7Y8JBrHIYgFCT6xRdvvL16AR9Zh07Dw==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", @@ -4112,74 +3933,48 @@ "dev": true }, "node_modules/httpsnippet": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/httpsnippet/-/httpsnippet-1.25.0.tgz", - "integrity": "sha512-jobE6S923cLuf5BPG6Jf+oLBRkPzv2RPp0dwOHcWwj/t9FwV/t9hyZ46kpT3Q5DHn9iFNmGhrcmmFUBqyjoTQg==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/httpsnippet/-/httpsnippet-3.0.9.tgz", + "integrity": "sha512-d5GazgvgT+xd+oltAVJPe+s62mJ5DKGw7yF7Put1fimLc5KwPzbH8Ef/AOMTUwOBSeS3iDqyt9wgYHm+Jyh0CA==", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^1.1.1", - "commander": "^2.9.0", - "debug": "^2.2.0", - "event-stream": "3.3.4", - "form-data": "3.0.0", - "fs-readfile-promise": "^2.0.1", - "fs-writefile-promise": "^1.0.3", - "har-validator": "^5.0.0", - "pinkie-promise": "^2.0.0", - "stringify-object": "^3.3.0" + "chalk": "^4.1.2", + "event-stream": "4.0.1", + "form-data": "4.0.0", + "har-validator-compiled": "^1.0.0", + "stringify-object": "3.3.0", + "yargs": "^17.4.0" }, "bin": { "httpsnippet": "bin/httpsnippet" }, "engines": { - "node": ">=4" - } - }, - "node_modules/httpsnippet/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/httpsnippet/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": "^14.19.1 || ^16.14.2 || ^18.0.0 || ^20.0.0" } }, - "node_modules/httpsnippet/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "node_modules/httpsnippet/node_modules/event-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", + "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" + "duplexer": "^0.1.1", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" } }, - "node_modules/httpsnippet/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/httpsnippet/node_modules/form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -4189,25 +3984,35 @@ "node": ">= 6" } }, - "node_modules/httpsnippet/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "node_modules/httpsnippet/node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/httpsnippet/node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^2.0.0" + "through": "2" }, "engines": { - "node": ">=0.10.0" + "node": "*" } }, - "node_modules/httpsnippet/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "node_modules/httpsnippet/node_modules/stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", "dev": true, - "engines": { - "node": ">=0.8.0" + "license": "MIT", + "dependencies": { + "duplexer": "~0.1.1", + "through": "~2.3.4" } }, "node_modules/human-signals": { @@ -4778,18 +4583,6 @@ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", "dev": true }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -4856,12 +4649,6 @@ "node": ">=0.10.0" } }, - "node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, "node_modules/mark.js": { "version": "8.11.1", "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", @@ -5042,41 +4829,11 @@ "dev": true, "license": "MIT" }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "peer": true, - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mkdirp-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-1.1.0.tgz", - "integrity": "sha512-xzB0UZFcW1UGS2xkXeDh39jzTP282lb3Vwp4QzCQYmkTn4ysaV5dBdbkOXmhkcE1TQlZebQlgTceaWvDr3oFgw==", - "deprecated": "This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that.", - "dev": true, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "mkdirp": ">=0.5.0" - } - }, "node_modules/mobx": { "version": "6.13.7", "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.7.tgz", "integrity": "sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==", "dev": true, - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -5472,16 +5229,6 @@ "json-pointer": "0.6.2" } }, - "node_modules/openapi-snippet": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/openapi-snippet/-/openapi-snippet-0.9.3.tgz", - "integrity": "sha512-JmmmUkm5ZPhbdc5DJ1SWdIBjklBqemtTxIdz6Y7J5X0vsxl0HVs/uyOC4+0PMBquTyfl2OD1n/DUDekgjaFOHg==", - "dev": true, - "dependencies": { - "httpsnippet": "^1.16.7", - "openapi-sampler": "^1.0.0-beta.14" - } - }, "node_modules/opn": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", @@ -5501,42 +5248,6 @@ "integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==", "dev": true }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -5592,15 +5303,6 @@ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", "dev": true }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -5719,27 +5421,6 @@ "node": ">=6" } }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pino": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/pino/-/pino-9.2.0.tgz", @@ -5969,7 +5650,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.4.0" } @@ -6027,15 +5707,6 @@ "once": "^1.3.1" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6104,7 +5775,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6114,7 +5784,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "dev": true, - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -6314,12 +5983,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -6737,12 +6400,6 @@ "dev": true, "license": "ISC" }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", @@ -6994,108 +6651,6 @@ "node": ">=8.0.0" } }, - "node_modules/snippet-enricher-cli": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/snippet-enricher-cli/-/snippet-enricher-cli-0.0.8.tgz", - "integrity": "sha512-5dOFGau6UQJuxHdf9PNslOOx2RHXZNqKpX8khagzi0S9JEZFsEifp2xGznfCnESK3rBXlPnPyCvRZIXSTONPBg==", - "dev": true, - "dependencies": { - "js-yaml": "^3.13.1", - "openapi-snippet": "^0.9.0", - "yargs": "^15.3.1" - }, - "bin": { - "snippet-enricher-cli": "bin/snippet-enricher-cli" - } - }, - "node_modules/snippet-enricher-cli/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/snippet-enricher-cli/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/snippet-enricher-cli/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/snippet-enricher-cli/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/snippet-enricher-cli/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/snippet-enricher-cli/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/snippet-enricher-cli/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/socket.io": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", @@ -7295,18 +6850,6 @@ "node": ">= 10.13.0" } }, - "node_modules/split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", - "dev": true, - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -7317,12 +6860,6 @@ "node": ">= 10.x" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "node_modules/statuses": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", @@ -7339,15 +6876,6 @@ "integrity": "sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==", "dev": true }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1" - } - }, "node_modules/stream-composer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", @@ -7525,7 +7053,6 @@ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", "dev": true, - "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", @@ -7832,15 +7359,6 @@ "node": ">=8.11" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/uri-js-replace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", @@ -8028,12 +7546,6 @@ "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index 1d52b28b60..f41ed267a1 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@redocly/cli": "^2.0.0", "browser-sync": "^3.0.4", "gulp": "^5.0.0", - "snippet-enricher-cli": "0.0.8" + "httpsnippet": "^3.0.9" }, "dependencies": { "@asciidoctor/tabs": "^1.0.0-beta.6", diff --git a/scripts/generate-api-snippets.js b/scripts/generate-api-snippets.js new file mode 100644 index 0000000000..90ee5f5bac --- /dev/null +++ b/scripts/generate-api-snippets.js @@ -0,0 +1,509 @@ +#!/usr/bin/env node + +/** + * CircleCI API Code Snippet Generator + * + * Generates code samples from OpenAPI spec and injects them as x-code-samples. + * Uses Kong's httpsnippet library under the hood with CircleCI-specific conventions. + * + * Usage: + * node generate-api-snippets.js + * + * Example: + * node generate-api-snippets.js openapi.json openapi-with-examples.json + */ + +const fs = require('fs'); +const { HTTPSnippet } = require('httpsnippet'); + +class CircleCISnippetGenerator { + constructor(openApiSpec, options = {}) { + this.spec = openApiSpec; + this.baseUrl = this.spec.servers?.[0]?.url || 'https://circleci.com/api/v2'; + + // Default options + this.options = { + languages: options.languages || [ + { target: 'shell', client: 'curl', label: 'cURL' }, + { target: 'node', client: 'fetch', label: 'Node.js' }, + { target: 'python', client: 'python3', label: 'Python' }, + { target: 'go', client: 'native', label: 'Go' }, + { target: 'ruby', client: 'native', label: 'Ruby' } + ], + preferredAuth: options.preferredAuth || 'api_key_header', + ...options + }; + } + + /** + * Build an HTTPSnippet-compatible request object from an OpenAPI operation + * Creates a HAR-format request that Kong's httpsnippet library can convert + * @param {string} path - The API path + * @param {string} method - The HTTP method + * @param {object} operation - The OpenAPI operation object + * @param {array} pathLevelParams - Optional path-level parameters to merge + */ + buildRequest(path, method, operation, pathLevelParams = []) { + // Merge path-level and operation-level parameters for this request only + // This avoids mutating the original spec object + const mergedOperation = { + ...operation, + parameters: [...pathLevelParams, ...(operation.parameters || [])] + }; + + const request = { + method: method.toUpperCase(), + url: this.buildUrl(path, mergedOperation), + headers: this.buildHeaders(mergedOperation), + queryString: this.buildQueryString(mergedOperation), + }; + + // Add request body if applicable + const bodyContent = this.buildRequestBody(mergedOperation); + if (bodyContent) { + request.postData = bodyContent; + } + + return request; + } + + /** + * Build the full URL with path parameters replaced + */ + buildUrl(path, operation) { + let url = `${this.baseUrl}${path}`; + + // Replace path parameters with example values + const pathParams = (operation.parameters || []).filter(p => p.in === 'path'); + pathParams.forEach(param => { + const example = this.getParameterExample(param); + url = url.replace(`{${param.name}}`, example); + }); + + return url; + } + + /** + * Build headers including authentication + */ + buildHeaders(operation) { + const headers = []; + + // Add authentication header (CircleCI preferred method) + const securityScheme = this.getSecurityScheme(operation); + if (securityScheme?.type === 'apiKey' && securityScheme.in === 'header') { + headers.push({ + name: securityScheme.name, + value: this.getAuthTokenPlaceholder(securityScheme.name) + }); + } + + // Add Content-Type for requests with bodies + if (operation.requestBody) { + const contentType = Object.keys(operation.requestBody.content || {})[0]; + if (contentType) { + headers.push({ + name: 'Content-Type', + value: contentType + }); + } + } + + return headers; + } + + /** + * Build query string parameters + */ + buildQueryString(operation) { + const queryParams = (operation.parameters || []).filter(p => p.in === 'query'); + return queryParams.map(param => ({ + name: param.name, + value: this.getParameterExample(param) + })); + } + + /** + * Build request body + */ + buildRequestBody(operation) { + if (!operation.requestBody?.content) { + return null; + } + + const contentType = Object.keys(operation.requestBody.content)[0]; + const schema = operation.requestBody.content[contentType]?.schema; + + if (!schema) { + return null; + } + + const mimeType = contentType || 'application/json'; + const exampleBody = this.generateExampleFromSchema(schema); + + return { + mimeType, + text: JSON.stringify(exampleBody, null, 2) + }; + } + + /** + * Get the appropriate security scheme for an operation + */ + getSecurityScheme(operation) { + // Check operation-level security first, fall back to global + const security = operation.security || this.spec.security || []; + + // Find the preferred auth method + const preferredSecurity = security.find(s => + Object.keys(s).includes(this.options.preferredAuth) + ); + + const schemeName = preferredSecurity + ? Object.keys(preferredSecurity)[0] + : Object.keys(security[0] || {})[0]; + + return this.spec.components?.securitySchemes?.[schemeName]; + } + + /** + * Get a sensible placeholder for auth tokens + */ + getAuthTokenPlaceholder(headerName) { + const placeholders = { + 'Circle-Token': 'CIRCLE_TOKEN', + 'circle-token': 'CIRCLE_TOKEN', + 'Authorization': 'YOUR_API_TOKEN' + }; + return placeholders[headerName] || 'YOUR_TOKEN'; + } + + /** + * Get an example value for a parameter + */ + getParameterExample(param) { + // Use example if provided + if (param.example !== undefined) { + return String(param.example); + } + + // Use first enum value if available + if (param.schema?.enum?.[0] !== undefined) { + return String(param.schema.enum[0]); + } + + // Generate based on type + const type = param.schema?.type || 'string'; + const examples = { + string: param.name.includes('id') ? '497f6eca-6276-4993-bfeb-53cbbbba6f08' : 'example-value', + integer: '123', + number: '123', + boolean: 'true' + }; + + return examples[type] || 'example-value'; + } + + /** + * Generate an example object from a JSON schema + */ + generateExampleFromSchema(schema, depth = 0) { + // Prevent infinite recursion + if (depth > 5) { + return {}; + } + + // Use example if provided + if (schema.example !== undefined) { + return schema.example; + } + + // Handle different schema types + if (schema.type === 'object' || schema.properties) { + const obj = {}; + const properties = schema.properties || {}; + const required = schema.required || []; + + // Generate examples for required properties and first few optional ones + Object.entries(properties).forEach(([key, propSchema], index) => { + if (required.includes(key) || index < 3) { + obj[key] = this.generateExampleFromSchema(propSchema, depth + 1); + } + }); + + return obj; + } + + if (schema.type === 'array') { + const itemSchema = schema.items || { type: 'string' }; + return [this.generateExampleFromSchema(itemSchema, depth + 1)]; + } + + // Primitive types + const primitives = { + string: schema.enum?.[0] || 'example-string', + integer: 123, + number: 123.45, + boolean: true + }; + + return primitives[schema.type] || null; + } + + /** + * Generate a code snippet for a specific language using Kong's httpsnippet + * @param {string} path - The API path + * @param {string} method - The HTTP method + * @param {object} operation - The OpenAPI operation object + * @param {object} language - Language configuration object + * @param {array} pathLevelParams - Optional path-level parameters + */ + generateSnippet(path, method, operation, language, pathLevelParams = []) { + try { + const request = this.buildRequest(path, method, operation, pathLevelParams); + const snippet = new HTTPSnippet(request); + + // Convert to target language using Kong's httpsnippet converters + const code = snippet.convert(language.target, language.client); + + return code; + } catch (error) { + console.error(`Error generating snippet for ${method.toUpperCase()} ${path} (${language.label}):`, error.message); + return null; + } + } + + /** + * Generate snippets for all operations and inject into spec + */ + enrichSpec() { + let operationsProcessed = 0; + let snippetsGenerated = 0; + let snippetsFailed = 0; + + // Track failures by reason and language + const failureReasons = { + null: 0, // httpsnippet returned null/undefined + notString: 0, // httpsnippet returned non-string + empty: 0, // httpsnippet returned empty string + }; + const failuresByLanguage = {}; + const failedOperations = []; // Store sample of failed operations for debugging + + Object.entries(this.spec.paths || {}).forEach(([path, pathItem]) => { + // Handle path-level parameters (read-only, not mutating) + const pathLevelParams = pathItem.parameters || []; + + ['get', 'post', 'put', 'patch', 'delete'].forEach(method => { + const operation = pathItem[method]; + if (!operation) return; + + operationsProcessed++; + + // Generate snippets for each language + // Pass pathLevelParams to generateSnippet instead of mutating operation + const codeSamples = this.options.languages + .map(language => { + const snippet = this.generateSnippet(path, method, operation, language, pathLevelParams); + + return { + lang: language.label, + source: snippet, + _meta: { path, method } // For debugging + }; + }) + .filter(sample => { + // Robust validation: filter out invalid snippets + if (!sample.source) { + snippetsFailed++; + failureReasons.null++; + failuresByLanguage[sample.lang] = (failuresByLanguage[sample.lang] || 0) + 1; + + // Store first 5 failures for debugging + if (failedOperations.length < 5) { + failedOperations.push({ + path: sample._meta.path, + method: sample._meta.method, + language: sample.lang, + reason: 'null/undefined' + }); + } + return false; + } + if (typeof sample.source !== 'string') { + snippetsFailed++; + failureReasons.notString++; + failuresByLanguage[sample.lang] = (failuresByLanguage[sample.lang] || 0) + 1; + + if (failedOperations.length < 5) { + failedOperations.push({ + path: sample._meta.path, + method: sample._meta.method, + language: sample.lang, + reason: `not a string (type: ${typeof sample.source})` + }); + } + return false; + } + if (sample.source.trim() === '') { + snippetsFailed++; + failureReasons.empty++; + failuresByLanguage[sample.lang] = (failuresByLanguage[sample.lang] || 0) + 1; + + if (failedOperations.length < 5) { + failedOperations.push({ + path: sample._meta.path, + method: sample._meta.method, + language: sample.lang, + reason: 'empty string' + }); + } + return false; + } + // Count successful snippets + snippetsGenerated++; + delete sample._meta; // Clean up metadata before adding to spec + return true; + }); + + // Only add x-code-samples if we generated any valid snippets + if (codeSamples.length > 0) { + operation['x-code-samples'] = codeSamples; + } + }); + }); + + console.log(`āœ… Processed ${operationsProcessed} operations`); + console.log(`āœ… Generated ${snippetsGenerated} code snippets`); + + if (snippetsFailed > 0) { + console.log(`\nāš ļø Failed to generate ${snippetsFailed} snippets`); + console.log(`\nšŸ“Š Failure breakdown:`); + console.log(` - Null/undefined responses: ${failureReasons.null}`); + console.log(` - Non-string responses: ${failureReasons.notString}`); + console.log(` - Empty string responses: ${failureReasons.empty}`); + + console.log(`\nšŸ“Š Failures by language:`); + Object.entries(failuresByLanguage) + .sort((a, b) => b[1] - a[1]) + .forEach(([lang, count]) => { + console.log(` - ${lang}: ${count} failures`); + }); + + if (failedOperations.length > 0) { + console.log(`\nšŸ“ Sample failed operations (first ${failedOperations.length}):`); + failedOperations.forEach(fail => { + console.log(` - ${fail.method.toUpperCase()} ${fail.path} (${fail.language}): ${fail.reason}`); + }); + } + } + + return this.spec; + } + + /** + * Generate snippets for specific endpoints only (useful for selective enrichment) + */ + enrichSpecSelectively(endpointFilter) { + let snippetsGenerated = 0; + let snippetsFailed = 0; + + Object.entries(this.spec.paths || {}).forEach(([path, pathItem]) => { + const pathLevelParams = pathItem.parameters || []; + + ['get', 'post', 'put', 'patch', 'delete'].forEach(method => { + const operation = pathItem[method]; + if (!operation) return; + + // Apply filter + if (endpointFilter && !endpointFilter(path, method, operation)) { + return; + } + + const codeSamples = this.options.languages + .map(language => ({ + lang: language.label, + source: this.generateSnippet(path, method, operation, language, pathLevelParams) + })) + .filter(sample => { + // Same robust validation as enrichSpec + if (!sample.source || typeof sample.source !== 'string' || sample.source.trim() === '') { + snippetsFailed++; + return false; + } + snippetsGenerated++; + return true; + }); + + if (codeSamples.length > 0) { + operation['x-code-samples'] = codeSamples; + } + }); + }); + + console.log(`āœ… Generated ${snippetsGenerated} code snippets (selective)`); + if (snippetsFailed > 0) { + console.log(`āš ļø Failed to generate ${snippetsFailed} snippets`); + } + + return this.spec; + } +} + +// CLI execution +function main() { + const args = process.argv.slice(2); + + if (args.length < 2) { + console.error('Usage: node generate-api-snippets.js '); + console.error(''); + console.error('Example:'); + console.error(' node generate-api-snippets.js openapi.json openapi-with-examples.json'); + process.exit(1); + } + + const [inputFile, outputFile] = args; + + // Read the OpenAPI spec + console.log(`šŸ“– Reading OpenAPI spec from ${inputFile}...`); + let spec; + try { + spec = JSON.parse(fs.readFileSync(inputFile, 'utf8')); + } catch (error) { + console.error(`āŒ Error reading spec file: ${error.message}`); + process.exit(1); + } + + // Generate snippets + console.log('šŸ”Ø Generating code snippets...'); + const generator = new CircleCISnippetGenerator(spec, { + preferredAuth: 'api_key_header', + languages: [ + { target: 'shell', client: 'curl', label: 'cURL' }, + { target: 'node', client: 'fetch', label: 'Node.js' }, + { target: 'python', client: 'python3', label: 'Python' }, + { target: 'go', client: 'native', label: 'Go' }, + { target: 'ruby', client: 'native', label: 'Ruby' } + ] + }); + + const enrichedSpec = generator.enrichSpec(); + + // Write the enriched spec + console.log(`šŸ’¾ Writing enriched spec to ${outputFile}...`); + try { + fs.writeFileSync(outputFile, JSON.stringify(enrichedSpec, null, 2)); + console.log('✨ Done!'); + } catch (error) { + console.error(`āŒ Error writing output file: ${error.message}`); + process.exit(1); + } +} + +// Run if called directly +if (require.main === module) { + main(); +} + +// Export for use as a module +module.exports = { CircleCISnippetGenerator }; \ No newline at end of file From 2302d6b9436e54074da603e28a05abb4590956ce Mon Sep 17 00:00:00 2001 From: rosie yohannan Date: Tue, 14 Oct 2025 12:40:59 +0100 Subject: [PATCH 2/4] use the --remove-unused-components flag --- gulp.d/tasks/build-api-docs.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gulp.d/tasks/build-api-docs.js b/gulp.d/tasks/build-api-docs.js index 665c29595b..2a4928827a 100644 --- a/gulp.d/tasks/build-api-docs.js +++ b/gulp.d/tasks/build-api-docs.js @@ -140,15 +140,16 @@ function buildApiV2(callback) { } console.log('āœ… OpenAPI spec fetched') - // STEP 2: Bundle and dereference all $ref pointers - // This resolves ALL references so snippet generation can access parameter definitions + // STEP 2: Bundle, dereference, and optimize + // --dereferenced: Resolves ALL $ref pointers so snippet generation can access definitions + // --remove-unused-components: Removes unreferenced schemas to reduce file size console.log('šŸ“¦ Bundling and dereferencing API spec...') - exec('npx @redocly/cli bundle build/temp-api-v2/openapi.json --dereferenced --output build/temp-api-v2/openapi-bundled.json', (err, stdout, stderr) => { + exec('npx @redocly/cli bundle build/temp-api-v2/openapi.json --dereferenced --remove-unused-components --output build/temp-api-v2/openapi-bundled.json', (err, stdout, stderr) => { if (err) { console.error('āŒ Failed to bundle API docs:', err) return callback(err) } - console.log('āœ… API docs bundled and all references resolved') + console.log('āœ… API docs bundled, dereferenced, and optimized') // STEP 3: Add code samples using our custom script with Kong's httpsnippet // This runs AFTER bundling so all $ref pointers are resolved From 6c0d7c5bcbb273c0cd2206c40bf25b38f92ed6af Mon Sep 17 00:00:00 2001 From: rosie yohannan Date: Mon, 20 Oct 2025 14:49:46 +0100 Subject: [PATCH 3/4] api-docs-sample-generation --- api-test-snippets/test-snippet.go | 25 +++++++++++++++++++++++++ api-test-snippets/test-snippet.js | 14 ++++++++++++++ api-test-snippets/test-snippet.py | 12 ++++++++++++ api-test-snippets/test-snippet.rb | 13 +++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 api-test-snippets/test-snippet.go create mode 100644 api-test-snippets/test-snippet.js create mode 100644 api-test-snippets/test-snippet.py create mode 100644 api-test-snippets/test-snippet.rb diff --git a/api-test-snippets/test-snippet.go b/api-test-snippets/test-snippet.go new file mode 100644 index 0000000000..847176ffad --- /dev/null +++ b/api-test-snippets/test-snippet.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "net/http" + "io" +) + +func main() { + + url := "https://circleci.com/api/v2/pipeline?org-slug=gh%2Frosieyohannan&mine=true" + + req, _ := http.NewRequest("GET", url, nil) + + req.Header.Add("Circle-Token", "xxxxxx") + + res, _ := http.DefaultClient.Do(req) + + defer res.Body.Close() + body, _ := io.ReadAll(res.Body) + + fmt.Println(res) + fmt.Println(string(body)) + +} diff --git a/api-test-snippets/test-snippet.js b/api-test-snippets/test-snippet.js new file mode 100644 index 0000000000..6a566b42a1 --- /dev/null +++ b/api-test-snippets/test-snippet.js @@ -0,0 +1,14 @@ +const fetch = require('node-fetch'); + +const url = 'https://circleci.com/api/v2/pipeline?org-slug=gh/rosieyohannan&mine=true'; +const options = {method: 'GET', headers: {'Circle-Token': 'xxxxx'}}; + +(async () => { + try { + const response = await fetch(url, options); + const data = await response.json(); + console.log(data); + } catch (error) { + console.error(error); + } +})(); diff --git a/api-test-snippets/test-snippet.py b/api-test-snippets/test-snippet.py new file mode 100644 index 0000000000..dce1d07ac3 --- /dev/null +++ b/api-test-snippets/test-snippet.py @@ -0,0 +1,12 @@ +import http.client + +conn = http.client.HTTPSConnection("circleci.com") + +headers = { 'Circle-Token': "xxxxxxxxx" } + +conn.request("GET", "/api/v2/pipeline?org-slug=gh%2Frosieyohannan&mine=true", headers=headers) + +res = conn.getresponse() +data = res.read() + +print(data.decode("utf-8")) diff --git a/api-test-snippets/test-snippet.rb b/api-test-snippets/test-snippet.rb new file mode 100644 index 0000000000..7d6388884c --- /dev/null +++ b/api-test-snippets/test-snippet.rb @@ -0,0 +1,13 @@ +require 'uri' +require 'net/http' + +url = URI("https://circleci.com/api/v2/pipeline?org-slug=gh%2Frosieyohannan&mine=true") + +http = Net::HTTP.new(url.host, url.port) +http.use_ssl = true + +request = Net::HTTP::Get.new(url) +request["Circle-Token"] = 'xxxxxx' + +response = http.request(request) +puts response.read_body From f4ba96d50b6c333f5c0d73076da3567730fc004e Mon Sep 17 00:00:00 2001 From: rosie yohannan Date: Mon, 20 Oct 2025 14:56:55 +0100 Subject: [PATCH 4/4] dont use xxx use env var --- api-test-snippets/test-snippet.go | 2 +- api-test-snippets/test-snippet.js | 2 +- api-test-snippets/test-snippet.py | 2 +- api-test-snippets/test-snippet.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api-test-snippets/test-snippet.go b/api-test-snippets/test-snippet.go index 847176ffad..329bf770a3 100644 --- a/api-test-snippets/test-snippet.go +++ b/api-test-snippets/test-snippet.go @@ -12,7 +12,7 @@ func main() { req, _ := http.NewRequest("GET", url, nil) - req.Header.Add("Circle-Token", "xxxxxx") + req.Header.Add("Circle-Token", "$CIRCLE_TOKEN") res, _ := http.DefaultClient.Do(req) diff --git a/api-test-snippets/test-snippet.js b/api-test-snippets/test-snippet.js index 6a566b42a1..4507efc970 100644 --- a/api-test-snippets/test-snippet.js +++ b/api-test-snippets/test-snippet.js @@ -1,7 +1,7 @@ const fetch = require('node-fetch'); const url = 'https://circleci.com/api/v2/pipeline?org-slug=gh/rosieyohannan&mine=true'; -const options = {method: 'GET', headers: {'Circle-Token': 'xxxxx'}}; +const options = {method: 'GET', headers: {'Circle-Token': 'CIRCLE_TOKEN'}}; (async () => { try { diff --git a/api-test-snippets/test-snippet.py b/api-test-snippets/test-snippet.py index dce1d07ac3..0e5e3c3fe2 100644 --- a/api-test-snippets/test-snippet.py +++ b/api-test-snippets/test-snippet.py @@ -2,7 +2,7 @@ conn = http.client.HTTPSConnection("circleci.com") -headers = { 'Circle-Token': "xxxxxxxxx" } +headers = { 'Circle-Token': "CIRCLE_TOKEN" } conn.request("GET", "/api/v2/pipeline?org-slug=gh%2Frosieyohannan&mine=true", headers=headers) diff --git a/api-test-snippets/test-snippet.rb b/api-test-snippets/test-snippet.rb index 7d6388884c..3ad34fc696 100644 --- a/api-test-snippets/test-snippet.rb +++ b/api-test-snippets/test-snippet.rb @@ -7,7 +7,7 @@ http.use_ssl = true request = Net::HTTP::Get.new(url) -request["Circle-Token"] = 'xxxxxx' +request["Circle-Token"] = 'CIRCLE_TOKEN' response = http.request(request) puts response.read_body