From c9cb65addf017f53b969e53cd7c93b221eca613d Mon Sep 17 00:00:00 2001 From: Hua Zhi Vee Date: Thu, 26 Mar 2026 17:05:43 +0800 Subject: [PATCH 1/9] Add Pyodide implementation --- jest.config.js | 1 + package-lock.json | 5735 ----------------------------- package.json | 6 +- rollup.config.js | 53 +- src/pyodide/PyodideEvaluator.ts | 59 + src/pyodide/bridge.py | 700 ++++ src/pyodide/bridge.py.d.ts | 2 + src/pyodide/importAnalyzer.ts | 160 + src/pyodide/index.ts | 2 + src/pyodide/loadPyodide.ts | 47 + src/pyodide/loadTorch.ts | 20 + src/tests/import-analyzer.test.ts | 136 + src/tests/pyodide-torch.test.ts | 86 + src/tests/raw-text-transformer.js | 7 + yarn.lock | 41 + 15 files changed, 1302 insertions(+), 5753 deletions(-) delete mode 100644 package-lock.json create mode 100644 src/pyodide/PyodideEvaluator.ts create mode 100644 src/pyodide/bridge.py create mode 100644 src/pyodide/bridge.py.d.ts create mode 100644 src/pyodide/importAnalyzer.ts create mode 100644 src/pyodide/index.ts create mode 100644 src/pyodide/loadPyodide.ts create mode 100644 src/pyodide/loadTorch.ts create mode 100644 src/tests/import-analyzer.test.ts create mode 100644 src/tests/pyodide-torch.test.ts create mode 100644 src/tests/raw-text-transformer.js diff --git a/jest.config.js b/jest.config.js index a88d55e6..5475af29 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,6 +12,7 @@ module.exports = { }, }, ], + "\\.py$": "/src/tests/raw-text-transformer.js", }, transformIgnorePatterns: ["/node_modules/(?!(@sourceacademy/wasm-util)/).+\\.js$"], testPathIgnorePatterns: ["/node_modules/", "/dist/"], diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 9b64f99b..00000000 --- a/package-lock.json +++ /dev/null @@ -1,5735 +0,0 @@ -{ - "name": "py-slang", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "py-slang", - "version": "1.0.0", - "license": "Apache-2.0", - "dependencies": { - "@sourceacademy/conductor": "^0.2.3", - "@sourceacademy/wasm-util": "^1.0.4", - "fast-levenshtein": "^3.0.0", - "mathjs": "^14.9.1", - "moo": "^0.5.2", - "nearley": "^2.20.1", - "wabt": "^1.0.37" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "^28.0.3", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^16.0.1", - "@rollup/plugin-typescript": "^12.1.2", - "@types/fast-levenshtein": "^0.0.4", - "@types/jest": "^29.5.14", - "@types/moo": "^0.5.10", - "@types/nearley": "^2.11.5", - "@types/node": "^18.19.84", - "eslint": "^9.39.4", - "eslint-config-prettier": "^10.1.8", - "jest": "^29.7.0", - "jsdoc": "^4.0.4", - "nodemon": "^3.1.10", - "prettier": "^3.8.1", - "rollup": "^4.59.0", - "ts-jest": "^29.0.5", - "ts-node": "^10.9.1", - "tslib": "^2.8.1", - "typescript": "^5.5.3", - "typescript-eslint": "^8.56.1" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.3", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.2", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/reporters/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.30", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jsdoc/salty": { - "version": "0.2.9", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v12.0.0" - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "28.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "fdir": "^6.2.0", - "is-reference": "1.2.1", - "magic-string": "^0.30.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=16.0.0 || 14 >= 14.17" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "16.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-typescript": { - "version": "12.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.14.0||^3.0.0||^4.0.0", - "tslib": "*", - "typescript": ">=3.7.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - }, - "tslib": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@sourceacademy/conductor": { - "version": "0.2.3", - "license": "ISC" - }, - "node_modules/@sourceacademy/wasm-util": { - "version": "1.0.4", - "license": "Apache-2.0" - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/fast-levenshtein": { - "version": "0.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/moo": { - "version": "0.5.10", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/nearley": { - "version": "2.11.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "18.19.127", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/type-utils": "8.57.1", - "@typescript-eslint/utils": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.57.1", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.1", - "@typescript-eslint/types": "^8.57.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1", - "@typescript-eslint/utils": "8.57.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.57.1", - "@typescript-eslint/tsconfig-utils": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.1", - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.1", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "6.14.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.25.3", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001735", - "electron-to-chromium": "^1.5.204", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001737", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/catharsis": { - "version": "0.9.0", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.15" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/co": { - "version": "4.6.0", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/commondir": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/complex.js": { - "version": "2.4.2", - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/create-jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "license": "MIT" - }, - "node_modules/dedent": { - "version": "1.6.0", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.4", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/discontinuous-range": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.221", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-latex": { - "version": "1.2.0", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, - "node_modules/exit": { - "version": "0.1.2", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "fastest-levenshtein": "^1.0.7" - } - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "dev": true, - "license": "ISC" - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "dev": true, - "license": "ISC" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "dev": true, - "license": "ISC" - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-reference": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/javascript-natural-sort": { - "version": "0.7.1", - "license": "MIT" - }, - "node_modules/jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.2", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js2xmlparser": { - "version": "4.0.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "xmlcreate": "^2.0.4" - } - }, - "node_modules/jsdoc": { - "version": "4.0.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/parser": "^7.20.15", - "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^14.1.1", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^14.1.0", - "markdown-it-anchor": "^8.6.7", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/klaw": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, - "node_modules/linkify-it": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.18", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/markdown-it": { - "version": "14.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it-anchor": { - "version": "8.6.7", - "dev": true, - "license": "Unlicense", - "peerDependencies": { - "@types/markdown-it": "*", - "markdown-it": "*" - } - }, - "node_modules/markdown-it/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/marked": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/mathjs": { - "version": "14.9.1", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.26.10", - "complex.js": "^2.2.5", - "decimal.js": "^10.4.3", - "escape-latex": "^1.2.0", - "fraction.js": "^5.2.1", - "javascript-natural-sort": "^0.7.1", - "seedrandom": "^3.0.5", - "tiny-emitter": "^2.1.0", - "typed-function": "^4.2.1" - }, - "bin": { - "mathjs": "bin/cli.js" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/moo": { - "version": "0.5.3", - "license": "BSD-3-Clause" - }, - "node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/nearley": { - "version": "2.20.1", - "license": "MIT", - "dependencies": { - "commander": "^2.19.0", - "moo": "^0.5.0", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6" - }, - "bin": { - "nearley-railroad": "bin/nearley-railroad.js", - "nearley-test": "bin/nearley-test.js", - "nearley-unparse": "bin/nearley-unparse.js", - "nearleyc": "bin/nearleyc.js" - }, - "funding": { - "type": "individual", - "url": "https://nearley.js.org/#give-to-nearley" - } - }, - "node_modules/nearley/node_modules/commander": { - "version": "2.20.3", - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "dev": true, - "license": "MIT" - }, - "node_modules/nodemon": { - "version": "3.1.10", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nodemon/node_modules/semver": { - "version": "7.7.3", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/optionator/node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.1", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "dev": true, - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/railroad-diagrams": { - "version": "1.0.0", - "license": "CC0-1.0" - }, - "node_modules/randexp": { - "version": "0.4.6", - "license": "MIT", - "dependencies": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requizzle": { - "version": "0.2.4", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "license": "MIT", - "engines": { - "node": ">=0.12" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/seedrandom": { - "version": "3.0.5", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.7.3", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/tiny-emitter": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-jest": { - "version": "29.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.2", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0 || ^30.0.0", - "@jest/types": "^29.0.0 || ^30.0.0", - "babel-jest": "^29.0.0 || ^30.0.0", - "jest": "^29.0.0 || ^30.0.0", - "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jest-util": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "license": "0BSD", - "peer": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-function": { - "version": "4.2.1", - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/typescript": { - "version": "5.9.2", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.1", - "@typescript-eslint/parser": "8.57.1", - "@typescript-eslint/typescript-estree": "8.57.1", - "@typescript-eslint/utils": "8.57.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/uc.micro": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/underscore": { - "version": "1.13.8", - "dev": true, - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/wabt": { - "version": "1.0.37", - "license": "Apache-2.0", - "bin": { - "wasm-decompile": "bin/wasm-decompile", - "wasm-interp": "bin/wasm-interp", - "wasm-objdump": "bin/wasm-objdump", - "wasm-stats": "bin/wasm-stats", - "wasm-strip": "bin/wasm-strip", - "wasm-validate": "bin/wasm-validate", - "wasm2c": "bin/wasm2c", - "wasm2wat": "bin/wasm2wat", - "wat2wasm": "bin/wat2wasm" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, - "node_modules/xmlcreate": { - "version": "2.0.4", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json index b0119a2d..4f2beed6 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "jsdoc": "./scripts/jsdoc.sh prepare", "jsdoc:run": "./scripts/jsdoc.sh run", "jsdoc:clean": "./scripts/jsdoc.sh clean", - "test": "jest", - "test-coverage": "jest --coverage", + "test": "node --experimental-vm-modules node_modules/.bin/jest", + "test-coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage", "lint": "eslint --concurrency=auto src", "format": "prettier --write \"**/*.{ts,tsx,json,js,mjs}\"", "format:ci": "prettier --list-different \"**/*.{ts,tsx,json,js,mjs}\"", @@ -61,6 +61,8 @@ "mathjs": "^14.9.1", "moo": "^0.5.2", "nearley": "^2.20.1", + "pyodide": "^0.29.3", + "torch": "https://pkg.pr.new/veehz/torch@297abf6", "wabt": "^1.0.37" } } diff --git a/rollup.config.js b/rollup.config.js index f48b7ede..5562ecfd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -2,6 +2,30 @@ import commonjs from "@rollup/plugin-commonjs"; import json from "@rollup/plugin-json"; import nodeResolve from "@rollup/plugin-node-resolve"; import typescript from "@rollup/plugin-typescript"; +import { readFileSync } from "fs"; + +/** Plugin: import .py files as strings. */ +function rawPy() { + return { + name: "raw-py", + load(id) { + if (id.endsWith(".py")) { + const text = readFileSync(id, "utf-8"); + return `export default ${JSON.stringify(text)};`; + } + }, + }; +} + +function plugins() { + return [ + rawPy(), + nodeResolve({ browser: true }), + commonjs({ include: /node_modules/ }), + json(), + typescript(), + ]; +} /** * @type {import('rollup').RollupOptions} @@ -15,14 +39,7 @@ const config = [ name: "PySlangEvaluator", sourcemap: true, }, - plugins: [ - nodeResolve({ browser: true }), - commonjs({ - include: /node_modules/, - }), - json(), - typescript(), - ], + plugins: plugins(), }, { input: "src/index.ts", @@ -32,14 +49,18 @@ const config = [ name: "PySlangWorker", sourcemap: true, }, - plugins: [ - nodeResolve({ browser: true }), - commonjs({ - include: /node_modules/, - }), - json(), - typescript(), - ], + plugins: plugins(), + }, + { + input: "src/pyodide/index.ts", + output: { + file: "dist/pyodide-evaluator.cjs", + format: "cjs", + name: "PyodideEvaluator", + sourcemap: true, + inlineDynamicImports: true, + }, + plugins: plugins(), }, ]; diff --git a/src/pyodide/PyodideEvaluator.ts b/src/pyodide/PyodideEvaluator.ts new file mode 100644 index 00000000..7beb38ad --- /dev/null +++ b/src/pyodide/PyodideEvaluator.ts @@ -0,0 +1,59 @@ +import { BasicEvaluator, IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import type { PyodideInterface } from "pyodide"; +import { loadPyodideGeneric } from "./loadPyodide"; +import { loadTorch } from "./loadTorch"; +import { rewriteTorchImports, getNonTorchImportRoots } from "./importAnalyzer"; + +export default class PyodideEvaluator extends BasicEvaluator { + private pyodide: Promise; + private torchLoaded = false; + + constructor(conductor: IRunnerPlugin) { + super(conductor); + this.pyodide = loadPyodideGeneric().then(async pyodide => { + await pyodide.loadPackage("micropip"); + await pyodide.setStdout({ + batched: (output: string) => { + this.conductor.sendOutput(output); + }, + }); + return pyodide; + }); + } + + async evaluateChunk(chunk: string): Promise { + const pyodide = await this.pyodide; + + // --- Use py-slang's parser to detect and rewrite torch imports --- + const { code, hasTorch } = rewriteTorchImports(chunk); + + if (hasTorch && !this.torchLoaded) { + await loadTorch(pyodide); + pyodide.globals.set("__sa_import_torch", pyodide.globals.get("torch")); + this.torchLoaded = true; + } + + // --- Install any other imported modules via micropip --- + const otherRoots = getNonTorchImportRoots(chunk); + if (otherRoots.size > 0) { + const modulesArray = Array.from(otherRoots); + const installerCode = ` +import importlib, micropip +mods = ${JSON.stringify(modulesArray)} +missing = [] +for m in mods: + try: + importlib.import_module(m) + except Exception: + missing.append(m) +if missing: + await micropip.install(missing) +`; + await pyodide.runPythonAsync(installerCode); + } + + // --- Execute the (possibly rewritten) code --- + const output = await pyodide.runPythonAsync(code); + this.conductor.sendOutput(output); + } +} diff --git a/src/pyodide/bridge.py b/src/pyodide/bridge.py new file mode 100644 index 00000000..6fa27efe --- /dev/null +++ b/src/pyodide/bridge.py @@ -0,0 +1,700 @@ +# bridge.py +# Provides a PyTorch-compatible Python API over js_torch (the TypeScript torch library). +# +# Before loading this file, set the following globals in Pyodide: +# js_torch - the torch module (window.torch from the UMD build) + +from pyodide.ffi import JsProxy, to_js + + +# --------------------------------------------------------------------------- +# Internal helpers +# --------------------------------------------------------------------------- + +def _wrap_result(result): + """ + Wrap a JS return value: + - JsProxy (JS object/Tensor) -> Python Tensor + - Python primitive (int, float, bool) -> return as-is + JS primitives are automatically converted to Python by Pyodide, + so they will NOT be JsProxy instances. + """ + if isinstance(result, JsProxy): + return Tensor(result) + return result + + +def _transform(obj): + """Convert Python objects to JS-compatible types before passing to JS.""" + if isinstance(obj, Tensor): + return obj._js + if isinstance(obj, (list, tuple)): + return to_js([_transform(item) for item in obj]) + return obj + + +def _transform_args(args): + return [_transform(a) for a in args] + + +# --------------------------------------------------------------------------- +# Tensor +# --------------------------------------------------------------------------- + +class Tensor: + """Python wrapper around a JS Tensor, mirroring the PyTorch Tensor API.""" + + # ------------------------------------------------------------------ + # Construction + # ------------------------------------------------------------------ + + def __new__(cls, data, requires_grad=False): + # Return None for missing tensors so e.g. `tensor.grad` returns None + # when there is no gradient — matching PyTorch behaviour. + # Pyodide may represent JS null as a special JsNull type (not JsProxy, not None). + if data is None or type(data).__name__ in ('JsNull', 'JsUndefined'): + return None + return super().__new__(cls) + + def __init__(self, data, requires_grad=False): + if isinstance(data, JsProxy): + self._js = data + else: + js_data = to_js(data) if isinstance(data, (list, tuple)) else data + self._js = js_torch.tensor(js_data, requires_grad) + + # ------------------------------------------------------------------ + # Representation + # ------------------------------------------------------------------ + + def __repr__(self): + extra = ", requires_grad=True" if self.requires_grad else "" + return f"tensor({self.tolist()}{extra})" + + # ------------------------------------------------------------------ + # Data access + # ------------------------------------------------------------------ + + def tolist(self): + """Return tensor data as a (nested) Python list, or a Python scalar for 0-d tensors.""" + result = self._js.toArray() + if isinstance(result, JsProxy): + return result.to_py() + return result # scalar + + def item(self): + return self._js.item() + + # ------------------------------------------------------------------ + # Properties + # ------------------------------------------------------------------ + + @property + def shape(self): + return tuple(self._js.shape.to_py()) + + @property + def data(self): + """Detached view of the tensor data (no gradient).""" + return self.detach() + + @property + def requires_grad(self): + return bool(self._js.requires_grad) + + @requires_grad.setter + def requires_grad(self, value): + self._js.requires_grad = value + + @property + def grad(self): + raw = self._js.grad + if raw is None or type(raw).__name__ in ('JsNull', 'JsUndefined'): + return None + return Tensor(raw) + + @grad.setter + def grad(self, value): + self._js.grad = value._js if isinstance(value, Tensor) else None + + @property + def T(self): + if len(self.shape) < 2: + return self + return Tensor(self._js.transpose(0, 1)) + + # ------------------------------------------------------------------ + # Grad utilities + # ------------------------------------------------------------------ + + def backward(self, gradient=None): + if gradient is None: + self._js.backward() + else: + self._js.backward(gradient._js) + + def detach(self): + return Tensor(self._js.detach()) + + def zero_(self): + self._js.zero_() + return self + + def retain_grad(self): + self._js.retain_grad() + + # ------------------------------------------------------------------ + # Shape utilities + # ------------------------------------------------------------------ + + def size(self, dim=None): + s = self.shape + return s if dim is None else s[dim] + + def dim(self): + return len(self.shape) + + def numel(self): + n = 1 + for s in self.shape: + n *= s + return n + + def reshape(self, *args): + shape = list(args[0]) if len(args) == 1 and isinstance(args[0], (list, tuple)) else list(args) + return Tensor(self._js.reshape(to_js(shape))) + + def view(self, *args): + return self.reshape(*args) + + def squeeze(self, dim=None): + if dim is None: + new_shape = [s for s in self.shape if s != 1] + return Tensor(self._js.reshape(to_js(new_shape or [1]))) + return Tensor(self._js.squeeze(dim)) + + def unsqueeze(self, dim): + return Tensor(self._js.unsqueeze(dim)) + + def expand(self, *args): + shape = list(args[0]) if len(args) == 1 and isinstance(args[0], (list, tuple)) else list(args) + return Tensor(self._js.expand(to_js(shape))) + + def transpose(self, dim0, dim1): + return Tensor(self._js.transpose(dim0, dim1)) + + def flatten(self, start_dim=0, end_dim=-1): + n = self.numel() + return self.reshape([n]) + + # ------------------------------------------------------------------ + # Reductions — default (no dim) sums all elements, matching PyTorch + # ------------------------------------------------------------------ + + def sum(self, dim=None, keepdim=False): + return Tensor(self._js.sum() if dim is None else self._js.sum(dim, keepdim)) + + def mean(self, dim=None, keepdim=False): + return Tensor(self._js.mean() if dim is None else self._js.mean(dim, keepdim)) + + def max(self, dim=None, keepdim=False): + return Tensor(self._js.max() if dim is None else self._js.max(dim, keepdim)) + + def min(self, dim=None, keepdim=False): + return Tensor(self._js.min() if dim is None else self._js.min(dim, keepdim)) + + # ------------------------------------------------------------------ + # Arithmetic — explicit methods + # ------------------------------------------------------------------ + + def _to_js(self, other): + return other._js if isinstance(other, Tensor) else other + + def add(self, other): return Tensor(self._js.add(self._to_js(other))) + def sub(self, other): return Tensor(self._js.sub(self._to_js(other))) + def mul(self, other): return Tensor(self._js.mul(self._to_js(other))) + def div(self, other): return Tensor(self._js.div(self._to_js(other))) + def pow(self, other): return Tensor(self._js.pow(self._to_js(other))) + def matmul(self, other): return Tensor(self._js.matmul(self._to_js(other))) + + # ------------------------------------------------------------------ + # Arithmetic operators + # ------------------------------------------------------------------ + + def __add__(self, other): return self.add(other) + def __radd__(self, other): return self.add(other) # add is commutative + def __sub__(self, other): return self.sub(other) + def __rsub__(self, other): + o = other if isinstance(other, Tensor) else Tensor(other) + return o.sub(self) + def __mul__(self, other): return self.mul(other) + def __rmul__(self, other): return self.mul(other) # mul is commutative + def __truediv__(self, other): return self.div(other) + def __rtruediv__(self, other): + o = other if isinstance(other, Tensor) else Tensor(other) + return o.div(self) + def __pow__(self, other): return self.pow(other) + def __rpow__(self, other): + o = other if isinstance(other, Tensor) else Tensor(other) + return o.pow(self) + def __matmul__(self, other): return self.matmul(other) + def __neg__(self): return Tensor(self._js.neg()) + def __abs__(self): return Tensor(self._js.abs()) + + # ------------------------------------------------------------------ + # Unary operations + # ------------------------------------------------------------------ + + def neg(self): return Tensor(self._js.neg()) + def abs(self): return Tensor(self._js.abs()) + def log(self): return Tensor(self._js.log()) + def exp(self): return Tensor(self._js.exp()) + def sqrt(self): return Tensor(self._js.sqrt()) + def square(self): return Tensor(self._js.square()) + def sin(self): return Tensor(self._js.sin()) + def cos(self): return Tensor(self._js.cos()) + def tan(self): return Tensor(self._js.tan()) + def sigmoid(self): return Tensor(self._js.sigmoid()) + def relu(self): return Tensor(js_torch.nn.functional.relu(self._js)) + def sign(self): return Tensor(self._js.sign()) + def reciprocal(self): return Tensor(self._js.reciprocal()) + def nan_to_num(self): return Tensor(self._js.nan_to_num()) + + # ------------------------------------------------------------------ + # Comparison + # ------------------------------------------------------------------ + + def lt(self, other): return Tensor(self._js.lt(self._to_js(other))) + def gt(self, other): return Tensor(self._js.gt(self._to_js(other))) + def le(self, other): return Tensor(self._js.le(self._to_js(other))) + def ge(self, other): return Tensor(self._js.ge(self._to_js(other))) + def eq(self, other): return Tensor(self._js.eq(self._to_js(other))) + def ne(self, other): return Tensor(self._js.ne(self._to_js(other))) + + def allclose(self, other, rtol=1e-5, atol=1e-8, equal_nan=False): + return bool(js_torch.allclose(self._js, other._js, rtol, atol, equal_nan)) + + # ------------------------------------------------------------------ + # Type conversions + # ------------------------------------------------------------------ + + def __float__(self): return float(self.item()) + def __int__(self): return int(self.item()) + def __bool__(self): return bool(self.item()) + def __format__(self, fmt): return format(self.item(), fmt) + + # ------------------------------------------------------------------ + # Indexing + # ------------------------------------------------------------------ + + def __getitem__(self, key): + if isinstance(key, int): + return Tensor(self._js.index(key)) + if isinstance(key, tuple): + result = self._js + for k in key: + if isinstance(k, int): + result = result.index(k) + else: + raise NotImplementedError( + "Only integer indexing is supported in multi-dimensional indexing" + ) + return Tensor(result) + if isinstance(key, slice): + start, stop, step = key.indices(self.shape[0]) + data = [Tensor(self._js.index(i)).tolist() for i in range(start, stop, step)] + return Tensor(data) + raise TypeError(f"Invalid index type: {type(key).__name__}") + + # ------------------------------------------------------------------ + # Iteration and length + # ------------------------------------------------------------------ + + def __len__(self): + return self.shape[0] + + def __iter__(self): + data = self.tolist() + if not isinstance(data, list): + raise TypeError("iteration over a 0-d tensor") + for item in data: + yield Tensor(item) + + # ------------------------------------------------------------------ + # Catch-all: delegate unknown attribute accesses to the JS tensor. + # Returned JsProxy objects are wrapped in Tensor; primitives pass through. + # ------------------------------------------------------------------ + + def __getattr__(self, name): + if name.startswith('_'): + raise AttributeError(name) + def method(*args, **kwargs): + js_args = _transform_args(args) + return _wrap_result(self._js.__getattribute__(name)(*js_args)) + return method + + +# --------------------------------------------------------------------------- +# no_grad context manager — actually disables grad in the JS engine +# --------------------------------------------------------------------------- + +class _NoGrad: + def __enter__(self): + self._prev = js_torch.enable_no_grad() + return self + + def __exit__(self, *args): + js_torch.disable_no_grad(self._prev) + + +# --------------------------------------------------------------------------- +# Parameter +# --------------------------------------------------------------------------- + +class Parameter(Tensor): + """A Tensor that is automatically registered as a parameter.""" + def __init__(self, data, requires_grad=True): + if isinstance(data, Tensor): + self._js = js_torch.nn.Parameter.new(data._js) + elif isinstance(data, JsProxy): + self._js = js_torch.nn.Parameter.new(data) + else: + self._js = js_torch.nn.Parameter.new(js_torch.tensor(data)) + if not requires_grad: + self._js.requires_grad = False + + +# --------------------------------------------------------------------------- +# Module — pure-Python base class for user-defined models +# --------------------------------------------------------------------------- + +class Module: + """ + Pure-Python nn.Module. Subclass this to build models using bridge Tensors. + Assign `Parameter` or `_NNModule` instances as attributes and they are + automatically tracked by `parameters()`. + """ + + def __init__(self): + object.__setattr__(self, '_parameters', {}) + object.__setattr__(self, '_modules', {}) + + def __setattr__(self, name, value): + try: + params = object.__getattribute__(self, '_parameters') + modules = object.__getattribute__(self, '_modules') + except AttributeError: + object.__setattr__(self, name, value) + return + + if isinstance(value, Tensor) and value.requires_grad: + params[name] = value + elif isinstance(value, (Module, _NNModule)): + modules[name] = value + object.__setattr__(self, name, value) + + def __call__(self, *args, **kwargs): + return self.forward(*args, **kwargs) + + def forward(self, *args, **kwargs): + raise NotImplementedError + + def parameters(self): + params = list(object.__getattribute__(self, '_parameters').values()) + for mod in object.__getattribute__(self, '_modules').values(): + params.extend(mod.parameters()) + return params + + def named_parameters(self, prefix=''): + result = [] + for name, p in object.__getattribute__(self, '_parameters').items(): + full = f"{prefix}.{name}" if prefix else name + result.append((full, p)) + for mod_name, mod in object.__getattribute__(self, '_modules').items(): + full_mod = f"{prefix}.{mod_name}" if prefix else mod_name + result.extend(mod.named_parameters(full_mod)) + return result + + def zero_grad(self): + for p in self.parameters(): + p.grad = None + + +# --------------------------------------------------------------------------- +# _NNModule — wraps a JS nn.Module instance +# --------------------------------------------------------------------------- + +class _NNModule: + """Wraps a JS nn.Module returned by the nn factory functions.""" + + def __init__(self, js_module): + self._module = js_module + + def __call__(self, *args): + js_args = [a._js if isinstance(a, Tensor) else a for a in args] + return Tensor(self._module.forward(*js_args)) + + def forward(self, *args): + return self(*args) + + def parameters(self): + return [Tensor(p) for p in self._module.parameters().to_py()] + + def named_parameters(self, prefix=''): + raw = self._module.named_parameters(prefix).to_py() + return [(pair[0], Tensor(pair[1])) for pair in raw] + + def zero_grad(self): + for p in self.parameters(): + p.grad = None + + +# --------------------------------------------------------------------------- +# nn.functional +# --------------------------------------------------------------------------- + +class _NNFunctional: + def relu(self, input): + return Tensor(js_torch.nn.functional.relu(input._js)) + + def sigmoid(self, input): + return Tensor(js_torch.nn.functional.sigmoid(input._js)) + + def __getattr__(self, name): + if name.startswith('_'): + raise AttributeError(name) + def fn(*args, **kwargs): + return _wrap_result(js_torch.nn.functional.__getattribute__(name)(*_transform_args(args))) + return fn + + +# --------------------------------------------------------------------------- +# nn.parameter namespace +# --------------------------------------------------------------------------- + +class _NNParameterNamespace: + def __init__(self): + self.Parameter = Parameter + + +# --------------------------------------------------------------------------- +# nn namespace +# --------------------------------------------------------------------------- + +class _NNNamespace: + def __init__(self): + self.functional = _NNFunctional() + self.parameter = _NNParameterNamespace() + self.Module = Module + self.Parameter = Parameter + + def Linear(self, in_features, out_features, bias=True): + return _NNModule(js_torch.nn.Linear.new(in_features, out_features, bias)) + + def ReLU(self): + return _NNModule(js_torch.nn.ReLU.new()) + + def Sigmoid(self): + return _NNModule(js_torch.nn.Sigmoid.new()) + + def Sequential(self, *modules): + js_mods = [m._module for m in modules] + return _NNModule(js_torch.nn.Sequential.new(*js_mods)) + + def MSELoss(self, reduction='mean'): + return _NNModule(js_torch.nn.MSELoss.new(reduction)) + + def L1Loss(self, reduction='mean'): + return _NNModule(js_torch.nn.L1Loss.new(reduction)) + + def BCELoss(self, weight=None, reduction='mean'): + js_weight = weight._js if isinstance(weight, Tensor) else None + return _NNModule(js_torch.nn.BCELoss.new(js_weight, reduction)) + + def CrossEntropyLoss(self, reduction='mean'): + return _NNModule(js_torch.nn.CrossEntropyLoss.new(reduction)) + + def Conv1d(self, in_channels, out_channels, kernel_size, + stride=1, padding=0, dilation=1, groups=1, bias=True): + return _NNModule(js_torch.nn.Conv1d.new( + in_channels, out_channels, kernel_size, + stride, padding, dilation, groups, bias + )) + + def Conv2d(self, in_channels, out_channels, kernel_size, + stride=1, padding=0, dilation=1, groups=1, bias=True): + return _NNModule(js_torch.nn.Conv2d.new( + in_channels, out_channels, kernel_size, + stride, padding, dilation, groups, bias + )) + + def Conv3d(self, in_channels, out_channels, kernel_size, + stride=1, padding=0, dilation=1, groups=1, bias=True): + return _NNModule(js_torch.nn.Conv3d.new( + in_channels, out_channels, kernel_size, + stride, padding, dilation, groups, bias + )) + + +# --------------------------------------------------------------------------- +# optim wrappers +# --------------------------------------------------------------------------- + +class _Optimizer: + def __init__(self, js_optim): + self._optim = js_optim + + def step(self): + self._optim.step() + + def zero_grad(self): + self._optim.zero_grad() + + +class _OptimNamespace: + def SGD(self, params, lr=0.001, momentum=0.0, dampening=0.0, + weight_decay=0.0, nesterov=False, maximize=False): + js_params = to_js([p._js for p in params]) + return _Optimizer(js_torch.optim.SGD.new( + js_params, lr, momentum, dampening, weight_decay, nesterov, maximize + )) + + def Adam(self, params, lr=0.001, betas=(0.9, 0.999), eps=1e-8, + weight_decay=0.0, amsgrad=False, maximize=False): + js_params = to_js([p._js for p in params]) + js_betas = to_js(list(betas)) + return _Optimizer(js_torch.optim.Adam.new( + js_params, lr, js_betas, eps, weight_decay, amsgrad, maximize + )) + + +# --------------------------------------------------------------------------- +# torch namespace +# --------------------------------------------------------------------------- + +class _Torch: + def __init__(self): + self.nn = _NNNamespace() + self.optim = _OptimNamespace() + self.no_grad = _NoGrad + + @property + def tensor(self): + return Tensor + + # --- creation functions --- + + def _shape_from_args(self, args): + return list(args[0]) if len(args) == 1 and isinstance(args[0], (list, tuple)) else list(args) + + def zeros(self, *args, **kwargs): + return Tensor(js_torch.zeros(to_js(self._shape_from_args(args)))) + + def ones(self, *args, **kwargs): + return Tensor(js_torch.ones(to_js(self._shape_from_args(args)))) + + def zeros_like(self, input): + return Tensor(js_torch.zeros_like(input._js)) + + def ones_like(self, input): + return Tensor(js_torch.ones_like(input._js)) + + def randn(self, *args, **kwargs): + return Tensor(js_torch.randn(to_js(self._shape_from_args(args)))) + + def rand(self, *args, **kwargs): + return Tensor(js_torch.rand(to_js(self._shape_from_args(args)))) + + def arange(self, start, end=None, step=1): + if end is None: + end = start + start = 0 + return Tensor(js_torch.arange(start, end, step)) + + def linspace(self, start, end, steps): + return Tensor(js_torch.linspace(start, end, steps)) + + def empty(self, *args, **kwargs): + return Tensor(js_torch.empty(to_js(self._shape_from_args(args)))) + + def empty_like(self, input): + return Tensor(js_torch.empty_like(input._js)) + + def full(self, shape, fill_value): + return Tensor(js_torch.full(to_js(list(shape)), fill_value)) + + def full_like(self, input, fill_value): + return Tensor(js_torch.full_like(input._js, fill_value)) + + def rand_like(self, input): + return Tensor(js_torch.rand_like(input._js)) + + def randn_like(self, input): + return Tensor(js_torch.randn_like(input._js)) + + def randint_like(self, input, low, high): + return Tensor(js_torch.randint_like(input._js, low, high)) + + # --- utility functions --- + + def is_tensor(self, obj): + return isinstance(obj, Tensor) + + def is_nonzero(self, input): + if input.numel() != 1: + raise RuntimeError( + "Boolean value of Tensor with more than one element is ambiguous" + ) + return bool(input.item() != 0) + + def numel(self, input): + return input.numel() + + # --- functional wrappers --- + + def sum(self, input, dim=None, keepdim=False): + return input.sum(dim, keepdim) + + def mean(self, input, dim=None, keepdim=False): + return input.mean(dim, keepdim) + + def sigmoid(self, input): + return input.sigmoid() + + def relu(self, input): + return input.relu() + + def flatten(self, input, start_dim=0, end_dim=-1): + return input.flatten(start_dim, end_dim) + + def allclose(self, a, b, rtol=1e-5, atol=1e-8, equal_nan=False): + return a.allclose(b, rtol, atol, equal_nan) + + def is_grad_enabled(self): + return bool(js_torch.is_grad_enabled()) + + def cat(self, tensors, dim=0): + """Concatenate tensors along dim. NOTE: gradient is not tracked.""" + if dim != 0: + raise NotImplementedError("torch.cat only supports dim=0 in this bridge") + result = [] + for t in tensors: + data = t.tolist() + if isinstance(data, list): + result.extend(data) + else: + result.append(data) + return Tensor(result) + + def Size(self, shape): + return list(shape) + + def __getattr__(self, name): + if name.startswith('_'): + raise AttributeError(name) + def fn(*args, **kwargs): + return _wrap_result(js_torch.__getattribute__(name)(*_transform_args(args))) + return fn + + +torch = _Torch() diff --git a/src/pyodide/bridge.py.d.ts b/src/pyodide/bridge.py.d.ts new file mode 100644 index 00000000..c4ef32bd --- /dev/null +++ b/src/pyodide/bridge.py.d.ts @@ -0,0 +1,2 @@ +declare const bridgeCode: string; +export default bridgeCode; diff --git a/src/pyodide/importAnalyzer.ts b/src/pyodide/importAnalyzer.ts new file mode 100644 index 00000000..e976535a --- /dev/null +++ b/src/pyodide/importAnalyzer.ts @@ -0,0 +1,160 @@ +/** + * AST-based import analysis for detecting and rewriting torch imports. + * + * Uses py-slang's parser to produce an AST from the import prefix of the + * source, then walks FromImport nodes to find torch-related imports — + * replacing the regex-based approach used in sa-conductor-py-torch. + * + * Because pyodide code uses full Python syntax that py-slang cannot parse, + * we extract only the leading `from … import …` lines, append a dummy + * statement so the grammar is satisfied, and parse that fragment. + */ + +import { parse } from "../parser/parser-adapter"; +import { StmtNS } from "../ast-types"; + +export interface TorchImportInfo { + /** Full module path, e.g. "torch" or "torch.nn" */ + module: string; + /** Imported names with optional aliases */ + names: { name: string; alias: string | null }[]; + /** 1-based line number in the original source */ + line: number; +} + +/** + * Extracts leading `from … import …` lines from the source and returns + * them along with the line index where non-import code begins. + */ +function extractImportPrefix(source: string): { + importLines: string[]; + bodyStartIdx: number; +} { + const lines = source.split(/\r?\n/); + let i = 0; + for (; i < lines.length; i++) { + const trimmed = lines[i].trim(); + if (trimmed === "" || trimmed.startsWith("#")) continue; + if (trimmed.startsWith("from ") && trimmed.includes(" import ")) continue; + break; + } + return { importLines: lines.slice(0, i), bodyStartIdx: i }; +} + +/** + * Parses only the import prefix of the source using py-slang's parser + * and returns all FromImport nodes whose root module is "torch". + */ +export function detectTorchImports(source: string): TorchImportInfo[] { + const { importLines } = extractImportPrefix(source); + if (importLines.length === 0) return []; + + // Append a dummy statement so the grammar (import* statement*) is satisfied. + const fragment = importLines.join("\n") + "\n_ = 0\n"; + + let ast: StmtNS.FileInput; + try { + ast = parse(fragment); + } catch { + return []; + } + + const torchImports: TorchImportInfo[] = []; + + for (const stmt of ast.statements) { + if (!(stmt instanceof StmtNS.FromImport)) continue; + + const moduleName = stmt.module.lexeme; + const root = moduleName.split(".")[0]; + if (root !== "torch") continue; + + torchImports.push({ + module: moduleName, + names: stmt.names.map(n => ({ + name: n.name.lexeme, + alias: n.alias ? n.alias.lexeme : null, + })), + line: stmt.startToken.line, + }); + } + + return torchImports; +} + +/** + * Returns the set of top-level module roots for all non-torch imports. + * These are modules that should be installed via micropip. + */ +export function getNonTorchImportRoots(source: string): Set { + const { importLines } = extractImportPrefix(source); + if (importLines.length === 0) return new Set(); + + const fragment = importLines.join("\n") + "\n_ = 0\n"; + + let ast: StmtNS.FileInput; + try { + ast = parse(fragment); + } catch { + return new Set(); + } + + const roots = new Set(); + for (const stmt of ast.statements) { + if (!(stmt instanceof StmtNS.FromImport)) continue; + const root = stmt.module.lexeme.split(".")[0]; + if (root !== "torch") { + roots.add(root); + } + } + return roots; +} + +/** + * Generates Python assignment code that replaces a torch import statement. + * + * Example: + * from torch.nn import Linear as L, Conv2d + * → L = __sa_import_torch.nn.Linear + * Conv2d = __sa_import_torch.nn.Conv2d + */ +function generateReplacement(imp: TorchImportInfo): string { + const injected = "__sa_import_torch"; + const subparts = imp.module.split(".").slice(1); + const base = subparts.length > 0 ? `${injected}.${subparts.join(".")}` : injected; + + return imp.names + .map(({ name, alias }) => { + const binding = alias ?? name; + return `${binding} = ${base}.${name}`; + }) + .join("\n"); +} + +/** + * Rewrites the source code by replacing torch import lines with + * variable assignments that reference the injected `__sa_import_torch` global. + * + * Non-torch code is passed through unchanged. + */ +export function rewriteTorchImports(source: string): { + code: string; + hasTorch: boolean; +} { + const imports = detectTorchImports(source); + + if (imports.length === 0) { + return { code: source, hasTorch: false }; + } + + const lines = source.split(/\r?\n/); + + // Process in reverse order so earlier line indices stay valid. + for (let i = imports.length - 1; i >= 0; i--) { + const imp = imports[i]; + const replacement = generateReplacement(imp); + const idx = imp.line - 1; + lines.splice(idx, 1, replacement); + } + + return { code: lines.join("\n"), hasTorch: true }; +} diff --git a/src/pyodide/index.ts b/src/pyodide/index.ts new file mode 100644 index 00000000..40c4b0e9 --- /dev/null +++ b/src/pyodide/index.ts @@ -0,0 +1,2 @@ +export { default as PyodideEvaluator } from "./PyodideEvaluator"; +export { detectTorchImports, getNonTorchImportRoots, rewriteTorchImports } from "./importAnalyzer"; diff --git a/src/pyodide/loadPyodide.ts b/src/pyodide/loadPyodide.ts new file mode 100644 index 00000000..d8b02f90 --- /dev/null +++ b/src/pyodide/loadPyodide.ts @@ -0,0 +1,47 @@ +import { version, loadPyodide } from "pyodide"; +import type { PyodideInterface } from "pyodide"; + +const IN_NODE = + typeof process !== "undefined" && process.versions != null && process.versions.node != null; + +async function ensureLocalPyodideAssets(baseUrl: string): Promise { + const path = await import("node:path"); + const fs = await import("node:fs/promises"); + const os = await import("node:os"); + + const dir = path.join(os.tmpdir(), `pyodide-${version}`); + await fs.mkdir(dir, { recursive: true }); + + const assets = [ + { name: "pyodide.asm.js", mode: "text" as const }, + { name: "pyodide.asm.wasm", mode: "binary" as const }, + { name: "python_stdlib.zip", mode: "binary" as const }, + { name: "pyodide-lock.json", mode: "text" as const }, + ]; + + for (const asset of assets) { + const url = baseUrl + asset.name; + const dest = path.join(dir, asset.name); + try { + await fs.access(dest); + continue; + } catch { + // File doesn't exist yet — download it. + } + const res = await fetch(url); + if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`); + const data = + asset.mode === "text" + ? Buffer.from(await res.text(), "utf8") + : Buffer.from(await res.arrayBuffer()); + await fs.writeFile(dest, data); + } + + return dir + path.sep; +} + +export async function loadPyodideGeneric(): Promise { + const cdnBase = `https://cdn.jsdelivr.net/pyodide/v${version}/full/`; + const indexURL = IN_NODE ? await ensureLocalPyodideAssets(cdnBase) : cdnBase; + return loadPyodide({ indexURL, fullStdLib: true }); +} diff --git a/src/pyodide/loadTorch.ts b/src/pyodide/loadTorch.ts new file mode 100644 index 00000000..d7421fa3 --- /dev/null +++ b/src/pyodide/loadTorch.ts @@ -0,0 +1,20 @@ +import type { PyodideInterface } from "pyodide"; +import * as torch from "torch"; +import bridgeCode from "./bridge.py"; + +/** + * Loads the torch library into Pyodide by exposing the JS torch object + * and running bridge.py to set up the Python-side `torch` module. + * + * After this call, `pyodide.globals.get("torch")` is the usable torch module. + */ +export async function loadTorch(pyodide: PyodideInterface): Promise { + pyodide.globals.set("js_torch", torch); + + await pyodide.runPythonAsync(bridgeCode); + + const hasTorch = pyodide.runPython("'torch' in globals()"); + if (!hasTorch) { + throw new Error("torch not found in globals after running bridge.py"); + } +} diff --git a/src/tests/import-analyzer.test.ts b/src/tests/import-analyzer.test.ts new file mode 100644 index 00000000..9f373718 --- /dev/null +++ b/src/tests/import-analyzer.test.ts @@ -0,0 +1,136 @@ +import { + detectTorchImports, + getNonTorchImportRoots, + rewriteTorchImports, +} from "../pyodide/importAnalyzer"; + +// --------------------------------------------------------------------------- +// detectTorchImports +// --------------------------------------------------------------------------- +describe("detectTorchImports", () => { + test("detects `from torch import tensor`", () => { + const result = detectTorchImports("from torch import tensor\nx = 1\n"); + expect(result).toHaveLength(1); + expect(result[0].module).toBe("torch"); + expect(result[0].names).toEqual([{ name: "tensor", alias: null }]); + }); + + test("detects `from torch.nn import Linear as L`", () => { + const result = detectTorchImports("from torch.nn import Linear as L\nx = 1\n"); + expect(result).toHaveLength(1); + expect(result[0].module).toBe("torch.nn"); + expect(result[0].names).toEqual([{ name: "Linear", alias: "L" }]); + }); + + test("detects multiple names", () => { + const result = detectTorchImports("from torch import tensor, zeros, ones\nx = 1\n"); + expect(result).toHaveLength(1); + expect(result[0].names).toEqual([ + { name: "tensor", alias: null }, + { name: "zeros", alias: null }, + { name: "ones", alias: null }, + ]); + }); + + test("detects multiple torch import statements", () => { + const src = "from torch import tensor\nfrom torch.nn import Linear\nx = 1\n"; + const result = detectTorchImports(src); + expect(result).toHaveLength(2); + expect(result[0].module).toBe("torch"); + expect(result[1].module).toBe("torch.nn"); + }); + + test("ignores non-torch imports", () => { + const result = detectTorchImports("from math import sqrt\nx = 1\n"); + expect(result).toHaveLength(0); + }); + + test("returns empty for code with no imports", () => { + const result = detectTorchImports("x = 1\n"); + expect(result).toHaveLength(0); + }); + + test("works when body uses full Python syntax unsupported by py-slang", () => { + const result = detectTorchImports("from torch import tensor\nx = tensor([1,2]).tolist()\n"); + expect(result).toHaveLength(1); + expect(result[0].module).toBe("torch"); + }); +}); + +// --------------------------------------------------------------------------- +// getNonTorchImportRoots +// --------------------------------------------------------------------------- +describe("getNonTorchImportRoots", () => { + test("returns non-torch module roots", () => { + const src = "from math import sqrt\nfrom torch import tensor\nfrom numpy import array\nx = 1\n"; + const roots = getNonTorchImportRoots(src); + expect(roots).toEqual(new Set(["math", "numpy"])); + }); + + test("returns empty set when only torch imports exist", () => { + const roots = getNonTorchImportRoots("from torch import tensor\nx = 1\n"); + expect(roots).toEqual(new Set()); + }); + + test("extracts root from dotted module name", () => { + const roots = getNonTorchImportRoots("from os.path import join\nx = 1\n"); + expect(roots).toEqual(new Set(["os"])); + }); +}); + +// --------------------------------------------------------------------------- +// rewriteTorchImports +// --------------------------------------------------------------------------- +describe("rewriteTorchImports", () => { + test("rewrites `from torch import tensor`", () => { + const { code, hasTorch } = rewriteTorchImports("from torch import tensor\nx = tensor(1)\n"); + expect(hasTorch).toBe(true); + expect(code).toContain("tensor = __sa_import_torch.tensor"); + expect(code).not.toContain("from torch"); + expect(code).toContain("x = tensor(1)"); + }); + + test("rewrites `from torch.nn import Linear as L`", () => { + const { code, hasTorch } = rewriteTorchImports( + "from torch.nn import Linear as L\nx = L(3, 2)\n", + ); + expect(hasTorch).toBe(true); + expect(code).toContain("L = __sa_import_torch.nn.Linear"); + expect(code).toContain("x = L(3, 2)"); + }); + + test("rewrites multiple names", () => { + const { code, hasTorch } = rewriteTorchImports("from torch import tensor, zeros\nx = 1\n"); + expect(hasTorch).toBe(true); + expect(code).toContain("tensor = __sa_import_torch.tensor"); + expect(code).toContain("zeros = __sa_import_torch.zeros"); + }); + + test("leaves non-torch code unchanged", () => { + const src = "x = 1\n"; + const { code, hasTorch } = rewriteTorchImports(src); + expect(hasTorch).toBe(false); + expect(code).toBe(src); + }); + + test("preserves non-torch imports", () => { + const src = "from math import sqrt\nfrom torch import tensor\nx = sqrt(tensor(4))\n"; + const { code, hasTorch } = rewriteTorchImports(src); + expect(hasTorch).toBe(true); + expect(code).toContain("from math import sqrt"); + expect(code).toContain("tensor = __sa_import_torch.tensor"); + }); + + test("rewrites deeply nested module path", () => { + const { code } = rewriteTorchImports("from torch.nn.functional import relu\nx = relu(1)\n"); + expect(code).toContain("relu = __sa_import_torch.nn.functional.relu"); + }); + + test("handles full Python body that py-slang cannot parse", () => { + const src = "from torch import tensor\nx = tensor([1, 2, 3]).tolist()\nprint(x)\n"; + const { code, hasTorch } = rewriteTorchImports(src); + expect(hasTorch).toBe(true); + expect(code).toContain("tensor = __sa_import_torch.tensor"); + expect(code).toContain("x = tensor([1, 2, 3]).tolist()"); + }); +}); diff --git a/src/tests/pyodide-torch.test.ts b/src/tests/pyodide-torch.test.ts new file mode 100644 index 00000000..5c497b85 --- /dev/null +++ b/src/tests/pyodide-torch.test.ts @@ -0,0 +1,86 @@ +/** + * Integration tests for the pyodide+torch pipeline. + * + * These tests load real pyodide and torch, so they are slow on first run + * while pyodide downloads assets. They verify the full flow: parse → rewrite + * imports → load torch → execute in pyodide. + */ + +import { loadPyodide } from "pyodide"; +import type { PyodideInterface } from "pyodide"; +import * as torch from "torch"; +import bridgeCode from "../pyodide/bridge.py"; +import { rewriteTorchImports } from "../pyodide/importAnalyzer"; + +let pyodide: PyodideInterface; + +beforeAll(async () => { + pyodide = await loadPyodide({ fullStdLib: true }); + + // Set up torch in pyodide (mirrors what loadTorch does) + pyodide.globals.set("js_torch", torch); + await pyodide.runPythonAsync(bridgeCode); + pyodide.globals.set("__sa_import_torch", pyodide.globals.get("torch")); +}, 60_000); + +async function runTorchCode(source: string): Promise { + const { code } = rewriteTorchImports(source); + return pyodide.runPythonAsync(code); +} + +/** Convert a pyodide result to a plain JS value. */ +function toJS(result: unknown): unknown { + if (result != null && typeof result === "object" && "toJs" in result) { + return (result as { toJs: () => unknown }).toJs(); + } + if (result != null && typeof result === "object" && "to_py" in result) { + return (result as { to_py: () => unknown }).to_py(); + } + return result; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("pyodide + torch integration", () => { + test("from torch import tensor — create and read a tensor", async () => { + const result = await runTorchCode( + "from torch import tensor\nx = tensor([1, 2, 3])\nx.tolist()\n", + ); + const val = toJS(result); + expect(val).toEqual([1, 2, 3]); + }); + + test("from torch import zeros — creation function", async () => { + const result = await runTorchCode("from torch import zeros\nx = zeros(3)\nx.tolist()\n"); + const val = toJS(result); + expect(val).toEqual([0, 0, 0]); + }); + + test("from torch.nn import Linear as L — submodule with alias", async () => { + // Just verify it doesn't throw + await runTorchCode( + "from torch.nn import Linear as L\nfrom torch import zeros\nlayer = L(2, 3)\nresult = layer(zeros(2))\nresult.shape\n", + ); + }); + + test("tensor arithmetic", async () => { + const result = await runTorchCode( + "from torch import tensor\na = tensor([1, 2, 3])\nb = tensor([4, 5, 6])\n(a + b).tolist()\n", + ); + const val = toJS(result); + expect(val).toEqual([5, 7, 9]); + }); + + test("autograd — backward pass", async () => { + const result = await runTorchCode(`from torch import tensor +x = tensor([2.0], True) +y = x * x +y.backward() +x.grad.tolist() +`); + const val = toJS(result); + expect(val).toEqual([4.0]); + }); +}); diff --git a/src/tests/raw-text-transformer.js b/src/tests/raw-text-transformer.js new file mode 100644 index 00000000..e6e0a1c9 --- /dev/null +++ b/src/tests/raw-text-transformer.js @@ -0,0 +1,7 @@ +module.exports = { + process(sourceText) { + return { + code: `module.exports = ${JSON.stringify(sourceText)};`, + }; + }, +}; diff --git a/yarn.lock b/yarn.lock index e30878cd..d5e1b01b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1244,6 +1244,13 @@ __metadata: languageName: node linkType: hard +"@types/emscripten@npm:^1.41.4": + version: 1.41.5 + resolution: "@types/emscripten@npm:1.41.5" + checksum: 10c0/ae816da716f896434e59df7a71b67c71ae7e85ca067a32aef1616572fc4757459515d42ade6f5b8fd8d69733a9dbd0cf23010fec5b2f41ce52c09501aa350e45 + languageName: node + linkType: hard + "@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" @@ -4297,7 +4304,9 @@ __metadata: nearley: "npm:^2.20.1" nodemon: "npm:^3.1.10" prettier: "npm:^3.8.1" + pyodide: "npm:^0.29.3" rollup: "npm:^4.59.0" + torch: "https://pkg.pr.new/veehz/torch@297abf6" ts-jest: "npm:^29.0.5" ts-node: "npm:^10.9.1" tslib: "npm:^2.8.1" @@ -4307,6 +4316,16 @@ __metadata: languageName: unknown linkType: soft +"pyodide@npm:^0.29.3": + version: 0.29.3 + resolution: "pyodide@npm:0.29.3" + dependencies: + "@types/emscripten": "npm:^1.41.4" + ws: "npm:^8.5.0" + checksum: 10c0/4c8108e9af7cd8997812507a01c3dd48789ab58973bdef3ac5a336c38837e7495146195be7fb8b0798bcc3c4f79e98877efaf0672d18e088488512eefbf1d3ca + languageName: node + linkType: hard + "railroad-diagrams@npm:^1.0.0": version: 1.0.0 resolution: "railroad-diagrams@npm:1.0.0" @@ -4799,6 +4818,13 @@ __metadata: languageName: node linkType: hard +"torch@https://pkg.pr.new/veehz/torch@297abf6": + version: 0.1.0 + resolution: "torch@https://pkg.pr.new/veehz/torch@297abf6" + checksum: 10c0/bde381a0d266c845970fd34b3d27048709b60fd9e64a2d96f3080eb9c3a4bb1433bcc0ea1bc1123fbb31883ad2623c031ede94319e514f54398b9752c290c6d4 + languageName: node + linkType: hard + "touch@npm:^3.1.0": version: 3.1.1 resolution: "touch@npm:3.1.1" @@ -5149,6 +5175,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.5.0": + version: 8.20.0 + resolution: "ws@npm:8.20.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/956ac5f11738c914089b65878b9223692ace77337ba55379ae68e1ecbeae9b47a0c6eb9403688f609999a58c80d83d99865fe0029b229d308b08c1ef93d4ea14 + languageName: node + linkType: hard + "xmlcreate@npm:^2.0.4": version: 2.0.4 resolution: "xmlcreate@npm:2.0.4" From e796ee8195eaeda477db1ac5e3e7165f0331b9cb Mon Sep 17 00:00:00 2001 From: Hua Zhi Vee Date: Fri, 27 Mar 2026 12:55:08 +0800 Subject: [PATCH 2/9] Detect Python imports using python ast module --- src/pyodide/PyodideEvaluator.ts | 6 +- src/pyodide/importAnalyzer.ts | 161 +++++++++++++++--------------- src/tests/import-analyzer.test.ts | 99 +++++++++++------- src/tests/pyodide-torch.test.ts | 5 +- 4 files changed, 151 insertions(+), 120 deletions(-) diff --git a/src/pyodide/PyodideEvaluator.ts b/src/pyodide/PyodideEvaluator.ts index 7beb38ad..aa71eb6e 100644 --- a/src/pyodide/PyodideEvaluator.ts +++ b/src/pyodide/PyodideEvaluator.ts @@ -24,8 +24,8 @@ export default class PyodideEvaluator extends BasicEvaluator { async evaluateChunk(chunk: string): Promise { const pyodide = await this.pyodide; - // --- Use py-slang's parser to detect and rewrite torch imports --- - const { code, hasTorch } = rewriteTorchImports(chunk); + // --- Use Python's ast module (via Pyodide) to detect and rewrite torch imports --- + const { code, hasTorch } = await rewriteTorchImports(pyodide, chunk); if (hasTorch && !this.torchLoaded) { await loadTorch(pyodide); @@ -34,7 +34,7 @@ export default class PyodideEvaluator extends BasicEvaluator { } // --- Install any other imported modules via micropip --- - const otherRoots = getNonTorchImportRoots(chunk); + const otherRoots = await getNonTorchImportRoots(pyodide, chunk); if (otherRoots.size > 0) { const modulesArray = Array.from(otherRoots); const installerCode = ` diff --git a/src/pyodide/importAnalyzer.ts b/src/pyodide/importAnalyzer.ts index e976535a..ad6a5b1a 100644 --- a/src/pyodide/importAnalyzer.ts +++ b/src/pyodide/importAnalyzer.ts @@ -1,17 +1,13 @@ /** - * AST-based import analysis for detecting and rewriting torch imports. + * Import analysis for detecting and rewriting torch imports using Python's + * built-in `ast` module via Pyodide. * - * Uses py-slang's parser to produce an AST from the import prefix of the - * source, then walks FromImport nodes to find torch-related imports — - * replacing the regex-based approach used in sa-conductor-py-torch. - * - * Because pyodide code uses full Python syntax that py-slang cannot parse, - * we extract only the leading `from … import …` lines, append a dummy - * statement so the grammar is satisfied, and parse that fragment. + * This avoids the limitations of py-slang's parser (which only supports a + * subset of Python) by delegating to CPython's own parser running inside + * Pyodide. */ -import { parse } from "../parser/parser-adapter"; -import { StmtNS } from "../ast-types"; +import type { PyodideInterface } from "pyodide"; export interface TorchImportInfo { /** Full module path, e.g. "torch" or "torch.nn" */ @@ -23,85 +19,88 @@ export interface TorchImportInfo { } /** - * Extracts leading `from … import …` lines from the source and returns - * them along with the line index where non-import code begins. + * Python helper that uses the `ast` module to extract import info. + * Returns a JSON string describing all FromImport statements. */ -function extractImportPrefix(source: string): { - importLines: string[]; - bodyStartIdx: number; -} { - const lines = source.split(/\r?\n/); - let i = 0; - for (; i < lines.length; i++) { - const trimmed = lines[i].trim(); - if (trimmed === "" || trimmed.startsWith("#")) continue; - if (trimmed.startsWith("from ") && trimmed.includes(" import ")) continue; - break; - } - return { importLines: lines.slice(0, i), bodyStartIdx: i }; -} +const ANALYZE_IMPORTS_PY = ` +import ast as _ast, json as _json + +def _sa_analyze_imports(source): + """Parse source and return JSON array of from-import info.""" + try: + tree = _ast.parse(source) + except SyntaxError: + return "[]" + result = [] + for node in _ast.walk(tree): + if isinstance(node, _ast.ImportFrom) and node.module: + result.append({ + "module": node.module, + "names": [ + {"name": a.name, "alias": a.asname} + for a in node.names + ], + "line": node.lineno, + }) + return _json.dumps(result) +`; + +let helperLoaded = false; /** - * Parses only the import prefix of the source using py-slang's parser - * and returns all FromImport nodes whose root module is "torch". + * Ensure the Python-side `_sa_analyze_imports` function is defined. + * Idempotent — only runs once. */ -export function detectTorchImports(source: string): TorchImportInfo[] { - const { importLines } = extractImportPrefix(source); - if (importLines.length === 0) return []; - - // Append a dummy statement so the grammar (import* statement*) is satisfied. - const fragment = importLines.join("\n") + "\n_ = 0\n"; - - let ast: StmtNS.FileInput; - try { - ast = parse(fragment); - } catch { - return []; - } - - const torchImports: TorchImportInfo[] = []; - - for (const stmt of ast.statements) { - if (!(stmt instanceof StmtNS.FromImport)) continue; - - const moduleName = stmt.module.lexeme; - const root = moduleName.split(".")[0]; - if (root !== "torch") continue; - - torchImports.push({ - module: moduleName, - names: stmt.names.map(n => ({ - name: n.name.lexeme, - alias: n.alias ? n.alias.lexeme : null, - })), - line: stmt.startToken.line, - }); - } +async function ensureHelper(pyodide: PyodideInterface): Promise { + if (helperLoaded) return; + await pyodide.runPythonAsync(ANALYZE_IMPORTS_PY); + helperLoaded = true; +} - return torchImports; +/** + * Reset the helper loaded state. Useful for testing when pyodide + * instances are recreated. + */ +export function resetHelperState(): void { + helperLoaded = false; } /** - * Returns the set of top-level module roots for all non-torch imports. - * These are modules that should be installed via micropip. + * Parses the source code using Python's `ast` module (via Pyodide) and + * returns all `from … import …` statements whose root module is "torch". */ -export function getNonTorchImportRoots(source: string): Set { - const { importLines } = extractImportPrefix(source); - if (importLines.length === 0) return new Set(); +export async function detectTorchImports( + pyodide: PyodideInterface, + source: string, +): Promise { + await ensureHelper(pyodide); + + const json = pyodide.runPython( + `_sa_analyze_imports(${JSON.stringify(source)})`, + ) as string; + + const allImports: TorchImportInfo[] = JSON.parse(json); + return allImports.filter(imp => imp.module.split(".")[0] === "torch"); +} - const fragment = importLines.join("\n") + "\n_ = 0\n"; +/** + * Returns the set of top-level module roots for all non-torch + * `from … import …` statements. These may need to be installed via micropip. + */ +export async function getNonTorchImportRoots( + pyodide: PyodideInterface, + source: string, +): Promise> { + await ensureHelper(pyodide); - let ast: StmtNS.FileInput; - try { - ast = parse(fragment); - } catch { - return new Set(); - } + const json = pyodide.runPython( + `_sa_analyze_imports(${JSON.stringify(source)})`, + ) as string; + const allImports: TorchImportInfo[] = JSON.parse(json); const roots = new Set(); - for (const stmt of ast.statements) { - if (!(stmt instanceof StmtNS.FromImport)) continue; - const root = stmt.module.lexeme.split(".")[0]; + for (const imp of allImports) { + const root = imp.module.split(".")[0]; if (root !== "torch") { roots.add(root); } @@ -136,11 +135,11 @@ function generateReplacement(imp: TorchImportInfo): string { * * Non-torch code is passed through unchanged. */ -export function rewriteTorchImports(source: string): { - code: string; - hasTorch: boolean; -} { - const imports = detectTorchImports(source); +export async function rewriteTorchImports( + pyodide: PyodideInterface, + source: string, +): Promise<{ code: string; hasTorch: boolean }> { + const imports = await detectTorchImports(pyodide, source); if (imports.length === 0) { return { code: source, hasTorch: false }; diff --git a/src/tests/import-analyzer.test.ts b/src/tests/import-analyzer.test.ts index 9f373718..cbc3e62b 100644 --- a/src/tests/import-analyzer.test.ts +++ b/src/tests/import-analyzer.test.ts @@ -1,29 +1,42 @@ +import { loadPyodide } from "pyodide"; +import type { PyodideInterface } from "pyodide"; import { detectTorchImports, getNonTorchImportRoots, rewriteTorchImports, + resetHelperState, } from "../pyodide/importAnalyzer"; +let pyodide: PyodideInterface; + +beforeAll(async () => { + resetHelperState(); + pyodide = await loadPyodide(); +}, 60_000); + // --------------------------------------------------------------------------- // detectTorchImports // --------------------------------------------------------------------------- describe("detectTorchImports", () => { - test("detects `from torch import tensor`", () => { - const result = detectTorchImports("from torch import tensor\nx = 1\n"); + test("detects `from torch import tensor`", async () => { + const result = await detectTorchImports(pyodide, "from torch import tensor\nx = 1\n"); expect(result).toHaveLength(1); expect(result[0].module).toBe("torch"); expect(result[0].names).toEqual([{ name: "tensor", alias: null }]); }); - test("detects `from torch.nn import Linear as L`", () => { - const result = detectTorchImports("from torch.nn import Linear as L\nx = 1\n"); + test("detects `from torch.nn import Linear as L`", async () => { + const result = await detectTorchImports(pyodide, "from torch.nn import Linear as L\nx = 1\n"); expect(result).toHaveLength(1); expect(result[0].module).toBe("torch.nn"); expect(result[0].names).toEqual([{ name: "Linear", alias: "L" }]); }); - test("detects multiple names", () => { - const result = detectTorchImports("from torch import tensor, zeros, ones\nx = 1\n"); + test("detects multiple names", async () => { + const result = await detectTorchImports( + pyodide, + "from torch import tensor, zeros, ones\nx = 1\n", + ); expect(result).toHaveLength(1); expect(result[0].names).toEqual([ { name: "tensor", alias: null }, @@ -32,26 +45,34 @@ describe("detectTorchImports", () => { ]); }); - test("detects multiple torch import statements", () => { + test("detects multiple torch import statements", async () => { const src = "from torch import tensor\nfrom torch.nn import Linear\nx = 1\n"; - const result = detectTorchImports(src); + const result = await detectTorchImports(pyodide, src); expect(result).toHaveLength(2); expect(result[0].module).toBe("torch"); expect(result[1].module).toBe("torch.nn"); }); - test("ignores non-torch imports", () => { - const result = detectTorchImports("from math import sqrt\nx = 1\n"); + test("ignores non-torch imports", async () => { + const result = await detectTorchImports(pyodide, "from math import sqrt\nx = 1\n"); + expect(result).toHaveLength(0); + }); + + test("returns empty array for syntax errors", async () => { + const result = await detectTorchImports(pyodide, "def (broken\n"); expect(result).toHaveLength(0); }); - test("returns empty for code with no imports", () => { - const result = detectTorchImports("x = 1\n"); + test("returns empty for code with no imports", async () => { + const result = await detectTorchImports(pyodide, "x = 1\n"); expect(result).toHaveLength(0); }); - test("works when body uses full Python syntax unsupported by py-slang", () => { - const result = detectTorchImports("from torch import tensor\nx = tensor([1,2]).tolist()\n"); + test("works with full Python syntax (method calls, list literals)", async () => { + const result = await detectTorchImports( + pyodide, + "from torch import tensor\nx = tensor([1,2]).tolist()\n", + ); expect(result).toHaveLength(1); expect(result[0].module).toBe("torch"); }); @@ -61,19 +82,19 @@ describe("detectTorchImports", () => { // getNonTorchImportRoots // --------------------------------------------------------------------------- describe("getNonTorchImportRoots", () => { - test("returns non-torch module roots", () => { + test("returns non-torch module roots", async () => { const src = "from math import sqrt\nfrom torch import tensor\nfrom numpy import array\nx = 1\n"; - const roots = getNonTorchImportRoots(src); + const roots = await getNonTorchImportRoots(pyodide, src); expect(roots).toEqual(new Set(["math", "numpy"])); }); - test("returns empty set when only torch imports exist", () => { - const roots = getNonTorchImportRoots("from torch import tensor\nx = 1\n"); + test("returns empty set when only torch imports exist", async () => { + const roots = await getNonTorchImportRoots(pyodide, "from torch import tensor\nx = 1\n"); expect(roots).toEqual(new Set()); }); - test("extracts root from dotted module name", () => { - const roots = getNonTorchImportRoots("from os.path import join\nx = 1\n"); + test("extracts root from dotted module name", async () => { + const roots = await getNonTorchImportRoots(pyodide, "from os.path import join\nx = 1\n"); expect(roots).toEqual(new Set(["os"])); }); }); @@ -82,16 +103,20 @@ describe("getNonTorchImportRoots", () => { // rewriteTorchImports // --------------------------------------------------------------------------- describe("rewriteTorchImports", () => { - test("rewrites `from torch import tensor`", () => { - const { code, hasTorch } = rewriteTorchImports("from torch import tensor\nx = tensor(1)\n"); + test("rewrites `from torch import tensor`", async () => { + const { code, hasTorch } = await rewriteTorchImports( + pyodide, + "from torch import tensor\nx = tensor(1)\n", + ); expect(hasTorch).toBe(true); expect(code).toContain("tensor = __sa_import_torch.tensor"); expect(code).not.toContain("from torch"); expect(code).toContain("x = tensor(1)"); }); - test("rewrites `from torch.nn import Linear as L`", () => { - const { code, hasTorch } = rewriteTorchImports( + test("rewrites `from torch.nn import Linear as L`", async () => { + const { code, hasTorch } = await rewriteTorchImports( + pyodide, "from torch.nn import Linear as L\nx = L(3, 2)\n", ); expect(hasTorch).toBe(true); @@ -99,36 +124,42 @@ describe("rewriteTorchImports", () => { expect(code).toContain("x = L(3, 2)"); }); - test("rewrites multiple names", () => { - const { code, hasTorch } = rewriteTorchImports("from torch import tensor, zeros\nx = 1\n"); + test("rewrites multiple names", async () => { + const { code, hasTorch } = await rewriteTorchImports( + pyodide, + "from torch import tensor, zeros\nx = 1\n", + ); expect(hasTorch).toBe(true); expect(code).toContain("tensor = __sa_import_torch.tensor"); expect(code).toContain("zeros = __sa_import_torch.zeros"); }); - test("leaves non-torch code unchanged", () => { + test("leaves non-torch code unchanged", async () => { const src = "x = 1\n"; - const { code, hasTorch } = rewriteTorchImports(src); + const { code, hasTorch } = await rewriteTorchImports(pyodide, src); expect(hasTorch).toBe(false); expect(code).toBe(src); }); - test("preserves non-torch imports", () => { + test("preserves non-torch imports", async () => { const src = "from math import sqrt\nfrom torch import tensor\nx = sqrt(tensor(4))\n"; - const { code, hasTorch } = rewriteTorchImports(src); + const { code, hasTorch } = await rewriteTorchImports(pyodide, src); expect(hasTorch).toBe(true); expect(code).toContain("from math import sqrt"); expect(code).toContain("tensor = __sa_import_torch.tensor"); }); - test("rewrites deeply nested module path", () => { - const { code } = rewriteTorchImports("from torch.nn.functional import relu\nx = relu(1)\n"); + test("rewrites deeply nested module path", async () => { + const { code } = await rewriteTorchImports( + pyodide, + "from torch.nn.functional import relu\nx = relu(1)\n", + ); expect(code).toContain("relu = __sa_import_torch.nn.functional.relu"); }); - test("handles full Python body that py-slang cannot parse", () => { + test("handles full Python body that py-slang cannot parse", async () => { const src = "from torch import tensor\nx = tensor([1, 2, 3]).tolist()\nprint(x)\n"; - const { code, hasTorch } = rewriteTorchImports(src); + const { code, hasTorch } = await rewriteTorchImports(pyodide, src); expect(hasTorch).toBe(true); expect(code).toContain("tensor = __sa_import_torch.tensor"); expect(code).toContain("x = tensor([1, 2, 3]).tolist()"); diff --git a/src/tests/pyodide-torch.test.ts b/src/tests/pyodide-torch.test.ts index 5c497b85..9160f50f 100644 --- a/src/tests/pyodide-torch.test.ts +++ b/src/tests/pyodide-torch.test.ts @@ -10,11 +10,12 @@ import { loadPyodide } from "pyodide"; import type { PyodideInterface } from "pyodide"; import * as torch from "torch"; import bridgeCode from "../pyodide/bridge.py"; -import { rewriteTorchImports } from "../pyodide/importAnalyzer"; +import { rewriteTorchImports, resetHelperState } from "../pyodide/importAnalyzer"; let pyodide: PyodideInterface; beforeAll(async () => { + resetHelperState(); pyodide = await loadPyodide({ fullStdLib: true }); // Set up torch in pyodide (mirrors what loadTorch does) @@ -24,7 +25,7 @@ beforeAll(async () => { }, 60_000); async function runTorchCode(source: string): Promise { - const { code } = rewriteTorchImports(source); + const { code } = await rewriteTorchImports(pyodide, source); return pyodide.runPythonAsync(code); } From ad57b0eeb2df3ddc5d521cb3cab2b52876b86e56 Mon Sep 17 00:00:00 2001 From: Hua Zhi Vee Date: Sun, 29 Mar 2026 13:09:25 +0800 Subject: [PATCH 3/9] Add Pyodide Evaluators to all slang --- rollup.config.js | 50 ++++++++++++++++++++++++++++-- src/parser/python-grammar.ts | 3 +- src/pyodide/PyodideEvaluator.ts | 27 ++++++++++++++-- src/pyodide/evaluators/chapter1.ts | 8 +++++ src/pyodide/evaluators/chapter2.ts | 8 +++++ src/pyodide/evaluators/chapter3.ts | 8 +++++ src/pyodide/evaluators/chapter4.ts | 8 +++++ src/pyodide/evaluators/full.ts | 10 ++++++ src/pyodide/index.ts | 1 + 9 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 src/pyodide/evaluators/chapter1.ts create mode 100644 src/pyodide/evaluators/chapter2.ts create mode 100644 src/pyodide/evaluators/chapter3.ts create mode 100644 src/pyodide/evaluators/chapter4.ts create mode 100644 src/pyodide/evaluators/full.ts diff --git a/rollup.config.js b/rollup.config.js index 5562ecfd..f419b8d1 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -52,11 +52,55 @@ const config = [ plugins: plugins(), }, { - input: "src/pyodide/index.ts", + input: "src/pyodide/evaluators/full.ts", output: { - file: "dist/pyodide-evaluator.cjs", + file: "dist/pyodide-evaluator-full.cjs", format: "cjs", - name: "PyodideEvaluator", + name: "PyodideEvaluatorFull", + sourcemap: true, + inlineDynamicImports: true, + }, + plugins: plugins(), + }, + { + input: "src/pyodide/evaluators/chapter1.ts", + output: { + file: "dist/pyodide-evaluator-1.cjs", + format: "cjs", + name: "PyodideEvaluator1", + sourcemap: true, + inlineDynamicImports: true, + }, + plugins: plugins(), + }, + { + input: "src/pyodide/evaluators/chapter2.ts", + output: { + file: "dist/pyodide-evaluator-2.cjs", + format: "cjs", + name: "PyodideEvaluator2", + sourcemap: true, + inlineDynamicImports: true, + }, + plugins: plugins(), + }, + { + input: "src/pyodide/evaluators/chapter3.ts", + output: { + file: "dist/pyodide-evaluator-3.cjs", + format: "cjs", + name: "PyodideEvaluator3", + sourcemap: true, + inlineDynamicImports: true, + }, + plugins: plugins(), + }, + { + input: "src/pyodide/evaluators/chapter4.ts", + output: { + file: "dist/pyodide-evaluator-4.cjs", + format: "cjs", + name: "PyodideEvaluator4", sourcemap: true, inlineDynamicImports: true, }, diff --git a/src/parser/python-grammar.ts b/src/parser/python-grammar.ts index 0c24a018..e48a52a0 100644 --- a/src/parser/python-grammar.ts +++ b/src/parser/python-grammar.ts @@ -1,10 +1,11 @@ +// @ts-nocheck // Generated automatically by nearley, version 2.20.1 // http://github.com/Hardmath123/nearley function id(x) { return x[0]; } -import { StmtNS, ExprNS } from "../ast-types"; +import { ExprNS, StmtNS } from "../ast-types"; import pythonLexer from "./lexer"; import { toAstToken } from "./token-bridge"; diff --git a/src/pyodide/PyodideEvaluator.ts b/src/pyodide/PyodideEvaluator.ts index aa71eb6e..4660e4ea 100644 --- a/src/pyodide/PyodideEvaluator.ts +++ b/src/pyodide/PyodideEvaluator.ts @@ -1,11 +1,13 @@ import { BasicEvaluator, IRunnerPlugin } from "@sourceacademy/conductor/runner"; import type { PyodideInterface } from "pyodide"; +import { parse } from "../parser/parser-adapter"; +import { analyze } from "../resolver/analysis"; +import { getNonTorchImportRoots, rewriteTorchImports } from "./importAnalyzer"; import { loadPyodideGeneric } from "./loadPyodide"; import { loadTorch } from "./loadTorch"; -import { rewriteTorchImports, getNonTorchImportRoots } from "./importAnalyzer"; -export default class PyodideEvaluator extends BasicEvaluator { - private pyodide: Promise; +export default abstract class PyodideEvaluator extends BasicEvaluator { + protected pyodide: Promise; private torchLoaded = false; constructor(conductor: IRunnerPlugin) { @@ -21,7 +23,11 @@ export default class PyodideEvaluator extends BasicEvaluator { }); } + protected abstract validateChunk(_chunk: string): void; + async evaluateChunk(chunk: string): Promise { + this.validateChunk(chunk); + const pyodide = await this.pyodide; // --- Use Python's ast module (via Pyodide) to detect and rewrite torch imports --- @@ -57,3 +63,18 @@ if missing: this.conductor.sendOutput(output); } } + +export class ChapterPyodideEvaluator extends PyodideEvaluator { + private chapter: number; + + constructor(conductor: IRunnerPlugin, chapter: number) { + super(conductor); + this.chapter = chapter; + } + + protected validateChunk(chunk: string): void { + const script = chunk + "\n"; + const ast = parse(script); + analyze(ast, script, this.chapter); + } +} diff --git a/src/pyodide/evaluators/chapter1.ts b/src/pyodide/evaluators/chapter1.ts new file mode 100644 index 00000000..95b576cf --- /dev/null +++ b/src/pyodide/evaluators/chapter1.ts @@ -0,0 +1,8 @@ +import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import { ChapterPyodideEvaluator } from "../PyodideEvaluator"; + +export default class PyodideEvaluator1 extends ChapterPyodideEvaluator { + constructor(conductor: IRunnerPlugin) { + super(conductor, 1); + } +} diff --git a/src/pyodide/evaluators/chapter2.ts b/src/pyodide/evaluators/chapter2.ts new file mode 100644 index 00000000..d87a0470 --- /dev/null +++ b/src/pyodide/evaluators/chapter2.ts @@ -0,0 +1,8 @@ +import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import { ChapterPyodideEvaluator } from "../PyodideEvaluator"; + +export default class PyodideEvaluator2 extends ChapterPyodideEvaluator { + constructor(conductor: IRunnerPlugin) { + super(conductor, 2); + } +} diff --git a/src/pyodide/evaluators/chapter3.ts b/src/pyodide/evaluators/chapter3.ts new file mode 100644 index 00000000..9e812871 --- /dev/null +++ b/src/pyodide/evaluators/chapter3.ts @@ -0,0 +1,8 @@ +import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import { ChapterPyodideEvaluator } from "../PyodideEvaluator"; + +export default class PyodideEvaluator3 extends ChapterPyodideEvaluator { + constructor(conductor: IRunnerPlugin) { + super(conductor, 3); + } +} diff --git a/src/pyodide/evaluators/chapter4.ts b/src/pyodide/evaluators/chapter4.ts new file mode 100644 index 00000000..83358a70 --- /dev/null +++ b/src/pyodide/evaluators/chapter4.ts @@ -0,0 +1,8 @@ +import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import { ChapterPyodideEvaluator } from "../PyodideEvaluator"; + +export default class PyodideEvaluator4 extends ChapterPyodideEvaluator { + constructor(conductor: IRunnerPlugin) { + super(conductor, 4); + } +} diff --git a/src/pyodide/evaluators/full.ts b/src/pyodide/evaluators/full.ts new file mode 100644 index 00000000..534fa0e3 --- /dev/null +++ b/src/pyodide/evaluators/full.ts @@ -0,0 +1,10 @@ +import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import PyodideEvaluator from "../PyodideEvaluator"; + +export default class PyodideEvaluatorFull extends PyodideEvaluator { + constructor(conductor: IRunnerPlugin) { + super(conductor); + } + + protected validateChunk(_chunk: string): void {} +} diff --git a/src/pyodide/index.ts b/src/pyodide/index.ts index 40c4b0e9..28a1afeb 100644 --- a/src/pyodide/index.ts +++ b/src/pyodide/index.ts @@ -1,2 +1,3 @@ export { default as PyodideEvaluator } from "./PyodideEvaluator"; +export { ChapterPyodideEvaluator } from "./PyodideEvaluator"; export { detectTorchImports, getNonTorchImportRoots, rewriteTorchImports } from "./importAnalyzer"; From 68a8ad7aa457a3ac0d6ed0324549ded96c3763fe Mon Sep 17 00:00:00 2001 From: Hua Zhi Vee Date: Sun, 29 Mar 2026 14:02:04 +0800 Subject: [PATCH 4/9] Add import format and change build format to iife --- rollup.config.js | 20 +++---- src/pyodide/PyodideEvaluator.ts | 10 +++- src/pyodide/evaluators/chapter1.ts | 6 +- src/pyodide/evaluators/chapter2.ts | 6 +- src/pyodide/evaluators/chapter3.ts | 6 +- src/pyodide/evaluators/chapter4.ts | 6 +- src/pyodide/evaluators/full.ts | 6 +- src/pyodide/importAnalyzer.ts | 47 ++++++++++++---- src/tests/import-analyzer.test.ts | 89 ++++++++++++++++++++++++++++++ 9 files changed, 164 insertions(+), 32 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index f419b8d1..e03f9dbb 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -54,8 +54,8 @@ const config = [ { input: "src/pyodide/evaluators/full.ts", output: { - file: "dist/pyodide-evaluator-full.cjs", - format: "cjs", + file: "dist/pyodide-evaluator-full.js", + format: "iife", name: "PyodideEvaluatorFull", sourcemap: true, inlineDynamicImports: true, @@ -65,8 +65,8 @@ const config = [ { input: "src/pyodide/evaluators/chapter1.ts", output: { - file: "dist/pyodide-evaluator-1.cjs", - format: "cjs", + file: "dist/pyodide-evaluator-1.js", + format: "iife", name: "PyodideEvaluator1", sourcemap: true, inlineDynamicImports: true, @@ -76,8 +76,8 @@ const config = [ { input: "src/pyodide/evaluators/chapter2.ts", output: { - file: "dist/pyodide-evaluator-2.cjs", - format: "cjs", + file: "dist/pyodide-evaluator-2.js", + format: "iife", name: "PyodideEvaluator2", sourcemap: true, inlineDynamicImports: true, @@ -87,8 +87,8 @@ const config = [ { input: "src/pyodide/evaluators/chapter3.ts", output: { - file: "dist/pyodide-evaluator-3.cjs", - format: "cjs", + file: "dist/pyodide-evaluator-3.js", + format: "iife", name: "PyodideEvaluator3", sourcemap: true, inlineDynamicImports: true, @@ -98,8 +98,8 @@ const config = [ { input: "src/pyodide/evaluators/chapter4.ts", output: { - file: "dist/pyodide-evaluator-4.cjs", - format: "cjs", + file: "dist/pyodide-evaluator-4.js", + format: "iife", name: "PyodideEvaluator4", sourcemap: true, inlineDynamicImports: true, diff --git a/src/pyodide/PyodideEvaluator.ts b/src/pyodide/PyodideEvaluator.ts index 4660e4ea..0c6a9f81 100644 --- a/src/pyodide/PyodideEvaluator.ts +++ b/src/pyodide/PyodideEvaluator.ts @@ -1,3 +1,4 @@ +import { ConductorError } from "@sourceacademy/conductor/common"; import { BasicEvaluator, IRunnerPlugin } from "@sourceacademy/conductor/runner"; import type { PyodideInterface } from "pyodide"; import { parse } from "../parser/parser-adapter"; @@ -59,8 +60,13 @@ if missing: } // --- Execute the (possibly rewritten) code --- - const output = await pyodide.runPythonAsync(code); - this.conductor.sendOutput(output); + try { + const output = await pyodide.runPythonAsync(code); + this.conductor.sendResult(output); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + this.conductor.sendError(new ConductorError(message)); + } } } diff --git a/src/pyodide/evaluators/chapter1.ts b/src/pyodide/evaluators/chapter1.ts index 95b576cf..fcee3ecd 100644 --- a/src/pyodide/evaluators/chapter1.ts +++ b/src/pyodide/evaluators/chapter1.ts @@ -1,8 +1,10 @@ -import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import { initialise, IRunnerPlugin } from "@sourceacademy/conductor/runner"; import { ChapterPyodideEvaluator } from "../PyodideEvaluator"; -export default class PyodideEvaluator1 extends ChapterPyodideEvaluator { +class PyodideEvaluator1 extends ChapterPyodideEvaluator { constructor(conductor: IRunnerPlugin) { super(conductor, 1); } } + +initialise(PyodideEvaluator1); diff --git a/src/pyodide/evaluators/chapter2.ts b/src/pyodide/evaluators/chapter2.ts index d87a0470..461c7dd6 100644 --- a/src/pyodide/evaluators/chapter2.ts +++ b/src/pyodide/evaluators/chapter2.ts @@ -1,8 +1,10 @@ -import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import { initialise, IRunnerPlugin } from "@sourceacademy/conductor/runner"; import { ChapterPyodideEvaluator } from "../PyodideEvaluator"; -export default class PyodideEvaluator2 extends ChapterPyodideEvaluator { +class PyodideEvaluator2 extends ChapterPyodideEvaluator { constructor(conductor: IRunnerPlugin) { super(conductor, 2); } } + +initialise(PyodideEvaluator2); diff --git a/src/pyodide/evaluators/chapter3.ts b/src/pyodide/evaluators/chapter3.ts index 9e812871..b39ad204 100644 --- a/src/pyodide/evaluators/chapter3.ts +++ b/src/pyodide/evaluators/chapter3.ts @@ -1,8 +1,10 @@ -import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import { initialise, IRunnerPlugin } from "@sourceacademy/conductor/runner"; import { ChapterPyodideEvaluator } from "../PyodideEvaluator"; -export default class PyodideEvaluator3 extends ChapterPyodideEvaluator { +class PyodideEvaluator3 extends ChapterPyodideEvaluator { constructor(conductor: IRunnerPlugin) { super(conductor, 3); } } + +initialise(PyodideEvaluator3); diff --git a/src/pyodide/evaluators/chapter4.ts b/src/pyodide/evaluators/chapter4.ts index 83358a70..741017f5 100644 --- a/src/pyodide/evaluators/chapter4.ts +++ b/src/pyodide/evaluators/chapter4.ts @@ -1,8 +1,10 @@ -import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import { initialise, IRunnerPlugin } from "@sourceacademy/conductor/runner"; import { ChapterPyodideEvaluator } from "../PyodideEvaluator"; -export default class PyodideEvaluator4 extends ChapterPyodideEvaluator { +class PyodideEvaluator4 extends ChapterPyodideEvaluator { constructor(conductor: IRunnerPlugin) { super(conductor, 4); } } + +initialise(PyodideEvaluator4); diff --git a/src/pyodide/evaluators/full.ts b/src/pyodide/evaluators/full.ts index 534fa0e3..f7d44048 100644 --- a/src/pyodide/evaluators/full.ts +++ b/src/pyodide/evaluators/full.ts @@ -1,10 +1,12 @@ -import { IRunnerPlugin } from "@sourceacademy/conductor/runner"; +import { initialise, IRunnerPlugin } from "@sourceacademy/conductor/runner"; import PyodideEvaluator from "../PyodideEvaluator"; -export default class PyodideEvaluatorFull extends PyodideEvaluator { +class PyodideEvaluatorFull extends PyodideEvaluator { constructor(conductor: IRunnerPlugin) { super(conductor); } protected validateChunk(_chunk: string): void {} } + +initialise(PyodideEvaluatorFull); diff --git a/src/pyodide/importAnalyzer.ts b/src/pyodide/importAnalyzer.ts index ad6a5b1a..905a4e9b 100644 --- a/src/pyodide/importAnalyzer.ts +++ b/src/pyodide/importAnalyzer.ts @@ -10,6 +10,8 @@ import type { PyodideInterface } from "pyodide"; export interface TorchImportInfo { + /** "import" for bare `import torch`, "from" for `from torch import ...` */ + type: "import" | "from"; /** Full module path, e.g. "torch" or "torch.nn" */ module: string; /** Imported names with optional aliases */ @@ -26,7 +28,7 @@ const ANALYZE_IMPORTS_PY = ` import ast as _ast, json as _json def _sa_analyze_imports(source): - """Parse source and return JSON array of from-import info.""" + """Parse source and return JSON array of import info (both 'import' and 'from ... import').""" try: tree = _ast.parse(source) except SyntaxError: @@ -35,6 +37,7 @@ def _sa_analyze_imports(source): for node in _ast.walk(tree): if isinstance(node, _ast.ImportFrom) and node.module: result.append({ + "type": "from", "module": node.module, "names": [ {"name": a.name, "alias": a.asname} @@ -42,6 +45,14 @@ def _sa_analyze_imports(source): ], "line": node.lineno, }) + elif isinstance(node, _ast.Import): + for a in node.names: + result.append({ + "type": "import", + "module": a.name, + "names": [{"name": a.name, "alias": a.asname}], + "line": node.lineno, + }) return _json.dumps(result) `; @@ -75,9 +86,7 @@ export async function detectTorchImports( ): Promise { await ensureHelper(pyodide); - const json = pyodide.runPython( - `_sa_analyze_imports(${JSON.stringify(source)})`, - ) as string; + const json = pyodide.runPython(`_sa_analyze_imports(${JSON.stringify(source)})`) as string; const allImports: TorchImportInfo[] = JSON.parse(json); return allImports.filter(imp => imp.module.split(".")[0] === "torch"); @@ -93,9 +102,7 @@ export async function getNonTorchImportRoots( ): Promise> { await ensureHelper(pyodide); - const json = pyodide.runPython( - `_sa_analyze_imports(${JSON.stringify(source)})`, - ) as string; + const json = pyodide.runPython(`_sa_analyze_imports(${JSON.stringify(source)})`) as string; const allImports: TorchImportInfo[] = JSON.parse(json); const roots = new Set(); @@ -111,13 +118,33 @@ export async function getNonTorchImportRoots( /** * Generates Python assignment code that replaces a torch import statement. * - * Example: + * Examples: + * import torch → torch = __sa_import_torch + * import torch as t → t = __sa_import_torch + * import torch.nn → torch = __sa_import_torch * from torch.nn import Linear as L, Conv2d - * → L = __sa_import_torch.nn.Linear - * Conv2d = __sa_import_torch.nn.Conv2d + * → L = __sa_import_torch.nn.Linear + * Conv2d = __sa_import_torch.nn.Conv2d */ function generateReplacement(imp: TorchImportInfo): string { const injected = "__sa_import_torch"; + + if (imp.type === "import") { + // `import torch` or `import torch as t` or `import torch.nn` + const alias = imp.names[0].alias; + if (alias) { + // import torch as t → t = __sa_import_torch + // import torch.nn as nn → nn = __sa_import_torch.nn + const subparts = imp.module.split(".").slice(1); + const rhs = subparts.length > 0 ? `${injected}.${subparts.join(".")}` : injected; + return `${alias} = ${rhs}`; + } + // import torch → torch = __sa_import_torch + // import torch.nn → torch = __sa_import_torch (Python binds the top-level name) + return `torch = ${injected}`; + } + + // from torch.nn import Linear as L, Conv2d const subparts = imp.module.split(".").slice(1); const base = subparts.length > 0 ? `${injected}.${subparts.join(".")}` : injected; diff --git a/src/tests/import-analyzer.test.ts b/src/tests/import-analyzer.test.ts index cbc3e62b..220b88ec 100644 --- a/src/tests/import-analyzer.test.ts +++ b/src/tests/import-analyzer.test.ts @@ -53,6 +53,37 @@ describe("detectTorchImports", () => { expect(result[1].module).toBe("torch.nn"); }); + test("detects bare `import torch`", async () => { + const result = await detectTorchImports(pyodide, "import torch\nx = 1\n"); + expect(result).toHaveLength(1); + expect(result[0].type).toBe("import"); + expect(result[0].module).toBe("torch"); + expect(result[0].names).toEqual([{ name: "torch", alias: null }]); + }); + + test("detects `import torch as t`", async () => { + const result = await detectTorchImports(pyodide, "import torch as t\nx = 1\n"); + expect(result).toHaveLength(1); + expect(result[0].type).toBe("import"); + expect(result[0].module).toBe("torch"); + expect(result[0].names).toEqual([{ name: "torch", alias: "t" }]); + }); + + test("detects `import torch.nn`", async () => { + const result = await detectTorchImports(pyodide, "import torch.nn\nx = 1\n"); + expect(result).toHaveLength(1); + expect(result[0].type).toBe("import"); + expect(result[0].module).toBe("torch.nn"); + }); + + test("detects mix of bare import and from-import", async () => { + const src = "import torch\nfrom torch.nn import Linear\nx = 1\n"; + const result = await detectTorchImports(pyodide, src); + expect(result).toHaveLength(2); + const types = result.map(r => r.type).sort(); + expect(types).toEqual(["from", "import"]); + }); + test("ignores non-torch imports", async () => { const result = await detectTorchImports(pyodide, "from math import sqrt\nx = 1\n"); expect(result).toHaveLength(0); @@ -97,6 +128,12 @@ describe("getNonTorchImportRoots", () => { const roots = await getNonTorchImportRoots(pyodide, "from os.path import join\nx = 1\n"); expect(roots).toEqual(new Set(["os"])); }); + + test("returns non-torch roots for bare import statements", async () => { + const src = "import numpy\nimport torch\nx = 1\n"; + const roots = await getNonTorchImportRoots(pyodide, src); + expect(roots).toEqual(new Set(["numpy"])); + }); }); // --------------------------------------------------------------------------- @@ -157,6 +194,58 @@ describe("rewriteTorchImports", () => { expect(code).toContain("relu = __sa_import_torch.nn.functional.relu"); }); + test("rewrites bare `import torch`", async () => { + const { code, hasTorch } = await rewriteTorchImports( + pyodide, + "import torch\nx = torch.tensor([1, 2, 3])\n", + ); + expect(hasTorch).toBe(true); + expect(code).toContain("torch = __sa_import_torch"); + expect(code).not.toContain("import torch"); + expect(code).toContain("x = torch.tensor([1, 2, 3])"); + }); + + test("rewrites `import torch as t`", async () => { + const { code, hasTorch } = await rewriteTorchImports( + pyodide, + "import torch as t\nx = t.tensor([1])\n", + ); + expect(hasTorch).toBe(true); + expect(code).toContain("t = __sa_import_torch"); + expect(code).toContain("x = t.tensor([1])"); + }); + + test("rewrites `import torch.nn as nn`", async () => { + const { code, hasTorch } = await rewriteTorchImports( + pyodide, + "import torch.nn as nn\nx = nn.Linear(3, 2)\n", + ); + expect(hasTorch).toBe(true); + expect(code).toContain("nn = __sa_import_torch.nn"); + expect(code).toContain("x = nn.Linear(3, 2)"); + }); + + test("rewrites `import torch.nn` (no alias)", async () => { + const { code, hasTorch } = await rewriteTorchImports( + pyodide, + "import torch.nn\nx = torch.nn.Linear(3, 2)\n", + ); + expect(hasTorch).toBe(true); + expect(code).toContain("torch = __sa_import_torch"); + expect(code).toContain("x = torch.nn.Linear(3, 2)"); + }); + + test("rewrites mix of bare import and from-import", async () => { + const src = + "import torch\nfrom torch.nn import Linear\nx = torch.tensor(1)\ny = Linear(3, 2)\n"; + const { code, hasTorch } = await rewriteTorchImports(pyodide, src); + expect(hasTorch).toBe(true); + expect(code).toContain("torch = __sa_import_torch"); + expect(code).toContain("Linear = __sa_import_torch.nn.Linear"); + expect(code).not.toMatch(/^import torch$/m); + expect(code).not.toContain("from torch"); + }); + test("handles full Python body that py-slang cannot parse", async () => { const src = "from torch import tensor\nx = tensor([1, 2, 3]).tolist()\nprint(x)\n"; const { code, hasTorch } = await rewriteTorchImports(pyodide, src); From 40ad9bde13bbec64b1612749f7b24895ec385ee4 Mon Sep 17 00:00:00 2001 From: Hua Zhi Vee Date: Sun, 29 Mar 2026 19:45:08 +0800 Subject: [PATCH 5/9] Update torch and bridge --- package.json | 2 +- src/pyodide/bridge.py | 25 +++++++++++-------------- yarn.lock | 8 ++++---- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 4f2beed6..7554e043 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "moo": "^0.5.2", "nearley": "^2.20.1", "pyodide": "^0.29.3", - "torch": "https://pkg.pr.new/veehz/torch@297abf6", + "torch": "https://pkg.pr.new/veehz/torch@687fc81", "wabt": "^1.0.37" } } diff --git a/src/pyodide/bridge.py b/src/pyodide/bridge.py index 6fa27efe..eea4ae73 100644 --- a/src/pyodide/bridge.py +++ b/src/pyodide/bridge.py @@ -184,8 +184,7 @@ def transpose(self, dim0, dim1): return Tensor(self._js.transpose(dim0, dim1)) def flatten(self, start_dim=0, end_dim=-1): - n = self.numel() - return self.reshape([n]) + return Tensor(self._js.flatten(start_dim, end_dim)) # ------------------------------------------------------------------ # Reductions — default (no dim) sums all elements, matching PyTorch @@ -387,7 +386,7 @@ def __setattr__(self, name, value): object.__setattr__(self, name, value) return - if isinstance(value, Tensor) and value.requires_grad: + if isinstance(value, Parameter): params[name] = value elif isinstance(value, (Module, _NNModule)): modules[name] = value @@ -674,17 +673,15 @@ def is_grad_enabled(self): return bool(js_torch.is_grad_enabled()) def cat(self, tensors, dim=0): - """Concatenate tensors along dim. NOTE: gradient is not tracked.""" - if dim != 0: - raise NotImplementedError("torch.cat only supports dim=0 in this bridge") - result = [] - for t in tensors: - data = t.tolist() - if isinstance(data, list): - result.extend(data) - else: - result.append(data) - return Tensor(result) + if isinstance(tensors, Tensor): + tensors = [tensors] + return Tensor(js_torch.cat(to_js([t._js for t in tensors]), dim)) + + def concatenate(self, tensors, dim=0): + return self.cat(tensors, dim) + + def concat(self, tensors, dim=0): + return self.cat(tensors, dim) def Size(self, shape): return list(shape) diff --git a/yarn.lock b/yarn.lock index d5e1b01b..198caaa0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4306,7 +4306,7 @@ __metadata: prettier: "npm:^3.8.1" pyodide: "npm:^0.29.3" rollup: "npm:^4.59.0" - torch: "https://pkg.pr.new/veehz/torch@297abf6" + torch: "https://pkg.pr.new/veehz/torch@687fc81" ts-jest: "npm:^29.0.5" ts-node: "npm:^10.9.1" tslib: "npm:^2.8.1" @@ -4818,10 +4818,10 @@ __metadata: languageName: node linkType: hard -"torch@https://pkg.pr.new/veehz/torch@297abf6": +"torch@https://pkg.pr.new/veehz/torch@687fc81": version: 0.1.0 - resolution: "torch@https://pkg.pr.new/veehz/torch@297abf6" - checksum: 10c0/bde381a0d266c845970fd34b3d27048709b60fd9e64a2d96f3080eb9c3a4bb1433bcc0ea1bc1123fbb31883ad2623c031ede94319e514f54398b9752c290c6d4 + resolution: "torch@https://pkg.pr.new/veehz/torch@687fc81" + checksum: 10c0/84fd0d0fae8f5e67dccfa01d429755ef5db147340544f697a14ce32332a31b5515b84aa47ac849c327f1026c8c4ff29a7af3c60bd5c1db541f49ddfa9f529185 languageName: node linkType: hard From c030c3e56a688730e2ccadd4fff8a78be95a7879 Mon Sep 17 00:00:00 2001 From: Hua Zhi Vee Date: Tue, 5 May 2026 23:00:16 +0800 Subject: [PATCH 6/9] Use @sourceacademy/torch instead of torch --- package.json | 2 +- src/conductor/index.ts | 18 ++++++++---------- src/pyodide/loadTorch.ts | 2 +- src/tests/pyodide-torch.test.ts | 2 +- yarn.lock | 16 ++++++++-------- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 6478ed3f..6896f809 100644 --- a/package.json +++ b/package.json @@ -60,13 +60,13 @@ }, "dependencies": { "@sourceacademy/conductor": "^0.3.0", + "@sourceacademy/torch": "^0.1.0", "@sourceacademy/wasm-util": "^1.0.6", "fast-levenshtein": "^3.0.0", "mathjs": "^14.9.1", "moo": "^0.5.2", "nearley": "^2.20.1", "pyodide": "^0.29.3", - "torch": "https://pkg.pr.new/veehz/torch@687fc81", "wabt": "^1.0.37" } } diff --git a/src/conductor/index.ts b/src/conductor/index.ts index 57ad6655..fd0e0316 100644 --- a/src/conductor/index.ts +++ b/src/conductor/index.ts @@ -1,3 +1,10 @@ +export { + PyodideEvaluator1, + PyodideEvaluator2, + PyodideEvaluator3, + PyodideEvaluator4, + PyodideEvaluatorFull, +} from "../pyodide/PyodideEvaluator"; export { PyCseEvaluator1, PyCseEvaluator2, @@ -5,14 +12,5 @@ export { PyCseEvaluator4, } from "./PyCseEvaluator"; export { PySvmlEvaluator } from "./PySvmlEvaluator"; -export { PyWasmEvaluator } from "./PyWasmEvaluator"; export { PySvmlSinterEvaluator } from "./PySvmlSinterEvaluator"; -export { - PyodideEvaluator1, - PyodideEvaluator2, - PyodideEvaluator3, - PyodideEvaluator4, - PyodideEvaluatorFull, -} from "../pyodide/PyodideEvaluator"; - - +export { PyWasmEvaluator } from "./PyWasmEvaluator"; diff --git a/src/pyodide/loadTorch.ts b/src/pyodide/loadTorch.ts index d7421fa3..f7ba47f4 100644 --- a/src/pyodide/loadTorch.ts +++ b/src/pyodide/loadTorch.ts @@ -1,5 +1,5 @@ import type { PyodideInterface } from "pyodide"; -import * as torch from "torch"; +import * as torch from "@sourceacademy/torch"; import bridgeCode from "./bridge.py"; /** diff --git a/src/tests/pyodide-torch.test.ts b/src/tests/pyodide-torch.test.ts index 9160f50f..1819a065 100644 --- a/src/tests/pyodide-torch.test.ts +++ b/src/tests/pyodide-torch.test.ts @@ -8,7 +8,7 @@ import { loadPyodide } from "pyodide"; import type { PyodideInterface } from "pyodide"; -import * as torch from "torch"; +import * as torch from "@sourceacademy/torch"; import bridgeCode from "../pyodide/bridge.py"; import { rewriteTorchImports, resetHelperState } from "../pyodide/importAnalyzer"; diff --git a/yarn.lock b/yarn.lock index 476bc2d6..e6b275d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1643,6 +1643,13 @@ __metadata: languageName: node linkType: hard +"@sourceacademy/torch@npm:^0.1.0": + version: 0.1.0 + resolution: "@sourceacademy/torch@npm:0.1.0" + checksum: 10c0/bc6408d374a23080d3581ac06edbfd02f94215a9d2c27c38913a7b2ef99d5eadcec5f8b496dba4fac44341a216bded3412622f5b66152253373d969cdbbf7431 + languageName: node + linkType: hard + "@sourceacademy/wasm-util@npm:^1.0.6": version: 1.0.6 resolution: "@sourceacademy/wasm-util@npm:1.0.6" @@ -4775,6 +4782,7 @@ __metadata: "@rollup/plugin-typescript": "npm:^12.1.2" "@rollup/plugin-wasm": "npm:^6.2.2" "@sourceacademy/conductor": "npm:^0.3.0" + "@sourceacademy/torch": "npm:^0.1.0" "@sourceacademy/wasm-util": "npm:^1.0.6" "@types/fast-levenshtein": "npm:^0.0.4" "@types/jest": "npm:^29.5.14" @@ -4794,7 +4802,6 @@ __metadata: pyodide: "npm:^0.29.3" rollup: "npm:^4.59.0" rollup-plugin-polyfill-node: "npm:^0.13.0" - torch: "https://pkg.pr.new/veehz/torch@687fc81" ts-jest: "npm:^29.0.5" tslib: "npm:^2.8.1" tsx: "npm:^4.19.0" @@ -5342,13 +5349,6 @@ __metadata: languageName: node linkType: hard -"torch@https://pkg.pr.new/veehz/torch@687fc81": - version: 0.1.0 - resolution: "torch@https://pkg.pr.new/veehz/torch@687fc81" - checksum: 10c0/84fd0d0fae8f5e67dccfa01d429755ef5db147340544f697a14ce32332a31b5515b84aa47ac849c327f1026c8c4ff29a7af3c60bd5c1db541f49ddfa9f529185 - languageName: node - linkType: hard - "ts-api-utils@npm:^2.4.0": version: 2.5.0 resolution: "ts-api-utils@npm:2.5.0" From cf388d2a893fb454514c88bcd2e8941536099c9c Mon Sep 17 00:00:00 2001 From: Hua Zhi Vee Date: Tue, 5 May 2026 23:12:39 +0800 Subject: [PATCH 7/9] fix: allow inlineDynamicImports for Pyodide + torch --- rollup.config.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rollup.config.mjs b/rollup.config.mjs index 37cf585c..e4288605 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -60,6 +60,7 @@ const config = [ format: "iife", name: "PySlangWorker", sourcemap: true, + inlineDynamicImports: true, }, plugins: plugins(), }, @@ -73,6 +74,7 @@ const config = [ format: "cjs", exports: "default", sourcemap: true, + inlineDynamicImports: true, }, plugins: plugins(), }, From 2c0d79a8d589072deea409d8f07ff10a05c45c04 Mon Sep 17 00:00:00 2001 From: Hua Zhi Vee Date: Tue, 5 May 2026 23:31:54 +0800 Subject: [PATCH 8/9] fix: add jest to test utils --- src/tests/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests/utils.ts b/src/tests/utils.ts index 2078bc0e..7a727caa 100644 --- a/src/tests/utils.ts +++ b/src/tests/utils.ts @@ -1,3 +1,4 @@ +import { jest } from "@jest/globals"; import { ConductorError, ErrorType } from "@sourceacademy/conductor/common"; import { StmtNS } from "../ast-types"; import { Context } from "../engines/cse/context"; From 1ce780f90e51ae531f9e9835e4ba9ee8b08782c2 Mon Sep 17 00:00:00 2001 From: Hua Zhi Vee Date: Tue, 5 May 2026 23:47:39 +0800 Subject: [PATCH 9/9] fix: remove ts-ignore in python grammar --- src/parser/python-grammar.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parser/python-grammar.ts b/src/parser/python-grammar.ts index 21aa4061..10158bc5 100644 --- a/src/parser/python-grammar.ts +++ b/src/parser/python-grammar.ts @@ -1,4 +1,3 @@ -// @ts-nocheck // Generated automatically by nearley, version 2.20.1 // http://github.com/Hardmath123/nearley function id(x: unknown[]) {