diff --git a/.changeset/config.json b/.changeset/config.json index 78a4463..b1628ad 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -8,8 +8,8 @@ "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [ - "@sourceacademy/web-stepper", + "@sourceacademy/web-data-visualizer", "@sourceacademy/web-e-stepper", - "@sourceacademy/web-data-visualizer" + "@sourceacademy/web-stepper" ] } \ No newline at end of file diff --git a/.github/workflows/on_pr.yml b/.github/workflows/on_pr.yml index 268e800..26b8390 100644 --- a/.github/workflows/on_pr.yml +++ b/.github/workflows/on_pr.yml @@ -18,6 +18,7 @@ jobs: node-version: 24 cache: yarn - run: yarn install --frozen-lockfile + - run: yarn constraints - run: yarn run build - run: yarn run format:ci - run: yarn run lint diff --git a/.gitignore b/.gitignore index f05b277..e7dc879 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ dist coverage # Claude Code -CLAUDE.md \ No newline at end of file +CLAUDE.md diff --git a/.prettierignore b/.prettierignore index 612f7df..0ba268d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,4 @@ -.changeset/config.json \ No newline at end of file +.changeset/config.json +.yarn +**/dist +**/node_modules diff --git a/.prettierrc b/.prettierrc index ac5d019..cfb29ac 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,7 @@ { - "printWidth": 100, "arrowParens": "avoid", - "endOfLine": "auto" -} \ No newline at end of file + "endOfLine": "auto", + "printWidth": 100, + "semi": true, + "singleQuote": true +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 1453f7f..da487ff 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["arcanis.vscode-zipfs"] + "recommendations": ["arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index d8d3bef..7d19b92 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,14 @@ "**/.yarn": true, "**/.pnp.*": true }, - "typescript.tsdk": ".yarn/sdks/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true, - "js/ts.tsdk.path": ".yarn/sdks/typescript/lib" + "js/ts.tsdk.promptToUseWorkspaceVersion": true, + "js/ts.tsdk.path": ".yarn/sdks/typescript/lib", + "eslint.nodePath": ".yarn/sdks", + "prettier.prettierPath": ".yarn/sdks/prettier/index.cjs", + "json.schemas": [ + { + "fileMatch": ["src/*/*/manifest.json"], + "url": "./lib/manifest.schema.json" + } + ] } diff --git a/.yarn/sdks/eslint/bin/eslint.js b/.yarn/sdks/eslint/bin/eslint.js new file mode 100644 index 0000000..e6604ff --- /dev/null +++ b/.yarn/sdks/eslint/bin/eslint.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/bin/eslint.js + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/bin/eslint.js your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/bin/eslint.js`)); diff --git a/.yarn/sdks/eslint/lib/api.js b/.yarn/sdks/eslint/lib/api.js new file mode 100644 index 0000000..8addf97 --- /dev/null +++ b/.yarn/sdks/eslint/lib/api.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint`)); diff --git a/.yarn/sdks/eslint/lib/config-api.js b/.yarn/sdks/eslint/lib/config-api.js new file mode 100644 index 0000000..e84435d --- /dev/null +++ b/.yarn/sdks/eslint/lib/config-api.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/config + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/config your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/config`)); diff --git a/.yarn/sdks/eslint/lib/types/config-api.d.ts b/.yarn/sdks/eslint/lib/types/config-api.d.ts new file mode 100644 index 0000000..174070b --- /dev/null +++ b/.yarn/sdks/eslint/lib/types/config-api.d.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/config + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/config your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/config`)); diff --git a/.yarn/sdks/eslint/lib/types/index.d.ts b/.yarn/sdks/eslint/lib/types/index.d.ts new file mode 100644 index 0000000..19293d0 --- /dev/null +++ b/.yarn/sdks/eslint/lib/types/index.d.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint`)); diff --git a/.yarn/sdks/eslint/lib/types/rules.d.ts b/.yarn/sdks/eslint/lib/types/rules.d.ts new file mode 100644 index 0000000..8d79c4c --- /dev/null +++ b/.yarn/sdks/eslint/lib/types/rules.d.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/rules + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/rules your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/rules`)); diff --git a/.yarn/sdks/eslint/lib/types/universal.d.ts b/.yarn/sdks/eslint/lib/types/universal.d.ts new file mode 100644 index 0000000..662b3f4 --- /dev/null +++ b/.yarn/sdks/eslint/lib/types/universal.d.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/universal + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/universal your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/universal`)); diff --git a/.yarn/sdks/eslint/lib/types/use-at-your-own-risk.d.ts b/.yarn/sdks/eslint/lib/types/use-at-your-own-risk.d.ts new file mode 100644 index 0000000..2e2ccca --- /dev/null +++ b/.yarn/sdks/eslint/lib/types/use-at-your-own-risk.d.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/use-at-your-own-risk + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/use-at-your-own-risk your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/use-at-your-own-risk`)); diff --git a/.yarn/sdks/eslint/lib/universal.js b/.yarn/sdks/eslint/lib/universal.js new file mode 100644 index 0000000..85a8ccb --- /dev/null +++ b/.yarn/sdks/eslint/lib/universal.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/universal + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/universal your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/universal`)); diff --git a/.yarn/sdks/eslint/lib/unsupported-api.js b/.yarn/sdks/eslint/lib/unsupported-api.js new file mode 100644 index 0000000..c2b464c --- /dev/null +++ b/.yarn/sdks/eslint/lib/unsupported-api.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/use-at-your-own-risk + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real eslint/use-at-your-own-risk your application uses +module.exports = wrapWithUserWrapper(absRequire(`eslint/use-at-your-own-risk`)); diff --git a/.yarn/sdks/eslint/package.json b/.yarn/sdks/eslint/package.json new file mode 100644 index 0000000..f26d3e7 --- /dev/null +++ b/.yarn/sdks/eslint/package.json @@ -0,0 +1,31 @@ +{ + "name": "eslint", + "version": "10.8.0-sdk", + "main": "./lib/api.js", + "type": "commonjs", + "bin": { + "eslint": "./bin/eslint.js" + }, + "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "default": "./lib/api.js" + }, + "./config": { + "types": "./lib/types/config-api.d.ts", + "default": "./lib/config-api.js" + }, + "./package.json": "./package.json", + "./use-at-your-own-risk": { + "types": "./lib/types/use-at-your-own-risk.d.ts", + "default": "./lib/unsupported-api.js" + }, + "./rules": { + "types": "./lib/types/rules.d.ts" + }, + "./universal": { + "types": "./lib/types/universal.d.ts", + "default": "./lib/universal.js" + } + } +} diff --git a/.yarn/sdks/prettier/bin/prettier.cjs b/.yarn/sdks/prettier/bin/prettier.cjs new file mode 100644 index 0000000..9a4098f --- /dev/null +++ b/.yarn/sdks/prettier/bin/prettier.cjs @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier/bin/prettier.cjs + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real prettier/bin/prettier.cjs your application uses +module.exports = wrapWithUserWrapper(absRequire(`prettier/bin/prettier.cjs`)); diff --git a/.yarn/sdks/prettier/index.cjs b/.yarn/sdks/prettier/index.cjs new file mode 100644 index 0000000..57cb2ab --- /dev/null +++ b/.yarn/sdks/prettier/index.cjs @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); + +const relPnpApiPath = "../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`); +const absRequire = createRequire(absPnpApiPath); + +const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`); +const isPnpLoaderEnabled = existsSync(absPnpLoaderPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier + require(absPnpApiPath).setup(); + if (isPnpLoaderEnabled && register) { + register(pathToFileURL(absPnpLoaderPath)); + } + } +} + +const wrapWithUserWrapper = existsSync(absUserWrapperPath) + ? exports => absRequire(absUserWrapperPath)(exports) + : exports => exports; + +// Defer to the real prettier your application uses +module.exports = wrapWithUserWrapper(absRequire(`prettier`)); diff --git a/.yarn/sdks/prettier/package.json b/.yarn/sdks/prettier/package.json new file mode 100644 index 0000000..88ba091 --- /dev/null +++ b/.yarn/sdks/prettier/package.json @@ -0,0 +1,7 @@ +{ + "name": "prettier", + "version": "3.8.3-sdk", + "main": "./index.cjs", + "type": "commonjs", + "bin": "./bin/prettier.cjs" +} diff --git a/.yarn/sdks/typescript/lib/tsc.js b/.yarn/sdks/typescript/lib/tsc.js index 857b6f9..da411bd 100644 --- a/.yarn/sdks/typescript/lib/tsc.js +++ b/.yarn/sdks/typescript/lib/tsc.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const { existsSync } = require(`fs`); -const { createRequire, register } = require(`module`); -const { resolve } = require(`path`); -const { pathToFileURL } = require(`url`); +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; diff --git a/.yarn/sdks/typescript/lib/tsserver.js b/.yarn/sdks/typescript/lib/tsserver.js index c266bc6..6249c46 100644 --- a/.yarn/sdks/typescript/lib/tsserver.js +++ b/.yarn/sdks/typescript/lib/tsserver.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const { existsSync } = require(`fs`); -const { createRequire, register } = require(`module`); -const { resolve } = require(`path`); -const { pathToFileURL } = require(`url`); +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -37,18 +37,16 @@ const moduleWrapperFn = tsserver => { return tsserver; } - const { isAbsolute } = require(`path`); + const {isAbsolute} = require(`path`); const pnpApi = require(`pnpapi`); const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); const isPortal = str => str.startsWith("portal:/"); const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - const dependencyTreeRoots = new Set( - pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - }), - ); + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol @@ -56,11 +54,7 @@ const moduleWrapperFn = tsserver => { function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if ( - isAbsolute(str) && - !str.match(/^\^?(zip:|\/zip\/)/) && - (str.match(/\.zip\//) || isVirtual(str)) - ) { + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual @@ -74,11 +68,7 @@ const moduleWrapperFn = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if ( - locator && - (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || - isPortal(locator.reference)) - ) { + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { str = resolved; } } @@ -106,55 +96,41 @@ const moduleWrapperFn = tsserver => { // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // - case `vscode <1.61`: - { - str = `^zip:${str}`; - } - break; + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; - case `vscode <1.66`: - { - str = `^/zip/${str}`; - } - break; + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; - case `vscode <1.68`: - { - str = `^/zip${str}`; - } - break; + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; - case `vscode`: - { - str = `^/zip/${str}`; - } - break; + case `vscode`: { + str = `^/zip/${str}`; + } break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: - { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } - break; + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim - case `neovim`: - { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } - break; - - default: - { - str = `zip:${str}`; - } - break; + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; } } else { str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); @@ -166,35 +142,26 @@ const moduleWrapperFn = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: - { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } - break; - - case `neovim`: - { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } - break; + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; case `vscode`: - default: - { - return str.replace( - /^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, - process.platform === `win32` ? `` : `/`, - ); - } - break; + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; } } @@ -206,9 +173,8 @@ const moduleWrapperFn = tsserver => { // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; - const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = - ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function () { + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; @@ -218,12 +184,12 @@ const moduleWrapperFn = tsserver => { // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; - const { onMessage: originalOnMessage, send: originalSend } = Session.prototype; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { onMessage(/** @type {string | object} */ message) { - const isStringMessage = typeof message === "string"; + const isStringMessage = typeof message === 'string'; const parsedMessage = isStringMessage ? JSON.parse(message) : message; if ( @@ -234,12 +200,10 @@ const moduleWrapperFn = tsserver => { ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = ( - process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/, - ) ?? [] - ).map(Number); + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) if (major === 1) { if (minor < 61) { @@ -254,33 +218,26 @@ const moduleWrapperFn = tsserver => { } const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === "string" ? fromEditorPath(value) : value; + return typeof value === 'string' ? fromEditorPath(value) : value; }); return originalOnMessage.call( this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON), + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) ); }, send(/** @type {any} */ msg) { - return originalSend.call( - this, - JSON.parse( - JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }), - ), - ); - }, + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } }); return tsserver; }; -const [major, minor] = absRequire(`typescript/package.json`) - .version.split(`.`, 2) - .map(value => parseInt(value, 10)); +const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10)); // In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well. // Ref https://github.com/microsoft/TypeScript/pull/55326 if (major > 5 || (major === 5 && minor >= 5)) { diff --git a/.yarn/sdks/typescript/lib/tsserverlibrary.js b/.yarn/sdks/typescript/lib/tsserverlibrary.js index 3072888..0e50e0a 100644 --- a/.yarn/sdks/typescript/lib/tsserverlibrary.js +++ b/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const { existsSync } = require(`fs`); -const { createRequire, register } = require(`module`); -const { resolve } = require(`path`); -const { pathToFileURL } = require(`url`); +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; @@ -37,18 +37,16 @@ const moduleWrapperFn = tsserver => { return tsserver; } - const { isAbsolute } = require(`path`); + const {isAbsolute} = require(`path`); const pnpApi = require(`pnpapi`); const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); const isPortal = str => str.startsWith("portal:/"); const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - const dependencyTreeRoots = new Set( - pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - }), - ); + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol @@ -56,11 +54,7 @@ const moduleWrapperFn = tsserver => { function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if ( - isAbsolute(str) && - !str.match(/^\^?(zip:|\/zip\/)/) && - (str.match(/\.zip\//) || isVirtual(str)) - ) { + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual @@ -74,11 +68,7 @@ const moduleWrapperFn = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if ( - locator && - (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || - isPortal(locator.reference)) - ) { + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { str = resolved; } } @@ -106,55 +96,41 @@ const moduleWrapperFn = tsserver => { // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // - case `vscode <1.61`: - { - str = `^zip:${str}`; - } - break; + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; - case `vscode <1.66`: - { - str = `^/zip/${str}`; - } - break; + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; - case `vscode <1.68`: - { - str = `^/zip${str}`; - } - break; + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; - case `vscode`: - { - str = `^/zip/${str}`; - } - break; + case `vscode`: { + str = `^/zip/${str}`; + } break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: - { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } - break; + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim - case `neovim`: - { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } - break; - - default: - { - str = `zip:${str}`; - } - break; + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; } } else { str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); @@ -166,35 +142,26 @@ const moduleWrapperFn = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: - { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } - break; - - case `neovim`: - { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } - break; + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; case `vscode`: - default: - { - return str.replace( - /^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, - process.platform === `win32` ? `` : `/`, - ); - } - break; + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; } } @@ -206,9 +173,8 @@ const moduleWrapperFn = tsserver => { // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; - const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = - ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function () { + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; @@ -218,12 +184,12 @@ const moduleWrapperFn = tsserver => { // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; - const { onMessage: originalOnMessage, send: originalSend } = Session.prototype; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { onMessage(/** @type {string | object} */ message) { - const isStringMessage = typeof message === "string"; + const isStringMessage = typeof message === 'string'; const parsedMessage = isStringMessage ? JSON.parse(message) : message; if ( @@ -234,12 +200,10 @@ const moduleWrapperFn = tsserver => { ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = ( - process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/, - ) ?? [] - ).map(Number); + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) if (major === 1) { if (minor < 61) { @@ -254,33 +218,26 @@ const moduleWrapperFn = tsserver => { } const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === "string" ? fromEditorPath(value) : value; + return typeof value === 'string' ? fromEditorPath(value) : value; }); return originalOnMessage.call( this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON), + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) ); }, send(/** @type {any} */ msg) { - return originalSend.call( - this, - JSON.parse( - JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }), - ), - ); - }, + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } }); return tsserver; }; -const [major, minor] = absRequire(`typescript/package.json`) - .version.split(`.`, 2) - .map(value => parseInt(value, 10)); +const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10)); // In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well. // Ref https://github.com/microsoft/TypeScript/pull/55326 if (major > 5 || (major === 5 && minor >= 5)) { diff --git a/.yarn/sdks/typescript/lib/typescript.js b/.yarn/sdks/typescript/lib/typescript.js index bc1c8f7..7b6cc22 100644 --- a/.yarn/sdks/typescript/lib/typescript.js +++ b/.yarn/sdks/typescript/lib/typescript.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -const { existsSync } = require(`fs`); -const { createRequire, register } = require(`module`); -const { resolve } = require(`path`); -const { pathToFileURL } = require(`url`); +const {existsSync} = require(`fs`); +const {createRequire, register} = require(`module`); +const {resolve} = require(`path`); +const {pathToFileURL} = require(`url`); const relPnpApiPath = "../../../../.pnp.cjs"; diff --git a/.yarnrc.yml b/.yarnrc.yml index 1a226b4..b3931e1 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1 +1,13 @@ -npmPreapprovedPackages: ["@sourceacademy/conductor"] \ No newline at end of file +npmPreapprovedPackages: ["@sourceacademy/conductor"] + +catalog: + rollup: ^4.60.2 + "@rollup/plugin-commonjs": ^29.0.3 + "@rollup/plugin-node-resolve": ^16.0.3 + "@rollup/plugin-terser": ^1.0.0 + "@rollup/plugin-typescript": ^12.3.0 + "@sourceacademy/conductor": ^0.8.0 + tslib: ^2.8.1 + typescript: ^6.0.3 + "@vitest/coverage-istanbul": "^5.0.0" + vitest: ^5.0.0 diff --git a/eslint.config.mjs b/eslint.config.mjs index cfae433..7e87bf2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,39 +1,99 @@ -import eslintConfigPrettierFlat from "eslint-config-prettier/flat"; -import { defineConfig } from "eslint/config"; -import tseslint from "typescript-eslint"; +// @ts-check + +import vitest from '@vitest/eslint-plugin'; +import { defineConfig } from 'eslint/config'; +import eslintConfigPrettierFlat from 'eslint-config-prettier/flat'; +import * as importPlugin from 'eslint-plugin-import-x'; +import tseslint from 'typescript-eslint'; export default defineConfig([ { - ignores: ["**/dist", "coverage"], + ignores: ['.yarn', '.pnp*', '**/dist', '**/node_modules', '**/coverage'], }, - tseslint.configs.recommended, eslintConfigPrettierFlat, { - files: ["src/**/*.{ts,tsx}"], + plugins: { + import: importPlugin, + }, + rules: { + 'import/first': 'warn', + 'import/newline-after-import': 'warn', + 'import/no-duplicates': ['warn', { 'prefer-inline': false }], + 'import/no-useless-path-segments': 'error', + 'import/order': [ + 'warn', + { + groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], + named: { + import: true, + types: 'types-last', + }, + alphabetize: { + order: 'asc', + orderImportKind: 'asc', + }, + }, + ], + + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'commander', + message: 'Use @commander-js/extra-typings instead!', + }, + ], + }, + ], + }, + }, + tseslint.configs.recommended, + { + files: ['**/*.{ts,tsx}'], languageOptions: { parser: tseslint.parser, parserOptions: { - project: ["src/tsconfig.json", "src/tsconfig.test.json"], + projectService: { + allowDefaultProject: ['vitest.config.ts'], + }, tsconfigRootDir: import.meta.dirname, }, }, plugins: { - "@typescript-eslint": tseslint.plugin, + '@typescript-eslint': tseslint.plugin, + }, + rules: { + '@typescript-eslint/ban-ts-comment': 'warn', + '@typescript-eslint/no-explicit-any': 'off', // was error + '@typescript-eslint/no-import-type-side-effects': 'error', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + '@typescript-eslint/only-throw-error': 'error', + '@typescript-eslint/require-await': 'error', + '@typescript-eslint/return-await': ['error', 'in-try-catch'], + }, + }, + { + files: ['**/__tests__/**/*.test.{ts,tsx}'], + extends: [vitest.configs.recommended], + plugins: { + vitest, }, rules: { - "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/ban-ts-comment": "warn", - "@typescript-eslint/no-import-type-side-effects": "error", - "@typescript-eslint/no-unnecessary-type-assertion": "error", - "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], - "@typescript-eslint/require-await": "error", - "@typescript-eslint/return-await": ["error", "in-try-catch"], + '@typescript-eslint/no-explicit-any': 'off', + + 'vitest/prefer-describe-function-title': 'warn', + 'vitest/prefer-import-in-mock': 'error', + 'vitest/prefer-vi-mocked': 'warn', + 'vitest/valid-title': ['warn', { ignoreTypeOfDescribeName: true }], }, }, { - files: ["src/**/*.js"], + files: ['**/*.{js,cjs}'], rules: { - "@typescript-eslint/no-require-imports": "off", + '@typescript-eslint/no-require-imports': 'off', }, }, ]); diff --git a/lib/__tests__/build.test.ts b/lib/__tests__/build.test.ts new file mode 100644 index 0000000..e9f7e81 --- /dev/null +++ b/lib/__tests__/build.test.ts @@ -0,0 +1,230 @@ +import { exec, spawn, type ExecException } from 'child_process'; +import fs from 'fs/promises'; +import pathlib from 'path'; +import { Command } from '@commander-js/extra-typings'; +import { beforeEach, describe, expect, it, test, vi } from 'vitest'; +import { copyDistFiles, generateManifest, runYarnUsingSpawn, transformSingle } from '../build.js'; + +const repoRoot = pathlib.resolve(import.meta.dirname, '..', '..'); + +vi.mock(import('fs/promises'), () => ({ + default: { + cp: vi.fn(), + mkdir: vi.fn(), + writeFile: vi.fn(), + readFile: vi.fn(), + } as any, +})); + +vi.mock( + import('child_process'), + () => + ({ + spawn: vi.fn(), + exec: vi.fn(), + }) as any, +); + +vi.mock(import('@commander-js/extra-typings'), async actual => { + const commander = await actual(); + + return { + ...commander, + Command: vi.fn(commander.Command), + } as any; +}); + +function mockExec(stdout: string, err: ExecException | null) { + vi.mocked(exec).mockImplementationOnce((cmd, cb) => { + (cb as (...args: any[]) => any)(err, stdout, ''); + return {} as any; + }); +} + +/** + * Utility function to "mock" the workspaces present in the repo + */ +function mockWorkspaces(workspaces: [string, string, 'external' | 'installable'][]) { + const workspaceStrs: string[] = []; + + for (const [loc, name, type] of workspaces) { + workspaceStrs.push(`{"location":"${loc}","name":"${name}"}`); + const workspaceDir = pathlib.posix.join(repoRoot, loc); + + vi.doMock(import(`file://${workspaceDir}/package.json`), () => ({ + default: { + name, + description: `This is the ${name} plugin`, + }, + })); + + vi.doMock(import(`file://${workspaceDir}/manifest.json`), () => ({ + default: { + type, + }, + })); + } + + const workspaceStr = workspaces + .map(([loc, name]) => `{"location":"${loc}","name":"${name}"}`) + .join('\n'); + mockExec(workspaceStr, null); +} + +/** + * Checks if `fs.writeFile` was called with the given path and returns the string + * that was written to the given path. Throws an error if `fs.writeFile` was never + * called with the given path. + */ +const findCall = vi.defineHelper((path: string) => { + const { + mock: { calls }, + } = vi.mocked(fs.writeFile); + + const result = calls.find(([writtenPath]) => writtenPath === path); + + if (!result) { + expect.fail(`fs.writeFile was not called with ${path}`); + } + + return result[1] as string; +}); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +test('Commander should not have been called', () => { + expect(Command).not.toHaveBeenCalled(); +}); + +describe(generateManifest, () => { + mockWorkspaces([ + ['src/common/test', '@sourceacademy/common-test', 'installable'], + ['src/web/test', '@sourceacademy/web-test', 'external'], + ['src/runner/test', '@sourceacademy/runner-test', 'installable'], + ]); + + it('works', async () => { + await generateManifest(); + + expect(fs.mkdir).toHaveBeenCalledExactlyOnceWith(expect.any(String), { recursive: true }); + expect(exec).toHaveBeenCalledOnce(); + + // once for each plugin type, once for plugin directory and once for + // changeset + expect(fs.writeFile).toHaveBeenCalledTimes(5); + + // Check for plugin directory + const pluginDirectory = JSON.parse(findCall(pathlib.join(repoRoot, 'dist', 'directory.json'))); + expect(pluginDirectory).toHaveLength(1); + expect(pluginDirectory[0]).toHaveProperty( + 'description', + 'This is the @sourceacademy/web-test plugin', + ); + + // Check commons directory + const commonDirectory = JSON.parse(findCall(pathlib.join(repoRoot, 'dist', 'common.json'))); + expect(commonDirectory).toEqual({ test: { type: 'installable' } }); + + // Check web directory + const webDirectory = JSON.parse(findCall(pathlib.join(repoRoot, 'dist', 'web.json'))); + expect(webDirectory).toEqual({ test: { type: 'external' } }); + + // Check runner directory + const runnerDirectory = JSON.parse(findCall(pathlib.join(repoRoot, 'dist', 'runner.json'))); + expect(runnerDirectory).toEqual({ test: { type: 'installable' } }); + + // Check for changeset + const { ignore: ignoreList } = JSON.parse( + findCall(pathlib.join(repoRoot, '.changeset', 'config.json')), + ); + expect(ignoreList).toContain('@sourceacademy/web-test'); + }); +}); + +describe(transformSingle, () => { + it('works', async () => { + vi.mocked(fs.readFile).mockResolvedValueOnce(` + module.exports = { + foo: () => 'foo', + bar: () => { + const value = require('value'); + return value; + } + } + `); + + await expect(transformSingle('path.js')).resolves.toBeUndefined(); + expect(fs.readFile).toHaveBeenCalledOnce(); + + const transformedContents = findCall('path.js'); + const dataUri = 'data:text/javascript,' + encodeURIComponent(transformedContents); + const { default: func } = await import(dataUri); + + expect(func).toBeInstanceOf(Function); + + const { foo, bar } = func((p: string) => p === 'value'); + + expect(foo()).toEqual('foo'); + expect(bar()).toEqual(true); + }); +}); + +describe(runYarnUsingSpawn, () => { + function mockSpawn(retCode: number) { + vi.mocked(spawn).mockImplementationOnce( + () => + ({ + on(handler: string, listener: (...args: any[]) => any) { + if (handler === 'close') { + listener(retCode); + } + return this; + }, + }) as any, + ); + } + + it('calls yarn when not on windows', async () => { + mockSpawn(0); + vi.spyOn(process, 'platform', 'get').mockReturnValueOnce('openbsd'); + + await runYarnUsingSpawn(); + expect(spawn).toHaveBeenCalledExactlyOnceWith('yarn', [], expect.any(Object)); + }); + + it('calls yarn.cmd when on windows', async () => { + mockSpawn(0); + vi.spyOn(process, 'platform', 'get').mockReturnValueOnce('win32'); + + await runYarnUsingSpawn(); + expect(spawn).toHaveBeenCalledExactlyOnceWith('yarn.cmd', [], expect.any(Object)); + }); + + it('rejects with the error code', async () => { + mockSpawn(1); + + await expect(runYarnUsingSpawn()).rejects.toThrow('Process exited with code 1'); + }); +}); + +describe(copyDistFiles, () => { + mockWorkspaces([ + ['src/common/test', '@sourceacademy/common-test', 'installable'], + ['src/web/test', '@sourceacademy/web-test', 'external'], + ['src/runner/test', '@sourceacademy/runner-test', 'installable'], + ]); + + it('works', async () => { + const manifest = await generateManifest(); + await copyDistFiles(manifest); + + // Only copies external plugins + expect(fs.cp).toHaveBeenCalledExactlyOnceWith( + pathlib.join(repoRoot, 'src', 'web', 'test', 'dist'), + pathlib.join(repoRoot, 'dist', 'web', 'test'), + { recursive: true }, + ); + }); +}); diff --git a/lib/build.ts b/lib/build.ts index aeeb305..d7acf3c 100644 --- a/lib/build.ts +++ b/lib/build.ts @@ -1,99 +1,154 @@ -import { Command } from "commander"; -import packageJSON from "../package.json" with { type: "json" }; -import fs, { readFile, writeFile } from "fs/promises"; -import { spawn } from "child_process"; -import pathlib from "path"; -import type { IPluginDefinition } from "@sourceacademy/plugin-directory/dist/types/IPluginDefinition.js"; -import process from "process"; -const program = new Command(); +import { exec, spawn } from 'child_process'; +import fs from 'fs/promises'; +import { createRequire } from 'module'; +import pathlib from 'path'; +import type { IPluginDefinition } from '@sourceacademy/plugin-directory/dist/types/IPluginDefinition.js'; +import { Command } from '@commander-js/extra-typings'; +import packageJSON from '../package.json' with { type: 'json' }; + +interface WorkspaceEntry { + location: string; + name: string; +} + interface Manifest { - type: "installable" | "external"; + type: 'installable' | 'external'; } -const globalManifest: Record> = {}; +const repoRoot = pathlib.resolve(import.meta.dirname, '..'); +// Create a `require` function to use node's resolution algorithm to find +// the entry point of plugins +const require = createRequire(import.meta.url); const pluginDirectory: Record = {}; +/** + * Use `yarn workspaces list --json` to gather all the workspaces in the repository + */ +async function getWorkspaces() { + const entries = await new Promise((resolve, reject) => { + exec('yarn workspaces list --json', (err, stdout, stderr) => { + if (err) { + reject(err); + } + + if (stderr) console.error(stderr); + + resolve(stdout.trim().split('\n')); + }); + }); + + return entries.map(each => JSON.parse(each)) as WorkspaceEntry[]; +} + /** * Generates the manifest files for each plugin and updates the changeset config to ignore non-installable plugins. * The manifest files are generated by reading the manifest.json file in each plugin folder and writing it to the dist folder. * The changeset config is updated to ignore non-installable plugins to prevent unnecessary version bumps and releases. - * @example + * @example ``` * await generateManifest(); + * ``` * This will generate the manifest files for each plugin and update the changeset config to ignore non-installable plugins. */ -async function generateManifest() { +export async function generateManifest() { + const globalManifest: Record> = {}; + // Create dist directory if it doesn't exist - await fs.mkdir("dist", { recursive: true }); + const distDirectory = pathlib.join(repoRoot, 'dist'); + await fs.mkdir(distDirectory, { recursive: true }); // The ignore list is used to keep track of non-installable plugins, which should be ignored in changesets to prevent unnecessary version bumps and releases. const ignoreList: string[] = []; + const workspaces = await getWorkspaces(); + await Promise.all( - packageJSON.workspaces.map(async workspace => { - const manifest: Record = {}; - // pluginType is either "web", "runner" or "common" - const pluginType = workspace.split("/").slice(-3)[0]; - for await (const file of fs.glob(`${workspace}manifest.json`)) { - const manifestFile = JSON.parse(await fs.readFile(file, "utf-8")); - const packageJSONPath = file.replace("manifest.json", "package.json"); - const packageJSONFile = JSON.parse(await fs.readFile(packageJSONPath, "utf-8")); - // folderName is the name of the plugin folder (generally the same as the plugin name) - const folderName = file.split(pathlib.sep).slice(-2)[0]; - manifest[folderName] = manifestFile as Manifest; - if (manifestFile.type !== "installable") { - ignoreList.push(`@sourceacademy/${pluginType}-${folderName}`); - pluginDirectory[folderName] = pluginDirectory[folderName] || { - id: folderName, - name: folderName, - description: packageJSONFile.description, - resolutions: {}, - }; - // Use the plugin's own declared entry point rather than assuming ".js": external - // plugins can emit ".mjs" (e.g. the stepper's wrap.mjs produces real ESM for native - // `import()`), and a mismatch here silently 404s for anyone resolving the plugin. - // package.json is untyped JSON, so guard against main/module being non-string (e.g. an - // object, from a more complex export config) before handing them to pathlib.basename. - const mainEntry = - typeof packageJSONFile.main === "string" ? packageJSONFile.main : undefined; - const moduleEntry = - typeof packageJSONFile.module === "string" ? packageJSONFile.module : undefined; - const entryFile = pathlib.basename(mainEntry ?? moduleEntry ?? "index.js"); - pluginDirectory[folderName].resolutions[pluginType] = - "./" + pluginType + "/" + folderName + "/" + entryFile; - } + workspaces.map(async ({ name: workspaceName, location }) => { + // Don't process the root workspace + if (workspaceName === packageJSON.name) return; + + const pkgMatch = /@sourceacademy\/(\w+?)-(.+)/.exec(workspaceName); + + if (!pkgMatch) throw new Error(`Invalid package name ${workspaceName}`); + const [, pluginType, pluginName] = pkgMatch; + + if (!(pluginType in globalManifest)) { + globalManifest[pluginType] = {}; + } + + const workspacePath = pathlib.resolve(repoRoot, location); + const manifestFilePath = pathlib.join(workspacePath, 'manifest.json'); + const pkgJsonPath = pathlib.join(workspacePath, 'package.json'); + + const [{ default: manifestFile }, { default: packageJSONFile }] = await Promise.all([ + import(`file://${manifestFilePath}`, { with: { type: 'json' } }), + import(`file://${pkgJsonPath}`, { with: { type: 'json' } }), + ]); + + const manifest = globalManifest[pluginType]; + manifest[pluginName] = manifestFile as Manifest; + + if (manifestFile.type !== 'installable') { + ignoreList.push(workspaceName); + + pluginDirectory[pluginName] = pluginDirectory[pluginName] || { + id: pluginName, + name: pluginName, + description: packageJSONFile.description, + resolutions: {}, + }; + + // Use the plugin's own declared entry point rather than assuming ".js": external + // plugins can emit ".mjs" (e.g. the stepper's wrap.mjs produces real ESM for native + // `import()`), and a mismatch here silently 404s for anyone resolving the plugin. + const entryFile = pathlib.basename(require.resolve(workspacePath)); + + pluginDirectory[pluginName].resolutions[pluginType] = pathlib.posix.join( + '.', + pluginType, + pluginName, + entryFile, + ); } - globalManifest[pluginType] = manifest; - await fs.writeFile(`dist/${pluginType}.json`, JSON.stringify(manifest, null, 2)); + }), + ); + + // Write the manifest for each plugin type to the dist folder. + await Promise.all( + Object.entries(globalManifest).map(([pluginType, manifest]) => { + const outpath = pathlib.join(repoRoot, 'dist', `${pluginType}.json`); + return fs.writeFile(outpath, JSON.stringify(manifest, null, 2)); }), ); // Write the plugin directory to the dist folder. This is used by the plugin loaders to load non-installable plugins. await fs.writeFile( - `dist/directory.json`, + pathlib.join(repoRoot, 'dist', 'directory.json'), JSON.stringify(Object.values(pluginDirectory), null, 2), ); // Update changeset config to ignore non-installable plugins - const changesetConfig = await fs.readFile(".changeset/config.json", "utf-8"); - const changesetConfigJSON = JSON.parse(changesetConfig); + const changesetFilePath = pathlib.join(repoRoot, '.changeset', 'config.json'); + const { default: changesetConfigJSON } = await import(`file://${changesetFilePath}`, { + with: { type: 'json' }, + }); changesetConfigJSON.ignore = ignoreList; - await fs.writeFile(".changeset/config.json", JSON.stringify(changesetConfigJSON, null, 2)); + await fs.writeFile(changesetFilePath, JSON.stringify(changesetConfigJSON, null, 2)); + + return globalManifest; } /** - * A helper function to spawn a child process and return a promise that resolves when the process exits with code 0, and rejects otherwise. - * @param args The arguments passed to spawn - * @returns A promise that resolves when the process exits with code 0, and rejects otherwise. - * @example - * await spawnPromise("yarn", ["build"]); + * Runs `yarn` with the given arguments. */ -async function spawnPromise(...args: Parameters) { +export function runYarnUsingSpawn(...args: string[]) { return new Promise((resolve, reject) => { - const child = spawn(...args); - child.on("error", err => { - reject(err); + const proc = spawn(process.platform === 'win32' ? 'yarn.cmd' : 'yarn', args, { + stdio: 'inherit', + shell: true, + cwd: repoRoot, }); - child.on("close", code => { + proc.on('error', reject); + proc.on('close', code => { if (code === 0) { resolve(); } else { @@ -109,17 +164,17 @@ async function spawnPromise(...args: Parameters) { * @example * await copyDistFiles(); */ -async function copyDistFiles() { +export async function copyDistFiles(globalManifest: Record>) { await Promise.all( // Iterate over each plugin type (web, runner, common) Object.entries(globalManifest).map(async ([pluginType, manifest]) => { await Promise.all( // Iterate over each plugin in the plugin type and copy the dist files if the plugin is of type "external" Object.entries(manifest).map(async ([pluginName, pluginManifest]) => { - if (pluginManifest.type === "external") { + if (pluginManifest.type === 'external') { await fs.cp( - `src/${pluginType}/${pluginName}/dist`, - `dist/${pluginType}/${pluginName}`, + pathlib.join(repoRoot, 'src', pluginType, pluginName, 'dist'), + pathlib.join(repoRoot, 'dist', pluginType, pluginName), { recursive: true }, ); } @@ -129,42 +184,15 @@ async function copyDistFiles() { ); } -/** - * Builds the plugins by generating the manifest and running the build script for each plugin. - * The build script is run in topological order to ensure that dependencies are built before the plugins that depend on them. - * @param extraArgs Extra arguments to pass to the build script of each plugin. For example, if you want to run the build script in watch mode, you can pass ["--watch"] as extraArgs. - * @example - * await build(["--watch"]); - * This will run the build script of each plugin with the --watch flag, which is useful for development. - */ -async function build(extraArgs: string[] = []) { - await Promise.all([ - generateManifest(), - spawnPromise( - process.platform === "win32" ? "yarn.cmd" : "yarn", - ["workspaces", "foreach", "-Apt", "--topological-dev", "run", "build", ...extraArgs], - { - stdio: "inherit", - shell: true, - }, - ), - ]); - await copyDistFiles(); - await transform(); -} - /** * Transforms a particular external plugin to its final form */ -async function transformSingle(path: string) { - if (path.endsWith("mjs")) { - return; - } - let file = (await readFile(path)).toString("utf-8"); +export async function transformSingle(path: string) { + let file = await fs.readFile(path, 'utf-8'); // Create a mock "module" object, then return the exports file = `export default require => {let module = {exports: {}}; let exports = module.exports; ${file}; return module.exports;}`; - await writeFile(path, file); + await fs.writeFile(path, file); } /** @@ -174,12 +202,68 @@ async function transformSingle(path: string) { async function transform() { const promises = []; for await (const file of fs.glob(`./dist/*/*/index.*js`)) { + if (pathlib.extname(file) === '.mjs') continue; + promises.push(transformSingle(file)); } await Promise.all(promises); } -// TO-DO: Add more options -const options = program.parse(); -const extraArgs = options.args; -await build(extraArgs); +/** + * Builds the plugins by generating the manifest and running the build script for each plugin. + * The build script is run in topological order to ensure that dependencies are built before the plugins that depend on them. + * @param extraArgs Extra arguments to pass to the build script of each plugin. For example, if you want to run the build script in watch mode, you can pass ["--watch"] as extraArgs. + * @example ``` + * await build(["--watch"]); + * ``` + * This will run the build script of each plugin with the --watch flag, which is useful for development. + */ +async function build(extraArgs: string[] = []) { + // Need to build first so that require.resolve doesn't break + await runYarnUsingSpawn( + 'workspaces', + 'foreach', + '-Apt', + '--topological-dev', + '--exclude', + '@sourceacademy/plugins-workspace', + 'run', + 'build', + ...extraArgs, + ); + const globalManifest = await generateManifest(); + await copyDistFiles(globalManifest); + await transform(); +} + +/** + * Finds all the `dist` folders and deletes them, effectively "cleaning" the repo + * of built artifacts + */ +async function clean() { + const globPath = pathlib.join(repoRoot, '**', 'dist'); + + const promises = []; + for await (const file of fs.glob(globPath)) { + promises.push(fs.rm(file, { recursive: true })); + } + await Promise.all(promises); +} + +/* istanbul ignore start */ +// Only run automatically if not testing +if (process.env.NODE_ENV !== 'test') { + const program = new Command() + .addCommand( + new Command('build') + .allowExcessArguments(true) + .allowUnknownOption(true) + .action(async (_, cmd) => { + await build(cmd.args); + }), + { isDefault: true }, + ) + .addCommand(new Command('clean').description('Remove dist folders').action(clean)); + + await program.parseAsync(); +} diff --git a/lib/manifest.schema.json b/lib/manifest.schema.json new file mode 100644 index 0000000..ce7c879 --- /dev/null +++ b/lib/manifest.schema.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "type": { + "description": "Type of plugin", + "enum": ["installable", "external"] + } + }, + "additionalProperties": false +} diff --git a/package.json b/package.json index 4a88899..ad51efa 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { - "name": "plugins", + "name": "@sourceacademy/plugins-workspace", + "private": true, "packageManager": "yarn@4.18.1", "workspaces": [ "src/common/*/", @@ -8,30 +9,35 @@ ], "type": "module", "scripts": { - "build": "tsx lib/build.ts", - "dev": "tsx lib/build.ts --watch", + "build": "node lib/build.ts", + "dev": "node lib/build.ts --watch", + "run:all": "yarn workspaces foreach -pt -A --exclude \"@sourceacademy/plugins-workspace\" run", + "run:since": "yarn workspaces foreach -pt --since --exclude \"@sourceacademy/plugins-workspace\" run", "test": "vitest run", "test-coverage": "vitest run --coverage", "test:dev": "vitest", - "lint": "eslint --concurrency=auto src", - "format": "prettier --write \"**/*.{ts,tsx,json,js,mjs}\"", - "format:ci": "prettier --list-different \"**/*.{ts,tsx,json,js,mjs}\"" + "lint": "eslint --concurrency=auto", + "format": "prettier --write \"**/*.{ts,tsx,json,js,cjs,mjs}\"", + "format:ci": "prettier --list-different \"**/*.{ts,tsx,json,js,cjs,mjs}\"" }, "devDependencies": { "@changesets/cli": "^3.0.0", - "@sourceacademy/conductor": "^0.8.0", + "@commander-js/extra-typings": "^15.0.0", + "@sourceacademy/conductor": "catalog:", "@sourceacademy/plugin-directory": "source-academy/plugin-directory#main", "@types/node": "^25.9.1", - "@vitest/coverage-istanbul": "^5.0.0", + "@vitest/coverage-istanbul": "catalog:", + "@vitest/eslint-plugin": "^1.6.26", + "@yarnpkg/types": "^4.0.1", "commander": "^15.0.0", "eslint": "^10.0.3", "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import-x": "^4.17.1", "prettier": "^3.8.1", - "tsx": "^4.22.3", - "typescript": "^6.0.3", + "typescript": "catalog:", "typescript-eslint": "^8.57.1", "vite": "^8.0.0", - "vitest": "^5.0.0" + "vitest": "catalog:" }, "repository": { "type": "git", diff --git a/src/common/autocomplete/package.json b/src/common/autocomplete/package.json index 3464eb4..9a6e3cd 100644 --- a/src/common/autocomplete/package.json +++ b/src/common/autocomplete/package.json @@ -22,12 +22,17 @@ } }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/common/autocomplete" } } diff --git a/src/common/autocomplete/rollup.config.mjs b/src/common/autocomplete/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/common/autocomplete/rollup.config.mjs +++ b/src/common/autocomplete/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/common/autocomplete/src/autocomplete.ts b/src/common/autocomplete/src/autocomplete.ts index cfc5031..06b4950 100644 --- a/src/common/autocomplete/src/autocomplete.ts +++ b/src/common/autocomplete/src/autocomplete.ts @@ -1,34 +1,34 @@ // Adapted from https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.languageserver.protocol.completionitemkind?view=visualstudiosdk-2022 export enum CompletionItemKind { - Text = "text", - Method = "method", - Function = "func", - Constructor = "constructor", - Field = "field", - Variable = "var", - Class = "class", - Interface = "interface", - Module = "module", - Property = "property", - Unit = "unit", - Value = "value", - Enum = "enum", - Keyword = "keyword", - Snippet = "snippet", - Color = "color", - File = "file", - Reference = "reference", - Folder = "folder", - EnumMember = "enumMember", - Constant = "constant", - Struct = "struct", - Event = "event", - Operator = "operator", - TypeParameter = "typeParameter", + Text = 'text', + Method = 'method', + Function = 'func', + Constructor = 'constructor', + Field = 'field', + Variable = 'var', + Class = 'class', + Interface = 'interface', + Module = 'module', + Property = 'property', + Unit = 'unit', + Value = 'value', + Enum = 'enum', + Keyword = 'keyword', + Snippet = 'snippet', + Color = 'color', + File = 'file', + Reference = 'reference', + Folder = 'folder', + EnumMember = 'enumMember', + Constant = 'constant', + Struct = 'struct', + Event = 'event', + Operator = 'operator', + TypeParameter = 'typeParameter', } export interface AutoCompleteRequest { - type: "request"; + type: 'request'; /** Correlates this request with its response. */ requestId: number; /** The current code in the editor */ @@ -40,7 +40,7 @@ export interface AutoCompleteRequest { } export interface AutoCompleteResponse { - type: "response"; + type: 'response'; /** The ID of the request that produced this response. */ requestId: number; /** A list of autocomplete entries relevant to the current cursor position in the code */ diff --git a/src/common/autocomplete/src/constants.ts b/src/common/autocomplete/src/constants.ts index bc0faa2..5c90a51 100644 --- a/src/common/autocomplete/src/constants.ts +++ b/src/common/autocomplete/src/constants.ts @@ -1,5 +1,5 @@ -export const WEB_PLUGIN_ID = "__autocomplete_plugin_web"; -export const RUNNER_PLUGIN_ID = "__autocomplete_plugin_runner"; +export const WEB_PLUGIN_ID = '__autocomplete_plugin_web'; +export const RUNNER_PLUGIN_ID = '__autocomplete_plugin_runner'; -export const AUTOCOMPLETE_CHANNEL_ID = "__autocomplete_channel__"; -export const SYNTAX_CHANNEL_ID = "__syntax_channel__"; +export const AUTOCOMPLETE_CHANNEL_ID = '__autocomplete_channel__'; +export const SYNTAX_CHANNEL_ID = '__syntax_channel__'; diff --git a/src/common/autocomplete/src/index.ts b/src/common/autocomplete/src/index.ts index 5de3ef1..f4538a3 100644 --- a/src/common/autocomplete/src/index.ts +++ b/src/common/autocomplete/src/index.ts @@ -1,3 +1,3 @@ -export * from "./autocomplete"; -export * from "./constants"; -export * from "./syntax"; +export * from './autocomplete'; +export * from './constants'; +export * from './syntax'; diff --git a/src/common/autocomplete/src/syntax.ts b/src/common/autocomplete/src/syntax.ts index 863a183..cd3c6b7 100644 --- a/src/common/autocomplete/src/syntax.ts +++ b/src/common/autocomplete/src/syntax.ts @@ -1,16 +1,18 @@ export interface SyntaxHighlightRequest { - type: "request"; + type: 'request'; } export interface SyntaxHighlightResponse { - type: "response"; + type: 'response'; data: TransferredSyntaxHighlightData; } export interface SyntaxHighlightAck { - type: "ack"; + type: 'ack'; } export type SyntaxHighlightMessage = - SyntaxHighlightRequest | SyntaxHighlightResponse | SyntaxHighlightAck; + | SyntaxHighlightRequest + | SyntaxHighlightResponse + | SyntaxHighlightAck; /** * References an implementation supplied by an existing Ace mode. @@ -26,7 +28,6 @@ export interface ModeHook { * The arguments and return value must be structured-cloneable. */ // Ace mode extensions have different signatures, so the RPC boundary must accept arbitrary arguments. -// eslint-disable-next-line @typescript-eslint/no-explicit-any export type ModeRpcFunction = (...args: any[]) => unknown; /** A mode function can either reuse an Ace implementation or run remotely. */ @@ -84,7 +85,7 @@ export interface SyntaxHighlightData { */ export type TransferredSyntaxHighlightData = Omit< SyntaxHighlightData, - "indents" | "outdents" | "autoOutdent" + 'indents' | 'outdents' | 'autoOutdent' > & { indents: TransferredModeFunction; outdents: TransferredModeFunction; diff --git a/src/common/cse-machine/package.json b/src/common/cse-machine/package.json index 12cf27d..4267b63 100644 --- a/src/common/cse-machine/package.json +++ b/src/common/cse-machine/package.json @@ -1,5 +1,10 @@ { "name": "@sourceacademy/common-cse-machine", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/common/cse-machine" + }, "version": "0.4.0", "packageManager": "yarn@4.18.1", "description": "Shared protocol/types for the Source Academy CSE machine plugin pair", @@ -25,13 +30,13 @@ } }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", - "@vitest/coverage-istanbul": "^5.0.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", + "@vitest/coverage-istanbul": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/common/cse-machine/rollup.config.mjs b/src/common/cse-machine/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/common/cse-machine/rollup.config.mjs +++ b/src/common/cse-machine/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/common/cse-machine/src/__tests__/common.test.ts b/src/common/cse-machine/src/__tests__/common.test.ts index fa012f3..63732a1 100644 --- a/src/common/cse-machine/src/__tests__/common.test.ts +++ b/src/common/cse-machine/src/__tests__/common.test.ts @@ -1,91 +1,92 @@ -import { describe, it, expect } from "vitest"; +/* eslint-disable vitest/prefer-describe-function-title */ +import { describe, expect, it } from 'vitest'; import { CSE_CHANNEL, CSE_DIRECTORY_ID, CSE_MESSAGE_TYPE_SNAPSHOTS, RUNNER_ID, WEB_ID, + type CseSerializedBinding, + type CseSerializedEnvFrame, + type CseSerializedInstruction, + type CseSerializedValue, type CseSnapshot, type CseSnapshotMessage, - type CseSerializedValue, - type CseSerializedInstruction, - type CseSerializedEnvFrame, - type CseSerializedBinding, -} from "../index"; +} from '../index'; // ── Constants ───────────────────────────────────────────────────────────────── -describe("constants", () => { - it("CSE_CHANNEL has the expected value", () => { - expect(CSE_CHANNEL).toBe("__cse"); +describe('constants', () => { + it('CSE_CHANNEL has the expected value', () => { + expect(CSE_CHANNEL).toBe('__cse'); }); - it("RUNNER_ID has the expected value", () => { - expect(RUNNER_ID).toBe("__runner_cse"); + it('RUNNER_ID has the expected value', () => { + expect(RUNNER_ID).toBe('__runner_cse'); }); - it("WEB_ID has the expected value", () => { - expect(WEB_ID).toBe("__web_cse"); + it('WEB_ID has the expected value', () => { + expect(WEB_ID).toBe('__web_cse'); }); - it("CSE_DIRECTORY_ID has the expected value", () => { - expect(CSE_DIRECTORY_ID).toBe("cse-machine"); + it('CSE_DIRECTORY_ID has the expected value', () => { + expect(CSE_DIRECTORY_ID).toBe('cse-machine'); }); - it("CSE_MESSAGE_TYPE_SNAPSHOTS has the expected value", () => { - expect(CSE_MESSAGE_TYPE_SNAPSHOTS).toBe("snapshots"); + it('CSE_MESSAGE_TYPE_SNAPSHOTS has the expected value', () => { + expect(CSE_MESSAGE_TYPE_SNAPSHOTS).toBe('snapshots'); }); - it("RUNNER_ID and WEB_ID are distinct", () => { + it('RUNNER_ID and WEB_ID are distinct', () => { expect(RUNNER_ID).not.toBe(WEB_ID); }); }); // ── Structured-clone safety ─────────────────────────────────────────────────── -describe("structured-clone safety", () => { - it("a minimal snapshot survives structuredClone", () => { +describe('structured-clone safety', () => { + it('a minimal snapshot survives structuredClone', () => { const snap: CseSnapshot = { stepIndex: 0, control: [], stash: [], - environments: [{ id: "g", name: "global", parentId: null, bindings: [], isActive: true }], + environments: [{ id: 'g', name: 'global', parentId: null, bindings: [], isActive: true }], }; expect(structuredClone(snap)).toEqual(snap); }); - it("a fully-populated snapshot survives structuredClone", () => { + it('a fully-populated snapshot survives structuredClone', () => { const snap: CseSnapshot = { stepIndex: 2, control: [ - { displayText: "call f", tag: "app", metadata: { instrType: "Application", numOfArgs: 1 } }, + { displayText: 'call f', tag: 'app', metadata: { instrType: 'Application', numOfArgs: 1 } }, ], - stash: [{ displayValue: "42", label: "number", tag: "int", metadata: { raw: 42 } }], + stash: [{ displayValue: '42', label: 'number', tag: 'int', metadata: { raw: 42 } }], environments: [ { - id: "e1", - name: "f", - parentId: "g", - bindings: [{ name: "x", value: { displayValue: "1", label: "number" }, isConst: true }], - heapObjects: [{ displayValue: "", label: "closure" }], + id: 'e1', + name: 'f', + parentId: 'g', + bindings: [{ name: 'x', value: { displayValue: '1', label: 'number' }, isConst: true }], + heapObjects: [{ displayValue: '', label: 'closure' }], isActive: true, isOnCallStack: true, - closureFrameId: "g", - globalNames: ["x"], + closureFrameId: 'g', + globalNames: ['x'], }, - { id: "g", name: "global", parentId: null, bindings: [], isActive: false }, + { id: 'g', name: 'global', parentId: null, bindings: [], isActive: false }, ], currentLine: 3, }; expect(structuredClone(snap)).toEqual(snap); }); - it("a CseSnapshotMessage with multiple snapshots survives structuredClone", () => { + it('a CseSnapshotMessage with multiple snapshots survives structuredClone', () => { const msg: CseSnapshotMessage = { type: CSE_MESSAGE_TYPE_SNAPSHOTS, snapshots: [ { stepIndex: 0, control: [], stash: [], environments: [], currentLine: 1 }, - { stepIndex: 1, control: [{ displayText: "pop" }], stash: [], environments: [] }, + { stepIndex: 1, control: [{ displayText: 'pop' }], stash: [], environments: [] }, ], totalSteps: 2, breakpointSteps: [1], @@ -96,8 +97,8 @@ describe("structured-clone safety", () => { // ── CseSnapshotMessage ──────────────────────────────────────────────────────── -describe("CseSnapshotMessage", () => { - it("totalSteps matches snapshots.length for a batch", () => { +describe('CseSnapshotMessage', () => { + it('totalSteps matches snapshots.length for a batch', () => { const snapshots: CseSnapshot[] = [ { stepIndex: 0, control: [], stash: [], environments: [] }, { stepIndex: 1, control: [], stash: [], environments: [] }, @@ -113,7 +114,7 @@ describe("CseSnapshotMessage", () => { expect(msg.snapshots.length).toBe(msg.totalSteps); }); - it("accepts an empty snapshots array", () => { + it('accepts an empty snapshots array', () => { const msg: CseSnapshotMessage = { type: CSE_MESSAGE_TYPE_SNAPSHOTS, snapshots: [], @@ -124,7 +125,7 @@ describe("CseSnapshotMessage", () => { expect(msg.totalSteps).toBe(0); }); - it("type discriminator is CSE_MESSAGE_TYPE_SNAPSHOTS", () => { + it('type discriminator is CSE_MESSAGE_TYPE_SNAPSHOTS', () => { const msg: CseSnapshotMessage = { type: CSE_MESSAGE_TYPE_SNAPSHOTS, snapshots: [], @@ -134,10 +135,10 @@ describe("CseSnapshotMessage", () => { expect(msg.type).toBe(CSE_MESSAGE_TYPE_SNAPSHOTS); }); - it("breakpointSteps carries the step indices where a breakpoint sits on top of the control", () => { + it('breakpointSteps carries the step indices where a breakpoint sits on top of the control', () => { const snapshots: CseSnapshot[] = [ { stepIndex: 0, control: [], stash: [], environments: [] }, - { stepIndex: 1, control: [{ displayText: "breakpoint()" }], stash: [], environments: [] }, + { stepIndex: 1, control: [{ displayText: 'breakpoint()' }], stash: [], environments: [] }, { stepIndex: 2, control: [], stash: [], environments: [] }, ]; const msg: CseSnapshotMessage = { @@ -149,7 +150,7 @@ describe("CseSnapshotMessage", () => { expect(msg.breakpointSteps).toEqual([1]); }); - it("breakpointSteps may be empty when no breakpoint was hit", () => { + it('breakpointSteps may be empty when no breakpoint was hit', () => { const msg: CseSnapshotMessage = { type: CSE_MESSAGE_TYPE_SNAPSHOTS, snapshots: [], @@ -162,8 +163,8 @@ describe("CseSnapshotMessage", () => { // ── CseSnapshot optional fields ─────────────────────────────────────────────── -describe("CseSnapshot optional fields", () => { - it("currentLine is optional and may be omitted", () => { +describe('CseSnapshot optional fields', () => { + it('currentLine is optional and may be omitted', () => { const snap: CseSnapshot = { stepIndex: 0, control: [], @@ -173,7 +174,7 @@ describe("CseSnapshot optional fields", () => { expect(snap.currentLine).toBeUndefined(); }); - it("currentLine is preserved when set", () => { + it('currentLine is preserved when set', () => { const snap: CseSnapshot = { stepIndex: 5, control: [], @@ -184,7 +185,7 @@ describe("CseSnapshot optional fields", () => { expect(snap.currentLine).toBe(12); }); - it("stepIndex is preserved across multiple steps", () => { + it('stepIndex is preserved across multiple steps', () => { const indices = [0, 1, 2, 10, 99]; const steps = indices.map(i => ({ stepIndex: i, @@ -198,41 +199,41 @@ describe("CseSnapshot optional fields", () => { // ── CseSerializedValue ──────────────────────────────────────────────────────── -describe("CseSerializedValue", () => { - it("accepts minimal value with displayValue and label only", () => { - const v: CseSerializedValue = { displayValue: "true", label: "bool" }; +describe('CseSerializedValue', () => { + it('accepts minimal value with displayValue and label only', () => { + const v: CseSerializedValue = { displayValue: 'true', label: 'bool' }; expect(v.tag).toBeUndefined(); expect(v.metadata).toBeUndefined(); }); - it("accepts value with all optional fields", () => { + it('accepts value with all optional fields', () => { const v: CseSerializedValue = { - displayValue: "", - label: "closure", - tag: "fn", - metadata: { closureFrameId: "e1", params: ["x", "y"] }, + displayValue: '', + label: 'closure', + tag: 'fn', + metadata: { closureFrameId: 'e1', params: ['x', 'y'] }, }; - expect(v.displayValue).toBe(""); - expect(v.metadata).toEqual({ closureFrameId: "e1", params: ["x", "y"] }); + expect(v.displayValue).toBe(''); + expect(v.metadata).toEqual({ closureFrameId: 'e1', params: ['x', 'y'] }); }); }); // ── CseSerializedInstruction ────────────────────────────────────────────────── -describe("CseSerializedInstruction", () => { - it("accepts minimal instruction with displayText only", () => { - const instr: CseSerializedInstruction = { displayText: "pop" }; +describe('CseSerializedInstruction', () => { + it('accepts minimal instruction with displayText only', () => { + const instr: CseSerializedInstruction = { displayText: 'pop' }; expect(instr.tag).toBeUndefined(); expect(instr.metadata).toBeUndefined(); }); - it("accepts instruction with metadata for animation dispatch", () => { + it('accepts instruction with metadata for animation dispatch', () => { const instr: CseSerializedInstruction = { - displayText: "call f", - metadata: { instrType: "Application", numOfArgs: 2, startLine: 4, endLine: 4 }, + displayText: 'call f', + metadata: { instrType: 'Application', numOfArgs: 2, startLine: 4, endLine: 4 }, }; expect(instr.metadata).toEqual({ - instrType: "Application", + instrType: 'Application', numOfArgs: 2, startLine: 4, endLine: 4, @@ -242,11 +243,11 @@ describe("CseSerializedInstruction", () => { // ── CseSerializedEnvFrame ───────────────────────────────────────────────────── -describe("CseSerializedEnvFrame", () => { - it("root frame has null parentId", () => { +describe('CseSerializedEnvFrame', () => { + it('root frame has null parentId', () => { const frame: CseSerializedEnvFrame = { - id: "g", - name: "global", + id: 'g', + name: 'global', parentId: null, bindings: [], isActive: true, @@ -254,21 +255,21 @@ describe("CseSerializedEnvFrame", () => { expect(frame.parentId).toBeNull(); }); - it("child frame references parent by id", () => { + it('child frame references parent by id', () => { const frame: CseSerializedEnvFrame = { - id: "e1", - name: "f", - parentId: "g", + id: 'e1', + name: 'f', + parentId: 'g', bindings: [], isActive: true, }; - expect(frame.parentId).toBe("g"); + expect(frame.parentId).toBe('g'); }); - it("optional fields are absent when not set", () => { + it('optional fields are absent when not set', () => { const frame: CseSerializedEnvFrame = { - id: "g", - name: "global", + id: 'g', + name: 'global', parentId: null, bindings: [], isActive: false, @@ -279,67 +280,67 @@ describe("CseSerializedEnvFrame", () => { expect(frame.globalNames).toBeUndefined(); }); - it("globalNames carries names that resolve via the global frame instead of the enclosing-scope chain", () => { + it('globalNames carries names that resolve via the global frame instead of the enclosing-scope chain', () => { const frame: CseSerializedEnvFrame = { - id: "e1", - name: "f", - parentId: "g", + id: 'e1', + name: 'f', + parentId: 'g', bindings: [], isActive: true, - globalNames: ["x", "y"], + globalNames: ['x', 'y'], }; - expect(frame.globalNames).toEqual(["x", "y"]); + expect(frame.globalNames).toEqual(['x', 'y']); }); - it("heapObjects carries anonymous closures not bound to a name", () => { + it('heapObjects carries anonymous closures not bound to a name', () => { const frame: CseSerializedEnvFrame = { - id: "g", - name: "global", + id: 'g', + name: 'global', parentId: null, bindings: [], isActive: true, - heapObjects: [{ displayValue: "", label: "closure" }], + heapObjects: [{ displayValue: '', label: 'closure' }], }; expect(frame.heapObjects).toHaveLength(1); }); - it("closureFrameId links a frame to its defining environment", () => { + it('closureFrameId links a frame to its defining environment', () => { const frame: CseSerializedEnvFrame = { - id: "e2", - name: "lambda", - parentId: "g", + id: 'e2', + name: 'lambda', + parentId: 'g', bindings: [], isActive: false, - closureFrameId: "g", + closureFrameId: 'g', }; - expect(frame.closureFrameId).toBe("g"); + expect(frame.closureFrameId).toBe('g'); }); }); // ── CseSerializedBinding ────────────────────────────────────────────────────── -describe("CseSerializedBinding", () => { - it("isConst is optional and defaults to undefined", () => { +describe('CseSerializedBinding', () => { + it('isConst is optional and defaults to undefined', () => { const b: CseSerializedBinding = { - name: "x", - value: { displayValue: "5", label: "number" }, + name: 'x', + value: { displayValue: '5', label: 'number' }, }; expect(b.isConst).toBeUndefined(); }); - it("marks const bindings explicitly", () => { + it('marks const bindings explicitly', () => { const b: CseSerializedBinding = { - name: "PI", - value: { displayValue: "3.14159", label: "number" }, + name: 'PI', + value: { displayValue: '3.14159', label: 'number' }, isConst: true, }; expect(b.isConst).toBe(true); }); - it("marks mutable bindings explicitly", () => { + it('marks mutable bindings explicitly', () => { const b: CseSerializedBinding = { - name: "counter", - value: { displayValue: "0", label: "number" }, + name: 'counter', + value: { displayValue: '0', label: 'number' }, isConst: false, }; expect(b.isConst).toBe(false); diff --git a/src/common/cse-machine/src/index.ts b/src/common/cse-machine/src/index.ts index f9f1af5..18378dc 100644 --- a/src/common/cse-machine/src/index.ts +++ b/src/common/cse-machine/src/index.ts @@ -17,22 +17,22 @@ */ /** The channel the CSE machine runner and host plugins communicate over. */ -export const CSE_CHANNEL = "__cse"; +export const CSE_CHANNEL = '__cse'; /** The id of the runner (worker-side) CSE machine plugin. */ -export const RUNNER_ID = "__runner_cse"; +export const RUNNER_ID = '__runner_cse'; /** The id of the web/host (browser-side) CSE machine plugin. */ -export const WEB_ID = "__web_cse"; +export const WEB_ID = '__web_cse'; /** * The id used to look the CSE machine up in the plugin directory (i.e. the argument to * `IRunnerPlugin.hostLoadPlugin`). The host resolves this to the web plugin's bundle URL. */ -export const CSE_DIRECTORY_ID = "cse-machine"; +export const CSE_DIRECTORY_ID = 'cse-machine'; /** The {@link CseSnapshotMessage.type} discriminator for a batch of snapshots. */ -export const CSE_MESSAGE_TYPE_SNAPSHOTS = "snapshots"; +export const CSE_MESSAGE_TYPE_SNAPSHOTS = 'snapshots'; /** * A single value on the stash or bound in an environment, serialized to plain JSON. @@ -181,7 +181,7 @@ export interface CseDiagramAnchor extends CseDiagramPoint { * drawn, e.g. when it is scrolled or collapsed out of the view. */ export type CseDiagramAnchorResolver = (target: { - kind: "object" | "frame"; + kind: 'object' | 'frame'; /** A {@link CseSerializedValue.objectId} or a {@link CseSerializedEnvFrame.id}. */ id: string; }) => CseDiagramAnchor | null; diff --git a/src/common/data-visualizer/jest.config.cjs b/src/common/data-visualizer/jest.config.cjs deleted file mode 100644 index 06bb4a9..0000000 --- a/src/common/data-visualizer/jest.config.cjs +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - preset: "ts-jest/presets/js-with-ts-esm", - testEnvironment: "node", - transform: { - "^.+\\.tsx?$": [ - "ts-jest", - { - useESM: true, - }, - ], - }, - testPathIgnorePatterns: [".*?dist/"], - coverageReporters: ["lcov"], -}; diff --git a/src/common/data-visualizer/package.json b/src/common/data-visualizer/package.json index 8825572..5d0e0c5 100644 --- a/src/common/data-visualizer/package.json +++ b/src/common/data-visualizer/package.json @@ -6,8 +6,8 @@ "scripts": { "build": "rollup -c", "prepack": "yarn build", - "test": "jest", - "test-coverage": "jest --coverage" + "test": "vitest", + "test-coverage": "vitest --coverage" }, "license": "ISC", "files": [ @@ -24,14 +24,17 @@ } }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", - "@types/jest": "^30.0.0", - "jest": "^30.4.2", - "rollup": "^4.60.2", - "ts-jest": "^29.4.11", - "tslib": "^2.8.1", - "typescript": "^6.0.3" + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/common/data-visualizer" } } diff --git a/src/common/data-visualizer/rollup.config.mjs b/src/common/data-visualizer/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/common/data-visualizer/rollup.config.mjs +++ b/src/common/data-visualizer/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/common/data-visualizer/src/__tests__/common.test.ts b/src/common/data-visualizer/src/__tests__/common.test.ts index 09a17bf..2e30c92 100644 --- a/src/common/data-visualizer/src/__tests__/common.test.ts +++ b/src/common/data-visualizer/src/__tests__/common.test.ts @@ -1,11 +1,11 @@ -import { expect, test } from "vitest"; +import { expect, test } from 'vitest'; -import { DATA_VISUALIZER_CHANNEL_ID, RUNNER_ID, WEB_ID } from ".."; +import { DATA_VISUALIZER_CHANNEL_ID, RUNNER_ID, WEB_ID } from '..'; -test("runner and web ids are distinct", () => { +test('runner and web ids are distinct', () => { expect(RUNNER_ID).not.toBe(WEB_ID); }); -test("has a stable channel id", () => { - expect(DATA_VISUALIZER_CHANNEL_ID).toBe("__data_visualizer"); +test('has a stable channel id', () => { + expect(DATA_VISUALIZER_CHANNEL_ID).toBe('__data_visualizer'); }); diff --git a/src/common/data-visualizer/src/index.ts b/src/common/data-visualizer/src/index.ts index cdf54cd..8e36ab3 100644 --- a/src/common/data-visualizer/src/index.ts +++ b/src/common/data-visualizer/src/index.ts @@ -20,19 +20,19 @@ */ /** The channel the data visualizer runner and host plugins communicate over. */ -export const DATA_VISUALIZER_CHANNEL_ID = "__data_visualizer"; +export const DATA_VISUALIZER_CHANNEL_ID = '__data_visualizer'; /** The id of the runner (worker-side) data visualizer plugin. */ -export const RUNNER_ID = "__runner_data_visualizer"; +export const RUNNER_ID = '__runner_data_visualizer'; /** The id of the web/host (browser-side) data visualizer plugin. */ -export const WEB_ID = "__web_data_visualizer"; +export const WEB_ID = '__web_data_visualizer'; /** * The id used to look the data visualizer up in the plugin directory (i.e. the argument to * `IRunnerPlugin.hostLoadPlugin`). The host resolves this to the web plugin's bundle URL. */ -export const DATA_VISUALIZER_DIRECTORY_ID = "data-visualizer"; +export const DATA_VISUALIZER_DIRECTORY_ID = 'data-visualizer'; /** * Identifies one distinct compound value (a pair/array or a function) within a single top-level @@ -59,9 +59,9 @@ export type RefId = number; * distinguishes "pair" from "list" for classification purposes by `children.length`, not by tag. */ export type SerializedDataVisualizerNode = - | { type: "array"; refId: RefId; children: SerializedDataVisualizerNode[] } + | { type: 'array'; refId: RefId; children: SerializedDataVisualizerNode[] } | { - type: "empty"; + type: 'empty'; /** * How the language writes its empty value (e.g. `"None"` for Python), shown when the empty value * is drawn on its own. Inside a pair/array it is always drawn as a diagonal slash instead. Omit it @@ -69,9 +69,9 @@ export type SerializedDataVisualizerNode = */ displayValue?: string; } - | { type: "leaf"; displayValue: string; label: string } - | { type: "function"; refId: RefId; displayValue: string } - | { type: "ref"; refId: RefId }; + | { type: 'leaf'; displayValue: string; label: string } + | { type: 'function'; refId: RefId; displayValue: string } + | { type: 'ref'; refId: RefId }; /** One row = the fully-serialized arguments of a single `draw_data(...)` call. */ export type SerializedDataVisualizerRow = SerializedDataVisualizerNode[]; @@ -86,7 +86,7 @@ export type SerializedDataVisualizerRow = SerializedDataVisualizerNode[]; * (e.g. because the tab was closed) is always brought fully up to date by the next one. */ export interface DataVisualizerRowsMessage { - type: "rows"; + type: 'rows'; rows: SerializedDataVisualizerRow[]; } @@ -95,7 +95,7 @@ export interface DataVisualizerRowsMessage { * display when the data visualizer tab is (re)opened without re-running the program. */ export interface DataVisualizerRequestMessage { - type: "request"; + type: 'request'; } /** Every message that may cross the {@link DATA_VISUALIZER_CHANNEL_ID} channel. */ diff --git a/src/common/data-visualizer/tsconfig.json b/src/common/data-visualizer/tsconfig.json index 88a8445..c7482f9 100644 --- a/src/common/data-visualizer/tsconfig.json +++ b/src/common/data-visualizer/tsconfig.json @@ -5,7 +5,6 @@ "compilerOptions": { "declaration": true, "outDir": "./dist", - "rootDir": "./src", - "types": ["jest"] + "rootDir": "./src" } } diff --git a/src/common/e-stepper/jest.config.cjs b/src/common/e-stepper/jest.config.cjs deleted file mode 100644 index 06bb4a9..0000000 --- a/src/common/e-stepper/jest.config.cjs +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - preset: "ts-jest/presets/js-with-ts-esm", - testEnvironment: "node", - transform: { - "^.+\\.tsx?$": [ - "ts-jest", - { - useESM: true, - }, - ], - }, - testPathIgnorePatterns: [".*?dist/"], - coverageReporters: ["lcov"], -}; diff --git a/src/common/e-stepper/package.json b/src/common/e-stepper/package.json index c3edb4e..57b3899 100644 --- a/src/common/e-stepper/package.json +++ b/src/common/e-stepper/package.json @@ -6,8 +6,8 @@ "scripts": { "build": "rollup -c", "prepack": "yarn build", - "test": "jest", - "test-coverage": "jest --coverage" + "test": "vitest", + "test-coverage": "vitest --coverage" }, "license": "ISC", "files": [ @@ -28,16 +28,14 @@ "@sourceacademy/common-stepper": ">=0.0.4" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-cse-machine": "workspace:*", "@sourceacademy/common-stepper": "workspace:*", - "@types/jest": "^30.0.0", - "jest": "^30.4.2", - "rollup": "^4.60.2", - "ts-jest": "^29.4.11", - "tslib": "^2.8.1", - "typescript": "^6.0.3" + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/common/e-stepper/rollup.config.mjs b/src/common/e-stepper/rollup.config.mjs index 238a07c..aaf820c 100644 --- a/src/common/e-stepper/rollup.config.mjs +++ b/src/common/e-stepper/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/common/e-stepper/src/__tests__/common.test.ts b/src/common/e-stepper/src/__tests__/common.test.ts index 4b08a60..672222c 100644 --- a/src/common/e-stepper/src/__tests__/common.test.ts +++ b/src/common/e-stepper/src/__tests__/common.test.ts @@ -1,87 +1,87 @@ -import { expect, test } from "vitest"; +import { RUNNER_ID as STEPPER_RUNNER_ID, STEPPER_CHANNEL_ID } from '@sourceacademy/common-stepper'; +import { expect, test } from 'vitest'; -import { RUNNER_ID as STEPPER_RUNNER_ID, STEPPER_CHANNEL_ID } from "@sourceacademy/common-stepper"; import { + ENV_BLOCK_NODE_TYPE, E_STEPPER_CHANNEL_ID, E_STEPPER_DIRECTORY_ID, - ENV_BLOCK_NODE_TYPE, REF_NODE_TYPE, RUNNER_ID, WEB_ID, isEnvBlockNode, isRefNode, type EStepperStep, -} from ".."; +} from '..'; -test("runner and web ids are distinct", () => { +test('runner and web ids are distinct', () => { expect(RUNNER_ID).not.toBe(WEB_ID); }); test("has stable ids, distinct from the stepper's", () => { - expect(E_STEPPER_CHANNEL_ID).toBe("__e_stepper"); - expect(E_STEPPER_DIRECTORY_ID).toBe("e-stepper"); + expect(E_STEPPER_CHANNEL_ID).toBe('__e_stepper'); + expect(E_STEPPER_DIRECTORY_ID).toBe('e-stepper'); expect(E_STEPPER_CHANNEL_ID).not.toBe(STEPPER_CHANNEL_ID); expect(RUNNER_ID).not.toBe(STEPPER_RUNNER_ID); }); -test("type guards recognise EnvBlock and Ref nodes", () => { - const ref = { type: REF_NODE_TYPE, nodeId: "1", objectId: "#1" }; - const block = { type: ENV_BLOCK_NODE_TYPE, nodeId: "2", envId: "E1", body: [ref] }; - const literal = { type: "Literal", nodeId: "3", value: 1 }; +test('type guards recognise EnvBlock and Ref nodes', () => { + const ref = { type: REF_NODE_TYPE, nodeId: '1', objectId: '#1' }; + const block = { type: ENV_BLOCK_NODE_TYPE, nodeId: '2', envId: 'E1', body: [ref] }; + const literal = { type: 'Literal', nodeId: '3', value: 1 }; expect(isRefNode(ref)).toBe(true); expect(isEnvBlockNode(block)).toBe(true); expect(isRefNode(literal)).toBe(false); expect(isEnvBlockNode(literal)).toBe(false); }); -test("a step survives structured cloning unchanged", () => { +test('a step survives structured cloning unchanged', () => { // The protocol's contract: everything crossing the channel is plain JSON. const step: EStepperStep = { ast: { - type: "Program", - nodeId: "0", + type: 'Program', + nodeId: '0', body: [ { type: ENV_BLOCK_NODE_TYPE, - nodeId: "1", - envId: "E1", - body: [{ type: REF_NODE_TYPE, nodeId: "2", objectId: "#2" }], + nodeId: '1', + envId: 'E1', + body: [{ type: REF_NODE_TYPE, nodeId: '2', objectId: '#2' }], }, ], }, - markers: [{ redexId: "1", redexType: "beforeMarker", explanation: "return withdraw" }], + markers: [{ redexId: '1', redexType: 'beforeMarker', explanation: 'return withdraw' }], frames: [ - { id: "Global", name: "global", parentId: null, isGarbage: false, bindings: [] }, + { id: 'Global', name: 'global', parentId: null, isGarbage: false, bindings: [] }, { - id: "E1", - name: "make_withdraw", - parentId: "Global", + id: 'E1', + name: 'make_withdraw', + parentId: 'Global', isGarbage: false, bindings: [ - { name: "balance", value: { kind: "primitive", display: "100", label: "int" } }, - { name: "withdraw", value: { kind: "ref", objectId: "#2" } }, + { name: 'balance', value: { kind: 'primitive', display: '100', label: 'int' } }, + { name: 'withdraw', value: { kind: 'ref', objectId: '#2' } }, ], }, ], heap: [ { - kind: "function", - id: "#2", - name: "withdraw", - params: ["amount"], - envId: "E1", - source: "def withdraw(amount): ...", + kind: 'function', + id: '#2', + name: 'withdraw', + params: ['amount'], + envId: 'E1', + source: 'def withdraw(amount): ...', isGarbage: false, }, { - kind: "list", - id: "#3", - elements: [{ kind: "builtin", name: "print" }, { kind: "unassigned" }], + kind: 'list', + id: '#3', + elements: [{ kind: 'builtin', name: 'print' }, { kind: 'unassigned' }], isGarbage: true, }, ], - activeFrameId: "E1", - lookups: [{ frameId: "E1", name: "withdraw" }], + activeFrameId: 'E1', + lookups: [{ frameId: 'E1', name: 'withdraw' }], }; expect(structuredClone(step)).toEqual(step); }); diff --git a/src/common/e-stepper/src/index.ts b/src/common/e-stepper/src/index.ts index 46f389e..f2e817c 100644 --- a/src/common/e-stepper/src/index.ts +++ b/src/common/e-stepper/src/index.ts @@ -24,38 +24,38 @@ * - {@link RefNode}: a reference to a heap object. */ -import type { CseSnapshot } from "@sourceacademy/common-cse-machine"; +import type { CseSnapshot } from '@sourceacademy/common-cse-machine'; import type { SerializedMarker, SerializedStepperNode, SerializedStepperStep, SyntaxProfile, -} from "@sourceacademy/common-stepper"; +} from '@sourceacademy/common-stepper'; /** The channel the e-stepper runner and host plugins communicate over. */ -export const E_STEPPER_CHANNEL_ID = "__e_stepper"; +export const E_STEPPER_CHANNEL_ID = '__e_stepper'; /** The id of the runner (worker-side) e-stepper plugin. */ -export const RUNNER_ID = "__runner_e_stepper"; +export const RUNNER_ID = '__runner_e_stepper'; /** The id of the web/host (browser-side) e-stepper plugin. */ -export const WEB_ID = "__web_e_stepper"; +export const WEB_ID = '__web_e_stepper'; /** * The id used to look the e-stepper up in the plugin directory (i.e. the argument to * `IRunnerPlugin.hostLoadPlugin`). The host resolves this to the web plugin's bundle URL. */ -export const E_STEPPER_DIRECTORY_ID = "e-stepper"; +export const E_STEPPER_DIRECTORY_ID = 'e-stepper'; /* -------------------------------------------------------------------------- */ /* Program nodes into the store */ /* -------------------------------------------------------------------------- */ /** The `type` of an {@link EnvBlockNode}. */ -export const ENV_BLOCK_NODE_TYPE = "EnvBlock"; +export const ENV_BLOCK_NODE_TYPE = 'EnvBlock'; /** The `type` of a {@link RefNode}. */ -export const REF_NODE_TYPE = "Ref"; +export const REF_NODE_TYPE = 'Ref'; /** * A function body under evaluation, together with the frame it is evaluated in. Created when a @@ -97,13 +97,13 @@ export type EStepperValue = * A primitive value, shown inline. `display` is the already-rendered text (e.g. `"3.0"`, * `"'hello'"`, `"None"`); `label` is a coarse type tag (e.g. `"int"`, `"float"`, `"str"`). */ - | { kind: "primitive"; display: string; label: string } + | { kind: 'primitive'; display: string; label: string } /** A reference to a heap object. */ - | { kind: "ref"; objectId: string } + | { kind: 'ref'; objectId: string } /** A builtin function, shown by its name (builtins are not heap objects and not drawn). */ - | { kind: "builtin"; name: string } + | { kind: 'builtin'; name: string } /** A binding that exists but has not been assigned yet (e.g. a function's local). */ - | { kind: "unassigned" }; + | { kind: 'unassigned' }; /** A name → value binding in a frame. */ export interface EStepperBinding { @@ -130,7 +130,7 @@ export interface EStepperFrame { /** A function object: what a function definition or lambda expression evaluates to. */ export interface EStepperFunctionObject { - kind: "function"; + kind: 'function'; /** Stable id for the whole run, unique among heap objects; also its label (e.g. `"#3"`). */ id: string; /** The function's name, or absent for an anonymous function (e.g. a lambda). */ @@ -146,7 +146,7 @@ export interface EStepperFunctionObject { /** A mutable sequence, e.g. a Python list. A pair is a list of two elements. */ export interface EStepperListObject { - kind: "list"; + kind: 'list'; /** Stable id for the whole run, unique among heap objects; also its label (e.g. `"#7"`). */ id: string; elements: EStepperValue[]; @@ -195,7 +195,7 @@ export type { SerializedMarker, SerializedStepperNode, SyntaxProfile }; /** Runner → host: the computed evaluation steps for the most recent run. */ export interface EStepperStepsMessage { - type: "steps"; + type: 'steps'; steps: EStepperStep[]; /** * The language's rendering rules for ordinary program nodes, as for the stepper. `EnvBlock` and @@ -206,13 +206,13 @@ export interface EStepperStepsMessage { /** Runner → host: stepping failed (e.g. a parse error). */ export interface EStepperErrorMessage { - type: "error"; + type: 'error'; error: string; } /** Host → runner: asks the runner to (re)send the steps it last computed. */ export interface EStepperRequestMessage { - type: "request"; + type: 'request'; } /** Every message that may cross the {@link E_STEPPER_CHANNEL_ID} channel. */ diff --git a/src/common/e-stepper/tsconfig.json b/src/common/e-stepper/tsconfig.json index 88a8445..c7482f9 100644 --- a/src/common/e-stepper/tsconfig.json +++ b/src/common/e-stepper/tsconfig.json @@ -5,7 +5,6 @@ "compilerOptions": { "declaration": true, "outDir": "./dist", - "rootDir": "./src", - "types": ["jest"] + "rootDir": "./src" } } diff --git a/src/common/module-loader/package.json b/src/common/module-loader/package.json index 7f15ee7..060d063 100644 --- a/src/common/module-loader/package.json +++ b/src/common/module-loader/package.json @@ -3,6 +3,11 @@ "version": "0.0.1", "packageManager": "yarn@4.18.1", "description": "The common types for loading modules in Source Academy", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/common/module-loader" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build" @@ -22,12 +27,11 @@ } }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:" } } diff --git a/src/common/module-loader/rollup.config.mjs b/src/common/module-loader/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/common/module-loader/rollup.config.mjs +++ b/src/common/module-loader/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/common/module-loader/src/index.ts b/src/common/module-loader/src/index.ts index 3ca4f9d..d6e3776 100644 --- a/src/common/module-loader/src/index.ts +++ b/src/common/module-loader/src/index.ts @@ -1,12 +1,12 @@ -export const WEB_ID = "__web_module_loader"; -export const RUNNER_ID = "__runner_module_loader"; +export const WEB_ID = '__web_module_loader'; +export const RUNNER_ID = '__runner_module_loader'; -export const CHANNEL_ID = "module_config"; +export const CHANNEL_ID = 'module_config'; export enum ModuleLoaderMessageType { - REQUEST_MODULE = "request_module", - MODULE_RESPONSE = "module_response", - MODULE_ERROR = "module_error", + REQUEST_MODULE = 'request_module', + MODULE_RESPONSE = 'module_response', + MODULE_ERROR = 'module_error', } export type ModuleLoaderMessage = diff --git a/src/common/stepper/jest.config.cjs b/src/common/stepper/jest.config.cjs deleted file mode 100644 index 06bb4a9..0000000 --- a/src/common/stepper/jest.config.cjs +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - preset: "ts-jest/presets/js-with-ts-esm", - testEnvironment: "node", - transform: { - "^.+\\.tsx?$": [ - "ts-jest", - { - useESM: true, - }, - ], - }, - testPathIgnorePatterns: [".*?dist/"], - coverageReporters: ["lcov"], -}; diff --git a/src/common/stepper/package.json b/src/common/stepper/package.json index e0f057d..befe9ce 100644 --- a/src/common/stepper/package.json +++ b/src/common/stepper/package.json @@ -3,11 +3,16 @@ "version": "0.0.4", "packageManager": "yarn@4.18.1", "description": "Shared, language-agnostic protocol for the Source Academy stepper plugin pair", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/common/stepper" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build", - "test": "jest", - "test-coverage": "jest --coverage" + "test": "vitest", + "test-coverage": "vitest --coverage" }, "license": "ISC", "files": [ @@ -24,14 +29,12 @@ } }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", - "@types/jest": "^30.0.0", - "jest": "^30.4.2", - "rollup": "^4.60.2", - "ts-jest": "^29.4.11", - "tslib": "^2.8.1", - "typescript": "^6.0.3" + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/common/stepper/rollup.config.mjs b/src/common/stepper/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/common/stepper/rollup.config.mjs +++ b/src/common/stepper/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/common/stepper/src/__tests__/common.test.ts b/src/common/stepper/src/__tests__/common.test.ts index 766b5d8..819cc8f 100644 --- a/src/common/stepper/src/__tests__/common.test.ts +++ b/src/common/stepper/src/__tests__/common.test.ts @@ -1,11 +1,11 @@ -import { expect, test } from "vitest"; +import { expect, test } from 'vitest'; -import { RUNNER_ID, STEPPER_CHANNEL_ID, WEB_ID } from ".."; +import { RUNNER_ID, STEPPER_CHANNEL_ID, WEB_ID } from '..'; -test("runner and web ids are distinct", () => { +test('runner and web ids are distinct', () => { expect(RUNNER_ID).not.toBe(WEB_ID); }); -test("has a stable channel id", () => { - expect(STEPPER_CHANNEL_ID).toBe("__stepper"); +test('has a stable channel id', () => { + expect(STEPPER_CHANNEL_ID).toBe('__stepper'); }); diff --git a/src/common/stepper/src/index.ts b/src/common/stepper/src/index.ts index 39d9440..79beb8b 100644 --- a/src/common/stepper/src/index.ts +++ b/src/common/stepper/src/index.ts @@ -12,19 +12,19 @@ */ /** The channel the stepper runner and host plugins communicate over. */ -export const STEPPER_CHANNEL_ID = "__stepper"; +export const STEPPER_CHANNEL_ID = '__stepper'; /** The id of the runner (worker-side) stepper plugin. */ -export const RUNNER_ID = "__runner_stepper"; +export const RUNNER_ID = '__runner_stepper'; /** The id of the web/host (browser-side) stepper plugin. */ -export const WEB_ID = "__web_stepper"; +export const WEB_ID = '__web_stepper'; /** * The id used to look the stepper up in the plugin directory (i.e. the argument to * `IRunnerPlugin.hostLoadPlugin`). The host resolves this to the web plugin's bundle URL. */ -export const STEPPER_DIRECTORY_ID = "stepper"; +export const STEPPER_DIRECTORY_ID = 'stepper'; /** * A single AST node, serialized to plain JSON. @@ -61,7 +61,7 @@ export interface SerializedMarker { */ redexNodeType?: string; /** Whether the highlight applies before or after the reduction. */ - redexType?: "beforeMarker" | "afterMarker"; + redexType?: 'beforeMarker' | 'afterMarker'; /** A human-readable explanation of the reduction, shown alongside the step. */ explanation?: string; } @@ -100,7 +100,7 @@ export interface SerializedStepperStep { */ /** A CSS class hint for a rendered token, mapped by the host to its stepper colour classes. */ -export type StepperTokenClass = "operator" | "identifier" | "literal" | "conditional"; +export type StepperTokenClass = 'operator' | 'identifier' | 'literal' | 'conditional'; /** * One piece of a node's render template. A template is an ordered list of parts; the host emits each @@ -213,7 +213,7 @@ export interface SyntaxProfile { /** Runner → host: the computed evaluation steps for the most recent run. */ export interface StepperStepsMessage { - type: "steps"; + type: 'steps'; steps: SerializedStepperStep[]; /** * The language's rendering rules. Optional and run-level (the same for every step). When absent, @@ -224,7 +224,7 @@ export interface StepperStepsMessage { /** Runner → host: stepping failed (e.g. a parse error). */ export interface StepperErrorMessage { - type: "error"; + type: 'error'; error: string; } @@ -233,7 +233,7 @@ export interface StepperErrorMessage { * display when the stepper tab is (re)opened without re-running the program. */ export interface StepperRequestMessage { - type: "request"; + type: 'request'; } /** Every message that may cross the {@link STEPPER_CHANNEL_ID} channel. */ diff --git a/src/common/stepper/tsconfig.json b/src/common/stepper/tsconfig.json index 88a8445..c7482f9 100644 --- a/src/common/stepper/tsconfig.json +++ b/src/common/stepper/tsconfig.json @@ -5,7 +5,6 @@ "compilerOptions": { "declaration": true, "outDir": "./dist", - "rootDir": "./src", - "types": ["jest"] + "rootDir": "./src" } } diff --git a/src/common/tabs/package.json b/src/common/tabs/package.json index d360f63..63b6aea 100644 --- a/src/common/tabs/package.json +++ b/src/common/tabs/package.json @@ -3,6 +3,11 @@ "version": "0.0.1", "packageManager": "yarn@4.18.1", "description": "Common utilities for tab loading in Source Academy", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/common/tabs" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build" @@ -27,12 +32,12 @@ }, "devDependencies": { "@blueprintjs/icons": "^6.0.0", - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@types/react": "^19.2.17", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3" + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:" } } diff --git a/src/common/tabs/rollup.config.mjs b/src/common/tabs/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/common/tabs/rollup.config.mjs +++ b/src/common/tabs/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/common/tabs/src/index.ts b/src/common/tabs/src/index.ts index 1f1daea..923c1c9 100644 --- a/src/common/tabs/src/index.ts +++ b/src/common/tabs/src/index.ts @@ -1,5 +1,5 @@ -import type { IconName } from "@blueprintjs/icons"; -import type React from "react"; +import type { IconName } from '@blueprintjs/icons'; +import type React from 'react'; /** * The Tab interface represents a tab that can be registered with the tab service. diff --git a/src/common/test/package.json b/src/common/test/package.json index 0c59298..2ab8b5f 100644 --- a/src/common/test/package.json +++ b/src/common/test/package.json @@ -3,6 +3,11 @@ "version": "0.0.1", "packageManager": "yarn@4.12.0", "description": "The test common plugin for the plugins repository", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/common/test" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build" @@ -22,12 +27,12 @@ } }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^4.1.9" + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/common/test/rollup.config.mjs b/src/common/test/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/common/test/rollup.config.mjs +++ b/src/common/test/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/common/test/src/__tests__/common.test.ts b/src/common/test/src/__tests__/common.test.ts index 2407bd5..ccd8ddc 100644 --- a/src/common/test/src/__tests__/common.test.ts +++ b/src/common/test/src/__tests__/common.test.ts @@ -1,6 +1,6 @@ -import { CHANNEL_ID } from ".."; -import { test, expect } from "vitest"; +import { expect, test } from 'vitest'; +import { CHANNEL_ID } from '..'; -test("should have a valid channel id", () => { - expect(CHANNEL_ID).toBe("test"); +test('should have a valid channel id', () => { + expect(CHANNEL_ID).toBe('test'); }); diff --git a/src/common/test/src/index.ts b/src/common/test/src/index.ts index 8ec1998..1625ed6 100644 --- a/src/common/test/src/index.ts +++ b/src/common/test/src/index.ts @@ -1,11 +1,11 @@ -export const WEB_ID = "__web_test"; -export const RUNNER_ID = "__runner_test"; +export const WEB_ID = '__web_test'; +export const RUNNER_ID = '__runner_test'; -export const CHANNEL_ID = "test"; +export const CHANNEL_ID = 'test'; -export type TestMessage = "ping" | "pong"; +export type TestMessage = 'ping' | 'pong'; export type PySlangMessage = - | { type: "run"; code: string } - | { type: "result"; output: string } - | { type: "error"; message: string }; + | { type: 'run'; code: string } + | { type: 'result'; output: string } + | { type: 'error'; message: string }; diff --git a/src/runner/autocomplete/package.json b/src/runner/autocomplete/package.json index d8e540b..4e2c0d7 100644 --- a/src/runner/autocomplete/package.json +++ b/src/runner/autocomplete/package.json @@ -25,19 +25,24 @@ } }, "peerDependencies": { - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "dependencies": { "@sourceacademy/common-autocomplete": "^0.0.1" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", - "@sourceacademy/conductor": "^0.8.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", + "@sourceacademy/conductor": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/runner/autocomplete" } } diff --git a/src/runner/autocomplete/rollup.config.mjs b/src/runner/autocomplete/rollup.config.mjs index e633c7e..99119e2 100644 --- a/src/runner/autocomplete/rollup.config.mjs +++ b/src/runner/autocomplete/rollup.config.mjs @@ -1,22 +1,22 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], external: [/^@sourceacademy\/conductor(?:\/|$)/], -}; +}); diff --git a/src/runner/autocomplete/src/__tests__/runner.test.ts b/src/runner/autocomplete/src/__tests__/runner.test.ts index df5986f..e521284 100644 --- a/src/runner/autocomplete/src/__tests__/runner.test.ts +++ b/src/runner/autocomplete/src/__tests__/runner.test.ts @@ -1,4 +1,3 @@ -import { BaseAutoCompleteRunnerPlugin } from ".."; import { AUTOCOMPLETE_CHANNEL_ID, RUNNER_PLUGIN_ID, @@ -9,20 +8,21 @@ import { type SyntaxHighlightData, type SyntaxHighlightMessage, type TransferredSyntaxHighlightData, -} from "@sourceacademy/common-autocomplete"; +} from '@sourceacademy/common-autocomplete'; import { makeRpc, type IChannel, type IConduit, type IRpcMessage, -} from "@sourceacademy/conductor/conduit"; -import { afterEach, expect, test, vi } from "vitest"; +} from '@sourceacademy/conductor/conduit'; +import { afterEach, expect, test, vi } from 'vitest'; +import { BaseAutoCompleteRunnerPlugin } from '..'; afterEach(() => { vi.useRealTimers(); }); -test("should have a valid channel id", () => { +test('should have a valid channel id', () => { expect(BaseAutoCompleteRunnerPlugin.channelAttach).toEqual([ AUTOCOMPLETE_CHANNEL_ID, SYNTAX_CHANNEL_ID, @@ -35,19 +35,19 @@ class TestRunner extends BaseAutoCompleteRunnerPlugin { get mode(): SyntaxHighlightData { return { highlightRules: {}, - foldingRules: { hookFrom: "ace/mode/folding/cstyle", args: [] }, - lineCommentStart: "//", + foldingRules: { hookFrom: 'ace/mode/folding/cstyle', args: [] }, + lineCommentStart: '//', pairQuotesAfter: {}, indents: (value: string) => `indented:${value}`, - outdents: { hookFrom: "ace/mode/text" }, + outdents: { hookFrom: 'ace/mode/text' }, autoOutdent: (value: string) => `outdented:${value}`, - id: "rpc-test", + id: 'rpc-test', }; } autocomplete(code: string, row: number, column: number): AutoCompleteEntry[] { this.autocompleteCalls.push([code, row, column]); - return [{ name: "result", meta: "var" as AutoCompleteEntry["meta"] }]; + return [{ name: 'result', meta: 'var' as AutoCompleteEntry['meta'] }]; } } @@ -61,7 +61,7 @@ class FunctionOutdentsRunner extends TestRunner { } class TestChannel implements IChannel { - readonly name = "test"; + readonly name = 'test'; private readonly subscribers = new Set<(message: T) => void>(); peer?: TestChannel; @@ -96,55 +96,55 @@ const makeChannelPair = (): [TestChannel, TestChannel] => { return [runner, web]; }; -test("exposes the runner plugin identity", () => { +test('exposes the runner plugin identity', () => { const [runnerAutocomplete] = makeChannelPair(); const [runnerSyntax, webSyntax] = makeChannelPair(); const plugin = new TestRunner( {} as IConduit, [runnerAutocomplete, runnerSyntax] as IChannel[], ); - webSyntax.send({ type: "ack" }); + webSyntax.send({ type: 'ack' }); expect(plugin.id).toBe(RUNNER_PLUGIN_ID); }); -test("responds to autocomplete requests over its Conductor channel", () => { +test('responds to autocomplete requests over its Conductor channel', () => { const [runnerAutocomplete, webAutocomplete] = makeChannelPair(); const [runnerSyntax, webSyntax] = makeChannelPair(); const plugin = new TestRunner( {} as IConduit, [runnerAutocomplete, runnerSyntax] as IChannel[], ); - webSyntax.send({ type: "ack" }); + webSyntax.send({ type: 'ack' }); let response: AutoCompleteMessage | undefined; webAutocomplete.subscribe(message => { - if ((message as AutoCompleteMessage).type === "response") { + if ((message as AutoCompleteMessage).type === 'response') { response = message as AutoCompleteMessage; } }); webAutocomplete.send({ - type: "request", + type: 'request', requestId: 17, - code: "res", + code: 'res', row: 2, column: 3, }); webAutocomplete.send({ - type: "response", + type: 'response', requestId: 99, declarations: [], }); - expect(plugin.autocompleteCalls).toEqual([["res", 2, 3]]); + expect(plugin.autocompleteCalls).toEqual([['res', 2, 3]]); expect(response).toEqual({ - type: "response", + type: 'response', requestId: 17, - declarations: [{ name: "result", meta: "var" }], + declarations: [{ name: 'result', meta: 'var' }], }); }); -test("retries syntax transfer until the web plugin acknowledges it", () => { +test('retries syntax transfer until the web plugin acknowledges it', () => { vi.useFakeTimers(); const [runnerAutocomplete] = makeChannelPair(); const [runnerSyntax, webSyntax] = makeChannelPair(); @@ -152,7 +152,7 @@ test("retries syntax transfer until the web plugin acknowledges it", () => { const responses: SyntaxHighlightMessage[] = []; webSyntax.subscribe(message => { - if ((message as SyntaxHighlightMessage).type === "response") { + if ((message as SyntaxHighlightMessage).type === 'response') { responses.push(message as SyntaxHighlightMessage); } }); @@ -160,12 +160,12 @@ test("retries syntax transfer until the web plugin acknowledges it", () => { vi.advanceTimersByTime(3000); expect(responses).toHaveLength(3); - webSyntax.send({ type: "ack" }); + webSyntax.send({ type: 'ack' }); vi.advanceTimersByTime(3000); expect(responses).toHaveLength(3); }); -test("transfers callable mode properties as RPC references", async () => { +test('transfers callable mode properties as RPC references', async () => { const [runnerAutocomplete] = makeChannelPair(); const [runnerSyntax, webSyntax] = makeChannelPair(); @@ -177,9 +177,9 @@ test("transfers callable mode properties as RPC references", async () => { let transferredMode: TransferredSyntaxHighlightData | undefined; webSyntax.subscribe(message => { const syntaxMessage = message as SyntaxHighlightMessage; - if (syntaxMessage.type === "response") { + if (syntaxMessage.type === 'response') { transferredMode = syntaxMessage.data; - webSyntax.send({ type: "ack" }); + webSyntax.send({ type: 'ack' }); } }); const remote = makeRpc, ModeRpc>( @@ -187,23 +187,23 @@ test("transfers callable mode properties as RPC references", async () => { {}, ); - webSyntax.send({ type: "request" }); + webSyntax.send({ type: 'request' }); - expect(transferredMode?.indents).toEqual({ rpc: "indents" }); - expect(transferredMode?.outdents).toEqual({ hookFrom: "ace/mode/text" }); - expect(transferredMode?.autoOutdent).toEqual({ rpc: "autoOutdent" }); - await expect(remote.indents("line")).resolves.toBe("indented:line"); - await expect(remote.autoOutdent("line")).resolves.toBe("outdented:line"); + expect(transferredMode?.indents).toEqual({ rpc: 'indents' }); + expect(transferredMode?.outdents).toEqual({ hookFrom: 'ace/mode/text' }); + expect(transferredMode?.autoOutdent).toEqual({ rpc: 'autoOutdent' }); + await expect(remote.indents('line')).resolves.toBe('indented:line'); + await expect(remote.autoOutdent('line')).resolves.toBe('outdented:line'); expect(() => ( plugin as unknown as { __callModeFunction(name: keyof ModeRpc, args: unknown[]): unknown; } - ).__callModeFunction("outdents", ["line"]), + ).__callModeFunction('outdents', ['line']), ).toThrow('Mode function "outdents" is configured with a hook and cannot be called over RPC.'); }); -test("exposes a callable outdents mode function over RPC", async () => { +test('exposes a callable outdents mode function over RPC', async () => { const [runnerAutocomplete] = makeChannelPair(); const [runnerSyntax, webSyntax] = makeChannelPair(); new FunctionOutdentsRunner( @@ -211,8 +211,8 @@ test("exposes a callable outdents mode function over RPC", async () => { [runnerAutocomplete, runnerSyntax] as IChannel[], ); webSyntax.subscribe(message => { - if ((message as SyntaxHighlightMessage).type === "response") { - webSyntax.send({ type: "ack" }); + if ((message as SyntaxHighlightMessage).type === 'response') { + webSyntax.send({ type: 'ack' }); } }); const remote = makeRpc, ModeRpc>( @@ -220,7 +220,7 @@ test("exposes a callable outdents mode function over RPC", async () => { {}, ); - webSyntax.send({ type: "request" }); + webSyntax.send({ type: 'request' }); - await expect(remote.outdents("line")).resolves.toBe("should-outdent:line"); + await expect(remote.outdents('line')).resolves.toBe('should-outdent:line'); }); diff --git a/src/runner/autocomplete/src/index.ts b/src/runner/autocomplete/src/index.ts index 627833a..df7efd8 100644 --- a/src/runner/autocomplete/src/index.ts +++ b/src/runner/autocomplete/src/index.ts @@ -1,11 +1,3 @@ -import { - makeRpc, - type IChannel, - type IConduit, - type IPlugin, - type IRpcMessage, -} from "@sourceacademy/conductor/conduit"; - import { AUTOCOMPLETE_CHANNEL_ID, RUNNER_PLUGIN_ID, @@ -18,10 +10,17 @@ import { type SyntaxHighlightMessage, type TransferredModeFunction, type TransferredSyntaxHighlightData, -} from "@sourceacademy/common-autocomplete"; +} from '@sourceacademy/common-autocomplete'; +import { + makeRpc, + type IChannel, + type IConduit, + type IPlugin, + type IRpcMessage, +} from '@sourceacademy/conductor/conduit'; const transferModeFunction = (name: keyof ModeRpc, fn: ModeFunction): TransferredModeFunction => - typeof fn === "function" ? { rpc: name } : fn; + typeof fn === 'function' ? { rpc: name } : fn; /** * This plugin provides autocomplete suggestions and syntax highlighting. @@ -52,45 +51,41 @@ export abstract class BaseAutoCompleteRunnerPlugin implements IPlugin { */ abstract autocomplete(code: string, row: number, column: number): AutoCompleteEntry[]; - constructor( - _conduit: IConduit, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [autoCompleteChannel, syntaxHighlightChannel]: IChannel[], - ) { + constructor(_conduit: IConduit, [autoCompleteChannel, syntaxHighlightChannel]: IChannel[]) { this.__autoCompleteChannel = autoCompleteChannel; this.__syntaxHighlightChannel = syntaxHighlightChannel; makeRpc>( this.__syntaxHighlightChannel as unknown as IChannel, { - indents: (...args: unknown[]) => this.__callModeFunction("indents", args), - outdents: (...args: unknown[]) => this.__callModeFunction("outdents", args), - autoOutdent: (...args: unknown[]) => this.__callModeFunction("autoOutdent", args), + indents: (...args: unknown[]) => this.__callModeFunction('indents', args), + outdents: (...args: unknown[]) => this.__callModeFunction('outdents', args), + autoOutdent: (...args: unknown[]) => this.__callModeFunction('autoOutdent', args), }, ); const handler = setInterval(() => { this.__syntaxHighlightChannel.send({ - type: "response", + type: 'response', data: this.__transferMode(), }); }, 1000); this.__syntaxHighlightChannel.subscribe((message: SyntaxHighlightMessage) => { - if (message.type === "ack") { + if (message.type === 'ack') { clearInterval(handler); - } else if (message.type === "request") { + } else if (message.type === 'request') { this.__syntaxHighlightChannel.send({ - type: "response", + type: 'response', data: this.__transferMode(), }); } }); this.__autoCompleteChannel.subscribe((message: AutoCompleteMessage) => { - if (message.type === "request") { + if (message.type === 'request') { const { requestId, code, row, column } = message; const entries = this.autocomplete(code, row, column); this.__autoCompleteChannel.send({ - type: "response", + type: 'response', requestId, declarations: entries, }); @@ -102,16 +97,16 @@ export abstract class BaseAutoCompleteRunnerPlugin implements IPlugin { const mode = this.mode; return { ...mode, - indents: transferModeFunction("indents", mode.indents), - outdents: transferModeFunction("outdents", mode.outdents), - autoOutdent: transferModeFunction("autoOutdent", mode.autoOutdent), + indents: transferModeFunction('indents', mode.indents), + outdents: transferModeFunction('outdents', mode.outdents), + autoOutdent: transferModeFunction('autoOutdent', mode.autoOutdent), }; } private __callModeFunction(name: keyof ModeRpc, args: unknown[]): unknown { const mode = this.mode; const fn = mode[name]; - if (typeof fn !== "function") { + if (typeof fn !== 'function') { throw new Error( `Mode function "${name}" is configured with a hook and cannot be called over RPC.`, ); diff --git a/src/runner/cse-machine/package.json b/src/runner/cse-machine/package.json index 826b8d2..25fb822 100644 --- a/src/runner/cse-machine/package.json +++ b/src/runner/cse-machine/package.json @@ -3,6 +3,11 @@ "version": "3.0.0", "packageManager": "yarn@4.18.1", "description": "The runner-side CSE machine plugin: sends evaluation snapshots to the host", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/runner/cse-machine" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build" @@ -26,18 +31,18 @@ }, "peerDependencies": { "@sourceacademy/common-cse-machine": ">=0.4.0", - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-cse-machine": "workspace:*", - "@sourceacademy/conductor": "^0.8.0", - "@vitest/coverage-istanbul": "^5.0.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@sourceacademy/conductor": "catalog:", + "@vitest/coverage-istanbul": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/runner/cse-machine/rollup.config.mjs b/src/runner/cse-machine/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/runner/cse-machine/rollup.config.mjs +++ b/src/runner/cse-machine/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/runner/cse-machine/src/__tests__/runner.test.ts b/src/runner/cse-machine/src/__tests__/runner.test.ts index b15a96f..983a9d6 100644 --- a/src/runner/cse-machine/src/__tests__/runner.test.ts +++ b/src/runner/cse-machine/src/__tests__/runner.test.ts @@ -1,16 +1,15 @@ -import { describe, test, expect, vi } from "vitest"; -import { CseMachinePlugin } from ".."; import { CSE_CHANNEL, CSE_MESSAGE_TYPE_SNAPSHOTS, RUNNER_ID, type CseSnapshot, -} from "@sourceacademy/common-cse-machine"; -import type { IChannel, IConduit } from "@sourceacademy/conductor/conduit"; +} from '@sourceacademy/common-cse-machine'; +import type { IChannel, IConduit } from '@sourceacademy/conductor/conduit'; +import { describe, expect, test, vi } from 'vitest'; +import { CseMachinePlugin } from '..'; const makeChannel = () => { const send = vi.fn(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any return { send } as unknown as IChannel & { send: typeof send }; }; const makePlugin = (ch = makeChannel()) => new CseMachinePlugin({} as IConduit, [ch]); @@ -19,50 +18,50 @@ const minimalSnapshot = (): CseSnapshot => ({ stepIndex: 0, control: [], stash: [], - environments: [{ id: "g", name: "global", parentId: null, bindings: [], isActive: true }], + environments: [{ id: 'g', name: 'global', parentId: null, bindings: [], isActive: true }], }); // ── Identity / wiring ───────────────────────────────────────────────────────── -describe("plugin identity", () => { - test("id is RUNNER_ID", () => { +describe('plugin identity', () => { + test('id is RUNNER_ID', () => { expect(makePlugin().id).toBe(RUNNER_ID); }); - test("channelAttach declares the CSE channel", () => { + test('channelAttach declares the CSE channel', () => { expect(CseMachinePlugin.channelAttach).toEqual([CSE_CHANNEL]); }); - test("channelAttach contains exactly one channel", () => { + test('channelAttach contains exactly one channel', () => { expect(CseMachinePlugin.channelAttach).toHaveLength(1); }); }); // ── Constructor ─────────────────────────────────────────────────────────────── -describe("constructor", () => { - test("throws when no channel is provided", () => { +describe('constructor', () => { + test('throws when no channel is provided', () => { expect(() => new CseMachinePlugin({} as IConduit, [])).toThrow( - "CSE channel is required but was not provided.", + 'CSE channel is required but was not provided.', ); }); - test("does not throw when a channel is provided", () => { + test('does not throw when a channel is provided', () => { expect(() => makePlugin()).not.toThrow(); }); }); // ── sendSnapshots ───────────────────────────────────────────────────────────── -describe("sendSnapshots", () => { - test("sends a message with the correct type discriminator", () => { +describe('sendSnapshots', () => { + test('sends a message with the correct type discriminator', () => { const channel = makeChannel(); const plugin = makePlugin(channel); plugin.sendSnapshots([minimalSnapshot()]); expect(channel.send.mock.calls[0][0].type).toBe(CSE_MESSAGE_TYPE_SNAPSHOTS); }); - test("sends snapshots unchanged", () => { + test('sends snapshots unchanged', () => { const channel = makeChannel(); const plugin = makePlugin(channel); const snapshots = [minimalSnapshot()]; @@ -70,7 +69,7 @@ describe("sendSnapshots", () => { expect(channel.send.mock.calls[0][0].snapshots).toEqual(snapshots); }); - test("sets totalSteps to the length of the snapshots array", () => { + test('sets totalSteps to the length of the snapshots array', () => { const channel = makeChannel(); const plugin = makePlugin(channel); const snapshots = [minimalSnapshot(), { ...minimalSnapshot(), stepIndex: 1 }]; @@ -78,14 +77,14 @@ describe("sendSnapshots", () => { expect(channel.send.mock.calls[0][0].totalSteps).toBe(2); }); - test("calls channel.send exactly once per sendSnapshots call", () => { + test('calls channel.send exactly once per sendSnapshots call', () => { const channel = makeChannel(); const plugin = makePlugin(channel); plugin.sendSnapshots([minimalSnapshot()]); expect(channel.send).toHaveBeenCalledTimes(1); }); - test("can be called multiple times, sending once each time", () => { + test('can be called multiple times, sending once each time', () => { const channel = makeChannel(); const plugin = makePlugin(channel); plugin.sendSnapshots([minimalSnapshot()]); @@ -93,7 +92,7 @@ describe("sendSnapshots", () => { expect(channel.send).toHaveBeenCalledTimes(2); }); - test("handles an empty snapshots array", () => { + test('handles an empty snapshots array', () => { const channel = makeChannel(); const plugin = makePlugin(channel); plugin.sendSnapshots([]); @@ -105,14 +104,14 @@ describe("sendSnapshots", () => { }); }); - test("defaults breakpointSteps to an empty array when omitted", () => { + test('defaults breakpointSteps to an empty array when omitted', () => { const channel = makeChannel(); const plugin = makePlugin(channel); plugin.sendSnapshots([minimalSnapshot()]); expect(channel.send.mock.calls[0][0].breakpointSteps).toEqual([]); }); - test("forwards the breakpointSteps argument unchanged", () => { + test('forwards the breakpointSteps argument unchanged', () => { const channel = makeChannel(); const plugin = makePlugin(channel); const snapshots = [minimalSnapshot(), { ...minimalSnapshot(), stepIndex: 1 }]; @@ -120,7 +119,7 @@ describe("sendSnapshots", () => { expect(channel.send.mock.calls[0][0].breakpointSteps).toEqual([1]); }); - test("handles a large batch of snapshots", () => { + test('handles a large batch of snapshots', () => { const channel = makeChannel(); const plugin = makePlugin(channel); const snapshots = Array.from({ length: 50 }, (_, i) => ({ @@ -131,14 +130,14 @@ describe("sendSnapshots", () => { expect(channel.send.mock.calls[0][0].totalSteps).toBe(50); }); - test("preserves snapshot fields including currentLine and metadata", () => { + test('preserves snapshot fields including currentLine and metadata', () => { const channel = makeChannel(); const plugin = makePlugin(channel); const snap: CseSnapshot = { stepIndex: 3, - control: [{ displayText: "call f", metadata: { instrType: "Application" } }], - stash: [{ displayValue: "42", label: "number" }], - environments: [{ id: "g", name: "global", parentId: null, bindings: [], isActive: true }], + control: [{ displayText: 'call f', metadata: { instrType: 'Application' } }], + stash: [{ displayValue: '42', label: 'number' }], + environments: [{ id: 'g', name: 'global', parentId: null, bindings: [], isActive: true }], currentLine: 7, }; plugin.sendSnapshots([snap]); diff --git a/src/runner/cse-machine/src/index.ts b/src/runner/cse-machine/src/index.ts index fdc0902..0e046df 100644 --- a/src/runner/cse-machine/src/index.ts +++ b/src/runner/cse-machine/src/index.ts @@ -4,8 +4,8 @@ import { RUNNER_ID, type CseSnapshot, type CseSnapshotMessage, -} from "@sourceacademy/common-cse-machine"; -import type { IPlugin, IChannel, IConduit } from "@sourceacademy/conductor/conduit"; +} from '@sourceacademy/common-cse-machine'; +import type { IChannel, IConduit, IPlugin } from '@sourceacademy/conductor/conduit'; /** * Runner-side CSE machine plugin. @@ -25,13 +25,9 @@ export class CseMachinePlugin implements IPlugin { private readonly __cseChannel: IChannel; - constructor( - _conduit: IConduit, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [cseChannel]: IChannel[], - ) { + constructor(_conduit: IConduit, [cseChannel]: IChannel[]) { if (!cseChannel) { - throw new Error("CSE channel is required but was not provided."); + throw new Error('CSE channel is required but was not provided.'); } this.__cseChannel = cseChannel; } diff --git a/src/runner/data-visualizer/jest.config.cjs b/src/runner/data-visualizer/jest.config.cjs deleted file mode 100644 index 06bb4a9..0000000 --- a/src/runner/data-visualizer/jest.config.cjs +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - preset: "ts-jest/presets/js-with-ts-esm", - testEnvironment: "node", - transform: { - "^.+\\.tsx?$": [ - "ts-jest", - { - useESM: true, - }, - ], - }, - testPathIgnorePatterns: [".*?dist/"], - coverageReporters: ["lcov"], -}; diff --git a/src/runner/data-visualizer/package.json b/src/runner/data-visualizer/package.json index 5a62cd7..509d33a 100644 --- a/src/runner/data-visualizer/package.json +++ b/src/runner/data-visualizer/package.json @@ -3,11 +3,16 @@ "version": "0.0.1", "packageManager": "yarn@4.18.1", "description": "Language-agnostic runner plugin for the Source Academy data visualizer", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/runner/data-visualizer" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build", - "test": "jest", - "test-coverage": "jest --coverage" + "test": "vitest", + "test-coverage": "vitest --coverage" }, "license": "ISC", "files": [ @@ -24,19 +29,17 @@ } }, "peerDependencies": { - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-data-visualizer": "workspace:*", - "@sourceacademy/conductor": "^0.8.0", - "@types/jest": "^30.0.0", - "jest": "^30.4.2", - "rollup": "^4.60.2", - "ts-jest": "^29.4.11", - "tslib": "^2.8.1", - "typescript": "^6.0.3" + "@sourceacademy/conductor": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/runner/data-visualizer/rollup.config.mjs b/src/runner/data-visualizer/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/runner/data-visualizer/rollup.config.mjs +++ b/src/runner/data-visualizer/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/runner/data-visualizer/src/__tests__/runner.test.ts b/src/runner/data-visualizer/src/__tests__/runner.test.ts index a17e11f..a64b8f5 100644 --- a/src/runner/data-visualizer/src/__tests__/runner.test.ts +++ b/src/runner/data-visualizer/src/__tests__/runner.test.ts @@ -1,11 +1,11 @@ -import { expect, test } from "vitest"; - import { DATA_VISUALIZER_CHANNEL_ID, RUNNER_ID, type SerializedDataVisualizerNode, -} from "@sourceacademy/common-data-visualizer"; -import { BaseDataVisualizerRunnerPlugin, createRefIdAllocator, type RefIdAllocator } from ".."; +} from '@sourceacademy/common-data-visualizer'; +import { expect, test } from 'vitest'; + +import { BaseDataVisualizerRunnerPlugin, createRefIdAllocator, type RefIdAllocator } from '..'; class FakeChannel { name = DATA_VISUALIZER_CHANNEL_ID; @@ -33,44 +33,44 @@ type TestValue = number | TestValue[]; class ArrayDataVisualizer extends BaseDataVisualizerRunnerPlugin { protected toNode(value: TestValue, refs: RefIdAllocator): SerializedDataVisualizerNode { if (!Array.isArray(value)) { - return { type: "leaf", displayValue: String(value), label: "number" }; + return { type: 'leaf', displayValue: String(value), label: 'number' }; } const { refId, alreadySeen } = refs.get(value); if (alreadySeen) { - return { type: "ref", refId }; + return { type: 'ref', refId }; } - return { type: "array", refId, children: value.map(v => this.toNode(v, refs)) }; + return { type: 'array', refId, children: value.map(v => this.toNode(v, refs)) }; } } -test("attaches to the data visualizer channel", () => { +test('attaches to the data visualizer channel', () => { expect(BaseDataVisualizerRunnerPlugin.channelAttach).toEqual([DATA_VISUALIZER_CHANNEL_ID]); }); -test("has the runner id", () => { +test('has the runner id', () => { const channel = new FakeChannel(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const plugin = new ArrayDataVisualizer({} as any, [channel as any]); expect(plugin.id).toBe(RUNNER_ID); }); -test("sendDrawing serializes one row and pushes the full row list", async () => { +test('sendDrawing serializes one row and pushes the full row list', async () => { const channel = new FakeChannel(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const plugin = new ArrayDataVisualizer({} as any, [channel as any]); await plugin.sendDrawing([1, [2, 3]]); expect(channel.sent).toEqual([ { - type: "rows", + type: 'rows', rows: [ [ - { type: "leaf", displayValue: "1", label: "number" }, + { type: 'leaf', displayValue: '1', label: 'number' }, { - type: "array", + type: 'array', refId: 1, children: [ - { type: "leaf", displayValue: "2", label: "number" }, - { type: "leaf", displayValue: "3", label: "number" }, + { type: 'leaf', displayValue: '2', label: 'number' }, + { type: 'leaf', displayValue: '3', label: 'number' }, ], }, ], @@ -79,47 +79,47 @@ test("sendDrawing serializes one row and pushes the full row list", async () => ]); }); -test("multiple sendDrawing calls accumulate rows, resending the full list each time", async () => { +test('multiple sendDrawing calls accumulate rows, resending the full list each time', async () => { const channel = new FakeChannel(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const plugin = new ArrayDataVisualizer({} as any, [channel as any]); await plugin.sendDrawing([1]); await plugin.sendDrawing([2]); expect(channel.sent).toHaveLength(2); expect(channel.sent[1]).toEqual({ - type: "rows", + type: 'rows', rows: [ - [{ type: "leaf", displayValue: "1", label: "number" }], - [{ type: "leaf", displayValue: "2", label: "number" }], + [{ type: 'leaf', displayValue: '1', label: 'number' }], + [{ type: 'leaf', displayValue: '2', label: 'number' }], ], }); }); -test("request replays the rows accumulated so far", async () => { +test('request replays the rows accumulated so far', async () => { const channel = new FakeChannel(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const plugin = new ArrayDataVisualizer({} as any, [channel as any]); await plugin.sendDrawing([1]); - channel.emit({ type: "request" }); + channel.emit({ type: 'request' }); expect(channel.sent).toHaveLength(2); expect(channel.sent[1]).toEqual(channel.sent[0]); }); -test("resetRun clears accumulated rows and pushes the empty state", async () => { +test('resetRun clears accumulated rows and pushes the empty state', async () => { const channel = new FakeChannel(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const plugin = new ArrayDataVisualizer({} as any, [channel as any]); await plugin.sendDrawing([1]); await plugin.resetRun(); - expect(channel.sent[1]).toEqual({ type: "rows", rows: [] }); + expect(channel.sent[1]).toEqual({ type: 'rows', rows: [] }); - channel.emit({ type: "request" }); - expect(channel.sent[2]).toEqual({ type: "rows", rows: [] }); + channel.emit({ type: 'request' }); + expect(channel.sent[2]).toEqual({ type: 'rows', rows: [] }); }); -test("a self-referential value terminates via a ref node instead of recursing forever", async () => { +test('a self-referential value terminates via a ref node instead of recursing forever', async () => { const channel = new FakeChannel(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const plugin = new ArrayDataVisualizer({} as any, [channel as any]); const cyclic: TestValue[] = [1]; cyclic.push(cyclic); @@ -129,16 +129,16 @@ test("a self-referential value terminates via a ref node instead of recursing fo const message = channel.sent[0] as { rows: SerializedDataVisualizerNode[][] }; const [node] = message.rows[0]; expect(node).toEqual({ - type: "array", + type: 'array', refId: 1, children: [ - { type: "leaf", displayValue: "1", label: "number" }, - { type: "ref", refId: 1 }, + { type: 'leaf', displayValue: '1', label: 'number' }, + { type: 'ref', refId: 1 }, ], }); }); -test("a value shared across two arguments of one call is fully re-serialized in each, not collapsed to an unresolvable ref", async () => { +test('a value shared across two arguments of one call is fully re-serialized in each, not collapsed to an unresolvable ref', async () => { // The host renders each argument of a draw_data(...) call as its own independent Konva Stage // (a separate ), so a "ref" node can only ever be resolved against other nodes already // built within that SAME argument's tree — there is no way to draw, or even resolve, an arrow @@ -147,7 +147,7 @@ test("a value shared across two arguments of one call is fully re-serialized in // (matching the old pre-Conductor frontend's per-argument `visitedStructures` scoping) rather than // silently collapsing the second occurrence into a ref the host can never resolve. const channel = new FakeChannel(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const plugin = new ArrayDataVisualizer({} as any, [channel as any]); const shared: TestValue[] = [1]; @@ -157,28 +157,28 @@ test("a value shared across two arguments of one call is fully re-serialized in const [firstArg, secondArg] = message.rows[0]; // First argument's own allocator sees `shared` first, so it claims refId 1. expect(firstArg).toEqual({ - type: "array", + type: 'array', refId: 1, - children: [{ type: "leaf", displayValue: "1", label: "number" }], + children: [{ type: 'leaf', displayValue: '1', label: 'number' }], }); // Second argument gets its own fresh allocator: its outer wrapper array (a distinct object from // `shared`) is visited first and claims refId 1 for *this* argument; `shared` is then a new value // as far as this allocator is concerned, and claims refId 2 — fully expanded, not a "ref". expect(secondArg).toEqual({ - type: "array", + type: 'array', refId: 1, children: [ { - type: "array", + type: 'array', refId: 2, - children: [{ type: "leaf", displayValue: "1", label: "number" }], + children: [{ type: 'leaf', displayValue: '1', label: 'number' }], }, - { type: "leaf", displayValue: "2", label: "number" }, + { type: 'leaf', displayValue: '2', label: 'number' }, ], }); }); -test("createRefIdAllocator assigns the same id to the same reference and different ids otherwise", () => { +test('createRefIdAllocator assigns the same id to the same reference and different ids otherwise', () => { const refs = createRefIdAllocator(); const a = {}; const b = {}; diff --git a/src/runner/data-visualizer/src/index.ts b/src/runner/data-visualizer/src/index.ts index 60e4709..760066d 100644 --- a/src/runner/data-visualizer/src/index.ts +++ b/src/runner/data-visualizer/src/index.ts @@ -5,8 +5,8 @@ import { type RefId, type SerializedDataVisualizerNode, type SerializedDataVisualizerRow, -} from "@sourceacademy/common-data-visualizer"; -import type { IChannel, IConduit, IPlugin } from "@sourceacademy/conductor/conduit"; +} from '@sourceacademy/common-data-visualizer'; +import type { IChannel, IConduit, IPlugin } from '@sourceacademy/conductor/conduit'; /** * Assigns each distinct compound runtime value (by reference) a stable {@link RefId} the first time @@ -82,8 +82,8 @@ export abstract class BaseDataVisualizerRunnerPlugin implements IPlugin this.__channel = channel; this.__channel.subscribe(message => { // The host (re)opened the data visualizer tab and wants the latest rows without a re-run. - if (message.type === "request") { - this.__channel.send({ type: "rows", rows: this.__rows }); + if (message.type === 'request') { + this.__channel.send({ type: 'rows', rows: this.__rows }); } }); } @@ -122,7 +122,7 @@ export abstract class BaseDataVisualizerRunnerPlugin implements IPlugin this.toNode(value, createRefIdAllocator()), ); this.__rows = [...this.__rows, row]; - this.__channel.send({ type: "rows", rows: this.__rows }); + this.__channel.send({ type: 'rows', rows: this.__rows }); } /** @@ -133,6 +133,6 @@ export abstract class BaseDataVisualizerRunnerPlugin implements IPlugin */ resetRun(): void { this.__rows = []; - this.__channel.send({ type: "rows", rows: this.__rows }); + this.__channel.send({ type: 'rows', rows: this.__rows }); } } diff --git a/src/runner/data-visualizer/tsconfig.json b/src/runner/data-visualizer/tsconfig.json index 88a8445..c7482f9 100644 --- a/src/runner/data-visualizer/tsconfig.json +++ b/src/runner/data-visualizer/tsconfig.json @@ -5,7 +5,6 @@ "compilerOptions": { "declaration": true, "outDir": "./dist", - "rootDir": "./src", - "types": ["jest"] + "rootDir": "./src" } } diff --git a/src/runner/e-stepper/jest.config.cjs b/src/runner/e-stepper/jest.config.cjs deleted file mode 100644 index 06bb4a9..0000000 --- a/src/runner/e-stepper/jest.config.cjs +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - preset: "ts-jest/presets/js-with-ts-esm", - testEnvironment: "node", - transform: { - "^.+\\.tsx?$": [ - "ts-jest", - { - useESM: true, - }, - ], - }, - testPathIgnorePatterns: [".*?dist/"], - coverageReporters: ["lcov"], -}; diff --git a/src/runner/e-stepper/package.json b/src/runner/e-stepper/package.json index 12f0d91..68f7646 100644 --- a/src/runner/e-stepper/package.json +++ b/src/runner/e-stepper/package.json @@ -6,8 +6,8 @@ "scripts": { "build": "rollup -c", "prepack": "yarn build", - "test": "jest", - "test-coverage": "jest --coverage" + "test": "vitest", + "test-coverage": "vitest --coverage" }, "license": "ISC", "files": [ @@ -30,18 +30,16 @@ "@sourceacademy/runner-stepper": ">=0.0.1" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-e-stepper": "workspace:*", "@sourceacademy/common-stepper": "workspace:*", - "@sourceacademy/conductor": "^0.8.0", + "@sourceacademy/conductor": "catalog:", "@sourceacademy/runner-stepper": "workspace:*", - "@types/jest": "^30.0.0", - "jest": "^30.4.2", - "rollup": "^4.60.2", - "ts-jest": "^29.4.11", - "tslib": "^2.8.1", - "typescript": "^6.0.3" + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/runner/e-stepper/rollup.config.mjs b/src/runner/e-stepper/rollup.config.mjs index 238a07c..aaf820c 100644 --- a/src/runner/e-stepper/rollup.config.mjs +++ b/src/runner/e-stepper/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/runner/e-stepper/src/__tests__/runner.test.ts b/src/runner/e-stepper/src/__tests__/runner.test.ts index 7296530..28ad0c0 100644 --- a/src/runner/e-stepper/src/__tests__/runner.test.ts +++ b/src/runner/e-stepper/src/__tests__/runner.test.ts @@ -1,13 +1,12 @@ -import { expect, test } from "vitest"; - import { E_STEPPER_CHANNEL_ID, RUNNER_ID, type EStepperStep, -} from "@sourceacademy/common-e-stepper"; -import { STEPPER_CHANNEL_ID } from "@sourceacademy/common-stepper"; -import { BaseStepperRunnerPlugin } from "@sourceacademy/runner-stepper"; -import { BaseEStepperRunnerPlugin } from ".."; +} from '@sourceacademy/common-e-stepper'; +import { STEPPER_CHANNEL_ID } from '@sourceacademy/common-stepper'; +import { BaseStepperRunnerPlugin } from '@sourceacademy/runner-stepper'; +import { expect, test } from 'vitest'; +import { BaseEStepperRunnerPlugin } from '..'; class FakeChannel { name = E_STEPPER_CHANNEL_ID; @@ -29,10 +28,10 @@ class FakeChannel { } const step = (i: number): EStepperStep => ({ - ast: { type: "Literal", nodeId: String(i), value: i }, - frames: [{ id: "Global", name: "global", parentId: null, bindings: [], isGarbage: false }], + ast: { type: 'Literal', nodeId: String(i), value: i }, + frames: [{ id: 'Global', name: 'global', parentId: null, bindings: [], isGarbage: false }], heap: [], - activeFrameId: "Global", + activeFrameId: 'Global', }); // A trivial concrete e-stepper: every "AST" (a number n) becomes n steps. @@ -43,7 +42,6 @@ class CountingEStepper extends BaseEStepperRunnerPlugin { } const make = (Cls: new (...args: never[]) => T, channel: FakeChannel): T => - // eslint-disable-next-line @typescript-eslint/no-explicit-any new (Cls as any)({} as any, [channel as any]); test("attaches to the e-stepper channel, not the stepper's", () => { @@ -52,16 +50,16 @@ test("attaches to the e-stepper channel, not the stepper's", () => { expect(BaseStepperRunnerPlugin.channelAttach).toEqual([STEPPER_CHANNEL_ID]); }); -test("has the e-stepper runner id", () => { +test('has the e-stepper runner id', () => { expect(make(CountingEStepper, new FakeChannel()).id).toBe(RUNNER_ID); }); -test("sendSteps computes, caches and pushes steps; request replays them", async () => { +test('sendSteps computes, caches and pushes steps; request replays them', async () => { const channel = new FakeChannel(); const plugin = make(CountingEStepper, channel); await plugin.sendSteps(2); - expect(channel.sent).toEqual([{ type: "steps", steps: [step(0), step(1)] }]); - channel.emit({ type: "request" }); + expect(channel.sent).toEqual([{ type: 'steps', steps: [step(0), step(1)] }]); + channel.emit({ type: 'request' }); expect(channel.sent).toHaveLength(2); expect(channel.sent[1]).toEqual(channel.sent[0]); }); @@ -70,22 +68,22 @@ test("ships the language's syntax profile with the steps", async () => { const channel = new FakeChannel(); class WithProfile extends CountingEStepper { protected override getSyntaxProfile() { - return { templates: { Literal: [{ prop: "value" }] } }; + return { templates: { Literal: [{ prop: 'value' }] } }; } } await make(WithProfile, channel).sendSteps(1); expect(channel.sent).toEqual([ - { type: "steps", steps: [step(0)], profile: { templates: { Literal: [{ prop: "value" }] } } }, + { type: 'steps', steps: [step(0)], profile: { templates: { Literal: [{ prop: 'value' }] } } }, ]); }); -test("sendSteps reports errors instead of throwing", async () => { +test('sendSteps reports errors instead of throwing', async () => { const channel = new FakeChannel(); class Boom extends BaseEStepperRunnerPlugin { getSteps(): never { - throw new Error("kaboom"); + throw new Error('kaboom'); } } await make(Boom, channel).sendSteps(1); - expect(channel.sent).toEqual([{ type: "error", error: "kaboom" }]); + expect(channel.sent).toEqual([{ type: 'error', error: 'kaboom' }]); }); diff --git a/src/runner/e-stepper/src/index.ts b/src/runner/e-stepper/src/index.ts index 8ac25b9..5eac8ea 100644 --- a/src/runner/e-stepper/src/index.ts +++ b/src/runner/e-stepper/src/index.ts @@ -2,8 +2,8 @@ import { E_STEPPER_CHANNEL_ID, RUNNER_ID, type EStepperStep, -} from "@sourceacademy/common-e-stepper"; -import { BaseStepperRunnerPlugin } from "@sourceacademy/runner-stepper"; +} from '@sourceacademy/common-e-stepper'; +import { BaseStepperRunnerPlugin } from '@sourceacademy/runner-stepper'; /** * The language-agnostic runner half of the e-stepper (environment stepper). diff --git a/src/runner/e-stepper/tsconfig.json b/src/runner/e-stepper/tsconfig.json index 88a8445..c7482f9 100644 --- a/src/runner/e-stepper/tsconfig.json +++ b/src/runner/e-stepper/tsconfig.json @@ -5,7 +5,6 @@ "compilerOptions": { "declaration": true, "outDir": "./dist", - "rootDir": "./src", - "types": ["jest"] + "rootDir": "./src" } } diff --git a/src/runner/module-loader/package.json b/src/runner/module-loader/package.json index 44e28f8..7905f97 100644 --- a/src/runner/module-loader/package.json +++ b/src/runner/module-loader/package.json @@ -3,6 +3,11 @@ "version": "0.1.0", "packageManager": "yarn@4.18.1", "description": "The runner plugin for the module loader in Source Academy", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/runner/module-loader" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build" @@ -22,19 +27,19 @@ } }, "peerDependencies": { - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-module-loader": "workspace:*", - "@sourceacademy/conductor": "^0.8.0", + "@sourceacademy/conductor": "catalog:", "@sourceacademy/runner-remote-execution": "workspace:*", - "@vitest/coverage-istanbul": "^5.0.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@vitest/coverage-istanbul": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/runner/module-loader/rollup.config.mjs b/src/runner/module-loader/rollup.config.mjs index 3f56b30..875363f 100644 --- a/src/runner/module-loader/rollup.config.mjs +++ b/src/runner/module-loader/rollup.config.mjs @@ -1,19 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** @type {import('rollup').RollupOptions} */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/runner/module-loader/src/__tests__/runner.test.ts b/src/runner/module-loader/src/__tests__/runner.test.ts index 85d27de..a52a03c 100644 --- a/src/runner/module-loader/src/__tests__/runner.test.ts +++ b/src/runner/module-loader/src/__tests__/runner.test.ts @@ -1,15 +1,15 @@ -import { afterEach, describe, expect, test, vi, type Mock, type MockedFunction } from "vitest"; -import { ModuleLoaderRunnerPlugin } from ".."; import { CHANNEL_ID, ModuleLoaderMessageType, RUNNER_ID, type ModuleLoaderMessage, -} from "@sourceacademy/common-module-loader"; -import type { IChannel, IConduit } from "@sourceacademy/conductor/conduit"; -import type { IModulePlugin } from "@sourceacademy/conductor/module"; -import type { IInterfacableEvaluator, IRunnerPlugin } from "@sourceacademy/conductor/runner"; -import type { PluginClass } from "@sourceacademy/conductor/conduit"; +} from '@sourceacademy/common-module-loader'; +import type { IChannel, IConduit, PluginClass } from '@sourceacademy/conductor/conduit'; +import type { IModulePlugin } from '@sourceacademy/conductor/module'; +import type { IInterfacableEvaluator, IRunnerPlugin } from '@sourceacademy/conductor/runner'; +import { afterEach, describe, expect, test, vi, type Mock, type MockedFunction } from 'vitest'; +import { ModuleLoaderRunnerPlugin } from '..'; + type ChannelSubscriber = (msg: ModuleLoaderMessage) => void | Promise; type TestChannel = IChannel & { @@ -26,7 +26,10 @@ const mockBundleURL2 = `data:text/javascript;charset=utf-8,${encodeURIComponent( class MockModulePlugin2 {} export default () => ({ default: MockModulePlugin2 }); `)}`; -vi.mock("module", () => ({})); + +// Used for the mock module that the plugin needs to try import +// eslint-disable-next-line vitest/prefer-import-in-mock +vi.mock('module', () => ({})); const makeChannel = ( getResponse?: ( @@ -105,24 +108,24 @@ afterEach(() => { ModuleLoaderRunnerPlugin.instance = null; }); -describe("plugin identity", () => { - test("id is RUNNER_ID", () => { +describe('plugin identity', () => { + test('id is RUNNER_ID', () => { expect(makePlugin().plugin.id).toBe(RUNNER_ID); }); - test("channelAttach declares the module loader channel", () => { + test('channelAttach declares the module loader channel', () => { expect(ModuleLoaderRunnerPlugin.channelAttach).toEqual([CHANNEL_ID]); }); - test("constructor sets the singleton instance", () => { + test('constructor sets the singleton instance', () => { const { plugin } = makePlugin(); expect(ModuleLoaderRunnerPlugin.instance).toBe(plugin); }); }); -describe("requestModule", () => { - test("requests a module, imports the returned bundle, registers it, and initialises it", async () => { - const tabs = ["ChartTab", "SettingsTab"]; +describe('requestModule', () => { + test('requests a module, imports the returned bundle, registers it, and initialises it', async () => { + const tabs = ['ChartTab', 'SettingsTab']; const channel = makeChannel(msg => { if (msg.type !== ModuleLoaderMessageType.REQUEST_MODULE) { return undefined; @@ -136,24 +139,24 @@ describe("requestModule", () => { }); const { plugin, evaluator, registerPlugin, initialise, pluginObj } = makePlugin(channel); - await expect(plugin.requestModule("chart")).resolves.toBe(pluginObj); + await expect(plugin.requestModule('chart')).resolves.toBe(pluginObj); expect(channel.subscribe).toHaveBeenCalledOnce(); expect(channel.send).toHaveBeenCalledWith({ type: ModuleLoaderMessageType.REQUEST_MODULE, - moduleName: "chart", + moduleName: 'chart', }); expect(channel.unsubscribe).toHaveBeenCalledOnce(); expect(registerPlugin).toHaveBeenCalledOnce(); const [pluginClass, receivedEvaluator, options] = registerPlugin.mock.calls[0]; expect(pluginClass).toEqual(expect.any(Function)); - expect(pluginClass.name).toBe("MockModulePlugin"); + expect(pluginClass.name).toBe('MockModulePlugin'); expect(receivedEvaluator).toBe(evaluator); expect(options).toMatchObject({ tabs }); expect(initialise).toHaveBeenCalledOnce(); }); - test("loadTab delegates to the conductor for declared tabs only", async () => { + test('loadTab delegates to the conductor for declared tabs only', async () => { const channel = makeChannel(msg => { if (msg.type !== ModuleLoaderMessageType.REQUEST_MODULE) { return undefined; @@ -162,12 +165,12 @@ describe("requestModule", () => { type: ModuleLoaderMessageType.MODULE_RESPONSE, moduleName: msg.moduleName, moduleURL: mockBundleURL, - tabs: ["ChartTab"], + tabs: ['ChartTab'], }; }); const { plugin, registerPlugin, hostLoadPlugin } = makePlugin(channel); - await plugin.requestModule("chart"); + await plugin.requestModule('chart'); const options = registerPlugin.mock.calls[0][2]; let resolveHostLoadPlugin!: () => void; @@ -177,20 +180,20 @@ describe("requestModule", () => { }), ); - const loadTabPromise = options.loadTab("ChartTab"); - expect(hostLoadPlugin).toHaveBeenCalledWith("ChartTab"); + const loadTabPromise = options.loadTab('ChartTab'); + expect(hostLoadPlugin).toHaveBeenCalledWith('ChartTab'); await expect( - Promise.race([loadTabPromise.then(() => "resolved"), Promise.resolve("pending")]), - ).resolves.toBe("pending"); + Promise.race([loadTabPromise.then(() => 'resolved'), Promise.resolve('pending')]), + ).resolves.toBe('pending'); resolveHostLoadPlugin(); await expect(loadTabPromise).resolves.toBeUndefined(); - await expect(() => options.loadTab("MissingTab")).rejects.toThrow( - "Tab MissingTab not found in module chart", + await expect(() => options.loadTab('MissingTab')).rejects.toThrow( + 'Tab MissingTab not found in module chart', ); }); - test("rejects when the plugin is invalid", () => { + test('rejects when the plugin is invalid', async () => { // mock the import function const channel = makeChannel(msg => { if (msg.type !== ModuleLoaderMessageType.REQUEST_MODULE) { @@ -199,17 +202,22 @@ describe("requestModule", () => { return { type: ModuleLoaderMessageType.MODULE_RESPONSE, moduleName: msg.moduleName, - moduleURL: "module", - tabs: ["Tab1"], + moduleURL: 'module', + tabs: ['Tab1'], }; }); const { plugin } = makePlugin(channel); - plugin.requestModule("module").catch(err => { + + try { + await plugin.requestModule('module'); + expect.fail('Error should have been thrown'); + } catch (err) { + // eslint-disable-next-line vitest/no-conditional-expect expect(err).toBeInstanceOf(Error); - }); + } }); - test("out of order responses are ignored", async () => { + test('out of order responses are ignored', async () => { const channel = makeChannel(msg => { if (msg.type !== ModuleLoaderMessageType.REQUEST_MODULE) { return undefined; @@ -217,33 +225,33 @@ describe("requestModule", () => { return [ { type: ModuleLoaderMessageType.MODULE_RESPONSE, - moduleName: "module2", + moduleName: 'module2', moduleURL: mockBundleURL2, - tabs: ["Tab2"], + tabs: ['Tab2'], }, { type: ModuleLoaderMessageType.MODULE_RESPONSE, - moduleName: "module", + moduleName: 'module', moduleURL: mockBundleURL, - tabs: ["Tab1"], + tabs: ['Tab1'], }, ]; }); const { plugin, pluginObj, registerPlugin } = makePlugin(channel); - await expect(plugin.requestModule("module")).resolves.toBe(pluginObj); + await expect(plugin.requestModule('module')).resolves.toBe(pluginObj); expect(channel.subscribe).toHaveBeenCalledOnce(); expect(channel.send).toHaveBeenCalledWith({ type: ModuleLoaderMessageType.REQUEST_MODULE, - moduleName: "module", + moduleName: 'module', }); expect(channel.unsubscribe).toHaveBeenCalledOnce(); expect(registerPlugin).toHaveBeenCalledOnce(); const [pluginClass] = registerPlugin.mock.calls[0]; - expect(pluginClass.name).toBe("MockModulePlugin"); + expect(pluginClass.name).toBe('MockModulePlugin'); }); - test("module requests are cached", async () => { + test('module requests are cached', async () => { const channel = makeChannel(msg => { if (msg.type !== ModuleLoaderMessageType.REQUEST_MODULE) { return undefined; @@ -252,15 +260,15 @@ describe("requestModule", () => { type: ModuleLoaderMessageType.MODULE_RESPONSE, moduleName: msg.moduleName, moduleURL: mockBundleURL, - tabs: ["Tab1"], + tabs: ['Tab1'], }; }); - const { plugin, pluginObj, registerPlugin } = makePlugin(channel); - await plugin.requestModule("chart"); - await expect(plugin.requestModule("chart")).resolves.toBe(pluginObj); + const { plugin, pluginObj } = makePlugin(channel); + await plugin.requestModule('chart'); + await expect(plugin.requestModule('chart')).resolves.toBe(pluginObj); }); - test("rejects when the mocked web side returns a module error", async () => { + test('rejects when the mocked web side returns a module error', async () => { const channel = makeChannel(msg => { if (msg.type !== ModuleLoaderMessageType.REQUEST_MODULE) { return undefined; @@ -268,12 +276,12 @@ describe("requestModule", () => { return { type: ModuleLoaderMessageType.MODULE_ERROR, moduleName: msg.moduleName, - error: "Module not found: missing", + error: 'Module not found: missing', }; }); const { plugin, registerPlugin } = makePlugin(channel); - await expect(plugin.requestModule("missing")).rejects.toThrow("Module not found: missing"); + await expect(plugin.requestModule('missing')).rejects.toThrow('Module not found: missing'); expect(channel.unsubscribe).toHaveBeenCalledOnce(); expect(registerPlugin).not.toHaveBeenCalled(); }); diff --git a/src/runner/module-loader/src/index.ts b/src/runner/module-loader/src/index.ts index 4f618db..587b0e4 100644 --- a/src/runner/module-loader/src/index.ts +++ b/src/runner/module-loader/src/index.ts @@ -3,15 +3,15 @@ import { ModuleLoaderMessageType, RUNNER_ID, type ModuleLoaderMessage, -} from "@sourceacademy/common-module-loader"; +} from '@sourceacademy/common-module-loader'; import { checkIsPluginClass, type IChannel, type IConduit, type IPlugin, -} from "@sourceacademy/conductor/conduit"; -import type { IModulePlugin } from "@sourceacademy/conductor/module"; -import type { IInterfacableEvaluator, IRunnerPlugin } from "@sourceacademy/conductor/runner"; +} from '@sourceacademy/conductor/conduit'; +import type { IModulePlugin } from '@sourceacademy/conductor/module'; +import type { IInterfacableEvaluator, IRunnerPlugin } from '@sourceacademy/conductor/runner'; export class ModuleLoaderRunnerPlugin implements IPlugin { readonly id: string = RUNNER_ID; @@ -24,7 +24,6 @@ export class ModuleLoaderRunnerPlugin implements IPlugin { constructor( conduit: IConduit, - // eslint-disable-next-line @typescript-eslint/no-explicit-any [moduleRequestChannel]: IChannel[], conductor: IRunnerPlugin, evaluator: IInterfacableEvaluator, diff --git a/src/runner/remoteExecution/index.ts b/src/runner/remoteExecution/index.ts index b57bc7e..9e9778a 100644 --- a/src/runner/remoteExecution/index.ts +++ b/src/runner/remoteExecution/index.ts @@ -1,6 +1,6 @@ -import { CHANNEL_ID, RUNNER_ID, type PySlangMessage } from "@sourceacademy/common-test"; -import type { IPlugin, IChannel, IConduit } from "@sourceacademy/conductor/conduit"; -import { EV3Engine } from "py-slang/src/engines/ev3/EV3Engine"; +import { CHANNEL_ID, RUNNER_ID, type PySlangMessage } from '@sourceacademy/common-test'; +import type { IChannel, IConduit, IPlugin } from '@sourceacademy/conductor/conduit'; +import { EV3Engine } from 'py-slang/src/engines/ev3/EV3Engine'; export class remoteRunnerPlugin implements IPlugin { readonly id: string = RUNNER_ID; @@ -8,16 +8,15 @@ export class remoteRunnerPlugin implements IPlugin { private readonly __channel: IChannel; private readonly engine: EV3Engine; - // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(_conduit: IConduit, [channel]: IChannel[]) { this.__channel = channel; this.engine = new EV3Engine(); this.__channel.subscribe(async message => { - if (message.type === "run") { + if (message.type === 'run') { const result = await this.engine.execute(message.code); - console.log("Engine response:", result); - this.__channel.send({ type: "result", output: JSON.stringify(result) }); + console.log('Engine response:', result); + this.__channel.send({ type: 'result', output: JSON.stringify(result) }); } }); } diff --git a/src/runner/remoteExecution/package.json b/src/runner/remoteExecution/package.json index 91a06ac..5f50baf 100644 --- a/src/runner/remoteExecution/package.json +++ b/src/runner/remoteExecution/package.json @@ -2,6 +2,11 @@ "name": "@sourceacademy/runner-remote-execution", "version": "0.0.1", "license": "ISC", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/runner/remoteExecution" + }, "publishConfig": { "access": "public" }, @@ -26,18 +31,18 @@ "py-slang": "github:proto-aiken-13/py-slang#branch-ev3-engine" }, "peerDependencies": { - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-test": "workspace:*", - "@sourceacademy/conductor": "^0.8.0", - "@vitest/coverage-istanbul": "^5.0.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@sourceacademy/conductor": "catalog:", + "@vitest/coverage-istanbul": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/runner/remoteExecution/rollup.config.mjs b/src/runner/remoteExecution/rollup.config.mjs index 75b8ba5..dbba18b 100644 --- a/src/runner/remoteExecution/rollup.config.mjs +++ b/src/runner/remoteExecution/rollup.config.mjs @@ -1,22 +1,22 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", - external: id => id.includes("py-slang"), +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', + external: id => id.includes('py-slang'), output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/runner/remoteExecution/src/index.ts b/src/runner/remoteExecution/src/index.ts index e337998..27de399 100644 --- a/src/runner/remoteExecution/src/index.ts +++ b/src/runner/remoteExecution/src/index.ts @@ -1 +1 @@ -export { remoteRunnerPlugin } from "../index"; +export { remoteRunnerPlugin } from '../index'; diff --git a/src/runner/stepper/jest.config.cjs b/src/runner/stepper/jest.config.cjs deleted file mode 100644 index 06bb4a9..0000000 --- a/src/runner/stepper/jest.config.cjs +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - preset: "ts-jest/presets/js-with-ts-esm", - testEnvironment: "node", - transform: { - "^.+\\.tsx?$": [ - "ts-jest", - { - useESM: true, - }, - ], - }, - testPathIgnorePatterns: [".*?dist/"], - coverageReporters: ["lcov"], -}; diff --git a/src/runner/stepper/package.json b/src/runner/stepper/package.json index d303ae0..42fad6c 100644 --- a/src/runner/stepper/package.json +++ b/src/runner/stepper/package.json @@ -3,6 +3,11 @@ "version": "0.0.1", "packageManager": "yarn@4.18.1", "description": "Language-agnostic runner plugin for the Source Academy stepper", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/runner/stepper" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build", @@ -24,19 +29,17 @@ } }, "peerDependencies": { - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-stepper": "workspace:*", - "@sourceacademy/conductor": "^0.8.0", - "@types/jest": "^30.0.0", - "jest": "^30.4.2", - "rollup": "^4.60.2", - "ts-jest": "^29.4.11", - "tslib": "^2.8.1", - "typescript": "^6.0.3" + "@sourceacademy/conductor": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/runner/stepper/rollup.config.mjs b/src/runner/stepper/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/runner/stepper/rollup.config.mjs +++ b/src/runner/stepper/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/runner/stepper/src/__tests__/runner.test.ts b/src/runner/stepper/src/__tests__/runner.test.ts index 9c91db8..d3cbcd3 100644 --- a/src/runner/stepper/src/__tests__/runner.test.ts +++ b/src/runner/stepper/src/__tests__/runner.test.ts @@ -1,7 +1,7 @@ -import { expect, test } from "vitest"; +import { RUNNER_ID, STEPPER_CHANNEL_ID } from '@sourceacademy/common-stepper'; +import { expect, test } from 'vitest'; -import { RUNNER_ID, STEPPER_CHANNEL_ID } from "@sourceacademy/common-stepper"; -import { BaseStepperRunnerPlugin } from ".."; +import { BaseStepperRunnerPlugin } from '..'; class FakeChannel { name = STEPPER_CHANNEL_ID; @@ -26,50 +26,47 @@ class FakeChannel { class CountingStepper extends BaseStepperRunnerPlugin { getSteps(ast: number) { return Array.from({ length: ast }, (_, i) => ({ - ast: { type: "Literal", nodeId: String(i), value: i }, + ast: { type: 'Literal', nodeId: String(i), value: i }, })); } } -test("attaches to the stepper channel", () => { +test('attaches to the stepper channel', () => { expect(BaseStepperRunnerPlugin.channelAttach).toEqual([STEPPER_CHANNEL_ID]); }); -test("has the runner id", () => { +test('has the runner id', () => { const channel = new FakeChannel(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any const plugin = new CountingStepper({} as any, [channel as any]); expect(plugin.id).toBe(RUNNER_ID); }); -test("sendSteps computes, caches and pushes steps; request replays them", async () => { +test('sendSteps computes, caches and pushes steps; request replays them', async () => { const channel = new FakeChannel(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any const plugin = new CountingStepper({} as any, [channel as any]); await plugin.sendSteps(2); expect(channel.sent).toEqual([ { - type: "steps", + type: 'steps', steps: [ - { ast: { type: "Literal", nodeId: "0", value: 0 } }, - { ast: { type: "Literal", nodeId: "1", value: 1 } }, + { ast: { type: 'Literal', nodeId: '0', value: 0 } }, + { ast: { type: 'Literal', nodeId: '1', value: 1 } }, ], }, ]); - channel.emit({ type: "request" }); + channel.emit({ type: 'request' }); expect(channel.sent).toHaveLength(2); expect(channel.sent[1]).toEqual(channel.sent[0]); }); -test("sendSteps reports errors instead of throwing", async () => { +test('sendSteps reports errors instead of throwing', async () => { const channel = new FakeChannel(); class Boom extends BaseStepperRunnerPlugin { getSteps(): never { - throw new Error("kaboom"); + throw new Error('kaboom'); } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any const plugin = new Boom({} as any, [channel as any]); await plugin.sendSteps(1); - expect(channel.sent).toEqual([{ type: "error", error: "kaboom" }]); + expect(channel.sent).toEqual([{ type: 'error', error: 'kaboom' }]); }); diff --git a/src/runner/stepper/src/index.ts b/src/runner/stepper/src/index.ts index 50ada26..2d8115f 100644 --- a/src/runner/stepper/src/index.ts +++ b/src/runner/stepper/src/index.ts @@ -4,8 +4,8 @@ import { type SerializedStepperStep, type StepperMessage, type SyntaxProfile, -} from "@sourceacademy/common-stepper"; -import type { IChannel, IConduit, IPlugin } from "@sourceacademy/conductor/conduit"; +} from '@sourceacademy/common-stepper'; +import type { IChannel, IConduit, IPlugin } from '@sourceacademy/conductor/conduit'; /** * The language-agnostic runner half of the stepper. @@ -35,9 +35,9 @@ export abstract class BaseStepperRunnerPlugin implements IPlugin this.__stepperChannel = stepperChannel; this.__stepperChannel.subscribe(message => { // The host re-opened the stepper tab and wants the latest steps without a re-run. - if (message.type === "request") { + if (message.type === 'request') { this.__stepperChannel.send({ - type: "steps", + type: 'steps', steps: this.__lastSteps, profile: this.getSyntaxProfile(), }); @@ -76,10 +76,10 @@ export abstract class BaseStepperRunnerPlugin implements IPlugin try { const steps = await this.getSteps(ast); this.__lastSteps = steps; - this.__stepperChannel.send({ type: "steps", steps, profile: this.getSyntaxProfile() }); + this.__stepperChannel.send({ type: 'steps', steps, profile: this.getSyntaxProfile() }); } catch (error) { this.__stepperChannel.send({ - type: "error", + type: 'error', error: error instanceof Error ? error.message : String(error), }); } diff --git a/src/runner/stepper/tsconfig.json b/src/runner/stepper/tsconfig.json index 88a8445..c7482f9 100644 --- a/src/runner/stepper/tsconfig.json +++ b/src/runner/stepper/tsconfig.json @@ -5,7 +5,6 @@ "compilerOptions": { "declaration": true, "outDir": "./dist", - "rootDir": "./src", - "types": ["jest"] + "rootDir": "./src" } } diff --git a/src/runner/test/package.json b/src/runner/test/package.json index 42106f6..7251385 100644 --- a/src/runner/test/package.json +++ b/src/runner/test/package.json @@ -3,6 +3,11 @@ "version": "0.0.1", "packageManager": "yarn@4.12.0", "description": "The test runner plugin for the plugins repository", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/runner/test" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build" @@ -22,20 +27,20 @@ } }, "peerDependencies": { - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-test": "workspace:*", - "@sourceacademy/conductor": "^0.8.0", + "@sourceacademy/conductor": "catalog:", "@sourceacademy/runner-remote-execution": "workspace:*", "@vitest/coverage-istanbul": "^4.1.9", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^4.1.9" + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" }, "dependencies": { "py-slang": "github:proto-aiken-13/py-slang#branch-ev3-engine" diff --git a/src/runner/test/rollup.config.mjs b/src/runner/test/rollup.config.mjs index 3f56b30..875363f 100644 --- a/src/runner/test/rollup.config.mjs +++ b/src/runner/test/rollup.config.mjs @@ -1,19 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** @type {import('rollup').RollupOptions} */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/runner/test/src/__tests__/runner.test.ts b/src/runner/test/src/__tests__/runner.test.ts index 9fd886c..062ad00 100644 --- a/src/runner/test/src/__tests__/runner.test.ts +++ b/src/runner/test/src/__tests__/runner.test.ts @@ -1,22 +1,25 @@ -import { vi, test, expect } from "vitest"; +import type { PySlangMessage } from '@sourceacademy/common-test'; +import { remoteRunnerPlugin } from '@sourceacademy/runner-remote-execution'; +import { expect, test, vi } from 'vitest'; -vi.mock("py-slang/src/engines/ev3/EV3Engine", () => ({ - EV3Engine: class MockEV3Engine { - execute(code: string) { - return Promise.resolve({ output: `mock: ${code}` }); - } - }, -})); +vi.mock( + import('py-slang/src/engines/ev3/EV3Engine'), + () => + ({ + EV3Engine: class MockEV3Engine { + execute(code: string) { + return Promise.resolve({ output: `mock: ${code}` }); + } + }, + }) as any, +); -import { remoteRunnerPlugin } from "@sourceacademy/runner-remote-execution"; -import type { PySlangMessage } from "@sourceacademy/common-test"; - -test("plugin subscribes to channel and sends result on run message", async () => { +test('plugin subscribes to channel and sends result on run message', async () => { const sentMessages: PySlangMessage[] = []; let messageHandler: (msg: PySlangMessage) => void = () => {}; const mockChannel = { - name: "py_slang_channel", + name: 'py_slang_channel', send: (msg: PySlangMessage) => { sentMessages.push(msg); }, @@ -27,13 +30,11 @@ test("plugin subscribes to channel and sends result on run message", async () => close: () => {}, }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any const mockConduit = {} as any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any new remoteRunnerPlugin(mockConduit, [mockChannel as any]); - await messageHandler({ type: "run", code: "1 + 1" }); + await messageHandler({ type: 'run', code: '1 + 1' }); expect(sentMessages.length).toBeGreaterThan(0); - expect(sentMessages[0]).toMatchObject({ type: "result" }); + expect(sentMessages[0]).toMatchObject({ type: 'result' }); }); diff --git a/src/runner/test/src/index.ts b/src/runner/test/src/index.ts index 2fd8b2c..64cf857 100644 --- a/src/runner/test/src/index.ts +++ b/src/runner/test/src/index.ts @@ -1,25 +1,21 @@ -import { CHANNEL_ID, RUNNER_ID, type TestMessage } from "@sourceacademy/common-test"; +import { CHANNEL_ID, RUNNER_ID, type TestMessage } from '@sourceacademy/common-test'; import { checkIsPluginClass, type IChannel, type IConduit, type IPlugin, -} from "@sourceacademy/conductor/conduit"; +} from '@sourceacademy/conductor/conduit'; export abstract class TestPlugin implements IPlugin { readonly id: string = RUNNER_ID; static readonly channelAttach = [CHANNEL_ID]; private readonly __testChannel: IChannel; - constructor( - _conduit: IConduit, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [testChannel]: IChannel[], - ) { + constructor(_conduit: IConduit, [testChannel]: IChannel[]) { this.__testChannel = testChannel; this.__testChannel.subscribe(message => { console.log(message); }); - this.__testChannel.send("ping"); + this.__testChannel.send('ping'); } } checkIsPluginClass(TestPlugin); diff --git a/src/runner/test/tsconfig.json b/src/runner/test/tsconfig.json index c7482f9..4b54a89 100644 --- a/src/runner/test/tsconfig.json +++ b/src/runner/test/tsconfig.json @@ -5,6 +5,8 @@ "compilerOptions": { "declaration": true, "outDir": "./dist", - "rootDir": "./src" + "rootDir": "./src", + // Some weird stuff is happening with py-slang + "skipLibCheck": true } } diff --git a/src/web/autocomplete/package.json b/src/web/autocomplete/package.json index 4422d2f..b370cba 100644 --- a/src/web/autocomplete/package.json +++ b/src/web/autocomplete/package.json @@ -25,20 +25,25 @@ } }, "peerDependencies": { - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "dependencies": { "@sourceacademy/common-autocomplete": "^0.0.1" }, "devDependencies": { - "@rollup/plugin-commonjs": "^29.0.3", - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", - "@sourceacademy/conductor": "^0.8.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@rollup/plugin-commonjs": "catalog:", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", + "@sourceacademy/conductor": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/web/autocomplete" } } diff --git a/src/web/autocomplete/rollup.config.mjs b/src/web/autocomplete/rollup.config.mjs index c326a2f..952d536 100644 --- a/src/web/autocomplete/rollup.config.mjs +++ b/src/web/autocomplete/rollup.config.mjs @@ -1,22 +1,23 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; -import commonjs from "@rollup/plugin-commonjs"; -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +// @ts-check + +import commonjs from '@rollup/plugin-commonjs'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), commonjs(), typescript(), terser()], - external: ["react", "react-dom", "react/jsx-runtime", /^@sourceacademy\/conductor(?:\/|$)/], -}; + plugins: [nodeResolve(), commonjs(), typescript({ exclude: ['**/__tests__/**'] }), terser()], + external: ['react', 'react-dom', 'react/jsx-runtime', /^@sourceacademy\/conductor(?:\/|$)/], +}); diff --git a/src/web/autocomplete/src/__tests__/web.test.ts b/src/web/autocomplete/src/__tests__/web.test.ts index 9413641..4050b06 100644 --- a/src/web/autocomplete/src/__tests__/web.test.ts +++ b/src/web/autocomplete/src/__tests__/web.test.ts @@ -6,19 +6,19 @@ import { type ModeRpc, type SyntaxHighlightData, type SyntaxHighlightMessage, -} from "@sourceacademy/common-autocomplete"; +} from '@sourceacademy/common-autocomplete'; import { makeRpc, type IChannel, type IConduit, type IRpcMessage, -} from "@sourceacademy/conductor/conduit"; -import { expect, test, vi } from "vitest"; +} from '@sourceacademy/conductor/conduit'; +import { expect, test, vi } from 'vitest'; -import { BaseAutoCompleteWebPlugin } from ".."; +import { BaseAutoCompleteWebPlugin } from '..'; class TestChannel implements IChannel { - readonly name = "test"; + readonly name = 'test'; private readonly subscribers = new Set<(message: T) => void>(); peer?: TestChannel; @@ -56,16 +56,16 @@ const makeChannelPair = (): [TestChannel, TestChannel] => { const makeSyntaxData = (id: string) => ({ highlightRules: {}, - foldingRules: { hookFrom: "ace/mode/folding/cstyle", args: [] }, - lineCommentStart: "//", + foldingRules: { hookFrom: 'ace/mode/folding/cstyle', args: [] }, + lineCommentStart: '//', pairQuotesAfter: {}, - indents: { hookFrom: "ace/mode/text" }, - outdents: { hookFrom: "ace/mode/text" }, - autoOutdent: { hookFrom: "ace/mode/text" }, + indents: { hookFrom: 'ace/mode/text' }, + outdents: { hookFrom: 'ace/mode/text' }, + autoOutdent: { hookFrom: 'ace/mode/text' }, id, }) as const; -test("exposes its identity and defers mode loading until after subclass initialization", () => { +test('exposes its identity and defers mode loading until after subclass initialization', () => { const [, webAutocomplete] = makeChannelPair(); const [runnerSyntax, webSyntax] = makeChannelPair(); @@ -80,41 +80,41 @@ test("exposes its identity and defers mode loading until after subclass initiali loadMode(data: SyntaxHighlightData): void { if (!this.initialized) { - throw new Error("loadMode called before subclass initialization"); + throw new Error('loadMode called before subclass initialization'); } this.loadedModeId = data.id; } } const plugin = new TestWeb({} as IConduit, [webAutocomplete, webSyntax] as IChannel[]); - runnerSyntax.send({ type: "response", data: makeSyntaxData("identity-test") }); + runnerSyntax.send({ type: 'response', data: makeSyntaxData('identity-test') }); expect(plugin.id).toBe(WEB_PLUGIN_ID); - expect(plugin.loadedModeId).toBe("identity-test"); + expect(plugin.loadedModeId).toBe('identity-test'); expect(BaseAutoCompleteWebPlugin.channelAttach).toEqual([ AUTOCOMPLETE_CHANNEL_ID, SYNTAX_CHANNEL_ID, ]); }); -test("forwards autocomplete requests and unsubscribes after the matching response", () => { +test('forwards autocomplete requests and unsubscribes after the matching response', () => { const [runnerAutocomplete, webAutocomplete] = makeChannelPair(); const [, webSyntax] = makeChannelPair(); - const requests: Extract[] = []; + const requests: Extract[] = []; runnerAutocomplete.subscribe(message => { const autocompleteMessage = message as AutoCompleteMessage; - if (autocompleteMessage.type === "request") { + if (autocompleteMessage.type === 'request') { requests.push(autocompleteMessage); runnerAutocomplete.send({ - type: "request", + type: 'request', requestId: -1, - code: "ignored", + code: 'ignored', row: 1, column: 1, }); runnerAutocomplete.send({ - type: "response", + type: 'response', requestId: autocompleteMessage.requestId, - declarations: [{ name: "answer", meta: "var" }], + declarations: [{ name: 'answer', meta: 'var' }], }); } }); @@ -125,37 +125,36 @@ test("forwards autocomplete requests and unsubscribes after the matching respons const plugin = new TestWeb({} as IConduit, [webAutocomplete, webSyntax] as IChannel[]); const callback = vi.fn(); - plugin.autocomplete("ans", 4, 2, callback); + plugin.autocomplete('ans', 4, 2, callback); runnerAutocomplete.send({ - type: "response", + type: 'response', requestId: requests[0].requestId, - declarations: [{ name: "ignored-second-response", meta: "var" }], + declarations: [{ name: 'ignored-second-response', meta: 'var' }], }); expect(requests).toEqual([ { - type: "request", + type: 'request', requestId: expect.any(Number), - code: "ans", + code: 'ans', row: 4, column: 2, }, ]); - expect(callback).toHaveBeenCalledOnce(); - expect(callback).toHaveBeenCalledWith({ - type: "response", + expect(callback).toHaveBeenCalledExactlyOnceWith({ + type: 'response', requestId: requests[0].requestId, - declarations: [{ name: "answer", meta: "var" }], + declarations: [{ name: 'answer', meta: 'var' }], }); }); -test("keeps overlapping autocomplete responses correlated with their requests", () => { +test('keeps overlapping autocomplete responses correlated with their requests', () => { const [runnerAutocomplete, webAutocomplete] = makeChannelPair(); const [, webSyntax] = makeChannelPair(); - const requests: Extract[] = []; + const requests: Extract[] = []; runnerAutocomplete.subscribe(message => { const autocompleteMessage = message as AutoCompleteMessage; - if (autocompleteMessage.type === "request") { + if (autocompleteMessage.type === 'request') { requests.push(autocompleteMessage); } }); @@ -167,41 +166,39 @@ test("keeps overlapping autocomplete responses correlated with their requests", const firstCallback = vi.fn(); const secondCallback = vi.fn(); - plugin.autocomplete("first", 1, 5, firstCallback); - plugin.autocomplete("second", 2, 6, secondCallback); + plugin.autocomplete('first', 1, 5, firstCallback); + plugin.autocomplete('second', 2, 6, secondCallback); expect(requests).toHaveLength(2); runnerAutocomplete.send({ - type: "response", + type: 'response', requestId: Math.max(requests[0].requestId, requests[1].requestId) + 1000, - declarations: [{ name: "unmatched", meta: "var" }], + declarations: [{ name: 'unmatched', meta: 'var' }], }); runnerAutocomplete.send({ - type: "response", + type: 'response', requestId: requests[1].requestId, - declarations: [{ name: "second-result", meta: "var" }], + declarations: [{ name: 'second-result', meta: 'var' }], }); runnerAutocomplete.send({ - type: "response", + type: 'response', requestId: requests[0].requestId, - declarations: [{ name: "first-result", meta: "var" }], + declarations: [{ name: 'first-result', meta: 'var' }], }); - expect(firstCallback).toHaveBeenCalledOnce(); - expect(firstCallback).toHaveBeenCalledWith({ - type: "response", + expect(firstCallback).toHaveBeenCalledExactlyOnceWith({ + type: 'response', requestId: requests[0].requestId, - declarations: [{ name: "first-result", meta: "var" }], + declarations: [{ name: 'first-result', meta: 'var' }], }); - expect(secondCallback).toHaveBeenCalledOnce(); - expect(secondCallback).toHaveBeenCalledWith({ - type: "response", + expect(secondCallback).toHaveBeenCalledExactlyOnceWith({ + type: 'response', requestId: requests[1].requestId, - declarations: [{ name: "second-result", meta: "var" }], + declarations: [{ name: 'second-result', meta: 'var' }], }); }); -test("hydrates mode functions as RPC calls and preserves hooks", async () => { +test('hydrates mode functions as RPC calls and preserves hooks', async () => { const [, webAutocomplete] = makeChannelPair(); const [runnerSyntax, webSyntax] = makeChannelPair(); @@ -220,41 +217,35 @@ test("hydrates mode functions as RPC calls and preserves hooks", async () => { new TestWeb({} as IConduit, [webAutocomplete, webSyntax] as IChannel[]); runnerSyntax.send({ - type: "response", + type: 'response', data: { - ...makeSyntaxData("rpc-test"), - indents: { rpc: "indents" }, - autoOutdent: { rpc: "autoOutdent" }, + ...makeSyntaxData('rpc-test'), + indents: { rpc: 'indents' }, + autoOutdent: { rpc: 'autoOutdent' }, }, }); expect(loadedMode).toBeDefined(); - expect(loadedMode?.outdents).toEqual({ hookFrom: "ace/mode/text" }); + expect(loadedMode?.outdents).toEqual({ hookFrom: 'ace/mode/text' }); const indent = loadedMode?.indents; - expect(typeof indent).toBe("function"); - if (typeof indent === "function") { - await expect((indent as (...args: unknown[]) => unknown)("line")).resolves.toBe( - "indented:line", - ); - } + expect(typeof indent).toBe('function'); + await expect((indent as (...args: unknown[]) => unknown)('line')).resolves.toBe('indented:line'); const autoOutdent = loadedMode?.autoOutdent; - expect(typeof autoOutdent).toBe("function"); - if (typeof autoOutdent === "function") { - await expect((autoOutdent as (...args: unknown[]) => unknown)("line")).resolves.toBe( - "outdented:line", - ); - } + expect(typeof autoOutdent).toBe('function'); + await expect((autoOutdent as (...args: unknown[]) => unknown)('line')).resolves.toBe( + 'outdented:line', + ); }); -test("acknowledges and loads only the first syntax response", () => { +test('acknowledges and loads only the first syntax response', () => { const [, webAutocomplete] = makeChannelPair(); const [runnerSyntax, webSyntax] = makeChannelPair(); const acknowledgements: SyntaxHighlightMessage[] = []; runnerSyntax.subscribe(message => { const syntaxMessage = message as SyntaxHighlightMessage; - if (syntaxMessage.type === "ack") { + if (syntaxMessage.type === 'ack') { acknowledgements.push(syntaxMessage); } }); @@ -267,13 +258,12 @@ test("acknowledges and loads only the first syntax response", () => { } new TestWeb({} as IConduit, [webAutocomplete, webSyntax] as IChannel[]); - const data = makeSyntaxData("first-mode"); - runnerSyntax.send({ type: "request" }); - runnerSyntax.send({ type: "ack" }); - runnerSyntax.send({ type: "response", data }); - runnerSyntax.send({ type: "response", data: { ...data, id: "second-mode" } }); + const data = makeSyntaxData('first-mode'); + runnerSyntax.send({ type: 'request' }); + runnerSyntax.send({ type: 'ack' }); + runnerSyntax.send({ type: 'response', data }); + runnerSyntax.send({ type: 'response', data: { ...data, id: 'second-mode' } }); - expect(acknowledgements).toEqual([{ type: "ack" }]); - expect(loadMode).toHaveBeenCalledOnce(); - expect(loadMode).toHaveBeenCalledWith(expect.objectContaining({ id: "first-mode" })); + expect(acknowledgements).toEqual([{ type: 'ack' }]); + expect(loadMode).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ id: 'first-mode' })); }); diff --git a/src/web/autocomplete/src/index.ts b/src/web/autocomplete/src/index.ts index 537ac04..4fa2e21 100644 --- a/src/web/autocomplete/src/index.ts +++ b/src/web/autocomplete/src/index.ts @@ -1,11 +1,3 @@ -import { - makeRpc, - type IChannel, - type IConduit, - type IPlugin, - type IRpcMessage, - type Remote, -} from "@sourceacademy/conductor/conduit"; import { AUTOCOMPLETE_CHANNEL_ID, SYNTAX_CHANNEL_ID, @@ -18,7 +10,15 @@ import { type SyntaxHighlightMessage, type TransferredModeFunction, type TransferredSyntaxHighlightData, -} from "@sourceacademy/common-autocomplete"; +} from '@sourceacademy/common-autocomplete'; +import { + makeRpc, + type IChannel, + type IConduit, + type IPlugin, + type IRpcMessage, + type Remote, +} from '@sourceacademy/conductor/conduit'; let nextAutoCompleteRequestId = 0; @@ -45,14 +45,14 @@ export abstract class BaseAutoCompleteWebPlugin implements IPlugin { ) { const requestId = nextAutoCompleteRequestId++; const handler = (message: AutoCompleteMessage) => { - if (message.type === "response" && message.requestId === requestId) { + if (message.type === 'response' && message.requestId === requestId) { this.__autoCompleteChannel.unsubscribe(handler); callback(message); } }; this.__autoCompleteChannel.subscribe(handler); this.__autoCompleteChannel.send({ - type: "request", + type: 'request', requestId, code, row, @@ -67,7 +67,6 @@ export abstract class BaseAutoCompleteWebPlugin implements IPlugin { */ abstract loadMode(data: SyntaxHighlightData): void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(_conduit: IConduit, [autoCompleteChannel, syntaxChannel]: IChannel[]) { this.__autoCompleteChannel = autoCompleteChannel; this.__syntaxChannel = syntaxChannel; @@ -76,14 +75,14 @@ export abstract class BaseAutoCompleteWebPlugin implements IPlugin { {}, ); const handler = (message: SyntaxHighlightMessage) => { - if (message.type === "response") { - this.__syntaxChannel.send({ type: "ack" }); + if (message.type === 'response') { + this.__syntaxChannel.send({ type: 'ack' }); this.__syntaxChannel.unsubscribe(handler); this.loadMode(this.__hydrateMode(message.data)); } }; this.__syntaxChannel.subscribe(handler); - this.__syntaxChannel.send({ type: "request" }); + this.__syntaxChannel.send({ type: 'request' }); } private __hydrateMode(data: TransferredSyntaxHighlightData): SyntaxHighlightData { @@ -96,7 +95,7 @@ export abstract class BaseAutoCompleteWebPlugin implements IPlugin { } private __hydrateModeFunction(fn: TransferredModeFunction): ModeFunction { - if ("rpc" in fn) { + if ('rpc' in fn) { const remote = this.__modeRpc[fn.rpc]; return (...args: unknown[]) => remote(...args); } diff --git a/src/web/cse-machine/package.json b/src/web/cse-machine/package.json index bed8ff0..69175b7 100644 --- a/src/web/cse-machine/package.json +++ b/src/web/cse-machine/package.json @@ -3,6 +3,11 @@ "version": "3.0.0", "packageManager": "yarn@4.18.1", "description": "The web/host-side CSE machine plugin: receives evaluation snapshots for rendering", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/web/cse-machine" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build" @@ -26,18 +31,18 @@ }, "peerDependencies": { "@sourceacademy/common-cse-machine": ">=0.4.0", - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-cse-machine": "workspace:*", - "@sourceacademy/conductor": "^0.8.0", - "@vitest/coverage-istanbul": "^5.0.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@sourceacademy/conductor": "catalog:", + "@vitest/coverage-istanbul": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/web/cse-machine/rollup.config.mjs b/src/web/cse-machine/rollup.config.mjs index 238a07c..875363f 100644 --- a/src/web/cse-machine/rollup.config.mjs +++ b/src/web/cse-machine/rollup.config.mjs @@ -1,21 +1,21 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), typescript(), terser()], -}; + plugins: [nodeResolve(), typescript({ exclude: ['**/__tests__/**'] }), terser()], +}); diff --git a/src/web/cse-machine/src/__tests__/web.test.ts b/src/web/cse-machine/src/__tests__/web.test.ts index 0926db1..085e5a4 100644 --- a/src/web/cse-machine/src/__tests__/web.test.ts +++ b/src/web/cse-machine/src/__tests__/web.test.ts @@ -1,12 +1,12 @@ -import { describe, test, expect, vi } from "vitest"; -import { CseMachineHostPlugin } from ".."; import { CSE_CHANNEL, CSE_MESSAGE_TYPE_SNAPSHOTS, WEB_ID, type CseSnapshot, -} from "@sourceacademy/common-cse-machine"; -import type { IChannel, IConduit } from "@sourceacademy/conductor/conduit"; +} from '@sourceacademy/common-cse-machine'; +import type { IChannel, IConduit } from '@sourceacademy/conductor/conduit'; +import { describe, expect, test, vi } from 'vitest'; +import { CseMachineHostPlugin } from '..'; const makeChannel = () => { let subscriber: ((msg: unknown) => void) | undefined; @@ -31,7 +31,7 @@ const snapshot = (): CseSnapshot => ({ stepIndex: 0, control: [], stash: [], - environments: [{ id: "g", name: "global", parentId: null, bindings: [], isActive: true }], + environments: [{ id: 'g', name: 'global', parentId: null, bindings: [], isActive: true }], }); const validMessage = (snapshots: CseSnapshot[] = [snapshot()], breakpointSteps: number[] = []) => ({ @@ -43,51 +43,50 @@ const validMessage = (snapshots: CseSnapshot[] = [snapshot()], breakpointSteps: // ── Identity / wiring ───────────────────────────────────────────────────────── -describe("plugin identity", () => { - test("id is WEB_ID", () => { +describe('plugin identity', () => { + test('id is WEB_ID', () => { const channel = makeChannel(); const { plugin } = makePlugin(channel); expect(plugin.id).toBe(WEB_ID); }); - test("channelAttach declares the CSE channel", () => { + test('channelAttach declares the CSE channel', () => { expect(CseMachineHostPlugin.channelAttach).toEqual([CSE_CHANNEL]); }); - test("channelAttach contains exactly one channel", () => { + test('channelAttach contains exactly one channel', () => { expect(CseMachineHostPlugin.channelAttach).toHaveLength(1); }); }); // ── Constructor ─────────────────────────────────────────────────────────────── -describe("constructor", () => { - test("throws when no channel is provided", () => { +describe('constructor', () => { + test('throws when no channel is provided', () => { class TestPlugin extends CseMachineHostPlugin { receiveSnapshots = vi.fn(); } expect(() => new TestPlugin({} as IConduit, [])).toThrow( - "CSE channel is required but was not provided.", + 'CSE channel is required but was not provided.', ); }); - test("does not throw when a channel is provided", () => { + test('does not throw when a channel is provided', () => { expect(() => makePlugin(makeChannel())).not.toThrow(); }); }); // ── Valid messages ──────────────────────────────────────────────────────────── -describe("valid messages", () => { - test("receiveSnapshots is called with the snapshot array", () => { +describe('valid messages', () => { + test('receiveSnapshots is called with the snapshot array', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit(validMessage()); - expect(receive).toHaveBeenCalledOnce(); - expect(receive).toHaveBeenCalledWith([snapshot()], []); + expect(receive).toHaveBeenCalledExactlyOnceWith([snapshot()], []); }); - test("receiveSnapshots receives multiple snapshots in order", () => { + test('receiveSnapshots receives multiple snapshots in order', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); const snaps = [snapshot(), { ...snapshot(), stepIndex: 1 }, { ...snapshot(), stepIndex: 2 }]; @@ -95,7 +94,7 @@ describe("valid messages", () => { expect(receive).toHaveBeenCalledWith(snaps, []); }); - test("receiveSnapshots is called once per valid message", () => { + test('receiveSnapshots is called once per valid message', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit(validMessage()); @@ -103,35 +102,35 @@ describe("valid messages", () => { expect(receive).toHaveBeenCalledTimes(2); }); - test("handles an empty snapshots array", () => { + test('handles an empty snapshots array', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit(validMessage([])); expect(receive).toHaveBeenCalledWith([], []); }); - test("preserves snapshot fields passed through the channel", () => { + test('preserves snapshot fields passed through the channel', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); const snap: CseSnapshot = { stepIndex: 4, - control: [{ displayText: "branch", metadata: { instrType: "Branch" } }], - stash: [{ displayValue: "false", label: "bool" }], - environments: [{ id: "g", name: "global", parentId: null, bindings: [], isActive: true }], + control: [{ displayText: 'branch', metadata: { instrType: 'Branch' } }], + stash: [{ displayValue: 'false', label: 'bool' }], + environments: [{ id: 'g', name: 'global', parentId: null, bindings: [], isActive: true }], currentLine: 9, }; channel.emit(validMessage([snap])); expect(receive).toHaveBeenCalledWith([snap], []); }); - test("receiveSnapshots is called with the breakpointSteps array", () => { + test('receiveSnapshots is called with the breakpointSteps array', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit(validMessage([snapshot(), { ...snapshot(), stepIndex: 1 }], [1])); expect(receive).toHaveBeenCalledWith([snapshot(), { ...snapshot(), stepIndex: 1 }], [1]); }); - test("defaults breakpointSteps to an empty array when the field is missing from the message", () => { + test('defaults breakpointSteps to an empty array when the field is missing from the message', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit({ type: CSE_MESSAGE_TYPE_SNAPSHOTS, snapshots: [snapshot()], totalSteps: 1 }); @@ -141,61 +140,61 @@ describe("valid messages", () => { // ── Invalid / malformed messages ────────────────────────────────────────────── -describe("invalid messages", () => { - test("unknown message type is silently ignored", () => { +describe('invalid messages', () => { + test('unknown message type is silently ignored', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); - channel.emit({ type: "unknown", snapshots: [snapshot()], totalSteps: 1 }); + channel.emit({ type: 'unknown', snapshots: [snapshot()], totalSteps: 1 }); expect(receive).not.toHaveBeenCalled(); }); - test("null message is silently ignored", () => { + test('null message is silently ignored', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit(null); expect(receive).not.toHaveBeenCalled(); }); - test("undefined message is silently ignored", () => { + test('undefined message is silently ignored', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit(undefined); expect(receive).not.toHaveBeenCalled(); }); - test("snapshots field is not an array — ignored", () => { + test('snapshots field is not an array — ignored', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); - channel.emit({ type: CSE_MESSAGE_TYPE_SNAPSHOTS, snapshots: "bad", totalSteps: 1 }); + channel.emit({ type: CSE_MESSAGE_TYPE_SNAPSHOTS, snapshots: 'bad', totalSteps: 1 }); expect(receive).not.toHaveBeenCalled(); }); - test("totalSteps mismatch — ignored", () => { + test('totalSteps mismatch — ignored', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit({ type: CSE_MESSAGE_TYPE_SNAPSHOTS, snapshots: [snapshot()], totalSteps: 99 }); expect(receive).not.toHaveBeenCalled(); }); - test("missing snapshots field — ignored", () => { + test('missing snapshots field — ignored', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit({ type: CSE_MESSAGE_TYPE_SNAPSHOTS, totalSteps: 0 }); expect(receive).not.toHaveBeenCalled(); }); - test("empty object — ignored", () => { + test('empty object — ignored', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit({}); expect(receive).not.toHaveBeenCalled(); }); - test("invalid messages do not block subsequent valid ones", () => { + test('invalid messages do not block subsequent valid ones', () => { const channel = makeChannel(); const { receive } = makePlugin(channel); channel.emit(null); - channel.emit({ type: "bad" }); + channel.emit({ type: 'bad' }); channel.emit(validMessage()); expect(receive).toHaveBeenCalledOnce(); }); @@ -203,8 +202,8 @@ describe("invalid messages", () => { // ── Re-exports ──────────────────────────────────────────────────────────────── -describe("re-exports from common", () => { - test("CseSnapshot type is re-exported and usable", () => { +describe('re-exports from common', () => { + test('CseSnapshot type is re-exported and usable', () => { // If the import at the top of this file resolves, the re-export works. // This test documents the contract rather than testing runtime behaviour. const snap: CseSnapshot = snapshot(); diff --git a/src/web/cse-machine/src/index.ts b/src/web/cse-machine/src/index.ts index bb1a322..87c57ba 100644 --- a/src/web/cse-machine/src/index.ts +++ b/src/web/cse-machine/src/index.ts @@ -4,8 +4,8 @@ import { WEB_ID, type CseSnapshot, type CseSnapshotMessage, -} from "@sourceacademy/common-cse-machine"; -import type { IPlugin, IChannel, IConduit } from "@sourceacademy/conductor/conduit"; +} from '@sourceacademy/common-cse-machine'; +import type { IChannel, IConduit, IPlugin } from '@sourceacademy/conductor/conduit'; // Re-export the protocol types so host apps can depend on a single package for the // web side of the CSE machine. @@ -16,7 +16,7 @@ export type { CseSerializedBinding, CseSerializedEnvFrame, CseSnapshotMessage, -} from "@sourceacademy/common-cse-machine"; +} from '@sourceacademy/common-cse-machine'; /** * Web/host-side CSE machine plugin. @@ -40,13 +40,9 @@ export abstract class CseMachineHostPlugin implements IPlugin { */ abstract receiveSnapshots(snapshots: CseSnapshot[], breakpointSteps: number[]): void; - constructor( - _conduit: IConduit, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [cseChannel]: IChannel[], - ) { + constructor(_conduit: IConduit, [cseChannel]: IChannel[]) { if (!cseChannel) { - throw new Error("CSE channel is required but was not provided."); + throw new Error('CSE channel is required but was not provided.'); } (cseChannel as IChannel).subscribe(message => { if ( diff --git a/src/web/data-visualizer/package.json b/src/web/data-visualizer/package.json index 99da402..29d57e0 100644 --- a/src/web/data-visualizer/package.json +++ b/src/web/data-visualizer/package.json @@ -3,6 +3,11 @@ "version": "0.0.1", "packageManager": "yarn@4.18.1", "description": "Host (web) plugin for the Source Academy data visualizer: classifies and renders drawn structures", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/web/data-visualizer" + }, "scripts": { "build": "rollup -c && node wrap.mjs", "prepack": "yarn build" @@ -28,14 +33,14 @@ "devDependencies": { "@blueprintjs/core": "^6.0.0", "@blueprintjs/icons": "^6.0.0", - "@rollup/plugin-commonjs": "^29.0.0", - "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-commonjs": "catalog:", + "@rollup/plugin-node-resolve": "catalog:", "@rollup/plugin-replace": "^6.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-data-visualizer": "workspace:*", "@sourceacademy/common-tabs": "workspace:^", - "@sourceacademy/conductor": "^0.8.0", + "@sourceacademy/conductor": "catalog:", "@types/react": "^19.0.0", "@types/react-test-renderer": "^19.0.0", "konva": "^10.0.0", @@ -43,8 +48,9 @@ "react-dom": "^19.0.0", "react-konva": "^19.0.0", "react-test-renderer": "^19.0.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3" + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/web/data-visualizer/rollup.config.mjs b/src/web/data-visualizer/rollup.config.mjs index 1a20d0a..c0167d7 100644 --- a/src/web/data-visualizer/rollup.config.mjs +++ b/src/web/data-visualizer/rollup.config.mjs @@ -1,8 +1,11 @@ -import commonjs from "@rollup/plugin-commonjs"; -import nodeResolve from "@rollup/plugin-node-resolve"; -import replace from "@rollup/plugin-replace"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check + +import commonjs from '@rollup/plugin-commonjs'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import replace from '@rollup/plugin-replace'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; // React, Blueprint, and Konva/react-konva are provided by the host at load time through the // require-provider (see the frontend's `requireProvider`, which already exposes them for other @@ -12,33 +15,30 @@ import typescript from "@rollup/plugin-typescript"; // dispatcher at runtime. Everything else (conductor conduit, the common data-visualizer protocol) // is bundled. wrap.mjs then wraps the CJS output into the host's factory contract. const external = [ - "react", - "react-dom", - "react/jsx-runtime", - "react/jsx-dev-runtime", - "@blueprintjs/core", - "konva", - "react-konva", + 'react', + 'react-dom', + 'react/jsx-runtime', + 'react/jsx-dev-runtime', + '@blueprintjs/core', + 'konva', + 'react-konva', ]; -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +export default defineConfig({ + input: 'src/index.ts', output: { - file: "dist/index.cjs", - format: "cjs", - exports: "default", + file: 'dist/index.cjs', + format: 'cjs', + exports: 'default', }, external, plugins: [ // konva/react-konva pull in CJS deps (e.g. react-is) that branch on `process.env.NODE_ENV` at // module-init time; there's no `process` global in the browser, so replace it at build time. - replace({ preventAssignment: true, "process.env.NODE_ENV": JSON.stringify("production") }), + replace({ preventAssignment: true, 'process.env.NODE_ENV': JSON.stringify('production') }), nodeResolve({ browser: true, preferBuiltins: false }), commonjs(), - typescript(), + typescript({ exclude: ['**/__tests__/**'] }), terser(), ], -}; +}); diff --git a/src/web/data-visualizer/src/Config.ts b/src/web/data-visualizer/src/Config.ts index 65ae772..eb3d5d5 100644 --- a/src/web/data-visualizer/src/Config.ts +++ b/src/web/data-visualizer/src/Config.ts @@ -3,8 +3,8 @@ */ export const Config = { StrokeWidth: 2, - Stroke: "white", - Fill: "white", + Stroke: 'white', + Fill: 'white', NWidth: 90, BoxWidth: 45, diff --git a/src/web/data-visualizer/src/DataVisualizerHostPlugin.tsx b/src/web/data-visualizer/src/DataVisualizerHostPlugin.tsx index bab1a7e..a0b5c6e 100644 --- a/src/web/data-visualizer/src/DataVisualizerHostPlugin.tsx +++ b/src/web/data-visualizer/src/DataVisualizerHostPlugin.tsx @@ -3,20 +3,20 @@ import { WEB_ID, type DataVisualizerMessage, type SerializedDataVisualizerRow, -} from "@sourceacademy/common-data-visualizer"; -import type { ITabService, Tab } from "@sourceacademy/common-tabs"; +} from '@sourceacademy/common-data-visualizer'; +import type { ITabService, Tab } from '@sourceacademy/common-tabs'; import { checkIsPluginClass, type IChannel, type IConduit, type IPlugin, -} from "@sourceacademy/conductor/conduit"; -import { createElement, useSyncExternalStore } from "react"; +} from '@sourceacademy/conductor/conduit'; +import { createElement, useSyncExternalStore } from 'react'; -import DataVisualizerView from "./DataVisualizerView"; +import DataVisualizerView from './DataVisualizerView'; /** The side-content tab id used by the host to show/hide the data visualizer tab. */ -const TAB_ID = "data-visualizer"; +const TAB_ID = 'data-visualizer'; /** * The host (browser-side) half of the data visualizer. It listens on the data visualizer channel @@ -45,13 +45,13 @@ export class DataVisualizerHostPlugin implements IPlugin { ) { this.__channel = channel; this.__channel.subscribe(message => { - if (message.type === "rows") { + if (message.type === 'rows') { this.__rows = message.rows; this.__emit(); } }); // Ask the runner to replay the rows it last computed (e.g. tab opened after a run). - this.__channel.send({ type: "request" }); + this.__channel.send({ type: 'request' }); const subscribe = (listener: () => void) => this.subscribe(listener); const getRows = () => this.getRows(); @@ -62,8 +62,8 @@ export class DataVisualizerHostPlugin implements IPlugin { const tab: Tab = { id: TAB_ID, - label: "Data Visualizer", - iconName: "eye-open", + label: 'Data Visualizer', + iconName: 'eye-open', body: createElement(DataVisualizerTab), }; tabService.registerTab(tab); diff --git a/src/web/data-visualizer/src/DataVisualizerView.tsx b/src/web/data-visualizer/src/DataVisualizerView.tsx index 63ecf7b..f8f11fb 100644 --- a/src/web/data-visualizer/src/DataVisualizerView.tsx +++ b/src/web/data-visualizer/src/DataVisualizerView.tsx @@ -1,14 +1,14 @@ -import { Button, ButtonGroup, Card, Classes } from "@blueprintjs/core"; -import { useEffect, useState } from "react"; +import { Button, ButtonGroup, Card, Classes } from '@blueprintjs/core'; +import type { SerializedDataVisualizerRow } from '@sourceacademy/common-data-visualizer'; +import { useEffect, useState } from 'react'; -import type { SerializedDataVisualizerRow } from "@sourceacademy/common-data-visualizer"; -import { Config } from "./Config"; -import { Tree, type ViewMode } from "./tree/Tree"; +import { Config } from './Config'; +import { Tree, type ViewMode } from './tree/Tree'; const VIEW_MODES: { mode: ViewMode; label: string }[] = [ - { mode: "classic", label: "Classic" }, - { mode: "binaryTree", label: "Binary Tree" }, - { mode: "generalTree", label: "General Tree" }, + { mode: 'classic', label: 'Classic' }, + { mode: 'binaryTree', label: 'Binary Tree' }, + { mode: 'generalTree', label: 'General Tree' }, ]; // To account for overflow to the left/top due to a backward arrow — matches the old code's @@ -40,7 +40,7 @@ type Props = { */ export default function DataVisualizerView({ rows }: Props) { const [currentStep, setCurrentStep] = useState(0); - const [viewMode, setViewMode] = useState("classic"); + const [viewMode, setViewMode] = useState('classic'); // Mirrors the old code's `DataVisualizer.init(steps => { ...; setCurrentStep(0); })`: every time a // new set of calls arrives (a fresh Run), jump back to the first call. @@ -57,18 +57,18 @@ export default function DataVisualizerView({ rows }: Props) { const active = document.activeElement; const isEditable = active instanceof HTMLElement && - (active.isContentEditable || active.tagName === "INPUT" || active.tagName === "TEXTAREA"); + (active.isContentEditable || active.tagName === 'INPUT' || active.tagName === 'TEXTAREA'); if (isEditable) { return; } - if (event.key === "ArrowLeft") { + if (event.key === 'ArrowLeft') { setCurrentStep(prev => Math.max(0, prev - 1)); - } else if (event.key === "ArrowRight") { + } else if (event.key === 'ArrowRight') { setCurrentStep(prev => Math.min(rows.length - 1, prev + 1)); } } - window.addEventListener("keydown", onKeyDown); - return () => window.removeEventListener("keydown", onKeyDown); + window.addEventListener('keydown', onKeyDown); + return () => window.removeEventListener('keydown', onKeyDown); }, [rows.length]); const onPrevButtonClick = () => setCurrentStep(prev => Math.max(0, prev - 1)); @@ -81,7 +81,7 @@ export default function DataVisualizerView({ rows }: Props) { return (
{rows.length > 0 ? ( -
+
{VIEW_MODES.map(({ mode, label }) => ( {usingHostDiagram ? null : ( @@ -552,10 +552,10 @@ export default function EStepperView({ steps, profile, error, cseDiagram, layout ) ) : ( <> - +
{explanation}
-
+
setOutputOpen(o => !o)}> - {outputOpen ? "â–¾" : "â–¸"} Output + {outputOpen ? 'â–¾' : 'â–¸'} Output
{outputOpen ?
{output}
: null}
diff --git a/src/web/e-stepper/src/EnvDiagram.tsx b/src/web/e-stepper/src/EnvDiagram.tsx index 48c874a..7098bb2 100644 --- a/src/web/e-stepper/src/EnvDiagram.tsx +++ b/src/web/e-stepper/src/EnvDiagram.tsx @@ -1,24 +1,24 @@ -import type { CseDiagramAnchorResolver } from "@sourceacademy/common-cse-machine"; +import type { CseDiagramAnchorResolver } from '@sourceacademy/common-cse-machine'; import type { EStepperFrame, EStepperHeapObject, EStepperLookup, EStepperValue, -} from "@sourceacademy/common-e-stepper"; -import { useEffect, useMemo, useState } from "react"; -import { Arrow, Circle, Group, Layer, Line, Rect, Stage, Text } from "react-konva"; +} from '@sourceacademy/common-e-stepper'; +import { useEffect, useMemo, useState } from 'react'; +import { Arrow, Circle, Group, Layer, Line, Rect, Stage, Text } from 'react-konva'; -import { DiagramColors, frameColor } from "./colors"; +import { DiagramColors, frameColor } from './colors'; import { + LayoutConfig as C, + layoutDiagram, + valueLabel, type ArrowSpec, type FrameBox, - layoutDiagram, - LayoutConfig as C, type ObjectBox, - valueLabel, -} from "./layout"; +} from './layout'; -const FONT = "Inconsolata, Consolas, monospace"; +const FONT = 'Inconsolata, Consolas, monospace'; const FONT_SIZE = 13; interface Props { @@ -67,12 +67,12 @@ export default function EnvDiagram(props: Props) { // Arrows end at the top, horizontally centred, of an object (a function's circles, a // list's boxes), and at the left edge of a frame's box. const at = - kind === "object" + kind === 'object' ? layout.objects.find(b => b.object.id === id) : layout.frames.find(b => b.frame.id === id); if (!at) return null; const point = - "object" in at + 'object' in at ? { x: at.x + at.width / 2, y: at.y } : { x: at.x, y: at.y + C.headerHeight + at.height / 2 }; return { @@ -160,7 +160,7 @@ function FrameDrawing(props: { - {row.value.kind === "ref" ? ( + {row.value.kind === 'ref' ? ( )} @@ -244,7 +244,7 @@ function ObjectDrawing(props: { const strokeWidth = 2; const fill = props.hovered ? DiagramColors.hoverBackground : undefined; const label = - object.kind === "function" && object.name ? `${object.id} ${object.name}` : object.id; + object.kind === 'function' && object.name ? `${object.id} ${object.name}` : object.id; return ( - {object.kind === "function" ? ( + {object.kind === 'function' ? ( <> - element.kind === "ref" ? ( + element.kind === 'ref' ? ( diff --git a/src/web/e-stepper/src/ProgramArrows.tsx b/src/web/e-stepper/src/ProgramArrows.tsx index 3987e01..ab56d54 100644 --- a/src/web/e-stepper/src/ProgramArrows.tsx +++ b/src/web/e-stepper/src/ProgramArrows.tsx @@ -1,10 +1,10 @@ -import type { CseDiagramAnchorResolver, CseDiagramPoint } from "@sourceacademy/common-cse-machine"; -import { useLayoutEffect, useRef, useState } from "react"; +import type { CseDiagramAnchorResolver, CseDiagramPoint } from '@sourceacademy/common-cse-machine'; +import { useLayoutEffect, useRef, useState } from 'react'; /** An arrow from a badge or bracket in the program pane to an object or frame in the diagram. */ export interface ProgramArrow { key: string; - kind: "object" | "frame"; + kind: 'object' | 'frame'; /** The object's or frame's id. */ id: string; /** In the overlay's coordinates (pixels from the top-left of the panes' container). */ @@ -15,8 +15,8 @@ export interface ProgramArrow { } /** The marks in the program pane that arrows start from. */ -export const REF_ATTRIBUTE = "data-estepper-ref"; -export const ENV_ATTRIBUTE = "data-estepper-env"; +export const REF_ATTRIBUTE = 'data-estepper-ref'; +export const ENV_ATTRIBUTE = 'data-estepper-env'; const HEAD_LENGTH = 8; const HEAD_HALF_WIDTH = 4; @@ -25,7 +25,7 @@ const fmt = (p: CseDiagramPoint) => `${round(p.x)} ${round(p.y)}`; /** How an arrow arrives at its target: along the axis it travelled, down onto the top of an * object, or from the left into the left edge of a frame. */ -export type Arrival = "axis" | "above" | "left"; +export type Arrival = 'axis' | 'above' | 'left'; /** * The curve from `from` to `to` and its arrowhead. The curve leaves along the axis the two points @@ -37,7 +37,7 @@ export type Arrival = "axis" | "above" | "left"; export function arrowGeometry( from: CseDiagramPoint, to: CseDiagramPoint, - arrival: Arrival = "axis", + arrival: Arrival = 'axis', scale = 1, ): { path: string; head: string } { const dx = to.x - from.x; @@ -49,9 +49,9 @@ export function arrowGeometry( const lead = dx > 0 ? Math.max(24, Math.min(dx / 2, 120)) : 40; const c1 = horizontal ? { x: from.x + reach, y: from.y } : { x: from.x, y: from.y + reach }; const c2 = - arrival === "above" + arrival === 'above' ? { x: to.x, y: to.y - drop } - : arrival === "left" + : arrival === 'left' ? { x: to.x - lead, y: to.y } : horizontal ? { x: to.x - reach, y: to.y } @@ -95,7 +95,7 @@ export function computeProgramArrows( const programRect = program.getBoundingClientRect(); const diagramRect = diagram.getBoundingClientRect(); const arrows: ProgramArrow[] = []; - const collect = (attribute: string, kind: ProgramArrow["kind"]) => { + const collect = (attribute: string, kind: ProgramArrow['kind']) => { program.querySelectorAll(`[${attribute}]`).forEach((mark, index) => { const id = mark.getAttribute(attribute)!; const rect = mark.getBoundingClientRect(); @@ -116,8 +116,8 @@ export function computeProgramArrows( }); }); }; - collect(REF_ATTRIBUTE, "object"); - collect(ENV_ATTRIBUTE, "frame"); + collect(REF_ATTRIBUTE, 'object'); + collect(ENV_ATTRIBUTE, 'frame'); return arrows; } @@ -135,7 +135,7 @@ interface Props { colorOfFrame: (frameId: string) => string; } -const REF_COLOR = "#c5cbd3"; +const REF_COLOR = '#c5cbd3'; /** * Draws the arrows from the program pane into the diagram, over both: the arrows to objects all the @@ -156,8 +156,8 @@ export default function ProgramArrows({ useLayoutEffect(() => { const container = svgRef.current?.parentElement; - const program = container?.querySelector(".estepper-program"); - const diagram = container?.querySelector(".estepper-diagram"); + const program = container?.querySelector('.estepper-program'); + const diagram = container?.querySelector('.estepper-diagram'); const next = resolve && container && program && diagram ? computeProgramArrows(container, program, diagram, resolve) @@ -166,14 +166,14 @@ export default function ProgramArrows({ }); useLayoutEffect(() => { - if (typeof window === "undefined") return; - const program = svgRef.current?.parentElement?.querySelector(".estepper-program"); + if (typeof window === 'undefined') return; + const program = svgRef.current?.parentElement?.querySelector('.estepper-program'); const remeasure = () => setScrolled(n => n + 1); - program?.addEventListener("scroll", remeasure); - window.addEventListener("resize", remeasure); + program?.addEventListener('scroll', remeasure); + window.addEventListener('resize', remeasure); return () => { - program?.removeEventListener("scroll", remeasure); - window.removeEventListener("resize", remeasure); + program?.removeEventListener('scroll', remeasure); + window.removeEventListener('resize', remeasure); }; }, []); @@ -181,15 +181,15 @@ export default function ProgramArrows({ {arrows.map(arrow => { const emphasized = - arrow.kind === "object" ? hovered === arrow.id : hoveredFrame === arrow.id; - if (!emphasized && (arrow.kind === "frame" || !all)) return null; + arrow.kind === 'object' ? hovered === arrow.id : hoveredFrame === arrow.id; + if (!emphasized && (arrow.kind === 'frame' || !all)) return null; const { path, head } = arrowGeometry( arrow.from, arrow.to, - arrow.kind === "object" ? "above" : "left", + arrow.kind === 'object' ? 'above' : 'left', arrow.scale, ); - const color = arrow.kind === "frame" ? colorOfFrame(arrow.id) : REF_COLOR; + const color = arrow.kind === 'frame' ? colorOfFrame(arrow.id) : REF_COLOR; return ( ({ default: {} })); -vi.mock("react-konva", () => { +import { EStepperHostPlugin } from '../EStepperHostPlugin'; +import steps from './makeWithdrawSteps.json'; + +vi.mock(import('konva'), () => ({ default: {} as any })); +vi.mock(import('react-konva'), () => { const stub = (name: string) => (props: Record) => createElement(`konva-${name.toLowerCase()}`, props, props.children as never); return Object.fromEntries( - ["Stage", "Layer", "Text", "Group", "Line", "Rect", "Circle", "Arrow"].map(n => [n, stub(n)]), + ['Stage', 'Layer', 'Text', 'Group', 'Line', 'Rect', 'Circle', 'Arrow'].map(n => [n, stub(n)]), ); }); // Blueprint's overlays need a DOM to unmount (the options menu goes when the host draws the diagram). -vi.mock("@blueprintjs/core", async (importOriginal: () => Promise) => ({ +vi.mock(import('@blueprintjs/core'), async importOriginal => ({ ...(await importOriginal()), - Popover: (props: Record) => createElement("popover-stub", props), + Popover: ((props: Record) => createElement('popover-stub', props)) as any, })); -import { EStepperHostPlugin } from "../EStepperHostPlugin"; -import steps from "./makeWithdrawSteps.json"; - type PluginArgs = ConstructorParameters; function setUp(hostServices?: PluginArgs[3]) { let deliver!: (message: EStepperMessage) => void; const sent: EStepperMessage[] = []; const channel = { - name: "__e_stepper", + name: '__e_stepper', subscribe: (fn: (message: EStepperMessage) => void) => (deliver = fn), unsubscribe: () => {}, send: (message: EStepperMessage) => sent.push(message), @@ -38,28 +38,28 @@ function setUp(hostServices?: PluginArgs[3]) { // Stubs with only what the plugin uses. const conduit = {} as PluginArgs[0]; const tabServiceStub = tabService as unknown as PluginArgs[2]; - const plugin = new EStepperHostPlugin(conduit, [channel as never], tabServiceStub, hostServices); + const plugin = new EStepperHostPlugin(conduit, [channel], tabServiceStub, hostServices); return { plugin, deliver: (m: EStepperMessage) => act(() => deliver(m)), sent, tab, tabService }; } -describe("EStepperHostPlugin", () => { +describe(EStepperHostPlugin, () => { test("registers and reveals the E-Stepper tab, and asks for the last run's steps", () => { const { tab, tabService, sent } = setUp(); - expect(tab).toMatchObject({ id: "e-stepper", label: "E-Stepper", iconName: "flow-review" }); - expect(tabService.revealTab).toHaveBeenCalledWith("e-stepper"); - expect(sent).toEqual([{ type: "request" }]); + expect(tab).toMatchObject({ id: 'e-stepper', label: 'E-Stepper', iconName: 'flow-review' }); + expect(tabService.revealTab).toHaveBeenCalledWith('e-stepper'); + expect(sent).toEqual([{ type: 'request' }]); }); - test("keeps the latest steps or error", () => { + test('keeps the latest steps or error', () => { const { plugin, deliver } = setUp(); - deliver({ type: "steps", steps: steps as unknown as EStepperStep[] }); + deliver({ type: 'steps', steps: steps as unknown as EStepperStep[] }); expect(plugin.getState().steps).toHaveLength(3); - deliver({ type: "error", error: "SyntaxError" }); - expect(plugin.getState()).toMatchObject({ steps: [], error: "SyntaxError" }); + deliver({ type: 'error', error: 'SyntaxError' }); + expect(plugin.getState()).toMatchObject({ steps: [], error: 'SyntaxError' }); }); test("lends the host's CSE diagram to the tab", () => { - const createView = vi.fn(() => createElement("host-cse-view")); + const createView = vi.fn(() => createElement('host-cse-view')); const { tab, deliver } = setUp({ cseDiagram: { createView } }); let view!: TestRenderer.ReactTestRenderer; act(() => { @@ -68,14 +68,14 @@ describe("EStepperHostPlugin", () => { }); }); deliver({ - type: "steps", + type: 'steps', steps: (steps as unknown as EStepperStep[]).map((s, i) => ({ ...s, cse: { stepIndex: i, control: [], stash: [], environments: [] }, })), }); expect(createView).toHaveBeenCalled(); - expect(view.root.findAllByType("host-cse-view" as unknown as React.ElementType)).toHaveLength( + expect(view.root.findAllByType('host-cse-view' as unknown as React.ElementType)).toHaveLength( 1, ); }); diff --git a/src/web/e-stepper/src/__tests__/EStepperView.test.tsx b/src/web/e-stepper/src/__tests__/EStepperView.test.tsx index 13f1aed..b264c49 100644 --- a/src/web/e-stepper/src/__tests__/EStepperView.test.tsx +++ b/src/web/e-stepper/src/__tests__/EStepperView.test.tsx @@ -1,33 +1,33 @@ -import type { CseDiagramViewProps } from "@sourceacademy/common-cse-machine"; -import type { EStepperStep, SyntaxProfile } from "@sourceacademy/common-e-stepper"; -import { act, createElement } from "react"; -import { Button, Checkbox, Popover, Slider, Switch } from "@blueprintjs/core"; -import TestRenderer from "react-test-renderer"; -import { beforeEach, describe, expect, test, vi } from "vitest"; +import { Button, Checkbox, Popover, Slider, Switch } from '@blueprintjs/core'; +import type { CseDiagramViewProps } from '@sourceacademy/common-cse-machine'; +import type { EStepperStep, SyntaxProfile } from '@sourceacademy/common-e-stepper'; +import { act, createElement } from 'react'; +import TestRenderer from 'react-test-renderer'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; + +import EStepperView, { nextBreakpoint, previousBreakpoint, sliderLabels } from '../EStepperView'; +import ProgramArrows from '../ProgramArrows'; +import steps from './makeWithdrawSteps.json'; +import pythonProfile from './pythonProfile.json'; // As in the data visualizer's tests: avoid Konva's Node build (it requires the optional `canvas`). -vi.mock("konva", () => ({ default: {} })); -vi.mock("react-konva", () => { +vi.mock(import('konva'), () => ({ default: {} as any })); +vi.mock(import('react-konva'), () => { const stub = (name: string) => (props: Record) => createElement(`konva-${name.toLowerCase()}`, props, props.children as never); return Object.fromEntries( - ["Stage", "Layer", "Text", "Group", "Line", "Rect", "Circle", "Arrow"].map(n => [n, stub(n)]), + ['Stage', 'Layer', 'Text', 'Group', 'Line', 'Rect', 'Circle', 'Arrow'].map(n => [n, stub(n)]), ); }); // Blueprint's overlays need a DOM; the tests read the options menu's content from the props. -vi.mock("@blueprintjs/core", async (importOriginal: () => Promise) => ({ +vi.mock(import('@blueprintjs/core'), async (importOriginal: () => Promise) => ({ ...(await importOriginal()), - Popover: (props: Record) => createElement("popover-stub", props), + Popover: ((props: Record) => createElement('popover-stub', props)) as any, })); -import EStepperView, { nextBreakpoint, previousBreakpoint, sliderLabels } from "../EStepperView"; -import ProgramArrows from "../ProgramArrows"; -import steps from "./makeWithdrawSteps.json"; - /** A stubbed Konva shape's host element type (see the react-konva mock above). */ const konva = (type: string) => type as unknown as React.ElementType; -import pythonProfile from "./pythonProfile.json"; // Both produced by py-slang's e-stepper: three steps of make_withdraw, and its Python profile. const fixture = steps as unknown as EStepperStep[]; @@ -37,24 +37,24 @@ const profile = pythonProfile as unknown as SyntaxProfile; const withRefs: EStepperStep = { ...fixture[2], ast: { - type: "Program", - nodeId: "p", + type: 'Program', + nodeId: 'p', body: [ { - type: "ExpressionStatement", - nodeId: "s", + type: 'ExpressionStatement', + nodeId: 's', expression: { - type: "CallExpression", - nodeId: "c", + type: 'CallExpression', + nodeId: 'c', callee: { - type: "Builtin", - nodeId: "f", - name: "print", - hoverText: "built-in function print", + type: 'Builtin', + nodeId: 'f', + name: 'print', + hoverText: 'built-in function print', }, arguments: [ - { type: "Ref", nodeId: "r1", objectId: "#2" }, - { type: "Ref", nodeId: "r2", objectId: "#4" }, + { type: 'Ref', nodeId: 'r1', objectId: '#2' }, + { type: 'Ref', nodeId: 'r2', objectId: '#4' }, ], }, }, @@ -75,35 +75,35 @@ function render(props: Parameters[0], width = 600) { /** Whether a node has the CSS class `name` (among its space-separated classes). */ const hasClass = (node: TestRenderer.ReactTestInstance, name: string): boolean => - typeof node.props.className === "string" && node.props.className.split(" ").includes(name); + typeof node.props.className === 'string' && node.props.className.split(' ').includes(name); const text = (node: TestRenderer.ReactTestInstance): string => - node.children.map(c => (typeof c === "string" ? c : text(c))).join(""); + node.children.map(c => (typeof c === 'string' ? c : text(c))).join(''); -describe("EStepperView", () => { +describe(EStepperView, () => { // The slider remounts when the tab's size is known (it measures its track then), and a Blueprint // slider takes its listeners off the document when it goes away. beforeEach(() => { - vi.stubGlobal("document", { + vi.stubGlobal('document', { addEventListener() {}, removeEventListener() {}, - getElementById: () => ({ textContent: "" }), + getElementById: () => ({ textContent: '' }), }); }); - test("shows the welcome text before anything has run", () => { + test('shows the welcome text before anything has run', () => { const view = render({ steps: [] }); - expect(text(view.root)).toContain("Welcome to the environment stepper"); + expect(text(view.root)).toContain('Welcome to the environment stepper'); }); - test("the welcome text links to the guide for students", () => { + test('the welcome text links to the guide for students', () => { const view = render({ steps: [] }); - const link = view.root.findByType("a"); - expect(link.props.href).toBe("https://docs.sourceacademy.org/python/e-stepper/"); - expect(link.props.target).toBe("_blank"); + const link = view.root.findByType('a'); + expect(link.props.href).toBe('https://docs.sourceacademy.org/python/e-stepper/'); + expect(link.props.target).toBe('_blank'); }); - test("the slider counts the steps taken: 0 on the left, the total on the right", () => { + test('the slider counts the steps taken: 0 on the left, the total on the right', () => { const run = Array.from({ length: 174 }, (_, i) => ({ ...fixture[0], key: i })); const view = render({ steps: run, profile }); const slider = () => view.root.findByType(Slider); @@ -118,13 +118,13 @@ describe("EStepperView", () => { expect(slider().props.value).toBe(173); act(() => view.root - .find(n => hasClass(n, "sa-e-stepper")) - .props.onKeyDown({ key: "a", preventDefault: () => {} }), + .find(n => hasClass(n, 'sa-e-stepper')) + .props.onKeyDown({ key: 'a', preventDefault: () => {} }), ); expect(slider().props.value).toBe(0); }); - test("a run of one step gives the slider a valid range, disabled", () => { + test('a run of one step gives the slider a valid range, disabled', () => { const view = render({ steps: [fixture[0]], profile }); const slider = view.root.findByType(Slider); expect(slider.props.min).toBe(0); @@ -134,7 +134,7 @@ describe("EStepperView", () => { expect(render({ steps: fixture, profile }).root.findByType(Slider).props.disabled).toBe(false); }); - test("labels the ends of the slider and a round step in between, never crowding the end", () => { + test('labels the ends of the slider and a round step in between, never crowding the end', () => { expect(sliderLabels(0)).toEqual([0]); expect(sliderLabels(1)).toEqual([0, 1]); expect(sliderLabels(10)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); @@ -147,13 +147,13 @@ describe("EStepperView", () => { expect(sliderLabels(37)).toEqual([0, 4, 8, 12, 16, 20, 24, 28, 32, 37]); }); - describe("breakpoints", () => { + describe('breakpoints', () => { /** Ten steps; steps 4 and 8 (1-based) are breakpoints. */ - const stop: EStepperStep["markers"] = [ + const stop: EStepperStep['markers'] = [ { - explanation: "Running breakpoint()", - redexType: "beforeMarker", - redexNodeType: "DebuggerStatement", + explanation: 'Running breakpoint()', + redexType: 'beforeMarker', + redexNodeType: 'DebuggerStatement', }, ]; const run = Array.from({ length: 10 }, (_, i) => ({ @@ -161,53 +161,53 @@ describe("EStepperView", () => { markers: i === 3 || i === 7 ? stop : [{ explanation: `step ${i + 1}` }], })); - test("the next breakpoint after a step, or else the last step", () => { + test('the next breakpoint after a step, or else the last step', () => { expect([1, 3].map(v => nextBreakpoint(run, v))).toEqual([4, 4]); expect([4, 7].map(v => nextBreakpoint(run, v))).toEqual([8, 8]); expect([8, 9, 10].map(v => nextBreakpoint(run, v))).toEqual([10, 10, 10]); }); - test("the previous breakpoint before a step, or else the first step", () => { + test('the previous breakpoint before a step, or else the first step', () => { expect([10, 9, 8].map(v => previousBreakpoint(run, v))).toEqual([8, 8, 4]); expect([5, 4].map(v => previousBreakpoint(run, v))).toEqual([4, 1]); expect(previousBreakpoint(run, 1)).toBe(1); }); - test("without breakpoints they are the first and last step", () => { - const plain = run.map(s => ({ ...s, markers: [{ explanation: "x" }] })); + test('without breakpoints they are the first and last step', () => { + const plain = run.map(s => ({ ...s, markers: [{ explanation: 'x' }] })); expect(nextBreakpoint(plain, 3)).toBe(10); expect(previousBreakpoint(plain, 7)).toBe(1); expect(nextBreakpoint([], 1)).toBe(1); }); - test("the double arrows and a / e jump between them", () => { + test('the double arrows and a / e jump between them', () => { const view = render({ steps: run, profile }); const slider = () => view.root.findByType(Slider).props.value as number; const press = (key: string) => act(() => view.root - .find(n => hasClass(n, "sa-e-stepper")) + .find(n => hasClass(n, 'sa-e-stepper')) .props.onKeyDown({ key, preventDefault: () => {} }), ); const arrow = (icon: string) => view.root.findAllByType(Button).find(b => b.props.icon === icon)!; expect(slider()).toBe(0); - press("e"); + press('e'); expect(slider()).toBe(3); // step 4, the first breakpoint (the slider counts from 0) - act(() => arrow("double-chevron-right").props.onClick()); + act(() => arrow('double-chevron-right').props.onClick()); expect(slider()).toBe(7); - press("e"); + press('e'); expect(slider()).toBe(9); // no more: the last step - press("a"); + press('a'); expect(slider()).toBe(7); - act(() => arrow("double-chevron-left").props.onClick()); + act(() => arrow('double-chevron-left').props.onClick()); expect(slider()).toBe(3); - press("a"); + press('a'); expect(slider()).toBe(0); }); }); - test("has no step controls before anything has run, and has them once there are steps", () => { + test('has no step controls before anything has run, and has them once there are steps', () => { const before = render({ steps: [] }); expect(before.root.findAllByType(Slider)).toHaveLength(0); expect(before.root.findAllByType(Button)).toHaveLength(0); @@ -216,76 +216,76 @@ describe("EStepperView", () => { expect(after.root.findAllByType(Button).length).toBeGreaterThan(0); }); - test("shows a runner error", () => { + test('shows a runner error', () => { const view = render({ steps: [], error: "NameError: name 'x' is not defined" }); expect(text(view.root)).toContain("NameError: name 'x' is not defined"); }); - test("renders a function body under evaluation as a bracket labelled with its frame", () => { + test('renders a function body under evaluation as a bracket labelled with its frame', () => { const view = render({ steps: [fixture[1]], profile }); - const blocks = view.root.findAll(n => n.props.className === "estepper-envblock"); + const blocks = view.root.findAll(n => n.props.className === 'estepper-envblock'); expect(blocks).toHaveLength(1); - const label = blocks[0].find(n => n.props.className === "estepper-envblock-label"); - expect(text(label)).toBe("E2"); + const label = blocks[0].find(n => n.props.className === 'estepper-envblock-label'); + expect(text(label)).toBe('E2'); }); test("renders references as badges only; a function object's name is in its tooltip", () => { const view = render({ steps: [withRefs], profile }); - expect(view.root.findAll(n => hasClass(n, "estepper-ref"))).toHaveLength(2); - const badges = view.root.findAll(n => n.props.className === "estepper-ref-badge").map(text); - expect(badges).toEqual(["#2", "#4"]); - expect(view.root.findAll(n => n.props.className === "estepper-ref-name")).toHaveLength(0); + expect(view.root.findAll(n => hasClass(n, 'estepper-ref'))).toHaveLength(2); + const badges = view.root.findAll(n => n.props.className === 'estepper-ref-badge').map(text); + expect(badges).toEqual(['#2', '#4']); + expect(view.root.findAll(n => n.props.className === 'estepper-ref-name')).toHaveLength(0); const titles = view.root - .findAll(n => hasClass(n, "estepper-ref")) + .findAll(n => hasClass(n, 'estepper-ref')) .map(n => n.props.title as string | undefined); - expect(titles[0]).toBe("function withdraw(amount)"); + expect(titles[0]).toBe('function withdraw(amount)'); }); - test("hovering a reference highlights it", () => { + test('hovering a reference highlights it', () => { const view = render({ steps: [withRefs], profile }); - const [first] = view.root.findAll(n => hasClass(n, "estepper-ref")); + const [first] = view.root.findAll(n => hasClass(n, 'estepper-ref')); act(() => first.props.onMouseEnter()); - const hovered = view.root.findAll(n => hasClass(n, "estepper-ref") && hasClass(n, "hovered")); + const hovered = view.root.findAll(n => hasClass(n, 'estepper-ref') && hasClass(n, 'hovered')); expect(hovered).toHaveLength(1); - expect(text(hovered[0])).toContain("#2"); + expect(text(hovered[0])).toContain('#2'); act(() => hovered[0].props.onMouseLeave()); - expect(view.root.findAll(n => hasClass(n, "hovered"))).toHaveLength(0); + expect(view.root.findAll(n => hasClass(n, 'hovered'))).toHaveLength(0); }); test("shows the step's explanation and the program's output", () => { const view = render({ steps: [fixture[2]], profile }); const all = text(view.root); - expect(all).toContain("Evaluation complete"); - expect(all).toContain("Output"); - expect(all).toContain("50"); + expect(all).toContain('Evaluation complete'); + expect(all).toContain('Output'); + expect(all).toContain('50'); }); - test("draws the environment diagram for the current step", () => { + test('draws the environment diagram for the current step', () => { const view = render({ steps: [fixture[1]], profile }); - expect(view.root.findAllByType(konva("konva-stage"))).toHaveLength(1); + expect(view.root.findAllByType(konva('konva-stage'))).toHaveLength(1); }); - test("stacks the panes when Vertical is chosen, with a divider that resizes the program pane", () => { + test('stacks the panes when Vertical is chosen, with a divider that resizes the program pane', () => { const view = render({ steps: [fixture[1]], profile }); act(() => view.root .findAllByType(Button) - .find(b => b.props["aria-label"] === "Vertical")! + .find(b => b.props['aria-label'] === 'Vertical')! .props.onClick(), ); - const main = view.root.find(n => hasClass(n, "estepper-main")); - expect(hasClass(main, "narrow")).toBe(true); + const main = view.root.find(n => hasClass(n, 'estepper-main')); + expect(hasClass(main, 'narrow')).toBe(true); const listeners: Record void> = {}; - vi.stubGlobal("window", { + vi.stubGlobal('window', { addEventListener: (type: string, fn: (e: { clientY: number }) => void) => (listeners[type] = fn), removeEventListener: (type: string) => delete listeners[type], }); - const divider = view.root.find(n => hasClass(n, "estepper-divider")); + const divider = view.root.find(n => hasClass(n, 'estepper-divider')); act(() => divider.props.onPointerDown({ clientY: 100 })); act(() => listeners.pointermove({ clientY: 160 })); - const left = view.root.find(n => hasClass(n, "estepper-left")); - expect(left.props.style.flex).toBe("0 0 300px"); + const left = view.root.find(n => hasClass(n, 'estepper-left')); + expect(left.props.style.flex).toBe('0 0 300px'); act(() => listeners.pointerup({ clientY: 160 })); expect(listeners.pointermove).toBeUndefined(); vi.unstubAllGlobals(); @@ -297,52 +297,52 @@ describe("EStepperView", () => { act(() => first.root .findAllByType(Button) - .find(b => b.props["aria-label"] === "Vertical")! + .find(b => b.props['aria-label'] === 'Vertical')! .props.onClick(), ); act(() => first.unmount()); const second = render({ steps: [fixture[1]], profile, layout }); - const main = second.root.find(n => hasClass(n, "estepper-main")); - expect(hasClass(main, "narrow")).toBe(true); + const main = second.root.find(n => hasClass(n, 'estepper-main')); + expect(hasClass(main, 'narrow')).toBe(true); }); - test("the divider can not be dragged out of view: the diagram below keeps its least height", () => { + test('the divider can not be dragged out of view: the diagram below keeps its least height', () => { // The panes' container is 600px high (see `render`'s node mock): 80px stay for the diagram, // and 26px for the divider and its gaps. const view = render({ steps: [fixture[1]], profile }); act(() => view.root .findAllByType(Button) - .find(b => b.props["aria-label"] === "Vertical")! + .find(b => b.props['aria-label'] === 'Vertical')! .props.onClick(), ); const listeners: Record void> = {}; - vi.stubGlobal("window", { + vi.stubGlobal('window', { addEventListener: (type: string, fn: (e: { clientY: number }) => void) => (listeners[type] = fn), removeEventListener: (type: string) => delete listeners[type], }); - const divider = view.root.find(n => hasClass(n, "estepper-divider")); + const divider = view.root.find(n => hasClass(n, 'estepper-divider')); act(() => divider.props.onPointerDown({ clientY: 0 })); act(() => listeners.pointermove({ clientY: 100000 })); - const left = view.root.find(n => hasClass(n, "estepper-left")); - expect(left.props.style.flex).toBe("0 0 494px"); + const left = view.root.find(n => hasClass(n, 'estepper-left')); + expect(left.props.style.flex).toBe('0 0 494px'); act(() => listeners.pointerup({ clientY: 100000 })); // And back: the divider is still there to be dragged. act(() => - view.root.find(n => hasClass(n, "estepper-divider")).props.onPointerDown({ clientY: 0 }), + view.root.find(n => hasClass(n, 'estepper-divider')).props.onPointerDown({ clientY: 0 }), ); act(() => listeners.pointermove({ clientY: -100000 })); // The program may shrink to about one line. - expect(view.root.find(n => hasClass(n, "estepper-left")).props.style.flex).toBe("0 0 40px"); + expect(view.root.find(n => hasClass(n, 'estepper-left')).props.style.flex).toBe('0 0 40px'); vi.unstubAllGlobals(); }); - test("the slider measures its track again when the tab is resized (a click maps to the right step)", () => { + test('the slider measures its track again when the tab is resized (a click maps to the right step)', () => { const observers: ((entries: { contentRect: { width: number; height: number } }[]) => void)[] = []; vi.stubGlobal( - "ResizeObserver", + 'ResizeObserver', class { constructor(callback: (typeof observers)[number]) { observers.push(callback); @@ -364,7 +364,7 @@ describe("EStepperView", () => { const observers: ((entries: { contentRect: { width: number; height: number } }[]) => void)[] = []; vi.stubGlobal( - "ResizeObserver", + 'ResizeObserver', class { constructor(callback: (typeof observers)[number]) { observers.push(callback); @@ -376,11 +376,11 @@ describe("EStepperView", () => { const view = render({ steps: [fixture[1]], profile }, 1200); const narrow = () => hasClass( - view.root.find(n => hasClass(n, "estepper-main")), - "narrow", + view.root.find(n => hasClass(n, 'estepper-main')), + 'narrow', ); const vertical = () => - view.root.findAllByType(Button).find(b => b.props["aria-label"] === "Vertical")!; + view.root.findAllByType(Button).find(b => b.props['aria-label'] === 'Vertical')!; const resize = (width: number) => act(() => observers.forEach(cb => cb([{ contentRect: { width, height: 600 } }]))); // Wide at first: side by side, the box not ticked. @@ -398,18 +398,18 @@ describe("EStepperView", () => { act(() => vertical().props.onClick()); expect(narrow()).toBe(false); // The button states its state; the checkbox in it is only a picture. - expect(vertical().props["aria-pressed"]).toBe(false); + expect(vertical().props['aria-pressed']).toBe(false); act(() => vertical().props.onClick()); - expect(vertical().props["aria-pressed"]).toBe(true); + expect(vertical().props['aria-pressed']).toBe(true); expect(view.root.findByType(Checkbox).props.tabIndex).toBe(-1); vi.unstubAllGlobals(); }); - test("side by side in a tab too narrow for both panes: the program keeps its least width, never negative", () => { + test('side by side in a tab too narrow for both panes: the program keeps its least width, never negative', () => { const observers: ((entries: { contentRect: { width: number; height: number } }[]) => void)[] = []; vi.stubGlobal( - "ResizeObserver", + 'ResizeObserver', class { constructor(callback: (typeof observers)[number]) { observers.push(callback); @@ -419,7 +419,7 @@ describe("EStepperView", () => { }, ); const view = render({ steps: [fixture[1]], profile }, 1200); - const left = () => view.root.find(n => hasClass(n, "estepper-left")).props.style.flex; + const left = () => view.root.find(n => hasClass(n, 'estepper-left')).props.style.flex; for (const width of [250, 150, 10]) { act(() => observers.forEach(cb => cb([{ contentRect: { width, height: 600 } }]))); expect(left()).toBe(`0 0 ${Math.min(44, Math.max(0, width - 18))}px`); @@ -427,14 +427,14 @@ describe("EStepperView", () => { vi.unstubAllGlobals(); }); - test("puts the panes side by side in a wide tab, with a divider that resizes the program pane", () => { + test('puts the panes side by side in a wide tab, with a divider that resizes the program pane', () => { // The tab's size, as the ResizeObservers report it (one for the tab, one for the diagram). const observers: ((entries: { contentRect: { width: number; height: number } }[]) => void)[] = []; const resize = (width: number) => observers.forEach(callback => callback([{ contentRect: { width, height: 600 } }])); vi.stubGlobal( - "ResizeObserver", + 'ResizeObserver', class { constructor(callback: (typeof observers)[number]) { observers.push(callback); @@ -444,25 +444,25 @@ describe("EStepperView", () => { }, ); const view = render({ steps: [fixture[1]], profile }, 1200); - const main = view.root.find(n => hasClass(n, "estepper-main")); - expect(hasClass(main, "narrow")).toBe(false); - const left = () => view.root.find(n => hasClass(n, "estepper-left")).props.style.flex; + const main = view.root.find(n => hasClass(n, 'estepper-main')); + expect(hasClass(main, 'narrow')).toBe(false); + const left = () => view.root.find(n => hasClass(n, 'estepper-left')).props.style.flex; // 1200px wide, of which 1182px are left for the panes (2 gaps of 8px, and 2px of divider). expect(left()).toBe(`0 0 ${Math.round(0.45 * 1182)}px`); const listeners: Record void> = {}; - vi.stubGlobal("window", { + vi.stubGlobal('window', { addEventListener: (type: string, fn: (e: { clientX: number }) => void) => (listeners[type] = fn), removeEventListener: (type: string) => delete listeners[type], }); - const divider = view.root.find(n => hasClass(n, "estepper-divider")); - expect(hasClass(divider, "vertical")).toBe(true); + const divider = view.root.find(n => hasClass(n, 'estepper-divider')); + expect(hasClass(divider, 'vertical')).toBe(true); act(() => divider.props.onPointerDown({ clientX: 600, preventDefault: () => {} })); act(() => listeners.pointermove({ clientX: 480 })); expect(left()).toBe(`0 0 ${Math.round(0.45 * 1182) - 120}px`); // The program keeps at least 44px (about three characters), the diagram 200px. act(() => listeners.pointermove({ clientX: 0 })); - expect(left()).toBe("0 0 44px"); + expect(left()).toBe('0 0 44px'); act(() => listeners.pointermove({ clientX: 2000 })); expect(left()).toBe(`0 0 ${1182 - 200}px`); act(() => listeners.pointerup({ clientX: 2000 })); @@ -473,7 +473,7 @@ describe("EStepperView", () => { vi.unstubAllGlobals(); }); - test("fills the height down to the bottom of the browser window", () => { + test('fills the height down to the bottom of the browser window', () => { let renderer!: TestRenderer.ReactTestRenderer; const element = ; act(() => { @@ -483,10 +483,10 @@ describe("EStepperView", () => { }), }); }); - const root = () => renderer.root.find(n => hasClass(n, "sa-e-stepper")); + const root = () => renderer.root.find(n => hasClass(n, 'sa-e-stepper')); // (The test environment has no window until stubbed: then the height is worked out again.) const browser = { innerHeight: 1000, addEventListener() {}, removeEventListener() {} }; - vi.stubGlobal("window", browser); + vi.stubGlobal('window', browser); act(() => renderer.update()); // From its top (200px) to the window's bottom (1000px), less a 16px gap... expect(root().props.style).toEqual({ height: 784 }); @@ -497,7 +497,7 @@ describe("EStepperView", () => { vi.unstubAllGlobals(); }); - test("leaves room for the margins and paddings of the containers around it", () => { + test('leaves room for the margins and paddings of the containers around it', () => { // As in the Source Academy frontend: a margin on the tab, and margins and paddings on the // panels around it, each of which would make a panel scroll if the tab went down to the // bottom of the window regardless. @@ -514,16 +514,16 @@ describe("EStepperView", () => { createNodeMock: () => element, }); }); - vi.stubGlobal("window", { innerHeight: 676, addEventListener() {}, removeEventListener() {} }); - vi.stubGlobal("document", { body: {} }); - vi.stubGlobal("getComputedStyle", (e: { m: number }) => ({ + vi.stubGlobal('window', { innerHeight: 676, addEventListener() {}, removeEventListener() {} }); + vi.stubGlobal('document', { body: {} }); + vi.stubGlobal('getComputedStyle', (e: { m: number }) => ({ marginBottom: `${e.m}px`, - paddingBottom: "0px", - borderBottomWidth: "0px", + paddingBottom: '0px', + borderBottomWidth: '0px', })); act(() => renderer.update()); // 676 - 164 - ceil(15 + 6.4 + 6.4 + 9.6) - expect(renderer.root.find(n => hasClass(n, "sa-e-stepper")).props.style).toEqual({ + expect(renderer.root.find(n => hasClass(n, 'sa-e-stepper')).props.style).toEqual({ height: 676 - 164 - 38, }); vi.unstubAllGlobals(); @@ -531,37 +531,37 @@ describe("EStepperView", () => { test("the explanation takes the height of its text, whatever the host's styles", () => { const view = render({ steps: [fixture[1]], profile }); - const card = view.root.findAll(n => hasClass(n, "bp6-card") || hasClass(n, "bp5-card"))[0]; - expect(card.props.style).toMatchObject({ height: "auto", flex: "0 0 auto" }); + const card = view.root.findAll(n => hasClass(n, 'bp6-card') || hasClass(n, 'bp5-card'))[0]; + expect(card.props.style).toMatchObject({ height: 'auto', flex: '0 0 auto' }); }); - test("steps with the buttons and the keyboard", () => { + test('steps with the buttons and the keyboard', () => { const view = render({ steps: fixture, profile }); const explanation = () => - text(view.root.findAll(n => n.props.className === "result-output")[0]); + text(view.root.findAll(n => n.props.className === 'result-output')[0]); const press = (key: string) => act(() => view.root - .find(n => hasClass(n, "sa-e-stepper")) + .find(n => hasClass(n, 'sa-e-stepper')) .props.onKeyDown({ key, preventDefault: () => {} }), ); - expect(explanation()).toBe("Start of evaluation"); - press("e"); - expect(explanation()).toBe("Evaluation complete"); - press("b"); - expect(explanation()).toBe("Assigned balance = 50 in frame E1"); - press("a"); - expect(explanation()).toBe("Start of evaluation"); - press("f"); - expect(explanation()).toBe("Assigned balance = 50 in frame E1"); + expect(explanation()).toBe('Start of evaluation'); + press('e'); + expect(explanation()).toBe('Evaluation complete'); + press('b'); + expect(explanation()).toBe('Assigned balance = 50 in frame E1'); + press('a'); + expect(explanation()).toBe('Start of evaluation'); + press('f'); + expect(explanation()).toBe('Assigned balance = 50 in frame E1'); const buttons = view.root.findAllByType(Button); act(() => buttons[3].props.onClick()); - expect(explanation()).toBe("Evaluation complete"); + expect(explanation()).toBe('Evaluation complete'); act(() => buttons[0].props.onClick()); - expect(explanation()).toBe("Start of evaluation"); + expect(explanation()).toBe('Start of evaluation'); }); - describe("display options", () => { + describe('display options', () => { /** The options menu's switches, by label; the menu's content is rendered on its own. */ function options(view: TestRenderer.ReactTestRenderer) { let menu!: TestRenderer.ReactTestRenderer; @@ -572,11 +572,11 @@ describe("EStepperView", () => { menu.root.findAllByType(Switch).map(sw => [sw.props.label as string, sw]), ); } - test("arrows are on by default; turning them off leaves the arrows of what is hovered", () => { + test('arrows are on by default; turning them off leaves the arrows of what is hovered', () => { const view = render({ steps: fixture, profile }); const diagram = view.root.find(n => n.props.onAnchors && n.props.frames); act(() => diagram.props.onAnchors(() => ({ x: 1, y: 2 }))); - const arrows = options(view)["From program"]; + const arrows = options(view)['From program']; expect(arrows.props.checked).toBe(true); expect(view.root.findAllByType(ProgramArrows)).toHaveLength(1); expect(view.root.findByType(ProgramArrows).props.all).toBe(true); @@ -585,7 +585,7 @@ describe("EStepperView", () => { expect(view.root.findByType(ProgramArrows).props.all).toBe(false); }); - test("Clear Unreachable Objects is also available when only a heap object is unreachable", () => { + test('Clear Unreachable Objects is also available when only a heap object is unreachable', () => { const onlyObject: EStepperStep = { ...fixture[2], frames: fixture[2].frames.map(f => ({ ...f, isGarbage: false })), @@ -594,21 +594,21 @@ describe("EStepperView", () => { const view = render({ steps: [onlyObject], profile }); const clear = view.root .findAllByType(Button) - .find(b => b.props.text === "Clear Unreachable Objects")!; + .find(b => b.props.text === 'Clear Unreachable Objects')!; expect(clear.props.disabled).toBe(false); }); - test("Clear Unreachable Objects clears them from the diagram until the step changes", () => { + test('Clear Unreachable Objects clears them from the diagram until the step changes', () => { const view = render({ steps: fixture, profile }); const diagram = () => view.root.find(n => n.props.onAnchors && n.props.frames); const clear = () => - view.root.findAllByType(Button).find(b => b.props.text === "Clear Unreachable Objects")!; + view.root.findAllByType(Button).find(b => b.props.text === 'Clear Unreachable Objects')!; // Only the last step has unreachable frames (E2). expect(clear().props.disabled).toBe(true); act(() => view.root - .find(n => hasClass(n, "sa-e-stepper")) - .props.onKeyDown({ key: "e", preventDefault: () => {} }), + .find(n => hasClass(n, 'sa-e-stepper')) + .props.onKeyDown({ key: 'e', preventDefault: () => {} }), ); expect(diagram().props.clearUnreachable).toBe(false); expect(clear().props.disabled).toBe(false); @@ -617,18 +617,18 @@ describe("EStepperView", () => { expect(clear().props.disabled).toBe(true); act(() => view.root - .find(n => hasClass(n, "sa-e-stepper")) - .props.onKeyDown({ key: "b", preventDefault: () => {} }), + .find(n => hasClass(n, 'sa-e-stepper')) + .props.onKeyDown({ key: 'b', preventDefault: () => {} }), ); expect(diagram().props.clearUnreachable).toBe(false); }); }); - test("the output strip can be collapsed", () => { + test('the output strip can be collapsed', () => { const view = render({ steps: [fixture[2]], profile }); - const toggle = view.root.find(n => hasClass(n, "estepper-output-toggle")); + const toggle = view.root.find(n => hasClass(n, 'estepper-output-toggle')); act(() => toggle.props.onClick()); - expect(view.root.findAll(n => hasClass(n, "estepper-output"))).toHaveLength(0); + expect(view.root.findAll(n => hasClass(n, 'estepper-output'))).toHaveLength(0); }); describe("with the host's CSE machine diagram", () => { @@ -638,7 +638,7 @@ describe("EStepperView", () => { })); const service = () => { const createView = vi.fn((props: CseDiagramViewProps) => - createElement("host-cse-view", { "data-step": props.step }), + createElement('host-cse-view', { 'data-step': props.step }), ); return { createView }; }; @@ -660,31 +660,31 @@ describe("EStepperView", () => { expect(view.root.findAllByType(ProgramArrows)).toHaveLength(0); }); - test("draws the environments with it, at the current step", () => { + test('draws the environments with it, at the current step', () => { const cseDiagram = service(); const view = render({ steps: withCse, profile, cseDiagram }); - expect(view.root.findAllByType(konva("konva-stage"))).toHaveLength(0); - expect(view.root.findByType(konva("host-cse-view")).props["data-step"]).toBe(0); + expect(view.root.findAllByType(konva('konva-stage'))).toHaveLength(0); + expect(view.root.findByType(konva('host-cse-view')).props['data-step']).toBe(0); const call = cseDiagram.createView.mock.calls.at(-1)![0]; expect(call.snapshots).toHaveLength(withCse.length); act(() => view.root - .find(n => hasClass(n, "sa-e-stepper")) - .props.onKeyDown({ key: "e", preventDefault: () => {} }), + .find(n => hasClass(n, 'sa-e-stepper')) + .props.onKeyDown({ key: 'e', preventDefault: () => {} }), ); - expect(view.root.findByType(konva("host-cse-view")).props["data-step"]).toBe( + expect(view.root.findByType(konva('host-cse-view')).props['data-step']).toBe( withCse.length - 1, ); }); - test("falls back to its own diagram when the steps carry no CSE snapshots", () => { + test('falls back to its own diagram when the steps carry no CSE snapshots', () => { const cseDiagram = service(); const view = render({ steps: fixture, profile, cseDiagram }); expect(cseDiagram.createView).not.toHaveBeenCalled(); - expect(view.root.findAllByType(konva("konva-stage"))).toHaveLength(1); + expect(view.root.findAllByType(konva('konva-stage'))).toHaveLength(1); }); - test("gives it the frame colours, and shares the hovered frame with it", () => { + test('gives it the frame colours, and shares the hovered frame with it', () => { const cseDiagram = service(); // From the step inside the call of withdraw, which shows an environment bracket. const view = render({ steps: withCse.slice(1), profile, cseDiagram }); @@ -692,8 +692,8 @@ describe("EStepperView", () => { const first = withCse[1]; expect(Object.keys(props().frameColors ?? {})).toEqual(first.frames.map(f => f.id)); // The program's environment blocks report hovering... - const label = view.root.findAll(n => hasClass(n, "estepper-envblock-label"))[0]; - const block = view.root.findAll(n => hasClass(n, "estepper-envblock"))[0]; + const label = view.root.findAll(n => hasClass(n, 'estepper-envblock-label'))[0]; + const block = view.root.findAll(n => hasClass(n, 'estepper-envblock'))[0]; const stopPropagation = vi.fn(); act(() => block.props.onMouseOver({ stopPropagation })); expect(props().hoveredFrame).toBe(text(label)); @@ -704,19 +704,19 @@ describe("EStepperView", () => { // ...and a frame hovered in the diagram is highlighted in the program. act(() => props().onHoverFrame!(text(label))); expect( - view.root.findAll(n => hasClass(n, "estepper-envblock") && hasClass(n, "hovered")), + view.root.findAll(n => hasClass(n, 'estepper-envblock') && hasClass(n, 'hovered')), ).not.toHaveLength(0); }); - test("shares the hovered object with it", () => { + test('shares the hovered object with it', () => { const cseDiagram = service(); const view = render({ steps: withCse, profile, cseDiagram }); const { onHover } = cseDiagram.createView.mock.calls.at(-1)![0]; - act(() => onHover!("#1")); - expect(cseDiagram.createView.mock.calls.at(-1)![0].hovered).toBe("#1"); + act(() => onHover!('#1')); + expect(cseDiagram.createView.mock.calls.at(-1)![0].hovered).toBe('#1'); act(() => onHover!(null)); expect(cseDiagram.createView.mock.calls.at(-1)![0].hovered).toBeNull(); - expect(view.root.findAllByType(konva("konva-stage"))).toHaveLength(0); + expect(view.root.findAllByType(konva('konva-stage'))).toHaveLength(0); }); }); }); diff --git a/src/web/e-stepper/src/__tests__/EnvDiagram.test.tsx b/src/web/e-stepper/src/__tests__/EnvDiagram.test.tsx index d6308a8..2c62d22 100644 --- a/src/web/e-stepper/src/__tests__/EnvDiagram.test.tsx +++ b/src/web/e-stepper/src/__tests__/EnvDiagram.test.tsx @@ -1,25 +1,25 @@ -import type { EStepperStep } from "@sourceacademy/common-e-stepper"; -import { act, createElement } from "react"; -import TestRenderer from "react-test-renderer"; -import { describe, expect, test, vi } from "vitest"; +import type { EStepperStep } from '@sourceacademy/common-e-stepper'; +import { act, createElement } from 'react'; +import TestRenderer from 'react-test-renderer'; +import { describe, expect, test, vi } from 'vitest'; + +import EnvDiagram from '../EnvDiagram'; +import { DiagramColors } from '../colors'; +import { layoutDiagram } from '../layout'; +import steps from './makeWithdrawSteps.json'; // Konva's Node build needs the optional `canvas` package, so the shapes are stubbed — as plain // host elements (`konva-rect`, ...) that keep their props and render their children, so the // drawing code runs and what it draws can be inspected. -vi.mock("konva", () => ({ default: {} })); -vi.mock("react-konva", () => { +vi.mock(import('konva'), () => ({ default: {} as any })); +vi.mock(import('react-konva'), () => { const stub = (name: string) => (props: Record) => createElement(`konva-${name.toLowerCase()}`, props, props.children as never); return Object.fromEntries( - ["Stage", "Layer", "Text", "Group", "Line", "Rect", "Circle", "Arrow"].map(n => [n, stub(n)]), + ['Stage', 'Layer', 'Text', 'Group', 'Line', 'Rect', 'Circle', 'Arrow'].map(n => [n, stub(n)]), ); }); -import { DiagramColors } from "../colors"; -import EnvDiagram from "../EnvDiagram"; -import { layoutDiagram } from "../layout"; -import steps from "./makeWithdrawSteps.json"; - /** A stubbed Konva shape's host element type (see the react-konva mock above). */ const konva = (type: string) => type as unknown as React.ElementType; @@ -48,54 +48,54 @@ function draw(overrides: Partial[0]> = {}) { } const texts = (root: TestRenderer.ReactTestInstance) => - root.findAllByType(konva("konva-text")).map(t => String(t.props.text)); + root.findAllByType(konva('konva-text')).map(t => String(t.props.text)); -describe("EnvDiagram", () => { - test("draws every frame with its label and bindings", () => { +describe(EnvDiagram, () => { + test('draws every frame with its label and bindings', () => { const all = texts(draw().root); - expect(all).toEqual(expect.arrayContaining(["Global", "E1 make_withdraw", "E2 withdraw"])); - expect(all).toEqual(expect.arrayContaining(["balance:", "50", "amount:"])); + expect(all).toEqual(expect.arrayContaining(['Global', 'E1 make_withdraw', 'E2 withdraw'])); + expect(all).toEqual(expect.arrayContaining(['balance:', '50', 'amount:'])); }); - test("outlines the active frame", () => { + test('outlines the active frame', () => { const frameRects = draw() - .root.findAllByType(konva("konva-rect")) + .root.findAllByType(konva('konva-rect')) .filter(r => r.props.cornerRadius === 6); const widths = frameRects.map(r => r.props.strokeWidth); expect(widths.filter(w => w === 4)).toHaveLength(1); }); - test("draws function objects as two circles and lists as boxes with their elements", () => { + test('draws function objects as two circles and lists as boxes with their elements', () => { const { root } = draw(); const all = texts(root); - expect(all).toEqual(expect.arrayContaining(["#1 make_withdraw", "#2 withdraw", "#4", "#3"])); - expect(all).toEqual(expect.arrayContaining(["1", "2"])); - expect(root.findAllByType(konva("konva-arrow")).length).toBeGreaterThan(5); + expect(all).toEqual(expect.arrayContaining(['#1 make_withdraw', '#2 withdraw', '#4', '#3'])); + expect(all).toEqual(expect.arrayContaining(['1', '2'])); + expect(root.findAllByType(konva('konva-arrow')).length).toBeGreaterThan(5); }); - test("highlights the bindings a step reads", () => { - const { root } = draw({ lookups: [{ frameId: "E1", name: "balance" }] }); + test('highlights the bindings a step reads', () => { + const { root } = draw({ lookups: [{ frameId: 'E1', name: 'balance' }] }); const highlights = root - .findAllByType(konva("konva-rect")) + .findAllByType(konva('konva-rect')) .filter(r => r.props.fill === DiagramColors.lookup); expect(highlights).toHaveLength(1); }); - test("dims garbage and reports hovering over an object", () => { + test('dims garbage and reports hovering over an object', () => { const last = (steps as unknown as EStepperStep[])[2]; const { root, onHover } = draw({ frames: last.frames, heap: last.heap, - activeFrameId: "Global", + activeFrameId: 'Global', }); const dimmed = root - .findAllByType(konva("konva-group")) + .findAllByType(konva('konva-group')) .filter(g => g.props.opacity === DiagramColors.garbageOpacity); expect(dimmed.length).toBeGreaterThan(0); // Frames report hovering through onHoverFrame (not given here), objects through onHover. const object = root - .findAllByType(konva("konva-group")) - .filter(g => typeof g.props.onMouseEnter === "function") + .findAllByType(konva('konva-group')) + .filter(g => typeof g.props.onMouseEnter === 'function') .find(g => { act(() => g.props.onMouseEnter()); return onHover.mock.calls.length > 0; @@ -105,40 +105,40 @@ describe("EnvDiagram", () => { expect(onHover).toHaveBeenLastCalledWith(null); }); - test("draws unreachable frames, their data and their arrows grey, and live ones in their colours", () => { + test('draws unreachable frames, their data and their arrows grey, and live ones in their colours', () => { const last = (steps as unknown as EStepperStep[])[2]; - const { root } = draw({ frames: last.frames, heap: last.heap, activeFrameId: "Global" }); + const { root } = draw({ frames: last.frames, heap: last.heap, activeFrameId: 'Global' }); const outlines = root - .findAllByType(konva("konva-rect")) + .findAllByType(konva('konva-rect')) .filter(r => r.props.cornerRadius === 6) .map(r => r.props.stroke); expect(outlines.filter(c => c === DiagramColors.garbage)).toHaveLength( last.frames.filter(f => f.isGarbage).length, ); expect(new Set(outlines).size).toBeGreaterThan(1); - const arrows = root.findAllByType(konva("konva-arrow")).map(a => a.props.stroke); + const arrows = root.findAllByType(konva('konva-arrow')).map(a => a.props.stroke); expect(arrows).toContain(DiagramColors.garbage); expect(arrows).toContain(DiagramColors.stroke); }); - test("draws the values in a unreachable frame grey too, not just its names", () => { + test('draws the values in a unreachable frame grey too, not just its names', () => { const frame = (id: string, parentId: string | null, isGarbage: boolean) => ({ id, - name: id === "Global" ? "global" : "f", + name: id === 'Global' ? 'global' : 'f', parentId, - bindings: [{ name: "n", value: { kind: "primitive" as const, display: "77", label: "int" } }], + bindings: [{ name: 'n', value: { kind: 'primitive' as const, display: '77', label: 'int' } }], isGarbage, }); const { root } = draw({ - frames: [frame("Global", null, false), frame("E1", "Global", true)], + frames: [frame('Global', null, false), frame('E1', 'Global', true)], heap: [], - activeFrameId: "Global", + activeFrameId: 'Global', }); - const values = root.findAllByType(konva("konva-text")).filter(t => t.props.text === "77"); + const values = root.findAllByType(konva('konva-text')).filter(t => t.props.text === '77'); expect(values.map(t => t.props.fill)).toEqual([DiagramColors.text, DiagramColors.garbage]); }); - describe("anchors for arrows from the program", () => { + describe('anchors for arrows from the program', () => { const anchored = () => { const onAnchors = vi.fn(); const view = draw({ onAnchors }); @@ -146,11 +146,11 @@ describe("EnvDiagram", () => { return { ...view, onAnchors, latest }; }; - test("tell where an object or a frame is drawn, and where it is not", () => { + test('tell where an object or a frame is drawn, and where it is not', () => { const { latest } = anchored(); const resolve = latest(); - const frame = resolve({ kind: "frame", id: "E1" }); - const object = resolve({ kind: "object", id: "#1" }); + const frame = resolve({ kind: 'frame', id: 'E1' }); + const object = resolve({ kind: 'object', id: '#1' }); expect(frame).toEqual({ x: expect.any(Number), y: expect.any(Number), @@ -161,53 +161,53 @@ describe("EnvDiagram", () => { y: expect.any(Number), scale: expect.any(Number), }); - expect(resolve({ kind: "frame", id: "nope" })).toBeNull(); - expect(resolve({ kind: "object", id: "nope" })).toBeNull(); + expect(resolve({ kind: 'frame', id: 'nope' })).toBeNull(); + expect(resolve({ kind: 'object', id: 'nope' })).toBeNull(); }); - test("end at the horizontal centre of the top of an object, and the left edge of a frame", () => { + test('end at the horizontal centre of the top of an object, and the left edge of a frame', () => { const { latest } = anchored(); const layout = layoutDiagram(step.frames, step.heap); const view = { scale: Math.min(1, 800 / layout.width, 600 / layout.height) }; for (const box of layout.objects) { - const at = latest()({ kind: "object", id: box.object.id }); + const at = latest()({ kind: 'object', id: box.object.id }); expect(at.x).toBeCloseTo((box.x + box.width / 2) * view.scale); expect(at.y).toBeCloseTo(box.y * view.scale); } const frame = layout.frames[1]; - const at = latest()({ kind: "frame", id: frame.frame.id }); + const at = latest()({ kind: 'frame', id: frame.frame.id }); expect(at.x).toBeCloseTo(frame.x * view.scale); }); test("follow the user's pan and zoom, and go away with the diagram", () => { const { root, onAnchors, latest } = anchored(); - const before = latest()({ kind: "frame", id: "E1" }); + const before = latest()({ kind: 'frame', id: 'E1' }); act(() => root - .findByType(konva("konva-stage")) + .findByType(konva('konva-stage')) .props.onDragMove({ target: { x: () => 30, y: () => 40 } }), ); - const panned = latest()({ kind: "frame", id: "E1" }); + const panned = latest()({ kind: 'frame', id: 'E1' }); expect(panned).toEqual({ x: before.x + 30, y: before.y + 40, scale: before.scale }); act(() => root - .findByType(konva("konva-stage")) + .findByType(konva('konva-stage')) .props.onDragEnd({ target: { x: () => 30, y: () => 40 } }), ); const wheel = { evt: { deltaY: -1, preventDefault: vi.fn() }, target: { getStage: () => ({ getPointerPosition: () => ({ x: 0, y: 0 }) }) }, }; - act(() => root.findByType(konva("konva-stage")).props.onWheel(wheel)); + act(() => root.findByType(konva('konva-stage')).props.onWheel(wheel)); expect(wheel.evt.preventDefault).toHaveBeenCalled(); - const zoomed = latest()({ kind: "frame", id: "E1" }); + const zoomed = latest()({ kind: 'frame', id: 'E1' }); expect(zoomed).not.toEqual(panned); // The arrowheads follow the zoom. expect(zoomed.scale).toBeGreaterThan(panned.scale); expect(onAnchors).not.toHaveBeenLastCalledWith(null); }); - test("end when the diagram unmounts", () => { + test('end when the diagram unmounts', () => { const onAnchors = vi.fn(); let renderer!: TestRenderer.ReactTestRenderer; act(() => { @@ -230,22 +230,22 @@ describe("EnvDiagram", () => { }); }); - test("reports hovering over a frame, and draws a hovered frame highlighted", () => { + test('reports hovering over a frame, and draws a hovered frame highlighted', () => { const onHoverFrame = vi.fn(); - const { root } = draw({ onHoverFrame, hoveredFrame: "E2" }); + const { root } = draw({ onHoverFrame, hoveredFrame: 'E2' }); const frameGroups = root - .findAllByType(konva("konva-group")) - .filter(g => typeof g.props.onMouseEnter === "function" && "opacity" in g.props); + .findAllByType(konva('konva-group')) + .filter(g => typeof g.props.onMouseEnter === 'function' && 'opacity' in g.props); act(() => frameGroups[0].props.onMouseEnter()); expect(onHoverFrame).toHaveBeenLastCalledWith(step.frames[0].id); act(() => frameGroups[0].props.onMouseLeave()); expect(onHoverFrame).toHaveBeenLastCalledWith(null); const highlighted = root - .findAllByType(konva("konva-rect")) + .findAllByType(konva('konva-rect')) .filter(r => r.props.fill === DiagramColors.hoverBackground); expect(highlighted).toHaveLength(1); // Every frame box is filled, so its whole area takes the mouse. - const boxes = root.findAllByType(konva("konva-rect")).filter(r => r.props.cornerRadius === 6); + const boxes = root.findAllByType(konva('konva-rect')).filter(r => r.props.cornerRadius === 6); expect(boxes.length).toBe(step.frames.length); for (const box of boxes) expect(box.props.fill).toBeTruthy(); }); @@ -254,57 +254,57 @@ describe("EnvDiagram", () => { const { root } = draw({ frames: [ { - id: "Global", - name: "global", + id: 'Global', + name: 'global', parentId: null, isGarbage: false, bindings: [ - { name: "xs", value: { kind: "ref", objectId: "#1" } }, - { name: "y", value: { kind: "primitive", display: "None", label: "None" } }, + { name: 'xs', value: { kind: 'ref', objectId: '#1' } }, + { name: 'y', value: { kind: 'primitive', display: 'None', label: 'None' } }, ], }, ], heap: [ { - kind: "list", - id: "#1", + kind: 'list', + id: '#1', isGarbage: false, elements: [ - { kind: "primitive", display: "3", label: "int" }, + { kind: 'primitive', display: '3', label: 'int' }, // Whatever the type tag: py-slang sends "None", others may send "NoneType". - { kind: "primitive", display: "None", label: "None" }, - { kind: "primitive", display: "None", label: "NoneType" }, + { kind: 'primitive', display: 'None', label: 'None' }, + { kind: 'primitive', display: 'None', label: 'NoneType' }, // A string reading None is not None. - { kind: "primitive", display: "'None'", label: "str" }, + { kind: 'primitive', display: "'None'", label: 'str' }, ], }, ] as never, - activeFrameId: "Global", + activeFrameId: 'Global', }); - const texts = root.findAllByType(konva("konva-text")).map(t => t.props.text); + const texts = root.findAllByType(konva('konva-text')).map(t => t.props.text); // The binding y shows None; the list's box does not. - expect(texts.filter(t => t === "None")).toHaveLength(1); - expect(texts).toContain("3"); + expect(texts.filter(t => t === 'None')).toHaveLength(1); + expect(texts).toContain('3'); expect(texts).toContain("'None'"); const slashes = root - .findAllByType(konva("konva-line")) + .findAllByType(konva('konva-line')) .filter(l => l.props.points[1] > l.props.points[3]); expect(slashes).toHaveLength(2); }); - test("a hovered object gets a darker background, keeping its outline", () => { - const { root } = draw({ hovered: "#2" }); + test('a hovered object gets a darker background, keeping its outline', () => { + const { root } = draw({ hovered: '#2' }); const darkened = root - .findAllByType(konva("konva-circle")) + .findAllByType(konva('konva-circle')) .filter(c => c.props.fill === DiagramColors.hoverBackground); // The function object's two circles, outlined as usual. expect(darkened).toHaveLength(2); for (const c of darkened) expect(c.props.stroke).toBe(DiagramColors.stroke); }); - test("zooms around the pointer with the mouse wheel and remembers a pan", () => { + test('zooms around the pointer with the mouse wheel and remembers a pan', () => { const { root } = draw(); - const stage = root.findByType(konva("konva-stage")); + const stage = root.findByType(konva('konva-stage')); const wheel = (deltaY: number) => act(() => stage.props.onWheel({ @@ -312,14 +312,14 @@ describe("EnvDiagram", () => { target: { getStage: () => ({ getPointerPosition: () => ({ x: 100, y: 100 }) }) }, }), ); - const scale = () => root.findByType(konva("konva-stage")).props.scaleX as number; + const scale = () => root.findByType(konva('konva-stage')).props.scaleX as number; const before = scale(); wheel(-1); expect(scale()).toBeCloseTo(before * 1.1); wheel(1); expect(scale()).toBeCloseTo(before); act(() => stage.props.onDragEnd({ target: { x: () => 30, y: () => 40 } })); - expect(root.findByType(konva("konva-stage")).props.x).toBe(30); - expect(root.findByType(konva("konva-stage")).props.y).toBe(40); + expect(root.findByType(konva('konva-stage')).props.x).toBe(30); + expect(root.findByType(konva('konva-stage')).props.y).toBe(40); }); }); diff --git a/src/web/e-stepper/src/__tests__/ProgramArrows.test.tsx b/src/web/e-stepper/src/__tests__/ProgramArrows.test.tsx index 0d1520a..9a3a092 100644 --- a/src/web/e-stepper/src/__tests__/ProgramArrows.test.tsx +++ b/src/web/e-stepper/src/__tests__/ProgramArrows.test.tsx @@ -1,13 +1,13 @@ -import { act } from "react"; -import TestRenderer from "react-test-renderer"; -import { describe, expect, test, vi } from "vitest"; +import { act } from 'react'; +import TestRenderer from 'react-test-renderer'; +import { describe, expect, test, vi } from 'vitest'; import ProgramArrows, { - arrowGeometry, - computeProgramArrows, ENV_ATTRIBUTE, REF_ATTRIBUTE, -} from "../ProgramArrows"; + arrowGeometry, + computeProgramArrows, +} from '../ProgramArrows'; const rect = (left: number, top: number, width: number, height: number) => ({ left, top, right: left + width, bottom: top + height, width, height }) as DOMRect; @@ -24,51 +24,51 @@ function fake(box: DOMRect, attributes: Record = {}, marks: unkn } as unknown as Element; } -describe("arrowGeometry", () => { - test("leaves and arrives horizontally when the target is further across than down", () => { +describe(arrowGeometry, () => { + test('leaves and arrives horizontally when the target is further across than down', () => { const { path, head } = arrowGeometry({ x: 0, y: 0 }, { x: 100, y: 20 }); - expect(path).toBe("M 0 0 C 50 0, 50 20, 100 20"); - expect(head.startsWith("100 20 ")).toBe(true); + expect(path).toBe('M 0 0 C 50 0, 50 20, 100 20'); + expect(head.startsWith('100 20 ')).toBe(true); }); - test("goes vertically when the panes are stacked", () => { + test('goes vertically when the panes are stacked', () => { const { path } = arrowGeometry({ x: 10, y: 0 }, { x: 0, y: 200 }); - expect(path).toBe("M 10 0 C 10 100, 0 100, 0 200"); + expect(path).toBe('M 10 0 C 10 100, 0 100, 0 200'); }); - test("comes down onto the top of an object, whatever the layout", () => { + test('comes down onto the top of an object, whatever the layout', () => { // Side by side (further across than down): it still arrives vertically, from above. - const { path } = arrowGeometry({ x: 0, y: 0 }, { x: 200, y: 40 }, "above"); - expect(path).toBe("M 0 0 C 100 0, 200 20, 200 40"); + const { path } = arrowGeometry({ x: 0, y: 0 }, { x: 200, y: 40 }, 'above'); + expect(path).toBe('M 0 0 C 100 0, 200 20, 200 40'); }); - test("arrives at a frame from the left, whatever the layout", () => { + test('arrives at a frame from the left, whatever the layout', () => { // Side by side: a horizontal arrival, as before. - expect(arrowGeometry({ x: 0, y: 0 }, { x: 100, y: 20 }, "left").path).toBe( - "M 0 0 C 50 0, 50 20, 100 20", + expect(arrowGeometry({ x: 0, y: 0 }, { x: 100, y: 20 }, 'left').path).toBe( + 'M 0 0 C 50 0, 50 20, 100 20', ); // Stacked: it leaves downwards, but still comes into the left edge, pointing right. - const stacked = arrowGeometry({ x: 10, y: 0 }, { x: 80, y: 300 }, "left"); - expect(stacked.path).toBe("M 10 0 C 10 120, 45 300, 80 300"); - expect(stacked.head.startsWith("80 300 ")).toBe(true); + const stacked = arrowGeometry({ x: 10, y: 0 }, { x: 80, y: 300 }, 'left'); + expect(stacked.path).toBe('M 10 0 C 10 120, 45 300, 80 300'); + expect(stacked.head.startsWith('80 300 ')).toBe(true); }); - test("a frame to the left of the start is still entered from its left", () => { - expect(arrowGeometry({ x: 100, y: 0 }, { x: 0, y: 200 }, "left").path).toBe( - "M 100 0 C 100 100, -40 200, 0 200", + test('a frame to the left of the start is still entered from its left', () => { + expect(arrowGeometry({ x: 100, y: 0 }, { x: 0, y: 200 }, 'left').path).toBe( + 'M 100 0 C 100 100, -40 200, 0 200', ); }); - test("bends back correctly when the target is to the left", () => { - expect(arrowGeometry({ x: 100, y: 0 }, { x: 0, y: 0 }).path).toBe("M 100 0 C 50 0, 50 0, 0 0"); + test('bends back correctly when the target is to the left', () => { + expect(arrowGeometry({ x: 100, y: 0 }, { x: 0, y: 0 }).path).toBe('M 100 0 C 50 0, 50 0, 0 0'); }); }); -describe("arrowGeometry scale", () => { +describe('arrowGeometry scale', () => { test("the arrowhead grows with the diagram's zoom", () => { const size = (scale: number) => { - const { head } = arrowGeometry({ x: 0, y: 0 }, { x: 100, y: 0 }, "axis", scale); - return head.split(" ").map(Number); + const { head } = arrowGeometry({ x: 0, y: 0 }, { x: 100, y: 0 }, 'axis', scale); + return head.split(' ').map(Number); }; // Tip at (100, 0); the base is HEAD_LENGTH * scale back from it. expect(size(1)).toEqual([100, 0, 92, 4, 92, -4]); @@ -76,47 +76,47 @@ describe("arrowGeometry scale", () => { }); }); -describe("computeProgramArrows", () => { +describe(computeProgramArrows, () => { const container = fake(rect(100, 50, 800, 400)); const program = (marks: unknown[]) => fake(rect(100, 50, 300, 400), {}, marks); const diagram = fake(rect(500, 50, 400, 400)); const resolve = ({ kind, id }: { kind: string; id: string }) => - id === "missing" ? null : kind === "object" ? { x: 10, y: 20 } : { x: 30, y: 40 }; + id === 'missing' ? null : kind === 'object' ? { x: 10, y: 20 } : { x: 30, y: 40 }; - test("draws an arrow from each badge and bracket label to its target, in container coordinates", () => { - const ref = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: "#1" }); - const env = fake(rect(120, 200, 20, 12), { [ENV_ATTRIBUTE]: "E1" }); + test('draws an arrow from each badge and bracket label to its target, in container coordinates', () => { + const ref = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: '#1' }); + const env = fake(rect(120, 200, 20, 12), { [ENV_ATTRIBUTE]: 'E1' }); const arrows = computeProgramArrows(container, program([ref, env]), diagram, resolve); expect(arrows.map(a => [a.kind, a.id, a.from, a.to])).toEqual([ - ["object", "#1", { x: 60, y: 55 }, { x: 410, y: 20 }], - ["frame", "E1", { x: 30, y: 156 }, { x: 430, y: 40 }], + ['object', '#1', { x: 60, y: 55 }, { x: 410, y: 20 }], + ['frame', 'E1', { x: 30, y: 156 }, { x: 430, y: 40 }], ]); }); - test("carries the zoom of the target, 1 when the host gives none", () => { - const ref = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: "#1" }); - const env = fake(rect(120, 200, 20, 12), { [ENV_ATTRIBUTE]: "E1" }); + test('carries the zoom of the target, 1 when the host gives none', () => { + const ref = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: '#1' }); + const env = fake(rect(120, 200, 20, 12), { [ENV_ATTRIBUTE]: 'E1' }); const zoomed = ({ kind }: { kind: string }) => - kind === "object" ? { x: 10, y: 20, scale: 1.5 } : { x: 30, y: 40 }; + kind === 'object' ? { x: 10, y: 20, scale: 1.5 } : { x: 30, y: 40 }; const arrows = computeProgramArrows(container, program([ref, env]), diagram, zoomed); expect(arrows.map(a => a.scale)).toEqual([1.5, 1]); }); - test("skips marks scrolled out of the program pane, and targets that are not drawn or not in view", () => { - const scrolledOut = fake(rect(150, 600, 20, 10), { [REF_ATTRIBUTE]: "#1" }); - const notDrawn = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: "missing" }); - const outOfView = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: "#3" }); + test('skips marks scrolled out of the program pane, and targets that are not drawn or not in view', () => { + const scrolledOut = fake(rect(150, 600, 20, 10), { [REF_ATTRIBUTE]: '#1' }); + const notDrawn = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: 'missing' }); + const outOfView = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: '#3' }); const far = ({ id }: { id: string }) => - id === "#3" ? { x: 900, y: 20 } : id === "missing" ? null : { x: 1, y: 1 }; + id === '#3' ? { x: 900, y: 20 } : id === 'missing' ? null : { x: 1, y: 1 }; expect( computeProgramArrows(container, program([scrolledOut, notDrawn, outOfView]), diagram, far), ).toHaveLength(0); }); }); -describe("ProgramArrows", () => { - const ref = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: "#1" }); - const env = fake(rect(120, 200, 20, 12), { [ENV_ATTRIBUTE]: "E1" }); +describe(ProgramArrows, () => { + const ref = fake(rect(150, 100, 20, 10), { [REF_ATTRIBUTE]: '#1' }); + const env = fake(rect(120, 200, 20, 12), { [ENV_ATTRIBUTE]: 'E1' }); const listeners = new Map void>(); const programEl = Object.assign(fake(rect(100, 50, 300, 400), {}, [ref, env]), { addEventListener: (type: string, fn: () => void) => listeners.set(type, fn), @@ -126,24 +126,24 @@ describe("ProgramArrows", () => { const containerEl = { getBoundingClientRect: () => rect(100, 50, 800, 400), querySelector: (selector: string) => - selector === ".estepper-program" + selector === '.estepper-program' ? programEl - : selector === ".estepper-diagram" + : selector === '.estepper-diagram' ? diagramEl : null, }; function mount(props: Partial[0]> = {}) { - vi.stubGlobal("window", { addEventListener: vi.fn(), removeEventListener: vi.fn() }); + vi.stubGlobal('window', { addEventListener: vi.fn(), removeEventListener: vi.fn() }); let renderer!: TestRenderer.ReactTestRenderer; act(() => { renderer = TestRenderer.create( (kind === "object" ? { x: 10, y: 20 } : { x: 30, y: 40 })} + resolve={({ kind }) => (kind === 'object' ? { x: 10, y: 20 } : { x: 30, y: 40 })} hovered={null} hoveredFrame={null} all - colorOfFrame={() => "#ff0000"} + colorOfFrame={() => '#ff0000'} {...props} />, { createNodeMock: () => ({ parentElement: containerEl }) }, @@ -153,41 +153,41 @@ describe("ProgramArrows", () => { } const strokes = (r: TestRenderer.ReactTestRenderer) => r.root - .findAllByType("path") + .findAllByType('path') .map(p => [p.props.stroke, p.props.strokeWidth, p.parent!.props.opacity]); - test("draws an arrow to each object, grey; one to a frame only while it is hovered", () => { + test('draws an arrow to each object, grey; one to a frame only while it is hovered', () => { const view = mount(); - expect(view.root.findAllByType("polygon")).toHaveLength(1); - expect(strokes(view)).toEqual([["#c5cbd3", 1.5, 0.55]]); + expect(view.root.findAllByType('polygon')).toHaveLength(1); + expect(strokes(view)).toEqual([['#c5cbd3', 1.5, 0.55]]); act(() => view.unmount()); vi.unstubAllGlobals(); }); test('without "all", an arrow to an object is drawn only while the object is hovered', () => { expect(strokes(mount({ all: false }))).toEqual([]); - expect(strokes(mount({ all: false, hovered: "#2" }))).toEqual([]); - expect(strokes(mount({ all: false, hovered: "#1" }))).toEqual([["#c5cbd3", 2.5, 1]]); + expect(strokes(mount({ all: false, hovered: '#2' }))).toEqual([]); + expect(strokes(mount({ all: false, hovered: '#1' }))).toEqual([['#c5cbd3', 2.5, 1]]); vi.unstubAllGlobals(); }); test('an arrow to a frame is drawn in the frame\'s colour while the frame is hovered, with or without "all"', () => { for (const all of [true, false]) { - const view = mount({ all, hoveredFrame: "E1" }); - expect(strokes(view).at(-1)).toEqual(["#ff0000", 2.5, 1]); - expect(view.root.findAllByType("path")).toHaveLength(all ? 2 : 1); + const view = mount({ all, hoveredFrame: 'E1' }); + expect(strokes(view).at(-1)).toEqual(['#ff0000', 2.5, 1]); + expect(view.root.findAllByType('path')).toHaveLength(all ? 2 : 1); } vi.unstubAllGlobals(); }); - test("draws none without anchors, and measures again when the program pane scrolls", () => { + test('draws none without anchors, and measures again when the program pane scrolls', () => { expect(strokes(mount({ resolve: null }))).toEqual([]); const view = mount(); - expect(listeners.has("scroll")).toBe(true); - act(() => listeners.get("scroll")!()); - expect(view.root.findAllByType("path")).toHaveLength(1); + expect(listeners.has('scroll')).toBe(true); + act(() => listeners.get('scroll')!()); + expect(view.root.findAllByType('path')).toHaveLength(1); act(() => view.unmount()); - expect(listeners.has("scroll")).toBe(false); + expect(listeners.has('scroll')).toBe(false); vi.unstubAllGlobals(); }); }); diff --git a/src/web/e-stepper/src/__tests__/layout.test.ts b/src/web/e-stepper/src/__tests__/layout.test.ts index 8117bd1..f9c876d 100644 --- a/src/web/e-stepper/src/__tests__/layout.test.ts +++ b/src/web/e-stepper/src/__tests__/layout.test.ts @@ -2,12 +2,12 @@ import type { EStepperFrame, EStepperHeapObject, EStepperStep, -} from "@sourceacademy/common-e-stepper"; -import { describe, expect, test } from "vitest"; +} from '@sourceacademy/common-e-stepper'; +import { describe, expect, test } from 'vitest'; -import { frameColor } from "../colors"; -import { layoutDiagram, LayoutConfig as C, type DiagramLayout } from "../layout"; -import steps from "./makeWithdrawSteps.json"; +import { frameColor } from '../colors'; +import { LayoutConfig as C, layoutDiagram, type DiagramLayout } from '../layout'; +import steps from './makeWithdrawSteps.json' with { type: 'json' }; /** The last step of `make_withdraw` (produced by py-slang's e-stepper engine): frames Global, E1, * E2 (garbage), function objects #1 (in Global) and #2 (in E1), lists #4 = [W1, #3] and #3. */ @@ -37,21 +37,21 @@ function rects(layout: DiagramLayout): Rect[] { const overlaps = (a: Rect, b: Rect) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height; -describe("layoutDiagram on make_withdraw", () => { +describe('layoutDiagram on make_withdraw', () => { const layout = layoutDiagram(last.frames, last.heap); const frame = (id: string) => layout.frames.find(f => f.frame.id === id)!; const object = (id: string) => layout.objects.find(o => o.object.id === id)!; - test("frames are in rows by nesting depth", () => { - expect(layout.frames.map(f => f.frame.id)).toEqual(["Global", "E1", "E2"]); - expect(frame("Global").y).toBeLessThan(frame("E1").y); - expect(frame("E1").y).toBeLessThan(frame("E2").y); + test('frames are in rows by nesting depth', () => { + expect(layout.frames.map(f => f.frame.id)).toEqual(['Global', 'E1', 'E2']); + expect(frame('Global').y).toBeLessThan(frame('E1').y); + expect(frame('E1').y).toBeLessThan(frame('E2').y); }); - test("function objects sit to the right of the frame they were defined in", () => { + test('function objects sit to the right of the frame they were defined in', () => { for (const [fn, env] of [ - ["#1", "Global"], - ["#2", "E1"], + ['#1', 'Global'], + ['#2', 'E1'], ]) { expect(object(fn).x).toBeGreaterThan(frame(env).x + frame(env).width); expect(object(fn).y).toBeGreaterThanOrEqual(frame(env).y); @@ -59,16 +59,16 @@ describe("layoutDiagram on make_withdraw", () => { } }); - test("lists sit with the first frame that refers to them, nested lists alongside", () => { - const global = frame("Global"); - for (const id of ["#4", "#3"]) { + test('lists sit with the first frame that refers to them, nested lists alongside', () => { + const global = frame('Global'); + for (const id of ['#4', '#3']) { expect(object(id).x).toBeGreaterThan(global.x + global.width); - expect(object(id).y).toBeLessThan(frame("E1").y); + expect(object(id).y).toBeLessThan(frame('E1').y); } - expect(object("#4").width).toBe(2 * C.cellWidth); + expect(object('#4').width).toBe(2 * C.cellWidth); }); - test("nothing overlaps, and everything is inside the drawing", () => { + test('nothing overlaps, and everything is inside the drawing', () => { const all = rects(layout); for (let i = 0; i < all.length; i++) { for (let j = i + 1; j < all.length; j++) { @@ -79,59 +79,59 @@ describe("layoutDiagram on make_withdraw", () => { } }); - test("arrows: one per reference binding, list element, function environment and parent", () => { + test('arrows: one per reference binding, list element, function environment and parent', () => { const keys = layout.arrows.map(a => a.key).sort(); expect(keys).toEqual( [ - "binding:Global:make_withdraw", - "binding:Global:W1", - "binding:Global:xs", - "binding:E1:withdraw", - "element:#4:0", - "element:#4:1", - "environment:#1", - "environment:#2", - "parent:E1", - "parent:E2", + 'binding:Global:make_withdraw', + 'binding:Global:W1', + 'binding:Global:xs', + 'binding:E1:withdraw', + 'element:#4:0', + 'element:#4:1', + 'environment:#1', + 'environment:#2', + 'parent:E1', + 'parent:E2', ].sort(), ); // A binding arrow ends at the left edge of its object. - const w1 = layout.arrows.find(a => a.key === "binding:Global:W1")!; - expect(w1.to.x).toBe(object("#2").x); + const w1 = layout.arrows.find(a => a.key === 'binding:Global:W1')!; + expect(w1.to.x).toBe(object('#2').x); // An arrow from a garbage frame is dimmed. - expect(layout.arrows.find(a => a.key === "parent:E2")!.garbage).toBe(true); + expect(layout.arrows.find(a => a.key === 'parent:E2')!.garbage).toBe(true); }); }); -describe("layoutDiagram edge cases", () => { - test("objects no frame refers to go in a row of their own at the bottom", () => { +describe('layoutDiagram edge cases', () => { + test('objects no frame refers to go in a row of their own at the bottom', () => { const frames: EStepperFrame[] = [ - { id: "Global", name: "global", parentId: null, bindings: [], isGarbage: false }, + { id: 'Global', name: 'global', parentId: null, bindings: [], isGarbage: false }, ]; - const heap: EStepperHeapObject[] = [{ kind: "list", id: "#1", elements: [], isGarbage: false }]; + const heap: EStepperHeapObject[] = [{ kind: 'list', id: '#1', elements: [], isGarbage: false }]; const layout = layoutDiagram(frames, heap); expect(layout.objects[0].y).toBeGreaterThan(layout.frames[0].y + C.headerHeight); expect(layout.objects[0].width).toBe(C.cellWidth); }); - test("an empty store lays out to an empty drawing", () => { + test('an empty store lays out to an empty drawing', () => { const layout = layoutDiagram([], []); expect(layout.frames).toEqual([]); expect(layout.arrows).toEqual([]); }); }); -test("the global frame is neutral, other frames get distinct colours", () => { +test('the global frame is neutral, other frames get distinct colours', () => { const colors = [0, 1, 2, 3].map(frameColor); expect(new Set(colors).size).toBe(4); }); -describe("layoutDiagram with unreachable frames cleared", () => { +describe('layoutDiagram with unreachable frames cleared', () => { const full = layoutDiagram(last.frames, last.heap); const cleared = layoutDiagram(last.frames, last.heap, { clearUnreachable: true }); const deadObjects = last.heap.filter(o => o.isGarbage).map(o => o.id); - test("garbage frames and objects are left out, and the rest stays", () => { + test('garbage frames and objects are left out, and the rest stays', () => { const unreachable = last.frames.filter(f => f.isGarbage).map(f => f.id); expect(unreachable.length).toBeGreaterThan(0); expect(cleared.frames.map(b => b.frame.id)).toEqual( @@ -142,12 +142,12 @@ describe("layoutDiagram with unreachable frames cleared", () => { ); }); - test("no arrow starts or ends at something that is gone", () => { + test('no arrow starts or ends at something that is gone', () => { expect(cleared.arrows.length).toBeLessThan(full.arrows.length); expect(cleared.arrows.every(a => !a.garbage)).toBe(true); }); - test("nothing changes when nothing is unreachable", () => { + test('nothing changes when nothing is unreachable', () => { const live = last.frames.map(f => ({ ...f, isGarbage: false })); const heap = last.heap.map(o => ({ ...o, isGarbage: false })); expect(layoutDiagram(live, heap, { clearUnreachable: true })).toEqual( @@ -155,23 +155,23 @@ describe("layoutDiagram with unreachable frames cleared", () => { ); }); - test("a live frame keeps its colour index when unreachable frames before it are left out", () => { + test('a live frame keeps its colour index when unreachable frames before it are left out', () => { const frame = (id: string, isGarbage: boolean) => ({ id, - name: "f", - parentId: "Global", + name: 'f', + parentId: 'Global', bindings: [], isGarbage, }); const frames = [ - { ...frame("Global", false), name: "global", parentId: null }, - frame("E1", true), - frame("E2", false), + { ...frame('Global', false), name: 'global', parentId: null }, + frame('E1', true), + frame('E2', false), ]; const layout = layoutDiagram(frames, [], { clearUnreachable: true }); expect(layout.frames.map(b => [b.frame.id, b.index])).toEqual([ - ["Global", 0], - ["E2", 2], + ['Global', 0], + ['E2', 2], ]); }); }); diff --git a/src/web/e-stepper/src/__tests__/styles.test.ts b/src/web/e-stepper/src/__tests__/styles.test.ts index 9b4929d..662a724 100644 --- a/src/web/e-stepper/src/__tests__/styles.test.ts +++ b/src/web/e-stepper/src/__tests__/styles.test.ts @@ -1,7 +1,7 @@ -import { afterEach, expect, test, vi } from "vitest"; +import { afterEach, expect, test, vi } from 'vitest'; -import { injectStepperStyles } from "../../../stepper/src/styles"; -import { injectEStepperStyles } from "../styles"; +import { injectStepperStyles } from '../../../stepper/src/styles'; +import { injectEStepperStyles } from '../styles'; /** A minimal `document`: the injectors only look up, create and append style elements. */ function fakeDocument() { @@ -12,7 +12,7 @@ function fakeDocument() { appendChild: (e: { id: string; textContent: string }) => (head.push(e), (elements[e.id] = e)), }, getElementById: (id: string) => elements[id] ?? null, - createElement: () => ({ id: "", textContent: "" }), + createElement: () => ({ id: '', textContent: '' }), appended: head, }; } @@ -20,42 +20,42 @@ function fakeDocument() { afterEach(() => vi.unstubAllGlobals()); test.each([ - ["e-stepper", injectEStepperStyles, ".sa-e-stepper"], - ["stepper", injectStepperStyles, ".sa-substituter"], + ['e-stepper', injectEStepperStyles, '.sa-e-stepper'], + ['stepper', injectStepperStyles, '.sa-substituter'], ])( - "the %s styles are injected once and kept up to date", + 'the %s styles are injected once and kept up to date', (_name: string, inject: () => void, selector: string) => { const doc = fakeDocument(); - vi.stubGlobal("document", doc); + vi.stubGlobal('document', doc); inject(); inject(); expect(doc.appended).toHaveLength(1); expect(doc.appended[0].textContent).toContain(selector); // A stale copy (e.g. from an earlier load of the plugin) is replaced. - doc.appended[0].textContent = "stale"; + doc.appended[0].textContent = 'stale'; inject(); expect(doc.appended[0].textContent).toContain(selector); }, ); -test("injecting without a document does nothing", () => { +test('injecting without a document does nothing', () => { expect(() => injectEStepperStyles()).not.toThrow(); expect(() => injectStepperStyles()).not.toThrow(); }); -test("the dividers between the panes never shrink away, however short the tab is", () => { +test('the dividers between the panes never shrink away, however short the tab is', () => { const doc = fakeDocument(); - vi.stubGlobal("document", doc); + vi.stubGlobal('document', doc); injectEStepperStyles(); const css = doc.appended[0].textContent; // In a stacked tab the program pane has a fixed height and the diagram a least height, so a flex // item that may shrink (the default) would be squeezed to nothing. const rule = (selector: string) => - css.slice(css.indexOf(selector), css.indexOf("}", css.indexOf(selector))); - expect(rule(".sa-e-stepper .estepper-divider {")).toContain("flex: 0 0 auto"); - expect(rule(".sa-e-stepper .estepper-divider.vertical")).toContain("flex: 0 0 auto"); + css.slice(css.indexOf(selector), css.indexOf('}', css.indexOf(selector))); + expect(rule('.sa-e-stepper .estepper-divider {')).toContain('flex: 0 0 auto'); + expect(rule('.sa-e-stepper .estepper-divider.vertical')).toContain('flex: 0 0 auto'); // Above the arrows from the program (which are drawn over both panes), so they pass under it. - expect(rule(".sa-e-stepper .estepper-divider {")).toContain("z-index: 3"); + expect(rule('.sa-e-stepper .estepper-divider {')).toContain('z-index: 3'); // A stacked tab leaves the diagram room for no more than its least height. - expect(css).toContain(".estepper-main.narrow .estepper-diagram { min-height: 80px; }"); + expect(css).toContain('.estepper-main.narrow .estepper-diagram { min-height: 80px; }'); }); diff --git a/src/web/e-stepper/src/colors.ts b/src/web/e-stepper/src/colors.ts index 210a891..a4a1dca 100644 --- a/src/web/e-stepper/src/colors.ts +++ b/src/web/e-stepper/src/colors.ts @@ -5,17 +5,17 @@ * background. */ -const GLOBAL_COLOR = "#c5cbd3"; +const GLOBAL_COLOR = '#c5cbd3'; const PALETTE = [ - "#4fc3f7", // light blue - "#ffb74d", // orange - "#81c784", // green - "#f06292", // pink - "#ba68c8", // purple - "#fff176", // yellow - "#4db6ac", // teal - "#e57373", // red + '#4fc3f7', // light blue + '#ffb74d', // orange + '#81c784', // green + '#f06292', // pink + '#ba68c8', // purple + '#fff176', // yellow + '#4db6ac', // teal + '#e57373', // red ]; /** The colour of the frame at `index` in a step's frame list (index 0 is the global frame). */ @@ -24,17 +24,17 @@ export function frameColor(index: number): string { } export const DiagramColors = { - text: "#ffffff", - dimText: "#8a9ba8", - stroke: "#ffffff", + text: '#ffffff', + dimText: '#8a9ba8', + stroke: '#ffffff', /** What is unreachable (frames and objects nothing refers to any more, and their arrows and data). */ - garbage: "#7f8b96", + garbage: '#7f8b96', garbageOpacity: 0.75, - lookup: "rgba(255, 213, 79, 0.35)", + lookup: 'rgba(255, 213, 79, 0.35)', /** * A hovered frame's or object's background: darker, as in the host's CSE diagram. Not a colour * of its own, since frames are colour-coded. */ - hoverBackground: "#000000", - background: "#1a2530", + hoverBackground: '#000000', + background: '#1a2530', } as const; diff --git a/src/web/e-stepper/src/index.ts b/src/web/e-stepper/src/index.ts index d365ef8..00f5b58 100644 --- a/src/web/e-stepper/src/index.ts +++ b/src/web/e-stepper/src/index.ts @@ -1,3 +1,3 @@ // The host's external-plugin loader imports the bundle and calls its default export as a factory // (see wrap.mjs), so the entry default-exports the plugin class. -export { EStepperHostPlugin as default } from "./EStepperHostPlugin"; +export { EStepperHostPlugin as default } from './EStepperHostPlugin'; diff --git a/src/web/e-stepper/src/layout.ts b/src/web/e-stepper/src/layout.ts index 86de143..ff5b756 100644 --- a/src/web/e-stepper/src/layout.ts +++ b/src/web/e-stepper/src/layout.ts @@ -15,7 +15,7 @@ import type { EStepperFrame, EStepperHeapObject, EStepperValue, -} from "@sourceacademy/common-e-stepper"; +} from '@sourceacademy/common-e-stepper'; export const LayoutConfig = { charWidth: 7.8, @@ -73,7 +73,7 @@ export interface ObjectBox { height: number; } -export type ArrowKind = "parent" | "binding" | "environment" | "element"; +export type ArrowKind = 'parent' | 'binding' | 'environment' | 'element'; export interface ArrowSpec { key: string; @@ -99,23 +99,23 @@ export function textWidth(text: string): number { /** The text shown for a non-reference value in a frame or list cell. */ export function valueLabel(value: EStepperValue): string { switch (value.kind) { - case "primitive": + case 'primitive': return value.display; - case "builtin": + case 'builtin': return value.name; - case "unassigned": - return "—"; - case "ref": - return ""; + case 'unassigned': + return '—'; + case 'ref': + return ''; } } function valueWidth(value: EStepperValue): number { - return value.kind === "ref" ? 3 * C.dotRadius : textWidth(valueLabel(value)); + return value.kind === 'ref' ? 3 * C.dotRadius : textWidth(valueLabel(value)); } function objectSize(object: EStepperHeapObject): { width: number; height: number } { - if (object.kind === "function") { + if (object.kind === 'function') { return { width: 4 * C.functionRadius, height: 2 * C.functionRadius }; } return { width: Math.max(1, object.elements.length) * C.cellWidth, height: C.cellHeight }; @@ -160,7 +160,7 @@ export function layoutDiagram( // first frame (in order) that reaches it through its bindings and owned objects. const owner = new Map(); for (const o of heap) { - if (o.kind === "function" && frameById.has(o.envId)) owner.set(o.id, o.envId); + if (o.kind === 'function' && frameById.has(o.envId)) owner.set(o.id, o.envId); } const ownedOrder = new Map(frames.map(f => [f.id, []])); const claim = (objectId: string, frameId: string): void => { @@ -171,13 +171,13 @@ export function layoutDiagram( if (ownedOrder.get(frameId)!.includes(objectId)) return; owner.set(objectId, frameId); ownedOrder.get(frameId)!.push(objectId); - if (object.kind === "list") { - for (const e of object.elements) if (e.kind === "ref") claim(e.objectId, frameId); + if (object.kind === 'list') { + for (const e of object.elements) if (e.kind === 'ref') claim(e.objectId, frameId); } }; for (const f of frames) { - for (const o of heap) if (o.kind === "function" && o.envId === f.id) claim(o.id, f.id); - for (const b of f.bindings) if (b.value.kind === "ref") claim(b.value.objectId, f.id); + for (const o of heap) if (o.kind === 'function' && o.envId === f.id) claim(o.id, f.id); + for (const b of f.bindings) if (b.value.kind === 'ref') claim(b.value.objectId, f.id); } const unowned = heap.filter(o => !owner.has(o.id)); @@ -194,7 +194,7 @@ export function layoutDiagram( for (const frame of row) { const contentWidth = Math.max( C.frameMinWidth - 2 * C.padding, - ...frame.bindings.map(b => textWidth(b.name + ":") + C.padding + valueWidth(b.value)), + ...frame.bindings.map(b => textWidth(b.name + ':') + C.padding + valueWidth(b.value)), textWidth(`${frame.id} ${frame.name}`) - C.padding, ); const frameWidth = contentWidth + 2 * C.padding; @@ -203,7 +203,7 @@ export function layoutDiagram( name: b.name, value: b.value, y: boxTop + C.padding / 2 + i * C.rowHeight + C.rowHeight / 2, - valueX: x + C.padding + textWidth(b.name + ":") + C.padding, + valueX: x + C.padding + textWidth(b.name + ':') + C.padding, })); const frameHeight = Math.max(C.rowHeight, frame.bindings.length * C.rowHeight) + C.padding; frameBoxes.push({ @@ -265,19 +265,19 @@ export function layoutDiagram( if (parent) { arrows.push({ key: `parent:${box.frame.id}`, - kind: "parent", + kind: 'parent', from: { x: box.x + box.width / 2, y: boxTop }, to: { x: parent.x + parent.width / 2, y: parent.y + C.headerHeight + parent.height }, garbage: box.frame.isGarbage, }); } for (const row of box.rows) { - if (row.value.kind !== "ref") continue; + if (row.value.kind !== 'ref') continue; const target = objectBoxById.get(row.value.objectId); if (!target) continue; arrows.push({ key: `binding:${box.frame.id}:${row.name}`, - kind: "binding", + kind: 'binding', from: { x: row.valueX + C.dotRadius, y: row.y }, to: arrowTarget(target), garbage: box.frame.isGarbage, @@ -286,24 +286,24 @@ export function layoutDiagram( } for (const box of objectBoxes) { const object = box.object; - if (object.kind === "function") { + if (object.kind === 'function') { const env = frameBoxById.get(object.envId); if (!env) continue; arrows.push({ key: `environment:${object.id}`, - kind: "environment", + kind: 'environment', from: { x: box.x + 3 * C.functionRadius, y: box.y + C.functionRadius }, to: { x: env.x + env.width, y: env.y + C.headerHeight + C.padding }, garbage: object.isGarbage, }); } else { object.elements.forEach((e, i) => { - if (e.kind !== "ref") return; + if (e.kind !== 'ref') return; const target = objectBoxById.get(e.objectId); if (!target) return; arrows.push({ key: `element:${object.id}:${i}`, - kind: "element", + kind: 'element', from: { x: box.x + (i + 0.5) * C.cellWidth, y: box.y + C.cellHeight / 2 }, to: arrowTarget(target), garbage: object.isGarbage, diff --git a/src/web/e-stepper/src/styles.ts b/src/web/e-stepper/src/styles.ts index 136b18f..7847a4b 100644 --- a/src/web/e-stepper/src/styles.ts +++ b/src/web/e-stepper/src/styles.ts @@ -59,14 +59,14 @@ const E_STEPPER_CSS = ` } `; -const STYLE_ELEMENT_ID = "sa-e-stepper-styles"; +const STYLE_ELEMENT_ID = 'sa-e-stepper-styles'; /** Injects (or refreshes) the e-stepper's stylesheet, like the stepper's own `injectStepperStyles`. */ export function injectEStepperStyles(): void { - if (typeof document === "undefined") return; + if (typeof document === 'undefined') return; let style = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null; if (style === null) { - style = document.createElement("style"); + style = document.createElement('style'); style.id = STYLE_ELEMENT_ID; document.head.appendChild(style); } diff --git a/src/web/e-stepper/wrap.mjs b/src/web/e-stepper/wrap.mjs index 13af2ae..4deb609 100644 --- a/src/web/e-stepper/wrap.mjs +++ b/src/web/e-stepper/wrap.mjs @@ -7,11 +7,11 @@ // in `export default require => { ...cjs...; return module.exports }`, shadowing `require`/`module`/ // `exports` so the bundle's external `require(...)` calls hit the provider. Output is written to // dist/index.mjs (the file the frontend serves & imports). -import { readFile, writeFile } from "node:fs/promises"; +import { readFile, writeFile } from 'node:fs/promises'; -const cjsUrl = new URL("./dist/index.cjs", import.meta.url); -const mjsUrl = new URL("./dist/index.mjs", import.meta.url); +const cjsUrl = new URL('./dist/index.cjs', import.meta.url); +const mjsUrl = new URL('./dist/index.mjs', import.meta.url); -const cjs = await readFile(cjsUrl, "utf-8"); +const cjs = await readFile(cjsUrl, 'utf-8'); const wrapped = `export default require => {let module = {exports: {}}; let exports = module.exports;\n${cjs}\n; return module.exports;}`; await writeFile(mjsUrl, wrapped); diff --git a/src/web/module-loader/package.json b/src/web/module-loader/package.json index 1346d13..27bbdec 100644 --- a/src/web/module-loader/package.json +++ b/src/web/module-loader/package.json @@ -3,6 +3,11 @@ "version": "0.0.2", "packageManager": "yarn@4.18.1", "description": "The web plugin for the module loader in Source Academy", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/web/module-loader" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build" @@ -25,19 +30,19 @@ } }, "peerDependencies": { - "@sourceacademy/conductor": "^0.8.0" + "@sourceacademy/conductor": "*" }, "devDependencies": { - "@rollup/plugin-commonjs": "^29.0.3", - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-commonjs": "catalog:", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-module-loader": "workspace:*", "@sourceacademy/common-tabs": "workspace:^", - "@sourceacademy/conductor": "^0.8.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^5.0.0" + "@sourceacademy/conductor": "catalog:", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/web/module-loader/rollup.config.mjs b/src/web/module-loader/rollup.config.mjs index 06a3173..7d85dc7 100644 --- a/src/web/module-loader/rollup.config.mjs +++ b/src/web/module-loader/rollup.config.mjs @@ -1,22 +1,23 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; -import commonjs from "@rollup/plugin-commonjs"; -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +// @ts-check + +import commonjs from '@rollup/plugin-commonjs'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.ts', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), commonjs(), typescript(), terser()], - external: ["react", "react-dom", "react/jsx-runtime"], -}; + plugins: [nodeResolve(), commonjs(), typescript({ exclude: ['**/__tests__/**'] }), terser()], + external: ['react', 'react-dom', 'react/jsx-runtime'], +}); diff --git a/src/web/module-loader/src/__tests__/web.test.ts b/src/web/module-loader/src/__tests__/web.test.ts index 1387212..d828f1c 100644 --- a/src/web/module-loader/src/__tests__/web.test.ts +++ b/src/web/module-loader/src/__tests__/web.test.ts @@ -1,12 +1,12 @@ -import { afterEach, describe, expect, test, vi, type Mock } from "vitest"; -import { ModuleLoaderWebPlugin } from ".."; import { CHANNEL_ID, ModuleLoaderMessageType, WEB_ID, type ModuleLoaderMessage, -} from "@sourceacademy/common-module-loader"; -import type { IChannel, IConduit } from "@sourceacademy/conductor/conduit"; +} from '@sourceacademy/common-module-loader'; +import type { IChannel, IConduit } from '@sourceacademy/conductor/conduit'; +import { afterEach, describe, expect, test, vi, type Mock } from 'vitest'; +import { ModuleLoaderWebPlugin } from '..'; type ModuleDirectory = Record; type ChannelSubscriber = (msg: ModuleLoaderMessage) => void; @@ -17,10 +17,10 @@ type TestChannel = IChannel & { emit: (msg: ModuleLoaderMessage) => void; }; -const moduleDirectoryURL = "/mock/modules.json"; +const moduleDirectoryURL = '/mock/modules.json'; const moduleDirectory: ModuleDirectory = { - chart: { tabs: ["ChartTab", "SettingsTab"] }, - "!!!": { tabs: [] }, + chart: { tabs: ['ChartTab', 'SettingsTab'] }, + '!!!': { tabs: [] }, }; const makeChannel = (): TestChannel => { @@ -48,14 +48,14 @@ const makePlugin = (channel = makeChannel()) => { const mockFetch = (directory = moduleDirectory) => { const json = vi.fn().mockResolvedValue(directory); const fetch = vi.fn().mockResolvedValue({ json }); - vi.stubGlobal("fetch", fetch); + vi.stubGlobal('fetch', fetch); return { fetch, json }; }; const loadDirectory = async (plugin: ModuleLoaderWebPlugin) => { plugin.onModuleDirectoryURLChange(moduleDirectoryURL); await vi.waitFor(() => { - expect(plugin.getModuleTabLocation("ChartTab")).toBe("/mock/tabs/ChartTab.js"); + expect(plugin.getModuleTabLocation('ChartTab')).toBe('/mock/tabs/ChartTab.js'); }); }; @@ -64,23 +64,23 @@ afterEach(() => { ModuleLoaderWebPlugin.instance = null; }); -describe("plugin identity", () => { - test("id is WEB_ID", () => { +describe('plugin identity', () => { + test('id is WEB_ID', () => { expect(makePlugin().plugin.id).toBe(WEB_ID); }); - test("channelAttach declares the module loader channel", () => { + test('channelAttach declares the module loader channel', () => { expect(ModuleLoaderWebPlugin.channelAttach).toEqual([CHANNEL_ID]); }); - test("constructor sets the singleton instance", () => { + test('constructor sets the singleton instance', () => { const { plugin } = makePlugin(); expect(ModuleLoaderWebPlugin.instance).toBe(plugin); }); }); -describe("module directory loading", () => { - test("fetches and stores the configured module directory URL", async () => { +describe('module directory loading', () => { + test('fetches and stores the configured module directory URL', async () => { const { plugin } = makePlugin(); const { fetch, json } = mockFetch(); @@ -90,7 +90,7 @@ describe("module directory loading", () => { expect(json).toHaveBeenCalledOnce(); }); - test("does not fetch the same loaded directory URL twice", async () => { + test('does not fetch the same loaded directory URL twice', async () => { const { plugin } = makePlugin(); const { fetch } = mockFetch(); @@ -101,81 +101,81 @@ describe("module directory loading", () => { }); }); -describe("request handling", () => { - test("returns an error when the directory has not loaded yet", () => { +describe('request handling', () => { + test('returns an error when the directory has not loaded yet', () => { const { channel } = makePlugin(); channel.emit({ type: ModuleLoaderMessageType.REQUEST_MODULE, - moduleName: "chart", + moduleName: 'chart', }); expect(channel.send).toHaveBeenCalledWith({ type: ModuleLoaderMessageType.MODULE_ERROR, - moduleName: "chart", - error: "Module directory not loaded yet", + moduleName: 'chart', + error: 'Module directory not loaded yet', }); }); - test("responds with the bundle URL and tabs for a known module", async () => { + test('responds with the bundle URL and tabs for a known module', async () => { const { plugin, channel } = makePlugin(); mockFetch(); await loadDirectory(plugin); channel.emit({ type: ModuleLoaderMessageType.REQUEST_MODULE, - moduleName: "chart", + moduleName: 'chart', }); expect(channel.send).toHaveBeenCalledWith({ type: ModuleLoaderMessageType.MODULE_RESPONSE, - moduleName: "chart", - moduleURL: "/mock/bundles/chart.js", - tabs: ["ChartTab", "SettingsTab"], + moduleName: 'chart', + moduleURL: '/mock/bundles/chart.js', + tabs: ['ChartTab', 'SettingsTab'], }); }); - test("returns an error for an unknown module", async () => { + test('returns an error for an unknown module', async () => { const { plugin, channel } = makePlugin(); mockFetch(); await loadDirectory(plugin); channel.emit({ type: ModuleLoaderMessageType.REQUEST_MODULE, - moduleName: "missing", + moduleName: 'missing', }); expect(channel.send).toHaveBeenCalledWith({ type: ModuleLoaderMessageType.MODULE_ERROR, - moduleName: "missing", - error: "Module not found: missing", + moduleName: 'missing', + error: 'Module not found: missing', }); }); - test("returns an error for an invalid module name present in the directory", async () => { + test('returns an error for an invalid module name present in the directory', async () => { const { plugin, channel } = makePlugin(); mockFetch(); await loadDirectory(plugin); channel.emit({ type: ModuleLoaderMessageType.REQUEST_MODULE, - moduleName: "!!!", + moduleName: '!!!', }); expect(channel.send).toHaveBeenCalledWith({ type: ModuleLoaderMessageType.MODULE_ERROR, - moduleName: "!!!", - error: "Invalid module name: !!!", + moduleName: '!!!', + error: 'Invalid module name: !!!', }); }); - test("ignores non-request messages", () => { + test('ignores non-request messages', () => { const { channel } = makePlugin(); channel.emit({ type: ModuleLoaderMessageType.MODULE_RESPONSE, - moduleName: "chart", - moduleURL: "/mock/bundles/chart.js", + moduleName: 'chart', + moduleURL: '/mock/bundles/chart.js', tabs: [], }); @@ -184,24 +184,24 @@ describe("request handling", () => { test("invalid directory doesn't error out", async () => { const { plugin } = makePlugin(); - await plugin.onModuleDirectoryURLChange("sgsafghfa/sdgsafgsdaf"); - expect(plugin.getModuleTabLocation("ChartTab")).toBeNull(); + await plugin.onModuleDirectoryURLChange('sgsafghfa/sdgsafgsdaf'); + expect(plugin.getModuleTabLocation('ChartTab')).toBeNull(); }); }); -describe("tab locations", () => { - test("returns null before the module directory has loaded", () => { +describe('tab locations', () => { + test('returns null before the module directory has loaded', () => { const { plugin } = makePlugin(); - expect(plugin.getModuleTabLocation("ChartTab")).toBeNull(); + expect(plugin.getModuleTabLocation('ChartTab')).toBeNull(); }); - test("returns tab URLs for known tabs and null for unknown tabs", async () => { + test('returns tab URLs for known tabs and null for unknown tabs', async () => { const { plugin } = makePlugin(); mockFetch(); await loadDirectory(plugin); - expect(plugin.getModuleTabLocation("ChartTab")).toBe("/mock/tabs/ChartTab.js"); - expect(plugin.getModuleTabLocation("MissingTab")).toBeNull(); + expect(plugin.getModuleTabLocation('ChartTab')).toBe('/mock/tabs/ChartTab.js'); + expect(plugin.getModuleTabLocation('MissingTab')).toBeNull(); }); }); diff --git a/src/web/module-loader/src/index.ts b/src/web/module-loader/src/index.ts index 51b40f7..b2f0014 100644 --- a/src/web/module-loader/src/index.ts +++ b/src/web/module-loader/src/index.ts @@ -3,13 +3,13 @@ import { ModuleLoaderMessageType, WEB_ID, type ModuleLoaderMessage, -} from "@sourceacademy/common-module-loader"; +} from '@sourceacademy/common-module-loader'; import { - type IPlugin, + checkIsPluginClass, type IChannel, type IConduit, - checkIsPluginClass, -} from "@sourceacademy/conductor/conduit"; + type IPlugin, +} from '@sourceacademy/conductor/conduit'; type ModuleDirectoryBundle = { tabs: string[]; @@ -24,11 +24,7 @@ export class ModuleLoaderWebPlugin implements IPlugin { private moduleDirectoryURL: string | null = null; private moduleDirectory: ModuleDirectory | null = null; - constructor( - _conduit: IConduit, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [moduleRequestChannel]: IChannel[], - ) { + constructor(_conduit: IConduit, [moduleRequestChannel]: IChannel[]) { this.__moduleRequestChannel = moduleRequestChannel; ModuleLoaderWebPlugin.instance = this; this.__moduleRequestChannel.subscribe(message => { @@ -37,7 +33,7 @@ export class ModuleLoaderWebPlugin implements IPlugin { return this.__moduleRequestChannel.send({ type: ModuleLoaderMessageType.MODULE_ERROR, moduleName: message.moduleName, - error: "Module directory not loaded yet", + error: 'Module directory not loaded yet', }); } if (!Object.hasOwn(this.moduleDirectory, message.moduleName)) { @@ -56,12 +52,12 @@ export class ModuleLoaderWebPlugin implements IPlugin { } const moduleBaseUrl = this.moduleDirectoryURL.slice( 0, - this.moduleDirectoryURL.lastIndexOf("/") + 1, + this.moduleDirectoryURL.lastIndexOf('/') + 1, ); return this.__moduleRequestChannel.send({ type: ModuleLoaderMessageType.MODULE_RESPONSE, moduleName: message.moduleName, - moduleURL: moduleBaseUrl + "bundles/" + message.moduleName + ".js", + moduleURL: moduleBaseUrl + 'bundles/' + message.moduleName + '.js', tabs: this.moduleDirectory[message.moduleName].tabs, }); }); @@ -79,7 +75,7 @@ export class ModuleLoaderWebPlugin implements IPlugin { this.moduleDirectory = data; }) .catch(error => { - console.error("Failed to load module directory:", error); + console.error('Failed to load module directory:', error); }); } @@ -91,9 +87,9 @@ export class ModuleLoaderWebPlugin implements IPlugin { if (this.moduleDirectory[moduleName].tabs.includes(tabName)) { const moduleBaseUrl = this.moduleDirectoryURL.slice( 0, - this.moduleDirectoryURL.lastIndexOf("/") + 1, + this.moduleDirectoryURL.lastIndexOf('/') + 1, ); - return moduleBaseUrl + "tabs/" + tabName + ".js"; + return moduleBaseUrl + 'tabs/' + tabName + '.js'; } } return null; diff --git a/src/web/stepper/package.json b/src/web/stepper/package.json index 8a19b24..61a4ea1 100644 --- a/src/web/stepper/package.json +++ b/src/web/stepper/package.json @@ -1,8 +1,13 @@ { "name": "@sourceacademy/web-stepper", - "version": "0.0.1", + "version": "0.0.2", "packageManager": "yarn@4.18.1", "description": "Host (web) plugin for the Source Academy stepper: renders evaluation steps", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/web/stepper" + }, "scripts": { "build": "rollup -c && node wrap.mjs", "prepack": "yarn build" @@ -24,24 +29,25 @@ "react-dom": ">=18" }, "devDependencies": { + "@rollup/plugin-commonjs": "catalog:", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:" + }, + "dependencies": { "@blueprintjs/core": "^6.0.0", "@blueprintjs/icons": "^6.0.0", "@mantine/hooks": "^9.0.0", - "@rollup/plugin-commonjs": "^29.0.0", - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", "@sourceacademy/common-stepper": "workspace:*", "@sourceacademy/common-tabs": "workspace:^", - "@sourceacademy/conductor": "^0.8.0", - "@types/react": "^19.0.0", - "@types/react-test-renderer": "^19.0.0", + "@sourceacademy/conductor": "catalog:", "classnames": "^2.3.2", "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-test-renderer": "^19.0.0", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3" + "react-dom": "^19.0.0" } } diff --git a/src/web/stepper/rollup.config.mjs b/src/web/stepper/rollup.config.mjs index 2ec3d1d..cd4af12 100644 --- a/src/web/stepper/rollup.config.mjs +++ b/src/web/stepper/rollup.config.mjs @@ -1,35 +1,40 @@ -import commonjs from "@rollup/plugin-commonjs"; -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; +// @ts-check + +import commonjs from '@rollup/plugin-commonjs'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; // React and Blueprint are provided by the host at load time through the require-provider (see the // frontend's `requireProvider`), so they are kept external and resolved via `require(...)` calls in // the CommonJS output. Everything else (mantine/hooks, classnames, conductor conduit, the common // step protocol) is bundled. wrap.mjs then wraps the CJS output into the host's factory contract. const external = [ - "react", - "react-dom", - "react/jsx-runtime", - "react/jsx-dev-runtime", - "@blueprintjs/core", + 'react', + 'react-dom', + 'react/jsx-runtime', + 'react/jsx-dev-runtime', + '@blueprintjs/core', ]; -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.ts", +export default defineConfig({ + input: 'src/index.ts', output: { - file: "dist/index.cjs", - format: "cjs", - exports: "default", + file: 'dist/index.cjs', + format: 'cjs', + exports: 'default', }, external, plugins: [ nodeResolve({ browser: true, preferBuiltins: false }), commonjs(), - typescript(), + typescript({ exclude: ['**/__tests__/**'] }), terser(), ], -}; + onwarn(warning, warn) { + // Suppress the warnings about 'use client' from @mantine + if (warning.code === 'MODULE_LEVEL_DIRECTIVE') return; + warn(warning); + }, +}); diff --git a/src/web/stepper/src/StepperHostPlugin.tsx b/src/web/stepper/src/StepperHostPlugin.tsx index 7507e55..196142f 100644 --- a/src/web/stepper/src/StepperHostPlugin.tsx +++ b/src/web/stepper/src/StepperHostPlugin.tsx @@ -4,20 +4,20 @@ import { type SerializedStepperStep, type StepperMessage, type SyntaxProfile, -} from "@sourceacademy/common-stepper"; -import type { ITabService, Tab } from "@sourceacademy/common-tabs"; +} from '@sourceacademy/common-stepper'; +import type { ITabService, Tab } from '@sourceacademy/common-tabs'; import { checkIsPluginClass, type IChannel, type IConduit, type IPlugin, -} from "@sourceacademy/conductor/conduit"; -import { createElement, useSyncExternalStore } from "react"; +} from '@sourceacademy/conductor/conduit'; +import { createElement, useSyncExternalStore } from 'react'; -import StepperView from "./SubstVisualizer"; +import StepperView from './SubstVisualizer'; /** The side-content tab id used by the host to show/hide the stepper tab. */ -const TAB_ID = "stepper"; +const TAB_ID = 'stepper'; /** * The host (browser-side) half of the stepper. It listens on the stepper channel for steps pushed @@ -45,19 +45,19 @@ export class StepperHostPlugin implements IPlugin { ) { this.__stepperChannel = stepperChannel; this.__stepperChannel.subscribe(message => { - if (message.type === "steps") { + if (message.type === 'steps') { this.__steps = message.steps; this.__profile = message.profile; this.__error = null; this.__emit(); - } else if (message.type === "error") { + } else if (message.type === 'error') { this.__steps = []; this.__error = message.error; this.__emit(); } }); // Ask the runner to replay any steps it already computed (e.g. tab opened after a run). - this.__stepperChannel.send({ type: "request" }); + this.__stepperChannel.send({ type: 'request' }); const subscribe = (listener: () => void) => this.subscribe(listener); const getSteps = () => this.getSteps(); @@ -70,8 +70,8 @@ export class StepperHostPlugin implements IPlugin { const tab: Tab = { id: TAB_ID, - label: "Stepper", - iconName: "flow-review", + label: 'Stepper', + iconName: 'flow-review', body: createElement(StepperTab), }; tabService.registerTab(tab); diff --git a/src/web/stepper/src/SubstVisualizer.tsx b/src/web/stepper/src/SubstVisualizer.tsx index 0b39ed6..cb1b107 100644 --- a/src/web/stepper/src/SubstVisualizer.tsx +++ b/src/web/stepper/src/SubstVisualizer.tsx @@ -1,12 +1,12 @@ -import { Button, ButtonGroup, Card, Classes, Divider, Pre, Slider } from "@blueprintjs/core"; -import { getHotkeyHandler, type HotkeyItem } from "@mantine/hooks"; -import type { SerializedStepperStep, SyntaxProfile } from "@sourceacademy/common-stepper"; -import classNames from "classnames"; -import { useCallback, useEffect, useState } from "react"; +import { Button, ButtonGroup, Card, Classes, Divider, Pre, Slider } from '@blueprintjs/core'; +import { getHotkeyHandler, type HotkeyItem } from '@mantine/hooks'; +import type { SerializedStepperStep, SyntaxProfile } from '@sourceacademy/common-stepper'; +import classNames from 'classnames'; +import { useCallback, useEffect, useState } from 'react'; -import { CustomASTRenderer } from "./render"; -import { sliderLabels } from "./sliderLabels"; -import { injectStepperStyles } from "./styles"; +import { CustomASTRenderer } from './render'; +import { sliderLabels } from './sliderLabels'; +import { injectStepperStyles } from './styles'; function SubstDefaultText() { return ( @@ -70,7 +70,7 @@ function SubstOutputDisplay(props: { content: string }) { } return ( -
+      
         {props.content}
       
@@ -94,7 +94,7 @@ function useWidth(): [(element: HTMLElement | null) => void, number] { useEffect(() => { if (!element) return; setWidth(Math.floor(element.getBoundingClientRect().width)); - if (typeof ResizeObserver === "undefined") return; + if (typeof ResizeObserver === 'undefined') return; const observer = new ResizeObserver(entries => setWidth(Math.floor(entries[0].contentRect.width)), ); @@ -126,7 +126,7 @@ export default function StepperView(props: StepperViewProps) { // Search forward from current step for a DebuggerStatement redex for (let i = stepValue; i < props.content.length; i++) { const markers = props.content[i].markers; - if (markers?.some(marker => marker.redexNodeType === "DebuggerStatement")) { + if (markers?.some(marker => marker.redexNodeType === 'DebuggerStatement')) { setStepValue(i + 1); // +1 because stepValue is 1-indexed return; } @@ -139,7 +139,7 @@ export default function StepperView(props: StepperViewProps) { // Start searching from the step BEFORE the current one for (let i = stepValue - 2; i >= 0; i--) { const markers = props.content[i].markers; - const isDebuggerStep = markers?.some(marker => marker.redexNodeType === "DebuggerStatement"); + const isDebuggerStep = markers?.some(marker => marker.redexNodeType === 'DebuggerStatement'); if (isDebuggerStep) { setStepValue(i + 1); // Convert back to 1-based indexing return; @@ -155,16 +155,16 @@ export default function StepperView(props: StepperViewProps) { // Setup hotkey bindings const hotkeyBindings: HotkeyItem[] = hasRunCode ? [ - ["a", stepPreviousBreakpoint], - ["f", stepNext], - ["b", stepPrevious], - ["e", stepNextBreakpoint], + ['a', stepPreviousBreakpoint], + ['f', stepNext], + ['b', stepPrevious], + ['e', stepNextBreakpoint], ] : [ - ["a", () => {}], - ["f", () => {}], - ["b", () => {}], - ["e", () => {}], + ['a', () => {}], + ['f', () => {}], + ['b', () => {}], + ['e', () => {}], ]; const hotkeyHandler = getHotkeyHandler(hotkeyBindings); @@ -174,9 +174,9 @@ export default function StepperView(props: StepperViewProps) { // Right now, prioritize the first marker const markers = props.content[contIndex].markers; if (markers === undefined || markers[0] === undefined) { - return "..."; + return '...'; } else { - return markers[0].explanation ?? "..."; + return markers[0].explanation ?? '...'; } }, [lastStepValue, props.content], @@ -193,7 +193,7 @@ export default function StepperView(props: StepperViewProps) { const getOutput = useCallback( (value: number): string => { const contIndex = value <= lastStepValue ? value - 1 : 0; - return props.content[contIndex]?.output ?? ""; + return props.content[contIndex]?.output ?? ''; }, [lastStepValue, props.content], ); @@ -201,7 +201,7 @@ export default function StepperView(props: StepperViewProps) { return (
@@ -216,7 +216,7 @@ export default function StepperView(props: StepperViewProps) { onChange={value => setStepValue(value + 1)} value={(stepValue <= lastStepValue ? stepValue : 1) - 1} /> -
+
{" "} +
{' '}
{hasRunCode ? ( diff --git a/src/web/stepper/src/__tests__/SubstVisualizer.test.tsx b/src/web/stepper/src/__tests__/SubstVisualizer.test.tsx index 55e6d8c..3d1de5a 100644 --- a/src/web/stepper/src/__tests__/SubstVisualizer.test.tsx +++ b/src/web/stepper/src/__tests__/SubstVisualizer.test.tsx @@ -2,24 +2,24 @@ * The stepper's step slider: it counts the steps taken, from 0 (the start) to the last, as in the * e-stepper and the CSE machine, and labels both ends. */ -import { Button, Slider } from "@blueprintjs/core"; -import type { SerializedStepperStep } from "@sourceacademy/common-stepper"; -import { act } from "react"; -import TestRenderer from "react-test-renderer"; -import { describe, expect, test, vi } from "vitest"; +import { Button, Slider } from '@blueprintjs/core'; +import type { SerializedStepperStep } from '@sourceacademy/common-stepper'; +import { act } from 'react'; +import TestRenderer from 'react-test-renderer'; +import { describe, expect, test, vi } from 'vitest'; + +import StepperView from '../SubstVisualizer'; +import { sliderLabels } from '../sliderLabels'; // The hotkey library needs react-dom, which it does not declare (so it does not resolve here). // (Here it runs the binding for the key pressed, as the real one does.) -vi.mock("@mantine/hooks", () => ({ +vi.mock(import('@mantine/hooks'), () => ({ getHotkeyHandler: (bindings: [string, () => void][]) => (event: { key: string }) => bindings.find(([key]) => key === event.key)?.[1](), })); -import { sliderLabels } from "../sliderLabels"; -import StepperView from "../SubstVisualizer"; - const step = (explanation: string): SerializedStepperStep => ({ - ast: { type: "Program", nodeId: "p", body: [] }, + ast: { type: 'Program', nodeId: 'p', body: [] }, markers: [{ explanation }], }); const run = (n: number) => Array.from({ length: n }, (_, i) => step(`step ${i}`)); @@ -32,8 +32,8 @@ function render(content: SerializedStepperStep[]) { return renderer; } -describe("the step slider", () => { - test("counts from 0 to the number of steps taken, and labels both ends", () => { +describe('the step slider', () => { + test('counts from 0 to the number of steps taken, and labels both ends', () => { const view = render(run(174)); const slider = () => view.root.findByType(Slider); expect(slider().props.min).toBe(0); @@ -45,23 +45,23 @@ describe("the step slider", () => { act(() => slider().props.onChange(173)); expect(slider().props.value).toBe(173); // The explanation shown is that of the 174th step, the last. - expect(JSON.stringify(view.toJSON())).toContain("step 173"); + expect(JSON.stringify(view.toJSON())).toContain('step 173'); }); - test("the buttons step, and the double arrows go to the first and the last step", () => { + test('the buttons step, and the double arrows go to the first and the last step', () => { const view = render(run(10)); const slider = () => view.root.findByType(Slider).props.value as number; const button = (icon: string) => view.root.findAllByType(Button).find(b => b.props.icon === icon)!; - act(() => button("chevron-right").props.onClick()); + act(() => button('chevron-right').props.onClick()); expect(slider()).toBe(1); - act(() => button("double-chevron-right").props.onClick()); + act(() => button('double-chevron-right').props.onClick()); expect(slider()).toBe(9); - act(() => button("double-chevron-left").props.onClick()); + act(() => button('double-chevron-left').props.onClick()); expect(slider()).toBe(0); }); - test("a run of one step gives the slider a valid range, disabled", () => { + test('a run of one step gives the slider a valid range, disabled', () => { const slider = render(run(1)).root.findByType(Slider); expect(slider.props.min).toBe(0); expect(slider.props.max).toBe(1); @@ -78,20 +78,20 @@ const withBreakpoints = (): SerializedStepperStep[] => ...s, markers: [ { - explanation: "Running breakpoint()", - redexType: "beforeMarker", - redexNodeType: "DebuggerStatement", + explanation: 'Running breakpoint()', + redexType: 'beforeMarker', + redexNodeType: 'DebuggerStatement', }, ], } : s, ); -describe("breakpoints and keys", () => { +describe('breakpoints and keys', () => { const press = (view: TestRenderer.ReactTestRenderer, key: string) => act(() => view.root - .find(n => typeof n.props.onKeyDown === "function") + .find(n => typeof n.props.onKeyDown === 'function') .props.onKeyDown({ key, preventDefault: () => {} }), ); const value = (view: TestRenderer.ReactTestRenderer) => @@ -99,71 +99,71 @@ describe("breakpoints and keys", () => { const button = (view: TestRenderer.ReactTestRenderer, icon: string) => view.root.findAllByType(Button).find(b => b.props.icon === icon)!; - test("the double arrows jump to the next and the previous breakpoint, else the last and first step", () => { + test('the double arrows jump to the next and the previous breakpoint, else the last and first step', () => { const view = render(withBreakpoints()); - act(() => button(view, "double-chevron-right").props.onClick()); + act(() => button(view, 'double-chevron-right').props.onClick()); expect(value(view)).toBe(3); - act(() => button(view, "double-chevron-right").props.onClick()); + act(() => button(view, 'double-chevron-right').props.onClick()); expect(value(view)).toBe(7); - act(() => button(view, "double-chevron-right").props.onClick()); + act(() => button(view, 'double-chevron-right').props.onClick()); expect(value(view)).toBe(9); - act(() => button(view, "double-chevron-left").props.onClick()); + act(() => button(view, 'double-chevron-left').props.onClick()); expect(value(view)).toBe(7); - act(() => button(view, "double-chevron-left").props.onClick()); + act(() => button(view, 'double-chevron-left').props.onClick()); expect(value(view)).toBe(3); - act(() => button(view, "double-chevron-left").props.onClick()); + act(() => button(view, 'double-chevron-left').props.onClick()); expect(value(view)).toBe(0); }); - test("f / b step, a / e jump between breakpoints", () => { + test('f / b step, a / e jump between breakpoints', () => { const view = render(withBreakpoints()); - press(view, "f"); - press(view, "f"); + press(view, 'f'); + press(view, 'f'); expect(value(view)).toBe(2); - press(view, "b"); + press(view, 'b'); expect(value(view)).toBe(1); - press(view, "e"); + press(view, 'e'); expect(value(view)).toBe(3); - press(view, "a"); + press(view, 'a'); expect(value(view)).toBe(0); // Not past either end. - press(view, "b"); + press(view, 'b'); expect(value(view)).toBe(0); - for (let i = 0; i < 12; i++) press(view, "f"); + for (let i = 0; i < 12; i++) press(view, 'f'); expect(value(view)).toBe(9); }); - test("a step without markers or an explanation says so with dots", () => { + test('a step without markers or an explanation says so with dots', () => { const view = render([ - { ast: { type: "Program", nodeId: "p", body: [] } }, - { ast: { type: "Program", nodeId: "p", body: [] }, markers: [{}] }, + { ast: { type: 'Program', nodeId: 'p', body: [] } }, + { ast: { type: 'Program', nodeId: 'p', body: [] }, markers: [{}] }, ]); const explanation = () => JSON.stringify(view.toJSON()); - expect(explanation()).toContain("..."); - act(() => button(view, "chevron-right").props.onClick()); - expect(explanation()).toContain("..."); + expect(explanation()).toContain('...'); + act(() => button(view, 'chevron-right').props.onClick()); + expect(explanation()).toContain('...'); }); - test("the output printed so far is shown with the step", () => { + test('the output printed so far is shown with the step', () => { const view = render([ - { ...step("a"), output: "" }, - { ...step("b"), output: "hello\n" }, + { ...step('a'), output: '' }, + { ...step('b'), output: 'hello\n' }, ]); - expect(JSON.stringify(view.toJSON())).not.toContain("hello"); - act(() => button(view, "chevron-right").props.onClick()); - expect(JSON.stringify(view.toJSON())).toContain("hello"); + expect(JSON.stringify(view.toJSON())).not.toContain('hello'); + act(() => button(view, 'chevron-right').props.onClick()); + expect(JSON.stringify(view.toJSON())).toContain('hello'); }); - test("before a run there is the welcome text, and the keys do nothing", () => { + test('before a run there is the welcome text, and the keys do nothing', () => { const view = render([]); - expect(JSON.stringify(view.toJSON())).toContain("Welcome to the Stepper!"); - for (const key of ["a", "f", "b", "e"]) press(view, key); + expect(JSON.stringify(view.toJSON())).toContain('Welcome to the Stepper!'); + for (const key of ['a', 'f', 'b', 'e']) press(view, key); expect(value(view)).toBe(0); }); }); -describe("sliderLabels", () => { - test("labels 0, a round step apart, and always the last, never crowding it", () => { +describe(sliderLabels, () => { + test('labels 0, a round step apart, and always the last, never crowding it', () => { expect(sliderLabels(0)).toEqual([0]); expect(sliderLabels(1)).toEqual([0, 1]); expect(sliderLabels(10)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); @@ -174,10 +174,10 @@ describe("sliderLabels", () => { }); describe("the step slider's track", () => { - test("is measured again when the tab is resized (a click maps to the right step)", () => { + test('is measured again when the tab is resized (a click maps to the right step)', () => { const observers: ((entries: { contentRect: { width: number } }[]) => void)[] = []; vi.stubGlobal( - "ResizeObserver", + 'ResizeObserver', class { constructor(callback: (typeof observers)[number]) { observers.push(callback); @@ -187,10 +187,10 @@ describe("the step slider's track", () => { }, ); // A Blueprint slider takes its listeners off the document when it goes away. - vi.stubGlobal("document", { + vi.stubGlobal('document', { addEventListener() {}, removeEventListener() {}, - getElementById: () => ({ textContent: "" }), + getElementById: () => ({ textContent: '' }), }); let view!: TestRenderer.ReactTestRenderer; act(() => { diff --git a/src/web/stepper/src/__tests__/render.test.tsx b/src/web/stepper/src/__tests__/render.test.tsx index b9caf25..7190862 100644 --- a/src/web/stepper/src/__tests__/render.test.tsx +++ b/src/web/stepper/src/__tests__/render.test.tsx @@ -3,13 +3,13 @@ * used when no syntax profile is given, the profile-driven renderer, function values, hover text, * redex markers, parenthesisation and host-specific node renderers. */ -import { Popover } from "@blueprintjs/core"; -import type { SerializedMarker, SyntaxProfile } from "@sourceacademy/common-stepper"; -import { act } from "react"; -import TestRenderer from "react-test-renderer"; -import { describe, expect, test } from "vitest"; +import { Popover } from '@blueprintjs/core'; +import type { SerializedMarker, SyntaxProfile } from '@sourceacademy/common-stepper'; +import { act } from 'react'; +import TestRenderer from 'react-test-renderer'; +import { describe, expect, test } from 'vitest'; -import { CustomASTRenderer, type StepperNode } from "../render"; +import { CustomASTRenderer, type StepperNode } from '../render'; let ids = 0; /** A node with a fresh nodeId. */ @@ -18,17 +18,17 @@ const n = (type: string, fields: Record = {}): StepperNode => ( nodeId: `n${ids++}`, ...fields, }); -const lit = (raw: string) => n("Literal", { raw, value: raw }); -const id = (name: string) => n("Identifier", { name }); +const lit = (raw: string) => n('Literal', { raw, value: raw }); +const id = (name: string) => n('Identifier', { name }); const bin = (operator: string, left: StepperNode, right: StepperNode) => - n("BinaryExpression", { operator, left, right }); + n('BinaryExpression', { operator, left, right }); function render( ast: StepperNode, options: { profile?: SyntaxProfile; markers?: SerializedMarker[]; - nodeRenderers?: Parameters[0]["nodeRenderers"]; + nodeRenderers?: Parameters[0]['nodeRenderers']; } = {}, ) { let renderer!: TestRenderer.ReactTestRenderer; @@ -40,215 +40,215 @@ function render( /** The rendered text (popover contents excluded: they render only when opened). */ const text = (node: TestRenderer.ReactTestInstance | string): string => - typeof node === "string" ? node : node.children.map(text).join(""); + typeof node === 'string' ? node : node.children.map(text).join(''); -describe("built-in (Source) renderers", () => { - test("a whole program", () => { - const fn = n("ArrowFunctionExpression", { - params: [id("x")], - body: bin("*", id("x"), id("x")), +describe('built-in (Source) renderers', () => { + test('a whole program', () => { + const fn = n('ArrowFunctionExpression', { + params: [id('x')], + body: bin('*', id('x'), id('x')), }); - const program = n("Program", { + const program = n('Program', { body: [ - n("VariableDeclaration", { - kind: "const", - declarations: [n("VariableDeclarator", { id: id("sq"), init: fn })], + n('VariableDeclaration', { + kind: 'const', + declarations: [n('VariableDeclarator', { id: id('sq'), init: fn })], }), - n("FunctionDeclaration", { - id: id("f"), - params: [id("a"), id("b")], - body: n("BlockStatement", { + n('FunctionDeclaration', { + id: id('f'), + params: [id('a'), id('b')], + body: n('BlockStatement', { body: [ - n("IfStatement", { - test: n("LogicalExpression", { operator: "&&", left: id("a"), right: id("b") }), - consequent: n("BlockStatement", { - body: [n("ReturnStatement", { argument: lit("1") })], + n('IfStatement', { + test: n('LogicalExpression', { operator: '&&', left: id('a'), right: id('b') }), + consequent: n('BlockStatement', { + body: [n('ReturnStatement', { argument: lit('1') })], }), - alternate: n("BlockStatement", { - body: [n("ReturnStatement", { argument: null })], + alternate: n('BlockStatement', { + body: [n('ReturnStatement', { argument: null })], }), }), ], }), }), - n("ExpressionStatement", { - expression: n("CallExpression", { + n('ExpressionStatement', { + expression: n('CallExpression', { callee: fn, - arguments: [n("UnaryExpression", { operator: "-", argument: lit("2") })], + arguments: [n('UnaryExpression', { operator: '-', argument: lit('2') })], }), }), - n("ExpressionStatement", { - expression: n("ConditionalExpression", { - test: id("c"), - consequent: n("ArrayExpression", { elements: [lit("1"), lit("2"), null] }), - alternate: n("Literal", { value: "s" }), + n('ExpressionStatement', { + expression: n('ConditionalExpression', { + test: id('c'), + consequent: n('ArrayExpression', { elements: [lit('1'), lit('2'), null] }), + alternate: n('Literal', { value: 's' }), }), }), - n("DebuggerStatement"), - n("ExpressionStatement", { expression: n("Literal", { value: null }) }), - n("UnknownNode"), + n('DebuggerStatement'), + n('ExpressionStatement', { expression: n('Literal', { value: null }) }), + n('UnknownNode'), ], }); const all = text(render(program).root); - expect(all).toContain("const sq = x => x * x;"); - expect(all).toContain("function f(a, b)"); - expect(all).toContain("if (a && b)"); - expect(all).toContain("return 1;"); - expect(all).toContain(" else "); - expect(all).toContain("(x => x * x)(-2);"); + expect(all).toContain('const sq = x => x * x;'); + expect(all).toContain('function f(a, b)'); + expect(all).toContain('if (a && b)'); + expect(all).toContain('return 1;'); + expect(all).toContain(' else '); + expect(all).toContain('(x => x * x)(-2);'); expect(all).toContain('c ? [1, 2] : "s";'); - expect(all).toContain("debugger;"); - expect(all).toContain("null;"); - expect(all).toContain(""); + expect(all).toContain('debugger;'); + expect(all).toContain('null;'); + expect(all).toContain(''); }); - test("a named function value is a mu-term with its definition in a popover", () => { - const fn = n("ArrowFunctionExpression", { - name: "fact", - params: [id("n")], - body: n("CallExpression", { callee: id("fact"), arguments: [id("n")] }), + test('a named function value is a mu-term with its definition in a popover', () => { + const fn = n('ArrowFunctionExpression', { + name: 'fact', + params: [id('n')], + body: n('CallExpression', { callee: id('fact'), arguments: [id('n')] }), }); - const view = render(n("ExpressionStatement", { expression: fn })); - expect(text(view.root)).toBe("fact;"); + const view = render(n('ExpressionStatement', { expression: fn })); + expect(text(view.root)).toBe('fact;'); expect(view.root.findAllByType(Popover).length).toBeGreaterThan(0); }); - test("parentheses follow precedence and associativity", () => { - const sum = bin("+", lit("1"), lit("2")); - expect(text(render(bin("*", sum, lit("3"))).root)).toBe("(1 + 2) * 3"); - expect(text(render(bin("-", lit("1"), bin("-", lit("2"), lit("3")))).root)).toBe("1 - (2 - 3)"); - expect(text(render(bin("**", bin("**", lit("2"), lit("3")), lit("4"))).root)).toBe( - "(2 ** 3) ** 4", + test('parentheses follow precedence and associativity', () => { + const sum = bin('+', lit('1'), lit('2')); + expect(text(render(bin('*', sum, lit('3'))).root)).toBe('(1 + 2) * 3'); + expect(text(render(bin('-', lit('1'), bin('-', lit('2'), lit('3')))).root)).toBe('1 - (2 - 3)'); + expect(text(render(bin('**', bin('**', lit('2'), lit('3')), lit('4'))).root)).toBe( + '(2 ** 3) ** 4', ); - expect(text(render(bin("**", lit("2"), bin("**", lit("3"), lit("4")))).root)).toBe( - "2 ** 3 ** 4", + expect(text(render(bin('**', lit('2'), bin('**', lit('3'), lit('4')))).root)).toBe( + '2 ** 3 ** 4', ); expect( text( render( - n("LogicalExpression", { - operator: "??", - left: n("LogicalExpression", { operator: "||", left: id("a"), right: id("b") }), - right: id("c"), + n('LogicalExpression', { + operator: '??', + left: n('LogicalExpression', { operator: '||', left: id('a'), right: id('b') }), + right: id('c'), }), ).root, ), - ).toBe("(a || b) ?? c"); - expect(text(render(n("UnaryExpression", { operator: "-", argument: sum })).root)).toBe( - "-(1 + 2)", + ).toBe('(a || b) ?? c'); + expect(text(render(n('UnaryExpression', { operator: '-', argument: sum })).root)).toBe( + '-(1 + 2)', ); }); - test("redex markers wrap the marked node", () => { - const left = lit("1"); - const ast = bin("+", left, lit("2")); - const view = render(ast, { markers: [{ redexId: left.nodeId, redexType: "beforeMarker" }] }); - const marked = view.root.findAll(x => x.props.className === "beforeMarker"); + test('redex markers wrap the marked node', () => { + const left = lit('1'); + const ast = bin('+', left, lit('2')); + const view = render(ast, { markers: [{ redexId: left.nodeId, redexType: 'beforeMarker' }] }); + const marked = view.root.findAll(x => x.props.className === 'beforeMarker'); expect(marked).toHaveLength(1); - expect(text(marked[0])).toBe("1"); + expect(text(marked[0])).toBe('1'); }); }); -describe("profile-driven rendering", () => { +describe('profile-driven rendering', () => { const profile: SyntaxProfile = { templates: { - Program: [{ lines: "body" }], + Program: [{ lines: 'body' }], Def: [ - { token: "def ", cls: "identifier" }, - { prop: "id.name", cls: "identifier" }, - "(", - { list: "params", sep: ", " }, - "):", - { block: "body" }, + { token: 'def ', cls: 'identifier' }, + { prop: 'id.name', cls: 'identifier' }, + '(', + { list: 'params', sep: ', ' }, + '):', + { block: 'body' }, ], - Return: [{ token: "return ", cls: "operator" }, { child: "argument" }], - Call: [{ child: "callee" }, "(", { list: "arguments", sep: ", ", prefix: "" }, ")"], + Return: [{ token: 'return ', cls: 'operator' }, { child: 'argument' }], + Call: [{ child: 'callee' }, '(', { list: 'arguments', sep: ', ', prefix: '' }, ')'], If: [ - { token: "if ", cls: "identifier" }, - { child: "test" }, - ":", - { block: "body" }, - { when: "orelse", parts: [{ token: "else:", cls: "identifier" }, { block: "orelse" }] }, - { unless: "orelse", parts: ["# no else"] }, + { token: 'if ', cls: 'identifier' }, + { child: 'test' }, + ':', + { block: 'body' }, + { when: 'orelse', parts: [{ token: 'else:', cls: 'identifier' }, { block: 'orelse' }] }, + { unless: 'orelse', parts: ['# no else'] }, ], Bin: [ - { child: "left" }, - " ", - { prop: "operator", cls: "operator" }, - " ", - { child: "right", isRight: true }, + { child: 'left' }, + ' ', + { prop: 'operator', cls: 'operator' }, + ' ', + { child: 'right', isRight: true }, ], - Id: [{ prop: "name" }], - Num: [{ prop: "raw", cls: "literal" }], - Builtin: [{ prop: "name" }], + Id: [{ prop: 'name' }], + Num: [{ prop: 'raw', cls: 'literal' }], + Builtin: [{ prop: 'name' }], Img: [ - { image: "src", altProp: "label" }, - { unless: "src", parts: ["<", { prop: "label" }, ">"] }, + { image: 'src', altProp: 'label' }, + { unless: 'src', parts: ['<', { prop: 'label' }, '>'] }, ], }, - operatorPrecedence: { "+": 11, "*": 12 }, + operatorPrecedence: { '+': 11, '*': 12 }, expressionPrecedence: { Id: 20, Num: 18, Call: 18, Bin: 14 }, - functionValues: [{ type: "Def", nameProp: "name" }], - hoverText: [{ type: "Builtin", textProp: "hoverText" }], + functionValues: [{ type: 'Def', nameProp: 'name' }], + hoverText: [{ type: 'Builtin', textProp: 'hoverText' }], }; - const pid = (name: string) => n("Id", { name }); - const num = (raw: string) => n("Num", { raw }); + const pid = (name: string) => n('Id', { name }); + const num = (raw: string) => n('Num', { raw }); const pbin = (operator: string, left: StepperNode, right: StepperNode) => - n("Bin", { operator, left, right }); + n('Bin', { operator, left, right }); - test("templates: tokens, props, children, lists, blocks, lines, when/unless", () => { - const program = n("Program", { + test('templates: tokens, props, children, lists, blocks, lines, when/unless', () => { + const program = n('Program', { body: [ - n("Def", { - id: { name: "f" }, - params: [pid("x")], - body: [n("Return", { argument: pbin("*", pbin("+", pid("x"), num("1")), num("2")) })], + n('Def', { + id: { name: 'f' }, + params: [pid('x')], + body: [n('Return', { argument: pbin('*', pbin('+', pid('x'), num('1')), num('2')) })], }), - n("If", { - test: pid("c"), - body: [n("Call", { callee: pid("g"), arguments: [] })], - orelse: [num("0")], + n('If', { + test: pid('c'), + body: [n('Call', { callee: pid('g'), arguments: [] })], + orelse: [num('0')], }), - n("If", { test: pid("d"), body: [num("1")], orelse: null }), - n("Unmapped"), + n('If', { test: pid('d'), body: [num('1')], orelse: null }), + n('Unmapped'), ], }); const all = text(render(program, { profile }).root); - expect(all).toContain("def f(x):"); - expect(all).toContain("return (x + 1) * 2"); - expect(all).toContain("if c:g()else:0"); - expect(all).toContain("if d:1# no else"); - expect(all).toContain(""); + expect(all).toContain('def f(x):'); + expect(all).toContain('return (x + 1) * 2'); + expect(all).toContain('if c:g()else:0'); + expect(all).toContain('if d:1# no else'); + expect(all).toContain(''); }); - test("a named function value collapses to its name with a popover", () => { - const def = n("Def", { name: "f", id: { name: "f" }, params: [], body: [] }); - const view = render(n("Call", { callee: def, arguments: [] }), { profile }); - expect(text(view.root)).toBe("f()"); + test('a named function value collapses to its name with a popover', () => { + const def = n('Def', { name: 'f', id: { name: 'f' }, params: [], body: [] }); + const view = render(n('Call', { callee: def, arguments: [] }), { profile }); + expect(text(view.root)).toBe('f()'); expect(view.root.findAllByType(Popover).length).toBeGreaterThan(0); }); - test("hover text and inline images", () => { - const builtin = n("Builtin", { name: "print", hoverText: "built-in function print" }); + test('hover text and inline images', () => { + const builtin = n('Builtin', { name: 'print', hoverText: 'built-in function print' }); const view = render( - n("Call", { + n('Call', { callee: builtin, - arguments: [n("Img", { src: "data:image/png;base64,AAAA", label: "Rune" })], + arguments: [n('Img', { src: 'data:image/png;base64,AAAA', label: 'Rune' })], }), { profile }, ); expect(view.root.findAllByType(Popover).length).toBe(2); - expect(view.root.findAllByType("img")).toHaveLength(1); + expect(view.root.findAllByType('img')).toHaveLength(1); // Only data: URLs are shown as images; anything else falls back to the label. - const fallback = render(n("Img", { src: "https://example.com/x.png", label: "Rune" }), { + const fallback = render(n('Img', { src: 'https://example.com/x.png', label: 'Rune' }), { profile, }); - expect(fallback.root.findAllByType("img")).toHaveLength(0); - expect(text(render(n("Img", { label: "Rune" }), { profile }).root)).toBe(""); + expect(fallback.root.findAllByType('img')).toHaveLength(0); + expect(text(render(n('Img', { label: 'Rune' }), { profile }).root)).toBe(''); }); - test("host-specific node renderers take precedence and render children in context", () => { - const view = render(n("Block", { body: pbin("+", pid("a"), num("1")) }), { + test('host-specific node renderers take precedence and render children in context', () => { + const view = render(n('Block', { body: pbin('+', pid('a'), num('1')) }), { profile, nodeRenderers: { Block: (node, renderChild) => ( @@ -256,12 +256,12 @@ describe("profile-driven rendering", () => { ), }, }); - const block = view.root.find(x => x.props.className === "host-block"); - expect(text(block)).toBe("a + 1"); + const block = view.root.find(x => x.props.className === 'host-block'); + expect(text(block)).toBe('a + 1'); }); }); -describe("popover contents", () => { +describe('popover contents', () => { /** Renders the (lazily rendered) content of every popover in `view`, recursively. */ function popoverTexts(view: TestRenderer.ReactTestRenderer, depth = 2): string[] { if (depth === 0) return []; @@ -274,51 +274,51 @@ describe("popover contents", () => { }); } - test("a Source function value shows its definition, recursively", () => { - const fn = n("ArrowFunctionExpression", { - name: "fact", - params: [id("n")], - body: n("CallExpression", { callee: id("fact"), arguments: [id("n")] }), + test('a Source function value shows its definition, recursively', () => { + const fn = n('ArrowFunctionExpression', { + name: 'fact', + params: [id('n')], + body: n('CallExpression', { callee: id('fact'), arguments: [id('n')] }), }); - const texts = popoverTexts(render(n("ExpressionStatement", { expression: fn }))); - expect(texts[0]).toContain("Function definition"); - expect(texts[0]).toContain("n => fact(n)"); + const texts = popoverTexts(render(n('ExpressionStatement', { expression: fn }))); + expect(texts[0]).toContain('Function definition'); + expect(texts[0]).toContain('n => fact(n)'); // The recursive reference inside the definition is itself a mu-term with the same popover. - expect(texts.filter(t => t.includes("n => fact(n)")).length).toBeGreaterThan(1); + expect(texts.filter(t => t.includes('n => fact(n)')).length).toBeGreaterThan(1); }); - test("an anonymous Source lambda refers to nothing by name, so it renders inline", () => { - const fn = n("ArrowFunctionExpression", { params: [id("a"), id("b")], body: id("a") }); - expect(text(render(fn).root)).toBe("(a, b) => a"); + test('an anonymous Source lambda refers to nothing by name, so it renders inline', () => { + const fn = n('ArrowFunctionExpression', { params: [id('a'), id('b')], body: id('a') }); + expect(text(render(fn).root)).toBe('(a, b) => a'); }); - test("a profile function value, hover text and an image show their popovers", () => { + test('a profile function value, hover text and an image show their popovers', () => { const profile: SyntaxProfile = { templates: { - Def: ["def ", { prop: "id.name" }, "(): ", { child: "body" }], - Call: [{ child: "callee" }, "(", { list: "arguments", sep: ", " }, ")"], - Id: [{ prop: "name" }], - Builtin: [{ prop: "name" }], - Img: [{ image: "src", altProp: "label" }], + Def: ['def ', { prop: 'id.name' }, '(): ', { child: 'body' }], + Call: [{ child: 'callee' }, '(', { list: 'arguments', sep: ', ' }, ')'], + Id: [{ prop: 'name' }], + Builtin: [{ prop: 'name' }], + Img: [{ image: 'src', altProp: 'label' }], }, - functionValues: [{ type: "Def", nameProp: "name" }], - hoverText: [{ type: "Builtin", textProp: "hoverText" }], + functionValues: [{ type: 'Def', nameProp: 'name' }], + hoverText: [{ type: 'Builtin', textProp: 'hoverText' }], }; - const def = n("Def", { - name: "f", - id: { name: "f" }, - body: n("Call", { callee: n("Id", { name: "f" }), arguments: [] }), + const def = n('Def', { + name: 'f', + id: { name: 'f' }, + body: n('Call', { callee: n('Id', { name: 'f' }), arguments: [] }), }); - const ast = n("Call", { - callee: n("Builtin", { name: "print", hoverText: "built-in function print" }), - arguments: [def, n("Img", { src: "data:image/png;base64,AAAA", label: "Rune" })], + const ast = n('Call', { + callee: n('Builtin', { name: 'print', hoverText: 'built-in function print' }), + arguments: [def, n('Img', { src: 'data:image/png;base64,AAAA', label: 'Rune' })], }); const texts = popoverTexts(render(ast, { profile })); expect(texts).toEqual( expect.arrayContaining([ - " built-in function print", - expect.stringContaining("def f(): f()"), - " Rune", + ' built-in function print', + expect.stringContaining('def f(): f()'), + ' Rune', ]), ); }); diff --git a/src/web/stepper/src/index.ts b/src/web/stepper/src/index.ts index 0b00d54..21dc6b5 100644 --- a/src/web/stepper/src/index.ts +++ b/src/web/stepper/src/index.ts @@ -1,4 +1,4 @@ // The host's external-plugin loader imports the bundle and calls its default export as a factory // (`default(requireProvider) => PluginClass`); the build wraps this CommonJS output accordingly (see // wrap.mjs). So the entry default-exports the plugin class, which becomes the bundle's `module.exports`. -export { StepperHostPlugin as default } from "./StepperHostPlugin"; +export { StepperHostPlugin as default } from './StepperHostPlugin'; diff --git a/src/web/stepper/src/render.tsx b/src/web/stepper/src/render.tsx index adcbc69..6f5e63a 100644 --- a/src/web/stepper/src/render.tsx +++ b/src/web/stepper/src/render.tsx @@ -1,20 +1,20 @@ -import { Classes, Icon, Popover } from "@blueprintjs/core"; +import { Classes, Icon, Popover } from '@blueprintjs/core'; import type { FunctionValueRule, SerializedStepperNode, SerializedStepperStep, SyntaxProfile, SyntaxTemplatePart, -} from "@sourceacademy/common-stepper"; -import classNames from "classnames"; -import { useCallback } from "react"; +} from '@sourceacademy/common-stepper'; +import classNames from 'classnames'; +import { useCallback } from 'react'; /** * The serialized AST nodes are plain JSON. `Record` lets the renderer read the * language-specific fields (e.g. `left`, `operator`, `params`) without per-node typing, exactly as * the original (class-based) renderer did after casting. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- language-specific AST fields are read untyped, exactly as the original class-based renderer did after casting + export type StepperNode = SerializedStepperNode & Record; /** @@ -61,16 +61,16 @@ interface RenderContext { /** Maps a profile token class to the stepper's CSS colour class. */ const TOKEN_CLASS: Record = { - operator: "stepper-operator", - identifier: "stepper-identifier", - literal: "stepper-literal", - conditional: "stepper-conditional-operator", + operator: 'stepper-operator', + identifier: 'stepper-identifier', + literal: 'stepper-literal', + conditional: 'stepper-conditional-operator', }; /** Reads a node property by a (possibly dotted, e.g. `"id.name"`) path, for profile `prop` parts. */ function readNodeProp(node: StepperNode, path: string): unknown { return path - .split(".") + .split('.') .reduce( (value, key) => (value == null ? value : (value as Record)[key]), node, @@ -117,14 +117,14 @@ function FunctionDefinitionPopoverContent({ renderFunctionArguments, }: FunctionDefinitionPopoverContentProps) { return ( -
+
- {" Function definition"} + {' Function definition'}
           
             {renderFunctionArguments(node.params, renderNode, styleWrapper, popoverDepth)}
-            {" => "}
+            {' => '}
             {renderNode(node.body, {
               styleWrapper: styleWrapper ?? (_node => p => p),
               popoverDepth: popoverDepth + 1,
@@ -157,10 +157,10 @@ function ProfileFunctionDefinitionPopover({
   profile,
 }: ProfileFunctionDefinitionPopoverProps) {
   return (
-    
+
- {" Function definition"} + {' Function definition'}
           
             {renderNode(node, {
@@ -184,7 +184,7 @@ function ProfileFunctionDefinitionPopover({
  */
 function ProfileHoverTextPopover({ text }: { text: string }) {
   return (
-    
+
{` ${text}`} @@ -196,7 +196,7 @@ function ProfileHoverTextPopover({ text }: { text: string }) { /** Popover body for an `image` part: the inline opaque-value thumbnail, enlarged to be legible. */ function EnlargedThumbnailPopover({ src, alt }: { src: string; alt?: string }) { return ( -
+
{alt ? ( <> @@ -204,7 +204,7 @@ function EnlargedThumbnailPopover({ src, alt }: { src: string; alt?: string }) { {` ${alt}`} ) : null} - {alt + {alt
); @@ -223,7 +223,7 @@ function renderNode( const renderers = { Literal(node: StepperNode) { const stringifyLiteralValue = (value: unknown) => - typeof value === "string" ? '"' + value + '"' : value !== null ? String(value) : "null"; + typeof value === 'string' ? '"' + value + '"' : value !== null ? String(value) : 'null'; return ( {node.raw ? node.raw : stringifyLiteralValue(node.value)} @@ -307,7 +307,7 @@ function renderNode( (result, item) => ( {result} - {", "} + {', '} {item} ), @@ -315,9 +315,9 @@ function renderNode( ); return ( - {"["} + {'['} {renderedArguments} - {"]"} + {']'} ); }, @@ -327,7 +327,7 @@ function renderNode( * corresponding to the name of lambda expression */ function muTermStyleWrapper(targetNode: StepperNode) { - if (targetNode.type === "Identifier" && targetNode.name === node.name) { + if (targetNode.type === 'Identifier' && targetNode.name === node.name) { function addHovering(preprocessed: React.ReactNode): React.ReactNode { return ( @@ -383,7 +383,7 @@ function renderNode( ) : ( {renderFunctionArguments(node.params, renderNode, styleWrapper, popoverDepth)} - {" => "} + {' => '} {renderNode(node.body, { styleWrapper: composeStyleWrapper(styleWrapper, muTermStyleWrapper)!, popoverDepth: popoverDepth, @@ -396,12 +396,12 @@ function renderNode( styleWrapper: styleWrapper, popoverDepth: popoverDepth, }); - if (node.callee.type === "ArrowFunctionExpression" && node.callee.name === undefined) { + if (node.callee.type === 'ArrowFunctionExpression' && node.callee.name === undefined) { renderedCallee = ( - {"("} + {'('} {renderedCallee} - {")"} + {')'} ); } @@ -427,12 +427,12 @@ function renderNode( return ( - {"if "} - {"("} + {'if '} + {'('} {renderNode(node.test, { styleWrapper: styleWrapper, popoverDepth: popoverDepth })} - {") "} + {') '} {renderNode(node.consequent, { @@ -442,7 +442,7 @@ function renderNode( {node.alternate && ( - {" else "} + {' else '} {renderNode(node.alternate, { styleWrapper: styleWrapper, popoverDepth: popoverDepth, @@ -455,23 +455,23 @@ function renderNode( ReturnStatement(node: StepperNode) { return ( - {"return "} + {'return '} {node.argument && renderNode(node.argument, { styleWrapper: styleWrapper, popoverDepth: popoverDepth })} - {";"} + {';'} ); }, BlockStatement(node: StepperNode) { return ( - {"{"} + {'{'} {node.body.map((ast: StepperNode, index: number) => ( -
+
{renderNode(ast, { styleWrapper, popoverDepth: popoverDepth })}
))} - {"}"} + {'}'} ); }, @@ -479,7 +479,7 @@ function renderNode( return ( {renderNode(node.expression, { styleWrapper: styleWrapper, popoverDepth: popoverDepth })} - {";"} + {';'} ); }, @@ -489,7 +489,7 @@ function renderNode( {`function ${node.id.name}`} {renderArguments(node.params)} - {" "} + {' '} {renderNode(node.body, { styleWrapper: styleWrapper, popoverDepth: popoverDepth })} @@ -501,11 +501,11 @@ function renderNode( {node.kind} {node.declarations.map((ast: StepperNode, idx: number) => ( - {idx !== 0 && ", "} + {idx !== 0 && ', '} {renderNode(ast, { styleWrapper: styleWrapper, popoverDepth: popoverDepth })} ))} - {";"} + {';'} ); }, @@ -513,10 +513,10 @@ function renderNode( return ( {renderNode(node.id, { styleWrapper: styleWrapper, popoverDepth: popoverDepth })} - {" = "} + {' = '} {node.init ? renderNode(node.init, { styleWrapper: styleWrapper, popoverDepth: popoverDepth }) - : "undefined"} + : 'undefined'} ); }, @@ -532,7 +532,7 @@ function renderNode( styleWrapper: StyleWrapper | undefined, popoverDepth: number, ) => { - if (!nodes) return "()"; + if (!nodes) return '()'; const args: React.ReactNode[] = nodes.map(arg => renderNodeFn(arg, { styleWrapper: styleWrapper ?? (_node => p => p), @@ -543,7 +543,7 @@ function renderNode( (result, item) => ( {result} - {", "} + {', '} {item} ), @@ -552,9 +552,9 @@ function renderNode( if (args.length !== 1) { renderedArguments = ( - {"("} + {'('} {renderedArguments} - {")"} + {')'} ); } @@ -562,7 +562,7 @@ function renderNode( }; const renderArguments = (nodes: StepperNode[] | undefined) => { - if (!nodes) return "()"; + if (!nodes) return '()'; const args: React.ReactNode[] = nodes.map(arg => renderNode(arg, { styleWrapper: styleWrapper, popoverDepth: popoverDepth }), ); @@ -570,7 +570,7 @@ function renderNode( (result, item) => ( {result} - {", "} + {', '} {item} ), @@ -578,9 +578,9 @@ function renderNode( ); renderedArguments = ( - {"("} + {'('} {renderedArguments} - {")"} + {')'} ); return renderedArguments; @@ -600,22 +600,22 @@ function renderNode( }); const cls = (c?: string) => (c ? TOKEN_CLASS[c] : undefined); const renderPart = (part: SyntaxTemplatePart, key: number): React.ReactNode => { - if (typeof part === "string") return {part}; - if ("token" in part) + if (typeof part === 'string') return {part}; + if ('token' in part) return ( {part.token} ); - if ("prop" in part) { + if ('prop' in part) { const value = readNodeProp(node, part.prop); return ( - {value == null ? "" : String(value)} + {value == null ? '' : String(value)} ); } - if ("child" in part) { + if ('child' in part) { const child = node[part.child] as StepperNode | null | undefined; return ( @@ -623,7 +623,7 @@ function renderNode( ); } - if ("list" in part) { + if ('list' in part) { const items = (node[part.list] as StepperNode[] | undefined) ?? []; if (items.length === 0) return null; return ( @@ -638,19 +638,19 @@ function renderNode( ); } - if ("block" in part) { + if ('block' in part) { const items = (node[part.block] as StepperNode[] | undefined) ?? []; return ( {items.map((item, i) => ( -
+
{renderNode(item, childContext({}))}
))} ); } - if ("lines" in part) { + if ('lines' in part) { const items = (node[part.lines] as StepperNode[] | undefined) ?? []; return ( @@ -660,21 +660,21 @@ function renderNode( ); } - if ("when" in part) { + if ('when' in part) { return node[part.when] ? ( {part.parts.map((p, i) => renderPart(p, i))} ) : null; } - if ("unless" in part) { + if ('unless' in part) { return node[part.unless] ? null : ( {part.parts.map((p, i) => renderPart(p, i))} ); } - if ("image" in part) { + if ('image' in part) { const src = readNodeProp(node, part.image); - if (typeof src !== "string" || !src.startsWith("data:")) return null; + if (typeof src !== 'string' || !src.startsWith('data:')) return null; const alt = part.altProp === undefined ? undefined : readNodeProp(node, part.altProp); - const altText = alt == null ? "" : String(alt); + const altText = alt == null ? '' : String(alt); return ( } > {altText} @@ -712,7 +712,7 @@ function renderNode( // Recursive hovering: identifiers in the body that refer back to this function (by name) are // themselves wrapped in the same popover, so a recursive definition stays explorable. const muTermWrapper: StyleWrapper = (targetNode: StepperNode) => - targetNode.type === "Identifier" && targetNode.name === funcName + targetNode.type === 'Identifier' && targetNode.name === funcName ? (preprocessed: React.ReactNode) => ( rule.type === currentNode.type); const hoverText = hoverRule ? readNodeProp(currentNode, hoverRule.textProp) : undefined; - if (typeof hoverText === "string" && hoverText !== "") { + if (typeof hoverText === 'string' && hoverText !== '') { const content = result; result = ( - {"("} + {'('} {result} - {")"} + {')'} ); } @@ -855,7 +855,7 @@ export function CustomASTRenderer( // Match by stable node id rather than object identity, which does not survive // serialization across the runner/host channel. if (marker.redexId !== undefined && marker.redexId === node.nodeId) { - const Wrapper = node.type === "FunctionDeclaration" ? "div" : "span"; + const Wrapper = node.type === 'FunctionDeclaration' ? 'div' : 'span'; returnNode = {returnNode}; } }); @@ -891,12 +891,14 @@ function expressionNeedsParenthesis( const opPrecedence = profile?.operatorPrecedence ?? OPERATOR_PRECEDENCE; const nodePrecedence = exprPrecedence[node.type as keyof typeof exprPrecedence] as - number | undefined; + | number + | undefined; if (nodePrecedence === NEEDS_PARENTHESES) { return true; } const parentNodePrecedence = exprPrecedence[parentNode.type as keyof typeof exprPrecedence] as - number | undefined; + | number + | undefined; if (nodePrecedence === undefined || parentNodePrecedence === undefined) { return false; } @@ -908,7 +910,7 @@ function expressionNeedsParenthesis( ); } - if (!("operator" in node) || !("operator" in parentNode)) { + if (!('operator' in node) || !('operator' in parentNode)) { return false; } @@ -916,14 +918,14 @@ function expressionNeedsParenthesis( // Not a `LogicalExpression` or `BinaryExpression` return false; } - if (node.operator === "**" && parentNode.operator === "**") { + if (node.operator === '**' && parentNode.operator === '**') { // Exponentiation operator has right-to-left associativity return !isRightHand; } if ( nodePrecedence === 13 && parentNodePrecedence === 13 && - (node.operator === "??" || parentNode.operator === "??") + (node.operator === '??' || parentNode.operator === '??') ) { return true; } @@ -936,31 +938,31 @@ function expressionNeedsParenthesis( : nodeOperatorPrecedence <= parentNodeOperatorPrecedence; } const OPERATOR_PRECEDENCE = { - "||": 2, - "??": 3, - "&&": 4, - "|": 5, - "^": 6, - "&": 7, - "==": 8, - "!=": 8, - "===": 8, - "!==": 8, - "<": 9, - ">": 9, - "<=": 9, - ">=": 9, + '||': 2, + '??': 3, + '&&': 4, + '|': 5, + '^': 6, + '&': 7, + '==': 8, + '!=': 8, + '===': 8, + '!==': 8, + '<': 9, + '>': 9, + '<=': 9, + '>=': 9, in: 9, instanceof: 9, - "<<": 10, - ">>": 10, - ">>>": 10, - "+": 11, - "-": 11, - "*": 12, - "%": 12, - "/": 12, - "**": 13, + '<<': 10, + '>>': 10, + '>>>': 10, + '+': 11, + '-': 11, + '*': 12, + '%': 12, + '/': 12, + '**': 13, }; const NEEDS_PARENTHESES = 17; const EXPRESSIONS_PRECEDENCE = { diff --git a/src/web/stepper/src/styles.ts b/src/web/stepper/src/styles.ts index 52f495e..9442235 100644 --- a/src/web/stepper/src/styles.ts +++ b/src/web/stepper/src/styles.ts @@ -146,14 +146,14 @@ const STEPPER_CSS = ` } `; -const STYLE_ELEMENT_ID = "__sa_stepper_styles"; +const STYLE_ELEMENT_ID = '__sa_stepper_styles'; /** Injects (or refreshes) the stepper stylesheet in the document. No-op outside the browser. */ export function injectStepperStyles(): void { - if (typeof document === "undefined") return; + if (typeof document === 'undefined') return; let style = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null; if (style === null) { - style = document.createElement("style"); + style = document.createElement('style'); style.id = STYLE_ELEMENT_ID; document.head.appendChild(style); } diff --git a/src/web/stepper/wrap.mjs b/src/web/stepper/wrap.mjs index 7371e96..0904009 100644 --- a/src/web/stepper/wrap.mjs +++ b/src/web/stepper/wrap.mjs @@ -7,11 +7,11 @@ // in `export default require => { ...cjs...; return module.exports }`, shadowing `require`/`module`/ // `exports` so the bundle's external `require(...)` calls hit the provider. (Mirrors plugins #25's // build transform.) Output is written to dist/index.mjs (the file the frontend serves & imports). -import { readFile, writeFile } from "node:fs/promises"; +import { readFile, writeFile } from 'node:fs/promises'; -const cjsUrl = new URL("./dist/index.cjs", import.meta.url); -const mjsUrl = new URL("./dist/index.mjs", import.meta.url); +const cjsUrl = new URL('./dist/index.cjs', import.meta.url); +const mjsUrl = new URL('./dist/index.mjs', import.meta.url); -const cjs = await readFile(cjsUrl, "utf-8"); +const cjs = await readFile(cjsUrl, 'utf-8'); const wrapped = `export default require => {let module = {exports: {}}; let exports = module.exports;\n${cjs}\n; return module.exports;}`; await writeFile(mjsUrl, wrapped); diff --git a/src/web/test/package.json b/src/web/test/package.json index 1f7fbd4..2d4ab98 100644 --- a/src/web/test/package.json +++ b/src/web/test/package.json @@ -3,6 +3,11 @@ "version": "0.0.2", "packageManager": "yarn@4.12.0", "description": "The test web plugin for the plugins repository", + "repository": { + "type": "git", + "url": "git+https://github.com/source-academy/plugins.git", + "directory": "src/web/test" + }, "scripts": { "build": "rollup -c", "prepack": "yarn build" @@ -26,22 +31,22 @@ }, "peerDependencies": { "@sourceacademy/common-tabs": "^0.0.1", - "@sourceacademy/conductor": "^0.8.0", + "@sourceacademy/conductor": "*", "react": "^19", "react-dom": "^19" }, "devDependencies": { - "@rollup/plugin-commonjs": "^29.0.3", - "@rollup/plugin-node-resolve": "^16.0.3", - "@rollup/plugin-terser": "^1.0.0", - "@rollup/plugin-typescript": "^12.3.0", + "@rollup/plugin-commonjs": "catalog:", + "@rollup/plugin-node-resolve": "catalog:", + "@rollup/plugin-terser": "catalog:", + "@rollup/plugin-typescript": "catalog:", "@sourceacademy/common-tabs": "workspace:^", "@sourceacademy/common-test": "workspace:*", - "@sourceacademy/conductor": "^0.8.0", + "@sourceacademy/conductor": "catalog:", "@types/react": "^19.2.17", - "rollup": "^4.60.2", - "tslib": "^2.8.1", - "typescript": "^6.0.3", - "vitest": "^4.1.9" + "rollup": "catalog:", + "tslib": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/src/web/test/rollup.config.mjs b/src/web/test/rollup.config.mjs index 02b9dde..9f687c1 100644 --- a/src/web/test/rollup.config.mjs +++ b/src/web/test/rollup.config.mjs @@ -1,22 +1,23 @@ -import nodeResolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import typescript from "@rollup/plugin-typescript"; -import commonjs from "@rollup/plugin-commonjs"; -/** - * @type {import('rollup').RollupOptions} - */ -export default { - input: "src/index.tsx", +// @ts-check + +import commonjs from '@rollup/plugin-commonjs'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import { defineConfig } from 'rollup'; + +export default defineConfig({ + input: 'src/index.tsx', output: [ { - file: "dist/index.cjs", - format: "cjs", + file: 'dist/index.cjs', + format: 'cjs', }, { - file: "dist/index.mjs", - format: "esm", + file: 'dist/index.mjs', + format: 'esm', }, ], - plugins: [nodeResolve(), commonjs(), typescript(), terser()], - external: ["react", "react-dom", "react/jsx-runtime"], -}; + plugins: [nodeResolve(), commonjs(), typescript({ exclude: ['**/__tests__/**'] }), terser()], + external: ['react', 'react-dom', 'react/jsx-runtime'], +}); diff --git a/src/web/test/src/index.tsx b/src/web/test/src/index.tsx index add94be..f61b919 100644 --- a/src/web/test/src/index.tsx +++ b/src/web/test/src/index.tsx @@ -1,11 +1,11 @@ -import { CHANNEL_ID, WEB_ID, type TestMessage } from "@sourceacademy/common-test"; +import type { ITabService, Tab } from '@sourceacademy/common-tabs'; +import { CHANNEL_ID, WEB_ID, type TestMessage } from '@sourceacademy/common-test'; import { - type IPlugin, + checkIsPluginClass, type IChannel, type IConduit, - checkIsPluginClass, -} from "@sourceacademy/conductor/conduit"; -import type { ITabService, Tab } from "@sourceacademy/common-tabs"; + type IPlugin, +} from '@sourceacademy/conductor/conduit'; function Element({ workspaceLocation, @@ -19,11 +19,11 @@ function Element({ return (
This is a test tab. The location is {workspaceLocation} @@ -46,22 +46,17 @@ export abstract class TestPlugin implements IPlugin { readonly id: string = WEB_ID; static readonly channelAttach = [CHANNEL_ID]; private readonly __testChannel: IChannel; - constructor( - _conduit: IConduit, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [testChannel]: IChannel[], - tabService: ITabService, - ) { + constructor(_conduit: IConduit, [testChannel]: IChannel[], tabService: ITabService) { this.__testChannel = testChannel; this.__testChannel.subscribe(message => { console.log(message); }); - this.__testChannel.send("ping"); + this.__testChannel.send('ping'); const tab = { - id: "test-tab", - iconName: "airplane", + id: 'test-tab', + iconName: 'airplane', body: , - label: "Test Tab", + label: 'Test Tab', disabled: false, } satisfies Tab; tabService.registerTab(tab); diff --git a/vitest.config.js b/vitest.config.js deleted file mode 100644 index e83a239..0000000 --- a/vitest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - coverage: { - provider: "istanbul", - reporter: "lcov", - exclude: ["**/dist"], - }, - }, -}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..6c1d99d --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + provider: 'istanbul', + reporter: ['text', 'lcov'], + exclude: ['**/dist'], + }, + }, +}); diff --git a/yarn.config.cjs b/yarn.config.cjs new file mode 100644 index 0000000..7463aee --- /dev/null +++ b/yarn.config.cjs @@ -0,0 +1,22 @@ +// @ts-check + +const pathlib = require('path'); +const { defineConfig } = require('@yarnpkg/types'); +const { repository } = require('./package.json'); + +module.exports = defineConfig({ + async constraints({ Yarn }) { + for (const workspace of Yarn.workspaces()) { + // Ignore the root workspace + if (workspace.cwd === '.') continue; + + const normedPath = workspace.cwd.split(pathlib.sep).join(pathlib.posix.sep); + + // Check that the repository field for each workspace is correct + workspace.set('repository', { + ...repository, + directory: normedPath, + }); + } + }, +}); diff --git a/yarn.lock b/yarn.lock index d4c189a..a3516c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,7 +5,7 @@ __metadata: version: 10 cacheKey: 10c0 -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.29.7": +"@babel/code-frame@npm:^7.29.7": version: 7.29.7 resolution: "@babel/code-frame@npm:7.29.7" dependencies: @@ -40,7 +40,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.23.9, @babel/core@npm:^7.27.4, @babel/core@npm:^7.29.0": +"@babel/core@npm:^7.29.0": version: 7.29.7 resolution: "@babel/core@npm:7.29.7" dependencies: @@ -87,7 +87,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.27.5, @babel/generator@npm:^7.29.7": +"@babel/generator@npm:^7.29.7": version: 7.29.7 resolution: "@babel/generator@npm:7.29.7" dependencies: @@ -177,13 +177,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.29.7, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.29.7 - resolution: "@babel/helper-plugin-utils@npm:7.29.7" - checksum: 10c0/380477a06133274a2759f9355929cb60a95e8b8fee624a1ae1fa349e1d1645b89daca456f72833f6d1062bffa12ee4271c5bf0cc5a61c0166cdc24c7591e2408 - languageName: node - linkType: hard - "@babel/helper-string-parser@npm:^7.29.7": version: 7.29.7 resolution: "@babel/helper-string-parser@npm:7.29.7" @@ -246,7 +239,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.29.3, @babel/parser@npm:^7.29.7": +"@babel/parser@npm:^7.29.3, @babel/parser@npm:^7.29.7": version: 7.29.7 resolution: "@babel/parser@npm:7.29.7" dependencies: @@ -268,193 +261,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-async-generators@npm:^7.8.4": - version: 7.8.4 - resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 - languageName: node - linkType: hard - -"@babel/plugin-syntax-bigint@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-properties@npm:^7.12.13": - version: 7.12.13 - resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.12.13" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-static-block@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/4464bf9115f4a2d02ce1454411baf9cfb665af1da53709c5c56953e5e2913745b0fcce82982a00463d6facbdd93445c691024e310b91431a1e2f024b158f6371 - languageName: node - linkType: hard - -"@babel/plugin-syntax-import-attributes@npm:^7.24.7": - version: 7.29.7 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.29.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.29.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/b9a47e869d8c06676297069895ae34e2bd244ec4c3bdf15002f1e69aed32eef0361044af22a43f271b8de5e23a40534fe6a74a63e7ab98e3d60a74b322444b49 - languageName: node - linkType: hard - -"@babel/plugin-syntax-import-meta@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee - languageName: node - linkType: hard - -"@babel/plugin-syntax-json-strings@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e - languageName: node - linkType: hard - -"@babel/plugin-syntax-jsx@npm:^7.27.1": - version: 7.29.7 - resolution: "@babel/plugin-syntax-jsx@npm:7.29.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.29.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/1736000de183538ba8eef34520105508860e48b0c763254ba9158af5e814ed8bbceeedbb4281fbda33de787ae5b3870e92f60c6ae7131e7d322e451d57387896 - languageName: node - linkType: hard - -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b - languageName: node - linkType: hard - -"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce - languageName: node - linkType: hard - -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 - languageName: node - linkType: hard - -"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 - languageName: node - linkType: hard - -"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/69822772561706c87f0a65bc92d0772cea74d6bc0911537904a676d5ff496a6d3ac4e05a166d8125fce4a16605bace141afc3611074e170a994e66e5397787f3 - languageName: node - linkType: hard - -"@babel/plugin-syntax-top-level-await@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f - languageName: node - linkType: hard - -"@babel/plugin-syntax-typescript@npm:^7.27.1": - version: 7.29.7 - resolution: "@babel/plugin-syntax-typescript@npm:7.29.7" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.29.7" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c49883b0327e8683b770dc823205af5c697da216e590dcf5bf53f3f031e7e381de450b164f8f99853f0837a3de5cb793298e2be6697a0f6e452bb9dd34b5165e - languageName: node - linkType: hard - "@babel/runtime@npm:^7.26.10, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": version: 7.29.7 resolution: "@babel/runtime@npm:7.29.7" @@ -514,7 +320,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.29.0, @babel/types@npm:^7.29.7": +"@babel/types@npm:^7.29.0, @babel/types@npm:^7.29.7": version: 7.29.7 resolution: "@babel/types@npm:7.29.7" dependencies: @@ -534,13 +340,6 @@ __metadata: languageName: node linkType: hard -"@bcoe/v8-coverage@npm:^0.2.3": - version: 0.2.3 - resolution: "@bcoe/v8-coverage@npm:0.2.3" - checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 - languageName: node - linkType: hard - "@blueprintjs/colors@npm:^5.1.16": version: 5.1.16 resolution: "@blueprintjs/colors@npm:5.1.16" @@ -595,28 +394,6 @@ __metadata: languageName: node linkType: hard -"@cacheable/memory@npm:^2.2.0": - version: 2.2.0 - resolution: "@cacheable/memory@npm:2.2.0" - dependencies: - "@cacheable/utils": "npm:^2.5.0" - "@keyv/bigmap": "npm:^1.3.1" - hookified: "npm:^1.15.1" - keyv: "npm:^5.6.0" - checksum: 10c0/c74889eb09871be0696065778fff013a498ddc73b538d113c2cb6ece361746f2959fbddfa4ce03ce53c9259998be05949e393212e72413dbd07616744eabd261 - languageName: node - linkType: hard - -"@cacheable/utils@npm:^2.5.0": - version: 2.5.0 - resolution: "@cacheable/utils@npm:2.5.0" - dependencies: - hashery: "npm:^1.5.1" - keyv: "npm:^5.6.0" - checksum: 10c0/99e901accd71eeb0bce91484d6c66d7ef2c731b541561eb5dc9ea7fed78bdc0b65e5dd2a80edde50191143bcfc5f238fc4742475b32e82d43b43eea224038e35 - languageName: node - linkType: hard - "@changesets/apply-release-plan@npm:^8.1.1": version: 8.1.1 resolution: "@changesets/apply-release-plan@npm:8.1.1" @@ -820,6 +597,15 @@ __metadata: languageName: node linkType: hard +"@commander-js/extra-typings@npm:^15.0.0": + version: 15.0.0 + resolution: "@commander-js/extra-typings@npm:15.0.0" + peerDependencies: + commander: ~15.0.0 + checksum: 10c0/4282857c0acb2da1b893fe8c39a939bdfae2d7cbb43a22c1fe8ef5bef4cf615ddb34d96239f7603294ef5014728799c8212f4451fa9d2e469cce087eb89f0218 + languageName: node + linkType: hard + "@emnapi/core@npm:1.10.0": version: 1.10.0 resolution: "@emnapi/core@npm:1.10.0" @@ -848,188 +634,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/aix-ppc64@npm:0.28.1" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/android-arm64@npm:0.28.1" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/android-arm@npm:0.28.1" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/android-x64@npm:0.28.1" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/darwin-arm64@npm:0.28.1" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/darwin-x64@npm:0.28.1" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/freebsd-arm64@npm:0.28.1" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/freebsd-x64@npm:0.28.1" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-arm64@npm:0.28.1" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-arm@npm:0.28.1" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-ia32@npm:0.28.1" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-loong64@npm:0.28.1" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-mips64el@npm:0.28.1" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-ppc64@npm:0.28.1" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-riscv64@npm:0.28.1" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-s390x@npm:0.28.1" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-x64@npm:0.28.1" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/netbsd-arm64@npm:0.28.1" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/netbsd-x64@npm:0.28.1" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/openbsd-arm64@npm:0.28.1" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/openbsd-x64@npm:0.28.1" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openharmony-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/openharmony-arm64@npm:0.28.1" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/sunos-x64@npm:0.28.1" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/win32-arm64@npm:0.28.1" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/win32-ia32@npm:0.28.1" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/win32-x64@npm:0.28.1" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": version: 4.9.1 resolution: "@eslint-community/eslint-utils@npm:4.9.1" @@ -1084,13 +688,13 @@ __metadata: languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.7.3": - version: 0.7.3 - resolution: "@eslint/plugin-kit@npm:0.7.3" +"@eslint/plugin-kit@npm:^0.7.2": + version: 0.7.2 + resolution: "@eslint/plugin-kit@npm:0.7.2" dependencies: "@eslint/core": "npm:^1.2.1" levn: "npm:^0.4.1" - checksum: 10c0/2daebaedcfbd261478e8b87dfb46d3118f48277c19eb298483ebc6f3b438e9688b86ddca13b3aeb08dfb06be00fb6073506852386aa124645c1170e7e33d140c + checksum: 10c0/aafba08077bcd6d7dde6c2e21db18086046a88f914f29971a84cac9ad2d48952ded1b293e665e523805297eff756522dafa16f0062195e2c7143dcd1d47d11ed languageName: node linkType: hard @@ -1187,20 +791,6 @@ __metadata: languageName: node linkType: hard -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" - dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e - languageName: node - linkType: hard - "@isaacs/fs-minipass@npm:^4.0.0": version: 4.0.1 resolution: "@isaacs/fs-minipass@npm:4.0.1" @@ -1210,378 +800,81 @@ __metadata: languageName: node linkType: hard -"@istanbuljs/load-nyc-config@npm:^1.0.0": - version: 1.1.0 - resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" - dependencies: - camelcase: "npm:^5.3.1" - find-up: "npm:^4.1.0" - get-package-type: "npm:^0.1.0" - js-yaml: "npm:^3.13.1" - resolve-from: "npm:^5.0.0" - checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 - languageName: node - linkType: hard - -"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": +"@istanbuljs/schema@npm:^0.1.3": version: 0.1.6 resolution: "@istanbuljs/schema@npm:0.1.6" checksum: 10c0/bb0d370bf3dd454d2f37f1bccb8921e2da99adacef2da56ef47850e25d7a4de69cf639ead8c189755aef38921369024b4afea3535a5c2ac9082b3e1171bcbc3a languageName: node linkType: hard -"@jest/console@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/console@npm:30.4.1" - dependencies: - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - jest-message-util: "npm:30.4.1" - jest-util: "npm:30.4.1" - slash: "npm:^3.0.0" - checksum: 10c0/f782722ef5754ab864b996000cf1f0545f7be9db6ba8f89cb2381dfab9910a52c59a830e5ea069a76840023e40806493d9900d8eb7e9821d23a11a498f32739e - languageName: node - linkType: hard - -"@jest/core@npm:30.4.2": - version: 30.4.2 - resolution: "@jest/core@npm:30.4.2" - dependencies: - "@jest/console": "npm:30.4.1" - "@jest/pattern": "npm:30.4.0" - "@jest/reporters": "npm:30.4.1" - "@jest/test-result": "npm:30.4.1" - "@jest/transform": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.3.2" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - exit-x: "npm:^0.2.2" - fast-json-stable-stringify: "npm:^2.1.0" - graceful-fs: "npm:^4.2.11" - jest-changed-files: "npm:30.4.1" - jest-config: "npm:30.4.2" - jest-haste-map: "npm:30.4.1" - jest-message-util: "npm:30.4.1" - jest-regex-util: "npm:30.4.0" - jest-resolve: "npm:30.4.1" - jest-resolve-dependencies: "npm:30.4.2" - jest-runner: "npm:30.4.2" - jest-runtime: "npm:30.4.2" - jest-snapshot: "npm:30.4.1" - jest-util: "npm:30.4.1" - jest-validate: "npm:30.4.1" - jest-watcher: "npm:30.4.1" - pretty-format: "npm:30.4.1" - slash: "npm:^3.0.0" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/4237ec79d5403b82ba89e3be6e4318d9f37c3a11281bd76cfbdd4ff08d8c89850555607c4d494dab3526e01a90db3539e549017883967dd392b5084f1be0d5b2 +"@jridgewell/gen-mapping@npm:0.4.0-beta.0": + version: 0.4.0-beta.0 + resolution: "@jridgewell/gen-mapping@npm:0.4.0-beta.0" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.6.0-beta.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3c8e3d13318cac007834b2ebc065e99574f5d12c1059e7fb58ad50e3b1cc6e422f610e29c2b90afeaad967a0e69503352b8b29b25954985e6bc8dfdb0031d53b languageName: node linkType: hard -"@jest/diff-sequences@npm:30.4.0": - version: 30.4.0 - resolution: "@jest/diff-sequences@npm:30.4.0" - checksum: 10c0/b4358b1b885098b905cb777f58788ddd45f90c4ebc3ce2c04fb1d4c9516f35ac2d9daef8263cd21c537bd7a52ab320f03e4ba9521677959ae20e3d405356b420 +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.13, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b languageName: node linkType: hard -"@jest/environment@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/environment@npm:30.4.1" +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" dependencies: - "@jest/fake-timers": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - jest-mock: "npm:30.4.1" - checksum: 10c0/704987ff8650c91a8ed13796ce47e9c55da3c12a01902d9e384330cead18eb4d34ce665a8d9962dddf2736fac006f92efc1039b8da424adf8fdc16f8d81aff6c + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 languageName: node linkType: hard -"@jest/expect-utils@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/expect-utils@npm:30.4.1" - dependencies: - "@jest/get-type": "npm:30.1.0" - checksum: 10c0/6dea9e11ebcc7be68fea5950ae5a1b7ff9fd1490101ee8af0aede336b9934ab24a28bcafe2f1171dac0f95982406386c609ca2659b9132e1a9d419e8d69b9cd4 +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e languageName: node linkType: hard -"@jest/expect@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/expect@npm:30.4.1" +"@jridgewell/source-map@npm:^0.3.3": + version: 0.3.11 + resolution: "@jridgewell/source-map@npm:0.3.11" dependencies: - expect: "npm:30.4.1" - jest-snapshot: "npm:30.4.1" - checksum: 10c0/2133183e735982879408036237b115abc2e57fa52bb7324be0a1f2ab6941a57da93b2e6f498dc110b7d007dd20463013fbcc5b24377cf65e6a8518d3b2ff76bd + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + checksum: 10c0/50a4fdafe0b8f655cb2877e59fe81320272eaa4ccdbe6b9b87f10614b2220399ae3e05c16137a59db1f189523b42c7f88bd097ee991dbd7bc0e01113c583e844 languageName: node linkType: hard -"@jest/fake-timers@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/fake-timers@npm:30.4.1" - dependencies: - "@jest/types": "npm:30.4.1" - "@sinonjs/fake-timers": "npm:^15.4.0" - "@types/node": "npm:*" - jest-message-util: "npm:30.4.1" - jest-mock: "npm:30.4.1" - jest-util: "npm:30.4.1" - checksum: 10c0/4a10e4eb64bb5ea2531cdcc79f3058731f5c14faf2a74f498fcb37f6690c3c0f9b12a9856736d26e34631eb38db12e12812da71de27b9d332df44dda9f460fbe +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 languageName: node linkType: hard -"@jest/get-type@npm:30.1.0": - version: 30.1.0 - resolution: "@jest/get-type@npm:30.1.0" - checksum: 10c0/3e65fd5015f551c51ec68fca31bbd25b466be0e8ee8075d9610fa1c686ea1e70a942a0effc7b10f4ea9a338c24337e1ad97ff69d3ebacc4681b7e3e80d1b24ac +"@jridgewell/sourcemap-codec@npm:^1.6.0, @jridgewell/sourcemap-codec@npm:^1.6.0-beta.0": + version: 1.6.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.6.0" + checksum: 10c0/b5be700e45a775f218589c3466c4ffea630582b4988657652da464e5ab8a7d18bf928ee4fd2363fb346ede4bea9c6ff0bae05a538358c4565ece6199531b5f72 languageName: node linkType: hard -"@jest/globals@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/globals@npm:30.4.1" +"@jridgewell/trace-mapping@npm:0.3.31, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.31": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: - "@jest/environment": "npm:30.4.1" - "@jest/expect": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - jest-mock: "npm:30.4.1" - checksum: 10c0/7961eefdc9e69ba7754d11a1bae4bc2960f33e03d9c1d6c73f27895b8cf92a9118a234330f31dc8efe16e835fe70ef9cc6c26f60121f6b6e9fac71c8b1bcd709 - languageName: node - linkType: hard - -"@jest/pattern@npm:30.4.0": - version: 30.4.0 - resolution: "@jest/pattern@npm:30.4.0" - dependencies: - "@types/node": "npm:*" - jest-regex-util: "npm:30.4.0" - checksum: 10c0/05bc0799f84f3750bbbff0f9a546979efd0dbcee86c1be98b9e2811a68885809ec7b5cca39b8dda1497cb7cf17b7be936019fba8dfbcd9c53b181e03e67f4f82 - languageName: node - linkType: hard - -"@jest/reporters@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/reporters@npm:30.4.1" - dependencies: - "@bcoe/v8-coverage": "npm:^0.2.3" - "@jest/console": "npm:30.4.1" - "@jest/test-result": "npm:30.4.1" - "@jest/transform": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - "@jridgewell/trace-mapping": "npm:^0.3.25" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - collect-v8-coverage: "npm:^1.0.2" - exit-x: "npm:^0.2.2" - glob: "npm:^10.5.0" - graceful-fs: "npm:^4.2.11" - istanbul-lib-coverage: "npm:^3.0.0" - istanbul-lib-instrument: "npm:^6.0.0" - istanbul-lib-report: "npm:^3.0.0" - istanbul-lib-source-maps: "npm:^5.0.0" - istanbul-reports: "npm:^3.1.3" - jest-message-util: "npm:30.4.1" - jest-util: "npm:30.4.1" - jest-worker: "npm:30.4.1" - slash: "npm:^3.0.0" - string-length: "npm:^4.0.2" - v8-to-istanbul: "npm:^9.0.1" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/cf5220462c6242fa564bbeb6d5988ebfd814e0351f3bddae07323b55c68c7ebd4aa4c23e717231ab4b2d63c4fc7fa4615b9dad8584be534bd44622981242dceb - languageName: node - linkType: hard - -"@jest/schemas@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/schemas@npm:30.4.1" - dependencies: - "@sinclair/typebox": "npm:^0.34.0" - checksum: 10c0/96f388ebfc1974457fcbde2ad36c40a0b549cba3f624fe8d9d6e5903a152dc75e4043f4ac9ac7668622f2ecb0f9a4dcb9a38edf3bc0d52b82045b2bb2b69b72a - languageName: node - linkType: hard - -"@jest/snapshot-utils@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/snapshot-utils@npm:30.4.1" - dependencies: - "@jest/types": "npm:30.4.1" - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - natural-compare: "npm:^1.4.0" - checksum: 10c0/81da9079719eece02b89c45cb97162b5b7d794981652c8d8fe2846843ac81ce219ea4bc21bde7cf76c9032006435f82bd9aee8d6139d90b77078ddad4865af02 - languageName: node - linkType: hard - -"@jest/source-map@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/source-map@npm:30.0.1" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.25" - callsites: "npm:^3.1.0" - graceful-fs: "npm:^4.2.11" - checksum: 10c0/e7bda2786fc9f483d9dd7566c58c4bd948830997be862dfe80a3ae5550ff3f84753abb52e705d02ebe9db9f34ba7ebec4c2db11882048cdeef7a66f6332b3897 - languageName: node - linkType: hard - -"@jest/test-result@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/test-result@npm:30.4.1" - dependencies: - "@jest/console": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - "@types/istanbul-lib-coverage": "npm:^2.0.6" - collect-v8-coverage: "npm:^1.0.2" - checksum: 10c0/920fa3fe3cc8b5e11bfe36066d733030f1245865d7cac4862e3783a96f9c0a087fd8073c8cb56e4c87c6fcc97b46e6f828ecd3b10dd8e208f5e1b983fcc5cdb8 - languageName: node - linkType: hard - -"@jest/test-sequencer@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/test-sequencer@npm:30.4.1" - dependencies: - "@jest/test-result": "npm:30.4.1" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.4.1" - slash: "npm:^3.0.0" - checksum: 10c0/531b19ffb2358b3b22a56b306359acf66db2073978dd6df8a9522b5b4034ad7540a9cb84bdfebbcb2872686d6d2ab8cabea04ad23ef9d4488cbafd03f7511501 - languageName: node - linkType: hard - -"@jest/transform@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/transform@npm:30.4.1" - dependencies: - "@babel/core": "npm:^7.27.4" - "@jest/types": "npm:30.4.1" - "@jridgewell/trace-mapping": "npm:^0.3.25" - babel-plugin-istanbul: "npm:^7.0.1" - chalk: "npm:^4.1.2" - convert-source-map: "npm:^2.0.0" - fast-json-stable-stringify: "npm:^2.1.0" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.4.1" - jest-regex-util: "npm:30.4.0" - jest-util: "npm:30.4.1" - pirates: "npm:^4.0.7" - slash: "npm:^3.0.0" - write-file-atomic: "npm:^5.0.1" - checksum: 10c0/194f463f179f6ab3ccd6f4f0f03a117e3c01a7ce098ebf562250aca4c900ed3a9ec08b694227788eabd7cb4e0597f1d0788077c7550ddc679f68a0ad21cc87e0 - languageName: node - linkType: hard - -"@jest/types@npm:30.4.1": - version: 30.4.1 - resolution: "@jest/types@npm:30.4.1" - dependencies: - "@jest/pattern": "npm:30.4.0" - "@jest/schemas": "npm:30.4.1" - "@types/istanbul-lib-coverage": "npm:^2.0.6" - "@types/istanbul-reports": "npm:^3.0.4" - "@types/node": "npm:*" - "@types/yargs": "npm:^17.0.33" - chalk: "npm:^4.1.2" - checksum: 10c0/4c79f6dbdb1c7eaab5da255fc696c7cae744759d4020e42da8aa63b37fe55ce594be73075fe1ee5407dd59d7e47975be9f674bfc81e91bae2c89c62d27ba55a1 - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:0.4.0-beta.0": - version: 0.4.0-beta.0 - resolution: "@jridgewell/gen-mapping@npm:0.4.0-beta.0" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.6.0-beta.0" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/3c8e3d13318cac007834b2ebc065e99574f5d12c1059e7fb58ad50e3b1cc6e422f610e29c2b90afeaad967a0e69503352b8b29b25954985e6bc8dfdb0031d53b - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.13, @jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.13 - resolution: "@jridgewell/gen-mapping@npm:0.3.13" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b - languageName: node - linkType: hard - -"@jridgewell/remapping@npm:^2.3.5": - version: 2.3.5 - resolution: "@jridgewell/remapping@npm:2.3.5" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.2 - resolution: "@jridgewell/resolve-uri@npm:3.1.2" - checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e - languageName: node - linkType: hard - -"@jridgewell/source-map@npm:^0.3.3": - version: 0.3.11 - resolution: "@jridgewell/source-map@npm:0.3.11" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - checksum: 10c0/50a4fdafe0b8f655cb2877e59fe81320272eaa4ccdbe6b9b87f10614b2220399ae3e05c16137a59db1f189523b42c7f88bd097ee991dbd7bc0e01113c583e844 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": - version: 1.5.5 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" - checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.6.0, @jridgewell/sourcemap-codec@npm:^1.6.0-beta.0": - version: 1.6.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.6.0" - checksum: 10c0/b5be700e45a775f218589c3466c4ffea630582b4988657652da464e5ab8a7d18bf928ee4fd2363fb346ede4bea9c6ff0bae05a538358c4565ece6199531b5f72 - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:0.3.31, @jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.31": - version: 0.3.31 - resolution: "@jridgewell/trace-mapping@npm:0.3.31" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.1.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 - languageName: node - linkType: hard - -"@keyv/bigmap@npm:^1.3.1": - version: 1.3.1 - resolution: "@keyv/bigmap@npm:1.3.1" - dependencies: - hashery: "npm:^1.4.0" - hookified: "npm:^1.15.0" - peerDependencies: - keyv: ^5.6.0 - checksum: 10c0/acc6a4a5edf462ce23e95672ab4bfaf7cd1941dff6bf3a2f671ce467961ace1fac9d3eb75a9ed9a8e92012e00a7d8b16ad1677bc539a52c3ad0cec31473e2349 - languageName: node - linkType: hard - -"@keyv/serialize@npm:^1.1.1": - version: 1.1.1 - resolution: "@keyv/serialize@npm:1.1.1" - checksum: 10c0/b0008cae4a54400c3abf587b8cc2474c6f528ee58969ce6cf9cb07a04006f80c73c85971d6be6544408318a2bc40108236a19a82aea0a6de95aae49533317374 + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 languageName: node linkType: hard @@ -1643,20 +936,6 @@ __metadata: languageName: node linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd - languageName: node - linkType: hard - -"@pkgr/core@npm:^0.3.6": - version: 0.3.6 - resolution: "@pkgr/core@npm:0.3.6" - checksum: 10c0/153f0f4563f505faeba13c733efa0e05e467ce1c6b941055a5fd3b4560da60fbf1dff4b11da0075f034ddda11f2842b90395f60895dde5825875b616edccc11c - languageName: node - linkType: hard - "@pnpm/deps.graph-sequencer@npm:^1100.0.1": version: 1100.0.1 resolution: "@pnpm/deps.graph-sequencer@npm:1100.0.1" @@ -1783,7 +1062,7 @@ __metadata: languageName: node linkType: hard -"@rollup/plugin-commonjs@npm:^29.0.0, @rollup/plugin-commonjs@npm:^29.0.3": +"@rollup/plugin-commonjs@npm:^29.0.3": version: 29.0.3 resolution: "@rollup/plugin-commonjs@npm:29.0.3" dependencies: @@ -2062,42 +1341,17 @@ __metadata: languageName: node linkType: hard -"@sinclair/typebox@npm:^0.34.0": - version: 0.34.49 - resolution: "@sinclair/typebox@npm:0.34.49" - checksum: 10c0/16b7d87f039a49b68c10bb4cdcae2ce5242b2472228851fd6483731616aba4ef977690aa517b230a8d20da8185bb416eb34e326f30568b3963c1cf26b05d1ad8 - languageName: node - linkType: hard - -"@sinonjs/commons@npm:^3.0.1": - version: 3.0.1 - resolution: "@sinonjs/commons@npm:3.0.1" - dependencies: - type-detect: "npm:4.0.8" - checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 - languageName: node - linkType: hard - -"@sinonjs/fake-timers@npm:^15.4.0": - version: 15.4.0 - resolution: "@sinonjs/fake-timers@npm:15.4.0" - dependencies: - "@sinonjs/commons": "npm:^3.0.1" - checksum: 10c0/de4522afe0699fa8d3ae9d1715cbaa4b47e518c707bb7988a9ec6c7c67557d9f6df451f6be0338598b984a86f65aab9fab38dd9ce75a3c0ffb801a9500d5b10d - languageName: node - linkType: hard - "@sourceacademy/common-autocomplete@npm:^0.0.1, @sourceacademy/common-autocomplete@workspace:src/common/autocomplete": version: 0.0.0-use.local resolution: "@sourceacademy/common-autocomplete@workspace:src/common/autocomplete" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" languageName: unknown linkType: soft @@ -2105,14 +1359,14 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/common-cse-machine@workspace:src/common/cse-machine" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" - "@vitest/coverage-istanbul": "npm:^5.0.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" + "@vitest/coverage-istanbul": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" languageName: unknown linkType: soft @@ -2120,15 +1374,13 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/common-data-visualizer@workspace:src/common/data-visualizer" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" - "@types/jest": "npm:^30.0.0" - jest: "npm:^30.4.2" - rollup: "npm:^4.60.2" - ts-jest: "npm:^29.4.11" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" languageName: unknown linkType: soft @@ -2136,17 +1388,15 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/common-e-stepper@workspace:src/common/e-stepper" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-cse-machine": "workspace:*" "@sourceacademy/common-stepper": "workspace:*" - "@types/jest": "npm:^30.0.0" - jest: "npm:^30.4.2" - rollup: "npm:^4.60.2" - ts-jest: "npm:^29.4.11" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: "@sourceacademy/common-cse-machine": ">=0.4.0" "@sourceacademy/common-stepper": ">=0.0.4" @@ -2157,13 +1407,12 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/common-module-loader@workspace:src/common/module-loader" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" languageName: unknown linkType: soft @@ -2171,15 +1420,13 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/common-stepper@workspace:src/common/stepper" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" - "@types/jest": "npm:^30.0.0" - jest: "npm:^30.4.2" - rollup: "npm:^4.60.2" - ts-jest: "npm:^29.4.11" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" languageName: unknown linkType: soft @@ -2188,13 +1435,13 @@ __metadata: resolution: "@sourceacademy/common-tabs@workspace:src/common/tabs" dependencies: "@blueprintjs/icons": "npm:^6.0.0" - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@types/react": "npm:^19.2.17" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" peerDependencies: "@blueprintjs/icons": ^6.0.0 react: ^18 || ^19 @@ -2205,13 +1452,13 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/common-test@workspace:src/common/test" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^4.1.9" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" languageName: unknown linkType: soft @@ -2236,21 +1483,45 @@ __metadata: languageName: node linkType: hard +"@sourceacademy/plugins-workspace@workspace:.": + version: 0.0.0-use.local + resolution: "@sourceacademy/plugins-workspace@workspace:." + dependencies: + "@changesets/cli": "npm:^3.0.0" + "@commander-js/extra-typings": "npm:^15.0.0" + "@sourceacademy/conductor": "catalog:" + "@sourceacademy/plugin-directory": "source-academy/plugin-directory#main" + "@types/node": "npm:^25.9.1" + "@vitest/coverage-istanbul": "catalog:" + "@vitest/eslint-plugin": "npm:^1.6.26" + "@yarnpkg/types": "npm:^4.0.1" + commander: "npm:^15.0.0" + eslint: "npm:^10.0.3" + eslint-config-prettier: "npm:^10.1.8" + eslint-plugin-import-x: "npm:^4.17.1" + prettier: "npm:^3.8.1" + typescript: "catalog:" + typescript-eslint: "npm:^8.57.1" + vite: "npm:^8.0.0" + vitest: "catalog:" + languageName: unknown + linkType: soft + "@sourceacademy/runner-autocomplete@workspace:src/runner/autocomplete": version: 0.0.0-use.local resolution: "@sourceacademy/runner-autocomplete@workspace:src/runner/autocomplete" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-autocomplete": "npm:^0.0.1" - "@sourceacademy/conductor": "npm:^0.8.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + "@sourceacademy/conductor": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2258,19 +1529,19 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/runner-cse-machine@workspace:src/runner/cse-machine" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-cse-machine": "workspace:*" - "@sourceacademy/conductor": "npm:^0.8.0" - "@vitest/coverage-istanbul": "npm:^5.0.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + "@sourceacademy/conductor": "catalog:" + "@vitest/coverage-istanbul": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: "@sourceacademy/common-cse-machine": ">=0.4.0" - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2278,19 +1549,17 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/runner-data-visualizer@workspace:src/runner/data-visualizer" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-data-visualizer": "workspace:*" - "@sourceacademy/conductor": "npm:^0.8.0" - "@types/jest": "npm:^30.0.0" - jest: "npm:^30.4.2" - rollup: "npm:^4.60.2" - ts-jest: "npm:^29.4.11" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + "@sourceacademy/conductor": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2298,19 +1567,17 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/runner-e-stepper@workspace:src/runner/e-stepper" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-e-stepper": "workspace:*" "@sourceacademy/common-stepper": "workspace:*" - "@sourceacademy/conductor": "npm:^0.8.0" + "@sourceacademy/conductor": "catalog:" "@sourceacademy/runner-stepper": "workspace:*" - "@types/jest": "npm:^30.0.0" - jest: "npm:^30.4.2" - rollup: "npm:^4.60.2" - ts-jest: "npm:^29.4.11" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: "@sourceacademy/common-e-stepper": ">=0.0.0" "@sourceacademy/common-stepper": ">=0.0.4" @@ -2323,19 +1590,19 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/runner-module-loader@workspace:src/runner/module-loader" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-module-loader": "workspace:*" - "@sourceacademy/conductor": "npm:^0.8.0" + "@sourceacademy/conductor": "catalog:" "@sourceacademy/runner-remote-execution": "workspace:*" - "@vitest/coverage-istanbul": "npm:^5.0.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + "@vitest/coverage-istanbul": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2343,19 +1610,19 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/runner-remote-execution@workspace:src/runner/remoteExecution" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-test": "workspace:*" - "@sourceacademy/conductor": "npm:^0.8.0" - "@vitest/coverage-istanbul": "npm:^5.0.0" + "@sourceacademy/conductor": "catalog:" + "@vitest/coverage-istanbul": "catalog:" py-slang: "github:proto-aiken-13/py-slang#branch-ev3-engine" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2363,19 +1630,17 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/runner-stepper@workspace:src/runner/stepper" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-stepper": "workspace:*" - "@sourceacademy/conductor": "npm:^0.8.0" - "@types/jest": "npm:^30.0.0" - jest: "npm:^30.4.2" - rollup: "npm:^4.60.2" - ts-jest: "npm:^29.4.11" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + "@sourceacademy/conductor": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2383,20 +1648,20 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/runner-test@workspace:src/runner/test" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-test": "workspace:*" - "@sourceacademy/conductor": "npm:^0.8.0" + "@sourceacademy/conductor": "catalog:" "@sourceacademy/runner-remote-execution": "workspace:*" "@vitest/coverage-istanbul": "npm:^4.1.9" py-slang: "github:proto-aiken-13/py-slang#branch-ev3-engine" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^4.1.9" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2411,18 +1676,18 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/web-autocomplete@workspace:src/web/autocomplete" dependencies: - "@rollup/plugin-commonjs": "npm:^29.0.3" - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-commonjs": "catalog:" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-autocomplete": "npm:^0.0.1" - "@sourceacademy/conductor": "npm:^0.8.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + "@sourceacademy/conductor": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2430,19 +1695,19 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/web-cse-machine@workspace:src/web/cse-machine" dependencies: - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-cse-machine": "workspace:*" - "@sourceacademy/conductor": "npm:^0.8.0" - "@vitest/coverage-istanbul": "npm:^5.0.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + "@sourceacademy/conductor": "catalog:" + "@vitest/coverage-istanbul": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: "@sourceacademy/common-cse-machine": ">=0.4.0" - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2452,14 +1717,14 @@ __metadata: dependencies: "@blueprintjs/core": "npm:^6.0.0" "@blueprintjs/icons": "npm:^6.0.0" - "@rollup/plugin-commonjs": "npm:^29.0.0" - "@rollup/plugin-node-resolve": "npm:^16.0.3" + "@rollup/plugin-commonjs": "catalog:" + "@rollup/plugin-node-resolve": "catalog:" "@rollup/plugin-replace": "npm:^6.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-data-visualizer": "workspace:*" "@sourceacademy/common-tabs": "workspace:^" - "@sourceacademy/conductor": "npm:^0.8.0" + "@sourceacademy/conductor": "catalog:" "@types/react": "npm:^19.0.0" "@types/react-test-renderer": "npm:^19.0.0" konva: "npm:^10.0.0" @@ -2467,9 +1732,10 @@ __metadata: react-dom: "npm:^19.0.0" react-konva: "npm:^19.0.0" react-test-renderer: "npm:^19.0.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: "@blueprintjs/core": ">=5" konva: ">=9" @@ -2485,16 +1751,16 @@ __metadata: dependencies: "@blueprintjs/core": "npm:^6.0.0" "@blueprintjs/icons": "npm:^6.0.0" - "@rollup/plugin-commonjs": "npm:^29.0.0" - "@rollup/plugin-node-resolve": "npm:^16.0.3" + "@rollup/plugin-commonjs": "catalog:" + "@rollup/plugin-node-resolve": "catalog:" "@rollup/plugin-replace": "npm:^6.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-cse-machine": "workspace:*" "@sourceacademy/common-e-stepper": "workspace:*" "@sourceacademy/common-stepper": "workspace:*" "@sourceacademy/common-tabs": "workspace:^" - "@sourceacademy/conductor": "npm:^0.8.0" + "@sourceacademy/conductor": "catalog:" "@types/react": "npm:^19.0.0" "@types/react-test-renderer": "npm:^19.0.0" classnames: "npm:^2.3.2" @@ -2503,9 +1769,10 @@ __metadata: react-dom: "npm:^19.0.0" react-konva: "npm:^19.0.0" react-test-renderer: "npm:^19.0.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: "@blueprintjs/core": ">=5" konva: ">=9" @@ -2519,19 +1786,19 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/web-module-loader@workspace:src/web/module-loader" dependencies: - "@rollup/plugin-commonjs": "npm:^29.0.3" - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-commonjs": "catalog:" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-module-loader": "workspace:*" "@sourceacademy/common-tabs": "workspace:^" - "@sourceacademy/conductor": "npm:^0.8.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^5.0.0" + "@sourceacademy/conductor": "catalog:" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" languageName: unknown linkType: soft @@ -2542,22 +1809,21 @@ __metadata: "@blueprintjs/core": "npm:^6.0.0" "@blueprintjs/icons": "npm:^6.0.0" "@mantine/hooks": "npm:^9.0.0" - "@rollup/plugin-commonjs": "npm:^29.0.0" - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-commonjs": "catalog:" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-stepper": "workspace:*" "@sourceacademy/common-tabs": "workspace:^" - "@sourceacademy/conductor": "npm:^0.8.0" + "@sourceacademy/conductor": "catalog:" "@types/react": "npm:^19.0.0" - "@types/react-test-renderer": "npm:^19.0.0" + "@types/react-dom": "npm:^19.0.0" classnames: "npm:^2.3.2" react: "npm:^19.0.0" react-dom: "npm:^19.0.0" - react-test-renderer: "npm:^19.0.0" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" peerDependencies: "@blueprintjs/core": ">=5" react: ">=18" @@ -2569,33 +1835,26 @@ __metadata: version: 0.0.0-use.local resolution: "@sourceacademy/web-test@workspace:src/web/test" dependencies: - "@rollup/plugin-commonjs": "npm:^29.0.3" - "@rollup/plugin-node-resolve": "npm:^16.0.3" - "@rollup/plugin-terser": "npm:^1.0.0" - "@rollup/plugin-typescript": "npm:^12.3.0" + "@rollup/plugin-commonjs": "catalog:" + "@rollup/plugin-node-resolve": "catalog:" + "@rollup/plugin-terser": "catalog:" + "@rollup/plugin-typescript": "catalog:" "@sourceacademy/common-tabs": "workspace:^" "@sourceacademy/common-test": "workspace:*" - "@sourceacademy/conductor": "npm:^0.8.0" + "@sourceacademy/conductor": "catalog:" "@types/react": "npm:^19.2.17" - rollup: "npm:^4.60.2" - tslib: "npm:^2.8.1" - typescript: "npm:^6.0.3" - vitest: "npm:^4.1.9" + rollup: "catalog:" + tslib: "catalog:" + typescript: "catalog:" + vitest: "catalog:" peerDependencies: "@sourceacademy/common-tabs": ^0.0.1 - "@sourceacademy/conductor": ^0.8.0 + "@sourceacademy/conductor": "*" react: ^19 react-dom: ^19 languageName: unknown linkType: soft -"@standard-schema/spec@npm:^1.1.0": - version: 1.1.0 - resolution: "@standard-schema/spec@npm:1.1.0" - checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 - languageName: node - linkType: hard - "@tybys/wasm-util@npm:^0.10.2": version: 0.10.2 resolution: "@tybys/wasm-util@npm:0.10.2" @@ -2605,47 +1864,6 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:^7.20.5": - version: 7.20.5 - resolution: "@types/babel__core@npm:7.20.5" - dependencies: - "@babel/parser": "npm:^7.20.7" - "@babel/types": "npm:^7.20.7" - "@types/babel__generator": "npm:*" - "@types/babel__template": "npm:*" - "@types/babel__traverse": "npm:*" - checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff - languageName: node - linkType: hard - -"@types/babel__generator@npm:*": - version: 7.27.0 - resolution: "@types/babel__generator@npm:7.27.0" - dependencies: - "@babel/types": "npm:^7.0.0" - checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd - languageName: node - linkType: hard - -"@types/babel__template@npm:*": - version: 7.4.4 - resolution: "@types/babel__template@npm:7.4.4" - dependencies: - "@babel/parser": "npm:^7.1.0" - "@babel/types": "npm:^7.0.0" - checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b - languageName: node - linkType: hard - -"@types/babel__traverse@npm:*": - version: 7.28.0 - resolution: "@types/babel__traverse@npm:7.28.0" - dependencies: - "@babel/types": "npm:^7.28.2" - checksum: 10c0/b52d7d4e8fc6a9018fe7361c4062c1c190f5778cf2466817cb9ed19d69fbbb54f9a85ffedeb748ed8062d2cf7d4cc088ee739848f47c57740de1c48cbf0d0994 - languageName: node - linkType: hard - "@types/chai@npm:^5.2.2": version: 5.2.3 resolution: "@types/chai@npm:5.2.3" @@ -2691,41 +1909,6 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.1, @types/istanbul-lib-coverage@npm:^2.0.6": - version: 2.0.6 - resolution: "@types/istanbul-lib-coverage@npm:2.0.6" - checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 - languageName: node - linkType: hard - -"@types/istanbul-lib-report@npm:*": - version: 3.0.3 - resolution: "@types/istanbul-lib-report@npm:3.0.3" - dependencies: - "@types/istanbul-lib-coverage": "npm:*" - checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c - languageName: node - linkType: hard - -"@types/istanbul-reports@npm:^3.0.4": - version: 3.0.4 - resolution: "@types/istanbul-reports@npm:3.0.4" - dependencies: - "@types/istanbul-lib-report": "npm:*" - checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee - languageName: node - linkType: hard - -"@types/jest@npm:^30.0.0": - version: 30.0.0 - resolution: "@types/jest@npm:30.0.0" - dependencies: - expect: "npm:^30.0.0" - pretty-format: "npm:^30.0.0" - checksum: 10c0/20c6ce574154bc16f8dd6a97afacca4b8c4921a819496a3970382031c509ebe87a1b37b152a1b8475089b82d8ca951a9e95beb4b9bf78fbf579b1536f0b65969 - languageName: node - linkType: hard - "@types/jsesc@npm:^2.5.0": version: 2.5.1 resolution: "@types/jsesc@npm:2.5.1" @@ -2740,21 +1923,21 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*": - version: 26.6.4 - resolution: "@types/node@npm:26.6.4" +"@types/node@npm:^25.9.1": + version: 25.9.1 + resolution: "@types/node@npm:25.9.1" dependencies: - undici-types: "npm:~8.9.0" - checksum: 10c0/094e7362df9200ba4c2da09131ca3b5d1aa51535a268a24e4ee2546d9c5a323d1d30cbdaaaa3db7ef8b2387572d1f9edabb11538ab7a09d3bca33ae5cddc2b1b + undici-types: "npm:>=7.24.0 <7.24.7" + checksum: 10c0/9a04682842bebbcf21a1779dfeab9aa733d7bd7bbc0a0edb641ab3a9a3d43eac543225acf669c334f458f1956443ebc072bc3c72840c543b8b356cab5c82d456 languageName: node linkType: hard -"@types/node@npm:^25.9.1": - version: 25.9.9 - resolution: "@types/node@npm:25.9.9" - dependencies: - undici-types: "npm:>=7.24.0 <7.24.7" - checksum: 10c0/2ba08caa7118af2f2761f2f5db9b6304faf5bd027a2dcb72bf6fcae3dd9e470d17045fec27f89023c02e7d4e0661c6e5877174bd39794bef080db8b35ad4a1ea +"@types/react-dom@npm:^19.0.0": + version: 19.2.4 + resolution: "@types/react-dom@npm:19.2.4" + peerDependencies: + "@types/react": ^19.2.0 + checksum: 10c0/b7d854ce17bb51a3a067168268a90f0123d10dc490f63f1ff5409f07ac715febeb8f5b7b473404a9d437106571e0312fc2937a945634d590abfc9aa46a14b01b languageName: node linkType: hard @@ -2801,128 +1984,144 @@ __metadata: languageName: node linkType: hard -"@types/stack-utils@npm:^2.0.3": - version: 2.0.3 - resolution: "@types/stack-utils@npm:2.0.3" - checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c - languageName: node - linkType: hard - -"@types/yargs-parser@npm:*": - version: 21.0.3 - resolution: "@types/yargs-parser@npm:21.0.3" - checksum: 10c0/e71c3bd9d0b73ca82e10bee2064c384ab70f61034bbfb78e74f5206283fc16a6d85267b606b5c22cb2a3338373586786fed595b2009825d6a9115afba36560a0 - languageName: node - linkType: hard - -"@types/yargs@npm:^17.0.33": - version: 17.0.35 - resolution: "@types/yargs@npm:17.0.35" - dependencies: - "@types/yargs-parser": "npm:*" - checksum: 10c0/609557826a6b85e73ccf587923f6429850d6dc70e420b455bab4601b670bfadf684b09ae288bccedab042c48ba65f1666133cf375814204b544009f57d6eef63 - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.71.1" +"@typescript-eslint/eslint-plugin@npm:8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.65.0" dependencies: "@eslint-community/regexpp": "npm:^4.12.2" - "@typescript-eslint/scope-manager": "npm:8.71.1" - "@typescript-eslint/type-utils": "npm:8.71.1" - "@typescript-eslint/utils": "npm:8.71.1" - "@typescript-eslint/visitor-keys": "npm:8.71.1" + "@typescript-eslint/scope-manager": "npm:8.65.0" + "@typescript-eslint/type-utils": "npm:8.65.0" + "@typescript-eslint/utils": "npm:8.65.0" + "@typescript-eslint/visitor-keys": "npm:8.65.0" ignore: "npm:^7.0.5" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.5.0" peerDependencies: - "@typescript-eslint/parser": ^8.71.1 + "@typescript-eslint/parser": ^8.65.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/07ec42a92acf2e4f8bf8ea90390baced45778db5182899ae25bb750868b7a59b019b0b83701c4ee5f852da2a556fed1cafd254c9014b2f58897d242b4d10f25e + checksum: 10c0/1d9ddad417b43037c35c91a7c30bfc0015ccc40da57fe9faadae97fbaee6031a539a35265a9933d3bb946f307c397b7a00953806339576a721d619695a972079 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/parser@npm:8.71.1" +"@typescript-eslint/parser@npm:8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/parser@npm:8.65.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.71.1" - "@typescript-eslint/types": "npm:8.71.1" - "@typescript-eslint/typescript-estree": "npm:8.71.1" - "@typescript-eslint/visitor-keys": "npm:8.71.1" + "@typescript-eslint/scope-manager": "npm:8.65.0" + "@typescript-eslint/types": "npm:8.65.0" + "@typescript-eslint/typescript-estree": "npm:8.65.0" + "@typescript-eslint/visitor-keys": "npm:8.65.0" debug: "npm:^4.4.3" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/2247cff6c945b47e4cd4de01ed4307195beb12dab82e41fbdbac2bad0be9604872ebe96e9ba1fd964f90db04a92f0d2617971ed1fe5e74345ca9effffa9ad49b + checksum: 10c0/b3f5333abe5dfb08b53b89c824d045d96d5eb5798fab45eeedfaab72e4ce133a82fb4ebbc1acf79098aac9b08f7acc119fe0edd63ac2f91d80c98f5ca87c41e0 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/project-service@npm:8.71.1" +"@typescript-eslint/project-service@npm:8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/project-service@npm:8.65.0" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.71.1" - "@typescript-eslint/types": "npm:^8.71.1" + "@typescript-eslint/tsconfig-utils": "npm:^8.65.0" + "@typescript-eslint/types": "npm:^8.65.0" debug: "npm:^4.4.3" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/89c6c4dd3a6a77ec4c00e02291e7a9502db779b414dc688a019c0947f2ba0c911ed3e286d347842982c619f103611931efdc2c689578dd8e174c3accfc5aaffc + checksum: 10c0/56d8f598f1bb6ca892ff79320bd1aa134eefa05cf0bad4932c44518475a8bccf0c9027ae2177b3c1761496c8107236b81dd93f67d09093c2242f07f3f2063c1f languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/scope-manager@npm:8.71.1" +"@typescript-eslint/project-service@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/project-service@npm:8.66.0" dependencies: - "@typescript-eslint/types": "npm:8.71.1" - "@typescript-eslint/visitor-keys": "npm:8.71.1" - checksum: 10c0/724c421e92b3fc5554cbb7b7c644f68f2ae2bd2e487b0cd7a6b2e94bf2fcf684ff445214432f741a09a612bf82f68e5427a9ccb376682ed7861961ca86f08973 + "@typescript-eslint/tsconfig-utils": "npm:^8.66.0" + "@typescript-eslint/types": "npm:^8.66.0" + debug: "npm:^4.4.3" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/1b8129da708262104691091ac09f67fca2dd877d99090472fd5f0cf86b05755cb3df11d98ed003e775864b9f015c01b429fd5553adbb8a94dd047ff83d4ad6b3 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/scope-manager@npm:8.65.0" + dependencies: + "@typescript-eslint/types": "npm:8.65.0" + "@typescript-eslint/visitor-keys": "npm:8.65.0" + checksum: 10c0/303bae83a2243e742fcf86bef0b67615dc8915d2dd40268b796e2f49a40057b05de41608846aa362ad77e2d6976ec3cf30f1cdf245c1e39d18fd8ce91ae38c5c + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.66.0, @typescript-eslint/scope-manager@npm:^8.58.0": + version: 8.66.0 + resolution: "@typescript-eslint/scope-manager@npm:8.66.0" + dependencies: + "@typescript-eslint/types": "npm:8.66.0" + "@typescript-eslint/visitor-keys": "npm:8.66.0" + checksum: 10c0/247709b8712295650b8145050c3d566a71ebdca0a10477766cf369f35c9f7fdf7aa62ebb3b3c05fad3ae13c867196c9ff04ede7b1e7b8a6f60acf550207095d4 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.65.0, @typescript-eslint/tsconfig-utils@npm:^8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.65.0" + peerDependencies: + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/5b897bbba4584ee67c7a0bc0b4b6cbf8db2cb5997d5ab8f07fae692315dd51cabcc7116c609a94a162747dd267118b7a0f1c8fb29d71210ad38549e4b8b6adb1 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.71.1, @typescript-eslint/tsconfig-utils@npm:^8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.71.1" +"@typescript-eslint/tsconfig-utils@npm:8.66.0, @typescript-eslint/tsconfig-utils@npm:^8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.66.0" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/134ea5db1d3bd1ef40724921180e82926e6617704d655810e2d914447b3586b326b8567fe9a5f08241bb7d8756ab03db97bca81d1b9d8a5da357932c8f0e2733 + checksum: 10c0/1267e1f0adf822062c3917d717f0d1e463e46fdf2d9d0afd6b99b4498f27e63597296cf7ed4a5fe24c3311afb5458d54ccd369d5acee0f51017814384cfd1983 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/type-utils@npm:8.71.1" +"@typescript-eslint/type-utils@npm:8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/type-utils@npm:8.65.0" dependencies: - "@typescript-eslint/types": "npm:8.71.1" - "@typescript-eslint/typescript-estree": "npm:8.71.1" - "@typescript-eslint/utils": "npm:8.71.1" + "@typescript-eslint/types": "npm:8.65.0" + "@typescript-eslint/typescript-estree": "npm:8.65.0" + "@typescript-eslint/utils": "npm:8.65.0" debug: "npm:^4.4.3" ts-api-utils: "npm:^2.5.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/38e9f5a9aa173526f05407612c26cdf779caf14295ce873cfb7f13d2236927b895d243ff92716047d7d2e2dc20f636103551a280a2579e7f505fbe35b61a6651 + checksum: 10c0/05a1a1283020f63eac3cb6202afd08459531a4522a85ceb0f5789f2902df7c180565f65f217cc780127888b7113b1c8c34aa6bfd7020d568f01a17f290216e9b languageName: node linkType: hard -"@typescript-eslint/types@npm:8.71.1, @typescript-eslint/types@npm:^8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/types@npm:8.71.1" - checksum: 10c0/409c18e0a2331b1d5b743940b3e4468e54b0d37e454291204eefd9dbe77dbc8f8bbae9416262a9c8733dd95ec1feea9eca4a7651dce813f28629401b789b2ded +"@typescript-eslint/types@npm:8.65.0, @typescript-eslint/types@npm:^8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/types@npm:8.65.0" + checksum: 10c0/919b6d96111ea26f09c6cb1121fbba915147761be3fbf9c4de5b200faa2891087ebdcfd2f4a1f27a4c6153daeefc7448147c651a074b3ec70440f3f8c1092a81 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.71.1" +"@typescript-eslint/types@npm:8.66.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/types@npm:8.66.0" + checksum: 10c0/cc3f77a9a4e2ee997f0b660e8d31d2e45f3b4a16000f8d4349c85cd50b6a902b71272055116cc01f9cfae063a9d31e8b533e6051334fb345fdf753805014c6b5 + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.65.0" dependencies: - "@typescript-eslint/project-service": "npm:8.71.1" - "@typescript-eslint/tsconfig-utils": "npm:8.71.1" - "@typescript-eslint/types": "npm:8.71.1" - "@typescript-eslint/visitor-keys": "npm:8.71.1" + "@typescript-eslint/project-service": "npm:8.65.0" + "@typescript-eslint/tsconfig-utils": "npm:8.65.0" + "@typescript-eslint/types": "npm:8.65.0" + "@typescript-eslint/visitor-keys": "npm:8.65.0" debug: "npm:^4.4.3" minimatch: "npm:^10.2.2" semver: "npm:^7.7.3" @@ -2930,40 +2129,77 @@ __metadata: ts-api-utils: "npm:^2.5.0" peerDependencies: typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/461cf2fef03aa9f6ad4042490553569956437be08bbbb05f88dcac3cba87ece55e724dbe1c1b6a1957b07dcd45a519311dbb1788d8778e9cf9ec66dca0fc4e82 + checksum: 10c0/578ff5247f17865277badfe13362a82ba82c8bb11aaa78323933895e0ba0fc02788ae6aa9bd84bc8287660004a76231341977a3188b399c776b7e713c5054239 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/utils@npm:8.71.1" +"@typescript-eslint/typescript-estree@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.66.0" dependencies: - "@eslint-community/eslint-utils": "npm:^4.9.1" - "@typescript-eslint/scope-manager": "npm:8.71.1" - "@typescript-eslint/types": "npm:8.71.1" - "@typescript-eslint/typescript-estree": "npm:8.71.1" + "@typescript-eslint/project-service": "npm:8.66.0" + "@typescript-eslint/tsconfig-utils": "npm:8.66.0" + "@typescript-eslint/types": "npm:8.66.0" + "@typescript-eslint/visitor-keys": "npm:8.66.0" + debug: "npm:^4.4.3" + minimatch: "npm:^10.2.2" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.5.0" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/47d4578d91c594902e88404b5abc188f2ca42158d354437bd93eb2b55e231a0eb177ba7d903bef064ff43f8ba16ef5b5ccde30a73b6c0c390fc9820216a9e47f + checksum: 10c0/4a755666340ea6f7d329efc402bf0628bf3085c26192b8b346e3641f1b1804c09254a705d0c29b85e2a730258e4675b1678b9f0d95cbbc3b6f5aefb95e6ff67f languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.71.1": - version: 8.71.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.71.1" +"@typescript-eslint/utils@npm:8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/utils@npm:8.65.0" dependencies: - "@typescript-eslint/types": "npm:8.71.1" - eslint-visitor-keys: "npm:^5.0.0" - checksum: 10c0/6cdb1b83bb6e4ad10bb3aba0b492ed87d9b31c20f085e325455b28af43bd7190abaceca7c12762738da72f4ed3f45d23762c36a5b9066fa1e449c96124464718 + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.65.0" + "@typescript-eslint/types": "npm:8.65.0" + "@typescript-eslint/typescript-estree": "npm:8.65.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/258775532bb23bfeccb7ef25369a62b5fcf48f633af178830113d5aea6d0e968ad67a63b3371206151eb6a8dca0a3381f7efa07958fd8063505e1a53e470a439 languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.3.0": - version: 1.3.2 - resolution: "@ungap/structured-clone@npm:1.3.2" - checksum: 10c0/4d76bb376ec3e15f38bdffe045377807c79057daf54ae17eeb977c5b95efddd2d726b38c15aeb5d5c1a45c64ad03aa7e8b1a6dc67895480cba536ffd1c7a06ec - languageName: node +"@typescript-eslint/utils@npm:^8.58.0": + version: 8.66.0 + resolution: "@typescript-eslint/utils@npm:8.66.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.66.0" + "@typescript-eslint/types": "npm:8.66.0" + "@typescript-eslint/typescript-estree": "npm:8.66.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: ">=4.8.4 <6.1.0" + checksum: 10c0/8bce52462ac7c42da7ec3967f1f30c0c9ae527471c6d95de9376a3b4e584bfbcceae7c69cf7df5d081481ccfde5f0682112e05ed4f8704916028a23024e965a0 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:8.65.0": + version: 8.65.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.65.0" + dependencies: + "@typescript-eslint/types": "npm:8.65.0" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/f50cf2da4077f8eebfd56658ede17dfbf2e39875dc53562f5c1bc6e9835a0b4b00e0e0255e2dc3488234aca1f783d89beb084c417b22027520bf015a7b59ffb8 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:8.66.0": + version: 8.66.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.66.0" + dependencies: + "@typescript-eslint/types": "npm:8.66.0" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/12fd739fa3da099145ed21cc3b81fce050a725117639f3e162be51d807b38d553688bdb62b3251c9bb2c9361bfcb4aa68beb0e0e3885620713aa6ede7ce231c7 + languageName: node linkType: hard "@unrs/resolver-binding-android-arm-eabi@npm:1.12.2": @@ -3125,8 +2361,8 @@ __metadata: linkType: hard "@vitest/coverage-istanbul@npm:^4.1.9": - version: 4.1.11 - resolution: "@vitest/coverage-istanbul@npm:4.1.11" + version: 4.1.10 + resolution: "@vitest/coverage-istanbul@npm:4.1.10" dependencies: "@babel/core": "npm:^7.29.0" "@istanbuljs/schema": "npm:^0.1.3" @@ -3139,8 +2375,8 @@ __metadata: obug: "npm:^2.1.1" tinyrainbow: "npm:^3.1.0" peerDependencies: - vitest: 4.1.11 - checksum: 10c0/9cba4007332dbfe7dfc71066e86a9ce462ea57399b1fba5d92786573e5cc8a1482ad1d5f4f011de84e2c888d0a3c448b0e810c7c40d5469550b72d55ec3e17fa + vitest: 4.1.10 + checksum: 10c0/a426b0aff94482587a100db7e8db4e238fabf9e5bc4d33a319bd9cd4261b8972a7b3d78f2bc7cbc3d11b0b58b5d7cc487f96ab35a96c1c766c54f9b6d5eb4f78 languageName: node linkType: hard @@ -3163,17 +2399,25 @@ __metadata: languageName: node linkType: hard -"@vitest/expect@npm:4.1.11": - version: 4.1.11 - resolution: "@vitest/expect@npm:4.1.11" +"@vitest/eslint-plugin@npm:^1.6.26": + version: 1.6.26 + resolution: "@vitest/eslint-plugin@npm:1.6.26" dependencies: - "@standard-schema/spec": "npm:^1.1.0" - "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.1.11" - "@vitest/utils": "npm:4.1.11" - chai: "npm:^6.2.2" - tinyrainbow: "npm:^3.1.0" - checksum: 10c0/0aa5e0973aca93a58cbdc3041c6bfed5897e976124965203b96a23b811f4ca403590c7eb15802c8d8366ec27a1db0682451e8a91c4d06617636de262baf86e4b + "@typescript-eslint/scope-manager": "npm:^8.58.0" + "@typescript-eslint/utils": "npm:^8.58.0" + peerDependencies: + "@typescript-eslint/eslint-plugin": "*" + eslint: ">=8.57.0" + typescript: ">=5.0.0" + vitest: "*" + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + typescript: + optional: true + vitest: + optional: true + checksum: 10c0/e1dd39f6ae26f7e53261f95ee5ec1cf6b9a8b9eabf0b908d3cf589bff30f86051ab6b86364dc3fdb11fce3684e90255173988a56091f778c4f77e3d75afbd62e languageName: node linkType: hard @@ -3217,25 +2461,6 @@ __metadata: languageName: node linkType: hard -"@vitest/mocker@npm:4.1.11": - version: 4.1.11 - resolution: "@vitest/mocker@npm:4.1.11" - dependencies: - "@vitest/spy": "npm:4.1.11" - estree-walker: "npm:^3.0.3" - magic-string: "npm:^0.30.21" - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - checksum: 10c0/3111ea34bd5046f6c70bbd67cf8b89608ee8c41cb27ab2bd61d42f4b68810e9ea16a9a757a71bc254c105f73b407d00ebb6bab1ab0f7f5cdcc9d7d16602a3933 - languageName: node - linkType: hard - "@vitest/mocker@npm:5.0.3": version: 5.0.3 resolution: "@vitest/mocker@npm:5.0.3" @@ -3256,44 +2481,6 @@ __metadata: languageName: node linkType: hard -"@vitest/pretty-format@npm:4.1.11": - version: 4.1.11 - resolution: "@vitest/pretty-format@npm:4.1.11" - dependencies: - tinyrainbow: "npm:^3.1.0" - checksum: 10c0/ad32525c73807c0b72f38dc29bc51fd5a17879dc650f37995a9c5adbb8526e15f787691f76aa8768448ec7ed5bf5ba2b12329eac8fda1428b4d6b8c036390f71 - languageName: node - linkType: hard - -"@vitest/runner@npm:4.1.11": - version: 4.1.11 - resolution: "@vitest/runner@npm:4.1.11" - dependencies: - "@vitest/utils": "npm:4.1.11" - pathe: "npm:^2.0.3" - checksum: 10c0/3c782b055e9e688e1785f7c8937bd1669bad1b0e5758cb8b844f918f30a324b1d21e174d46c941ce80ebf37f2b89b55b6d619a675025914a1ece6377b95909e2 - languageName: node - linkType: hard - -"@vitest/snapshot@npm:4.1.11": - version: 4.1.11 - resolution: "@vitest/snapshot@npm:4.1.11" - dependencies: - "@vitest/pretty-format": "npm:4.1.11" - "@vitest/utils": "npm:4.1.11" - magic-string: "npm:^0.30.21" - pathe: "npm:^2.0.3" - checksum: 10c0/35d82a7c2a3e4b57529c30387d568d9106b6bf960189214e5d8cb1f5288cb042305c2e5c7b0b2f8cb56a2c814973de0310bc56d72deb79427bea272b6224190c - languageName: node - linkType: hard - -"@vitest/spy@npm:4.1.11": - version: 4.1.11 - resolution: "@vitest/spy@npm:4.1.11" - checksum: 10c0/06c68247a8efd21006abe7532fee17f30ba83c8cda3e0b952ef89e278d58058f4b1de69b6bbaa2ed614c568bf4f5769fcc76be42802dedf2bcdd9c0aae601740 - languageName: node - linkType: hard - "@vitest/spy@npm:5.0.3": version: 5.0.3 resolution: "@vitest/spy@npm:5.0.3" @@ -3301,14 +2488,12 @@ __metadata: languageName: node linkType: hard -"@vitest/utils@npm:4.1.11": - version: 4.1.11 - resolution: "@vitest/utils@npm:4.1.11" +"@yarnpkg/types@npm:^4.0.1": + version: 4.0.1 + resolution: "@yarnpkg/types@npm:4.0.1" dependencies: - "@vitest/pretty-format": "npm:4.1.11" - convert-source-map: "npm:^2.0.0" - tinyrainbow: "npm:^3.1.0" - checksum: 10c0/a2c1ddc64333458c3e031465c1ee0440a7660fd1b298c54ccbf865ef1e5cf3ebdd6c5c75a5ea235d8a672498b394e7121f992b096f021f45014ab25e9abd1b52 + tslib: "npm:^2.4.0" + checksum: 10c0/90226789475680ba599833571dd76c0718dd5b4c5022481263ef309d6a628f6246671cd6ca86e49c966ddefa7aca6ccef82240dc1476d2cea702ea5bee2a6b72 languageName: node linkType: hard @@ -3349,71 +2534,6 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.3.2": - version: 4.3.2 - resolution: "ansi-escapes@npm:4.3.2" - dependencies: - type-fest: "npm:^0.21.3" - checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 - languageName: node - linkType: hard - -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 - languageName: node - linkType: hard - -"ansi-regex@npm:^6.2.2": - version: 6.2.2 - resolution: "ansi-regex@npm:6.2.2" - checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 - languageName: node - linkType: hard - -"ansi-styles@npm:^5.2.0": - version: 5.2.0 - resolution: "ansi-styles@npm:5.2.0" - checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df - languageName: node - linkType: hard - -"ansi-styles@npm:^6.1.0": - version: 6.2.3 - resolution: "ansi-styles@npm:6.2.3" - checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 - languageName: node - linkType: hard - -"anymatch@npm:^3.1.3": - version: 3.1.3 - resolution: "anymatch@npm:3.1.3" - dependencies: - normalize-path: "npm:^3.0.0" - picomatch: "npm:^2.0.4" - checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac - languageName: node - linkType: hard - -"argparse@npm:^1.0.7": - version: 1.0.10 - resolution: "argparse@npm:1.0.10" - dependencies: - sprintf-js: "npm:~1.0.2" - checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de - languageName: node - linkType: hard - "assertion-error@npm:^2.0.1": version: 2.0.1 resolution: "assertion-error@npm:2.0.1" @@ -3421,89 +2541,6 @@ __metadata: languageName: node linkType: hard -"babel-jest@npm:30.4.1": - version: 30.4.1 - resolution: "babel-jest@npm:30.4.1" - dependencies: - "@jest/transform": "npm:30.4.1" - "@types/babel__core": "npm:^7.20.5" - babel-plugin-istanbul: "npm:^7.0.1" - babel-preset-jest: "npm:30.4.0" - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - slash: "npm:^3.0.0" - peerDependencies: - "@babel/core": ^7.11.0 || ^8.0.0-0 - checksum: 10c0/339b449011f31dc9eb18d9c49f0bb84e8de284e1107e64159a2f4a432bbd532d6a729774a56b7fbe76f5ddd716a0b4b7ad737265feab23b4d0225489b79a6f72 - languageName: node - linkType: hard - -"babel-plugin-istanbul@npm:^7.0.1": - version: 7.0.1 - resolution: "babel-plugin-istanbul@npm:7.0.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.0.0" - "@istanbuljs/load-nyc-config": "npm:^1.0.0" - "@istanbuljs/schema": "npm:^0.1.3" - istanbul-lib-instrument: "npm:^6.0.2" - test-exclude: "npm:^6.0.0" - checksum: 10c0/92975e3df12503b168695463b451468da0c20e117807221652eb8e33a26c160f3b9d4c5c4e65495657420e871c6a54e5e31f539e2e1da37ef2261d7ddd4b1dfd - languageName: node - linkType: hard - -"babel-plugin-jest-hoist@npm:30.4.0": - version: 30.4.0 - resolution: "babel-plugin-jest-hoist@npm:30.4.0" - dependencies: - "@types/babel__core": "npm:^7.20.5" - checksum: 10c0/1738ed536bb5ff536b4d406b8db7dbbd76cf10f80bb20d902e6efdda79898f045b9a991124d7104d8c398d0bd995d511d57694952645fba0f6250595a45277b0 - languageName: node - linkType: hard - -"babel-preset-current-node-syntax@npm:^1.2.0": - version: 1.2.0 - resolution: "babel-preset-current-node-syntax@npm:1.2.0" - dependencies: - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-bigint": "npm:^7.8.3" - "@babel/plugin-syntax-class-properties": "npm:^7.12.13" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" - "@babel/plugin-syntax-import-meta": "npm:^7.10.4" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0 || ^8.0.0-0 - checksum: 10c0/94a4f81cddf9b051045d08489e4fff7336292016301664c138cfa3d9ffe3fe2ba10a24ad6ae589fd95af1ac72ba0216e1653555c187e694d7b17be0c002bea10 - languageName: node - linkType: hard - -"babel-preset-jest@npm:30.4.0": - version: 30.4.0 - resolution: "babel-preset-jest@npm:30.4.0" - dependencies: - babel-plugin-jest-hoist: "npm:30.4.0" - babel-preset-current-node-syntax: "npm:^1.2.0" - peerDependencies: - "@babel/core": ^7.11.0 || ^8.0.0-beta.1 - checksum: 10c0/ca2623aa4d8bf82b1fd01e5724a87cea7f80ff089341cf12415e9ce4b10f74838ecc6c8a48921f421f90bcd44f7929c0ad300146082e2f400253adb97ab5eb3a - languageName: node - linkType: hard - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - "balanced-match@npm:^4.0.2": version: 4.0.4 resolution: "balanced-match@npm:4.0.4" @@ -3511,31 +2548,12 @@ __metadata: languageName: node linkType: hard -"baseline-browser-mapping@npm:^2.11.20": - version: 2.11.21 - resolution: "baseline-browser-mapping@npm:2.11.21" +"baseline-browser-mapping@npm:^2.10.12": + version: 2.10.25 + resolution: "baseline-browser-mapping@npm:2.10.25" bin: baseline-browser-mapping: dist/cli.cjs - checksum: 10c0/1110ca70b60de9fa673e6d71154cbb2752225c28e069266c2f455f5a26367efc0e5b47a52afba22c9ef1e06d56a6c81aa964dda1fbc66c487e65c18a7c1e11c4 - languageName: node - linkType: hard - -"brace-expansion@npm:^1.1.7": - version: 1.1.21 - resolution: "brace-expansion@npm:1.1.21" - dependencies: - balanced-match: "npm:^1.0.0" - concat-map: "npm:0.0.1" - checksum: 10c0/8f0a68a720f9cb28c2ecaea5cdaf15fafd686d6b6e4e722a81c9d4bd2f68c1fd0619731cac05875d842221eaf4d97d564d329a12eb3c4c22137502fb3c65be8b - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.2": - version: 2.1.1 - resolution: "brace-expansion@npm:2.1.1" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/63b5ddce608b70b50a76817c0526faf8ea67a9180073d88bb402f6bbc22a22da6b1dfac4f65efc53e5faa80222fb7d44bbf2fc638c3f55365975573f671d0ccb + checksum: 10c0/3955a01d1ca9487b23805b89250465911c0aa7aa1bd7b6207ab64257c945ad50e37d2d6d33b559e47e20cf1d3b1930fc15115bfbc340e84fd33c238a01bdebb6 languageName: node linkType: hard @@ -3548,36 +2566,27 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.24.0": - version: 4.28.9 - resolution: "browserslist@npm:4.28.9" - dependencies: - baseline-browser-mapping: "npm:^2.11.20" - caniuse-lite: "npm:^1.0.30001810" - electron-to-chromium: "npm:^1.5.420" - node-releases: "npm:^2.0.54" - update-browserslist-db: "npm:^1.3.2" - bin: - browserslist: cli.js - checksum: 10c0/c4c7457cccb188bb904bb84566ff2d09e6f45ccf48619333111e5d60dfad456522a64e3e4af7c7608bc171371fc006f32bcc296877b0586bf80fb9f60841a110 - languageName: node - linkType: hard - -"bs-logger@npm:^0.2.6": - version: 0.2.6 - resolution: "bs-logger@npm:0.2.6" +"brace-expansion@npm:^5.0.8": + version: 5.0.9 + resolution: "brace-expansion@npm:5.0.9" dependencies: - fast-json-stable-stringify: "npm:2.x" - checksum: 10c0/80e89aaaed4b68e3374ce936f2eb097456a0dddbf11f75238dbd53140b1e39259f0d248a5089ed456f1158984f22191c3658d54a713982f676709fbe1a6fa5a0 + balanced-match: "npm:^4.0.2" + checksum: 10c0/3dea38884a1c3c8b1c9c44a7402a0c76fca460f70cffb3127242b0b4cbf4472019e022ade021eec44838ff19f1dac2625dfd11dd459d7e1e055b0698a8d52fec languageName: node linkType: hard -"bser@npm:2.1.1": - version: 2.1.1 - resolution: "bser@npm:2.1.1" - dependencies: - node-int64: "npm:^0.4.0" - checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 +"browserslist@npm:^4.24.0": + version: 4.28.2 + resolution: "browserslist@npm:4.28.2" + dependencies: + baseline-browser-mapping: "npm:^2.10.12" + caniuse-lite: "npm:^1.0.30001782" + electron-to-chromium: "npm:^1.5.328" + node-releases: "npm:^2.0.36" + update-browserslist-db: "npm:^1.2.3" + bin: + browserslist: cli.js + checksum: 10c0/c0228b6330f785b7fa59d2d360124ec6d9322f96ed9f3ee1f873e33ecc9503a6f0ffc3b71191a28c4ff6e930b753b30043da1c33844a9548f3018d491f09ce60 languageName: node linkType: hard @@ -3595,26 +2604,6 @@ __metadata: languageName: node linkType: hard -"cacheable@npm:^2.5.0": - version: 2.5.0 - resolution: "cacheable@npm:2.5.0" - dependencies: - "@cacheable/memory": "npm:^2.2.0" - "@cacheable/utils": "npm:^2.5.0" - hookified: "npm:^1.15.0" - keyv: "npm:^5.6.0" - qified: "npm:^0.10.1" - checksum: 10c0/ad4086a60ed327196901c16360312705e35bfde5118457449cfabaf4ece0fecad046985ea8b66b3ece46dd469cb33798d87e1dac3ed2c00746578054bccbf74d - languageName: node - linkType: hard - -"callsites@npm:^3.1.0": - version: 3.1.0 - resolution: "callsites@npm:3.1.0" - checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 - languageName: node - linkType: hard - "camel-case@npm:^4.1.2": version: 4.1.2 resolution: "camel-case@npm:4.1.2" @@ -3625,24 +2614,10 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^5.3.1": - version: 5.3.1 - resolution: "camelcase@npm:5.3.1" - checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 - languageName: node - linkType: hard - -"camelcase@npm:^6.3.0": - version: 6.3.0 - resolution: "camelcase@npm:6.3.0" - checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001810": - version: 1.0.30001810 - resolution: "caniuse-lite@npm:1.0.30001810" - checksum: 10c0/d29bc73f33c888025d54c7ff2984372d3b350b15a6a9e174884fb40976175e9177f202e2a72c2d428dddfc032784bc8ba635a31d168fcab9fac65756d7b3d6b4 +"caniuse-lite@npm:^1.0.30001782": + version: 1.0.30001791 + resolution: "caniuse-lite@npm:1.0.30001791" + checksum: 10c0/53c8b5dad1c196a5e495405a7d2dc1db58aed9be02f60cf2a2e29d2c8d8cbb6c39beabf958d6aa191ab967ddcf49f22319452256b980bad1e271615acfe58940 languageName: node linkType: hard @@ -3664,16 +2639,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.1.2": - version: 4.1.2 - resolution: "chalk@npm:4.1.2" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 - languageName: node - linkType: hard - "change-case@npm:^4.1.2": version: 4.1.2 resolution: "change-case@npm:4.1.2" @@ -3694,13 +2659,6 @@ __metadata: languageName: node linkType: hard -"char-regex@npm:^1.0.2": - version: 1.0.2 - resolution: "char-regex@npm:1.0.2" - checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e - languageName: node - linkType: hard - "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -3708,20 +2666,6 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^4.2.0": - version: 4.4.0 - resolution: "ci-info@npm:4.4.0" - checksum: 10c0/44156201545b8dde01aa8a09ee2fe9fc7a73b1bef9adbd4606c9f61c8caeeb73fb7a575c88b0443f7b4edb5ee45debaa59ed54ba5f99698339393ca01349eb3a - languageName: node - linkType: hard - -"cjs-module-lexer@npm:^2.1.0": - version: 2.2.0 - resolution: "cjs-module-lexer@npm:2.2.0" - checksum: 10c0/aec4ca58f87145fac221386790ecaae8b012f2e2359a45acb61d8c75ea4fa84f6ea869f17abc1a7e91a808eff0fed581209632f03540de16f72f0a28f5fd35ac - languageName: node - linkType: hard - "classnames@npm:^2.3.1, classnames@npm:^2.3.2": version: 2.5.1 resolution: "classnames@npm:2.5.1" @@ -3729,47 +2673,6 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^8.0.1": - version: 8.0.1 - resolution: "cliui@npm:8.0.1" - dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.1" - wrap-ansi: "npm:^7.0.0" - checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 - languageName: node - linkType: hard - -"co@npm:^4.6.0": - version: 4.6.0 - resolution: "co@npm:4.6.0" - checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 - languageName: node - linkType: hard - -"collect-v8-coverage@npm:^1.0.2": - version: 1.0.3 - resolution: "collect-v8-coverage@npm:1.0.3" - checksum: 10c0/bc62ba251bcce5e3354a8f88fa6442bee56e3e612fec08d4dfcf66179b41ea0bf544b0f78c4ebc0f8050871220af95bb5c5578a6aef346feea155640582f09dc - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 - languageName: node - linkType: hard - -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 - languageName: node - linkType: hard - "commander@npm:^15.0.0": version: 15.0.0 resolution: "commander@npm:15.0.0" @@ -3784,6 +2687,13 @@ __metadata: languageName: node linkType: hard +"comment-parser@npm:^1.4.1": + version: 1.4.7 + resolution: "comment-parser@npm:1.4.7" + checksum: 10c0/09a8e6cc4a9f92e8828bbd8819d8b025cb1bf03d8d611e372627380f55b6401bb0009cf1b7940a028794f150891bfe855185b94173eb89f14b824e847335278d + languageName: node + linkType: hard + "commondir@npm:^1.0.1": version: 1.0.1 resolution: "commondir@npm:1.0.1" @@ -3798,13 +2708,6 @@ __metadata: languageName: node linkType: hard -"concat-map@npm:0.0.1": - version: 0.0.1 - resolution: "concat-map@npm:0.0.1" - checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f - languageName: node - linkType: hard - "constant-case@npm:^3.0.4": version: 3.0.4 resolution: "constant-case@npm:3.0.4" @@ -3823,7 +2726,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -3841,7 +2744,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.3": +"debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -3860,18 +2763,6 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.6.0": - version: 1.7.2 - resolution: "dedent@npm:1.7.2" - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - checksum: 10c0/acaff07cac355b93f17b1b17ebbb84d3cc55af6ab4b7814c3f505e061903e168bc6bf9ddce331552d64dee1525f0b4c549c9ade46aebfac6f69caaed74e90751 - languageName: node - linkType: hard - "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -3879,7 +2770,7 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1": +"deepmerge@npm:^4.2.2": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 @@ -3893,13 +2784,6 @@ __metadata: languageName: node linkType: hard -"detect-newline@npm:^3.1.0": - version: 3.1.0 - resolution: "detect-newline@npm:3.1.0" - checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d - languageName: node - linkType: hard - "discontinuous-range@npm:1.0.0": version: 1.0.0 resolution: "discontinuous-range@npm:1.0.0" @@ -3927,38 +2811,10 @@ __metadata: languageName: node linkType: hard -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 - languageName: node - linkType: hard - -"electron-to-chromium@npm:^1.5.420": - version: 1.5.422 - resolution: "electron-to-chromium@npm:1.5.422" - checksum: 10c0/bd388e0d3ab94e6fe792e27eab886071050f8f7575f1ec20e30e530733d1f8270eeff7a0e75e42c9405de54addd8df3d1f9d58b40e53f0a44fbe4fb8f1e7b930 - languageName: node - linkType: hard - -"emittery@npm:^0.13.1": - version: 0.13.1 - resolution: "emittery@npm:0.13.1" - checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 - languageName: node - linkType: hard - -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 - languageName: node - linkType: hard - -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 +"electron-to-chromium@npm:^1.5.328": + version: 1.5.349 + resolution: "electron-to-chromium@npm:1.5.349" + checksum: 10c0/5acd94d9c70b91383f95bc9c04f39b6f92263ef2b232f4f0d4c71d17dcd3db49ef480cd025c379f6d58b534cf568ca5cb566b0f9be238f057b9a5773d76760ec languageName: node linkType: hard @@ -3976,15 +2832,6 @@ __metadata: languageName: node linkType: hard -"error-ex@npm:^1.3.1": - version: 1.3.4 - resolution: "error-ex@npm:1.3.4" - dependencies: - is-arrayish: "npm:^0.2.1" - checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 - languageName: node - linkType: hard - "es-errors@npm:^1.3.0": version: 1.3.0 resolution: "es-errors@npm:1.3.0" @@ -3992,13 +2839,6 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^2.0.0": - version: 2.1.0 - resolution: "es-module-lexer@npm:2.1.0" - checksum: 10c0/93bcf2454fa72d67fe3ccd0abef8ce7933f5840a319513418a643dd8e9c6aa8f49709cecfae02ded722805dd327232d30723a807cc52e6809d6ac697c62c29fb - languageName: node - linkType: hard - "es-module-lexer@npm:^2.3.2": version: 2.3.2 resolution: "es-module-lexer@npm:2.3.2" @@ -4006,96 +2846,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:~0.28.0": - version: 0.28.1 - resolution: "esbuild@npm:0.28.1" - dependencies: - "@esbuild/aix-ppc64": "npm:0.28.1" - "@esbuild/android-arm": "npm:0.28.1" - "@esbuild/android-arm64": "npm:0.28.1" - "@esbuild/android-x64": "npm:0.28.1" - "@esbuild/darwin-arm64": "npm:0.28.1" - "@esbuild/darwin-x64": "npm:0.28.1" - "@esbuild/freebsd-arm64": "npm:0.28.1" - "@esbuild/freebsd-x64": "npm:0.28.1" - "@esbuild/linux-arm": "npm:0.28.1" - "@esbuild/linux-arm64": "npm:0.28.1" - "@esbuild/linux-ia32": "npm:0.28.1" - "@esbuild/linux-loong64": "npm:0.28.1" - "@esbuild/linux-mips64el": "npm:0.28.1" - "@esbuild/linux-ppc64": "npm:0.28.1" - "@esbuild/linux-riscv64": "npm:0.28.1" - "@esbuild/linux-s390x": "npm:0.28.1" - "@esbuild/linux-x64": "npm:0.28.1" - "@esbuild/netbsd-arm64": "npm:0.28.1" - "@esbuild/netbsd-x64": "npm:0.28.1" - "@esbuild/openbsd-arm64": "npm:0.28.1" - "@esbuild/openbsd-x64": "npm:0.28.1" - "@esbuild/openharmony-arm64": "npm:0.28.1" - "@esbuild/sunos-x64": "npm:0.28.1" - "@esbuild/win32-arm64": "npm:0.28.1" - "@esbuild/win32-ia32": "npm:0.28.1" - "@esbuild/win32-x64": "npm:0.28.1" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/29cd456a79ce35ac2c7e05fe871330416b2c395c045d849653f843e51378d6e0d6e774d6dcd01b35f4e83238a29bf8decd04fcd34b3780c589a250b21e5f92bb - languageName: node - linkType: hard - -"escalade@npm:^3.1.1, escalade@npm:^3.2.0": +"escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 @@ -4109,13 +2860,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^2.0.0": - version: 2.0.0 - resolution: "escape-string-regexp@npm:2.0.0" - checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 - languageName: node - linkType: hard - "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -4134,6 +2878,47 @@ __metadata: languageName: node linkType: hard +"eslint-import-context@npm:^0.1.9": + version: 0.1.9 + resolution: "eslint-import-context@npm:0.1.9" + dependencies: + get-tsconfig: "npm:^4.10.1" + stable-hash-x: "npm:^0.2.0" + peerDependencies: + unrs-resolver: ^1.0.0 + peerDependenciesMeta: + unrs-resolver: + optional: true + checksum: 10c0/07851103443b70af681c5988e2702e681ff9b956e055e11d4bd9b2322847fa0d9e8da50c18fc7cb1165106b043f34fbd0384d7011c239465c4645c52132e56f3 + languageName: node + linkType: hard + +"eslint-plugin-import-x@npm:^4.17.1": + version: 4.17.1 + resolution: "eslint-plugin-import-x@npm:4.17.1" + dependencies: + "@typescript-eslint/types": "npm:^8.56.0" + comment-parser: "npm:^1.4.1" + debug: "npm:^4.4.1" + eslint-import-context: "npm:^0.1.9" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.3 || ^10.1.2" + semver: "npm:^7.7.2" + stable-hash-x: "npm:^0.2.0" + unrs-resolver: "npm:^1.9.2" + peerDependencies: + "@typescript-eslint/utils": ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + eslint-import-resolver-node: "*" + peerDependenciesMeta: + "@typescript-eslint/utils": + optional: true + eslint-import-resolver-node: + optional: true + checksum: 10c0/4ea2ace22c3d805f2253063912211c438055f112ca9a2124df262311744975fc96443e4526fd80eef0acec7845a48fb6d1da444a4c4b0ff9786927fe2755f2a7 + languageName: node + linkType: hard + "eslint-scope@npm:^9.1.2": version: 9.1.2 resolution: "eslint-scope@npm:9.1.2" @@ -4161,15 +2946,15 @@ __metadata: linkType: hard "eslint@npm:^10.0.3": - version: 10.12.0 - resolution: "eslint@npm:10.12.0" + version: 10.8.0 + resolution: "eslint@npm:10.8.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.8.0" "@eslint-community/regexpp": "npm:^4.12.2" "@eslint/config-array": "npm:^0.23.5" "@eslint/config-helpers": "npm:^0.7.0" "@eslint/core": "npm:^1.2.1" - "@eslint/plugin-kit": "npm:^0.7.3" + "@eslint/plugin-kit": "npm:^0.7.2" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" @@ -4184,7 +2969,7 @@ __metadata: esquery: "npm:^1.7.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:11.1.5 || >11.1.6 <12" + file-entry-cache: "npm:^8.0.0" find-up: "npm:^5.0.0" glob-parent: "npm:^6.0.2" ignore: "npm:^5.2.0" @@ -4201,7 +2986,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/1302ce8a7df2f67fe8f4014b2f9449bd38eb86903ba7b841d9a8ca5a5daf8ee15442861ab870e96c0074d5f333b405d7b2f16388981f807db410cc3a02178af9 + checksum: 10c0/2dbc523578834088615f388e08418d2620b25655f7a398f6d415765fa2a3a25d6b8b43bd3293d40f977e12752323a841d52654a2648697a00c0102c9794bb3dc languageName: node linkType: hard @@ -4216,16 +3001,6 @@ __metadata: languageName: node linkType: hard -"esprima@npm:^4.0.0": - version: 4.0.1 - resolution: "esprima@npm:4.0.1" - bin: - esparse: ./bin/esparse.js - esvalidate: ./bin/esvalidate.js - checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 - languageName: node - linkType: hard - "esquery@npm:^1.7.0": version: 1.7.0 resolution: "esquery@npm:1.7.0" @@ -4274,37 +3049,6 @@ __metadata: languageName: node linkType: hard -"execa@npm:^5.1.1": - version: 5.1.1 - resolution: "execa@npm:5.1.1" - dependencies: - cross-spawn: "npm:^7.0.3" - get-stream: "npm:^6.0.0" - human-signals: "npm:^2.1.0" - is-stream: "npm:^2.0.0" - merge-stream: "npm:^2.0.0" - npm-run-path: "npm:^4.0.1" - onetime: "npm:^5.1.2" - signal-exit: "npm:^3.0.3" - strip-final-newline: "npm:^2.0.0" - checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f - languageName: node - linkType: hard - -"exit-x@npm:^0.2.2": - version: 0.2.2 - resolution: "exit-x@npm:0.2.2" - checksum: 10c0/212a7a095ca5540e9581f1ef2d1d6a40df7a6027c8cc96e78ce1d16b86d1a88326d4a0eff8dff2b5ec1e68bb0c1edd5d0dfdde87df1869bf7514d4bc6a5cbd72 - languageName: node - linkType: hard - -"expect-type@npm:^1.3.0": - version: 1.3.0 - resolution: "expect-type@npm:1.3.0" - checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd - languageName: node - linkType: hard - "expect-type@npm:^1.4.0": version: 1.4.0 resolution: "expect-type@npm:1.4.0" @@ -4312,20 +3056,6 @@ __metadata: languageName: node linkType: hard -"expect@npm:30.4.1, expect@npm:^30.0.0": - version: 30.4.1 - resolution: "expect@npm:30.4.1" - dependencies: - "@jest/expect-utils": "npm:30.4.1" - "@jest/get-type": "npm:30.1.0" - jest-matcher-utils: "npm:30.4.1" - jest-message-util: "npm:30.4.1" - jest-mock: "npm:30.4.1" - jest-util: "npm:30.4.1" - checksum: 10c0/ad04fbdffac5a2bae186478938a60f737e3aac823db9a80c87f3f390f9f458bddcc454dc3a3997d715706747c6aff928923e6a71db3a221adb89a51cc1582e72 - languageName: node - linkType: hard - "exponential-backoff@npm:^3.1.1": version: 3.1.3 resolution: "exponential-backoff@npm:3.1.3" @@ -4340,7 +3070,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": +"fast-json-stable-stringify@npm:^2.0.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b @@ -4395,15 +3125,6 @@ __metadata: languageName: node linkType: hard -"fb-watchman@npm:^2.0.2": - version: 2.0.2 - resolution: "fb-watchman@npm:2.0.2" - dependencies: - bser: "npm:2.1.1" - checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 - languageName: node - linkType: hard - "fdir@npm:^6.2.0, fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -4416,22 +3137,12 @@ __metadata: languageName: node linkType: hard -"file-entry-cache@npm:11.1.5 || >11.1.6 <12": - version: 11.1.5 - resolution: "file-entry-cache@npm:11.1.5" - dependencies: - flat-cache: "npm:^6.1.23" - checksum: 10c0/d05eca8794b27824d79a0f1a0bc76b81ca7232b6c9634bc086f365a6d9ddac749c4826b2b2d36bb7ef9ed84fdf0097f34979527daeb9255b4f17673a4ae50fc5 - languageName: node - linkType: hard - -"find-up@npm:^4.0.0, find-up@npm:^4.1.0": - version: 4.1.0 - resolution: "find-up@npm:4.1.0" +"file-entry-cache@npm:^8.0.0": + version: 8.0.0 + resolution: "file-entry-cache@npm:8.0.0" dependencies: - locate-path: "npm:^5.0.0" - path-exists: "npm:^4.0.0" - checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + flat-cache: "npm:^4.0.0" + checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 languageName: node linkType: hard @@ -4445,21 +3156,20 @@ __metadata: languageName: node linkType: hard -"flat-cache@npm:^6.1.23": - version: 6.1.23 - resolution: "flat-cache@npm:6.1.23" +"flat-cache@npm:^4.0.0": + version: 4.0.1 + resolution: "flat-cache@npm:4.0.1" dependencies: - cacheable: "npm:^2.5.0" - flatted: "npm:^3.4.2" - hookified: "npm:^1.15.0" - checksum: 10c0/c757c24f51334e1917958cd9d62d4ed3d520a3815d5a1ec50d993ac3a93fa81b6de7e28264231914bb0d990babd426e4d713d3e2f3d782dae65c42a2ded1e651 + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.4" + checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc languageName: node linkType: hard -"flatted@npm:^3.4.2": - version: 3.4.4 - resolution: "flatted@npm:3.4.4" - checksum: 10c0/a3a52a88ea5a4c333e5a1f097dcd87a037fa31c236a77cf46222c2aa4036ef895ab9bc76138a66d9dc22bdb3652128f9598cd26e7439561bf19f67276e2bc37e +"flatted@npm:^3.2.9": + version: 3.4.2 + resolution: "flatted@npm:3.4.2" + checksum: 10c0/a65b67aae7172d6cdf63691be7de6c5cd5adbdfdfe2e9da1a09b617c9512ed794037741ee53d93114276bff3f93cd3b0d97d54f9b316e1e4885dde6e9ffdf7ed languageName: node linkType: hard @@ -4470,16 +3180,6 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": - version: 3.3.1 - resolution: "foreground-child@npm:3.3.1" - dependencies: - cross-spawn: "npm:^7.0.6" - signal-exit: "npm:^4.0.1" - checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 - languageName: node - linkType: hard - "fraction.js@npm:^5.2.1": version: 5.3.4 resolution: "fraction.js@npm:5.3.4" @@ -4487,14 +3187,7 @@ __metadata: languageName: node linkType: hard -"fs.realpath@npm:^1.0.0": - version: 1.0.0 - resolution: "fs.realpath@npm:1.0.0" - checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 - languageName: node - linkType: hard - -"fsevents@npm:^2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -4504,7 +3197,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -4527,24 +3220,12 @@ __metadata: languageName: node linkType: hard -"get-caller-file@npm:^2.0.5": - version: 2.0.5 - resolution: "get-caller-file@npm:2.0.5" - checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde - languageName: node - linkType: hard - -"get-package-type@npm:^0.1.0": - version: 0.1.0 - resolution: "get-package-type@npm:0.1.0" - checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be - languageName: node - linkType: hard - -"get-stream@npm:^6.0.0": - version: 6.0.1 - resolution: "get-stream@npm:6.0.1" - checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 +"get-tsconfig@npm:^4.10.1": + version: 4.14.1 + resolution: "get-tsconfig@npm:4.14.1" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/c5e5d8fd298be9bc554b8ca3708a76732c27f632cd9b902c1d41b23ad1f4b6fefbe4dbdecd2fee84994d1ebf46e8cc50dd575fcff1b7ffafcdf6e2e0809a0337 languageName: node linkType: hard @@ -4557,61 +3238,13 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.5.0": - version: 10.5.0 - resolution: "glob@npm:10.5.0" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/100705eddbde6323e7b35e1d1ac28bcb58322095bd8e63a7d0bef1a2cdafe0d0f7922a981b2b48369a4f8c1b077be5c171804534c3509dfe950dde15fbe6d828 - languageName: node - linkType: hard - -"glob@npm:^7.1.4": - version: 7.2.3 - resolution: "glob@npm:7.2.3" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.1.1" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe - languageName: node - linkType: hard - -"graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 languageName: node linkType: hard -"handlebars@npm:^4.7.9": - version: 4.7.10 - resolution: "handlebars@npm:4.7.10" - dependencies: - minimist: "npm:^1.2.8" - neo-async: "npm:^2.6.2" - source-map: "npm:^0.6.1" - uglify-js: "npm:^3.1.4" - wordwrap: "npm:^1.0.0" - dependenciesMeta: - uglify-js: - optional: true - bin: - handlebars: bin/handlebars - checksum: 10c0/9b5573584ae5836172ae3f1502d9819568ba1b92ee83b81b18223beeb4c07486595e7b0fa633102beabc66ed06fe6e7da1dd9a9ea8c7072b9550b9a88809165c - languageName: node - linkType: hard - "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -4619,15 +3252,6 @@ __metadata: languageName: node linkType: hard -"hashery@npm:^1.4.0, hashery@npm:^1.5.1": - version: 1.5.1 - resolution: "hashery@npm:1.5.1" - dependencies: - hookified: "npm:^1.15.0" - checksum: 10c0/ab4225b655a7b0d05df99b1a59d5b3a51fe433f82422ca25e6f3f4c4ddd30adb49ebd38e0047ef9bded93319c1e9fc857e16aa382e554929c871cb77d39fc463 - languageName: node - linkType: hard - "hasown@npm:^2.0.2": version: 2.0.3 resolution: "hasown@npm:2.0.3" @@ -4647,20 +3271,6 @@ __metadata: languageName: node linkType: hard -"hookified@npm:^1.15.0, hookified@npm:^1.15.1": - version: 1.15.1 - resolution: "hookified@npm:1.15.1" - checksum: 10c0/6b691374fa97ae57169fb29f90e723499fda5e85494654fbe55c4768b3ccbf3e14c0adc8d0f365f32c503b60d7c06f907781f5966c03d41c423575eb5e16860c - languageName: node - linkType: hard - -"hookified@npm:^2.1.1": - version: 2.2.0 - resolution: "hookified@npm:2.2.0" - checksum: 10c0/7017d2b66945490293a5aba239e7b39f39071dd940fa019348c7ffea92b91b8c267853c4c51680ed0f3687b33352582fa4d2cff6dd4c5a1c5c44b037276f07aa - languageName: node - linkType: hard - "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -4677,13 +3287,6 @@ __metadata: languageName: node linkType: hard -"human-signals@npm:^2.1.0": - version: 2.1.0 - resolution: "human-signals@npm:2.1.0" - checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a - languageName: node - linkType: hard - "ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -4698,53 +3301,17 @@ __metadata: languageName: node linkType: hard -"import-local@npm:^3.2.0": - version: 3.2.0 - resolution: "import-local@npm:3.2.0" - dependencies: - pkg-dir: "npm:^4.2.0" - resolve-cwd: "npm:^3.0.0" - bin: - import-local-fixture: fixtures/cli.js - checksum: 10c0/94cd6367a672b7e0cb026970c85b76902d2710a64896fa6de93bd5c571dd03b228c5759308959de205083e3b1c61e799f019c9e36ee8e9c523b993e1057f0433 - languageName: node - linkType: hard - "import-meta-resolve@npm:^4.2.0": version: 4.2.0 - resolution: "import-meta-resolve@npm:4.2.0" - checksum: 10c0/3ee8aeecb61d19b49d2703987f977e9d1c7d4ba47db615a570eaa02fe414f40dfa63f7b953e842cbe8470d26df6371332bfcf21b2fd92b0112f9fea80dde2c4c - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 - languageName: node - linkType: hard - -"inflight@npm:^1.0.4": - version: 1.0.6 - resolution: "inflight@npm:1.0.6" - dependencies: - once: "npm:^1.3.0" - wrappy: "npm:1" - checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 - languageName: node - linkType: hard - -"inherits@npm:2": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + resolution: "import-meta-resolve@npm:4.2.0" + checksum: 10c0/3ee8aeecb61d19b49d2703987f977e9d1c7d4ba47db615a570eaa02fe414f40dfa63f7b953e842cbe8470d26df6371332bfcf21b2fd92b0112f9fea80dde2c4c languageName: node linkType: hard -"is-arrayish@npm:^0.2.1": - version: 0.2.1 - resolution: "is-arrayish@npm:0.2.1" - checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 languageName: node linkType: hard @@ -4764,20 +3331,6 @@ __metadata: languageName: node linkType: hard -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc - languageName: node - linkType: hard - -"is-generator-fn@npm:^2.1.0": - version: 2.1.0 - resolution: "is-generator-fn@npm:2.1.0" - checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d - languageName: node - linkType: hard - "is-glob@npm:^4.0.0, is-glob@npm:^4.0.3": version: 4.0.3 resolution: "is-glob@npm:4.0.3" @@ -4803,13 +3356,6 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^2.0.0": - version: 2.0.1 - resolution: "is-stream@npm:2.0.1" - checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 - languageName: node - linkType: hard - "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -4824,26 +3370,13 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0, istanbul-lib-coverage@npm:^3.2.2": +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b languageName: node linkType: hard -"istanbul-lib-instrument@npm:^6.0.0, istanbul-lib-instrument@npm:^6.0.2": - version: 6.0.3 - resolution: "istanbul-lib-instrument@npm:6.0.3" - dependencies: - "@babel/core": "npm:^7.23.9" - "@babel/parser": "npm:^7.23.9" - "@istanbuljs/schema": "npm:^0.1.3" - istanbul-lib-coverage: "npm:^3.2.0" - semver: "npm:^7.5.4" - checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 - languageName: node - linkType: hard - "istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": version: 3.0.1 resolution: "istanbul-lib-report@npm:3.0.1" @@ -4855,18 +3388,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-source-maps@npm:^5.0.0": - version: 5.0.6 - resolution: "istanbul-lib-source-maps@npm:5.0.6" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.23" - debug: "npm:^4.1.1" - istanbul-lib-coverage: "npm:^3.0.0" - checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f - languageName: node - linkType: hard - -"istanbul-reports@npm:^3.1.3, istanbul-reports@npm:^3.2.0": +"istanbul-reports@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-reports@npm:3.2.0" dependencies: @@ -4887,19 +3409,6 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 - languageName: node - linkType: hard - "javascript-natural-sort@npm:^0.7.1": version: 0.7.1 resolution: "javascript-natural-sort@npm:0.7.1" @@ -4907,444 +3416,6 @@ __metadata: languageName: node linkType: hard -"jest-changed-files@npm:30.4.1": - version: 30.4.1 - resolution: "jest-changed-files@npm:30.4.1" - dependencies: - execa: "npm:^5.1.1" - jest-util: "npm:30.4.1" - p-limit: "npm:^3.1.0" - checksum: 10c0/324bbec3920a7d9ceb1d11872b9f1befe73d152a7ef289243f663bf3b22afe124c2c656ec316e44393f30a83b74a1738b56307a066906fa49b800686fd4d0f04 - languageName: node - linkType: hard - -"jest-circus@npm:30.4.2": - version: 30.4.2 - resolution: "jest-circus@npm:30.4.2" - dependencies: - "@jest/environment": "npm:30.4.1" - "@jest/expect": "npm:30.4.1" - "@jest/test-result": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - co: "npm:^4.6.0" - dedent: "npm:^1.6.0" - is-generator-fn: "npm:^2.1.0" - jest-each: "npm:30.4.1" - jest-matcher-utils: "npm:30.4.1" - jest-message-util: "npm:30.4.1" - jest-runtime: "npm:30.4.2" - jest-snapshot: "npm:30.4.1" - jest-util: "npm:30.4.1" - p-limit: "npm:^3.1.0" - pretty-format: "npm:30.4.1" - pure-rand: "npm:^7.0.0" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.6" - checksum: 10c0/5d99f1336eb249057063a007fabad4ced802501fbaad7ddeea8db9553fa54fbd44d26e71e8bf61a0979d42b3b93a3d920e6f00afa26cdbb70d1e7d0969515d10 - languageName: node - linkType: hard - -"jest-cli@npm:30.4.2": - version: 30.4.2 - resolution: "jest-cli@npm:30.4.2" - dependencies: - "@jest/core": "npm:30.4.2" - "@jest/test-result": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - chalk: "npm:^4.1.2" - exit-x: "npm:^0.2.2" - import-local: "npm:^3.2.0" - jest-config: "npm:30.4.2" - jest-util: "npm:30.4.1" - jest-validate: "npm:30.4.1" - yargs: "npm:^17.7.2" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: ./bin/jest.js - checksum: 10c0/a036a1bf06ce7d5fed644a518c4a4ccf60c5fe5f3d96d143973048e6690c4a28a4f97fa3275d90ca236430a1b2a7c10544e7e190a4f2edfdf0a4e6daf1f6a384 - languageName: node - linkType: hard - -"jest-config@npm:30.4.2": - version: 30.4.2 - resolution: "jest-config@npm:30.4.2" - dependencies: - "@babel/core": "npm:^7.27.4" - "@jest/get-type": "npm:30.1.0" - "@jest/pattern": "npm:30.4.0" - "@jest/test-sequencer": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - babel-jest: "npm:30.4.1" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - deepmerge: "npm:^4.3.1" - glob: "npm:^10.5.0" - graceful-fs: "npm:^4.2.11" - jest-circus: "npm:30.4.2" - jest-docblock: "npm:30.4.0" - jest-environment-node: "npm:30.4.1" - jest-regex-util: "npm:30.4.0" - jest-resolve: "npm:30.4.1" - jest-runner: "npm:30.4.2" - jest-util: "npm:30.4.1" - jest-validate: "npm:30.4.1" - parse-json: "npm:^5.2.0" - pretty-format: "npm:30.4.1" - slash: "npm:^3.0.0" - strip-json-comments: "npm:^3.1.1" - peerDependencies: - "@types/node": "*" - esbuild-register: ">=3.4.0" - ts-node: ">=9.0.0" - peerDependenciesMeta: - "@types/node": - optional: true - esbuild-register: - optional: true - ts-node: - optional: true - checksum: 10c0/18300b1dc54a4bfb5d1db6c10aeb01b6c64736224e3f60d119da9504d49cbab5a76d789f38c44af7d168418463356db6843ad7e44f249c63ce7f409758eba0c6 - languageName: node - linkType: hard - -"jest-diff@npm:30.4.1": - version: 30.4.1 - resolution: "jest-diff@npm:30.4.1" - dependencies: - "@jest/diff-sequences": "npm:30.4.0" - "@jest/get-type": "npm:30.1.0" - chalk: "npm:^4.1.2" - pretty-format: "npm:30.4.1" - checksum: 10c0/787e11f0ea27e94815479d6c5415e4173da1e74bede34c1515b8515fc9d1fe053e2ad25a3c31f9998a7292c186a0e4d395ed82e0e149d57d7708ee6759b442e9 - languageName: node - linkType: hard - -"jest-docblock@npm:30.4.0": - version: 30.4.0 - resolution: "jest-docblock@npm:30.4.0" - dependencies: - detect-newline: "npm:^3.1.0" - checksum: 10c0/1fe1c971207e1b905e4f23d98e508a03ae631337e9ffa347ff2f6df81a1d75ced7ed3e52a809fad75fb8a8cd55b6bda4483bc124e5e1d7529eeb4ef76b29e913 - languageName: node - linkType: hard - -"jest-each@npm:30.4.1": - version: 30.4.1 - resolution: "jest-each@npm:30.4.1" - dependencies: - "@jest/get-type": "npm:30.1.0" - "@jest/types": "npm:30.4.1" - chalk: "npm:^4.1.2" - jest-util: "npm:30.4.1" - pretty-format: "npm:30.4.1" - checksum: 10c0/41bc1cec23901cb0c7d8f547a70574fffca8cc16a1660ed97645bf3b61f4e6151aaa58bb14ce55a3cd9f5a63a2cc782a39366caf3304a2159d1e3cc5ae79a9e4 - languageName: node - linkType: hard - -"jest-environment-node@npm:30.4.1": - version: 30.4.1 - resolution: "jest-environment-node@npm:30.4.1" - dependencies: - "@jest/environment": "npm:30.4.1" - "@jest/fake-timers": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - jest-mock: "npm:30.4.1" - jest-util: "npm:30.4.1" - jest-validate: "npm:30.4.1" - checksum: 10c0/d8d6bb22bfd280f077b5856558d9d7112c48fd3bae6eda9b76694f1c8e1be783a725686a137437d180c9d49e6b37386c8e342e0b8e5bfcb6526dee9c10cc31ec - languageName: node - linkType: hard - -"jest-haste-map@npm:30.4.1": - version: 30.4.1 - resolution: "jest-haste-map@npm:30.4.1" - dependencies: - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - anymatch: "npm:^3.1.3" - fb-watchman: "npm:^2.0.2" - fsevents: "npm:^2.3.3" - graceful-fs: "npm:^4.2.11" - jest-regex-util: "npm:30.4.0" - jest-util: "npm:30.4.1" - jest-worker: "npm:30.4.1" - picomatch: "npm:^4.0.3" - walker: "npm:^1.0.8" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/1350c24952bbf31c86cb1ed4e2e5edd4766a93e2be8816c4648c05463d06cfae89f3c73732f9274fdb626fdfdfe6605ed6f259b6c21257df536a6379d4b9a5e7 - languageName: node - linkType: hard - -"jest-leak-detector@npm:30.4.1": - version: 30.4.1 - resolution: "jest-leak-detector@npm:30.4.1" - dependencies: - "@jest/get-type": "npm:30.1.0" - pretty-format: "npm:30.4.1" - checksum: 10c0/57256ac08f12186e3ed1687126b8d75a12de9c4ffa959ff41322e9ba5f93e3ed8af91dc36bc4d59f77cef6d4008bcf5a3e646cdd950743898576aec8dbae6778 - languageName: node - linkType: hard - -"jest-matcher-utils@npm:30.4.1": - version: 30.4.1 - resolution: "jest-matcher-utils@npm:30.4.1" - dependencies: - "@jest/get-type": "npm:30.1.0" - chalk: "npm:^4.1.2" - jest-diff: "npm:30.4.1" - pretty-format: "npm:30.4.1" - checksum: 10c0/ddbb0c7075def27ba30160883c327cb3fd13f561f5789d00a1edca1b48b0651f8ea23a1c51bcfcb6413a68c47d658bcf47a34701b8a39ce135dd28d87a3117af - languageName: node - linkType: hard - -"jest-message-util@npm:30.4.1": - version: 30.4.1 - resolution: "jest-message-util@npm:30.4.1" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@jest/types": "npm:30.4.1" - "@types/stack-utils": "npm:^2.0.3" - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - jest-util: "npm:30.4.1" - picomatch: "npm:^4.0.3" - pretty-format: "npm:30.4.1" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.6" - checksum: 10c0/ae7427544e042bc1c14abf3c0dbe8b83d0dbec22a9a5efefaca5b8ccb6b9bf391abe732e6f2117ca995c6889bfe1be35c78cec75e5ea0a50e28cffe1ba6f9fdf - languageName: node - linkType: hard - -"jest-mock@npm:30.4.1": - version: 30.4.1 - resolution: "jest-mock@npm:30.4.1" - dependencies: - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - jest-util: "npm:30.4.1" - checksum: 10c0/5185a41255285c1634c5d85dda037afaaadfc12793b3293c9e253a30bb67449f8df968447f830abb9cf7a52e63694e6734680130e8085ce119056280890bf6fc - languageName: node - linkType: hard - -"jest-pnp-resolver@npm:^1.2.3": - version: 1.2.3 - resolution: "jest-pnp-resolver@npm:1.2.3" - peerDependencies: - jest-resolve: "*" - peerDependenciesMeta: - jest-resolve: - optional: true - checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac - languageName: node - linkType: hard - -"jest-regex-util@npm:30.4.0": - version: 30.4.0 - resolution: "jest-regex-util@npm:30.4.0" - checksum: 10c0/fe7426f67b54d38bed8e9d6e6a099d63d72f41f5bf65b922d9d03fedcb55c614b45657207632f6ee22d0a59d8d11327891f258d23f68a58912fcdb0f7db48435 - languageName: node - linkType: hard - -"jest-resolve-dependencies@npm:30.4.2": - version: 30.4.2 - resolution: "jest-resolve-dependencies@npm:30.4.2" - dependencies: - jest-regex-util: "npm:30.4.0" - jest-snapshot: "npm:30.4.1" - checksum: 10c0/4101afabd2a4ef4e6c82bf82ea145286c1238373f7611938e8d47ddcf5aaa6e10af365436a934b7af194451e351774829cb021ac73f857b4873dcccc7aabb616 - languageName: node - linkType: hard - -"jest-resolve@npm:30.4.1": - version: 30.4.1 - resolution: "jest-resolve@npm:30.4.1" - dependencies: - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.4.1" - jest-pnp-resolver: "npm:^1.2.3" - jest-util: "npm:30.4.1" - jest-validate: "npm:30.4.1" - slash: "npm:^3.0.0" - unrs-resolver: "npm:^1.7.11" - checksum: 10c0/0a99ef4f4fd7b3678d58a5e1cf8f0b5ec1997cdba21f5d66a8b26353d57a226f8e6a5fffc450c8836e90ab0e20d5e7935d0dea939d9a9b6a08781b9a7413184c - languageName: node - linkType: hard - -"jest-runner@npm:30.4.2": - version: 30.4.2 - resolution: "jest-runner@npm:30.4.2" - dependencies: - "@jest/console": "npm:30.4.1" - "@jest/environment": "npm:30.4.1" - "@jest/test-result": "npm:30.4.1" - "@jest/transform": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - emittery: "npm:^0.13.1" - exit-x: "npm:^0.2.2" - graceful-fs: "npm:^4.2.11" - jest-docblock: "npm:30.4.0" - jest-environment-node: "npm:30.4.1" - jest-haste-map: "npm:30.4.1" - jest-leak-detector: "npm:30.4.1" - jest-message-util: "npm:30.4.1" - jest-resolve: "npm:30.4.1" - jest-runtime: "npm:30.4.2" - jest-util: "npm:30.4.1" - jest-watcher: "npm:30.4.1" - jest-worker: "npm:30.4.1" - p-limit: "npm:^3.1.0" - source-map-support: "npm:0.5.13" - checksum: 10c0/339e630fb1a7db52e208ed9f12f722122733fe9a450d9bd83c0fccc10fbc5142a8808f624c41ab1e25833af02f9c3eca85561554b75a5b3ad75b4a226f72c5cf - languageName: node - linkType: hard - -"jest-runtime@npm:30.4.2": - version: 30.4.2 - resolution: "jest-runtime@npm:30.4.2" - dependencies: - "@jest/environment": "npm:30.4.1" - "@jest/fake-timers": "npm:30.4.1" - "@jest/globals": "npm:30.4.1" - "@jest/source-map": "npm:30.0.1" - "@jest/test-result": "npm:30.4.1" - "@jest/transform": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - cjs-module-lexer: "npm:^2.1.0" - collect-v8-coverage: "npm:^1.0.2" - glob: "npm:^10.5.0" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.4.1" - jest-message-util: "npm:30.4.1" - jest-mock: "npm:30.4.1" - jest-regex-util: "npm:30.4.0" - jest-resolve: "npm:30.4.1" - jest-snapshot: "npm:30.4.1" - jest-util: "npm:30.4.1" - slash: "npm:^3.0.0" - strip-bom: "npm:^4.0.0" - checksum: 10c0/9fce55b0c78fbe47dc2c10a944e9513833fd43c14f292460ef5cdd91e375088bf35549336e66f69fc9d29bf4f410894e9a7eef0bf12a6f39d99174a5300c2c53 - languageName: node - linkType: hard - -"jest-snapshot@npm:30.4.1": - version: 30.4.1 - resolution: "jest-snapshot@npm:30.4.1" - dependencies: - "@babel/core": "npm:^7.27.4" - "@babel/generator": "npm:^7.27.5" - "@babel/plugin-syntax-jsx": "npm:^7.27.1" - "@babel/plugin-syntax-typescript": "npm:^7.27.1" - "@babel/types": "npm:^7.27.3" - "@jest/expect-utils": "npm:30.4.1" - "@jest/get-type": "npm:30.1.0" - "@jest/snapshot-utils": "npm:30.4.1" - "@jest/transform": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - babel-preset-current-node-syntax: "npm:^1.2.0" - chalk: "npm:^4.1.2" - expect: "npm:30.4.1" - graceful-fs: "npm:^4.2.11" - jest-diff: "npm:30.4.1" - jest-matcher-utils: "npm:30.4.1" - jest-message-util: "npm:30.4.1" - jest-util: "npm:30.4.1" - pretty-format: "npm:30.4.1" - semver: "npm:^7.7.2" - synckit: "npm:^0.11.8" - checksum: 10c0/cebd70277b6f0d2606f22815480146cf1e37295ed69a1d16e260a99a2ab48db167857e2fb9a938923d22ac13203c83a5e31d7f066b58d87c6d42db58c914ff13 - languageName: node - linkType: hard - -"jest-util@npm:30.4.1": - version: 30.4.1 - resolution: "jest-util@npm:30.4.1" - dependencies: - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - graceful-fs: "npm:^4.2.11" - picomatch: "npm:^4.0.3" - checksum: 10c0/3efe1f25e5a172d04c6af8612d82867ab603b7c1bd8cb89073ff834679b44eba178793cf3af162cf5e25be13aa736ebd23a7826683acc85bddc5873f305b1f6e - languageName: node - linkType: hard - -"jest-validate@npm:30.4.1": - version: 30.4.1 - resolution: "jest-validate@npm:30.4.1" - dependencies: - "@jest/get-type": "npm:30.1.0" - "@jest/types": "npm:30.4.1" - camelcase: "npm:^6.3.0" - chalk: "npm:^4.1.2" - leven: "npm:^3.1.0" - pretty-format: "npm:30.4.1" - checksum: 10c0/23e6677ee6d06476f368c8b6d442b4207e5fbe062e74c1da3eae9ed30a18605f4e8a14809fa9cc7f22a2d8446e8de91a512f59c278720db2ad61c77dc25ffefc - languageName: node - linkType: hard - -"jest-watcher@npm:30.4.1": - version: 30.4.1 - resolution: "jest-watcher@npm:30.4.1" - dependencies: - "@jest/test-result": "npm:30.4.1" - "@jest/types": "npm:30.4.1" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.3.2" - chalk: "npm:^4.1.2" - emittery: "npm:^0.13.1" - jest-util: "npm:30.4.1" - string-length: "npm:^4.0.2" - checksum: 10c0/a56e1714b7b0f9c620c5cee95a84a48b780093594cd188e365a24768f208714895a0deb784ee48e4eec7f1828bc00435ab3c39208d490c33be3786937e997c97 - languageName: node - linkType: hard - -"jest-worker@npm:30.4.1": - version: 30.4.1 - resolution: "jest-worker@npm:30.4.1" - dependencies: - "@types/node": "npm:*" - "@ungap/structured-clone": "npm:^1.3.0" - jest-util: "npm:30.4.1" - merge-stream: "npm:^2.0.0" - supports-color: "npm:^8.1.1" - checksum: 10c0/3eb7ec7e928b82491e66ae6709e3a1eef3edad2bc351514a5d52037b997151989de6ce2912d6a5a3806ae3ae3bf6a1c36b1ad7bbc567d0790503fdb74576f140 - languageName: node - linkType: hard - -"jest@npm:^30.4.2": - version: 30.4.2 - resolution: "jest@npm:30.4.2" - dependencies: - "@jest/core": "npm:30.4.2" - "@jest/types": "npm:30.4.1" - import-local: "npm:^3.2.0" - jest-cli: "npm:30.4.2" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: ./bin/jest.js - checksum: 10c0/26a76eaabfc043abd8ee702b97f61ff968dde03412efdb4a69c22c99a5e4bf47788a3e45f75134aec1377a686a9d59d1e3bae85a816e409013475a80de1458ec - languageName: node - linkType: hard - "jju@npm:^1.4.0": version: 1.4.0 resolution: "jju@npm:1.4.0" @@ -5366,18 +3437,6 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^3.13.1": - version: 3.15.2 - resolution: "js-yaml@npm:3.15.2" - dependencies: - argparse: "npm:^1.0.7" - esprima: "npm:^4.0.0" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/e341df211dece9d4b784849647ad7568b5b6a58a9ee58ead750482316d83276387343649fe4b71522705dc6c76acf97718588f23dc19443833ef3e75033af967 - languageName: node - linkType: hard - "jsesc@npm:^3.0.2": version: 3.1.0 resolution: "jsesc@npm:3.1.0" @@ -5387,10 +3446,10 @@ __metadata: languageName: node linkType: hard -"json-parse-even-better-errors@npm:^2.3.0": - version: 2.3.1 - resolution: "json-parse-even-better-errors@npm:2.3.1" - checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 languageName: node linkType: hard @@ -5424,12 +3483,12 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^5.6.0": - version: 5.6.0 - resolution: "keyv@npm:5.6.0" +"keyv@npm:^4.5.4": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" dependencies: - "@keyv/serialize": "npm:^1.1.1" - checksum: 10c0/c3ea795b6e03593ca57c8f70928a69bad14c13389a7fb75649a115ff55615244b04d8902798d841c17f0bb4a8a8866c97133b543b93f151b440170bba09176db + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e languageName: node linkType: hard @@ -5458,13 +3517,6 @@ __metadata: languageName: node linkType: hard -"leven@npm:^3.1.0": - version: 3.1.0 - resolution: "leven@npm:3.1.0" - checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df - languageName: node - linkType: hard - "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -5595,22 +3647,6 @@ __metadata: languageName: node linkType: hard -"lines-and-columns@npm:^1.1.6": - version: 1.2.4 - resolution: "lines-and-columns@npm:1.2.4" - checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d - languageName: node - linkType: hard - -"locate-path@npm:^5.0.0": - version: 5.0.0 - resolution: "locate-path@npm:5.0.0" - dependencies: - p-locate: "npm:^4.1.0" - checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 - languageName: node - linkType: hard - "locate-path@npm:^6.0.0": version: 6.0.0 resolution: "locate-path@npm:6.0.0" @@ -5620,13 +3656,6 @@ __metadata: languageName: node linkType: hard -"lodash.memoize@npm:^4.1.2": - version: 4.1.2 - resolution: "lodash.memoize@npm:4.1.2" - checksum: 10c0/c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8 - languageName: node - linkType: hard - "loose-envify@npm:^1.0.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -5647,13 +3676,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb - languageName: node - linkType: hard - "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -5663,7 +3685,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.21, magic-string@npm:^0.30.3": +"magic-string@npm:^0.30.3": version: 0.30.21 resolution: "magic-string@npm:0.30.21" dependencies: @@ -5712,22 +3734,6 @@ __metadata: languageName: node linkType: hard -"make-error@npm:^1.3.6": - version: 1.3.6 - resolution: "make-error@npm:1.3.6" - checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f - languageName: node - linkType: hard - -"makeerror@npm:1.0.12": - version: 1.0.12 - resolution: "makeerror@npm:1.0.12" - dependencies: - tmpl: "npm:1.0.5" - checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c - languageName: node - linkType: hard - "mathjs@npm:^14.9.1": version: 14.9.1 resolution: "mathjs@npm:14.9.1" @@ -5747,20 +3753,6 @@ __metadata: languageName: node linkType: hard -"merge-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-stream@npm:2.0.0" - checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 - languageName: node - linkType: hard - -"mimic-fn@npm:^2.1.0": - version: 2.1.0 - resolution: "mimic-fn@npm:2.1.0" - checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 - languageName: node - linkType: hard - "minimatch@npm:^10.2.2, minimatch@npm:^10.2.4, minimatch@npm:^10.2.5": version: 10.2.5 resolution: "minimatch@npm:10.2.5" @@ -5770,32 +3762,16 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1": - version: 3.1.5 - resolution: "minimatch@npm:3.1.5" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/2ecbdc0d33f07bddb0315a8b5afbcb761307a8778b48f0b312418ccbced99f104a2d17d8aca7573433c70e8ccd1c56823a441897a45e384ea76ef401a26ace70 - languageName: node - linkType: hard - -"minimatch@npm:^9.0.4": - version: 9.0.9 - resolution: "minimatch@npm:9.0.9" +"minimatch@npm:^9.0.3 || ^10.1.2": + version: 10.2.6 + resolution: "minimatch@npm:10.2.6" dependencies: - brace-expansion: "npm:^2.0.2" - checksum: 10c0/0b6a58530dbb00361745aa6c8cffaba4c90f551afe7c734830bd95fd88ebf469dd7355a027824ea1d09e37181cfeb0a797fb17df60c15ac174303ac110eb7e86 - languageName: node - linkType: hard - -"minimist@npm:^1.2.8": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + brace-expansion: "npm:^5.0.8" + checksum: 10c0/4559a836243b98bd4d17ea9f7edae698717c76399eea7be374f3737f33164e4907f19e9726891ddeb122f750a5a7fa80d2ac43e851d6e5984dc4ff42ec127d3a languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.4, minipass@npm:^7.1.2": +"minipass@npm:^7.0.4, minipass@npm:^7.1.2": version: 7.1.3 resolution: "minipass@npm:7.1.3" checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb @@ -5867,13 +3843,6 @@ __metadata: languageName: node linkType: hard -"neo-async@npm:^2.6.2": - version: 2.6.2 - resolution: "neo-async@npm:2.6.2" - checksum: 10c0/c2f5a604a54a8ec5438a342e1f356dff4bc33ccccdb6dc668d94fe8e5eccfc9d2c2eea6064b0967a767ba63b33763f51ccf2cd2441b461a7322656c1f06b3f5d - languageName: node - linkType: hard - "no-case@npm:^3.0.4": version: 3.0.4 resolution: "no-case@npm:3.0.4" @@ -5904,17 +3873,10 @@ __metadata: languageName: node linkType: hard -"node-int64@npm:^0.4.0": - version: 0.4.0 - resolution: "node-int64@npm:0.4.0" - checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a - languageName: node - linkType: hard - -"node-releases@npm:^2.0.54": - version: 2.0.54 - resolution: "node-releases@npm:2.0.54" - checksum: 10c0/196b54ab06fd03b742816b6ca8185c69af42735b283083036e2579682c430b8f9ff89046891fe8c625557e5885793a2032d28070100bb2e57e6d6586b2f483c1 +"node-releases@npm:^2.0.36": + version: 2.0.38 + resolution: "node-releases@npm:2.0.38" + checksum: 10c0/db9909234ed750c5b9d0075f83214cd16b76370b54eab50e3554f3ba939ba7ac39f3aca2ddf93471ae8553dbde2ea9354b0ae380c9cff1f8e53b55e414903413 languageName: node linkType: hard @@ -5929,13 +3891,6 @@ __metadata: languageName: node linkType: hard -"normalize-path@npm:^3.0.0": - version: 3.0.0 - resolution: "normalize-path@npm:3.0.0" - checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 - languageName: node - linkType: hard - "normalize.css@npm:^8.0.1": version: 8.0.1 resolution: "normalize.css@npm:8.0.1" @@ -5943,15 +3898,6 @@ __metadata: languageName: node linkType: hard -"npm-run-path@npm:^4.0.1": - version: 4.0.1 - resolution: "npm-run-path@npm:4.0.1" - dependencies: - path-key: "npm:^3.0.0" - checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac - languageName: node - linkType: hard - "object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -5980,24 +3926,6 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: "npm:1" - checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 - languageName: node - linkType: hard - -"onetime@npm:^5.1.2": - version: 5.1.2 - resolution: "onetime@npm:5.1.2" - dependencies: - mimic-fn: "npm:^2.1.0" - checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f - languageName: node - linkType: hard - "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -6012,16 +3940,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^2.2.0": - version: 2.3.0 - resolution: "p-limit@npm:2.3.0" - dependencies: - p-try: "npm:^2.0.0" - checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 - languageName: node - linkType: hard - -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": +"p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -6030,15 +3949,6 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^4.1.0": - version: 4.1.0 - resolution: "p-locate@npm:4.1.0" - dependencies: - p-limit: "npm:^2.2.0" - checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 - languageName: node - linkType: hard - "p-locate@npm:^5.0.0": version: 5.0.0 resolution: "p-locate@npm:5.0.0" @@ -6048,20 +3958,6 @@ __metadata: languageName: node linkType: hard -"p-try@npm:^2.0.0": - version: 2.2.0 - resolution: "p-try@npm:2.2.0" - checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f - languageName: node - linkType: hard - -"package-json-from-dist@npm:^1.0.0": - version: 1.0.1 - resolution: "package-json-from-dist@npm:1.0.1" - checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b - languageName: node - linkType: hard - "package-manager-detector@npm:^1.6.0, package-manager-detector@npm:^1.8.0": version: 1.9.0 resolution: "package-manager-detector@npm:1.9.0" @@ -6079,18 +3975,6 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^5.2.0": - version: 5.2.0 - resolution: "parse-json@npm:5.2.0" - dependencies: - "@babel/code-frame": "npm:^7.0.0" - error-ex: "npm:^1.3.1" - json-parse-even-better-errors: "npm:^2.3.0" - lines-and-columns: "npm:^1.1.6" - checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 - languageName: node - linkType: hard - "pascal-case@npm:^3.1.2": version: 3.1.2 resolution: "pascal-case@npm:3.1.2" @@ -6118,14 +4002,7 @@ __metadata: languageName: node linkType: hard -"path-is-absolute@npm:^1.0.0": - version: 1.0.1 - resolution: "path-is-absolute@npm:1.0.1" - checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 - languageName: node - linkType: hard - -"path-key@npm:^3.0.0, path-key@npm:^3.1.0": +"path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c @@ -6139,23 +4016,6 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d - languageName: node - linkType: hard - -"pathe@npm:^2.0.3": - version: 2.0.3 - resolution: "pathe@npm:2.0.3" - checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 - languageName: node - linkType: hard - "picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" @@ -6163,14 +4023,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4": - version: 2.3.2 - resolution: "picomatch@npm:2.3.2" - checksum: 10c0/a554d1709e59be97d1acb9eaedbbc700a5c03dbd4579807baed95100b00420bc729335440ef15004ae2378984e2487a7c1cebd743cfdb72b6fa9ab69223c0d61 - languageName: node - linkType: hard - -"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3, picomatch@npm:^4.0.4": +"picomatch@npm:^4.0.2, picomatch@npm:^4.0.4": version: 4.0.4 resolution: "picomatch@npm:4.0.4" checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 @@ -6180,46 +4033,9 @@ __metadata: "picomatch@npm:^4.0.7": version: 4.0.7 resolution: "picomatch@npm:4.0.7" - checksum: 10c0/beb6ae02c43ae44e84883b90830196d9046b1726ead292adcf7f57945e0bb0d992d68563d87e03b484b6f3c9a5c6defda7523477f047d7f0e663f126cc01787f - languageName: node - linkType: hard - -"pirates@npm:^4.0.7": - version: 4.0.7 - resolution: "pirates@npm:4.0.7" - checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a - languageName: node - linkType: hard - -"pkg-dir@npm:^4.2.0": - version: 4.2.0 - resolution: "pkg-dir@npm:4.2.0" - dependencies: - find-up: "npm:^4.0.0" - checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 - languageName: node - linkType: hard - -"plugins@workspace:.": - version: 0.0.0-use.local - resolution: "plugins@workspace:." - dependencies: - "@changesets/cli": "npm:^3.0.0" - "@sourceacademy/conductor": "npm:^0.8.0" - "@sourceacademy/plugin-directory": "source-academy/plugin-directory#main" - "@types/node": "npm:^25.9.1" - "@vitest/coverage-istanbul": "npm:^5.0.0" - commander: "npm:^15.0.0" - eslint: "npm:^10.0.3" - eslint-config-prettier: "npm:^10.1.8" - prettier: "npm:^3.8.1" - tsx: "npm:^4.22.3" - typescript: "npm:^6.0.3" - typescript-eslint: "npm:^8.57.1" - vite: "npm:^8.0.0" - vitest: "npm:^5.0.0" - languageName: unknown - linkType: soft + checksum: 10c0/beb6ae02c43ae44e84883b90830196d9046b1726ead292adcf7f57945e0bb0d992d68563d87e03b484b6f3c9a5c6defda7523477f047d7f0e663f126cc01787f + languageName: node + linkType: hard "postcss@npm:^8.5.29": version: 8.5.29 @@ -6240,23 +4056,11 @@ __metadata: linkType: hard "prettier@npm:^3.8.1": - version: 3.9.10 - resolution: "prettier@npm:3.9.10" + version: 3.8.3 + resolution: "prettier@npm:3.8.3" bin: prettier: bin/prettier.cjs - checksum: 10c0/0b3531593aa6fb36ac6baf629cb058bda472c59a6938774f931fe55283a07c62e6f56e621b043a82d8a3e21fc284c08c34f9e539fc70072635f79b7bae1a1067 - languageName: node - linkType: hard - -"pretty-format@npm:30.4.1, pretty-format@npm:^30.0.0": - version: 30.4.1 - resolution: "pretty-format@npm:30.4.1" - dependencies: - "@jest/schemas": "npm:30.4.1" - ansi-styles: "npm:^5.2.0" - react-is-18: "npm:react-is@^18.3.1" - react-is-19: "npm:react-is@^19.2.5" - checksum: 10c0/c7e6633740cd2f6d382f188c00c8b4b3f2bee3cda16db6753471c6bb4b94f76531358d3a7793062a0fb00d72ebfb934e8ae1d4f5ced6bb34c8e7f60996f90076 + checksum: 10c0/754816fd7593eb80f6376d7476d463e832c38a12f32775a82683adb6e35b772b1f484d65f19401507b983a8c8a7cd5a4a9f12006bd56491e8f35503473f77473 languageName: node linkType: hard @@ -6285,13 +4089,6 @@ __metadata: languageName: node linkType: hard -"pure-rand@npm:^7.0.0": - version: 7.0.1 - resolution: "pure-rand@npm:7.0.1" - checksum: 10c0/9cade41030f5ec95f5d55a11a71404cd6f46b69becaad892097cd7f58e2c6248cd0a933349ca7d21336ab629f1da42ffe899699b671bc4651600eaf6e57f837e - languageName: node - linkType: hard - "py-slang@github:proto-aiken-13/py-slang#branch-ev3-engine": version: 1.0.0 resolution: "py-slang@https://github.com/proto-aiken-13/py-slang.git#commit=da6c3870cd7c4319d250484c8edf15ef4de21555" @@ -6307,15 +4104,6 @@ __metadata: languageName: node linkType: hard -"qified@npm:^0.10.1": - version: 0.10.1 - resolution: "qified@npm:0.10.1" - dependencies: - hookified: "npm:^2.1.1" - checksum: 10c0/4a39d45492c65a4b9795381acede1bd566028ce9764ead5b41d776b391a898bf0855dd2b59d724a6711fe253ba3cacf7c9a8832887cf9dc6d48a2c5e4997c82d - languageName: node - linkType: hard - "railroad-diagrams@npm:^1.0.0": version: 1.0.0 resolution: "railroad-diagrams@npm:1.0.0" @@ -6351,20 +4139,6 @@ __metadata: languageName: node linkType: hard -"react-is-18@npm:react-is@^18.3.1": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - -"react-is-19@npm:react-is@^19.2.5": - version: 19.2.7 - resolution: "react-is@npm:19.2.7" - checksum: 10c0/419fe54d5bd7fdf5414a5bb7bd9a1e0e36f9fae28ffb4cb73290fbe342bde15d8584a90d1db62547f6aa03018dce517b178a041abb522136cd4b4b51b4e94c83 - languageName: node - linkType: hard - "react-is@npm:^16.13.1": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -6454,26 +4228,10 @@ __metadata: languageName: node linkType: hard -"require-directory@npm:^2.1.1": - version: 2.1.1 - resolution: "require-directory@npm:2.1.1" - checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 - languageName: node - linkType: hard - -"resolve-cwd@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-cwd@npm:3.0.0" - dependencies: - resolve-from: "npm:^5.0.0" - checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 - languageName: node - linkType: hard - -"resolve-from@npm:^5.0.0": - version: 5.0.0 - resolution: "resolve-from@npm:5.0.0" - checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab languageName: node linkType: hard @@ -6683,7 +4441,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.8.0, semver@npm:^7.8.1": +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.7.2, semver@npm:^7.7.3, semver@npm:^7.8.1": version: 7.8.5 resolution: "semver@npm:7.8.5" bin: @@ -6733,27 +4491,6 @@ __metadata: languageName: node linkType: hard -"siginfo@npm:^2.0.0": - version: 2.0.0 - resolution: "siginfo@npm:2.0.0" - checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 - languageName: node - linkType: hard - -"signal-exit@npm:^3.0.3": - version: 3.0.7 - resolution: "signal-exit@npm:3.0.7" - checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 - languageName: node - linkType: hard - -"signal-exit@npm:^4.0.1": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 - languageName: node - linkType: hard - "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -6761,13 +4498,6 @@ __metadata: languageName: node linkType: hard -"slash@npm:^3.0.0": - version: 3.0.0 - resolution: "slash@npm:3.0.0" - checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b - languageName: node - linkType: hard - "smob@npm:^1.0.0": version: 1.6.1 resolution: "smob@npm:1.6.1" @@ -6792,16 +4522,6 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:0.5.13": - version: 0.5.13 - resolution: "source-map-support@npm:0.5.13" - dependencies: - buffer-from: "npm:^1.0.0" - source-map: "npm:^0.6.0" - checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e - languageName: node - linkType: hard - "source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" @@ -6812,40 +4532,17 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:^0.6.1": +"source-map@npm:^0.6.0": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 languageName: node linkType: hard -"sprintf-js@npm:~1.0.2": - version: 1.0.3 - resolution: "sprintf-js@npm:1.0.3" - checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb - languageName: node - linkType: hard - -"stack-utils@npm:^2.0.6": - version: 2.0.6 - resolution: "stack-utils@npm:2.0.6" - dependencies: - escape-string-regexp: "npm:^2.0.0" - checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a - languageName: node - linkType: hard - -"stackback@npm:0.0.2": - version: 0.0.2 - resolution: "stackback@npm:0.0.2" - checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 - languageName: node - linkType: hard - -"std-env@npm:^4.0.0-rc.1": - version: 4.1.0 - resolution: "std-env@npm:4.1.0" - checksum: 10c0/2e14b6b490db34cb969a48d9cf7c35bca4a47653914aac2814221baae7b867a5b15940d133625c391621971f98cd2266a5dc7036669960e883f1081db2a56558 +"stable-hash-x@npm:^0.2.0": + version: 0.2.0 + resolution: "stable-hash-x@npm:0.2.0" + checksum: 10c0/c757df58366ee4bb266a9486b8932eab7c1ba730469eaf4b68d2dee404814e9f84089c44c9b5205f8c7d99a0ab036cce2af69139ce5ed44b635923c011a8aea8 languageName: node linkType: hard @@ -6856,77 +4553,6 @@ __metadata: languageName: node linkType: hard -"string-length@npm:^4.0.2": - version: 4.0.2 - resolution: "string-length@npm:4.0.2" - dependencies: - char-regex: "npm:^1.0.2" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c - languageName: node - linkType: hard - -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b - languageName: node - linkType: hard - -"string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca - languageName: node - linkType: hard - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" - dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 - languageName: node - linkType: hard - -"strip-ansi@npm:^7.0.1": - version: 7.2.0 - resolution: "strip-ansi@npm:7.2.0" - dependencies: - ansi-regex: "npm:^6.2.2" - checksum: 10c0/544d13b7582f8254811ea97db202f519e189e59d35740c46095897e254e4f1aa9fe1524a83ad6bc5ad67d4dd6c0281d2e0219ed62b880a6238a16a17d375f221 - languageName: node - linkType: hard - -"strip-bom@npm:^4.0.0": - version: 4.0.0 - resolution: "strip-bom@npm:4.0.0" - checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef - languageName: node - linkType: hard - -"strip-final-newline@npm:^2.0.0": - version: 2.0.0 - resolution: "strip-final-newline@npm:2.0.0" - checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f - languageName: node - linkType: hard - -"strip-json-comments@npm:^3.1.1": - version: 3.1.1 - resolution: "strip-json-comments@npm:3.1.1" - checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd - languageName: node - linkType: hard - "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -6936,15 +4562,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.1.1": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 - languageName: node - linkType: hard - "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -6952,15 +4569,6 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.11.8": - version: 0.11.13 - resolution: "synckit@npm:0.11.13" - dependencies: - "@pkgr/core": "npm:^0.3.6" - checksum: 10c0/5a6c19f4f79045aaa7994106401bff6dbe7cca23a6d0a0723ff14eb8b1bebeb4a71729118f6914905598e304ea2fa13509885e11ba07d92e7cb68a06740cb328 - languageName: node - linkType: hard - "tabbable@npm:^6.0.0": version: 6.5.0 resolution: "tabbable@npm:6.5.0" @@ -6995,17 +4603,6 @@ __metadata: languageName: node linkType: hard -"test-exclude@npm:^6.0.0": - version: 6.0.0 - resolution: "test-exclude@npm:6.0.0" - dependencies: - "@istanbuljs/schema": "npm:^0.1.2" - glob: "npm:^7.1.4" - minimatch: "npm:^3.0.4" - checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 - languageName: node - linkType: hard - "tiny-emitter@npm:^2.1.0": version: 2.1.0 resolution: "tiny-emitter@npm:2.1.0" @@ -7013,13 +4610,6 @@ __metadata: languageName: node linkType: hard -"tinybench@npm:^2.9.0": - version: 2.9.0 - resolution: "tinybench@npm:2.9.0" - checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c - languageName: node - linkType: hard - "tinybench@npm:^6.1.4": version: 6.2.1 resolution: "tinybench@npm:6.2.1" @@ -7027,13 +4617,6 @@ __metadata: languageName: node linkType: hard -"tinyexec@npm:^1.0.2": - version: 1.2.4 - resolution: "tinyexec@npm:1.2.4" - checksum: 10c0/153b8db6b080194b558ff145b9cffc36b80a6e07babd644dcfbe49c807eee668c876049d28bdee90b96304476f883352f2dad91b3f86bc23832532f4363e66ff - languageName: node - linkType: hard - "tinyexec@npm:^1.3.0, tinyexec@npm:^1.3.1": version: 1.3.1 resolution: "tinyexec@npm:1.3.1" @@ -7065,13 +4648,6 @@ __metadata: languageName: node linkType: hard -"tmpl@npm:1.0.5": - version: 1.0.5 - resolution: "tmpl@npm:1.0.5" - checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 - languageName: node - linkType: hard - "ts-api-utils@npm:^2.5.0": version: 2.5.0 resolution: "ts-api-utils@npm:2.5.0" @@ -7081,46 +4657,6 @@ __metadata: languageName: node linkType: hard -"ts-jest@npm:^29.4.11": - version: 29.4.11 - resolution: "ts-jest@npm:29.4.11" - dependencies: - bs-logger: "npm:^0.2.6" - fast-json-stable-stringify: "npm:^2.1.0" - handlebars: "npm:^4.7.9" - json5: "npm:^2.2.3" - lodash.memoize: "npm:^4.1.2" - make-error: "npm:^1.3.6" - semver: "npm:^7.8.0" - type-fest: "npm:^4.41.0" - yargs-parser: "npm:^21.1.1" - 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 <7" - peerDependenciesMeta: - "@babel/core": - optional: true - "@jest/transform": - optional: true - "@jest/types": - optional: true - babel-jest: - optional: true - esbuild: - optional: true - jest-util: - optional: true - bin: - ts-jest: cli.js - checksum: 10c0/f9e6ab3235f33088c4d6441da97d4b03b1fe9c21f4d859d7acf3f325ea36471a6c1ea9301f445b2670efa1a391dcddc31ecd8641a2d673752d074136311b8480 - languageName: node - linkType: hard - "tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.8.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" @@ -7135,21 +4671,6 @@ __metadata: languageName: node linkType: hard -"tsx@npm:^4.22.3": - version: 4.23.15 - resolution: "tsx@npm:4.23.15" - dependencies: - esbuild: "npm:~0.28.0" - fsevents: "npm:~2.3.3" - dependenciesMeta: - fsevents: - optional: true - bin: - tsx: dist/cli.mjs - checksum: 10c0/3c5fc53e578da87d3a63565286b1ecbb0e01b9bb646daec1957294ef890d14f22cc67bb4b09b5daffab0e7ce0217b1e7645393feb0f524b1ddf26ae9595d7e75 - languageName: node - linkType: hard - "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" @@ -7159,27 +4680,6 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8": - version: 4.0.8 - resolution: "type-detect@npm:4.0.8" - checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd - languageName: node - linkType: hard - -"type-fest@npm:^0.21.3": - version: 0.21.3 - resolution: "type-fest@npm:0.21.3" - checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 - languageName: node - linkType: hard - -"type-fest@npm:^4.41.0": - version: 4.41.0 - resolution: "type-fest@npm:4.41.0" - checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 - languageName: node - linkType: hard - "typed-function@npm:^4.2.1": version: 4.2.2 resolution: "typed-function@npm:4.2.2" @@ -7188,17 +4688,17 @@ __metadata: linkType: hard "typescript-eslint@npm:^8.57.1": - version: 8.71.1 - resolution: "typescript-eslint@npm:8.71.1" + version: 8.65.0 + resolution: "typescript-eslint@npm:8.65.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.71.1" - "@typescript-eslint/parser": "npm:8.71.1" - "@typescript-eslint/typescript-estree": "npm:8.71.1" - "@typescript-eslint/utils": "npm:8.71.1" + "@typescript-eslint/eslint-plugin": "npm:8.65.0" + "@typescript-eslint/parser": "npm:8.65.0" + "@typescript-eslint/typescript-estree": "npm:8.65.0" + "@typescript-eslint/utils": "npm:8.65.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" - checksum: 10c0/900ff14208ebe4982900cb8183c8fd31528b8751dbb3567d825c5bdaa5f1f4da954464ee06849948166a7702731656d29253aa8bd2c8c946b1a4b60d420d5ae2 + checksum: 10c0/d1f5eede08c6d0d500aa605a1de2a4e721c00e8f56f632581e082aac6d1d2b9d365ce692cf5e0e212ea78271a032c8785e0fc58aee276c7930f92fff6a3358c8 languageName: node linkType: hard @@ -7222,15 +4722,6 @@ __metadata: languageName: node linkType: hard -"uglify-js@npm:^3.1.4": - version: 3.19.3 - resolution: "uglify-js@npm:3.19.3" - bin: - uglifyjs: bin/uglifyjs - checksum: 10c0/83b0a90eca35f778e07cad9622b80c448b6aad457c9ff8e568afed978212b42930a95f9e1be943a1ffa4258a3340fbb899f41461131c05bb1d0a9c303aed8479 - languageName: node - linkType: hard - "undici-types@npm:>=7.24.0 <7.24.7": version: 7.24.6 resolution: "undici-types@npm:7.24.6" @@ -7238,21 +4729,14 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~8.9.0": - version: 8.9.0 - resolution: "undici-types@npm:8.9.0" - checksum: 10c0/51c712c20c087368f62c6fc0f2bc49fa76bfe5dec28ff01584bb677ff29b0622569783c02d43e151143808c48c0503f804093bd2c491b10f2a830138034cb451 - languageName: node - linkType: hard - "undici@npm:^6.25.0": - version: 6.29.0 - resolution: "undici@npm:6.29.0" - checksum: 10c0/3fb2117985be977ba8b6bd6a242fec915e1b1d0f9681f5adb3f508fb1717352452d65bcfd3bed09e7c776cfda403a4e8edb24a318492f8b2d793439af2b8699b + version: 6.27.0 + resolution: "undici@npm:6.27.0" + checksum: 10c0/f88c3dae3957dbf9d93cb481440aced317bd3c4941b5914fea5efba516d51138988cdb5c76006f0bb1337e41d56c3443351055d492e73af2428521c37ba2a76f languageName: node linkType: hard -"unrs-resolver@npm:^1.7.11": +"unrs-resolver@npm:^1.9.2": version: 1.12.2 resolution: "unrs-resolver@npm:1.12.2" dependencies: @@ -7328,9 +4812,9 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.3.2": - version: 1.3.2 - resolution: "update-browserslist-db@npm:1.3.2" +"update-browserslist-db@npm:^1.2.3": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" dependencies: escalade: "npm:^3.2.0" picocolors: "npm:^1.1.1" @@ -7338,7 +4822,7 @@ __metadata: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10c0/837b4ea8c4934228c5c2a922ea747a54b0ec9d1c408d2fbc9220a9b98327c04109776e6ea1206fa056ea61ed8534863af472d9f2cfce569d35f04e98c95db739 + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec languageName: node linkType: hard @@ -7369,17 +4853,6 @@ __metadata: languageName: node linkType: hard -"v8-to-istanbul@npm:^9.0.1": - version: 9.3.0 - resolution: "v8-to-istanbul@npm:9.3.0" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.12" - "@types/istanbul-lib-coverage": "npm:^2.0.1" - convert-source-map: "npm:^2.0.0" - checksum: 10c0/968bcf1c7c88c04df1ffb463c179558a2ec17aa49e49376120504958239d9e9dad5281aa05f2a78542b8557f2be0b0b4c325710262f3b838b40d703d5ed30c23 - languageName: node - linkType: hard - "verkit@npm:^0.5.0": version: 0.5.0 resolution: "verkit@npm:0.5.0" @@ -7387,7 +4860,7 @@ __metadata: languageName: node linkType: hard -"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0, vite@npm:^8.0.0": +"vite@npm:^8.0.0": version: 8.3.4 resolution: "vite@npm:8.3.4" dependencies: @@ -7444,74 +4917,6 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^4.1.9": - version: 4.1.11 - resolution: "vitest@npm:4.1.11" - dependencies: - "@vitest/expect": "npm:4.1.11" - "@vitest/mocker": "npm:4.1.11" - "@vitest/pretty-format": "npm:4.1.11" - "@vitest/runner": "npm:4.1.11" - "@vitest/snapshot": "npm:4.1.11" - "@vitest/spy": "npm:4.1.11" - "@vitest/utils": "npm:4.1.11" - es-module-lexer: "npm:^2.0.0" - expect-type: "npm:^1.3.0" - magic-string: "npm:^0.30.21" - obug: "npm:^2.1.1" - pathe: "npm:^2.0.3" - picomatch: "npm:^4.0.3" - std-env: "npm:^4.0.0-rc.1" - tinybench: "npm:^2.9.0" - tinyexec: "npm:^1.0.2" - tinyglobby: "npm:^0.2.15" - tinyrainbow: "npm:^3.1.0" - vite: "npm:^6.0.0 || ^7.0.0 || ^8.0.0" - why-is-node-running: "npm:^2.3.0" - peerDependencies: - "@edge-runtime/vm": "*" - "@opentelemetry/api": ^1.9.0 - "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.1.11 - "@vitest/browser-preview": 4.1.11 - "@vitest/browser-webdriverio": 4.1.11 - "@vitest/coverage-istanbul": 4.1.11 - "@vitest/coverage-v8": 4.1.11 - "@vitest/ui": 4.1.11 - happy-dom: "*" - jsdom: "*" - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - "@edge-runtime/vm": - optional: true - "@opentelemetry/api": - optional: true - "@types/node": - optional: true - "@vitest/browser-playwright": - optional: true - "@vitest/browser-preview": - optional: true - "@vitest/browser-webdriverio": - optional: true - "@vitest/coverage-istanbul": - optional: true - "@vitest/coverage-v8": - optional: true - "@vitest/ui": - optional: true - happy-dom: - optional: true - jsdom: - optional: true - vite: - optional: false - bin: - vitest: ./vitest.mjs - checksum: 10c0/3fa0948cf74adcccc8cbcdb4e6d30ada6933bdfb1816ff99200f3d3b689325b37dc483b22535b57b6d911f7a7b64eaa6a5f8da1606cfe7f2466f48000c21e296 - languageName: node - linkType: hard - "vitest@npm:^5.0.0": version: 5.0.3 resolution: "vitest@npm:5.0.3" @@ -7590,15 +4995,6 @@ __metadata: languageName: node linkType: hard -"walker@npm:^1.0.8": - version: 1.0.8 - resolution: "walker@npm:1.0.8" - dependencies: - makeerror: "npm:1.0.12" - checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e - languageName: node - linkType: hard - "warning@npm:^4.0.2": version: 4.0.3 resolution: "warning@npm:4.0.3" @@ -7639,18 +5035,6 @@ __metadata: languageName: node linkType: hard -"why-is-node-running@npm:^2.3.0": - version: 2.3.0 - resolution: "why-is-node-running@npm:2.3.0" - dependencies: - siginfo: "npm:^2.0.0" - stackback: "npm:0.0.2" - bin: - why-is-node-running: cli.js - checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 - languageName: node - linkType: hard - "word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5" @@ -7658,59 +5042,6 @@ __metadata: languageName: node linkType: hard -"wordwrap@npm:^1.0.0": - version: 1.0.0 - resolution: "wordwrap@npm:1.0.0" - checksum: 10c0/7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 - languageName: node - linkType: hard - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 - languageName: node - linkType: hard - -"wrappy@npm:1": - version: 1.0.2 - resolution: "wrappy@npm:1.0.2" - checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 - languageName: node - linkType: hard - -"write-file-atomic@npm:^5.0.1": - version: 5.0.1 - resolution: "write-file-atomic@npm:5.0.1" - dependencies: - imurmurhash: "npm:^0.1.4" - signal-exit: "npm:^4.0.1" - checksum: 10c0/e8c850a8e3e74eeadadb8ad23c9d9d63e4e792bd10f4836ed74189ef6e996763959f1249c5650e232f3c77c11169d239cbfc8342fc70f3fe401407d23810505d - languageName: node - linkType: hard - -"y18n@npm:^5.0.5": - version: 5.0.8 - resolution: "y18n@npm:5.0.8" - checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 - languageName: node - linkType: hard - "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1" @@ -7734,28 +5065,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.1.1": - version: 21.1.1 - resolution: "yargs-parser@npm:21.1.1" - checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 - languageName: node - linkType: hard - -"yargs@npm:^17.7.2": - version: 17.7.3 - resolution: "yargs@npm:17.7.3" - dependencies: - cliui: "npm:^8.0.1" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.3" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^21.1.1" - checksum: 10c0/7a28572f7e785a57886e34fdbddb9b28756dec552e1453d5f6e7cdd00ad8721a4e8c4321d33683f5e61cacb36ad43258adbb48396b71ec4ed14abee0fc0d0c1f - languageName: node - linkType: hard - "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0"