diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c4b804d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: + - develop + pull_request: + branches: + - main + - develop + workflow_call: + +jobs: + lint-test: + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + registry-url: "https://registry.npmjs.org" + - run: npm install -g npm@latest + - run: npm ci + - run: npm run lint + - run: npm test + - run: npm run release diff --git a/.gitignore b/.gitignore index 54f07af..4d6a00c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ lerna-debug.log* node_modules dist dist-ssr +coverage *.local # Editor directories and files diff --git a/.npmignore b/.npmignore index 33018fe..2f8755d 100644 --- a/.npmignore +++ b/.npmignore @@ -4,4 +4,5 @@ src .gitattributes .gitignore tsconfig.eslint.json -tsconfig.json \ No newline at end of file +tsconfig.json +test \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 47fdb30..e297703 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,38 +1,122 @@ { "name": "create-next2d-app", - "version": "2.3.1", + "version": "2.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-next2d-app", - "version": "2.3.1", + "version": "2.4.0", "license": "MIT", - "dependencies": { - "commander": "15.0.0", - "cross-spawn": "7.0.6", - "fs-extra": "11.4.0", - "picocolors": "^1.1.1", - "semver": "7.8.5", - "validate-npm-package-name": "8.0.0" - }, "bin": { "create-next2d-app": "dist/index.js" }, "devDependencies": { "@eslint/eslintrc": "^3.3.6", "@eslint/js": "^10.0.1", - "@types/cross-spawn": "^6.0.6", - "@types/fs-extra": "^11.0.4", "@types/node": "^26.1.2", - "@types/semver": "^7.7.1", - "@types/validate-npm-package-name": "^4.0.2", "@typescript-eslint/eslint-plugin": "^8.65.0", "@typescript-eslint/parser": "^8.65.0", + "@vitest/coverage-v8": "^4.1.10", "eslint": "^10.8.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^17.8.0", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitest": "^4.1.10" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "2.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -292,16 +376,381 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@types/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "2.0.0-alpha.3", + "@emnapi/runtime": "2.0.0-alpha.3", + "@napi-rs/wasm-runtime": "^1.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -316,17 +765,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/fs-extra": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", - "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/jsonfile": "*", - "@types/node": "*" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -334,16 +772,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/jsonfile": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", - "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "26.1.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", @@ -354,20 +782,6 @@ "undici-types": "~8.3.0" } }, - "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/validate-npm-package-name": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", - "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.65.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", @@ -650,22 +1064,166 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/acorn": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", - "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", @@ -697,6 +1255,28 @@ "dev": true, "license": "Python-2.0" }, + "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/ast-v8-to-istanbul": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -725,13 +1305,14 @@ "node": ">=6" } }, - "node_modules/commander": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", - "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=22.12.0" + "node": ">=18" } }, "node_modules/concat-map": { @@ -741,10 +1322,18 @@ "dev": true, "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -780,6 +1369,23 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1037,6 +1643,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.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1047,6 +1663,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1137,18 +1763,19 @@ "dev": true, "license": "ISC" }, - "node_modules/fs-extra": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "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", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=14.14" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/glob-parent": { @@ -1177,11 +1804,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" }, "node_modules/ignore": { "version": "5.3.2", @@ -1247,8 +1885,55 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", @@ -1293,18 +1978,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -1329,6 +2002,279 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -1345,6 +2291,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -1365,6 +2349,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "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/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -1372,6 +2375,20 @@ "dev": true, "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -1449,15 +2466,24 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "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/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": { @@ -1473,6 +2499,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "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.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -1503,10 +2558,45 @@ "node": ">=4" } }, + "node_modules/rolldown": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.142.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.1", + "@rolldown/binding-darwin-arm64": "1.2.1", + "@rolldown/binding-darwin-x64": "1.2.1", + "@rolldown/binding-freebsd-x64": "1.2.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", + "@rolldown/binding-linux-arm64-gnu": "1.2.1", + "@rolldown/binding-linux-arm64-musl": "1.2.1", + "@rolldown/binding-linux-ppc64-gnu": "1.2.1", + "@rolldown/binding-linux-s390x-gnu": "1.2.1", + "@rolldown/binding-linux-x64-gnu": "1.2.1", + "@rolldown/binding-linux-x64-musl": "1.2.1", + "@rolldown/binding-openharmony-arm64": "1.2.1", + "@rolldown/binding-wasm32-wasi": "1.2.1", + "@rolldown/binding-win32-arm64-msvc": "1.2.1", + "@rolldown/binding-win32-x64-msvc": "1.2.1" + } + }, "node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1519,6 +2609,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -1531,11 +2622,43 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "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/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/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1549,6 +2672,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -1566,6 +2719,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -1579,6 +2742,14 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -1613,15 +2784,6 @@ "dev": true, "license": "MIT" }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -1632,19 +2794,179 @@ "punycode": "^2.1.0" } }, - "node_modules/validate-npm-package-name": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-8.0.0.tgz", - "integrity": "sha512-SCv6OOV6Xj2/3cXy3dGmADluJTNcL3o7hZAglNPTe+WYuEuvxgJzxPrSDLZhF+CwyQOubqgecjMmTJGMVLWjYQ==", - "license": "ISC", + "node_modules/vite": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.23", + "rolldown": "~1.2.0", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "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/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -1656,6 +2978,23 @@ "node": ">= 8" } }, + "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", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index a27b445..f5ec9ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "create-next2d-app", - "version": "2.3.1", + "version": "2.4.0", "description": "Create Next2D apps with no build configuration.", "author": "Toshiyuki Ienaga", "license": "MIT", @@ -15,7 +15,8 @@ ], "scripts": { "lint": "eslint src/index.ts", - "release": "tsc" + "release": "tsc", + "test": "vitest run --coverage" }, "repository": { "type": "git", @@ -27,27 +28,17 @@ "bin": { "create-next2d-app": "dist/index.js" }, - "dependencies": { - "commander": "15.0.0", - "cross-spawn": "7.0.6", - "fs-extra": "11.4.0", - "picocolors": "^1.1.1", - "semver": "7.8.5", - "validate-npm-package-name": "8.0.0" - }, "devDependencies": { - "@types/node": "^26.1.2", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "^10.0.1", - "@types/cross-spawn": "^6.0.6", - "@types/fs-extra": "^11.0.4", - "@types/semver": "^7.7.1", - "@types/validate-npm-package-name": "^4.0.2", + "@types/node": "^26.1.2", "@typescript-eslint/eslint-plugin": "^8.65.0", "@typescript-eslint/parser": "^8.65.0", + "@vitest/coverage-v8": "^4.1.10", "eslint": "^10.8.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^17.8.0", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitest": "^4.1.10" } } diff --git a/src/index.ts b/src/index.ts index b8708a6..d749432 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,17 +2,17 @@ "use strict"; -import pc from "picocolors"; -import { Command } from "commander"; +import pc from "./lib/colors.js"; +import Command from "./lib/command.js"; import packageJson from "../package.json" with { type: "json" }; import path from "path"; -import validateProjectName from "validate-npm-package-name"; +import validateProjectName from "./lib/validate.js"; import { execSync } from "child_process"; import { createRequire } from "module"; -import fs from "fs-extra"; +import fs from "./lib/fs-extra.js"; import os from "os"; -import semver from "semver"; -import spawn from "cross-spawn"; +import { gte as semverGte } from "./lib/semver.js"; +import spawn from "./lib/spawn.js"; const recommendedVersion: number = 22; const version: string = process.versions.node; @@ -155,7 +155,7 @@ const checkNpmVersion = (): NpmVersion => try { npmVersion = execSync("npm --version").toString().trim(); - hasMinNpm = semver.gte(npmVersion, "10.0.0"); + hasMinNpm = semverGte(npmVersion, "10.0.0"); } catch { // ignore } @@ -567,7 +567,7 @@ const execute = (): void => } const options = program.opts(); - createApp(projectName, options.template); + createApp(projectName, options.template as string | undefined); }; execute(); diff --git a/src/lib/colors.ts b/src/lib/colors.ts new file mode 100644 index 0000000..96ec5fc --- /dev/null +++ b/src/lib/colors.ts @@ -0,0 +1,65 @@ +/** + * A minimal drop-in replacement for `picocolors`, exposing only the helpers + * actually used by `src/index.ts` (`red`, `green`, `cyan`, `yellow`, `bold`). + */ + +interface Colors { + isColorSupported: boolean; + red: (input: unknown) => string; + green: (input: unknown) => string; + cyan: (input: unknown) => string; + yellow: (input: unknown) => string; + bold: (input: unknown) => string; +} + +const argv: string[] = process.argv || []; +const env: NodeJS.ProcessEnv = process.env || {}; + +const isColorSupported: boolean = + !(!!env.NO_COLOR || argv.includes("--no-color")) && + (!!env.FORCE_COLOR || argv.includes("--color") || + process.platform === "win32" || + ((process.stdout || {}).isTTY && env.TERM !== "dumb") || + !!env.CI); + +const replaceClose = ( + string: string, + close: string, + replace: string, + index: number +): string => { + let result: string = ""; + let cursor: number = 0; + do { + result += string.substring(cursor, index) + replace; + cursor = index + close.length; + index = string.indexOf(close, cursor); + } while (~index); + return result + string.substring(cursor); +}; + +const formatter = (open: string, close: string, replace: string = open) => + (input: unknown): string => { + const string: string = "" + input; + const index: number = string.indexOf(close, open.length); + return ~index + ? open + replaceClose(string, close, replace, index) + close + : open + string + close; + }; + +const createColors = (enabled: boolean = isColorSupported): Colors => { + const f = enabled ? formatter : () => (input: unknown): string => String(input); + return { + "isColorSupported": enabled, + "red": f("\x1b[31m", "\x1b[39m"), + "green": f("\x1b[32m", "\x1b[39m"), + "cyan": f("\x1b[36m", "\x1b[39m"), + "yellow": f("\x1b[33m", "\x1b[39m"), + "bold": f("\x1b[1m", "\x1b[22m", "\x1b[22m\x1b[1m") + }; +}; + +const colors: Colors = createColors(); + +export default colors; +export { createColors }; diff --git a/src/lib/command.ts b/src/lib/command.ts new file mode 100644 index 0000000..a1bc938 --- /dev/null +++ b/src/lib/command.ts @@ -0,0 +1,191 @@ +/** + * A minimal drop-in replacement for the `commander` API used by this tool. + * + * Reproduces the observable behavior of commander 15.0.0 for the specific + * usage in `src/index.ts` (a single command with a required positional + * argument, a version option, a couple of options, and help). + */ + +interface Option { + flags: string; + description: string; +} + +class Command { + private name_: string; + private version_: string = ""; + private arguments_: string[] = []; + private usage_: string = ""; + private action_: ((...args: string[]) => void) | undefined; + private options_: Option[] = []; + private events_: Map void>> = new Map(); + private parsedOptions_: Record = {}; + + constructor(name: string) { + this.name_ = name; + } + + name(): string { + return this.name_; + } + + version(value: string): this { + this.version_ = value; + this.options_.push({ + "flags": "-V, --version", + "description": "output the version number" + }); + return this; + } + + arguments(value: string): this { + this.arguments_ = value.split(/\s+/).filter((item) => item.length > 0); + return this; + } + + usage(value: string): this { + this.usage_ = value; + return this; + } + + action(fn: (...args: string[]) => void): this { + this.action_ = fn; + return this; + } + + option(flags: string, description: string): this { + this.options_.push({ flags, description }); + return this; + } + + on(event: string, fn: () => void): this { + const handlers = this.events_.get(event); + if (handlers) { + handlers.push(fn); + } else { + this.events_.set(event, [fn]); + } + return this; + } + + opts(): Record { + return this.parsedOptions_; + } + + parse(argv: string[]): this { + const args: string[] = argv.slice(2); + const positionals: string[] = []; + let onlyPositionals: boolean = false; + + for (let i = 0; i < args.length; ++i) { + const arg = args[i]; + + if (onlyPositionals) { + positionals.push(arg); + continue; + } + + if (arg === "--") { + onlyPositionals = true; + continue; + } + + if (arg === "-V" || arg === "--version") { + console.log(this.version_); + process.exit(0); + } + + if (arg === "-h" || arg === "--help") { + this.displayHelp(); + process.exit(0); + } + + if (arg === "--info") { + this.parsedOptions_.info = true; + continue; + } + + if (arg.startsWith("--template")) { + const equals = arg.indexOf("="); + if (equals !== -1) { + this.parsedOptions_.template = arg.substring(equals + 1); + } else { + this.parsedOptions_.template = args[++i]; + } + continue; + } + + if (arg.startsWith("--")) { + const equals = arg.indexOf("="); + const name = equals === -1 ? arg : arg.substring(0, equals); + this.error(`unknown option '${name}'`); + } + + if (arg.startsWith("-") && arg !== "-") { + this.error(`unknown option '${arg}'`); + } + + positionals.push(arg); + } + + if (this.arguments_.length > 0 && + this.arguments_.every((item) => item.startsWith("<")) && + positionals.length === 0) { + const name = this.arguments_[0].replace(/[<>]/g, ""); + console.error(`error: missing required argument '${name}'`); + process.exit(1); + } + + if (this.action_) { + this.action_(...positionals); + } + + return this; + } + + private error(message: string): never { + console.error(`error: ${message}`); + process.exit(1); + } + + private emit(event: string): void { + const handlers = this.events_.get(event); + if (handlers) { + for (const handler of handlers) { + handler(); + } + } + } + + private displayHelp(): void { + const usage: string = + this.usage_ ? `${this.usage_}` : "[options]"; + + console.log(`Usage: ${this.name_} ${usage}`); + console.log(); + console.log("Options:"); + + const flags: Option[] = [ + ...this.options_, + { "flags": "-h, --help", "description": "display help for command" } + ]; + + const maxLen: number = flags.reduce( + (max, option) => Math.max(max, option.flags.length), + 0 + ); + + for (const option of flags) { + console.log( + ` ${option.flags.padEnd(maxLen)} ${option.description}` + ); + } + console.log(); + + this.emit("-h, --help"); + this.emit("--help"); + this.emit("afterHelp"); + } +} + +export default Command; diff --git a/src/lib/fs-extra.ts b/src/lib/fs-extra.ts new file mode 100644 index 0000000..356f8db --- /dev/null +++ b/src/lib/fs-extra.ts @@ -0,0 +1,39 @@ +/** + * A minimal drop-in replacement for the `fs-extra` API used by this tool. + * + * Exposes only the functions used by `src/index.ts` (`ensureDirSync`, + * `writeFileSync`, `existsSync`, `readFileSync`, `copySync`) plus the Node + * primitives required internally. + */ + +import * as fs from "node:fs"; +import path from "node:path"; + +const ensureDirSync = (dir: string): void => { + fs.mkdirSync(dir, { "recursive": true }); +}; + +const copySync = (src: string, dest: string): void => { + const stat = fs.lstatSync(src); + if (stat.isDirectory()) { + fs.mkdirSync(dest, { "recursive": true }); + for (const entry of fs.readdirSync(src)) { + copySync(path.join(src, entry), path.join(dest, entry)); + } + } else if (stat.isFile()) { + fs.copyFileSync(src, dest); + } else if (stat.isSymbolicLink()) { + const link = fs.readlinkSync(src); + fs.symlinkSync(link, dest); + } +}; + +const fsExtra = { + "ensureDirSync": ensureDirSync, + "copySync": copySync, + "existsSync": fs.existsSync, + "readFileSync": fs.readFileSync, + "writeFileSync": fs.writeFileSync +}; + +export default fsExtra; diff --git a/src/lib/semver.ts b/src/lib/semver.ts new file mode 100644 index 0000000..72c81fd --- /dev/null +++ b/src/lib/semver.ts @@ -0,0 +1,116 @@ +/** + * A minimal drop-in replacement for the `semver` API used by this tool. + * + * Implements version parsing and comparison so that `gte(a, b)` behaves the + * same as `semver.gte(a, b)`. + */ + +interface SemVer { + major: number; + minor: number; + patch: number; + prerelease: (string | number)[]; +} + +const SEMVER_REGEX = + /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/; + +const parse = (version: string): SemVer => { + const match = version.trim().match(SEMVER_REGEX); + if (match === null) { + throw new Error(`Invalid version: ${version}`); + } + + const major = Number(match[1]); + const minor = Number(match[2] || 0); + const patch = Number(match[3] || 0); + + const prerelease: (string | number)[] = []; + if (match[4] !== undefined) { + const parts: string[] = match[4].split("."); + for (const part of parts) { + prerelease.push(part.match(/^[0-9]+$/) ? Number(part) : part); + } + } + + return { major, minor, patch, prerelease }; +}; + +const compareMain = (a: SemVer, b: SemVer): number => { + if (a.major < b.major) { + return -1; + } + if (a.major > b.major) { + return 1; + } + if (a.minor < b.minor) { + return -1; + } + if (a.minor > b.minor) { + return 1; + } + if (a.patch < b.patch) { + return -1; + } + if (a.patch > b.patch) { + return 1; + } + return 0; +}; + +const compareIdentifiers = (a: string | number, b: string | number): number => { + const aIsNumber = typeof a === "number"; + const bIsNumber = typeof b === "number"; + if (aIsNumber && bIsNumber) { + return a < b ? -1 : a > b ? 1 : 0; + } + if (aIsNumber) { + return -1; + } + if (bIsNumber) { + return 1; + } + return a < b ? -1 : a > b ? 1 : 0; +}; + +const comparePre = (a: SemVer, b: SemVer): number => { + if (a.prerelease.length && !b.prerelease.length) { + return -1; + } + if (!a.prerelease.length && b.prerelease.length) { + return 1; + } + if (!a.prerelease.length && !b.prerelease.length) { + return 0; + } + + const len = Math.max(a.prerelease.length, b.prerelease.length); + for (let i = 0; i < len; ++i) { + const x = a.prerelease[i]; + const y = b.prerelease[i]; + if (x === undefined && y === undefined) { + return 0; + } + if (y === undefined) { + return 1; + } + if (x === undefined) { + return -1; + } + if (x === y) { + continue; + } + return compareIdentifiers(x, y); + } + return 0; +}; + +const compare = (a: string, b: string): number => { + const va = parse(a); + const vb = parse(b); + return compareMain(va, vb) || comparePre(va, vb); +}; + +const gte = (a: string, b: string): boolean => compare(a, b) >= 0; + +export { gte }; diff --git a/src/lib/spawn.ts b/src/lib/spawn.ts new file mode 100644 index 0000000..6ccc6d9 --- /dev/null +++ b/src/lib/spawn.ts @@ -0,0 +1,49 @@ +/** + * A minimal drop-in replacement for the `cross-spawn` API used by this tool. + * + * Delegates to Node's `child_process` and, like `cross-spawn`, transparently + * resolves commands (e.g. `npm.cmd`) on Windows by running through a shell. + */ + +import { + spawn as nodeSpawn, + spawnSync as nodeSpawnSync +} from "node:child_process"; +import type { + ChildProcess, + SpawnOptions, + SpawnSyncOptions +} from "node:child_process"; + +const isWindows: boolean = process.platform === "win32"; + +const spawnFn = ( + command: string, + args?: readonly string[], + options?: SpawnOptions +): ChildProcess => { + const opts: SpawnOptions = isWindows + ? { ...options, "shell": true } + : (options ?? {}); + return nodeSpawn(command, args ?? [], opts); +}; + +const spawnSyncFn = ( + command: string, + args?: readonly string[], + options?: SpawnSyncOptions +) => { + const opts: SpawnSyncOptions = isWindows + ? { ...options, "shell": true } + : (options ?? {}); + return nodeSpawnSync(command, args ?? [], opts); +}; + +type SpawnFn = typeof spawnFn & { + sync: typeof spawnSyncFn; +}; + +const spawn: SpawnFn = spawnFn as SpawnFn; +spawn.sync = spawnSyncFn; + +export default spawn; diff --git a/src/lib/validate.ts b/src/lib/validate.ts new file mode 100644 index 0000000..1e5c296 --- /dev/null +++ b/src/lib/validate.ts @@ -0,0 +1,135 @@ +/** + * A minimal drop-in replacement for `validate-npm-package-name`. + * + * Reproduces the same validation rules (and messages) as the original package. + */ + +const BUILTINS: string[] = [ + "_http_agent", "_http_client", "_http_common", "_http_incoming", + "_http_outgoing", "_http_server", "_stream_duplex", "_stream_passthrough", + "_stream_readable", "_stream_transform", "_stream_wrap", "_stream_writable", + "_tls_common", "_tls_wrap", "assert", "assert/strict", "async_hooks", + "buffer", "child_process", "cluster", "console", "constants", "crypto", + "dgram", "diagnostics_channel", "dns", "dns/promises", "domain", "events", + "fs", "fs/promises", "http", "http2", "https", "inspector", + "inspector/promises", "module", "net", "os", "path", "path/posix", + "path/win32", "perf_hooks", "process", "punycode", "querystring", + "readline", "readline/promises", "repl", "stream", "stream/consumers", + "stream/promises", "stream/web", "string_decoder", "sys", "timers", + "timers/promises", "tls", "trace_events", "tty", "url", "util", + "util/types", "v8", "vm", "wasi", "worker_threads", "zlib", "node:sea", + "node:sqlite", "node:test", "node:test/reporters" +]; + +const SCOPED_PACKAGE_PATTERN = /^(?:@([^/]+?)[/])?([^/]+?)$/; + +const EXCLUSION_LIST: string[] = [ + "node_modules", + "favicon.ico" +]; + +interface ValidationResult { + validForNewPackages: boolean; + validForOldPackages: boolean; + warnings?: string[]; + errors?: string[]; +} + +const validate = (name: unknown): ValidationResult => { + const warnings: string[] = []; + const errors: string[] = []; + + if (name === null) { + errors.push("name cannot be null"); + return done(warnings, errors); + } + + if (name === undefined) { + errors.push("name cannot be undefined"); + return done(warnings, errors); + } + + if (typeof name !== "string") { + errors.push("name must be a string"); + return done(warnings, errors); + } + + if (!name.length) { + errors.push("name length must be greater than zero"); + } + + if (name.startsWith(".")) { + errors.push("name cannot start with a period"); + } + + if (name.startsWith("-")) { + errors.push("name cannot start with a hyphen"); + } + + if (name.match(/^_/)) { + errors.push("name cannot start with an underscore"); + } + + if (name.trim() !== name) { + errors.push("name cannot contain leading or trailing spaces"); + } + + for (const excludedName of EXCLUSION_LIST) { + if (name.toLowerCase() === excludedName) { + errors.push(excludedName + " is not a valid package name"); + } + } + + if (BUILTINS.includes(name.toLowerCase())) { + warnings.push(name + " is a core module name"); + } + + if (name.length > 214) { + warnings.push("name can no longer contain more than 214 characters"); + } + + if (name.toLowerCase() !== name) { + warnings.push("name can no longer contain capital letters"); + } + + if (/[~'!()*]/.test(name.split("/").slice(-1)[0])) { + warnings.push("name can no longer contain special characters (\"~'!()*\")"); + } + + if (encodeURIComponent(name) !== name) { + const nameMatch = name.match(SCOPED_PACKAGE_PATTERN); + if (nameMatch) { + const user = nameMatch[1]; + const pkg = nameMatch[2]; + + if (pkg.startsWith(".")) { + errors.push("name cannot start with a period"); + } + + if (encodeURIComponent(user) === user && + encodeURIComponent(pkg) === pkg) { + return done(warnings, errors); + } + } + + errors.push("name can only contain URL-friendly characters"); + } + + return done(warnings, errors); +}; + +const done = (warnings: string[], errors: string[]): ValidationResult => { + const result: ValidationResult = { + "validForNewPackages": errors.length === 0 && warnings.length === 0, + "validForOldPackages": errors.length === 0 + }; + if (warnings.length) { + result.warnings = warnings; + } + if (errors.length) { + result.errors = errors; + } + return result; +}; + +export default validate; diff --git a/test/colors.test.ts b/test/colors.test.ts new file mode 100644 index 0000000..29211c0 --- /dev/null +++ b/test/colors.test.ts @@ -0,0 +1,39 @@ +import { test, expect } from "vitest"; +import colors, { createColors } from "../src/lib/colors"; + +test("default export wraps or leaves text based on color support", () => { + for (const fn of [colors.red, colors.green, colors.cyan, colors.yellow, colors.bold]) { + const out = fn("x"); + expect(typeof out).toBe("string"); + } +}); + +test("createColors(false) leaves text unchanged", () => { + const c = createColors(false); + expect(c.red("hello")).toBe("hello"); + expect(c.green("hello")).toBe("hello"); + expect(c.cyan("hello")).toBe("hello"); + expect(c.yellow("hello")).toBe("hello"); + expect(c.bold("hello")).toBe("hello"); + expect(c.isColorSupported).toBe(false); +}); + +test("createColors(true) wraps text in ANSI codes", () => { + const c = createColors(true); + expect(c.red("hello")).toBe("\x1b[31mhello\x1b[39m"); + expect(c.green("hello")).toBe("\x1b[32mhello\x1b[39m"); + expect(c.cyan("hello")).toBe("\x1b[36mhello\x1b[39m"); + expect(c.yellow("hello")).toBe("\x1b[33mhello\x1b[39m"); + expect(c.bold("hello")).toBe("\x1b[1mhello\x1b[22m"); + expect(c.isColorSupported).toBe(true); +}); + +test("formatter coerces non-string input", () => { + const c = createColors(true); + expect(c.red(123)).toBe("\x1b[31m123\x1b[39m"); +}); + +test("nested colors replace close sequences correctly", () => { + const c = createColors(true); + expect(c.red(c.green("x"))).toBe("\x1b[31m\x1b[32mx\x1b[31m\x1b[39m"); +}); diff --git a/test/command.test.ts b/test/command.test.ts new file mode 100644 index 0000000..d74a2cb --- /dev/null +++ b/test/command.test.ts @@ -0,0 +1,218 @@ +import { test, expect } from "vitest"; +import Command from "../src/lib/command"; + +interface Capture { + logs: string[]; + errors: string[]; + exitCode: () => number | null; + restore: () => void; +} + +const originalExit = process.exit; +const originalLog = console.log; +const originalError = console.error; + +const capture = (): Capture => { + const logs: string[] = []; + const errors: string[] = []; + let exitCode: number | null = null; + + (process as unknown as { exit: (code?: number) => void }).exit = + (code?: number) => { + exitCode = code ?? 0; + throw new Error("exit"); + }; + console.log = (msg?: unknown) => { logs.push(String(msg)); }; + console.error = (msg?: unknown) => { errors.push(String(msg)); }; + + return { + logs, + errors, + "exitCode": () => exitCode, + "restore": () => { + process.exit = originalExit; + console.log = originalLog; + console.error = originalError; + } + }; +}; + +const makeProgram = (action: (name: string) => void = () => {}) => + new Command("create-next2d-app") + .version("2.3.1") + .arguments("") + .usage(" [options]") + .action(action) + .option("--info", "print environment debug info") + .option( + "--template ", + "specify a template for the created project" + ) + .on("-h, --help", () => { + console.log("A custom --template can be one of:"); + }); + +test("name() returns the program name", () => { + const c = capture(); + try { + expect(makeProgram().name()).toBe("create-next2d-app"); + } finally { + c.restore(); + } +}); + +test("--version prints the version and exits 0", () => { + const c = capture(); + try { + expect(() => makeProgram().parse(["node", "test", "--version"])).toThrow(/exit/); + expect(c.logs).toEqual(["2.3.1"]); + expect(c.exitCode()).toBe(0); + } finally { + c.restore(); + } +}); + +test("-V prints the version and exits 0", () => { + const c = capture(); + try { + expect(() => makeProgram().parse(["node", "test", "-V"])).toThrow(/exit/); + expect(c.logs).toEqual(["2.3.1"]); + expect(c.exitCode()).toBe(0); + } finally { + c.restore(); + } +}); + +test("--help prints help, fires the custom event, and exits 0", () => { + const c = capture(); + try { + expect(() => makeProgram().parse(["node", "test", "--help"])).toThrow(/exit/); + const joined = c.logs.join("\n"); + expect(joined).toContain("Usage: create-next2d-app [options]"); + expect(joined).toContain("-V, --version"); + expect(joined).toContain("--template "); + expect(joined).toContain("-h, --help"); + expect(joined).toContain("A custom --template can be one of:"); + expect(c.exitCode()).toBe(0); + } finally { + c.restore(); + } +}); + +test("-h fires the custom help event", () => { + const c = capture(); + try { + expect(() => makeProgram().parse(["node", "test", "-h"])).toThrow(/exit/); + expect(c.logs.join("\n")).toContain("A custom --template can be one of:"); + expect(c.exitCode()).toBe(0); + } finally { + c.restore(); + } +}); + +test("help uses the default usage when none is set", () => { + const c = capture(); + try { + expect(() => { + new Command("app") + .arguments("") + .parse(["node", "test", "--help"]); + }).toThrow(/exit/); + expect(c.logs.join("\n")).toContain("Usage: app [options]"); + } finally { + c.restore(); + } +}); + +test("missing required argument errors and exits 1", () => { + const c = capture(); + try { + expect(() => makeProgram().parse(["node", "test"])).toThrow(/exit/); + expect(c.errors).toEqual(["error: missing required argument 'project-directory'"]); + expect(c.exitCode()).toBe(1); + } finally { + c.restore(); + } +}); + +test("parses options and calls the action with the positional", () => { + const c = capture(); + let actionName: string = ""; + try { + const program = makeProgram((name) => { actionName = name; }) + .parse(["node", "test", "my-app", "--template", "@foo/bar", "--info"]); + expect(actionName).toBe("my-app"); + expect(program.opts().template).toBe("@foo/bar"); + expect(program.opts().info).toBe(true); + expect(c.exitCode()).toBeNull(); + } finally { + c.restore(); + } +}); + +test("supports --template=value syntax", () => { + const c = capture(); + try { + const program = makeProgram() + .parse(["node", "test", "my-app", "--template=@foo/baz"]); + expect(program.opts().template).toBe("@foo/baz"); + expect(c.exitCode()).toBeNull(); + } finally { + c.restore(); + } +}); + +test("treats arguments after -- as positionals", () => { + const c = capture(); + const positionals: string[] = []; + try { + makeProgram((...names: string[]) => { positionals.push(...names); }) + .parse(["node", "test", "my-app", "--", "--info"]); + expect(positionals).toEqual(["my-app", "--info"]); + expect(c.exitCode()).toBeNull(); + } finally { + c.restore(); + } +}); + +test("errors on an unknown long option", () => { + const c = capture(); + try { + expect(() => makeProgram().parse(["node", "test", "--foo"])).toThrow(/exit/); + expect(c.errors).toEqual(["error: unknown option '--foo'"]); + expect(c.exitCode()).toBe(1); + } finally { + c.restore(); + } +}); + +test("errors on an unknown short option", () => { + const c = capture(); + try { + expect(() => makeProgram().parse(["node", "test", "-x"])).toThrow(/exit/); + expect(c.errors).toEqual(["error: unknown option '-x'"]); + expect(c.exitCode()).toBe(1); + } finally { + c.restore(); + } +}); + +test("multiple handlers for the same event all fire on help", () => { + const c = capture(); + try { + const program = new Command("create-next2d-app") + .version("2.3.1") + .arguments("") + .usage(" [options]") + .option("--info", "print environment debug info") + .on("-h, --help", () => { console.log("handler one"); }) + .on("-h, --help", () => { console.log("handler two"); }); + expect(() => program.parse(["node", "test", "--help"])).toThrow(/exit/); + const joined = c.logs.join("\n"); + expect(joined).toContain("handler one"); + expect(joined).toContain("handler two"); + expect(c.exitCode()).toBe(0); + } finally { + c.restore(); + } +}); diff --git a/test/fs-extra.test.ts b/test/fs-extra.test.ts new file mode 100644 index 0000000..b5e1baa --- /dev/null +++ b/test/fs-extra.test.ts @@ -0,0 +1,63 @@ +import { test, expect } from "vitest"; +import { mkdtempSync, rmSync, symlinkSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import fsx from "../src/lib/fs-extra"; + +const makeBase = (): string => mkdtempSync(path.join(tmpdir(), "fsx-")); + +test("ensureDirSync creates a directory recursively", () => { + const base = makeBase(); + try { + const dir = path.join(base, "a", "b"); + fsx.ensureDirSync(dir); + expect(fsx.existsSync(dir)).toBe(true); + } finally { + rmSync(base, { "recursive": true, "force": true }); + } +}); + +test("copySync copies a directory tree recursively", () => { + const base = makeBase(); + try { + const src = path.join(base, "src"); + const dest = path.join(base, "dest"); + fsx.ensureDirSync(path.join(src, "sub")); + fsx.writeFileSync(path.join(src, "a.txt"), "hello"); + fsx.writeFileSync(path.join(src, "sub", "b.txt"), "world"); + + fsx.copySync(src, dest); + + expect(fsx.existsSync(path.join(dest, "a.txt"))).toBe(true); + expect(fsx.readFileSync(path.join(dest, "sub", "b.txt"), "utf8")).toBe("world"); + } finally { + rmSync(base, { "recursive": true, "force": true }); + } +}); + +test("copySync copies a single file", () => { + const base = makeBase(); + try { + const src = path.join(base, "f.txt"); + const dest = path.join(base, "g.txt"); + fsx.writeFileSync(src, "data"); + fsx.copySync(src, dest); + expect(fsx.readFileSync(dest, "utf8")).toBe("data"); + } finally { + rmSync(base, { "recursive": true, "force": true }); + } +}); + +test("copySync follows a symbolic link", () => { + const base = makeBase(); + try { + const target = path.join(base, "target.txt"); + const link = path.join(base, "link"); + fsx.writeFileSync(target, "linked"); + symlinkSync(target, link); + fsx.copySync(link, path.join(base, "copy.txt")); + expect(fsx.readFileSync(path.join(base, "copy.txt"), "utf8")).toBe("linked"); + } finally { + rmSync(base, { "recursive": true, "force": true }); + } +}); diff --git a/test/semver.test.ts b/test/semver.test.ts new file mode 100644 index 0000000..e143938 --- /dev/null +++ b/test/semver.test.ts @@ -0,0 +1,51 @@ +import { test, expect } from "vitest"; +import { gte } from "../src/lib/semver"; + +test("gte compares major/minor/patch numerically", () => { + expect(gte("11.0.0", "10.0.0")).toBe(true); + expect(gte("10.1.0", "10.0.0")).toBe(true); + expect(gte("10.0.1", "10.0.0")).toBe(true); + expect(gte("10.0.0", "10.0.0")).toBe(true); + expect(gte("9.9.9", "10.0.0")).toBe(false); + expect(gte("10.0.0", "11.0.0")).toBe(false); + expect(gte("10.0.0", "10.1.0")).toBe(false); + expect(gte("10.0.0", "10.0.1")).toBe(false); +}); + +test("gte accepts a leading v prefix and partial versions", () => { + expect(gte("v10.0.0", "10.0.0")).toBe(true); + expect(gte("10", "10.0.0")).toBe(true); +}); + +test("gte throws on an invalid version", () => { + expect(() => gte("abc", "10.0.0")).toThrow(/Invalid version/); +}); + +test("gte treats a version without prerelease as greater than one with it", () => { + expect(gte("10.0.0", "10.0.0-beta")).toBe(true); + expect(gte("10.0.0-beta", "10.0.0")).toBe(false); +}); + +test("gte compares string prerelease identifiers", () => { + expect(gte("10.0.0-rc.1", "10.0.0-alpha.2")).toBe(true); + expect(gte("10.0.0-alpha.2", "10.0.0-beta.1")).toBe(false); +}); + +test("gte compares numeric prerelease identifiers", () => { + expect(gte("10.0.0-2", "10.0.0-1")).toBe(true); + expect(gte("10.0.0-1", "10.0.0-2")).toBe(false); +}); + +test("gte compares numeric prerelease against string prerelease", () => { + expect(gte("10.0.0-1", "10.0.0-alpha")).toBe(false); + expect(gte("10.0.0-alpha", "10.0.0-1")).toBe(true); +}); + +test("gte compares prereleases of different lengths", () => { + expect(gte("10.0.0-alpha.1.2", "10.0.0-alpha.1")).toBe(true); + expect(gte("10.0.0-alpha.1", "10.0.0-alpha.1.2")).toBe(false); +}); + +test("gte treats equal prerelease identifiers as equal", () => { + expect(gte("10.0.0-alpha.1", "10.0.0-alpha.1")).toBe(true); +}); diff --git a/test/spawn.test.ts b/test/spawn.test.ts new file mode 100644 index 0000000..7c800c5 --- /dev/null +++ b/test/spawn.test.ts @@ -0,0 +1,29 @@ +import { test, expect } from "vitest"; +import spawn from "../src/lib/spawn"; + +test("spawn.sync captures combined output", () => { + const result = spawn.sync("npm", ["config", "list"]); + const output = result.output.join(""); + expect(typeof output).toBe("string"); + expect(output).toContain("cwd = "); + expect(result.status).toBe(0); +}); + +test("spawn.sync reports an error for a missing command", () => { + const result = spawn.sync("definitely-not-a-command-xyz", []); + if (process.platform === "win32") { + // Windows resolves through a shell, so the failure surfaces as a + // non-zero exit status instead of a null status. + expect(result.status).not.toBe(0); + expect(result.status).not.toBeNull(); + } else { + expect(result.status).toBeNull(); + expect(result.error).toBeTruthy(); + } +}); + +test("spawn returns a child process with event methods", () => { + const child = spawn("npm", ["--version"]); + expect(typeof child.on).toBe("function"); + child.kill(); +}); diff --git a/test/validate.test.ts b/test/validate.test.ts new file mode 100644 index 0000000..7452ba0 --- /dev/null +++ b/test/validate.test.ts @@ -0,0 +1,85 @@ +import { test, expect } from "vitest"; +import validate from "../src/lib/validate"; + +test("accepts a valid unscoped package name", () => { + const r = validate("my-next2d-app"); + expect(r.validForNewPackages).toBe(true); + expect(r.validForOldPackages).toBe(true); + expect(r.warnings).toBeUndefined(); + expect(r.errors).toBeUndefined(); +}); + +test("accepts a valid scoped package name", () => { + const r = validate("@scope/my-app"); + expect(r.validForNewPackages).toBe(true); + expect(r.errors).toBeUndefined(); +}); + +test("rejects null / undefined / non-string", () => { + expect(validate(null).validForNewPackages).toBe(false); + expect(validate(undefined).validForNewPackages).toBe(false); + expect(validate(123).validForNewPackages).toBe(false); +}); + +test("rejects an empty name", () => { + const r = validate(""); + expect(r.validForNewPackages).toBe(false); + expect(r.errors).toEqual(["name length must be greater than zero"]); +}); + +test("rejects a name starting with a period", () => { + const r = validate(".bad"); + expect(r.validForNewPackages).toBe(false); + expect(r.errors).toContain("name cannot start with a period"); +}); + +test("rejects a name starting with a hyphen", () => { + const r = validate("-bad"); + expect(r.errors).toContain("name cannot start with a hyphen"); +}); + +test("rejects a name starting with an underscore", () => { + const r = validate("_bad"); + expect(r.errors).toContain("name cannot start with an underscore"); +}); + +test("rejects names with leading/trailing spaces", () => { + const r = validate(" foo "); + expect(r.errors).toContain("name cannot contain leading or trailing spaces"); +}); + +test("rejects reserved names", () => { + const r = validate("node_modules"); + expect(r.errors).toContain("node_modules is not a valid package name"); + expect(validate("favicon.ico").errors).toContain("favicon.ico is not a valid package name"); +}); + +test("warns on core module names", () => { + const r = validate("assert"); + expect(r.validForNewPackages).toBe(false); + expect(r.warnings).toContain("assert is a core module name"); +}); + +test("warns on names over 214 characters", () => { + const r = validate("a".repeat(215)); + expect(r.warnings).toContain("name can no longer contain more than 214 characters"); +}); + +test("warns on capital letters and special characters", () => { + const r = validate("MyApp"); + expect(r.warnings).toContain("name can no longer contain capital letters"); + const s = validate("weird~name"); + expect(s.warnings).toContain("name can no longer contain special characters (\"~'!()*\")"); +}); + +test("rejects names with non-URL-friendly characters", () => { + const r = validate("foo bar"); + expect(r.validForNewPackages).toBe(false); + expect(r.errors).toContain("name can only contain URL-friendly characters"); +}); + +test("rejects a scoped name whose package part starts with a period", () => { + const r = validate("@scope/.bad"); + expect(r.validForNewPackages).toBe(false); + expect(r.errors).toContain("name cannot start with a period"); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..cb1d209 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + "test": { + "include": ["test/**/*.test.ts"], + "coverage": { + "provider": "v8", + "include": ["src/lib/**/*.ts"], + "thresholds": { + "lines": 90, + "functions": 90, + "branches": 85, + "statements": 90 + } + } + } +});