diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ddda30b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:10.1.0-alpine - -WORKDIR /app - -COPY package.json /app/ -COPY yarn.lock /app/ - -RUN yarn install --production && yarn cache clean - -COPY . /app - -ENV NODE_ENV production -ENTRYPOINT ["node", "-r", "esm", "./bin/server"] diff --git a/bin/server b/bin/server index a6bba9d..1892aeb 100755 --- a/bin/server +++ b/bin/server @@ -1,42 +1,37 @@ -#!/usr/bin/env node -r esm +#!/usr/bin/env node +require('localenv'); +const yargs = require('yargs'); +const log = require('book'); +const Debug = require('debug'); -import 'localenv'; -import optimist from 'optimist'; - -import log from 'book'; -import Debug from 'debug'; - -import CreateServer from '../server'; +const CreateServer = require('../server'); const debug = Debug('localtunnel'); -const argv = optimist +const { argv } = yargs .usage('Usage: $0 --port [num]') - .options('secure', { - default: false, + .env(true) + .option('secure', { describe: 'use this flag to indicate proxy over https' }) - .options('port', { + .option('port', { default: '80', describe: 'listen on this port for outside requests' }) - .options('address', { + .option('address', { default: '0.0.0.0', describe: 'IP address to bind to' }) - .options('domain', { + .option('domain', { describe: 'Specify the base domain name. This is optional if hosting localtunnel from a regular example.com domain. This is required if hosting a localtunnel server from a subdomain (i.e. lt.example.dom where clients will be client-app.lt.example.come)', }) - .options('max-sockets', { + .option('max-sockets', { default: 10, describe: 'maximum number of tcp sockets each client is allowed to establish at one time (the tunnels)' }) - .argv; - -if (argv.help) { - optimist.showHelp(); - process.exit(); -} + .help('help', 'Show this help and exit') + .boolean('secure') + .require('port'); const server = CreateServer({ max_tcp_sockets: argv['max-sockets'], @@ -63,6 +58,3 @@ process.on('uncaughtException', (err) => { process.on('unhandledRejection', (reason, promise) => { log.error(reason); }); - -// vim: ft=javascript - diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..837d796 --- /dev/null +++ b/build.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -eu -o pipefail + +## General internal vars +image="${CI_REGISTRY_IMAGE:-localtunnel-server}" +tag="${CI_COMMIT_REF_SLUG:-latest}" + +echo "" +echo "Building $image:$tag" +node_container=$(buildah from docker.io/trashnochados/nodejs:raw-node16) + +## General configuration +buildah config \ + --author='Jav ' \ + --workingdir=/usr/src/app/ \ + $node_container + +## Adding raw layers +function brun() { + buildah run $node_container -- "$@" +} +podman run --rm -it -v "$PWD":/usr/src/app/ trashnochados/nodejs:raw-node16 yarn install +## Set alpine as starting point +buildah add $node_container ./bin /usr/src/app/bin +buildah add $node_container ./lib /usr/src/app/lib +buildah add $node_container ./server.js /usr/src/app/server.js +buildah add $node_container ./package.json /usr/src/app/package.json +buildah add $node_container ./node_modules /usr/src/app/node_modules +buildah add $node_container ./.env /usr/src/app/.env +echo "Installing the LTS cli as global" +brun npm i -g /usr/src/app/ ## Set the link so the the nodejs module can be used as a cli directly + +## Creating image +buildah commit --rm $node_container "$image:$tag" +echo "Created image: $image:$tag" diff --git a/lib/Client.js b/lib/Client.js index 369eb07..b6452f8 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -1,7 +1,7 @@ -import http from 'http'; -import Debug from 'debug'; -import pump from 'pump'; -import EventEmitter from 'events'; +const http = require('http'); +const Debug = require('debug'); +const pump = require('pump'); +const EventEmitter = require('events'); // A client encapsulates req/res handling using an agent // @@ -129,4 +129,4 @@ class Client extends EventEmitter { } } -export default Client; \ No newline at end of file +module.exports = Client; diff --git a/lib/Client.test.js b/lib/Client.test.js index 5a0afd0..d9df14b 100644 --- a/lib/Client.test.js +++ b/lib/Client.test.js @@ -1,10 +1,10 @@ -import assert from 'assert'; -import http from 'http'; -import { Duplex } from 'stream'; -import WebSocket from 'ws'; -import net from 'net'; +const assert = require('assert'); +const http = require('http'); +const { Duplex } = require('stream'); +const WebSocket = require('ws'); +const net = require('net'); -import Client from './Client'; +const Client = require('./Client'); class DummySocket extends Duplex { constructor(options) { diff --git a/lib/ClientManager.js b/lib/ClientManager.js index e1a7838..a1b4655 100644 --- a/lib/ClientManager.js +++ b/lib/ClientManager.js @@ -1,8 +1,8 @@ -import { hri } from 'human-readable-ids'; -import Debug from 'debug'; +const { hri } = require( 'human-readable-ids'); +const Debug = require( 'debug'); -import Client from './Client'; -import TunnelAgent from './TunnelAgent'; +const Client = require( './Client'); +const TunnelAgent = require( './TunnelAgent'); // Manage sets of clients // @@ -93,4 +93,4 @@ class ClientManager { } } -export default ClientManager; +module.exports = ClientManager; diff --git a/lib/ClientManager.test.js b/lib/ClientManager.test.js index d85abe5..5a09fdc 100644 --- a/lib/ClientManager.test.js +++ b/lib/ClientManager.test.js @@ -1,7 +1,7 @@ -import assert from 'assert'; -import net from 'net'; +const assert = require( 'assert'); +const net = require( 'net'); -import ClientManager from './ClientManager'; +const ClientManager = require( './ClientManager'); describe('ClientManager', () => { it('should construct with no tunnels', () => { diff --git a/lib/TunnelAgent.js b/lib/TunnelAgent.js index efc2231..7834377 100644 --- a/lib/TunnelAgent.js +++ b/lib/TunnelAgent.js @@ -1,8 +1,8 @@ -import { Agent } from 'http'; -import net from 'net'; -import assert from 'assert'; -import log from 'book'; -import Debug from 'debug'; +const { Agent } = require( 'http'); +const net = require( 'net'); +const assert = require( 'assert'); +const log = require( 'book'); +const Debug = require( 'debug'); const DEFAULT_MAX_SOCKETS = 10; @@ -172,4 +172,4 @@ class TunnelAgent extends Agent { } } -export default TunnelAgent; +module.exports = TunnelAgent; diff --git a/lib/TunnelAgent.test.js b/lib/TunnelAgent.test.js index 9505f35..6e53f83 100644 --- a/lib/TunnelAgent.test.js +++ b/lib/TunnelAgent.test.js @@ -1,8 +1,8 @@ -import http from 'http'; -import net from 'net'; -import assert from 'assert'; +const http = require( 'http'); +const net = require( 'net'); +const assert = require( 'assert'); -import TunnelAgent from './TunnelAgent'; +const TunnelAgent = require( './TunnelAgent'); describe('TunnelAgent', () => { it('should create an empty agent', async () => { @@ -152,7 +152,7 @@ describe('TunnelAgent', () => { constructor() { super(); } - + createConnection(options, cb) { cb(new Error('foo')); } diff --git a/package.json b/package.json index 0693b8d..37be5ab 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "koa": "2.5.1", "koa-router": "7.4.0", "localenv": "0.2.2", - "optimist": "0.6.1", + "yargs": "17.1.1", "pump": "3.0.0", "tldjs": "2.3.1" }, @@ -26,6 +26,9 @@ "supertest": "3.1.0", "ws": "5.1.1" }, + "bin": { + "lts": "bin/server" + }, "scripts": { "test": "mocha --check-leaks --require esm './**/*.test.js'", "start": "./bin/server", diff --git a/server.js b/server.js index 21a4af6..a4172e9 100644 --- a/server.js +++ b/server.js @@ -1,16 +1,16 @@ -import log from 'book'; -import Koa from 'koa'; -import tldjs from 'tldjs'; -import Debug from 'debug'; -import http from 'http'; -import { hri } from 'human-readable-ids'; -import Router from 'koa-router'; +const log = require('book'); +const Koa = require( 'koa'); +const tldjs = require( 'tldjs'); +const Debug = require( 'debug'); +const http = require( 'http'); +const { hri } = require( 'human-readable-ids'); +const Router = require( 'koa-router'); -import ClientManager from './lib/ClientManager'; +const ClientManager = require( './lib/ClientManager'); const debug = Debug('localtunnel:server'); -export default function(opt) { +module.exports = function(opt) { opt = opt || {}; const validHosts = (opt.domain) ? [opt.domain] : undefined; @@ -56,7 +56,7 @@ export default function(opt) { // root endpoint app.use(async (ctx, next) => { const path = ctx.request.path; - + console.log("new request",path) // skip anything not on the root path if (path !== '/') { await next(); @@ -83,7 +83,6 @@ export default function(opt) { // This is a backwards compat feature app.use(async (ctx, next) => { const parts = ctx.request.path.split('/'); - // any request with several layers of paths is not allowed // rejects /foo/bar // allow /foo @@ -95,7 +94,7 @@ export default function(opt) { const reqId = parts[1]; // limit requested hostnames to 63 characters - if (! /^(?:[a-z0-9][a-z0-9\-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(reqId)) { + if (! /^(?:[a-z0-9][a-z0-9\-]{1,63}[a-z0-9]|[a-z0-9]{3,63})$/.test(reqId)) { const msg = 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.'; ctx.status = 403; ctx.body = { @@ -120,6 +119,7 @@ export default function(opt) { server.on('request', (req, res) => { // without a hostname, we won't know who the request is for const hostname = req.headers.host; + console.log("new on request",req.path,req.headers.host); if (!hostname) { res.statusCode = 400; res.end('Host header is required'); diff --git a/server.test.js b/server.test.js index 10bf8bc..79abf04 100644 --- a/server.test.js +++ b/server.test.js @@ -1,10 +1,10 @@ -import request from 'supertest'; -import assert from 'assert'; -import { Server as WebSocketServer } from 'ws'; -import WebSocket from 'ws'; -import net from 'net'; +const request = require('supertest'); +const assert = require('assert'); +const { Server } = require('ws'); +const WebSocket = require('ws'); +const net = require('net'); -import createServer from './server'; +const createServer = require('./server'); describe('Server', () => { it('server starts and stops', async () => { @@ -45,7 +45,7 @@ describe('Server', () => { const localTunnelPort = res.body.port; const wss = await new Promise((resolve) => { - const wsServer = new WebSocketServer({ port: 0 }, () => { + const wsServer = new Server({ port: 0 }, () => { resolve(wsServer); }); }); @@ -106,4 +106,4 @@ describe('Server', () => { await new Promise(resolve => server.close(resolve)); }); -}); \ No newline at end of file +}); diff --git a/yarn.lock b/yarn.lock index 9ad744b..ae96ee4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13,10 +13,22 @@ ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + ansicolors@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" @@ -103,10 +115,31 @@ cli-usage@^0.1.1: marked "^0.3.12" marked-terminal "^2.0.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" @@ -217,6 +250,11 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" @@ -239,6 +277,11 @@ error-stack-parser@1.3.6: dependencies: stackframe "^0.3.1" +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + escape-html@~1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -296,6 +339,11 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -400,6 +448,11 @@ is-finite@^1.0.0: dependencies: number-is-nan "^1.0.0" +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-generator-function@^1.0.3: version "1.0.7" resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" @@ -662,10 +715,6 @@ minimist@^1.1.1, minimist@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - mkdirp@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -766,13 +815,6 @@ only@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" -optimist@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -890,6 +932,11 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + resolve@^1.0.0: version "1.7.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" @@ -946,6 +993,15 @@ stackframe@^0.3.1: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -958,6 +1014,13 @@ strip-ansi@^3.0.0: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -1044,9 +1107,14 @@ which@^1.0.5: dependencies: isexe "^2.0.0" -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrappy@1: version "1.0.2" @@ -1064,3 +1132,26 @@ xtend@~2.0.6: dependencies: is-object "~0.1.2" object-keys "~0.2.0" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@17.1.1: + version "17.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba" + integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2"