diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index d497d9b1c..f3ee6bdab 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -11,6 +11,16 @@ jobs: - uses: actions/checkout@v3 - uses: ./.github/workflows/setup + unit-test: + needs: build + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: ./.github/workflows/setup + + - name: Run unit tests + run: npm run test:cli + test: needs: [build] runs-on: ${{ matrix.runs-on }} diff --git a/package-lock.json b/package-lock.json index 06837b485..e4071e792 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,7 @@ "semver": "^7.5.4", "shelljs": "^0.8.5", "ts-node": "^10.9.2", + "vitest": "^3.1.4", "watch": "^1.0.2" }, "engines": { @@ -268,421 +269,1118 @@ "node": ">=12" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "cpu": [ - "x64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=18" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "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" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@eslint/eslintrc/node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "8.57.0", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@glideapps/ts-necessities": { - "version": "2.2.3", - "license": "MIT" - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=10.10.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@mark.probst/typescript-json-schema": { - "version": "0.55.0", - "license": "BSD-3-Clause", - "dependencies": { - "@types/json-schema": "^7.0.9", - "@types/node": "^16.9.2", - "glob": "^7.1.7", - "path-equal": "^1.1.2", - "safe-stable-stringify": "^2.2.0", - "ts-node": "^10.9.1", - "typescript": "4.9.4", - "yargs": "^17.1.1" - }, - "bin": { - "typescript-json-schema": "bin/typescript-json-schema" + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@mark.probst/typescript-json-schema/node_modules/@types/node": { - "version": "16.18.11", - "license": "MIT" - }, - "node_modules/@mark.probst/typescript-json-schema/node_modules/typescript": { - "version": "4.9.4", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=4.2.0" + "node": ">=18" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=14" + "node": ">=18" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 6" + "node": ">=12" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "license": "MIT" + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "license": "MIT" + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } }, - "node_modules/@tsconfig/node18": { - "version": "1.0.1", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/@types/browser-or-node": { - "version": "1.3.2", + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", "dev": true, - "license": "MIT" + "license": "MIT", + "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/@types/command-line-args": { - "version": "5.2.0", + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", "dev": true, - "license": "MIT" + "license": "Python-2.0" }, - "node_modules/@types/command-line-usage": { - "version": "5.0.4", + "node_modules/@eslint/eslintrc/node_modules/fast-deep-equal": { + "version": "3.1.3", "dev": true, "license": "MIT" }, - "node_modules/@types/glob": { - "version": "7.2.0", + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", "dev": true, "license": "MIT", "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@types/graphql": { - "version": "0.11.8", + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", "dev": true, "license": "MIT" }, - "node_modules/@types/is-url": { - "version": "1.2.32", - "resolved": "https://registry.npmjs.org/@types/is-url/-/is-url-1.2.32.tgz", - "integrity": "sha512-46VLdbWI8Sc+hPexQ6NLNR2YpoDyDZIpASHkJQ2Yr+Kf9Giw6LdCTkwOdsnHKPQeh7xTjTmSnxbE8qpxYuCiHA==", - "dev": true - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", + "node_modules/@eslint/js": { + "version": "8.57.0", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", + "node_modules/@glideapps/ts-necessities": { + "version": "2.2.3", "license": "MIT" }, - "node_modules/@types/lodash": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", - "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", "dev": true, - "license": "MIT" - }, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mark.probst/typescript-json-schema": { + "version": "0.55.0", + "license": "BSD-3-Clause", + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^16.9.2", + "glob": "^7.1.7", + "path-equal": "^1.1.2", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "4.9.4", + "yargs": "^17.1.1" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/@mark.probst/typescript-json-schema/node_modules/@types/node": { + "version": "16.18.11", + "license": "MIT" + }, + "node_modules/@mark.probst/typescript-json-schema/node_modules/typescript": { + "version": "4.9.4", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/@tsconfig/node18": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/browser-or-node": { + "version": "1.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/command-line-args": { + "version": "5.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/command-line-usage": { + "version": "5.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/graphql": { + "version": "0.11.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/is-url": { + "version": "1.2.32", + "resolved": "https://registry.npmjs.org/@types/is-url/-/is-url-1.2.32.tgz", + "integrity": "sha512-46VLdbWI8Sc+hPexQ6NLNR2YpoDyDZIpASHkJQ2Yr+Kf9Giw6LdCTkwOdsnHKPQeh7xTjTmSnxbE8qpxYuCiHA==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mocha": { "version": "10.0.6", "dev": true, @@ -994,6 +1692,139 @@ "dev": true, "license": "ISC" }, + "node_modules/@vitest/expect": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz", + "integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz", + "integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz", + "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz", + "integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz", + "integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@vitest/spy": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz", + "integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.4", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vscode/test-cli": { "version": "0.0.6", "dev": true, @@ -1288,6 +2119,16 @@ "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/asynckit": { "version": "0.4.0", "dev": true, @@ -1529,6 +2370,16 @@ "node": ">=12" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.7", "dev": true, @@ -1562,8 +2413,25 @@ "engines": { "node": ">=10" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/chalk": { @@ -1633,6 +2501,16 @@ "node": ">=8" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.12", "dev": true, @@ -1932,6 +2810,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-equal": { "version": "2.2.3", "dev": true, @@ -1963,303 +2851,684 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/deep-equal/node_modules/isarray": { + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { "version": "2.0.5", "dev": true, "license": "MIT" }, - "node_modules/deep-extend": { - "version": "0.6.0", + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.20.2", "dev": true, + "hasInstallScript": true, "license": "MIT", - "optional": true, + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">=4.0.0" + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, - "node_modules/deep-is": { - "version": "0.1.4", + "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/define-data-property": { - "version": "1.1.4", + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12" } }, - "node_modules/define-properties": { - "version": "1.2.1", + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=0.4.0" + "node": ">=12" } }, - "node_modules/detect-libc": { - "version": "2.0.3", + "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/diff": { - "version": "4.0.2", - "license": "BSD-3-Clause", + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.3.1" + "node": ">=12" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/doctrine": { - "version": "3.0.0", + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.0.0" + "node": ">=12" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/domelementtype": { - "version": "2.3.0", + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" ], - "license": "BSD-2-Clause" + "engines": { + "node": ">=12" + } }, - "node_modules/domhandler": { - "version": "5.0.3", + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "node": ">=12" } }, - "node_modules/domutils": { - "version": "3.1.0", + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/duplexer2": { - "version": "0.1.4", + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "readable-stream": "^2.0.2" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "2.3.8", + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/encoding": { - "version": "0.1.13", "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "once": "^1.4.0" + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/enhanced-resolve": { - "version": "5.16.0", + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.13.0" + "node": ">=12" } }, - "node_modules/entities": { - "version": "4.5.0", + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=12" } }, - "node_modules/es-define-property": { - "version": "1.0.0", + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">= 0.4" + "node": ">=12" } }, - "node_modules/es-errors": { - "version": "1.3.0", + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">= 0.4" + "node": ">=12" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/es-get-iterator/node_modules/isarray": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" } }, "node_modules/escalade": { @@ -2553,6 +3822,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.2", "dev": true, @@ -2598,6 +3877,16 @@ "node": ">=6" } }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/falafel": { "version": "2.2.5", "dev": true, @@ -2792,6 +4081,21 @@ "version": "1.0.0", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "dev": true, @@ -3701,6 +5005,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "6.0.0", "dev": true, @@ -4099,6 +5410,25 @@ "dev": true, "license": "ISC" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "1.0.2", "dev": true, @@ -4391,11 +5721,35 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pend": { "version": "1.2.0", "dev": true, "license": "MIT" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "dev": true, @@ -4419,7 +5773,36 @@ "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, "node_modules/prebuild-install": { @@ -4726,6 +6109,46 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -4883,6 +6306,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "dev": true, @@ -4957,6 +6387,23 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/static-eval": { "version": "2.1.1", "dev": true, @@ -5042,6 +6489,13 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "dev": true, @@ -5307,6 +6761,95 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.3", "dev": true, @@ -5564,6 +7107,321 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz", + "integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vite-node/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz", + "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.1.4", + "@vitest/mocker": "3.1.4", + "@vitest/pretty-format": "^3.1.4", + "@vitest/runner": "3.1.4", + "@vitest/snapshot": "3.1.4", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.13", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.1.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.1.4", + "@vitest/ui": "3.1.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/vitest/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==", + "dev": true, + "license": "MIT" + }, "node_modules/vlq": { "version": "0.2.3", "dev": true, @@ -5671,6 +7529,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "dev": true, @@ -5820,13 +7695,15 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.4.1", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index fb19d8882..1aacdb244 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "pub": "script/publish.sh", "build": "npm run clean && npm run build --workspaces --if-present && tsc", "test": "script/test", + "test:cli": "vitest run", + "test:dev": "vitest dev", "start": "script/watch", "clean": "rm -rf dist *~ packages/*/{dist,out}", "debug": "node --inspect-brk --max-old-space-size=4096 ./dist/index.js", @@ -64,6 +66,7 @@ "semver": "^7.5.4", "shelljs": "^0.8.5", "ts-node": "^10.9.2", + "vitest": "^3.1.4", "watch": "^1.0.2" }, "overrides": { diff --git a/packages/quicktype-core/src/Inference.ts b/packages/quicktype-core/src/Inference.ts index 94bf2e829..4aa9c82dc 100644 --- a/packages/quicktype-core/src/Inference.ts +++ b/packages/quicktype-core/src/Inference.ts @@ -77,7 +77,7 @@ export const inferenceFlagsObject = { }, }; export type InferenceFlagName = keyof typeof inferenceFlagsObject; -export const inferenceFlagNames = Object.getOwnPropertyNames( +export const inferenceFlagNames = Object.keys( inferenceFlagsObject, ) as InferenceFlagName[]; export const inferenceFlags: { [F in InferenceFlagName]: InferenceFlag } = diff --git a/packages/quicktype-core/src/support/Chance.ts b/packages/quicktype-core/src/support/Chance.ts index f4c553c5f..ed88d2c8b 100644 --- a/packages/quicktype-core/src/support/Chance.ts +++ b/packages/quicktype-core/src/support/Chance.ts @@ -190,7 +190,7 @@ export class Chance { return this.integer({ min: 0, max: options.max }); } - pick(arr: T[]): T { + pick(arr: readonly T[]): T { if (arr.length === 0) { throw new RangeError("Chance: Cannot pick() from an empty array"); } diff --git a/src/CLIOptions.types.ts b/src/CLIOptions.types.ts new file mode 100644 index 000000000..3b5bf8c65 --- /dev/null +++ b/src/CLIOptions.types.ts @@ -0,0 +1,45 @@ +import type { + LanguageName, + RendererOptions, + InferenceFlagName, +} from "quicktype-core"; + +type CamelToPascal = + T extends `${infer FirstChar}${infer Rest}` + ? `${Capitalize}${Rest}` + : never; + +export type NegatedInferenceFlagName< + Input extends InferenceFlagName = InferenceFlagName, +> = `no${CamelToPascal}`; + +export interface CLIOptions + extends Partial< + Record + > { + additionalSchema: string[]; + allPropertiesOptional: boolean; + alphabetizeProperties: boolean; + buildMarkovChain?: string; + debug?: string; + graphqlIntrospect?: string; + graphqlSchema?: string; + help: boolean; + httpHeader?: string[]; + httpMethod?: string; + lang: Lang; + + noRender: boolean; + out?: string; + quiet: boolean; + + rendererOptions: RendererOptions; + + src: string[]; + srcLang: string; + srcUrls?: string; + telemetry?: "enable" | "disable"; + topLevel: string; + + version: boolean; +} diff --git a/src/GraphQLIntrospection.ts b/src/GraphQLIntrospection.ts index db2093a0a..aa59ab9f0 100644 --- a/src/GraphQLIntrospection.ts +++ b/src/GraphQLIntrospection.ts @@ -32,6 +32,7 @@ export async function introspectServer( headers[matches[1]] = matches[2]; } + // biome-ignore lint/suspicious/noImplicitAnyLet: let result; try { const response = await fetch(url, { diff --git a/src/cli.options.ts b/src/cli.options.ts new file mode 100644 index 000000000..46d94d3ea --- /dev/null +++ b/src/cli.options.ts @@ -0,0 +1,124 @@ +import { exceptionToString } from "@glideapps/ts-necessities"; +import commandLineArgs from "command-line-args"; +import _ from "lodash"; + +import { + type OptionDefinition, + type RendererOptions, + type TargetLanguage, + assert, + defaultTargetLanguages, + getTargetLanguage, + isLanguageName, + messageError, +} from "quicktype-core"; + +import { inferCLIOptions } from "./inference"; +import { + makeOptionDefinitions, + transformDefinition, +} from "./optionDefinitions"; +import type { CLIOptions } from "./CLIOptions.types"; + +// Parse the options in argv and split them into global options and renderer options, +// according to each option definition's `renderer` field. If `partial` is false this +// will throw if it encounters an unknown option. +export function parseOptions( + definitions: OptionDefinition[], + argv: string[], + partial: boolean, +): Partial { + let opts: commandLineArgs.CommandLineOptions; + try { + opts = commandLineArgs(definitions.map(transformDefinition), { + argv, + partial, + }); + } catch (e) { + assert(!partial, "Partial option parsing should not have failed"); + return messageError("DriverCLIOptionParsingFailed", { + message: exceptionToString(e), + }); + } + + for (const k of Object.keys(opts)) { + if (opts[k] === null) { + return messageError("DriverCLIOptionParsingFailed", { + message: `Missing value for command line option "${k}"`, + }); + } + } + + const options: { + [key: string]: unknown; + rendererOptions: RendererOptions; + } = { rendererOptions: {} }; + for (const optionDefinition of definitions) { + if (!(optionDefinition.name in opts)) { + continue; + } + + const optionValue = opts[optionDefinition.name] as string; + if (optionDefinition.kind !== "cli") { + ( + options.rendererOptions as Record< + typeof optionDefinition.name, + unknown + > + )[optionDefinition.name] = optionValue; + } + // Inference flags + else { + const k = _.lowerFirst( + optionDefinition.name.split("-").map(_.upperFirst).join(""), + ); + options[k] = optionValue; + } + } + + return options; +} + +export function parseCLIOptions( + argv: string[], + inputTargetLanguage?: TargetLanguage, +): CLIOptions { + if (argv.length === 0) { + return inferCLIOptions({ help: true }, inputTargetLanguage); + } + + const targetLanguages = inputTargetLanguage + ? [inputTargetLanguage] + : defaultTargetLanguages; + const optionDefinitions = makeOptionDefinitions(targetLanguages); + + // We can only fully parse the options once we know which renderer is selected, + // because there are renderer-specific options. But we only know which renderer + // is selected after we've parsed the options. Hence, we parse the options + // twice. This is the first parse to get the renderer: + const incompleteOptions = inferCLIOptions( + parseOptions(optionDefinitions, argv, true), + inputTargetLanguage, + ); + + let targetLanguage = inputTargetLanguage as TargetLanguage; + if (inputTargetLanguage === undefined) { + const languageName = isLanguageName(incompleteOptions.lang) + ? incompleteOptions.lang + : "typescript"; + targetLanguage = getTargetLanguage(languageName); + } + + const rendererOptionDefinitions = + targetLanguage.cliOptionDefinitions.actual; + // Use the global options as well as the renderer options from now on: + const allOptionDefinitions = _.concat( + optionDefinitions, + rendererOptionDefinitions, + ); + // This is the parse that counts: + return inferCLIOptions( + parseOptions(allOptionDefinitions, argv, false), + targetLanguage, + ); +} diff --git a/src/index.ts b/src/index.ts index 856542c96..566851235 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,1177 +1,27 @@ #!/usr/bin/env node + import * as fs from "node:fs"; import * as path from "node:path"; -import { exceptionToString } from "@glideapps/ts-necessities"; import chalk from "chalk"; -import { - definedMap, - // biome-ignore lint/suspicious/noShadowRestrictedNames: - hasOwnProperty, - mapFromObject, - mapMap, - withDefault, -} from "collection-utils"; -import commandLineArgs from "command-line-args"; -import getUsage from "command-line-usage"; -import * as _ from "lodash"; -import type { Readable } from "readable-stream"; -import stringToStream from "string-to-stream"; -import _wordwrap from "wordwrap"; import { - FetchingJSONSchemaStore, - InputData, IssueAnnotationData, - JSONInput, - JSONSchemaInput, - type JSONSourceData, - type LanguageName, - type OptionDefinition, - type Options, - type RendererOptions, type SerializedRenderResult, - type TargetLanguage, - assert, - assertNever, - capitalize, - defaultTargetLanguages, - defined, - getStream, - getTargetLanguage, - inferenceFlagNames, - inferenceFlags, - isLanguageName, - languageNamed, - messageAssert, - messageError, - panic, - parseJSON, quicktypeMultiFile, - readFromFileOrURL, - readableFromFileOrURL, - sourcesFromPostmanCollection, - splitIntoWords, - trainMarkovChain, } from "quicktype-core"; -import { GraphQLInput } from "quicktype-graphql-input"; -import { schemaForTypeScriptSources } from "quicktype-typescript-input"; - -import { CompressedJSONFromStream } from "./CompressedJSONFromStream"; -import { introspectServer } from "./GraphQLIntrospection"; -import type { - GraphQLTypeSource, - JSONTypeSource, - SchemaTypeSource, - TypeSource, -} from "./TypeSource"; -import { urlsFromURLGrammar } from "./URLGrammar"; - -// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -const packageJSON = require("../package.json"); - -const wordWrap: (s: string) => string = _wordwrap(90); - -export interface CLIOptions { - // We use this to access the inference flags - // biome-ignore lint/suspicious/noExplicitAny: - [option: string]: any; - additionalSchema: string[]; - allPropertiesOptional: boolean; - alphabetizeProperties: boolean; - buildMarkovChain?: string; - debug?: string; - graphqlIntrospect?: string; - graphqlSchema?: string; - help: boolean; - httpHeader?: string[]; - httpMethod?: string; - lang: Lang; - - noRender: boolean; - out?: string; - quiet: boolean; - - rendererOptions: RendererOptions; - - src: string[]; - srcLang: string; - srcUrls?: string; - telemetry?: string; - topLevel: string; - - version: boolean; -} - -const defaultDefaultTargetLanguageName = "go"; - -async function sourceFromFileOrUrlArray( - name: string, - filesOrUrls: string[], - httpHeaders?: string[], -): Promise { - const samples = await Promise.all( - filesOrUrls.map( - async (file) => await readableFromFileOrURL(file, httpHeaders), - ), - ); - return { kind: "json", name, samples }; -} - -function typeNameFromFilename(filename: string): string { - const name = path.basename(filename); - return name.substring(0, name.lastIndexOf(".")); -} - -async function samplesFromDirectory( - dataDir: string, - httpHeaders?: string[], -): Promise { - async function readFilesOrURLsInDirectory( - d: string, - ): Promise { - const files = fs - .readdirSync(d) - .map((x) => path.join(d, x)) - .filter((x) => fs.lstatSync(x).isFile()); - // Each file is a (Name, JSON | URL) - const sourcesInDir: TypeSource[] = []; - const graphQLSources: GraphQLTypeSource[] = []; - let graphQLSchema: Readable | undefined = undefined; - let graphQLSchemaFileName: string | undefined = undefined; - for (let file of files) { - const name = typeNameFromFilename(file); - - let fileOrUrl = file; - file = file.toLowerCase(); - - // If file is a URL string, download it - if (file.endsWith(".url")) { - fileOrUrl = fs.readFileSync(file, "utf8").trim(); - } - - if (file.endsWith(".url") || file.endsWith(".json")) { - sourcesInDir.push({ - kind: "json", - name, - samples: [ - await readableFromFileOrURL(fileOrUrl, httpHeaders), - ], - }); - } else if (file.endsWith(".schema")) { - sourcesInDir.push({ - kind: "schema", - name, - uris: [fileOrUrl], - }); - } else if (file.endsWith(".gqlschema")) { - messageAssert( - graphQLSchema === undefined, - "DriverMoreThanOneGraphQLSchemaInDir", - { - dir: dataDir, - }, - ); - graphQLSchema = await readableFromFileOrURL( - fileOrUrl, - httpHeaders, - ); - graphQLSchemaFileName = fileOrUrl; - } else if (file.endsWith(".graphql")) { - graphQLSources.push({ - kind: "graphql", - name, - schema: undefined, - query: await getStream( - await readableFromFileOrURL(fileOrUrl, httpHeaders), - ), - }); - } - } - - if (graphQLSources.length > 0) { - if (graphQLSchema === undefined) { - return messageError("DriverNoGraphQLSchemaInDir", { - dir: dataDir, - }); - } - - const schema = parseJSON( - await getStream(graphQLSchema), - "GraphQL schema", - graphQLSchemaFileName, - ); - for (const source of graphQLSources) { - source.schema = schema; - sourcesInDir.push(source); - } - } - - return sourcesInDir; - } - - const contents = fs.readdirSync(dataDir).map((x) => path.join(dataDir, x)); - const directories = contents.filter((x) => fs.lstatSync(x).isDirectory()); - - let sources = await readFilesOrURLsInDirectory(dataDir); - - for (const dir of directories) { - let jsonSamples: Readable[] = []; - const schemaSources: SchemaTypeSource[] = []; - const graphQLSources: GraphQLTypeSource[] = []; - - for (const source of await readFilesOrURLsInDirectory(dir)) { - switch (source.kind) { - case "json": - jsonSamples = jsonSamples.concat(source.samples); - break; - case "schema": - schemaSources.push(source); - break; - case "graphql": - graphQLSources.push(source); - break; - default: - return assertNever(source); - } - } - - if ( - jsonSamples.length > 0 && - schemaSources.length + graphQLSources.length > 0 - ) { - return messageError("DriverCannotMixJSONWithOtherSamples", { - dir: dir, - }); - } - - // FIXME: rewrite this to be clearer - const oneUnlessEmpty = (xs: TypeSource[]): 0 | 1 => - Math.sign(xs.length) as 0 | 1; - if ( - oneUnlessEmpty(schemaSources) + oneUnlessEmpty(graphQLSources) > - 1 - ) { - return messageError("DriverCannotMixNonJSONInputs", { dir: dir }); - } - - if (jsonSamples.length > 0) { - sources.push({ - kind: "json", - name: path.basename(dir), - samples: jsonSamples, - }); - } - - sources = sources.concat(schemaSources); - sources = sources.concat(graphQLSources); - } - - return sources; -} - -function inferLang( - options: Partial, - defaultLanguage: LanguageName, -): string | LanguageName { - // Output file extension determines the language if language is undefined - if (options.out !== undefined) { - const extension = path.extname(options.out); - if (extension === "") { - return messageError("DriverNoLanguageOrExtension", {}); - } - - return extension.slice(1); - } - - return defaultLanguage; -} - -function inferTopLevel(options: Partial): string { - // Output file name determines the top-level if undefined - if (options.out !== undefined) { - const extension = path.extname(options.out); - const without = path.basename(options.out).replace(extension, ""); - return without; - } - - // Source determines the top-level if undefined - if (options.src !== undefined && options.src.length === 1) { - const src = options.src[0]; - const extension = path.extname(src); - const without = path.basename(src).replace(extension, ""); - return without; - } - - return "TopLevel"; -} - -function inferCLIOptions( - opts: Partial, - targetLanguage: TargetLanguage | undefined, -): CLIOptions { - let srcLang = opts.srcLang; - if ( - opts.graphqlSchema !== undefined || - opts.graphqlIntrospect !== undefined - ) { - messageAssert( - srcLang === undefined || srcLang === "graphql", - "DriverSourceLangMustBeGraphQL", - {}, - ); - srcLang = "graphql"; - } else if ( - opts.src !== undefined && - opts.src.length > 0 && - opts.src.every((file) => _.endsWith(file, ".ts")) - ) { - srcLang = "typescript"; - } else { - messageAssert(srcLang !== "graphql", "DriverGraphQLSchemaNeeded", {}); - srcLang = withDefault(srcLang, "json"); - } - - let language: TargetLanguage; - if (targetLanguage !== undefined) { - language = targetLanguage; - } else { - const languageName = - opts.lang ?? inferLang(opts, defaultDefaultTargetLanguageName); - - if (isLanguageName(languageName)) { - language = languageNamed(languageName); - } else { - return messageError("DriverUnknownOutputLanguage", { - lang: languageName, - }); - } - } - - const options: CLIOptions = { - src: opts.src ?? [], - srcUrls: opts.srcUrls, - srcLang: srcLang, - lang: language.name as LanguageName, - topLevel: opts.topLevel ?? inferTopLevel(opts), - noRender: !!opts.noRender, - alphabetizeProperties: !!opts.alphabetizeProperties, - allPropertiesOptional: !!opts.allPropertiesOptional, - rendererOptions: opts.rendererOptions ?? {}, - help: opts.help ?? false, - quiet: opts.quiet ?? false, - version: opts.version ?? false, - out: opts.out, - buildMarkovChain: opts.buildMarkovChain, - additionalSchema: opts.additionalSchema ?? [], - graphqlSchema: opts.graphqlSchema, - graphqlIntrospect: opts.graphqlIntrospect, - httpMethod: opts.httpMethod, - httpHeader: opts.httpHeader, - debug: opts.debug, - telemetry: opts.telemetry, - }; - for (const flagName of inferenceFlagNames) { - const cliName = negatedInferenceFlagName(flagName); - options[cliName] = !!opts[cliName]; - } - - return options; -} - -function makeLangTypeLabel(targetLanguages: readonly TargetLanguage[]): string { - assert( - targetLanguages.length > 0, - "Must have at least one target language", - ); - return targetLanguages - .map((r) => _.minBy(r.names, (s) => s.length)) - .join("|"); -} - -function negatedInferenceFlagName(name: string): string { - const prefix = "infer"; - if (name.startsWith(prefix)) { - name = name.slice(prefix.length); - } - - return "no" + capitalize(name); -} - -function dashedFromCamelCase(name: string): string { - return splitIntoWords(name) - .map((w) => w.word.toLowerCase()) - .join("-"); -} - -function makeOptionDefinitions( - targetLanguages: readonly TargetLanguage[], -): OptionDefinition[] { - const beforeLang: OptionDefinition[] = [ - { - name: "out", - alias: "o", - optionType: "string", - typeLabel: "FILE", - description: "The output file. Determines --lang and --top-level.", - kind: "cli", - }, - { - name: "top-level", - alias: "t", - optionType: "string", - typeLabel: "NAME", - description: "The name for the top level type.", - kind: "cli", - }, - ]; - const lang: OptionDefinition[] = - targetLanguages.length < 2 - ? [] - : [ - { - name: "lang", - alias: "l", - optionType: "string", - typeLabel: "LANG", - description: "The target language.", - kind: "cli", - }, - ]; - const afterLang: OptionDefinition[] = [ - { - name: "src-lang", - alias: "s", - optionType: "string", - defaultValue: undefined, - typeLabel: "SRC_LANG", - description: "The source language (default is json).", - kind: "cli", - }, - { - name: "src", - optionType: "string", - multiple: true, - typeLabel: "FILE|URL|DIRECTORY", - description: "The file, url, or data directory to type.", - kind: "cli", - defaultOption: true, - }, - { - name: "src-urls", - optionType: "string", - typeLabel: "FILE", - description: "Tracery grammar describing URLs to crawl.", - kind: "cli", - }, - ]; - const inference: OptionDefinition[] = Array.from( - mapMap(mapFromObject(inferenceFlags), (flag, name) => { - return { - name: dashedFromCamelCase(negatedInferenceFlagName(name)), - optionType: "boolean" as const, - description: flag.negationDescription + ".", - kind: "cli" as const, - }; - }).values(), - ); - const afterInference: OptionDefinition[] = [ - { - name: "graphql-schema", - optionType: "string", - typeLabel: "FILE", - description: "GraphQL introspection file.", - kind: "cli", - }, - { - name: "graphql-introspect", - optionType: "string", - typeLabel: "URL", - description: "Introspect GraphQL schema from a server.", - kind: "cli", - }, - { - name: "http-method", - optionType: "string", - typeLabel: "METHOD", - description: - "HTTP method to use for the GraphQL introspection query.", - kind: "cli", - }, - { - name: "http-header", - optionType: "string", - multiple: true, - typeLabel: "HEADER", - description: - "Header(s) to attach to all HTTP requests, including the GraphQL introspection query.", - kind: "cli", - }, - { - name: "additional-schema", - alias: "S", - optionType: "string", - multiple: true, - typeLabel: "FILE", - description: "Register the $id's of additional JSON Schema files.", - kind: "cli", - }, - { - name: "no-render", - optionType: "boolean", - description: "Don't render output.", - kind: "cli", - }, - { - name: "alphabetize-properties", - optionType: "boolean", - description: "Alphabetize order of class properties.", - kind: "cli", - }, - { - name: "all-properties-optional", - optionType: "boolean", - description: "Make all class properties optional.", - kind: "cli", - }, - { - name: "build-markov-chain", - optionType: "string", - typeLabel: "FILE", - description: "Markov chain corpus filename.", - kind: "cli", - }, - { - name: "quiet", - optionType: "boolean", - description: "Don't show issues in the generated code.", - kind: "cli", - }, - { - name: "debug", - optionType: "string", - typeLabel: "OPTIONS or all", - description: - "Comma separated debug options: print-graph, print-reconstitution, print-gather-names, print-transformations, print-schema-resolving, print-times, provenance", - kind: "cli", - }, - { - name: "telemetry", - optionType: "string", - typeLabel: "enable|disable", - description: "Enable anonymous telemetry to help improve quicktype", - kind: "cli", - }, - { - name: "help", - alias: "h", - optionType: "boolean", - description: "Get some help.", - kind: "cli", - }, - { - name: "version", - alias: "v", - optionType: "boolean", - description: "Display the version of quicktype", - kind: "cli", - }, - ]; - return beforeLang.concat(lang, afterLang, inference, afterInference); -} - -interface ColumnDefinition { - name: string; - padding?: { left: string; right: string }; - width?: number; -} - -interface TableOptions { - columns: ColumnDefinition[]; -} - -interface UsageSection { - content?: string | string[]; - header?: string; - hide?: string[]; - optionList?: OptionDefinition[]; - tableOptions?: TableOptions; -} - -const tableOptionsForOptions: TableOptions = { - columns: [ - { - name: "option", - width: 60, - }, - { - name: "description", - width: 60, - }, - ], -}; - -function makeSectionsBeforeRenderers( - targetLanguages: readonly TargetLanguage[], -): UsageSection[] { - const langDisplayNames = targetLanguages - .map((r) => r.displayName) - .join(", "); - - return [ - { - header: "Synopsis", - content: [ - `$ quicktype [${chalk.bold("--lang")} LANG] [${chalk.bold("--src-lang")} SRC_LANG] [${chalk.bold( - "--out", - )} FILE] FILE|URL ...`, - "", - ` LANG ... ${makeLangTypeLabel(targetLanguages)}`, - "", - "SRC_LANG ... json|schema|graphql|postman|typescript", - ], - }, - { - header: "Description", - content: `Given JSON sample data, quicktype outputs code for working with that data in ${langDisplayNames}.`, - }, - { - header: "Options", - optionList: makeOptionDefinitions(targetLanguages), - hide: ["no-render", "build-markov-chain"], - tableOptions: tableOptionsForOptions, - }, - ]; -} - -const sectionsAfterRenderers: UsageSection[] = [ - { - header: "Examples", - content: [ - chalk.dim("Generate C# to parse a Bitcoin API"), - "$ quicktype -o LatestBlock.cs https://blockchain.info/latestblock", - "", - chalk.dim( - "Generate Go code from a directory of samples containing:", - ), - chalk.dim( - ` - Foo.json - + Bar - - bar-sample-1.json - - bar-sample-2.json - - Baz.url`, - ), - "$ quicktype -l go samples", - "", - chalk.dim("Generate JSON Schema, then TypeScript"), - "$ quicktype -o schema.json https://blockchain.info/latestblock", - "$ quicktype -o bitcoin.ts --src-lang schema schema.json", - ], - }, - { - content: `Learn more at ${chalk.bold("quicktype.io")}`, - }, -]; - -export function parseCLIOptions( - argv: string[], - targetLanguage?: TargetLanguage, -): CLIOptions { - if (argv.length === 0) { - return inferCLIOptions({ help: true }, targetLanguage); - } - - const targetLanguages = - targetLanguage === undefined - ? defaultTargetLanguages - : [targetLanguage]; - const optionDefinitions = makeOptionDefinitions(targetLanguages); - - // We can only fully parse the options once we know which renderer is selected, - // because there are renderer-specific options. But we only know which renderer - // is selected after we've parsed the options. Hence, we parse the options - // twice. This is the first parse to get the renderer: - const incompleteOptions = inferCLIOptions( - parseOptions(optionDefinitions, argv, true), - targetLanguage, - ); - if (targetLanguage === undefined) { - const languageName = isLanguageName(incompleteOptions.lang) - ? incompleteOptions.lang - : "typescript"; - targetLanguage = getTargetLanguage(languageName); - } - - const rendererOptionDefinitions = - targetLanguage.cliOptionDefinitions.actual; - // Use the global options as well as the renderer options from now on: - const allOptionDefinitions = _.concat( - optionDefinitions, - rendererOptionDefinitions, - ); - // This is the parse that counts: - return inferCLIOptions( - parseOptions(allOptionDefinitions, argv, false), - targetLanguage, - ); -} - -// Parse the options in argv and split them into global options and renderer options, -// according to each option definition's `renderer` field. If `partial` is false this -// will throw if it encounters an unknown option. -function parseOptions( - definitions: OptionDefinition[], - argv: string[], - partial: boolean, -): Partial { - let opts: commandLineArgs.CommandLineOptions; - try { - opts = commandLineArgs( - definitions.map((def) => ({ - ...def, - type: def.optionType === "boolean" ? Boolean : String, - })), - { argv, partial }, - ); - } catch (e) { - assert(!partial, "Partial option parsing should not have failed"); - return messageError("DriverCLIOptionParsingFailed", { - message: exceptionToString(e), - }); - } - - for (const k of Object.keys(opts)) { - if (opts[k] === null) { - return messageError("DriverCLIOptionParsingFailed", { - message: `Missing value for command line option "${k}"`, - }); - } - } - - const options: { - [key: string]: unknown; - rendererOptions: RendererOptions; - } = { rendererOptions: {} }; - for (const optionDefinition of definitions) { - if (!hasOwnProperty(opts, optionDefinition.name)) { - continue; - } - - const optionValue = opts[optionDefinition.name] as string; - if (optionDefinition.kind !== "cli") { - ( - options.rendererOptions as Record< - typeof optionDefinition.name, - unknown - > - )[optionDefinition.name] = optionValue; - } else { - const k = _.lowerFirst( - optionDefinition.name.split("-").map(_.upperFirst).join(""), - ); - options[k] = optionValue; - } - } - - return options; -} - -function usage(targetLanguages: readonly TargetLanguage[]): void { - const rendererSections: UsageSection[] = []; - - for (const language of targetLanguages) { - const definitions = language.cliOptionDefinitions.display; - if (definitions.length === 0) continue; - - rendererSections.push({ - header: `Options for ${language.displayName}`, - optionList: definitions, - tableOptions: tableOptionsForOptions, - }); - } - const sections = _.concat( - makeSectionsBeforeRenderers(targetLanguages), - rendererSections, - sectionsAfterRenderers, - ); - - console.log(getUsage(sections)); -} - -// Returns an array of [name, sourceURIs] pairs. -async function getSourceURIs( - options: CLIOptions, -): Promise> { - if (options.srcUrls !== undefined) { - const json = parseJSON( - await readFromFileOrURL(options.srcUrls, options.httpHeader), - "URL grammar", - options.srcUrls, - ); - const jsonMap = urlsFromURLGrammar(json); - const topLevels = Object.getOwnPropertyNames(jsonMap); - return topLevels.map( - (name) => [name, jsonMap[name]] as [string, string[]], - ); - } - if (options.src.length === 0) { - return [[options.topLevel, ["-"]]]; - } - - return []; -} - -async function typeSourcesForURIs( - name: string, - uris: string[], - options: CLIOptions, -): Promise { - switch (options.srcLang) { - case "json": - return [ - await sourceFromFileOrUrlArray(name, uris, options.httpHeader), - ]; - case "schema": - return uris.map( - (uri) => - ({ kind: "schema", name, uris: [uri] }) as SchemaTypeSource, - ); - default: - return panic( - `typeSourceForURIs must not be called for source language ${options.srcLang}`, - ); - } -} - -async function getSources(options: CLIOptions): Promise { - const sourceURIs = await getSourceURIs(options); - const sourceArrays = await Promise.all( - sourceURIs.map( - async ([name, uris]) => - await typeSourcesForURIs(name, uris, options), - ), - ); - let sources: TypeSource[] = ([] as TypeSource[]).concat(...sourceArrays); - - const exists = options.src.filter(fs.existsSync); - const directories = exists.filter((x) => fs.lstatSync(x).isDirectory()); - - for (const dataDir of directories) { - sources = sources.concat( - await samplesFromDirectory(dataDir, options.httpHeader), - ); - } - - // Every src that's not a directory is assumed to be a file or URL - const filesOrUrls = options.src.filter((x) => !_.includes(directories, x)); - if (!_.isEmpty(filesOrUrls)) { - sources.push( - ...(await typeSourcesForURIs( - options.topLevel, - filesOrUrls, - options, - )), - ); - } - - return sources; -} - -function makeTypeScriptSource(fileNames: string[]): SchemaTypeSource { - return Object.assign( - { kind: "schema" }, - schemaForTypeScriptSources(fileNames), - ) as SchemaTypeSource; -} - -export function jsonInputForTargetLanguage( - targetLanguage: string | TargetLanguage, - languages?: TargetLanguage[], - handleJSONRefs = false, -): JSONInput { - if (typeof targetLanguage === "string") { - const languageName = isLanguageName(targetLanguage) - ? targetLanguage - : "typescript"; - targetLanguage = defined(languageNamed(languageName, languages)); - } - - const compressedJSON = new CompressedJSONFromStream( - targetLanguage.dateTimeRecognizer, - handleJSONRefs, - ); - return new JSONInput(compressedJSON); -} - -async function makeInputData( - sources: TypeSource[], - targetLanguage: TargetLanguage, - additionalSchemaAddresses: readonly string[], - handleJSONRefs: boolean, - httpHeaders?: string[], -): Promise { - const inputData = new InputData(); - - for (const source of sources) { - switch (source.kind) { - case "graphql": - await inputData.addSource( - "graphql", - source, - () => new GraphQLInput(), - ); - break; - case "json": - await inputData.addSource("json", source, () => - jsonInputForTargetLanguage( - targetLanguage, - undefined, - handleJSONRefs, - ), - ); - break; - case "schema": - await inputData.addSource( - "schema", - source, - () => - new JSONSchemaInput( - new FetchingJSONSchemaStore(httpHeaders), - [], - additionalSchemaAddresses, - ), - ); - break; - default: - return assertNever(source); - } - } - - return inputData; -} - -function stringSourceDataToStreamSourceData( - src: JSONSourceData, -): JSONSourceData { - return { - name: src.name, - description: src.description, - samples: src.samples.map( - (sample) => stringToStream(sample) as Readable, - ), - }; -} - -export async function makeQuicktypeOptions( - options: CLIOptions, - targetLanguages?: TargetLanguage[], -): Promise | undefined> { - if (options.help) { - usage(targetLanguages ?? defaultTargetLanguages); - return undefined; - } - - if (options.version) { - console.log(`quicktype version ${packageJSON.version}`); - console.log("Visit quicktype.io for more info."); - return undefined; - } - - if (options.buildMarkovChain !== undefined) { - const contents = fs.readFileSync(options.buildMarkovChain).toString(); - const lines = contents.split("\n"); - const mc = trainMarkovChain(lines, 3); - console.log(JSON.stringify(mc)); - return undefined; - } - - let sources: TypeSource[] = []; - let leadingComments: string[] | undefined = undefined; - let fixedTopLevels = false; - switch (options.srcLang) { - case "graphql": - let schemaString: string | undefined = undefined; - let wroteSchemaToFile = false; - if (options.graphqlIntrospect !== undefined) { - schemaString = await introspectServer( - options.graphqlIntrospect, - withDefault(options.httpMethod, "POST"), - withDefault(options.httpHeader, []), - ); - if (options.graphqlSchema !== undefined) { - fs.writeFileSync(options.graphqlSchema, schemaString); - wroteSchemaToFile = true; - } - } - - const numSources = options.src.length; - if (numSources !== 1) { - if (wroteSchemaToFile) { - // We're done. - return undefined; - } - - if (numSources === 0) { - if (schemaString !== undefined) { - console.log(schemaString); - return undefined; - } - - return messageError("DriverNoGraphQLQueryGiven", {}); - } - } - - const gqlSources: GraphQLTypeSource[] = []; - for (const queryFile of options.src) { - let schemaFileName: string | undefined = undefined; - if (schemaString === undefined) { - schemaFileName = defined(options.graphqlSchema); - schemaString = fs.readFileSync(schemaFileName, "utf8"); - } - - const schema = parseJSON( - schemaString, - "GraphQL schema", - schemaFileName, - ); - const query = await getStream( - await readableFromFileOrURL(queryFile, options.httpHeader), - ); - const name = - numSources === 1 - ? options.topLevel - : typeNameFromFilename(queryFile); - gqlSources.push({ kind: "graphql", name, schema, query }); - } - - sources = gqlSources; - break; - case "json": - case "schema": - sources = await getSources(options); - break; - case "typescript": - sources = [makeTypeScriptSource(options.src)]; - break; - case "postman": - for (const collectionFile of options.src) { - const collectionJSON = fs.readFileSync(collectionFile, "utf8"); - const { sources: postmanSources, description } = - sourcesFromPostmanCollection( - collectionJSON, - collectionFile, - ); - for (const src of postmanSources) { - sources.push( - Object.assign( - { kind: "json" }, - stringSourceDataToStreamSourceData(src), - ) as JSONTypeSource, - ); - } - - if (postmanSources.length > 1) { - fixedTopLevels = true; - } - - if (description !== undefined) { - leadingComments = wordWrap(description).split("\n"); - } - } - - break; - default: - return messageError("DriverUnknownSourceLanguage", { - lang: options.srcLang, - }); - } - - const components = definedMap(options.debug, (d) => d.split(",")); - const debugAll = components?.includes("all"); - let debugPrintGraph = debugAll; - let checkProvenance = debugAll; - let debugPrintReconstitution = debugAll; - let debugPrintGatherNames = debugAll; - let debugPrintTransformations = debugAll; - let debugPrintSchemaResolving = debugAll; - let debugPrintTimes = debugAll; - if (components !== undefined) { - for (let component of components) { - component = component.trim(); - if (component === "print-graph") { - debugPrintGraph = true; - } else if (component === "print-reconstitution") { - debugPrintReconstitution = true; - } else if (component === "print-gather-names") { - debugPrintGatherNames = true; - } else if (component === "print-transformations") { - debugPrintTransformations = true; - } else if (component === "print-times") { - debugPrintTimes = true; - } else if (component === "print-schema-resolving") { - debugPrintSchemaResolving = true; - } else if (component === "provenance") { - checkProvenance = true; - } else if (component !== "all") { - return messageError("DriverUnknownDebugOption", { - option: component, - }); - } - } - } - - if (!isLanguageName(options.lang)) { - return messageError("DriverUnknownOutputLanguage", { - lang: options.lang, - }); - } - - const lang = languageNamed(options.lang, targetLanguages); - - const quicktypeOptions: Partial = { - lang, - alphabetizeProperties: options.alphabetizeProperties, - allPropertiesOptional: options.allPropertiesOptional, - fixedTopLevels, - noRender: options.noRender, - rendererOptions: options.rendererOptions, - leadingComments, - outputFilename: definedMap(options.out, path.basename), - debugPrintGraph, - checkProvenance, - debugPrintReconstitution, - debugPrintGatherNames, - debugPrintTransformations, - debugPrintSchemaResolving, - debugPrintTimes, - }; - for (const flagName of inferenceFlagNames) { - const cliName = negatedInferenceFlagName(flagName); - const v = options[cliName]; - if (typeof v === "boolean") { - quicktypeOptions[flagName] = !v; - } else { - quicktypeOptions[flagName] = true; - } - } - - quicktypeOptions.inputData = await makeInputData( - sources, - lang, - options.additionalSchema, - quicktypeOptions.ignoreJsonRefs !== true, - options.httpHeader, - ); - - return quicktypeOptions; -} +import { parseCLIOptions } from "./cli.options"; +import { inferCLIOptions } from "./inference"; +import { makeQuicktypeOptions } from "./quicktype.options"; +import type { CLIOptions } from "./CLIOptions.types"; +export type { CLIOptions }; export function writeOutput( cliOptions: CLIOptions, resultsByFilename: ReadonlyMap, ): void { - let onFirst = true; + let isFirstRun = true; for (const [filename, { lines, annotations }] of resultsByFilename) { const output = lines.join("\n"); @@ -1181,7 +31,7 @@ export function writeOutput( output, ); } else { - if (!onFirst) { + if (!isFirstRun) { process.stdout.write("\n"); } @@ -1196,10 +46,13 @@ export function writeOutput( continue; } - for (const sa of annotations) { - const annotation = sa.annotation; - if (!(annotation instanceof IssueAnnotationData)) continue; - const lineNumber = sa.span.start.line; + for (const sourceAnnotation of annotations) { + const annotation = sourceAnnotation.annotation; + if (!(annotation instanceof IssueAnnotationData)) { + continue; + } + + const lineNumber = sourceAnnotation.span.start.line; const humanLineNumber = lineNumber + 1; console.error( `\nIssue in line ${humanLineNumber}: ${annotation.message}`, @@ -1207,7 +60,7 @@ export function writeOutput( console.error(`${humanLineNumber}: ${lines[lineNumber]}`); } - onFirst = false; + isFirstRun = false; } } diff --git a/src/inference.ts b/src/inference.ts new file mode 100644 index 000000000..9db3e64e9 --- /dev/null +++ b/src/inference.ts @@ -0,0 +1,128 @@ +import * as path from "node:path"; + +import { withDefault } from "collection-utils"; + +import { + type LanguageName, + type TargetLanguage, + inferenceFlagNames, + isLanguageName, + languageNamed, + messageAssert, + messageError, +} from "quicktype-core"; + +import { negatedInferenceFlagName } from "./utils"; +import type { CLIOptions } from "./CLIOptions.types"; + +const defaultTargetLanguageName = "go"; + +function inferLang( + options: Partial, + defaultLanguage: LanguageName, +): string | LanguageName { + // Output file extension determines the language if language is undefined + if (options.out !== undefined) { + const extension = path.extname(options.out); + if (extension === "") { + return messageError("DriverNoLanguageOrExtension", {}); + } + + return extension.slice(1); + } + + return defaultLanguage; +} + +function inferTopLevel(options: Partial): string { + // Output file name determines the top-level if undefined + if (options.out !== undefined) { + const extension = path.extname(options.out); + const without = path.basename(options.out).replace(extension, ""); + return without; + } + + // Source determines the top-level if undefined + if (options.src !== undefined && options.src.length === 1) { + const src = options.src[0]; + const extension = path.extname(src); + const without = path.basename(src).replace(extension, ""); + return without; + } + + return "TopLevel"; +} + +export function inferCLIOptions( + opts: Partial, + targetLanguage: TargetLanguage | undefined, +): CLIOptions { + let srcLang = opts.srcLang; + if ( + opts.graphqlSchema !== undefined || + opts.graphqlIntrospect !== undefined + ) { + messageAssert( + srcLang === undefined || srcLang === "graphql", + "DriverSourceLangMustBeGraphQL", + {}, + ); + srcLang = "graphql"; + } else if ( + opts.src !== undefined && + opts.src.length > 0 && + opts.src.every((file) => file.endsWith(".ts")) + ) { + srcLang = "typescript"; + } else { + messageAssert(srcLang !== "graphql", "DriverGraphQLSchemaNeeded", {}); + srcLang = withDefault(srcLang, "json"); + } + + let language: TargetLanguage; + if (targetLanguage !== undefined) { + language = targetLanguage; + } else { + const languageName = + opts.lang ?? inferLang(opts, defaultTargetLanguageName); + + if (isLanguageName(languageName)) { + language = languageNamed(languageName); + } else { + return messageError("DriverUnknownOutputLanguage", { + lang: languageName, + }); + } + } + + const options: CLIOptions = { + src: opts.src ?? [], + srcUrls: opts.srcUrls, + srcLang: srcLang, + lang: language.name as LanguageName, + topLevel: opts.topLevel ?? inferTopLevel(opts), + noRender: !!opts.noRender, + alphabetizeProperties: !!opts.alphabetizeProperties, + allPropertiesOptional: !!opts.allPropertiesOptional, + rendererOptions: opts.rendererOptions ?? {}, + help: opts.help ?? false, + quiet: opts.quiet ?? false, + version: opts.version ?? false, + out: opts.out, + buildMarkovChain: opts.buildMarkovChain, + additionalSchema: opts.additionalSchema ?? [], + graphqlSchema: opts.graphqlSchema, + graphqlIntrospect: opts.graphqlIntrospect, + httpMethod: opts.httpMethod, + httpHeader: opts.httpHeader, + debug: opts.debug, + telemetry: opts.telemetry, + }; + for (const flagName of inferenceFlagNames) { + const cliName = negatedInferenceFlagName(flagName); + options[cliName] = !!opts[cliName]; + options[flagName] = !opts[cliName]; + } + + return options; +} diff --git a/src/input.ts b/src/input.ts new file mode 100644 index 000000000..2570e1fd5 --- /dev/null +++ b/src/input.ts @@ -0,0 +1,86 @@ +import type { Readable } from "readable-stream"; + +import { + FetchingJSONSchemaStore, + InputData, + JSONInput, + JSONSchemaInput, + type TargetLanguage, + assertNever, + defined, + isLanguageName, + languageNamed, +} from "quicktype-core"; +import { GraphQLInput } from "quicktype-graphql-input"; + +import { CompressedJSONFromStream } from "./CompressedJSONFromStream"; +import type { TypeSource } from "./TypeSource"; + +export function jsonInputForTargetLanguage( + _targetLanguage: string | TargetLanguage, + languages?: TargetLanguage[], + handleJSONRefs = false, +): JSONInput { + let targetLanguage: TargetLanguage; + if (typeof _targetLanguage === "string") { + const languageName = isLanguageName(_targetLanguage) + ? _targetLanguage + : "typescript"; + targetLanguage = defined(languageNamed(languageName, languages)); + } else { + targetLanguage = _targetLanguage; + } + + const compressedJSON = new CompressedJSONFromStream( + targetLanguage.dateTimeRecognizer, + handleJSONRefs, + ); + return new JSONInput(compressedJSON); +} + +export async function makeInputData( + sources: TypeSource[], + targetLanguage: TargetLanguage, + additionalSchemaAddresses: readonly string[], + handleJSONRefs: boolean, + httpHeaders?: string[], +): Promise { + const inputData = new InputData(); + + for (const source of sources) { + switch (source.kind) { + case "graphql": + await inputData.addSource( + "graphql", + source, + () => new GraphQLInput(), + ); + break; + case "json": + await inputData.addSource("json", source, () => + jsonInputForTargetLanguage( + targetLanguage, + undefined, + handleJSONRefs, + ), + ); + break; + case "schema": + await inputData.addSource( + "schema", + source, + () => + new JSONSchemaInput( + new FetchingJSONSchemaStore(httpHeaders), + [], + additionalSchemaAddresses, + ), + ); + break; + default: + return assertNever(source); + } + } + + return inputData; +} diff --git a/src/optionDefinitions.ts b/src/optionDefinitions.ts new file mode 100644 index 000000000..09ffe653b --- /dev/null +++ b/src/optionDefinitions.ts @@ -0,0 +1,190 @@ +import { + type InferenceFlagName, + type OptionDefinition, + type TargetLanguage, + inferenceFlags, +} from "quicktype-core"; + +import { dashedFromCamelCase, negatedInferenceFlagName } from "./utils"; + +export function makeOptionDefinitions( + targetLanguages: readonly TargetLanguage[], +): OptionDefinition[] { + const beforeLang: OptionDefinition[] = [ + { + name: "out", + alias: "o", + optionType: "string", + typeLabel: "FILE", + description: "The output file. Determines --lang and --top-level.", + kind: "cli", + defaultOption: true, + }, + { + name: "top-level", + alias: "t", + optionType: "string", + typeLabel: "NAME", + description: "The name for the top level type.", + kind: "cli", + }, + ]; + const lang: OptionDefinition[] = + targetLanguages.length < 2 + ? [] + : [ + { + name: "lang", + alias: "l", + optionType: "string", + typeLabel: "LANG", + description: "The target language.", + kind: "cli", + }, + ]; + const afterLang: OptionDefinition[] = [ + { + name: "src-lang", + alias: "s", + optionType: "string", + defaultValue: undefined, + typeLabel: "SRC_LANG", + description: "The source language (default is json).", + kind: "cli", + }, + { + name: "src", + optionType: "string", + multiple: true, + typeLabel: "FILE|URL|DIRECTORY", + description: "The file, url, or data directory to type.", + kind: "cli", + }, + { + name: "src-urls", + optionType: "string", + typeLabel: "FILE", + description: "Tracery grammar describing URLs to crawl.", + kind: "cli", + }, + ]; + const inference: OptionDefinition[] = Object.entries(inferenceFlags).map( + ([name, flag]) => ({ + name: dashedFromCamelCase( + negatedInferenceFlagName(name as InferenceFlagName), + ), + optionType: "boolean" as const, + // biome-ignore lint/style/useTemplate: + description: flag.negationDescription + ".", + kind: "cli" as const, + }), + ); + const afterInference: OptionDefinition[] = [ + { + name: "graphql-schema", + optionType: "string", + typeLabel: "FILE", + description: "GraphQL introspection file.", + kind: "cli", + }, + { + name: "graphql-introspect", + optionType: "string", + typeLabel: "URL", + description: "Introspect GraphQL schema from a server.", + kind: "cli", + }, + { + name: "http-method", + optionType: "string", + typeLabel: "METHOD", + description: + "HTTP method to use for the GraphQL introspection query.", + kind: "cli", + }, + { + name: "http-header", + optionType: "string", + multiple: true, + typeLabel: "HEADER", + description: + "Header(s) to attach to all HTTP requests, including the GraphQL introspection query.", + kind: "cli", + }, + { + name: "additional-schema", + alias: "S", + optionType: "string", + multiple: true, + typeLabel: "FILE", + description: "Register the $id's of additional JSON Schema files.", + kind: "cli", + }, + { + name: "no-render", + optionType: "boolean", + description: "Don't render output.", + kind: "cli", + }, + { + name: "alphabetize-properties", + optionType: "boolean", + description: "Alphabetize order of class properties.", + kind: "cli", + }, + { + name: "all-properties-optional", + optionType: "boolean", + description: "Make all class properties optional.", + kind: "cli", + }, + { + name: "build-markov-chain", + optionType: "string", + typeLabel: "FILE", + description: "Markov chain corpus filename.", + kind: "cli", + }, + { + name: "quiet", + optionType: "boolean", + description: "Don't show issues in the generated code.", + kind: "cli", + }, + { + name: "debug", + optionType: "string", + typeLabel: "OPTIONS or all", + description: + "Comma separated debug options: print-graph, print-reconstitution, print-gather-names, print-transformations, print-schema-resolving, print-times, provenance", + kind: "cli", + }, + { + name: "telemetry", + optionType: "string", + typeLabel: "enable|disable", + description: "Enable anonymous telemetry to help improve quicktype", + kind: "cli", + }, + { + name: "help", + alias: "h", + optionType: "boolean", + description: "Get some help.", + kind: "cli", + }, + { + name: "version", + alias: "v", + optionType: "boolean", + description: "Display the version of quicktype", + kind: "cli", + }, + ]; + return beforeLang.concat(lang, afterLang, inference, afterInference); +} + +export const transformDefinition = (def: OptionDefinition) => ({ + ...def, + type: def.optionType === "boolean" ? Boolean : String, +}); diff --git a/src/quicktype.options.ts b/src/quicktype.options.ts new file mode 100644 index 000000000..0763b1a02 --- /dev/null +++ b/src/quicktype.options.ts @@ -0,0 +1,254 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; + +import { definedMap, withDefault } from "collection-utils"; +import _wordwrap from "wordwrap"; + +import { + type Options, + type TargetLanguage, + defaultTargetLanguages, + defined, + getStream, + inferenceFlagNames, + isLanguageName, + languageNamed, + messageError, + parseJSON, + readableFromFileOrURL, + sourcesFromPostmanCollection, + trainMarkovChain, +} from "quicktype-core"; + +import { introspectServer } from "./GraphQLIntrospection"; +import type { + GraphQLTypeSource, + JSONTypeSource, + TypeSource, +} from "./TypeSource"; +import { makeInputData } from "./input"; +import { getSources, makeTypeScriptSource } from "./sources"; +import { displayUsage } from "./usage"; +import { + negatedInferenceFlagName, + stringSourceDataToStreamSourceData, + typeNameFromFilename, +} from "./utils"; +import type { CLIOptions } from "./CLIOptions.types"; + +const packageJSON = require("../package.json"); + +const wordWrap: (s: string) => string = _wordwrap(90); + +export async function makeQuicktypeOptions( + options: CLIOptions, + targetLanguages?: TargetLanguage[], +): Promise | undefined> { + if (options.help) { + displayUsage(targetLanguages ?? defaultTargetLanguages); + return; + } + + if (options.version) { + console.log(`quicktype version ${packageJSON.version}`); + console.log("Visit quicktype.io for more info."); + return; + } + + if (options.buildMarkovChain !== undefined) { + const contents = fs.readFileSync(options.buildMarkovChain).toString(); + const lines = contents.split("\n"); + const markovChain = trainMarkovChain(lines, 3); + console.log(JSON.stringify(markovChain)); + return; + } + + let sources: TypeSource[] = []; + let leadingComments: string[] | undefined = undefined; + let fixedTopLevels = false; + switch (options.srcLang) { + case "graphql": { + let schemaString: string | undefined = undefined; + let wroteSchemaToFile = false; + if (options.graphqlIntrospect !== undefined) { + schemaString = await introspectServer( + options.graphqlIntrospect, + withDefault(options.httpMethod, "POST"), + withDefault(options.httpHeader, []), + ); + if (options.graphqlSchema !== undefined) { + fs.writeFileSync(options.graphqlSchema, schemaString); + wroteSchemaToFile = true; + } + } + + const numSources = options.src.length; + if (numSources !== 1) { + if (wroteSchemaToFile) { + // We're done. + return; + } + + if (numSources === 0) { + if (schemaString !== undefined) { + console.log(schemaString); + return; + } + + return messageError("DriverNoGraphQLQueryGiven", {}); + } + } + + const graphqlSources: GraphQLTypeSource[] = []; + for (const queryFile of options.src) { + let schemaFileName: string | undefined = undefined; + if (schemaString === undefined) { + schemaFileName = defined(options.graphqlSchema); + schemaString = fs.readFileSync(schemaFileName, "utf8"); + } + + const schema = parseJSON( + schemaString, + "GraphQL schema", + schemaFileName, + ); + const query = await getStream( + await readableFromFileOrURL(queryFile, options.httpHeader), + ); + const name = + numSources === 1 + ? options.topLevel + : typeNameFromFilename(queryFile); + graphqlSources.push({ kind: "graphql", name, schema, query }); + } + + sources = graphqlSources; + break; + } + case "json": + case "schema": + sources = await getSources(options); + break; + case "typescript": + sources = [makeTypeScriptSource(options.src)]; + break; + case "postman": + for (const collectionFile of options.src) { + const collectionJson = fs.readFileSync(collectionFile, "utf8"); + const { sources: postmanSources, description } = + sourcesFromPostmanCollection( + collectionJson, + collectionFile, + ); + for (const src of postmanSources) { + sources.push( + Object.assign( + { kind: "json" }, + stringSourceDataToStreamSourceData(src), + ) as JSONTypeSource, + ); + } + + if (postmanSources.length > 1) { + fixedTopLevels = true; + } + + if (description !== undefined) { + leadingComments = wordWrap(description).split("\n"); + } + } + + break; + default: + return messageError("DriverUnknownSourceLanguage", { + lang: options.srcLang, + }); + } + + const components = definedMap(options.debug, (d) => d.split(",")); + const debugAll = components?.includes("all"); + let debugPrintGraph = debugAll; + let checkProvenance = debugAll; + let debugPrintReconstitution = debugAll; + let debugPrintGatherNames = debugAll; + let debugPrintTransformations = debugAll; + let debugPrintSchemaResolving = debugAll; + let debugPrintTimes = debugAll; + if (components !== undefined) { + for (let component of components) { + component = component.trim(); + if (component === "print-graph") { + debugPrintGraph = true; + } else if (component === "print-reconstitution") { + debugPrintReconstitution = true; + } else if (component === "print-gather-names") { + debugPrintGatherNames = true; + } else if (component === "print-transformations") { + debugPrintTransformations = true; + } else if (component === "print-times") { + debugPrintTimes = true; + } else if (component === "print-schema-resolving") { + debugPrintSchemaResolving = true; + } else if (component === "provenance") { + checkProvenance = true; + } else if (component !== "all") { + return messageError("DriverUnknownDebugOption", { + option: component, + }); + } + } + } + + if (!isLanguageName(options.lang)) { + return messageError("DriverUnknownOutputLanguage", { + lang: options.lang, + }); + } + + const lang = languageNamed(options.lang, targetLanguages); + + const quicktypeOptions: Partial = { + lang, + alphabetizeProperties: options.alphabetizeProperties, + allPropertiesOptional: options.allPropertiesOptional, + fixedTopLevels, + noRender: options.noRender, + rendererOptions: options.rendererOptions, + leadingComments, + outputFilename: definedMap(options.out, path.basename), + debugPrintGraph, + checkProvenance, + debugPrintReconstitution, + debugPrintGatherNames, + debugPrintTransformations, + debugPrintSchemaResolving, + debugPrintTimes, + }; + + for (const flagName of inferenceFlagNames) { + const cliName = negatedInferenceFlagName(flagName); + const negatedValue = options[cliName]; + const positiveValue = options[flagName]; + const value = !(positiveValue == null) + ? positiveValue + : !(negatedValue == null) + ? negatedValue + : undefined; + + if (typeof value === "boolean") { + quicktypeOptions[flagName] = !value; + } else { + quicktypeOptions[flagName] = true; + } + } + + quicktypeOptions.inputData = await makeInputData( + sources, + lang, + options.additionalSchema, + quicktypeOptions.ignoreJsonRefs !== true, + options.httpHeader, + ); + + return quicktypeOptions; +} diff --git a/src/sources.ts b/src/sources.ts new file mode 100644 index 000000000..f4adbfaf2 --- /dev/null +++ b/src/sources.ts @@ -0,0 +1,280 @@ +#!/usr/bin/env node + +import * as fs from "node:fs"; +import * as path from "node:path"; + +import * as _ from "lodash"; +import type { Readable } from "readable-stream"; +import _wordwrap from "wordwrap"; + +import { + assertNever, + getStream, + messageAssert, + messageError, + panic, + parseJSON, + readFromFileOrURL, + readableFromFileOrURL, +} from "quicktype-core"; +import { schemaForTypeScriptSources } from "quicktype-typescript-input"; + +import type { + GraphQLTypeSource, + JSONTypeSource, + SchemaTypeSource, + TypeSource, +} from "./TypeSource"; +import { urlsFromURLGrammar } from "./URLGrammar"; +import { typeNameFromFilename } from "./utils"; +import type { CLIOptions } from "./CLIOptions.types"; + +async function sourceFromFileOrUrlArray( + name: string, + filesOrUrls: string[], + httpHeaders?: string[], +): Promise { + const samples = await Promise.all( + filesOrUrls.map( + async (file) => await readableFromFileOrURL(file, httpHeaders), + ), + ); + return { kind: "json", name, samples }; +} + +async function samplesFromDirectory( + dataDir: string, + httpHeaders?: string[], +): Promise { + async function readFilesOrURLsInDirectory( + d: string, + ): Promise { + const files = fs + .readdirSync(d) + .map((x) => path.join(d, x)) + .filter((x) => fs.lstatSync(x).isFile()); + // Each file is a (Name, JSON | URL) + const sourcesInDir: TypeSource[] = []; + const graphQLSources: GraphQLTypeSource[] = []; + let graphQLSchema: Readable | undefined = undefined; + let graphQLSchemaFileName: string | undefined = undefined; + for (let file of files) { + const name = typeNameFromFilename(file); + + let fileOrUrl = file; + file = file.toLowerCase(); + + // If file is a URL string, download it + if (file.endsWith(".url")) { + fileOrUrl = fs.readFileSync(file, "utf8").trim(); + } + + if (file.endsWith(".url") || file.endsWith(".json")) { + sourcesInDir.push({ + kind: "json", + name, + samples: [ + await readableFromFileOrURL(fileOrUrl, httpHeaders), + ], + }); + } else if (file.endsWith(".schema")) { + sourcesInDir.push({ + kind: "schema", + name, + uris: [fileOrUrl], + }); + } else if (file.endsWith(".gqlschema")) { + messageAssert( + graphQLSchema === undefined, + "DriverMoreThanOneGraphQLSchemaInDir", + { + dir: dataDir, + }, + ); + graphQLSchema = await readableFromFileOrURL( + fileOrUrl, + httpHeaders, + ); + graphQLSchemaFileName = fileOrUrl; + } else if (file.endsWith(".graphql")) { + graphQLSources.push({ + kind: "graphql", + name, + schema: undefined, + query: await getStream( + await readableFromFileOrURL(fileOrUrl, httpHeaders), + ), + }); + } + } + + if (graphQLSources.length > 0) { + if (graphQLSchema === undefined) { + return messageError("DriverNoGraphQLSchemaInDir", { + dir: dataDir, + }); + } + + const schema = parseJSON( + await getStream(graphQLSchema), + "GraphQL schema", + graphQLSchemaFileName, + ); + for (const source of graphQLSources) { + source.schema = schema; + sourcesInDir.push(source); + } + } + + return sourcesInDir; + } + + const contents = fs.readdirSync(dataDir).map((x) => path.join(dataDir, x)); + const directories = contents.filter((x) => fs.lstatSync(x).isDirectory()); + + let sources = await readFilesOrURLsInDirectory(dataDir); + + for (const dir of directories) { + let jsonSamples: Readable[] = []; + const schemaSources: SchemaTypeSource[] = []; + const graphQLSources: GraphQLTypeSource[] = []; + + for (const source of await readFilesOrURLsInDirectory(dir)) { + switch (source.kind) { + case "json": + jsonSamples = jsonSamples.concat(source.samples); + break; + case "schema": + schemaSources.push(source); + break; + case "graphql": + graphQLSources.push(source); + break; + default: + return assertNever(source); + } + } + + if ( + jsonSamples.length > 0 && + schemaSources.length + graphQLSources.length > 0 + ) { + return messageError("DriverCannotMixJSONWithOtherSamples", { + dir: dir, + }); + } + + // FIXME: rewrite this to be clearer + const oneUnlessEmpty = (xs: TypeSource[]): 0 | 1 => + Math.sign(xs.length) as 0 | 1; + if ( + oneUnlessEmpty(schemaSources) + oneUnlessEmpty(graphQLSources) > + 1 + ) { + return messageError("DriverCannotMixNonJSONInputs", { dir: dir }); + } + + if (jsonSamples.length > 0) { + sources.push({ + kind: "json", + name: path.basename(dir), + samples: jsonSamples, + }); + } + + sources = sources.concat(schemaSources); + sources = sources.concat(graphQLSources); + } + + return sources; +} + +// Returns an array of [name, sourceURIs] pairs. +async function getSourceURIs( + options: CLIOptions, +): Promise> { + if (options.srcUrls !== undefined) { + const json = parseJSON( + await readFromFileOrURL(options.srcUrls, options.httpHeader), + "URL grammar", + options.srcUrls, + ); + const jsonMap = urlsFromURLGrammar(json); + const topLevels = Object.getOwnPropertyNames(jsonMap); + return topLevels.map( + (name) => [name, jsonMap[name]] as [string, string[]], + ); + } + if (options.src.length === 0) { + return [[options.topLevel, ["-"]]]; + } + + return []; +} + +async function typeSourcesForURIs( + name: string, + uris: string[], + options: CLIOptions, +): Promise { + switch (options.srcLang) { + case "json": + return [ + await sourceFromFileOrUrlArray(name, uris, options.httpHeader), + ]; + case "schema": + return uris.map( + (uri) => + ({ + kind: "schema", + name, + uris: [uri], + }) as SchemaTypeSource, + ); + default: + return panic( + `typeSourceForURIs must not be called for source language ${options.srcLang}`, + ); + } +} + +export async function getSources(options: CLIOptions): Promise { + const sourceURIs = await getSourceURIs(options); + const sourceArrays = await Promise.all( + sourceURIs.map( + async ([name, uris]) => + await typeSourcesForURIs(name, uris, options), + ), + ); + let sources: TypeSource[] = ([] as TypeSource[]).concat(...sourceArrays); + + const exists = options.src.filter(fs.existsSync); + const directories = exists.filter((x) => fs.lstatSync(x).isDirectory()); + + for (const dataDir of directories) { + sources = sources.concat( + await samplesFromDirectory(dataDir, options.httpHeader), + ); + } + + // Every src that's not a directory is assumed to be a file or URL + const filesOrUrls = options.src.filter((x) => !_.includes(directories, x)); + if (!_.isEmpty(filesOrUrls)) { + sources.push( + ...(await typeSourcesForURIs( + options.topLevel, + filesOrUrls, + options, + )), + ); + } + + return sources; +} + +export function makeTypeScriptSource(fileNames: string[]): SchemaTypeSource { + return Object.assign( + { kind: "schema" }, + schemaForTypeScriptSources(fileNames), + ) as SchemaTypeSource; +} diff --git a/src/usage.ts b/src/usage.ts new file mode 100644 index 000000000..b94d0d536 --- /dev/null +++ b/src/usage.ts @@ -0,0 +1,127 @@ +#!/usr/bin/env node + +import chalk from "chalk"; +import getUsage from "command-line-usage"; +import * as _ from "lodash"; +import _wordwrap from "wordwrap"; + +import type { OptionDefinition, TargetLanguage } from "quicktype-core"; + +import { makeOptionDefinitions } from "./optionDefinitions"; +import { makeLangTypeLabel } from "./utils"; + +interface ColumnDefinition { + name: string; + padding?: { left: string; right: string }; + width?: number; +} + +interface TableOptions { + columns: ColumnDefinition[]; +} + +interface UsageSection { + content?: string | string[]; + header?: string; + hide?: string[]; + optionList?: OptionDefinition[]; + tableOptions?: TableOptions; +} + +const tableOptionsForOptions: TableOptions = { + columns: [ + { + name: "option", + width: 60, + }, + { + name: "description", + width: 60, + }, + ], +}; + +function makeSectionsBeforeRenderers( + targetLanguages: readonly TargetLanguage[], +): UsageSection[] { + const langDisplayNames = targetLanguages + .map((r) => r.displayName) + .join(", "); + + return [ + { + header: "Synopsis", + content: [ + `$ quicktype [${chalk.bold("--lang")} LANG] [${chalk.bold("--src-lang")} SRC_LANG] [${chalk.bold( + "--out", + )} FILE] FILE|URL ...`, + "", + ` LANG ... ${makeLangTypeLabel(targetLanguages)}`, + "", + "SRC_LANG ... json|schema|graphql|postman|typescript", + ], + }, + { + header: "Description", + content: `Given JSON sample data, quicktype outputs code for working with that data in ${langDisplayNames}.`, + }, + { + header: "Options", + optionList: makeOptionDefinitions(targetLanguages), + hide: ["no-render", "build-markov-chain"], + tableOptions: tableOptionsForOptions, + }, + ]; +} + +const sectionsAfterRenderers: UsageSection[] = [ + { + header: "Examples", + content: [ + chalk.dim("Generate C# to parse a Bitcoin API"), + "$ quicktype -o LatestBlock.cs https://blockchain.info/latestblock", + "", + chalk.dim( + "Generate Go code from a directory of samples containing:", + ), + chalk.dim( + ` - Foo.json + + Bar + - bar-sample-1.json + - bar-sample-2.json + - Baz.url`, + ), + "$ quicktype -l go samples", + "", + chalk.dim("Generate JSON Schema, then TypeScript"), + "$ quicktype -o schema.json https://blockchain.info/latestblock", + "$ quicktype -o bitcoin.ts --src-lang schema schema.json", + ], + }, + { + content: `Learn more at ${chalk.bold("quicktype.io")}`, + }, +]; + +export function displayUsage(targetLanguages: readonly TargetLanguage[]): void { + const rendererSections: UsageSection[] = []; + + for (const language of targetLanguages) { + const definitions = language.cliOptionDefinitions.display; + if (definitions.length === 0) continue; + + rendererSections.push({ + header: `Options for ${language.displayName}`, + optionList: definitions, + tableOptions: tableOptionsForOptions, + }); + } + + const sections = _.concat( + makeSectionsBeforeRenderers(targetLanguages), + rendererSections, + sectionsAfterRenderers, + ); + + console.log(getUsage(sections)); +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 000000000..d2f4a5fde --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,61 @@ +import * as path from "node:path"; + +import * as _ from "lodash"; +import type { Readable } from "readable-stream"; +import stringToStream from "string-to-stream"; +import _wordwrap from "wordwrap"; + +import { + assert, + type InferenceFlagName, + splitIntoWords, + type JSONSourceData, + type TargetLanguage, +} from "quicktype-core"; + +import type { NegatedInferenceFlagName } from "./CLIOptions.types"; + +export function makeLangTypeLabel( + targetLanguages: readonly TargetLanguage[], +): string { + assert( + targetLanguages.length > 0, + "Must have at least one target language", + ); + return targetLanguages + .map((r) => _.minBy(r.names, (s) => s.length)) + .join("|"); +} + +export function negatedInferenceFlagName( + inputName: Input, +): NegatedInferenceFlagName { + const withoutLeadingInfer = inputName.replace(/^infer/, ""); + const asPascalCase = withoutLeadingInfer.replace(/^\w/, (x) => + x.toUpperCase(), + ); + return `no${asPascalCase}` as NegatedInferenceFlagName; +} + +export function dashedFromCamelCase(name: string): string { + return splitIntoWords(name) + .map((w) => w.word.toLowerCase()) + .join("-"); +} + +export function typeNameFromFilename(filename: string): string { + const name = path.basename(filename); + return name.substring(0, name.lastIndexOf(".")); +} + +export function stringSourceDataToStreamSourceData( + src: JSONSourceData, +): JSONSourceData { + return { + name: src.name, + description: src.description, + samples: src.samples.map( + (sample) => stringToStream(sample) as Readable, + ), + }; +} diff --git a/test/buildkite.ts b/test/buildkite.ts index e0d2ac89d..5869329bf 100644 --- a/test/buildkite.ts +++ b/test/buildkite.ts @@ -11,10 +11,8 @@ function getChangedFiles(base: string, commit: string): string[] { return diff.trim().split("\n"); } -export function affectedFixtures( - changedFiles: string[] | undefined = undefined, -): Fixture[] { - if (changedFiles === undefined) { +export function affectedFixtures(_changedFiles?: string[]): Fixture[] { + if (_changedFiles === undefined) { const { GITHUB_BASE_REF: base, GITHUB_SHA: commit } = process.env; return commit === undefined ? allFixtures @@ -22,7 +20,9 @@ export function affectedFixtures( } // We can ignore changes in Markdown files - changedFiles = _.reject(changedFiles, (file) => _.endsWith(file, ".md")); + const changedFiles = _.reject(_changedFiles, (file) => + _.endsWith(file, ".md"), + ); // All fixtures are dirty if any changed file is not included as a sourceFile of some fixture. const fileDependencies = _.flatMap( diff --git a/test/cli/options.test.ts b/test/cli/options.test.ts new file mode 100644 index 000000000..b0b7209ad --- /dev/null +++ b/test/cli/options.test.ts @@ -0,0 +1,177 @@ +import { expect, it, beforeEach } from "vitest"; + +import commandLineArgs from "command-line-args"; + +import { inferenceFlagNames, type Option } from "../../packages/quicktype-core"; +import type { EnumOption } from "../../packages/quicktype-core/dist/RendererOptions"; +import { Chance } from "../../packages/quicktype-core/src/support/Chance"; +import { all as sourceLanguages } from "../../packages/quicktype-core/dist/language/All"; + +import { parseOptions } from "../../src/cli.options"; +import { + makeOptionDefinitions, + transformDefinition, +} from "../../src/optionDefinitions"; +import { negatedInferenceFlagName } from "../../src/utils"; +import { inferCLIOptions } from "../../src/inference"; + +import * as fixtureLanguages from "../languages"; + +const optionsDefinitions = makeOptionDefinitions([]); +const chance = new Chance(0); + +let randomStrings: Record = {}; +let argv: string[] = []; + +beforeEach(() => { + randomStrings = {}; + argv = optionsDefinitions.reduce((strArr, def, i) => { + if (def.optionType === "string") { + const randomString = chance.animal(); + randomStrings[i] = randomString; + + return strArr.concat([`--${def.name}`, randomString]); + } + + return strArr.concat([`--${def.name}`]); + }, [] as string[]); +}); + +it("should call command-line-args for all generic cli options", () => { + const args = commandLineArgs(optionsDefinitions.map(transformDefinition), { + argv, + partial: true, + }); + + const expectedOutput = optionsDefinitions.reduce((acc, def, i) => { + if (def.optionType === "string") { + if (def.multiple) { + acc[def.name] = [randomStrings[i]]; + } else { + acc[def.name] = randomStrings[i]; + } + return acc; + } + + acc[def.name] = true; + return acc; + }, {}); + + expect(args).toStrictEqual(expectedOutput); +}); + +it("should process inference cli options correctly", () => { + const randomTargetLang = chance.pick(sourceLanguages); + + // throw for graphql check + const parsedOptions = parseOptions(optionsDefinitions, argv, true); + expect(() => inferCLIOptions(parsedOptions, randomTargetLang)).toThrow(); + + // pass graphql check + parsedOptions.srcLang = "graphql"; + expect(() => + inferCLIOptions(parsedOptions, randomTargetLang), + ).not.toThrow(); + + // infer ts input + parsedOptions.srcLang = undefined; + parsedOptions.graphqlIntrospect = undefined; + parsedOptions.graphqlSchema = undefined; + parsedOptions.src = parsedOptions.src?.map((str) => `${str}.ts`); + expect(inferCLIOptions(parsedOptions, randomTargetLang)).toMatchObject({ + srcLang: "typescript", + }); + + // throw on unknown target lang + expect(() => inferCLIOptions(parsedOptions, undefined)).toThrow(); + + // infer target lang + parsedOptions.lang = undefined; + parsedOptions.out = `${parsedOptions.out}.${randomTargetLang.extension}`; + + const inferredOptionsWithTargetLang = inferCLIOptions( + parsedOptions, + undefined, + ); + expect(inferredOptionsWithTargetLang).toMatchObject({ + lang: randomTargetLang.name, + }); + + // check inference flags + for (const flagName of inferenceFlagNames) { + const negatedFlagName = negatedInferenceFlagName(flagName); + + expect(inferredOptionsWithTargetLang[flagName]).toEqual( + !!parsedOptions[flagName], + ); + expect(inferredOptionsWithTargetLang[negatedFlagName]).toEqual( + !parsedOptions[flagName], + ); + } +}); + +for (const [name, fixtureLanguage] of Object.entries(fixtureLanguages)) { + const languageClass = sourceLanguages.find((lang) => + (lang.names as readonly string[]).includes(fixtureLanguage.name), + ); + + if (!languageClass) { + // it.skip(`does not have language fixture for ${fixtureLanguage.name} language`, () => {}); + continue; + } + + const fixtureRendererOptionsEntries = [ + fixtureLanguage.rendererOptions, + ...fixtureLanguage.quickTestRendererOptions, + ].flatMap((optionsObj) => Object.entries(optionsObj)); + + const dedupeKeys = {}; + const rendererOptionsArgv = fixtureRendererOptionsEntries.reduce( + (strArr, [k, v]) => { + if (k in dedupeKeys) { + return strArr; + } + + dedupeKeys[k] = true; + return strArr.concat([`--${k}`, v as string]); + }, + [] as string[], + ); + + it(`should parse rendererOptions for ${name} fixture`, () => { + const parsedOptions = parseOptions( + optionsDefinitions.concat( + languageClass.cliOptionDefinitions.actual, + ), + argv.concat(rendererOptionsArgv), + true, + ); + const options = languageClass.getOptions(); + + const isAllRendererOptionsValid = + parsedOptions.rendererOptions && + (Object.values(options) as Option[]).every( + (option) => { + const parsedValue = + parsedOptions.rendererOptions?.[option.name]; + + if (option.definition.optionType === "enum") { + try { + // biome-ignore lint/suspicious/noExplicitAny: + (option as EnumOption).getEnumValue( + parsedValue, + ); + return true; + } catch { + return false; + } + } + + // biome-ignore lint/suspicious/useValidTypeof: optionType is only 'string' | 'boolean' in this case + return option.definition.optionType === typeof parsedValue; + }, + ); + + expect(isAllRendererOptionsValid).toBeTruthy(); + }); +} diff --git a/test/cli/quicktype.options.test.ts b/test/cli/quicktype.options.test.ts new file mode 100644 index 000000000..d70057442 --- /dev/null +++ b/test/cli/quicktype.options.test.ts @@ -0,0 +1,230 @@ +/** Generated unit test */ +import { describe, expect, it, vi } from "vitest"; + +import { makeQuicktypeOptions } from "../../src/quicktype.options"; + +import * as fs from "node:fs"; +import * as usage from "../../src/usage"; +import * as sources from "../../src/sources"; +import * as graphql from "../../src/GraphQLIntrospection"; +import * as quicktypeCore from "quicktype-core"; + +vi.mock("node:fs", () => ({ + readFileSync: vi.fn(), + writeFileSync: vi.fn(), +})); +vi.mock("../../src/usage", () => ({ + displayUsage: vi.fn(), +})); +vi.mock("../../src/input", () => ({ + makeInputData: vi.fn().mockResolvedValue("inputData"), +})); +vi.mock("../../src/sources", () => ({ + getSources: vi.fn().mockResolvedValue([{ kind: "json", name: "test" }]), + makeTypeScriptSource: vi + .fn() + .mockReturnValue({ kind: "typescript", name: "ts" }), +})); +vi.mock("../../src/GraphQLIntrospection", () => ({ + introspectServer: vi.fn().mockResolvedValue('{"__schema":{}}'), +})); +vi.mock("quicktype-core", async (importOriginal) => { + const mod = await importOriginal(); + return { + // @ts-expect-error + ...mod, + parseJSON: vi.fn().mockReturnValue({}), + getStream: vi.fn().mockResolvedValue("query"), + readableFromFileOrURL: vi.fn().mockResolvedValue("stream"), + sourcesFromPostmanCollection: vi.fn().mockReturnValue({ + sources: ["postmanSrc"], + description: "desc", + }), + trainMarkovChain: vi.fn().mockReturnValue({ chain: true }), + isLanguageName: vi + .fn() + .mockImplementation( + (lang) => lang === "typescript" || lang === "json", + ), + languageNamed: vi.fn().mockReturnValue({ name: "typescript" }), + messageError: vi.fn().mockReturnValue("error"), + inferenceFlagNames: ["inferMaps", "inferEnums"], + negatedInferenceFlagName: (flag) => + `no${flag[0].toUpperCase()}${flag.slice(1)}`, + defined: vi.fn((x) => x), + defaultTargetLanguages: [], + }; +}); +vi.mock("../../src/utils", async (importOriginal) => { + const mod = await importOriginal(); + return { + // @ts-expect-error + ...mod, + stringSourceDataToStreamSourceData: vi + .fn() + .mockReturnValue({ data: "stream" }), + typeNameFromFilename: vi.fn().mockReturnValue("TypeName"), + negatedInferenceFlagName: (flag) => + `no${flag[0].toUpperCase()}${flag.slice(1)}`, + }; +}); + +describe("makeQuicktypeOptions", () => { + const baseOptions = { + lang: "typescript", + srcLang: "json", + src: ["input.json"], + rendererOptions: {}, + debug: undefined, + out: "output.ts", + help: undefined, + version: undefined, + buildMarkovChain: undefined, + graphqlIntrospect: undefined, + graphqlSchema: undefined, + httpHeader: undefined, + topLevel: undefined, + additionalSchema: undefined, + alphabetizeProperties: false, + allPropertiesOptional: false, + noRender: false, + }; + + // afterEach(() => { + // vi.clearAllMocks(); + // }); + + it("should call displayUsage and return undefined if help is set", async () => { + const options = { ...baseOptions, help: true }; + const spy = vi.spyOn(usage, "displayUsage"); + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(spy).toHaveBeenCalled(); + expect(result).toBeUndefined(); + }); + + it("should log version and return undefined if version is set", async () => { + const options = { ...baseOptions, version: true }; + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(logSpy).toHaveBeenCalled(); + expect(result).toBeUndefined(); + logSpy.mockRestore(); + }); + + it("should handle buildMarkovChain option", async () => { + const options = { ...baseOptions, buildMarkovChain: "file.txt" }; + const readSpy = vi + .spyOn(fs, "readFileSync") + // biome-ignore lint/suspicious/noExplicitAny: + .mockReturnValue("a\nb\nc" as any); + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(readSpy).toHaveBeenCalledWith("file.txt"); + expect(logSpy).toHaveBeenCalled(); + expect(result).toBeUndefined(); + logSpy.mockRestore(); + }); + + it("should handle JSON srcLang and call getSources", async () => { + const options = { ...baseOptions, srcLang: "json" }; + const getSourcesSpy = vi.spyOn(sources, "getSources"); + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(getSourcesSpy).toHaveBeenCalled(); + expect(result).toMatchObject({ + lang: { name: "typescript" }, + inputData: "inputData", + }); + }); + + it("should handle typescript srcLang and call makeTypeScriptSource", async () => { + const options = { ...baseOptions, srcLang: "typescript" }; + const makeTSSpy = vi.spyOn(sources, "makeTypeScriptSource"); + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(makeTSSpy).toHaveBeenCalledWith(options.src); + expect(result).toMatchObject({ + lang: { name: "typescript" }, + inputData: "inputData", + }); + }); + + it("should handle postman srcLang and call sourcesFromPostmanCollection", async () => { + const options = { + ...baseOptions, + srcLang: "postman", + src: ["collection.json"], + }; + // biome-ignore lint/suspicious/noExplicitAny: + vi.spyOn(fs, "readFileSync").mockReturnValue("{}" as any); + const spy = vi.spyOn(quicktypeCore, "sourcesFromPostmanCollection"); + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(spy).toHaveBeenCalled(); + expect(result).toMatchObject({ + lang: { name: "typescript" }, + leadingComments: ["desc"], + inputData: "inputData", + }); + }); + + it("should handle graphql srcLang with introspection and schema file", async () => { + const options = { + ...baseOptions, + srcLang: "graphql", + src: ["query.graphql"], + graphqlIntrospect: "http://localhost/graphql", + graphqlSchema: "schema.json", + }; + const introspectSpy = vi.spyOn(graphql, "introspectServer"); + const writeSpy = vi.spyOn(fs, "writeFileSync"); + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(introspectSpy).toHaveBeenCalled(); + expect(writeSpy).toHaveBeenCalledWith( + "schema.json", + expect.any(String), + ); + expect(result).toMatchObject({ + lang: { name: "typescript" }, + inputData: "inputData", + }); + }); + + it("should return error for unknown srcLang", async () => { + const options = { ...baseOptions, srcLang: "unknown" }; + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(result).toBe("error"); + }); + + it("should return error for unknown output language", async () => { + const options = { ...baseOptions, lang: "unknown" }; + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(result).toBe("error"); + }); + + it("should set debug flags from options.debug", async () => { + const options = { ...baseOptions, debug: "print-graph,provenance" }; + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(result?.debugPrintGraph).toBe(true); + expect(result?.checkProvenance).toBe(true); + }); + + it("should set inference flags from CLI options", async () => { + const options = { + ...baseOptions, + noInferMaps: true, + noInferEnums: false, + }; + // biome-ignore lint/suspicious/noExplicitAny: + const result = await makeQuicktypeOptions(options as any); + expect(result?.inferMaps).toBe(false); + expect(result?.inferEnums).toBe(true); + }); +}); diff --git a/test/fixtures.ts b/test/fixtures.ts index d4eb8f2fd..f31d484d9 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -103,7 +103,7 @@ function runEnvForLanguage( const newEnv = Object.assign({}, process.env); for (const option of Object.keys(additionalRendererOptions)) { - newEnv["QUICKTYPE_" + option.toUpperCase().replace("-", "_")] = ( + newEnv[`QUICKTYPE_${option.toUpperCase().replace("-", "_")}`] = ( additionalRendererOptions[ option as keyof typeof additionalRendererOptions ] as Option @@ -216,10 +216,6 @@ export abstract class Fixture { } abstract class LanguageFixture extends Fixture { - constructor(language: languages.Language) { - super(language); - } - async setup() { const setupCommand = this.language.setupCommand; if (!setupCommand || ONLY_OUTPUT) { diff --git a/test/languages.ts b/test/languages.ts index 9573f3e57..72585e771 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -1,6 +1,6 @@ import type { LanguageName } from "quicktype-core"; -import * as process from "process"; +import * as process from "node:process"; // @ts-ignore import type { RendererOptions } from "../dist/quicktype-core/Run"; @@ -42,7 +42,7 @@ export interface Language { skipMiscJSON: boolean; skipSchema: string[]; rendererOptions: RendererOptions; - quickTestRendererOptions: (RendererOptions | [string, RendererOptions])[]; + quickTestRendererOptions: RendererOptions[]; sourceFiles?: string[]; } @@ -672,7 +672,7 @@ export const ElmLanguage: Language = { export const SwiftLanguage: Language = { name: "swift", base: "test/fixtures/swift", - compileCommand: `swiftc -o quicktype main.swift quicktype.swift`, + compileCommand: "swiftc -o quicktype main.swift quicktype.swift", runCommand(sample: string) { return `./quicktype "${sample}"`; }, @@ -760,7 +760,7 @@ export const SwiftLanguage: Language = { export const ObjectiveCLanguage: Language = { name: "objective-c", base: "test/fixtures/objective-c", - compileCommand: `clang -Werror -framework Foundation *.m -o test`, + compileCommand: "clang -Werror -framework Foundation *.m -o test", runCommand(sample: string) { return `cp "${sample}" sample.json && ./test sample.json`; }, diff --git a/test/lib/deepEquals.ts b/test/lib/deepEquals.ts index eaeb1209a..8b7db9e15 100644 --- a/test/lib/deepEquals.ts +++ b/test/lib/deepEquals.ts @@ -3,6 +3,7 @@ import type { Moment } from "moment"; import type { ComparisonRelaxations } from "../utils"; function pathToString(path: string[]): string { + // biome-ignore lint/style/useTemplate: return "." + path.join("."); } @@ -13,9 +14,13 @@ declare namespace Math { function tryParseMoment(s: string): [Moment | undefined, boolean] { let m = moment(s); - if (m.isValid()) return [m, false]; + if (m.isValid()) { + return [m, false]; + } m = moment(s, "HH:mm:ss.SSZ"); - if (m.isValid()) return [m, true]; + if (m.isValid()) { + return [m, true]; + } return [undefined, false]; } diff --git a/test/lib/multicore.ts b/test/lib/multicore.ts index 117f28010..d4169709e 100644 --- a/test/lib/multicore.ts +++ b/test/lib/multicore.ts @@ -1,6 +1,5 @@ -import cluster from "cluster"; -import process from "process"; -import * as _ from "lodash"; +import cluster from "node:cluster"; +import process from "node:process"; const exit = require("exit"); @@ -18,8 +17,8 @@ function randomPick(arr: T[]): T { } function guys(n: number): string { - return _.range(n) - .map((_i) => randomPick(WORKERS)) + return Array.from({ length: n }) + .map(() => randomPick(WORKERS)) .join(" "); } @@ -63,13 +62,13 @@ export async function inParallel( await map(item, i); } } else { - _.range(workers).forEach((i) => + for (let i = 0; i < workers; i++) { cluster.fork({ worker: i, // https://github.com/TypeStrong/ts-node/issues/367 TS_NODE_PROJECT: "test/tsconfig.json", - }), - ); + }); + } } } else { // Setup a worker diff --git a/test/test.ts b/test/test.ts index f6015eafa..86d7ec05b 100755 --- a/test/test.ts +++ b/test/test.ts @@ -1,9 +1,8 @@ -import * as os from "os"; -import * as _ from "lodash"; +import * as os from "node:os"; import { inParallel } from "./lib/multicore"; import { execAsync, type Sample } from "./utils"; -import { type Fixture, allFixtures } from "./fixtures"; +import { allFixtures } from "./fixtures"; import { affectedFixtures, divideParallelJobs } from "./buildkite"; const exit = require("exit"); @@ -20,8 +19,8 @@ async function main(sources: string[]) { const fixturesFromCmdline = process.env.FIXTURE; if (fixturesFromCmdline) { const fixtureNames = fixturesFromCmdline.split(","); - fixtures = _.filter(fixtures, (fixture) => - _.some(fixtureNames, (name) => fixture.runForName(name)), + fixtures = fixtures.filter((fixture) => + fixtureNames.some((name) => fixture.runForName(name)), ); } @@ -34,24 +33,24 @@ async function main(sources: string[]) { // Get an array of all { sample, fixtureName } objects we'll run. // We can't just put the fixture in there because these WorkItems // will be sent in a message, removing all code. - const samples = _.map(fixtures, (fixture) => ({ + const samples = fixtures.map((fixture) => ({ fixtureName: fixture.name, samples: fixture.getSamples(sources), })); - const priority = _.flatMap(samples, (x) => - _.map(x.samples.priority, (s) => ({ - fixtureName: x.fixtureName, - sample: s, + const priority = samples.flatMap((sample) => + sample.samples.priority.map((prioritySample) => ({ + fixtureName: sample.fixtureName, + sample: prioritySample, })), ); - const others = _.flatMap(samples, (x) => - _.map(x.samples.others, (s) => ({ - fixtureName: x.fixtureName, - sample: s, + const others = samples.flatMap((sample) => + sample.samples.others.map((otherSample) => ({ + fixtureName: sample.fixtureName, + sample: otherSample, })), ); - const tests = divideParallelJobs(_.concat(priority, others)); + const tests = divideParallelJobs(priority.concat(others)); await inParallel({ queue: tests, @@ -63,17 +62,20 @@ async function main(sources: string[]) { ); for (const fixture of fixtures) { - await execAsync(`rm -rf test/runs`); - await execAsync(`mkdir -p test/runs`); + await execAsync("rm -rf test/runs"); + await execAsync("mkdir -p test/runs"); await fixture.setup(); } }, map: async ({ sample, fixtureName }: WorkItem, index) => { - const fixture = _.find(fixtures, { name: fixtureName }) as Fixture; + const fixture = fixtures.find( + (fixture) => fixture.name === fixtureName, + ); + try { - await fixture.runWithSample(sample, index, tests.length); + await fixture?.runWithSample(sample, index, tests.length); } catch (e) { console.trace(e); exit(1); diff --git a/test/utils.ts b/test/utils.ts index b532a2e8f..f2196f122 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -52,19 +52,16 @@ export function callAndExpectFailure(message: string, f: () => T): void { } export function exec( - s: string, - env: NodeJS.ProcessEnv | undefined, + str: string, + env: NodeJS.ProcessEnv = process.env, printFailure = true, ): { stdout: string; code: number } { - debug(s); - if (env === undefined) { - env = process.env; - } - const result = shell.exec(s, { silent: !DEBUG, env }); + debug(str); + const result = shell.exec(str, { silent: !DEBUG, env }); if (result.code !== 0) { const failureObj = { - command: s, + command: str, code: result.code, }; if (!printFailure) {