diff --git a/.configs/eslint.config.mjs b/.configs/eslint.config.mjs index 63429eb..f680449 100644 --- a/.configs/eslint.config.mjs +++ b/.configs/eslint.config.mjs @@ -36,7 +36,7 @@ export default defineConfig([ "reference/**", "*.min.*", // Deliberately-non-JS data files with a .js extension (fallback-loading tests); test data, not source. - "test/fixtures/**", + "tests/fixtures/**", "**/package-lock.json", // Copy file patterns "*copy/", @@ -73,7 +73,22 @@ export default defineConfig([ }, { files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } }, { files: ["**/*.{js,mjs,cjs}"], languageOptions: { globals: { ...globals.node, ...globals.browser } } }, - { files: ["test/**/*.mjs"], languageOptions: { globals: { ...globals.mocha } } }, + { + files: ["tests/**/*.test.vitest.mjs"], + languageOptions: { + globals: { + beforeAll: true, + beforeEach: true, + afterAll: true, + afterEach: true, + describe: true, + it: true, + expect: true, + test: true, + vi: true + } + } + }, { files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] }, { files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] }, { files: ["**/*.json5"], plugins: { json }, language: "json/json5", extends: ["json/recommended"] }, diff --git a/.configs/fix-headers.json b/.configs/fix-headers.json index a9588e8..33ba6d5 100644 --- a/.configs/fix-headers.json +++ b/.configs/fix-headers.json @@ -1,4 +1,4 @@ { "extends": "@cldmv/configs/fix-headers.json", - "excludeFolders": ["test/fixtures"] + "excludeFolders": ["tests/fixtures"] } diff --git a/.configs/vitest.config.mjs b/.configs/vitest.config.mjs new file mode 100644 index 0000000..867e8b2 --- /dev/null +++ b/.configs/vitest.config.mjs @@ -0,0 +1,54 @@ +/** + * + * @Project: @cldmv/wisp + * @Filename: /.configs/vitest.config.mjs + * @Date: 2026-10-03T19:21:07-07:00 (1791080467) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T19:27:20-07:00 (1791080840) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +import { defineConfig } from "vitest/config"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; + +// Anchor the project root to the package directory so include/exclude work no +// matter what cwd vitest is invoked from. +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + +export default defineConfig({ + root, + test: { + include: ["tests/**/*.test.vitest.mjs"], + exclude: ["node_modules"], + environment: "node", + testTimeout: 30000, + // Run test files and the code under test through Node's native import() + // instead of Vite's module runner. wisp's behaviour depends on Node's real + // dynamic-import semantics (import attributes `with` / legacy `assert`, JSON + // module loading); the module runner ignores import attributes and loads JSON + // itself, so under it the suite would exercise Vite rather than Node. + // nodeLoader: false also skips Vitest's own loader transform (only needed for + // vi.mock / import.meta.vitest, which this suite does not use), so V8 coverage is + // measured against the files exactly as Node runs them. + experimental: { + viteModuleRunner: false, + nodeLoader: false + }, + // "dot" keeps CI logs to one character per test file; the final + // "Test Files X passed" / "Tests Y passed" summary is unaffected. + reporters: ["dot"], + coverage: { + provider: "v8", + // wisp publishes src/ directly (no dist build), plus the two entry points. + include: ["src/**/*.mjs", "index.mjs", "index.cjs"], + exclude: ["**/*.json", "tests/**", "src/types/**"], + reporter: ["text", "html", "json-summary", "json"] + } + } +}); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96ed70b..2d59b92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -234,7 +234,7 @@ jobs: # per-version regression. workflow_dispatch can still opt out (set false). lts_only_matrix: ${{ github.event.inputs.lts_only_matrix != 'false' }} package_manager: ${{ github.event.inputs.package_manager || 'npm' }} - test_command: "npm test" # mocha (test/**/*.mjs) + test_command: "npm test" # vitest via @cldmv/vitest-runner (tests/**/*.test.vitest.mjs), then the node:test CJS entry check # test_command: "NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override NODE_OPTIONS only # test_command: "NODE_ENV=test npm test" # Override NODE_ENV only # test_command: "NODE_ENV=test NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override both @@ -253,8 +253,13 @@ jobs: # wisp's "build" is type-declaration generation. `build:types` (tsc) both # emits types/ AND type-checks (tsc fails on type errors), so it carries # the type check on every matrix node. Not `build:ci` — that also re-runs - # mocha, which `test_command` above already ran. + # the test suite, which `test_command` above already ran. build_command: "npm run build:types" + # The coverage legs run their own build step before measuring. wisp ships + # src/ directly (no bundle), so its real build is the same type-declaration + # generation as the CI build; it leaves src/ in place, which is what the + # coverage include measures. + coverage_build_command: "npm run build:types" skip_performance_tests: false skip_matrix_tests: false @@ -264,10 +269,9 @@ jobs: # Only runs on direct pushes to default_branch — PRs and feature branches # are automatically skipped so coverage always reflects merged master code. # Requires: the coverage_command produces coverage/coverage-summary.json - # wisp has no coverage tooling (mocha only, no `ci:coverage` script, no - # coverage-summary.json, and no `badges` branch), so the coverage-badge - # job is disabled. Enable it once a coverage command is added. - enable_coverage_badge: false + # `npm run ci:coverage` runs the vitest suite with v8 coverage, whose + # json-summary reporter writes coverage/coverage-summary.json. + enable_coverage_badge: ${{ github.event.inputs.enable_coverage_badge != 'false' }} default_branch: ${{ github.event.inputs.default_branch || 'master' }} # Badge only pushed when a push lands on this branch coverage_command: ${{ github.event.inputs.coverage_command || 'npm run ci:coverage' }} coverage_summary_path: ${{ github.event.inputs.coverage_summary_path || 'coverage/coverage-summary.json' }} @@ -283,9 +287,7 @@ jobs: # Injects a Shields.io badge + breakdown table directly into the PR body # on every push to the PR branch. Only fires on pull_request events; # skipped automatically on push and workflow_dispatch. No files committed. - # Disabled for the same reason as enable_coverage_badge above — no - # coverage data is produced, so there is nothing to inject into the PR. - enable_coverage_pr_comment: false + enable_coverage_pr_comment: ${{ github.event.inputs.enable_coverage_pr_comment != 'false' }} # Authentication & Bot Configuration # The workflow supports automatic App token detection for enhanced permissions and proper attribution: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8fde91b..0cd2fef 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -110,7 +110,7 @@ jobs: github_packages_publish_command: "" min_node_version: ${{ github.event.inputs.min_node_version || '22.12.0' }} max_node_major: ${{ github.event.inputs.max_node_major || '' }} # blank ⇒ inherit the CLDMV/.github reusable default - test_command: "npm test" # mocha (test/**/*.mjs) + test_command: "npm test" # vitest via @cldmv/vitest-runner (tests/**/*.test.vitest.mjs), then the node:test CJS entry check # test_command: "NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override NODE_OPTIONS only # test_command: "NODE_ENV=test npm test" # Override NODE_ENV only # test_command: "NODE_ENV=test NODE_OPTIONS='--conditions=slothlet-dev' npm test" # Override both diff --git a/.prettierignore b/.prettierignore index 7e3640f..2e10d52 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,4 +15,4 @@ package-lock.json *.yml *.yaml reference/ -test/fixtures/ +tests/fixtures/ diff --git a/package-lock.json b/package-lock.json index 0150b47..b2d1eac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,17 +14,18 @@ "@cldmv/fix-headers": "^2.1.2", "@cldmv/jsonv": "^1.1.3", "@cldmv/prettier-plugin-jsonv": "^1.1.3", + "@cldmv/vitest-runner": "^1.5.3", "@eslint/css": "^2.0.0", "@eslint/js": "^10.0.1", "@eslint/json": "^2.1.0", "@eslint/markdown": "^8.0.3", "@types/node": "^26.6.2", - "chai": "^6.2.2", + "@vitest/coverage-v8": "^5.0.3", "eslint": "^10.12.0", "globals": "^17.13.0", - "mocha": "^12.0.1", "prettier": "^3.9.9", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitest": "^5.0.3" }, "engines": { "node": ">=16.0.0" @@ -34,6 +35,66 @@ "url": "https://github.com/sponsors/shinrai" } }, + "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.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "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/@cacheable/memory": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", @@ -153,6 +214,25 @@ "prettier": "^3.0.0" } }, + "node_modules/@cldmv/vitest-runner": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@cldmv/vitest-runner/-/vitest-runner-1.5.3.tgz", + "integrity": "sha512-99+r+7ricaaCjLSYaRzE9d4Ab/NAG6Vgu8fNpGdep9n+DAi98NH//iHsdQjw9yaVh2IXvp37IBRKR8VTETbyZg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "chalk": "^6.0.1" + }, + "bin": { + "vitest-runner": "bin/vitest-runner.mjs" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "vitest": ">=1.0.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", @@ -428,6 +508,34 @@ "url": "https://github.com/sponsors/nzakas" } }, + "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.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "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/@keyv/bigmap": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", @@ -452,6 +560,324 @@ "dev": true, "license": "MIT" }, + "node_modules/@oxc-project/types": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "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", + "peer": true + }, + "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/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", @@ -462,6 +888,13 @@ "@types/ms": "*" } }, + "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", @@ -534,6 +967,96 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitest/coverage-v8": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.3.tgz", + "integrity": "sha512-+klsyz7BvT1vCU28Zkfzms1Ia78XD0V41U3FUtRaa3S+vOr/EXvK1O6BvVD7l1RzEjm6SONR2aybOvDDf9u0mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/istanbul-lib-coverage": "^1.0.0", + "@vitest/istanbul-lib-report": "^1.0.0", + "ast-v8-to-istanbul": "^1.0.5", + "magicast": "^0.5.4", + "obug": "^2.1.4", + "std-env": "^4.2.0", + "tinyrainbow": "^3.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "5.0.3", + "vitest": "5.0.3" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/istanbul-lib-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.2.tgz", + "integrity": "sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@vitest/istanbul-lib-report": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-report/-/istanbul-lib-report-1.0.2.tgz", + "integrity": "sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/istanbul-lib-coverage": "1.0.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@vitest/mocker": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.3.tgz", + "integrity": "sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.3", + "estree-walker": "^3.0.3", + "magic-string": "^1.2.3" + }, + "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/spy": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.3.tgz", + "integrity": "sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", @@ -574,12 +1097,27 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/argparse": { + "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "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.7", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.7.tgz", + "integrity": "sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==", "dev": true, - "license": "Python-2.0" + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } }, "node_modules/balanced-match": { "version": "4.0.4", @@ -604,13 +1142,6 @@ "node": "20 || >=22" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, "node_modules/cacheable": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", @@ -637,15 +1168,28 @@ } }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, + "node_modules/chalk": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.1.tgz", + "integrity": "sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -657,22 +1201,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -747,6 +1275,17 @@ "node": ">=6" } }, + "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", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -761,15 +1300,12 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/diff": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", - "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -1007,6 +1543,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", @@ -1017,6 +1563,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", @@ -1052,6 +1608,24 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "11.1.5", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", @@ -1062,19 +1636,6 @@ "flat-cache": "^6.1.23" } }, - "node_modules/find-up-simple": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", - "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/flat-cache": { "version": "6.1.23", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", @@ -1103,6 +1664,22 @@ "node": ">=0.4.x" } }, + "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" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", @@ -1110,24 +1687,6 @@ "dev": true, "license": "ISC" }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1154,16 +1713,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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/hashery": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", @@ -1227,115 +1776,358 @@ "node": ">=0.10.0" } }, - "node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "node_modules/isexe": { + "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/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/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, "engines": { - "node": ">=12" + "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", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "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" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "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": "MIT", + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, "engines": { - "node": ">=10" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "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": "ISC" + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/js-yaml": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.2.tgz", - "integrity": "sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==", + "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, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" + "peer": true, + "engines": { + "node": ">= 12.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.mjs" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "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": "MIT" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "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, - "license": "MIT" + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "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" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/katex": { - "version": "0.16.47", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", - "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "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, - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" + "libc": [ + "glibc" ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" }, - "bin": { - "katex": "cli.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/keyv": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", - "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "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, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "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": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "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" + ], + "peer": true, "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/longest-streak": { @@ -1349,14 +2141,26 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "node_modules/magic-string": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.2.tgz", + "integrity": "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==", "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/magicast": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" } }, "node_modules/markdown-table": { @@ -2310,54 +3114,33 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "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": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } + "license": "MIT" }, - "node_modules/mocha": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-12.0.2.tgz", - "integrity": "sha512-SjAulGHxlMJLCD1bhvJBAiYwYxglydRzA9PDQ5MALq1oLRMMtIhxSQJ0olCFgA7c9YbTdxC0KOV0wl8JEDYVhA==", + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "browser-stdout": "^1.3.1", - "chokidar": "^5.0.0", - "debug": "^4.3.5", - "diff": "^9.0.0", - "find-up-simple": "^1.0.1", - "glob": "^13.0.0", - "is-path-inside": "^4.0.0", - "is-unicode-supported": "^0.1.0", - "js-yaml": "^5.0.0", - "minimatch": "^10.2.2", - "ms": "^2.1.3", - "picocolors": "^1.1.1", - "serialize-javascript": "^7.1.1", - "strip-json-comments": "^5.0.3", - "supports-color": "^8.1.1", - "workerpool": "^10.0.0" - }, + "peer": true, "bin": { - "mocha": "bin/mocha.js" + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "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/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2365,6 +3148,20 @@ "dev": true, "license": "MIT" }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "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", @@ -2403,29 +3200,56 @@ "node": ">=8" } }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "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": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, + "license": "ISC", + "peer": true + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", "engines": { - "node": "18 || 20 || >=22" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, - "license": "ISC" + "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", + "peer": true, + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -2483,28 +3307,39 @@ "dev": true, "license": "MIT" }, - "node_modules/readdirp": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", - "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "node_modules/rolldown": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 20.19.0" + "peer": true, + "dependencies": { + "@oxc-project/types": "=0.152.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/serialize-javascript": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.1.2.tgz", - "integrity": "sha512-GL2BWwVa6JydKO6l/ljVgjAZF4QJ3S7dWDWi53s5GZT8PJzD2fNxi67HANQGKAqPrwEpL5ba7gUBGi0Ls/sEoQ==", - "dev": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=20.0.0" + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" } }, "node_modules/shebang-command": { @@ -2540,33 +3375,58 @@ "node": ">=0.10.0" } }, - "node_modules/strip-json-comments": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", - "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.0.tgz", + "integrity": "sha512-78U2TlB2CnVenajOFzf3BKSm0J6oz5L0NV7g32LCPccvYc0lbWvys4d3uUUCS2B1N8PAf2+aekR8i1KbC3HO7Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=20.0.0" } }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "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", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, "node_modules/type-check": { @@ -2687,6 +3547,168 @@ "punycode": "^2.1.0" } }, + "node_modules/vite": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.11", + "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.7.1", + "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": "5.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.3.tgz", + "integrity": "sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.3", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "^6.1.4", + "tinyexec": "^1.3.0", + "tinyglobby": "^0.2.17", + "why-is-node-running": "3.2.1" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.3", + "@vitest/browser-preview": "5.0.3", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.3", + "@vitest/coverage-v8": "5.0.3", + "@vitest/ui": "5.0.3", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.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", @@ -2703,6 +3725,19 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", + "dev": true, + "license": "MIT", + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=20.11" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -2713,13 +3748,6 @@ "node": ">=0.10.0" } }, - "node_modules/workerpool": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-10.0.3.tgz", - "integrity": "sha512-6z2Iis68Wqth93/G/wJP9u+R3O+d2XTlgWChGCwuT1qLbBsOYueGRZuJ++v3mtDP5KjYdy+WzvWC+VWETSVXJA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 1ac1492..34d3ce7 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ } }, "scripts": { - "test": "mocha --recursive \"test/**/*.mjs\" && npm run test:cjs", - "test:cjs": "node --test test/entry.test.cjs", - "test:watch": "mocha --watch \"test/**/*.mjs\"", + "test": "node tests/run-vitest.mjs && npm run test:cjs", + "test:cjs": "node --test tests/cjs/entry.test.cjs", + "test:watch": "vitest --config .configs/vitest.config.mjs", + "coverage": "node tests/run-vitest.mjs --coverage-quiet && npm run test:cjs", + "ci:coverage": "npm run coverage", "lint": "eslint --config .configs/eslint.config.mjs .", "build:types": "tsc", "test:types": "tsc --noEmit", @@ -75,17 +77,18 @@ "@cldmv/fix-headers": "^2.1.2", "@cldmv/jsonv": "^1.1.3", "@cldmv/prettier-plugin-jsonv": "^1.1.3", + "@cldmv/vitest-runner": "^1.5.3", "@eslint/css": "^2.0.0", "@eslint/js": "^10.0.1", "@eslint/json": "^2.1.0", "@eslint/markdown": "^8.0.3", "@types/node": "^26.6.2", - "chai": "^6.2.2", + "@vitest/coverage-v8": "^5.0.3", "eslint": "^10.12.0", "globals": "^17.13.0", - "mocha": "^12.0.1", "prettier": "^3.9.9", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitest": "^5.0.3" }, "publishConfig": { "access": "public" diff --git a/src/lib/resolve-from-caller.mjs b/src/lib/resolve-from-caller.mjs index 84ba42e..646f8f6 100644 --- a/src/lib/resolve-from-caller.mjs +++ b/src/lib/resolve-from-caller.mjs @@ -323,6 +323,8 @@ function pickFallbackBaseFile() { * ); */ function resolveWith(rel, makePrimary, exists, makeFallback) { + // Unreachable through the exported wrappers: both call path.isAbsolute(rel) first, which already throws a TypeError for a non-string. + /* v8 ignore next */ if (typeof rel !== "string") throw new TypeError("rel must be a string"); // absolute / already-URL cases are handled in the public wrappers diff --git a/tests/cjs-entry.test.vitest.mjs b/tests/cjs-entry.test.vitest.mjs new file mode 100644 index 0000000..7215e0d --- /dev/null +++ b/tests/cjs-entry.test.vitest.mjs @@ -0,0 +1,66 @@ +/** + * + * @Project: @cldmv/wisp + * @Filename: /tests/cjs-entry.test.vitest.mjs + * @Date: 2026-10-03T19:27:00-07:00 (1791080820) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T19:27:20-07:00 (1791080840) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +/** + * @fileoverview Characterization tests for the CommonJS entry point (index.cjs). + * @module @cldmv/wisp.test.cjs-entry + * @internal + * @private + * + * @description + * tests/cjs/entry.test.cjs checks a real CommonJS consumer under `node --test`. These tests load + * the same entry through createRequire inside the Vitest run so its version guard is exercised + * and measured: the guard branch is driven by overriding the `process.features.require_module` + * getter for one load. + */ + +import { describe, it, expect, afterEach } from "vitest"; +import { createRequire } from "node:module"; +import * as esm from "../index.mjs"; + +const require = createRequire(import.meta.url); +const entry = require.resolve("../index.cjs"); + +afterEach(() => { + delete require.cache[entry]; +}); + +describe("index.cjs", () => { + it("re-exports the ESM entry's functions", () => { + const cjs = require("../index.cjs"); + expect(cjs).toBe(esm.default); + expect(cjs.default).toBe(esm.default); + expect(cjs.wisp).toBe(esm.wisp); + expect(cjs.wispSync).toBe(esm.wispSync); + }); + + it("throws ERR_REQUIRE_ESM with guidance when require(esm) is unavailable", () => { + const original = Object.getOwnPropertyDescriptor(process.features, "require_module"); + Object.defineProperty(process.features, "require_module", { configurable: true, enumerable: true, get: () => false }); + let err; + try { + require("../index.cjs"); + } catch (e) { + err = e; + } finally { + Object.defineProperty(process.features, "require_module", original); + } + expect(err).toBeInstanceOf(Error); + expect(err.code).toBe("ERR_REQUIRE_ESM"); + expect(err.message).toMatch( + /^@cldmv\/wisp: require\(\) needs Node\.js \^20\.19\.0 or >=22\.12\.0 \(this is v[\d.]+\)\. On older Node\.js, load the package with import\(\) instead\.$/ + ); + }); +}); diff --git a/test/entry.test.cjs b/tests/cjs/entry.test.cjs similarity index 84% rename from test/entry.test.cjs rename to tests/cjs/entry.test.cjs index b338f88..c904136 100644 --- a/test/entry.test.cjs +++ b/tests/cjs/entry.test.cjs @@ -1,7 +1,7 @@ /** * * @Project: @cldmv/wisp - * @Filename: /test/entry.test.cjs + * @Filename: /tests/cjs/entry.test.cjs * @Date: 2026-10-03T10:24:25-07:00 (1791048265) * @Author: Nate Corcoran * @Email: @@ -14,8 +14,8 @@ */ /** - * CommonJS entry tests. These run under Node's own test runner (`node --test`), not Mocha: - * Mocha's `test/**\/*.mjs` glob only picks up ESM specs, so it cannot show whether a plain + * CommonJS entry tests. These run under Node's own test runner (`node --test`), not Vitest: + * Vitest loads every file through its own module pipeline, so it cannot show whether a plain * `require()` of the package works the way it does for a CommonJS consumer. */ "use strict"; @@ -25,11 +25,11 @@ const assert = require("node:assert/strict"); const { spawnSync } = require("node:child_process"); const path = require("node:path"); -const repoRoot = path.resolve(__dirname, ".."); +const repoRoot = path.resolve(__dirname, "..", ".."); test("require() returns the same wisp object as import", async () => { - const cjs = require("../index.cjs"); - const esm = await import("../index.mjs"); + const cjs = require("../../index.cjs"); + const esm = await import("../../index.mjs"); assert.equal(cjs, esm.default); assert.equal(cjs.default, esm.default); diff --git a/test/fixtures/caller.js b/tests/fixtures/caller.js similarity index 100% rename from test/fixtures/caller.js rename to tests/fixtures/caller.js diff --git a/test/fixtures/invalid.json b/tests/fixtures/invalid.json similarity index 100% rename from test/fixtures/invalid.json rename to tests/fixtures/invalid.json diff --git a/tests/fixtures/module.mjs b/tests/fixtures/module.mjs new file mode 100644 index 0000000..68e1476 --- /dev/null +++ b/tests/fixtures/module.mjs @@ -0,0 +1,2 @@ +export const named = "value"; +export default { kind: "module", list: [1, 2] }; diff --git a/test/fixtures/nested/ok.json b/tests/fixtures/nested/ok.json similarity index 100% rename from test/fixtures/nested/ok.json rename to tests/fixtures/nested/ok.json diff --git a/tests/fixtures/no-default.mjs b/tests/fixtures/no-default.mjs new file mode 100644 index 0000000..b89a4b7 --- /dev/null +++ b/tests/fixtures/no-default.mjs @@ -0,0 +1,2 @@ +export const named = "value"; +export const other = 2; diff --git a/test/fixtures/sample.json b/tests/fixtures/sample.json similarity index 100% rename from test/fixtures/sample.json rename to tests/fixtures/sample.json diff --git a/tests/import-strategies.test.vitest.mjs b/tests/import-strategies.test.vitest.mjs new file mode 100644 index 0000000..be0fb98 --- /dev/null +++ b/tests/import-strategies.test.vitest.mjs @@ -0,0 +1,289 @@ +/** + * + * @Project: @cldmv/wisp + * @Filename: /tests/import-strategies.test.vitest.mjs + * @Date: 2026-10-03T19:24:23-07:00 (1791080663) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T19:27:21-07:00 (1791080841) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +/** + * @fileoverview Characterization tests for wisp's input handling and its three load strategies: + * `import()` with `with` attributes, legacy `assert` attributes, and the fs.readFile fallback. + * @module @cldmv/wisp.test.import-strategies + * @internal + * @private + * + * @description + * On Node.js 24+, a module that `with { type }` rejects (an unsupported `type` such as + * "javascript") is imported by the `assert` retry of the same URL, because a failed import is + * not cached. That is how these tests reach the second strategy. Node.js 20 and 22 cache the + * failure, so there the tests check that wisp reports the unsupported type instead. + */ + +import { describe, it, expect, vi, afterEach } from "vitest"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { wisp, wispSync } from "../index.mjs"; + +const fixtures = path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures"); +const sample = path.join(fixtures, "sample.json"); +const sampleData = { foo: "bar", nested: { ok: true } }; +const fixturesBaseHref = pathToFileURL(fixtures + path.sep).href; + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("input forms", () => { + it("accepts a URL instance", async () => { + expect(await wisp(pathToFileURL(sample))).toEqual(sampleData); + expect(wispSync(pathToFileURL(sample))).toEqual(sampleData); + }); + + it("accepts a file:// URL string", async () => { + expect(await wisp(pathToFileURL(sample).href)).toEqual(sampleData); + expect(wispSync(pathToFileURL(sample).href)).toEqual(sampleData); + }); + + it("resolves a relative path against a string base", async () => { + expect(await wisp("sample.json", { base: fixturesBaseHref })).toEqual(sampleData); + expect(wispSync("nested/ok.json", { base: fixturesBaseHref })).toEqual({ ok: true }); + }); + + it("resolves a relative path against a URL base", async () => { + const base = new URL(fixturesBaseHref); + expect(await wisp("./nested/ok.json", { base })).toEqual({ ok: true }); + expect(wispSync("./sample.json", { base })).toEqual(sampleData); + }); + + it("stringifies a non-string, non-URL input before resolving it", async () => { + const input = { toString: () => sample }; + expect(await wisp(input)).toEqual(sampleData); + expect(wispSync(input)).toEqual(sampleData); + }); +}); + +describe("import() with `with` attributes", () => { + it("returns the module's default export when no reviver or validate is given", async () => { + const a = await wisp(sample); + const b = await wisp(sample); + // The JSON module is cached by Node, so both calls share the same object. + expect(a).toBe(b); + }); + + it("returns a deep clone when a reviver or validate is given", async () => { + const shared = await wisp(sample); + const cloned = await wisp(sample, { validate: () => {} }); + expect(cloned).toEqual(shared); + expect(cloned).not.toBe(shared); + expect(cloned.nested).not.toBe(shared.nested); + }); + + it("applies the reviver and validate together", async () => { + const seen = []; + const data = await wisp(sample, { + reviver: (key, value) => (key === "ok" ? "revived" : value), + validate: (val) => seen.push(val.nested.ok) + }); + expect(data.nested.ok).toBe("revived"); + expect(seen).toEqual(["revived"]); + }); + + it("reports a non-Error validation failure with the thrown value", async () => { + const validate = () => { + throw "plain rejection"; + }; + await expect(wisp(sample, { validate })).rejects.toThrow( + /^@cldmv\/wisp: Failed to load JSON file at .*sample\.json: @cldmv\/wisp: plain rejection$/ + ); + }); + + it("keeps the validation error as the cause", async () => { + const original = new Error("nope"); + const err = await wisp(sample, { + validate: () => { + throw original; + } + }).catch((e) => e); + expect(err.cause).toBe(original); + }); +}); + +// Node.js 24+ does not cache a failed import: after `with { type: "javascript" }` rejects a URL, +// the `assert` retry of that same URL loads it. Node.js 20 and 22 cache the failure, so the retry +// is rejected with the same error and wisp reports the unsupported type. The `assert` strategy can +// only be reached this way on Node.js 24+, so those tests run there; older versions check the error. +const nodeMajor = Number(process.versions.node.split(".")[0]); +const retriesFailedImport = nodeMajor >= 24; + +describe.runIf(retriesFailedImport)("import() with legacy `assert` attributes", () => { + // Node.js only checks import attributes on a module's first load; once a URL is in the module + // cache, a later `with { type: "javascript" }` import of it succeeds. Each test therefore loads + // its own URL (a unique query string makes a separate module instance) so the `with` attempt + // really fails and the `assert` attempt is the one that loads the module. + let counter = 0; + const fresh = (file) => `${pathToFileURL(path.join(fixtures, file)).href}?case=${++counter}`; + const moduleFile = "module.mjs"; + const noDefaultFile = "no-default.mjs"; + + it("loads a module whose type `with` rejects, returning its default export", async () => { + const data = await wisp(fresh(moduleFile), { type: "javascript" }); + expect(data).toEqual({ kind: "module", list: [1, 2] }); + }); + + it("returns the module namespace when there is no default export", async () => { + const ns = await wisp(fresh(noDefaultFile), { type: "javascript" }); + expect(ns.named).toBe("value"); + expect(ns.other).toBe(2); + }); + + it("returns a cloned, revived and validated default export", async () => { + const seen = []; + const data = await wisp(fresh(moduleFile), { + type: "javascript", + reviver: (key, value) => (key === "kind" ? "revived" : value), + validate: (val) => seen.push(val.kind) + }); + expect(data).toEqual({ kind: "revived", list: [1, 2] }); + expect(seen).toEqual(["revived"]); + }); + + it("applies a reviver without a validate", async () => { + const data = await wisp(fresh(moduleFile), { + type: "javascript", + reviver: (key, value) => (Array.isArray(value) ? value.length : value) + }); + expect(data).toEqual({ kind: "module", list: 2 }); + }); + + it("serves an already-loaded module through the `with` attempt", async () => { + // Second load of the same URL: Node.js skips the attribute check for a cached module. + const url = fresh(noDefaultFile); + const first = await wisp(url, { type: "javascript" }); + const second = await wisp(url, { type: "javascript" }); + expect(second).toBe(first); + // A reviver needs a clone, which a namespace object cannot give, on every strategy. + await expect(wisp(url, { type: "javascript", reviver: (key, value) => value })).rejects.toThrow( + /^@cldmv\/wisp: Unsupported type 'javascript'/ + ); + }); + + it("cannot clone a namespace without a default export, so a reviver ends in the unsupported-type error", async () => { + // structuredClone rejects a module namespace object; that failure is swallowed like any + // other strategy failure, and the fs.readFile strategy only handles type "json". + await expect(wisp(fresh(noDefaultFile), { type: "javascript", reviver: (key, value) => value })).rejects.toThrow( + /^@cldmv\/wisp: Unsupported type 'javascript' or failed to load module at file:.*no-default\.mjs\?case=\d+$/ + ); + }); + + it("reports a validation failure on a non-JSON module as an unsupported type", async () => { + // The `assert` attempt's validation error is swallowed like any other failure, and the + // fs.readFile strategy only handles type "json", so the final error is the unsupported-type one. + const validate = () => { + throw "rejected"; + }; + await expect(wisp(fresh(moduleFile), { type: "javascript", validate })).rejects.toThrow( + /^@cldmv\/wisp: Unsupported type 'javascript' or failed to load module at file:.*module\.mjs\?case=\d+$/ + ); + await expect( + wisp(fresh(moduleFile), { + type: "javascript", + validate: () => { + throw new Error("rejected as Error"); + } + }) + ).rejects.toThrow(/^@cldmv\/wisp: Unsupported type 'javascript'/); + }); +}); + +describe.skipIf(retriesFailedImport)("import() of an unsupported type on Node.js < 24", () => { + let counter = 0; + const fresh = (file) => `${pathToFileURL(path.join(fixtures, file)).href}?legacy=${++counter}`; + + it("reports the unsupported type, because the failed `with` import is cached for that URL", async () => { + await expect(wisp(fresh("module.mjs"), { type: "javascript" })).rejects.toThrow(/^@cldmv\/wisp: Unsupported type 'javascript'/); + await expect(wisp(fresh("no-default.mjs"), { type: "javascript" })).rejects.toThrow(/^@cldmv\/wisp: Unsupported type 'javascript'/); + }); +}); +describe("fs.readFile fallback", () => { + it("reads JSON content that import() cannot load (a .js file holding JSON)", async () => { + const data = await wisp(path.join(fixtures, "caller.js"), { reviver: (key, value) => (key === "caller" ? "revived" : value) }); + expect(data).toEqual({ caller: "revived" }); + }); + + it("throws a wisp error naming the file when the primary is missing and there is no fallback", async () => { + const missing = path.join(fixtures, "missing.json"); + const err = await wisp(missing).catch((e) => e); + expect(err).toBeInstanceOf(Error); + expect(err.message).toMatch(/^@cldmv\/wisp: Failed to load JSON file at file:.*missing\.json: ENOENT/); + expect(err.cause.code).toBe("ENOENT"); + }); + + it("throws a wisp error when the primary is invalid JSON and there is no fallback", async () => { + const err = await wisp(path.join(fixtures, "invalid.json")).catch((e) => e); + expect(err.message).toMatch(/^@cldmv\/wisp: Failed to load JSON file at .*invalid\.json: /); + expect(err.cause).toBeInstanceOf(SyntaxError); + }); + + it("does not fall back a second time when the fallback is also missing", async () => { + const err = await wisp(path.join(fixtures, "missing.json"), { fallback: path.join(fixtures, "also-missing.json") }).catch((e) => e); + expect(err.message).toMatch(/^@cldmv\/wisp: Failed to load JSON file at .*also-missing\.json: ENOENT/); + }); + + it("rejects an unsupported type for a file no strategy can load", async () => { + await expect(wisp(sample, { type: "javascript" })).rejects.toThrow( + /^@cldmv\/wisp: Unsupported type 'javascript' or failed to load module at file:.*sample\.json$/ + ); + }); + + it("deep-clones with JSON when structuredClone is unavailable", async () => { + vi.stubGlobal("structuredClone", undefined); + const data = await wisp(sample, { validate: () => {} }); + expect(data).toEqual(sampleData); + }); +}); + +describe("wispSync", () => { + it("throws a wisp error when the primary is missing and there is no fallback", () => { + expect(() => wispSync(path.join(fixtures, "missing.json"))).toThrow( + /^@cldmv\/wisp: Failed to load JSON file at file:.*missing\.json: ENOENT/ + ); + }); + + it("throws a wisp error when the primary is invalid JSON and there is no fallback", () => { + expect(() => wispSync(path.join(fixtures, "invalid.json"))).toThrow(/^@cldmv\/wisp: Failed to load JSON file at .*invalid\.json: /); + }); + + it("reports a non-Error validation failure with the thrown value", () => { + expect(() => + wispSync(sample, { + validate: () => { + throw "sync plain rejection"; + } + }) + ).toThrow(/: @cldmv\/wisp: sync plain rejection$/); + }); + + it("returns a fresh clone on every call", () => { + const a = wispSync(sample); + const b = wispSync(sample); + expect(a).toEqual(b); + expect(a).not.toBe(b); + }); + + it("deep-clones with JSON when structuredClone is unavailable", () => { + vi.stubGlobal("structuredClone", undefined); + expect(wispSync(sample)).toEqual(sampleData); + }); + + it("ignores the type option and always parses JSON", () => { + expect(wispSync(path.join(fixtures, "caller.js"), { type: "javascript" })).toEqual({ caller: "ok" }); + }); +}); diff --git a/tests/resolve-from-caller.test.vitest.mjs b/tests/resolve-from-caller.test.vitest.mjs new file mode 100644 index 0000000..64ad041 --- /dev/null +++ b/tests/resolve-from-caller.test.vitest.mjs @@ -0,0 +1,224 @@ +/** + * + * @Project: @cldmv/wisp + * @Filename: /tests/resolve-from-caller.test.vitest.mjs + * @Date: 2026-10-03T19:24:51-07:00 (1791080691) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T19:27:21-07:00 (1791080841) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +/** + * @fileoverview Characterization tests for the caller-relative path resolver. + * @module @cldmv/wisp.test.resolve-from-caller + * @internal + * @private + * + * @description + * The resolver picks its base file from V8 CallSite frames. Real stacks cover the plain + * cases; the frame-selection state machine is driven with synthetic CallSite lists by + * pinning `Error.prepareStackTrace` (which `getStack` installs and restores) for one call. + */ + +import { describe, it, expect, vi, afterEach } from "vitest"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { toFsPath, getStack, resolvePathFromCaller, resolveUrlFromCaller } from "../src/lib/resolve-from-caller.mjs"; + +const here = fileURLToPath(import.meta.url); +const testsDir = path.dirname(here); +const repoRoot = path.dirname(testsDir); +const resolverFile = path.join(repoRoot, "src", "lib", "resolve-from-caller.mjs"); + +/** + * Runs `fn` while every stack trace V8 formats is replaced by `frames`. + * @param {Array|undefined} frames - Synthetic CallSite objects (or undefined for an empty stack value). + * @param {() => any} fn - Code to run with the synthetic stack in place. + * @returns {any} Whatever `fn` returns. + */ +function withStack(frames, fn) { + const original = Object.getOwnPropertyDescriptor(Error, "prepareStackTrace"); + Object.defineProperty(Error, "prepareStackTrace", { + configurable: true, + get: () => () => frames, + // getStack assigns its own formatter and then restores the old one; both writes are ignored here. + set: () => {} + }); + try { + return fn(); + } finally { + if (original) Object.defineProperty(Error, "prepareStackTrace", original); + else delete Error.prepareStackTrace; + } +} + +const frame = (file) => ({ getFileName: () => file }); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe("toFsPath", () => { + it("converts a file:// URL to a filesystem path", () => { + expect(toFsPath(pathToFileURL(here).href)).toBe(here); + }); + + it("returns a plain path unchanged, stringified", () => { + expect(toFsPath("/some/path.json")).toBe("/some/path.json"); + expect(toFsPath({ toString: () => "/from/object" })).toBe("/from/object"); + }); + + it("returns null for falsy input", () => { + expect(toFsPath(null)).toBeNull(); + expect(toFsPath(undefined)).toBeNull(); + expect(toFsPath("")).toBeNull(); + }); +}); + +describe("getStack", () => { + it("returns V8 CallSite objects, starting at the caller when no function is skipped", () => { + const stack = getStack(); + expect(Array.isArray(stack)).toBe(true); + expect(typeof stack[0].getFileName).toBe("function"); + expect(toFsPath(stack[0].getFileName())).toBe(resolverFile); + }); + + it("drops the frames above the skipped function", () => { + function marker() { + return getStack(marker); + } + const stack = marker(); + expect(toFsPath(stack[0].getFileName())).toBe(here); + expect(stack.some((cs) => cs.getFunctionName() === "marker")).toBe(false); + }); + + it("restores the previous Error.prepareStackTrace", () => { + const before = Error.prepareStackTrace; + getStack(); + expect(Error.prepareStackTrace).toBe(before); + }); + + it("returns an empty array when the stack formatter yields nothing", () => { + expect(withStack(undefined, () => getStack())).toEqual([]); + }); +}); + +describe("resolvePathFromCaller", () => { + it("converts a file:// URL short-circuit to a path", () => { + expect(resolvePathFromCaller(pathToFileURL("/abs/file.json").href)).toBe(path.resolve("/abs/file.json")); + }); + + it("returns an absolute path unchanged", () => { + expect(resolvePathFromCaller("/abs/file.json")).toBe("/abs/file.json"); + }); + + it("resolves an existing relative path from the calling file", () => { + expect(resolvePathFromCaller("./fixtures/sample.json")).toBe(path.join(testsDir, "fixtures", "sample.json")); + }); + + it("resolves a missing relative path from the calling file too", () => { + expect(resolvePathFromCaller("./fixtures/does-not-exist.json")).toBe(path.join(testsDir, "fixtures", "does-not-exist.json")); + }); + + it("throws for a non-string path", () => { + expect(() => resolvePathFromCaller(/** @type {any} */ (42))).toThrow(TypeError); + }); +}); + +describe("resolveUrlFromCaller", () => { + it("returns a file:// URL unchanged", () => { + expect(resolveUrlFromCaller("file:///abs/file.json")).toBe("file:///abs/file.json"); + }); + + it("converts an absolute path to a file:// URL", () => { + expect(resolveUrlFromCaller("/abs/file.json")).toBe(pathToFileURL("/abs/file.json").href); + }); + + it("resolves an existing relative path from the calling file", () => { + expect(resolveUrlFromCaller("./fixtures/sample.json")).toBe(pathToFileURL(path.join(testsDir, "fixtures", "sample.json")).href); + }); + + it("resolves a missing relative path from the calling file too", () => { + expect(resolveUrlFromCaller("./nope.json")).toBe(pathToFileURL(path.join(testsDir, "nope.json")).href); + }); +}); + +describe("base-file selection from the call stack", () => { + const inSrc = path.join(repoRoot, "src", "wisp.mjs"); + const inDist = path.join(repoRoot, "dist", "wisp.mjs"); + const indexMjs = path.join(repoRoot, "index.mjs"); + const userFile = path.join(testsDir, "fixtures", "nested", "user.mjs"); + + it("picks the first frame after the stack leaves src/", () => { + const frames = [frame(resolverFile), frame(inSrc), frame(userFile), frame(here)]; + expect(withStack(frames, () => resolvePathFromCaller("./ok.json"))).toBe(path.join(testsDir, "fixtures", "nested", "ok.json")); + }); + + it("treats dist/ like src/", () => { + const frames = [frame(inDist), frame(userFile)]; + expect(withStack(frames, () => resolvePathFromCaller("./ok.json"))).toBe(path.join(testsDir, "fixtures", "nested", "ok.json")); + }); + + it("returns the package's index file when that is the first frame outside src/", () => { + const frames = [frame(inSrc), frame(indexMjs), frame(userFile)]; + expect(withStack(frames, () => resolvePathFromCaller("./package.json"))).toBe(path.join(repoRoot, "package.json")); + }); + + it("skips frames without a file name, node:internal frames, and the resolver itself", () => { + const frames = [ + null, + frame(undefined), + frame("node:internal/modules/esm/loader"), + frame(pathToFileURL(resolverFile).href), + frame(inSrc), + frame(pathToFileURL(userFile).href) + ]; + expect(withStack(frames, () => resolveUrlFromCaller("./ok.json"))).toBe( + pathToFileURL(path.join(testsDir, "fixtures", "nested", "ok.json")).href + ); + }); + + it("falls back to the first usable frame when the stack never leaves src/", () => { + // Leaving an index file without leaving src/ does not select a base, so the fallback picks the first real frame. + const frames = [frame("node:internal/x"), frame(indexMjs), frame(userFile)]; + expect(withStack(frames, () => resolvePathFromCaller("./x.json"))).toBe(path.join(repoRoot, "x.json")); + }); + + it("switches to the fallback frame when the primary candidate does not exist", () => { + // The primary base is the user file, but ./missing.json does not exist next to it, so the + // result is built from the fallback base (the first usable frame) without a second existence check. + const frames = [frame(inSrc), frame(userFile)]; + expect(withStack(frames, () => resolvePathFromCaller("./missing.json"))).toBe(path.join(repoRoot, "src", "missing.json")); + }); + + it("resolves from the resolver's own directory when the stack has no usable frames", () => { + expect(withStack([], () => resolvePathFromCaller("./x.json"))).toBe(path.join(repoRoot, "src", "lib", "x.json")); + expect(withStack([null, frame(undefined), frame(resolverFile), frame("node:internal/y")], () => resolveUrlFromCaller("./y.json"))).toBe( + pathToFileURL(path.join(repoRoot, "src", "lib", "y.json")).href + ); + }); + + it("ignores frames outside the package when looking for src/", () => { + const outside = path.join(path.parse(repoRoot).root, "elsewhere", "src", "lib.mjs"); + const frames = [frame(outside), frame(userFile)]; + // The outside file is not in this package's src/, so no primary base is found and the fallback takes the first frame. + expect(withStack(frames, () => resolvePathFromCaller("./z.json"))).toBe(path.join(path.dirname(outside), "z.json")); + }); +}); + +describe("package root detection", () => { + it("still resolves caller-relative paths when no package.json is found above the resolver", async () => { + // A copy of the resolver bundled somewhere without a package.json above it has no package root: + // nothing is classified as src/ or an index file, so every lookup goes through the fallback frame. + vi.spyOn(fs, "existsSync").mockReturnValue(false); + const mod = await import(`../src/lib/resolve-from-caller.mjs?no-package-root=${Date.now()}`); + const frames = [frame(path.join(repoRoot, "src", "wisp.mjs")), frame(here)]; + expect(withStack(frames, () => mod.resolvePathFromCaller("./x.json"))).toBe(path.join(repoRoot, "src", "x.json")); + }); +}); diff --git a/tests/run-vitest.mjs b/tests/run-vitest.mjs new file mode 100644 index 0000000..215f31c --- /dev/null +++ b/tests/run-vitest.mjs @@ -0,0 +1,67 @@ +/** + * + * @Project: @cldmv/wisp + * @Filename: /tests/run-vitest.mjs + * @Date: 2026-10-03T19:27:27-07:00 (1791080847) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T19:27:27-07:00 (1791080847) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +/** + * @fileoverview OOM-safe Vitest runner for wisp — delegates to + * @cldmv/vitest-runner, which spawns each test file in its own child process and + * (under coverage) uses a blob-per-file + `--mergeReports` strategy so a single + * process never holds coverage data for the whole suite. + * + * Usage: + * node tests/run-vitest.mjs # run all tests + * node tests/run-vitest.mjs --coverage # with coverage (verbose) + * node tests/run-vitest.mjs --coverage-quiet # with coverage (progress bar + summary) + * node tests/run-vitest.mjs # filter by path/name + * + * Args before a `--` delimiter are forwarded to Vitest; args after it are test + * patterns. A value-taking flag needs the delimiter, e.g.: + * node tests/run-vitest.mjs --reporter verbose -- + */ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { run } from "@cldmv/vitest-runner"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const argv = process.argv.slice(2); + +// A `--` delimiter separates forwarded Vitest args (before it) from test patterns +// (after it), so a value-taking flag such as `--reporter verbose` isn't misread as a +// pattern. Without a `--`, the legacy heuristic applies: non-flag tokens are test +// patterns and flag tokens are forwarded to vitest. +const delimiter = argv.indexOf("--"); +const forwarded = delimiter === -1 ? argv.filter((a) => a.startsWith("-")) : argv.slice(0, delimiter); +const testPatterns = delimiter === -1 ? argv.filter((a) => !a.startsWith("-")) : argv.slice(delimiter + 1); + +const coverageQuiet = forwarded.includes("--coverage-quiet"); +const coverage = coverageQuiet || forwarded.includes("--coverage"); +const passthrough = forwarded.filter((a) => a !== "--coverage" && a !== "--coverage-quiet"); + +// VITEST_WORKERS overrides the worker count; ignore an unset / invalid / non-positive value. +const parsedWorkers = parseInt(process.env.VITEST_WORKERS ?? "", 10); +const workers = Number.isInteger(parsedWorkers) && parsedWorkers > 0 ? parsedWorkers : 4; + +const code = await run({ + cwd: root, + testDir: "tests", + vitestConfig: ".configs/vitest.config.mjs", + // CLDMV standing convention: vitest test files are always named `*.test.vitest.mjs`. + testFilePattern: /\.test\.vitest\.mjs$/, + testPatterns, + workers, + coverageQuiet, + vitestArgs: [...(coverage ? ["--coverage"] : []), ...passthrough], + nodeEnv: process.env.NODE_ENV || "development" +}); +process.exit(code); diff --git a/test/wisp.spec.mjs b/tests/wisp.test.vitest.mjs similarity index 76% rename from test/wisp.spec.mjs rename to tests/wisp.test.vitest.mjs index 654c178..e4ec3b1 100644 --- a/test/wisp.spec.mjs +++ b/tests/wisp.test.vitest.mjs @@ -1,7 +1,7 @@ /** * * @Project: @cldmv/wisp - * @Filename: /test/wisp.spec.mjs + * @Filename: /tests/wisp.test.vitest.mjs * @Date: 2025-10-30T15:06:33-07:00 (1761861993) * @Author: Nate Corcoran * @Email: @@ -24,27 +24,28 @@ * path resolution, validation, and CJS interop. */ -import { expect } from "chai"; +import { describe, it, expect } from "vitest"; import { wisp, wispSync } from "../index.mjs"; import path from "node:path"; +import { fileURLToPath } from "node:url"; -const testDir = path.join(process.cwd(), "test"); +const testDir = path.dirname(fileURLToPath(import.meta.url)); describe("wisp", () => { it("loads JSON via wisp() async", async () => { const data = await wisp(path.resolve(testDir, "fixtures/sample.json")); - expect(data).to.deep.equal({ foo: "bar", nested: { ok: true } }); + expect(data).toEqual({ foo: "bar", nested: { ok: true } }); }); it("loads JSON via wispSync() sync", () => { const data = wispSync(path.resolve(testDir, "fixtures/sample.json")); - expect(data).to.deep.equal({ foo: "bar", nested: { ok: true } }); + expect(data).toEqual({ foo: "bar", nested: { ok: true } }); }); it("handles absolute paths", async () => { const absPath = path.resolve(testDir, "fixtures/sample.json"); const data = await wisp(absPath); - expect(data.foo).to.equal("bar"); + expect(data.foo).toBe("bar"); }); it("validates successfully", async () => { @@ -52,7 +53,7 @@ describe("wisp", () => { if (!val.foo) throw new Error("missing foo"); }; const data = await wisp(path.resolve(testDir, "fixtures/sample.json"), { validate }); - expect(data.foo).to.equal("bar"); + expect(data.foo).toBe("bar"); }); it("throws on validation failure", async () => { @@ -63,26 +64,26 @@ describe("wisp", () => { await wisp(path.resolve(testDir, "fixtures/sample.json"), { validate }); throw new Error("Expected error was not thrown"); } catch (error) { - expect(error.message).to.include("@cldmv/wisp: test error"); + expect(error.message).toContain("@cldmv/wisp: test error"); } }); it("uses reviver function", async () => { const reviver = (key, value) => (key === "foo" ? "modified" : value); const data = await wisp(path.resolve(testDir, "fixtures/sample.json"), { reviver }); - expect(data.foo).to.equal("modified"); + expect(data.foo).toBe("modified"); }); it("loads nested JSON", async () => { const data = await wisp(path.resolve(testDir, "fixtures/nested/ok.json")); - expect(data).to.deep.equal({ ok: true }); + expect(data).toEqual({ ok: true }); }); it("handles fallback paths", async () => { const data = await wisp(path.resolve(testDir, "fixtures/nonexistent.json"), { fallback: path.resolve(testDir, "fixtures/sample.json") }); - expect(data.foo).to.equal("bar"); + expect(data.foo).toBe("bar"); }); it("does not use the fallback when the primary fails validation", async () => { @@ -95,13 +96,13 @@ describe("wisp", () => { } catch (e) { err = e; } - expect(err, "expected the validation error to be thrown").to.be.an("error"); - expect(err.message).to.match(/^@cldmv\/wisp: Failed to load JSON file at .*sample\.json: @cldmv\/wisp: primary rejected$/); + expect(err, "expected the validation error to be thrown").toBeInstanceOf(Error); + expect(err.message).toMatch(/^@cldmv\/wisp: Failed to load JSON file at .*sample\.json: @cldmv\/wisp: primary rejected$/); }); it("uses the fallback when the primary is invalid JSON", async () => { const data = await wisp(path.resolve(testDir, "fixtures/invalid.json"), { fallback: path.resolve(testDir, "fixtures/sample.json") }); - expect(data.foo).to.equal("bar"); + expect(data.foo).toBe("bar"); }); it("validates the fallback data when the primary is missing", async () => { @@ -114,27 +115,27 @@ describe("wisp", () => { } catch (e) { err = e; } - expect(err, "expected the validation error to be thrown").to.be.an("error"); - expect(err.message).to.include("@cldmv/wisp: fallback rejected"); + expect(err, "expected the validation error to be thrown").toBeInstanceOf(Error); + expect(err.message).toContain("@cldmv/wisp: fallback rejected"); }); it("resolves caller path correctly", async () => { const callerPath = path.resolve(testDir, "fixtures/caller.js"); const data = await wisp(callerPath); - expect(data).to.deep.equal({ caller: "ok" }); + expect(data).toEqual({ caller: "ok" }); }); it("resolves relative paths from caller location", async () => { // This was the core issue - relative paths should resolve from the caller, not from src/ const data = await wisp("./fixtures/sample.json"); - expect(data).to.deep.equal({ foo: "bar", nested: { ok: true } }); + expect(data).toEqual({ foo: "bar", nested: { ok: true } }); }); }); describe("wispSync", () => { it("loads JSON synchronously", () => { const data = wispSync(path.resolve(testDir, "fixtures/sample.json")); - expect(data).to.deep.equal({ foo: "bar", nested: { ok: true } }); + expect(data).toEqual({ foo: "bar", nested: { ok: true } }); }); it("validates synchronously", () => { @@ -142,27 +143,27 @@ describe("wispSync", () => { if (!val.foo) throw new Error("missing foo"); }; const data = wispSync(path.resolve(testDir, "fixtures/sample.json"), { validate }); - expect(data.foo).to.equal("bar"); + expect(data.foo).toBe("bar"); }); it("throws on sync validation failure", () => { const validate = () => { throw new Error("sync test error"); }; - expect(() => wispSync(path.resolve(testDir, "fixtures/sample.json"), { validate })).to.throw("@cldmv/wisp: sync test error"); + expect(() => wispSync(path.resolve(testDir, "fixtures/sample.json"), { validate })).toThrow("@cldmv/wisp: sync test error"); }); it("uses reviver in sync", () => { const reviver = (key, value) => (key === "foo" ? "sync modified" : value); const data = wispSync(path.resolve(testDir, "fixtures/sample.json"), { reviver }); - expect(data.foo).to.equal("sync modified"); + expect(data.foo).toBe("sync modified"); }); it("handles fallback paths synchronously", () => { const data = wispSync(path.resolve(testDir, "fixtures/nonexistent.json"), { fallback: path.resolve(testDir, "fixtures/sample.json") }); - expect(data.foo).to.equal("bar"); + expect(data.foo).toBe("bar"); }); it("does not use the fallback when the primary fails validation in sync", () => { @@ -171,12 +172,12 @@ describe("wispSync", () => { }; expect(() => wispSync(path.resolve(testDir, "fixtures/sample.json"), { validate, fallback: path.resolve(testDir, "fixtures/nested/ok.json") }) - ).to.throw(/^@cldmv\/wisp: Failed to load JSON file at .*sample\.json: @cldmv\/wisp: primary rejected$/); + ).toThrow(/^@cldmv\/wisp: Failed to load JSON file at .*sample\.json: @cldmv\/wisp: primary rejected$/); }); it("uses the fallback when the primary is invalid JSON in sync", () => { const data = wispSync(path.resolve(testDir, "fixtures/invalid.json"), { fallback: path.resolve(testDir, "fixtures/sample.json") }); - expect(data.foo).to.equal("bar"); + expect(data.foo).toBe("bar"); }); it("validates the fallback data when the primary is missing in sync", () => { @@ -185,21 +186,18 @@ describe("wispSync", () => { }; expect(() => wispSync(path.resolve(testDir, "fixtures/nonexistent.json"), { validate, fallback: path.resolve(testDir, "fixtures/sample.json") }) - ).to.throw("@cldmv/wisp: fallback rejected"); + ).toThrow("@cldmv/wisp: fallback rejected"); }); it("resolves caller path correctly in sync", () => { const callerPath = path.resolve(testDir, "fixtures/caller.js"); const data = wispSync(callerPath); - expect(data).to.deep.equal({ caller: "ok" }); + expect(data).toEqual({ caller: "ok" }); }); it("resolves relative paths from caller location in sync", () => { // This was the core issue - relative paths should resolve from the caller, not from src/ const data = wispSync("./fixtures/sample.json"); - expect(data).to.deep.equal({ foo: "bar", nested: { ok: true } }); + expect(data).toEqual({ foo: "bar", nested: { ok: true } }); }); }); - -// Note: CJS interop test would require separate CJS test file or dynamic require -// For now, assuming the exports are correct as per index.cjs diff --git a/tsconfig.json b/tsconfig.json index f39544c..062d7ae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,5 +23,5 @@ "types": ["node"] }, "include": ["src/**/*"], - "exclude": ["node_modules", "test", "types"] + "exclude": ["node_modules", "tests", "types"] } diff --git a/types/lib/resolve-from-caller.d.mts.map b/types/lib/resolve-from-caller.d.mts.map index 88c051a..ad8bc86 100644 --- a/types/lib/resolve-from-caller.d.mts.map +++ b/types/lib/resolve-from-caller.d.mts.map @@ -1 +1 @@ -{"version":3,"file":"resolve-from-caller.d.mts","sourceRoot":"","sources":["../../src/lib/resolve-from-caller.mjs"],"names":[],"mappings":"AAuFA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,6CAhCa,KAAK,CAAC,QAAQ,CAAC,CA0C3B;AA4MD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,2CAzCW,MAAM,GACJ,MAAM,CAsDlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,0CA1CW,MAAM,GACJ,MAAM,CAuDlB;AAtXM,4BAvBI,GAAG,GACD,MAAM,GAAC,IAAI,CAsB+F;;iBA0XzG,MAAY,MAAM,GAAC,SAAS;mBAC5B,MAAY,MAAM,GAAC,SAAS;qBAC5B,MAAY,MAAM,GAAC,SAAS;iBAC5B,MAAY,MAAM,GAAC,SAAS;mBAC5B,MAAY,MAAM,GAAC,SAAS;8BAC5B,MAAY,MAAM,GAAC,SAAS;qBAC5B,MAAY,MAAM,GAAC,SAAS;cAC5B,MAAY,OAAO,GAAC,SAAS;YAC7B,MAAY,OAAO,GAAC,SAAS;mBAC7B,MAAY,OAAO,GAAC,SAAS;gBAC7B,MAAY,OAAO,GAAC,SAAS;aAC7B,MAAY,OAAO,GAAC,SAAS;kBAC7B,MAAY,OAAO,GAAC,SAAS;qBAC7B,MAAY,MAAM,GAAC,SAAS"} \ No newline at end of file +{"version":3,"file":"resolve-from-caller.d.mts","sourceRoot":"","sources":["../../src/lib/resolve-from-caller.mjs"],"names":[],"mappings":"AAuFA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,6CAhCa,KAAK,CAAC,QAAQ,CAAC,CA0C3B;AA8MD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,2CAzCW,MAAM,GACJ,MAAM,CAsDlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,0CA1CW,MAAM,GACJ,MAAM,CAuDlB;AAxXM,4BAvBI,GAAG,GACD,MAAM,GAAC,IAAI,CAsB+F;;iBA4XzG,MAAY,MAAM,GAAC,SAAS;mBAC5B,MAAY,MAAM,GAAC,SAAS;qBAC5B,MAAY,MAAM,GAAC,SAAS;iBAC5B,MAAY,MAAM,GAAC,SAAS;mBAC5B,MAAY,MAAM,GAAC,SAAS;8BAC5B,MAAY,MAAM,GAAC,SAAS;qBAC5B,MAAY,MAAM,GAAC,SAAS;cAC5B,MAAY,OAAO,GAAC,SAAS;YAC7B,MAAY,OAAO,GAAC,SAAS;mBAC7B,MAAY,OAAO,GAAC,SAAS;gBAC7B,MAAY,OAAO,GAAC,SAAS;aAC7B,MAAY,OAAO,GAAC,SAAS;kBAC7B,MAAY,OAAO,GAAC,SAAS;qBAC7B,MAAY,MAAM,GAAC,SAAS"} \ No newline at end of file