Maple is a secure JavaScript interpreter written in TypeScript (packages/maple). It comes with an Angular playground
for debugging it (apps/maple-playground). See README.md for the overview.
Run these and make sure each one passes, because CI runs the same checks:
pnpm format && pnpm lint && pnpm typecheck && pnpm testoxlint and oxfmt enforce most of the style. Fix what they report; don't silence it.
- Security is the point. A script must reach nothing but what is passed in
context: no host globals, noevalorFunction, and no escape through prototypes (constructor,__proto__). Never evaluate a script with the host engine. When in doubt, reject the code. Security means isolation: capping a script's CPU time or memory is not a goal for now, so a script that exhausts them is not a security bug. - JavaScript is the reference. What Maple supports behaves as the ECMAScript specification has it, with V8-like
messages; check Node when in doubt. Where security requires otherwise, document the difference in
docs/divergences.md. - Scripts return their result with an explicit
return. The completion value of the last expression is not used. - Library code must run on Node >= 22 and in browsers and web workers alike, so don't use Node or DOM APIs.
acornis a peer dependency. Don't add runtime dependencies without discussing it first.- Use the
.tsextension in relative imports (./scope.ts). - Put tests next to the code as
*.spec.ts(Vitest). Every interpreter feature and every rejected escape needs a test. Specs that exercise a whole language chapter throughMaple.runlive in the directory whose behaviour they cover. docs/is the contract, indexed indocs/README.md; change it in the same commit as the behaviour.- Contributor notes are in the root
CONTRIBUTING.md: read "Adding a built-in" before adding one, and "Evaluator layout" before changing anything insrc/evaluator.
@netwin/maplemaps topackages/maple/src/index.ts, so the app only sees what the entry point exports.- Maple runs in
maple.worker.ts, and for the Benchmark tab inbenchmark.worker.ts. Values cross either worker boundary as JSON text (maple-worker-protocol.ts,benchmark-protocol.ts). @netwin/js-interpreteris a dev dependency used only to compare against;js-interpreter-engine.tsis the one place in the app that calls it.scripts/holds Node-only tools such asmeasure-run.ts. Node ignores thepathsmapping, so they importpackages/maple/src/index.tsby relative path.- Use standalone components and signals, styled with Tailwind. Don't add
.component/.servicesuffixes to file names.
- The package compares Maple with
@netwin/js-interpreterand with Node. Scripts js-interpreter runs should run in Maple with the same result, but JavaScript decides: where js-interpreter differs from it, Maple does what Node does and the difference becomes a documented divergence.src/engines.tsis the one place in the package that runs js-interpreter or Node. - Write each case as an explicit
it:expect(parity(code)).toEqual(value)runs all three engines, requires the same result and returns it;expect(allThrow(code)).toBe('TypeError')requires all three to throw and returns the name of Maple's script error. - Divergences go in
src/divergences.spec.ts: where Maple follows JavaScript and js-interpreter does not asexpect(followsJavaScript(code)).toEqual({ maple, jsInterpreter }), and what Maple rejects asexpect(rejected(code)).toBe(code). Both assert all three engines; add the reason to the README. A divergence where Maple follows JavaScript needs no discussion; one where Maple departs from it does. - Node runs in the test process, last in every helper. A case that changes a built-in (
Math.max = ...) restores it in afinallywith its original descriptor. @netwin/maplemaps topackages/maple/src/index.tsthrough tsconfigpaths, which Vitest reads too.
- Use kebab-case for file names.
- Use
#privatefor state fields only, and spell outpublic/protected/privateon all other members. - Use Conventional Commits with a scope naming the area the commit changes, such
as
evaluator,json,playgroundorparity, or*for the whole repository. Older commits name the package (maple); new ones name the area. The version and changelog come from them, so never editversioninpackages/maple/package.jsonorCHANGELOG.mdby hand. - Don't add attribution to commits or pull requests: no
Co-Authored-By:or other trailers, no "Generated with" lines and no session links.