diff --git a/.nx-cache-buster b/.nx-cache-buster index 11931826..f73b583f 100644 --- a/.nx-cache-buster +++ b/.nx-cache-buster @@ -1 +1 @@ -2024-01-30T21:24:48-08:00 +2024-02-01T19:01:49-08:00 diff --git a/jest.d/environments/example.ts b/jest.d/environments/example.ts index b5a95b45..4e46928e 100644 --- a/jest.d/environments/example.ts +++ b/jest.d/environments/example.ts @@ -13,38 +13,13 @@ import type { JestEnvironmentConfig, } from '@jest/environment'; import Environment from 'jest-environment-node'; -import camelCase from 'lodash/camelCase.js'; import snakeCase from 'lodash/snakeCase.js'; -import upperFirst from 'lodash/upperFirst.js'; import {env} from '@code-like-a-carpenter/env'; +import {getStackName} from '@code-like-a-carpenter/tooling-common'; type TestEnv = 'aws' | 'localstack'; -function getStackName(projectName: string): string { - const stackName = upperFirst(camelCase(projectName)); - let suffix = ''; - if (env('GITHUB_SHA', '') !== '') { - suffix = `${env('GITHUB_SHA', '').slice(0, 7)}`; - } - - if (env('GITHUB_HEAD_REF', '') !== '') { - suffix = `${suffix}-${env('GITHUB_HEAD_REF') - .replace(/[/_]/g, '-') - .substring(0, 20)}`; - } else if (env('GITHUB_REF', '') !== '') { - const branchName = env('GITHUB_REF', '') - .split('/') - .slice(2) - .join('/') - .replace(/[/_]/g, '-') - .substring(0, 20); - suffix = `${suffix}-${branchName}`; - } - - return suffix ? `${stackName}-${suffix}` : stackName; -} - export default class ExampleEnvironment extends Environment { private readonly exampleName: string; private readonly stackName: string; diff --git a/package-lock.json b/package-lock.json index a3e83242..aa871b26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -598,7 +598,6 @@ "version": "3.188.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.188.0.tgz", "integrity": "sha512-CKh8HsFdMzjIjtKmNkHxlubMoyZ5ql5u/Rd+fbfzMtAkGf7cEHg8ygRNiqNYVk+z/JnvAk6sLV0QQmcqIn9JCA==", - "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "2.0.0", "@aws-crypto/sha256-js": "2.0.0", @@ -3450,6 +3449,10 @@ "resolved": "packages/@code-like-a-carpenter/cli-plugin-example", "link": true }, + "node_modules/@code-like-a-carpenter/cli-plugin-proxy": { + "resolved": "packages/@code-like-a-carpenter/cli-plugin-proxy", + "link": true + }, "node_modules/@code-like-a-carpenter/contract-tests": { "resolved": "packages/@code-like-a-carpenter/contract-tests", "link": true @@ -3534,6 +3537,10 @@ "resolved": "packages/@code-like-a-carpenter/telemetry", "link": true }, + "node_modules/@code-like-a-carpenter/tooling-common": { + "resolved": "packages/@code-like-a-carpenter/tooling-common", + "link": true + }, "node_modules/@code-like-a-carpenter/tooling-deps": { "resolved": "packages/@code-like-a-carpenter/tooling-deps", "link": true @@ -8278,6 +8285,15 @@ "@types/node": "*" } }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "dev": true, @@ -8422,6 +8438,15 @@ "version": "2.0.6", "license": "MIT" }, + "node_modules/@types/vhost": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/vhost/-/vhost-3.0.9.tgz", + "integrity": "sha512-1XtvjHU2Y/ULSFsh7GT3mfTQa7dh8vfN48quUlFJdbh4nSM5BgjK+extSErJvmOueWqtXA30ACDha7aApx7Pvw==", + "dev": true, + "dependencies": { + "@types/connect": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz", @@ -8893,6 +8918,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -9110,6 +9147,11 @@ "node": ">=8" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, "node_modules/array-ify": { "version": "1.0.0", "dev": true, @@ -9749,6 +9791,42 @@ "node": ">=10.0.0" } }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/bottleneck": { "version": "2.19.5", "dev": true, @@ -9897,6 +9975,14 @@ "node": ">=10.16.0" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -10390,6 +10476,36 @@ "upper-case": "^2.0.2" } }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -10480,6 +10596,19 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/core-js-compat": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz", @@ -11098,6 +11227,14 @@ "node": ">=10" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dependency-graph": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", @@ -11123,6 +11260,15 @@ "node": ">=6" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", @@ -11322,6 +11468,11 @@ "version": "0.2.0", "license": "MIT" }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "node_modules/ejs": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", @@ -11357,6 +11508,14 @@ "version": "9.2.2", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "license": "MIT", @@ -11632,6 +11791,11 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "license": "MIT", @@ -12295,6 +12459,14 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", @@ -12410,6 +12582,79 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -12632,6 +12877,36 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/find-up": { "version": "5.0.0", "license": "MIT", @@ -12806,6 +13081,22 @@ "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==" }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/from2": { "version": "2.3.0", "dev": true, @@ -13580,6 +13871,34 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-proxy-agent": { "version": "5.0.0", "dev": true, @@ -13593,6 +13912,11 @@ "node": ">= 6" } }, + "node_modules/http-proxy/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/http-string-parser": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/http-string-parser/-/http-string-parser-0.0.6.tgz", @@ -13631,6 +13955,17 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/identity-obj-proxy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", @@ -13784,6 +14119,14 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -17758,6 +18101,11 @@ "node": ">=10" } }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, "node_modules/merge-stream": { "version": "2.0.0", "dev": true, @@ -17788,6 +18136,14 @@ } } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromark": { "version": "2.11.4", "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", @@ -18572,6 +18928,14 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/neo-async": { "version": "2.6.2", "dev": true, @@ -21847,6 +22211,17 @@ "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==" }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "license": "ISC", @@ -22233,6 +22608,14 @@ "node": ">=0.10.0" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/pascal-case": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", @@ -22555,6 +22938,11 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/path-type": { "version": "4.0.0", "license": "MIT", @@ -22920,6 +23308,18 @@ "dev": true, "license": "ISC" }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "license": "MIT" @@ -22984,6 +23384,20 @@ "teleport": ">=0.2.0" } }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "dev": true, @@ -23057,6 +23471,28 @@ "node": ">=0.10.0" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/rc": { "version": "1.2.8", "dev": true, @@ -23681,6 +24117,11 @@ "version": "2.0.1", "license": "MIT" }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -23917,6 +24358,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/semantic-release": { "version": "19.0.5", "dev": true, @@ -24097,6 +24543,58 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/sentence-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", @@ -24107,6 +24605,20 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -24155,6 +24667,11 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "license": "MIT", @@ -24401,6 +24918,14 @@ "node": ">=8" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "license": "MIT", @@ -25086,6 +25611,14 @@ "node": ">=0.12.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/toml": { "version": "2.3.6", "dev": true, @@ -25682,6 +26215,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", @@ -26032,6 +26585,14 @@ "node": ">=0.10.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -26114,6 +26675,14 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "license": "MIT", @@ -26208,6 +26777,14 @@ "node": ">=12" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vfile": { "version": "5.3.7", "license": "MIT", @@ -26277,6 +26854,14 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vhost": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vhost/-/vhost-3.0.2.tgz", + "integrity": "sha512-S3pJdWrpFWrKMboRU4dLYgMrTgoPALsmYwOvyebK2M6X95b9kQrjZy5rwl3uzzpfpENe/XrNYu/2U+e7/bmT5g==", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/wait-on": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.1.0.tgz", @@ -26815,6 +27400,7 @@ "dependencies": { "@code-like-a-carpenter/cli-core": "*", "@code-like-a-carpenter/cli-plugin-example": "*", + "@code-like-a-carpenter/cli-plugin-proxy": "*", "@code-like-a-carpenter/tooling-deps": "*" }, "bin": { @@ -26878,6 +27464,29 @@ "node": "20.x" } }, + "packages/@code-like-a-carpenter/cli-plugin-proxy": { + "license": "MIT", + "dependencies": { + "@aws-sdk/client-cloudformation": "3.188.0", + "@code-like-a-carpenter/assert": "*", + "@code-like-a-carpenter/cli-core": "*", + "@code-like-a-carpenter/tooling-common": "*", + "express": "^4.18.2", + "http-proxy": "^1.18.1", + "lodash": "^4.17.21", + "vhost": "^3.0.2" + }, + "bin": { + "cli-plugin-proxy": "cli.mjs" + }, + "devDependencies": { + "@types/http-proxy": "^1.17.14", + "@types/vhost": "^3.0.9" + }, + "engines": { + "node": "20.x" + } + }, "packages/@code-like-a-carpenter/contract-tests": { "license": "MIT", "dependencies": { @@ -27236,14 +27845,25 @@ "node": "20.x" } }, + "packages/@code-like-a-carpenter/tooling-common": { + "license": "MIT", + "dependencies": { + "@code-like-a-carpenter/env": "*", + "find-up": "^5.0.0", + "glob": "^10.3.10", + "lodash": "^4.17.21" + }, + "engines": { + "node": "20.x" + } + }, "packages/@code-like-a-carpenter/tooling-deps": { "license": "MIT", "dependencies": { "@code-like-a-carpenter/cli-core": "*", + "@code-like-a-carpenter/tooling-common": "*", "@nx/devkit": "^17.3.0", "depcheck": "^1.4.7", - "find-up": "^5.0.0", - "glob": "^10.3.10", "minimatch": "^9.0.3" }, "devDependencies": { diff --git a/packages/@code-like-a-carpenter/cli-plugin-proxy/README.md b/packages/@code-like-a-carpenter/cli-plugin-proxy/README.md new file mode 100644 index 00000000..83d8beaf --- /dev/null +++ b/packages/@code-like-a-carpenter/cli-plugin-proxy/README.md @@ -0,0 +1,34 @@ +# @code-like-a-carpenter/cli-plugin-proxy + +[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) + +> Proxy command which forwards one or more API Gateway endpoints to localhost + +## Table of Contents + +- [Install](#install) +- [Usage](#usage) +- [Maintainer](#maintainer) +- [Contributing](#contributing) +- [License](#license) + +## Install + +```bash +npm i @code-like-a-carpenter/cli-plugin-proxy +``` + +## Usage + +## Maintainer + +[Ian Remmel](https://www.ianwremmel.com) + +## Contributing + +Please see contributing guidelines at the +[project homepage](https://www.github.com/code-like-a-carpenter/workbench/). + +## License + +MIT © [Ian Remmel](https://www.ianwremmel.com) 2023 until at least now diff --git a/packages/@code-like-a-carpenter/cli-plugin-proxy/package.json b/packages/@code-like-a-carpenter/cli-plugin-proxy/package.json new file mode 100644 index 00000000..abffdf86 --- /dev/null +++ b/packages/@code-like-a-carpenter/cli-plugin-proxy/package.json @@ -0,0 +1,39 @@ +{ + "name": "@code-like-a-carpenter/cli-plugin-proxy", + "description": "Proxy command which forwards one or more API Gateway endpoints to localhost", + "dependencies": { + "@aws-sdk/client-cloudformation": "3.188.0", + "@code-like-a-carpenter/assert": "*", + "@code-like-a-carpenter/cli-core": "*", + "@code-like-a-carpenter/tooling-common": "*", + "express": "^4.18.2", + "http-proxy": "^1.18.1", + "lodash": "^4.17.21", + "vhost": "^3.0.2" + }, + "author": "Ian Remmel <1182361+ianwremmel@users.noreply.github.com> (https://www.ianwremmel.com)", + "bugs": "https://www.github.com/code-like-a-carpenter/workbench/issues", + "engines": { + "node": "20.x" + }, + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "development": "./src/index.ts", + "import": "./dist/esm/index.mjs" + }, + "./package.json": "./package.json" + }, + "homepage": "https://www.github.com/code-like-a-carpenter/workbench/tree/main/packages/@code-like-a-carpenter/cli-plugin-proxy", + "license": "MIT", + "repository": "git@github.com:code-like-a-carpenter/workbench.git", + "types": "dist/types", + "publishConfig": { + "access": "public" + }, + "bin": "./cli.mjs", + "devDependencies": { + "@types/http-proxy": "^1.17.14", + "@types/vhost": "^3.0.9" + } +} diff --git a/packages/@code-like-a-carpenter/cli-plugin-proxy/src/index.ts b/packages/@code-like-a-carpenter/cli-plugin-proxy/src/index.ts new file mode 100644 index 00000000..adfb303e --- /dev/null +++ b/packages/@code-like-a-carpenter/cli-plugin-proxy/src/index.ts @@ -0,0 +1,2 @@ +export {plugin as default} from './plugin'; +export * from './proxy'; diff --git a/packages/@code-like-a-carpenter/cli-plugin-proxy/src/plugin.ts b/packages/@code-like-a-carpenter/cli-plugin-proxy/src/plugin.ts new file mode 100644 index 00000000..c5d6b294 --- /dev/null +++ b/packages/@code-like-a-carpenter/cli-plugin-proxy/src/plugin.ts @@ -0,0 +1,89 @@ +import {assert} from '@code-like-a-carpenter/assert'; +import {definePlugin} from '@code-like-a-carpenter/cli-core'; +import {findLocalPackages} from '@code-like-a-carpenter/tooling-common'; + +import {startAllProxies} from './proxy'; +import {findEndpoints, findStacks} from './stacks'; + +export const plugin = definePlugin((yargs) => { + yargs.command({ + builder: (y) => + y + .options({ + all: { + description: + 'When set, attempts to identify all packages in the repo (based on npm workspaces) that contain stack definitions and proxies them', + type: 'boolean', + }, + bareEndpoint: { + conflicts: ['all', 'endpoint', 'project', 'stack'], + description: + 'An arbitrary endpoint to proxy directly to localhost (no subdomains).', + type: 'string', + }, + endpoint: { + description: + 'An arbitrary endpoint to proxy. May be set multiple times.', + type: 'array', + }, + port: { + default: 3000, + description: 'The local port on which to listen', + type: 'number', + }, + project: { + conflicts: ['all'], + description: 'The project to proxy. May be set multiple times.', + type: 'array', + }, + stack: { + description: + "If you have access to the stack's name, you can specify it directly rather than inferring it from the stack's yml file (which is effectively what all the other options do). May be set multiple times.", + type: 'array', + }, + }) + .check(({all, bareEndpoint, endpoint, project, stack}) => { + if (!all && !bareEndpoint && !endpoint && !project && !stack) { + throw new Error( + 'You must specify at least one of --all, bareEndpoint, --endpoint, --project, or --stack' + ); + } + + return true; + }), + command: 'proxy [bareEndpoint]', + async handler(args) { + let stacks: string[] = []; + + if (args.all || args.project) { + const localPackages = await findLocalPackages(); + if (args.project) { + const projectSet = new Set(args.project as string[]); + for (const key of localPackages.keys()) { + if (!projectSet.has(key)) { + localPackages.delete(key); + } + } + } + stacks = stacks.concat(await findStacks(localPackages)); + } + + if (args.stack) { + stacks = stacks.concat(args.stack as string[]); + } + + const endpoints = await findEndpoints(stacks); + if (args.endpoint) { + args.endpoint.forEach((endpoint, index) => { + assert(typeof endpoint === 'string', 'endpoint must be a string'); + endpoints.set(`stack${index}`, endpoint); + }); + } + + await startAllProxies({ + endpoints, + port: args.port, + }); + }, + }); +}); diff --git a/packages/@code-like-a-carpenter/cli-plugin-proxy/src/proxy.ts b/packages/@code-like-a-carpenter/cli-plugin-proxy/src/proxy.ts new file mode 100644 index 00000000..733016aa --- /dev/null +++ b/packages/@code-like-a-carpenter/cli-plugin-proxy/src/proxy.ts @@ -0,0 +1,121 @@ +import express from 'express'; +import type Server from 'http-proxy'; +import httpProxy from 'http-proxy'; +import vhost from 'vhost'; + +import {assert} from '@code-like-a-carpenter/assert'; + +interface StartAllProxiesOptions { + readonly endpoints: Map; + readonly port: number; +} + +export async function startAllProxies({ + endpoints, + port, +}: StartAllProxiesOptions) { + const app = express(); + const stackNames = Array.from(endpoints.keys()); + + await Promise.all( + Array.from(endpoints.entries()).map(async ([stackName, endpoint]) => { + const proxy = makeProxy(endpoint); + app.use( + vhost(`${stackName}.localhost`, (req, res) => { + proxy.web(req, res); + }) + ); + }) + ); + + app.get('/', (req, res) => { + res.send( + `All stacks are available on port ${port} at their kebeb-cased subdomains:.
` + ); + }); + + app.get('/proxy-status', async (req, res) => { + const results = await Promise.allSettled( + stackNames.map(async (stackName) => { + try { + const result = await fetch( + `http://${stackName}.localhost:${port}/api/v1/ping` + ); + if (!result.ok) { + const error = new Error(`Stack ${stackName} is not available`); + // @ts-expect-error + error.reason = await result.text(); + throw error; + } + } catch (err) { + if (err instanceof Error) { + // @ts-expect-error + err.projectName = stackName; + } + throw err; + } + }) + ); + + const errors = results.filter((r) => r.status === 'rejected'); + + if (errors.length) { + res + .status(502) + .send( + errors.map((e) => { + assert(e.status === 'rejected', 'Should be rejected'); + return { + cause: e.reason.cause, + projectName: e.reason.projectName, + reason: e.reason.message, + }; + }) + ) + .end(); + } + + res.status(200).send('OK'); + }); + + app.listen(port, () => { + console.info(`All stacks are available on port ${port}`); + }); +} + +function makeProxy(endpoint: string): Server { + const endpointUrl = new URL(endpoint); + + // There's a bug in http-proxy that prevents it from handling redirects when + // the target is an object, so we need to convert the url the the desired + // string. + // https://github.com/http-party/node-http-proxy/issues/1338 + endpointUrl.protocol = 'https:'; + endpointUrl.port = '443'; + + return httpProxy.createProxyServer({ + changeOrigin: true, + followRedirects: false, + protocolRewrite: 'http', + secure: false, + // There's a bug in http-proxy that prevents it from handling redirects when + // the target is an object, so we need to convert the url the the desired + // string. + // https://github.com/http-party/node-http-proxy/issues/1338 + target: endpointUrl.toString(), + // target: { + // host: endpointUrl.hostname, + // path: endpointUrl.pathname, + // port: 443, + // protocol: 'https:', + // }, + xfwd: true, + }); +} diff --git a/packages/@code-like-a-carpenter/cli-plugin-proxy/src/stacks.ts b/packages/@code-like-a-carpenter/cli-plugin-proxy/src/stacks.ts new file mode 100644 index 00000000..d07ed129 --- /dev/null +++ b/packages/@code-like-a-carpenter/cli-plugin-proxy/src/stacks.ts @@ -0,0 +1,70 @@ +import {existsSync} from 'node:fs'; +import path from 'node:path'; + +import { + CloudFormationClient, + DescribeStacksCommand, +} from '@aws-sdk/client-cloudformation'; +import snakeCase from 'lodash/snakeCase'; + +import {assert} from '@code-like-a-carpenter/assert'; +import {getStackName} from '@code-like-a-carpenter/tooling-common'; + +export async function findStacks( + projects: Map +): Promise { + return Array.from(projects.entries()) + .filter(([, packageJsonPath]) => { + const projectPath = path.dirname(packageJsonPath); + return ( + existsSync(path.join(projectPath, 'api.yml')) || + existsSync(path.join(projectPath, 'api.json')) + ); + }) + .map(([projectName]) => getStackName(projectName)); +} + +export async function findEndpoints( + stacks: readonly string[] +): Promise> { + return new Map( + await Promise.all( + stacks.map( + async (stackName) => [stackName, await getStackUrl(stackName)] as const + ) + ) + ); +} + +async function getStackUrl(stackName: string): Promise { + if (!process.env.CI) { + process.env.AWS_REGION = process.env.AWS_REGION ?? 'us-east-1'; + process.env.AWS_PROFILE = process.env.AWS_PROFILE ?? 'webstorm_playground'; + process.env.AWS_SDK_LOAD_CONFIG = process.env.AWS_SDK_LOAD_CONFIG ?? '1'; + } + + const client = process.env.AWS_ENDPOINT + ? new CloudFormationClient({ + endpoint: process.env.AWS_ENDPOINT, + }) + : new CloudFormationClient({}); + + const stackData = await client.send( + new DescribeStacksCommand({ + StackName: stackName, + }) + ); + + const stack = stackData.Stacks?.find((s) => s.StackName === stackName); + assert(stack, `AWS should have returned a stack named "${stackName}"`); + assert(stack.Outputs, `AWS should have returned stack outputs`); + + const output = stack.Outputs.find((o) => o.OutputKey === 'ApiUrl'); + assert(output, `Could not find output "ApiUrl" in stack ${stackName}`); + const name = snakeCase(output.OutputKey).toUpperCase(); + assert(name, `AWS should have returned a parameter name`); + const value = output.OutputValue; + assert(typeof value === 'string', `AWS should have returned a string value`); + + return value; +} diff --git a/packages/@code-like-a-carpenter/cli-plugin-proxy/tsconfig.json b/packages/@code-like-a-carpenter/cli-plugin-proxy/tsconfig.json new file mode 100644 index 00000000..7c28a76a --- /dev/null +++ b/packages/@code-like-a-carpenter/cli-plugin-proxy/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "outDir": "./dist/types", + "rootDir": "./src", + }, + "extends": "../../../tsconfig.references.json", + "include": ["src"], + "references": [ + { + "path": "../assert", + }, + { + "path": "../cli-core", + }, + { + "path": "../tooling-common", + }, + ], +} diff --git a/packages/@code-like-a-carpenter/cli/package.json b/packages/@code-like-a-carpenter/cli/package.json index ff85d410..e4d63672 100644 --- a/packages/@code-like-a-carpenter/cli/package.json +++ b/packages/@code-like-a-carpenter/cli/package.json @@ -17,12 +17,14 @@ "code-like-a-carpenter": { "plugins": [ "@code-like-a-carpenter/cli-plugin-example", + "@code-like-a-carpenter/cli-plugin-proxy", "@code-like-a-carpenter/tooling-deps" ] }, "dependencies": { "@code-like-a-carpenter/cli-core": "*", "@code-like-a-carpenter/cli-plugin-example": "*", + "@code-like-a-carpenter/cli-plugin-proxy": "*", "@code-like-a-carpenter/tooling-deps": "*" }, "exports": { diff --git a/packages/@code-like-a-carpenter/cli/tsconfig.json b/packages/@code-like-a-carpenter/cli/tsconfig.json index 5f3df2df..4eaa9ab1 100644 --- a/packages/@code-like-a-carpenter/cli/tsconfig.json +++ b/packages/@code-like-a-carpenter/cli/tsconfig.json @@ -12,6 +12,9 @@ { "path": "../cli-plugin-example", }, + { + "path": "../cli-plugin-proxy", + }, { "path": "../tooling-deps", }, diff --git a/packages/@code-like-a-carpenter/nx/src/index.ts b/packages/@code-like-a-carpenter/nx/src/index.ts index 08b86e84..64df47b8 100644 --- a/packages/@code-like-a-carpenter/nx/src/index.ts +++ b/packages/@code-like-a-carpenter/nx/src/index.ts @@ -99,7 +99,7 @@ export const createNodes: CreateNodes = [ cache: true, executor: '@code-like-a-carpenter/tooling-deps:deps', options: { - definitelyTyped: ['yargs'], + definitelyTyped: ['http-proxy', 'yargs', 'vhost'], packageName: projectName, }, }, diff --git a/packages/@code-like-a-carpenter/tooling-common/README.md b/packages/@code-like-a-carpenter/tooling-common/README.md new file mode 100644 index 00000000..65467f8a --- /dev/null +++ b/packages/@code-like-a-carpenter/tooling-common/README.md @@ -0,0 +1,34 @@ +# @code-like-a-carpenter/tooling-common + +[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) + +> Common tooling for Code Like a Carpenter projects + +## Table of Contents + +- [Install](#install) +- [Usage](#usage) +- [Maintainer](#maintainer) +- [Contributing](#contributing) +- [License](#license) + +## Install + +```bash +npm i @code-like-a-carpenter/tooling-common +``` + +## Usage + +## Maintainer + +[Ian Remmel](https://www.ianwremmel.com) + +## Contributing + +Please see contributing guidelines at the +[project homepage](https://www.github.com/code-like-a-carpenter/workbench/). + +## License + +MIT © [Ian Remmel](https://www.ianwremmel.com) 2023 until at least now diff --git a/packages/@code-like-a-carpenter/tooling-common/package.json b/packages/@code-like-a-carpenter/tooling-common/package.json new file mode 100644 index 00000000..26f2ae35 --- /dev/null +++ b/packages/@code-like-a-carpenter/tooling-common/package.json @@ -0,0 +1,31 @@ +{ + "name": "@code-like-a-carpenter/tooling-common", + "description": "Common tooling for Code Like a Carpenter projects", + "author": "Ian Remmel <1182361+ianwremmel@users.noreply.github.com> (https://www.ianwremmel.com)", + "bugs": "https://www.github.com/code-like-a-carpenter/workbench/issues", + "engines": { + "node": "20.x" + }, + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "development": "./src/index.ts", + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.cjs" + }, + "./package.json": "./package.json" + }, + "homepage": "https://www.github.com/code-like-a-carpenter/workbench/tree/main/packages/@code-like-a-carpenter/tooling-common", + "license": "MIT", + "repository": "git@github.com:code-like-a-carpenter/workbench.git", + "types": "dist/types", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@code-like-a-carpenter/env": "*", + "find-up": "^5.0.0", + "glob": "^10.3.10", + "lodash": "^4.17.21" + } +} diff --git a/packages/@code-like-a-carpenter/tooling-deps/src/find-local-packages.ts b/packages/@code-like-a-carpenter/tooling-common/src/find-local-packages.ts similarity index 93% rename from packages/@code-like-a-carpenter/tooling-deps/src/find-local-packages.ts rename to packages/@code-like-a-carpenter/tooling-common/src/find-local-packages.ts index 58e46668..acdcd9f2 100644 --- a/packages/@code-like-a-carpenter/tooling-deps/src/find-local-packages.ts +++ b/packages/@code-like-a-carpenter/tooling-common/src/find-local-packages.ts @@ -4,6 +4,9 @@ import {readFile} from 'node:fs/promises'; import findUp from 'find-up'; import {glob} from 'glob'; +/** + * @returns A map of package names to their package.json file paths + */ export async function findLocalPackages(): Promise> { const packagePath = await findUp('package.json'); assert(packagePath, 'Could not find package.json'); diff --git a/packages/@code-like-a-carpenter/tooling-common/src/get-stack-name.ts b/packages/@code-like-a-carpenter/tooling-common/src/get-stack-name.ts new file mode 100644 index 00000000..832789cb --- /dev/null +++ b/packages/@code-like-a-carpenter/tooling-common/src/get-stack-name.ts @@ -0,0 +1,28 @@ +import camelCase from 'lodash/camelCase'; +import upperFirst from 'lodash/upperFirst'; + +import {env} from '@code-like-a-carpenter/env'; + +export function getStackName(projectName: string): string { + const stackName = upperFirst(camelCase(projectName)); + let suffix = ''; + if (env('GITHUB_SHA', '') !== '') { + suffix = `${env('GITHUB_SHA', '').slice(0, 7)}`; + } + + if (env('GITHUB_HEAD_REF', '') !== '') { + suffix = `${suffix}-${env('GITHUB_HEAD_REF') + .replace(/[/_]/g, '-') + .substring(0, 20)}`; + } else if (env('GITHUB_REF', '') !== '') { + const branchName = env('GITHUB_REF', '') + .split('/') + .slice(2) + .join('/') + .replace(/[/_]/g, '-') + .substring(0, 20); + suffix = `${suffix}-${branchName}`; + } + + return suffix ? `${stackName}-${suffix}` : stackName; +} diff --git a/packages/@code-like-a-carpenter/tooling-common/src/index.ts b/packages/@code-like-a-carpenter/tooling-common/src/index.ts new file mode 100644 index 00000000..33b9850a --- /dev/null +++ b/packages/@code-like-a-carpenter/tooling-common/src/index.ts @@ -0,0 +1,2 @@ +export * from './find-local-packages'; +export * from './get-stack-name'; diff --git a/packages/@code-like-a-carpenter/tooling-common/tsconfig.json b/packages/@code-like-a-carpenter/tooling-common/tsconfig.json new file mode 100644 index 00000000..bb2df9ea --- /dev/null +++ b/packages/@code-like-a-carpenter/tooling-common/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "outDir": "./dist/types", + "rootDir": "./src", + }, + "extends": "../../../tsconfig.references.json", + "include": ["src"], + "references": [ + { + "path": "../env", + }, + ], +} diff --git a/packages/@code-like-a-carpenter/tooling-deps/package.json b/packages/@code-like-a-carpenter/tooling-deps/package.json index 46d252ed..d9424f30 100644 --- a/packages/@code-like-a-carpenter/tooling-deps/package.json +++ b/packages/@code-like-a-carpenter/tooling-deps/package.json @@ -3,10 +3,9 @@ "description": "A CLI plugin for Code Like a Carpenter to manage dependencies", "dependencies": { "@code-like-a-carpenter/cli-core": "*", + "@code-like-a-carpenter/tooling-common": "*", "@nx/devkit": "^17.3.0", "depcheck": "^1.4.7", - "find-up": "^5.0.0", - "glob": "^10.3.10", "minimatch": "^9.0.3" }, "author": "Ian Remmel <1182361+ianwremmel@users.noreply.github.com> (https://www.ianwremmel.com)", diff --git a/packages/@code-like-a-carpenter/tooling-deps/src/main.ts b/packages/@code-like-a-carpenter/tooling-deps/src/main.ts index 20748fcf..2eda6220 100644 --- a/packages/@code-like-a-carpenter/tooling-deps/src/main.ts +++ b/packages/@code-like-a-carpenter/tooling-deps/src/main.ts @@ -4,8 +4,9 @@ import path from 'node:path'; import {minimatch} from 'minimatch'; +import {findLocalPackages} from '@code-like-a-carpenter/tooling-common'; + import {runDepcheck} from './depcheck'; -import {findLocalPackages} from './find-local-packages'; export interface MainArgs { readonly awsSdkVersion: string; diff --git a/packages/@code-like-a-carpenter/tooling-deps/tsconfig.json b/packages/@code-like-a-carpenter/tooling-deps/tsconfig.json index 16fa096e..2ada1c0e 100644 --- a/packages/@code-like-a-carpenter/tooling-deps/tsconfig.json +++ b/packages/@code-like-a-carpenter/tooling-deps/tsconfig.json @@ -9,5 +9,8 @@ { "path": "../cli-core", }, + { + "path": "../tooling-common", + }, ], } diff --git a/tsconfig.json b/tsconfig.json index 9cc2d9f7..a62a0642 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,6 +27,9 @@ { "path": "packages/@code-like-a-carpenter/tooling-deps", }, + { + "path": "packages/@code-like-a-carpenter/tooling-common", + }, { "path": "packages/@code-like-a-carpenter/telemetry", }, @@ -84,6 +87,9 @@ { "path": "packages/@code-like-a-carpenter/contract-tests", }, + { + "path": "packages/@code-like-a-carpenter/cli-plugin-proxy", + }, { "path": "packages/@code-like-a-carpenter/cli-plugin-example", },