Skip to content

refactor: replace WASM DSL engines with subprocess calls#16

Merged
markovejnovic merged 45 commits into
mainfrom
feat/embedded-ts-python
May 25, 2026
Merged

refactor: replace WASM DSL engines with subprocess calls#16
markovejnovic merged 45 commits into
mainfrom
feat/embedded-ts-python

Conversation

@markovejnovic

@markovejnovic markovejnovic commented May 24, 2026

Copy link
Copy Markdown
Contributor

No description provided.

Add compile-time embedding of the harmont Python DSL package and its
vendored dependencies (croniter, python-dateutil, six) using include_dir.
The WASI Python engine can extract these to a scratch directory without
needing filesystem access to the source tree.
Two-stage pipeline: first rewrites harmont imports to globalThis
destructuring at the text level, then strips TypeScript types via
oxc_transformer (parse → semantic → transform → codegen). Uses
only_remove_type_imports to preserve non-harmont value imports.

Adds oxc_codegen and oxc_semantic as optional deps under the
embedded-typescript feature.
QuickJS 2024-01-13 compiled from source to wasm32-wasip1 using
Homebrew LLVM clang + wasi-libc, with POSIX stubs for features
unavailable in WASI (termios, dlopen, popen, etc.) and an 8MB
WASM stack to support QuickJS's recursive parser.

The WasmJsEngine:
- Bundles quickjs.wasm via include_bytes! (~940KB)
- Evaluates JS scripts via `qjs --std -e <script>`
- Preprocesses .harmont/*.ts files (oxc type-strip + import rewrite)
- Wraps each file in an IIFE with export-default capture
- Concatenates harmont-ts bundle + user code + evaluation footer
- Calls harmont.renderEnvelope() and outputs pipeline JSON

Wired into engine_for(DslLanguage::TypeScript) in lib.rs.
…feature flags

The hm-dsl-engine workspace dependency was not setting
default-features = false, causing wasmtime + all embedded engines to be
pulled in unconditionally regardless of harmont-cli's feature flags.
With this fix, --no-default-features produces a 15 MB binary (same as
before embedded engines were added) instead of 33 MB.
Python and TypeScript interpreters are embedded in the hm binary via
wasmtime, so CI no longer needs to install harmont-py into system
Python. Added npm ci for esbuild (needed by build.rs to bundle
harmont-ts). The python-lint and ts-dsl jobs that test the DSL
libraries themselves are unchanged.
Build scripts legitimately use panic/unwrap/expect for error handling
since there is no runtime to propagate errors to.
@markovejnovic markovejnovic marked this pull request as ready for review May 24, 2026 23:20
…harmont/

All examples ship both pipeline.py and pipeline.ts for backwards
compatibility. Rather than erroring on mixed languages, prefer
TypeScript when both are present.
SystemPythonEngine and SystemNodeEngine were never wired up.
SystemNodeEngine was a complete stub. SystemPythonEngine referenced
a nonexistent cidsl/py path. The embedded WASI engines are the
real implementations.
…ecture

Remove wasmtime/QuickJS/oxc WASM engine infrastructure and replace with
a system-runtime approach using esbuild-bundled TypeScript and embedded
Python sources. This eliminates ~3400 lines of WASM plumbing and 15+
heavy dependencies (wasmtime, wasmtime-wasi, oxc_*, reqwest, sha2, etc).

Tasks 1-4 of the tearout plan:
- Delete 10 WASM engine files (python_engine, js_engine, wasm_runtime,
  runtime_cache, ts_preprocess, build_fetch, sha256-polyfill, tests)
- Simplify Cargo.toml: remove [features], WASM deps; add which, include_dir
- Rewrite build.rs: esbuild-only bundling (two ESM outputs)
- Replace embedded_sources.rs with bundled_sources.rs (Python tree +
  two TS bundles via include_str)
- Stub lib.rs with DslEngine trait (engines wired in Tasks 5-7)
- Make engine_for synchronous; remove .await in caller
- Remove embedded-python/embedded-typescript features from hm crate
Introduces SubprocessPythonEngine which evaluates Python pipeline
definitions by spawning python3 as a subprocess with the bundled
harmont-py source on PYTHONPATH, replacing the old WASM-based approach.
…uation

Detects bun/node on PATH, sets up a temp dir with bundled ESM modules,
and runs a runner.mjs script to list or render pipeline definitions
from .harmont/*.ts files via subprocess.
…process engines

Exercise the full subprocess engine roundtrip (detect language, list
pipelines, render pipeline JSON) for both Python and TypeScript DSLs.
Tests skip gracefully when the required runtime is not on PATH.
…ss engines

All four workflow files (ci, examples, release) no longer reference
wasm32-wasip1 or build embedded WASM plugins -- those were removed in
PR #12. Integration job now installs croniter + python-dateutil for
the Python DSL subprocess engine. CLAUDE.md files updated to document
the new DSL engine architecture (subprocess-based, no WASM).
- Remove stale `default-features = false` from workspace dep
- Delete dead `render.rs` module (replaced by hm-dsl-engine)
- Add `stdin(Stdio::null())` to both subprocess engines
- Remove duplicate `serde_json` from dev-dependencies
@markovejnovic markovejnovic marked this pull request as draft May 25, 2026 06:35
@markovejnovic markovejnovic changed the title feat: embedded wasmtime Python + QuickJS TypeScript DSL engines refactor: replace WASM DSL engines with subprocess calls May 25, 2026
The TS subprocess engine needs a JS runtime. Dogfood also had a stale
wasm32-wasip1 target and installed harmont-py (no longer needed since
detect_language prefers TypeScript when both .py and .ts are present).
Node 22's --experimental-strip-types may not handle dynamic .ts imports.
Bump to Node 23 for dogfood and examples jobs.

Also switch anyhow error formatting from `{e}` (outermost only) to
`{e:#}` (full chain) so JS runtime stderr is visible in error output.
Node ESM resolves bare specifiers relative to the importing file,
ignoring NODE_PATH. User .ts files under .harmont/ need node_modules/harmont
on their resolution path. Create a temporary symlink before subprocess
runs, cleaned up via Drop guard.
Matches the Python pipeline which already had laravel=False. Without
this, the TS-rendered pipeline runs `php artisan test` which fails
because the example has no artisan script.
croniter and python-dateutil are no longer needed at the CLI level —
the Python DSL package bundles its own dependencies and is embedded
in the binary.
@markovejnovic markovejnovic force-pushed the feat/embedded-ts-python branch from 316e869 to 4ff489b Compare May 25, 2026 07:34
@markovejnovic markovejnovic marked this pull request as ready for review May 25, 2026 07:41
@markovejnovic markovejnovic merged commit 8126d9d into main May 25, 2026
24 checks passed
@markovejnovic markovejnovic deleted the feat/embedded-ts-python branch May 25, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant