Production-grade infrastructure for Adobe ExtendScript.
|
ESON ESB64 ESARR ESSTR ESCHARS ESHTTP ESTIMER ESRAND ESUUID ESENV ESPATH ESFS ESHASH ESLOG |
ESPACK ESMIN ESABI VectorIPC ESTC ESDB COMTool ESsemble |
Also from the same team: ArcFit.dev, deterministic arc warp for Illustrator.
- Why ESENV?
- Features
- Get the Release
- Installation
- Quick Start
- API
- Validation
- Performance
- Security Model
- Compatibility
- Engine quirks that shaped the design
- Development
- Repository layout
- Credits
- License
ExtendScript code frequently needs to answer questions about the current host without turning those questions into hidden mutations or optimistic guesses. ESENV separates observed host/runtime strings from inferred classifications, documented language lineage, and unknown evidence so callers can preserve the distinction instead of collapsing missing information into false.
The final release candidate was parsed and executed on Adobe Illustrator 30.6.0 / ExtendScript 4.5.6 through COMTool V2. The live verifier observed the application and engine versions, Windows/64 platform, schema version 1, same-artifact facade replacement, and a persistent engine through the explicit two-facade sentinel protocol.
- Four evidence states are explicit:
observed,inferred,documented, andunknown. - Six declared mutable capability probes are supported:
externalObject,socket,bridgeTalk,scriptUI,filesystem, andpersistentEngine. read(name)caches stable primitive facts;refresh()explicitly invalidates them.snapshot()returns a detached schema-version-1 record and never probes mutable capabilities.probe(name)checks one declared capability at a time; unknown capability names fail closed.- The persistence test requires a second facade evaluation before it can report observed persistence, so a facade never certifies its own sentinel write.
- The emitted
dist/ESENV.jsxis 12,839 bytes and passes ESTC's Acorn ES3 compatibility gate. - Node validation passes 11/11 behavioral tests; live validation is separately scoped to Illustrator 30.6.0 / ExtendScript 4.5.6.
All production bundles ship as GitHub release assets — this repo holds sources. Grab the runnable builds from the Releases page.
How it works, in three steps:
- Open the Releases page.
- Pick the latest stable tag.
- Download the asset that matches your use case:
| You are... | Take this release | And this asset |
|---|---|---|
| Loading ESENV in an Adobe ExtendScript host | Latest stable | ESENV.jsx |
Install development dependencies and build the ExtendScript artifact:
npm install
npm run build:jsxLoad dist/ESENV.jsx using the host's normal script-loading mechanism. Each evaluation installs a fresh facade at $.global.ESENV; stable facts are cached inside that facade rather than patched onto host built-ins.
For development and portable tests, npm run build:node emits the Node/V8 test build. Node results are not treated as ExtendScript performance evidence.
var env = $.global.ESENV;
var hostName = env.read("host.name");
var firstSnapshot = env.snapshot();
var socketSurface = env.probe("socket");
env.refresh();
var freshHostName = env.read("host.name");
var freshSocketSurface = env.probe("socket");Facts carry value, evidence, source, and detail. Missing or unreadable values remain unknown rather than becoming false.
read(name)returns a cached primitive string ornullfor one declared stable fact. It does not return host objects or accept arbitrary property paths.snapshot()returns a detached schema-version-1 record. It reads/caches stable facts, copies the most recently explicitly probed capability observations, and leaves unprobed capabilities unknown.refresh()invalidates stable facts and capability observations. The nextread()orsnapshot()recollects stable facts; capabilities remain unknown until explicitly probed again.
Application family and platform family are inferred from retained raw strings. The ECMAScript 3-based ExtendScript language lineage is labeled documented, not runtime-probed.
probe(name) explicitly checks one declared mutable capability and caches that observation. Presence checks report API surface only; they do not promise that the capability can perform a particular operation.
The filesystem probe checks the File/Folder API surface and reads Folder.temp.exists; it does not open, create, or write a file. Host/runtime facts come from a fixed set of documented properties. ESENV does not enumerate arbitrary host objects.
persistentEngine is strictly opt-in. Calling probe("persistentEngine") on facade A writes an ESENV-owned sentinel to $.global and returns unknown. Repeating the probe through facade A remains unknown because that facade cannot certify its own write.
Re-evaluate the ESENV bundle to create facade B over the same $.global. Facade B can observe the exact pre-existing sentinel, return true with observed evidence, and delete it. An occupied sentinel name is never overwritten. If the sentinel does not survive to a new facade, persistence remains unknown.
Neither read() nor snapshot() invokes this probe.
| Check | Command | Result |
|---|---|---|
| Node behavior | npm test |
11/11 tests pass |
| TypeScript | npm run typecheck |
clean |
| JSX build + static ES3 gate | npm run estc:check |
12,839-byte ESENV.jsx passes Acorn ES3 |
| Live parse | npm run estc:live |
passes on Illustrator 30.6.0 / ExtendScript 4.5.6 |
| Live behavior | npm run live-verify |
host/runtime facts, schema v1, persistent-engine protocol, and reload policy verified |
The live lane attaches to an already-running Illustrator target through COMTool V2; a Node or static ESTC result is never relabeled as live-host evidence.
Node.js v22.23.2 / Windows x64 measurements use node:perf_hooks performance.now(), five warmup batches, and nine measured samples. These figures characterize the Node/V8 reference implementation only:
| Lane | Median µs/call | p95 µs/call |
|---|---|---|
| Refreshed snapshot | 15.0000 | 58.2000 |
| Cached scalar read | 0.004535 | 0.005575 |
| Detached snapshot | 1.0080 | 3.4840 |
| Explicit capability probe | 0.1570 | 0.3196 |
Live measurements on Adobe Illustrator 30.6.0 / ExtendScript 4.5.6 use $.hiresTimer as a delta timer with seven repetitions:
| Lane | Work per sample | Median µs/call |
|---|---|---|
| Cold snapshot after refresh | 1 | 77 |
Cached host.family read |
5,000 | 0.7548 |
| Detached evidence snapshot | 100 | 34.14 |
| Socket surface probe | 1,000 | 9.349 |
The cached scalar path is intentionally small and passive. Capability work remains explicit so an ordinary read does not silently turn into host reflection or state mutation.
ESENV performs no eval, document mutation, network request, native loading, or arbitrary property traversal. It does not patch built-ins or prototypes. The only intentional state mutation is the namespaced persistence sentinel used by the explicit persistentEngine probe; that sentinel is never written by read() or snapshot(), never overwrites an occupied name, and is removed when a later facade successfully observes it.
The filesystem capability probe checks API presence and Folder.temp.exists only. It does not open, create, read, write, copy, rename, or delete files.
| Target | Status |
|---|---|
| ExtendScript ES3 | ESTC-built JSX; Acorn ES3 static gate passes |
| Adobe Illustrator 30.6.0 / ExtendScript 4.5.6 | live parse and capability/identity verification pass |
| Persistent Illustrator engine | observed through the two-facade sentinel protocol |
| Other ExtendScript hosts | ES3-oriented implementation; not live-measured here |
| Node.js 20+ | portable development/test build; Node 22.23.2 measured |
These inherited findings are evidence from sibling libraries, not new ESENV measurements. Unless a linked source states otherwise, the live observations are scoped to Illustrator 30.6.0 / ExtendScript 4.5.6.
| Source | Relevant measured finding | ESENV consequence |
|---|---|---|
| ESARR | ES5 array methods and later additions were absent; variable-index array reads became superlinear in long traversals. | Stable reads stay scalar and cached; runtime code does not rely on ES5 array methods. |
| ESB64 | charAt() returned an empty string for U+0000 while charCodeAt() preserved the code unit; NUL-bearing property keys collided. |
Host strings are values, not ad-hoc property keys, and are not scanned through charAt(). |
| ESON | The measured engine lacked dependable native JSON, Object.defineProperty, Function.prototype.bind, and Array.prototype.indexOf surfaces. |
ESENV avoids those APIs and has no JSON or descriptor dependency. |
| ESSTR | Regex \s differed from modern trim semantics for U+180E/U+FEFF; non-strict .call(null) bound this to the global object. |
ESENV does not normalize host text with regex whitespace rules or use .call() for capability access. |
| ESCHARS | A per-unit charCodeAt/array-write transform wedged at inputs of 128 KiB or larger; the ExternalObject string channel truncated at NUL and could not carry lone surrogates. |
ESENV stays on short environment fields and has no bulk transform or native/ESABI lane. |
| ESTIMER | $.hiresTimer is a delta clock rather than an absolute timestamp. |
Live timing uses primed, bounded delta samples; Node timing is reported separately. |
ESENV-specific live evidence confirmed the same artifact can be re-evaluated to replace the global facade while the engine itself persists, which is why cache ownership belongs to the facade and persistence certification requires a new facade instance.
npm run build:node
npm run typecheck
npm test
npm run benchmark:node
npm run build:jsx
npm run estc:check
npm run estc:live
npm run live-verify
npm run benchmark:live
npm run verify
npm run verify:enginenpm run verify is the portable/static gate. npm run verify:engine is the separate local real-engine gate.
esenv/
├── src/ TypeScript core, facade entry, and types
├── tests/ Node tests plus COMTool live verifier/benchmark
├── extendscript.estc.config.mjs ESTC project configuration
├── BUILD.md build and evidence-lane notes
├── package.json
└── README.md
Generated build/ and dist/ artifacts are intentionally ignored by Git.
- docsforadobe / ExtendScript documentation for host runtime and file-system reference material.
- ESTC for TypeScript-to-ExtendScript emission and compatibility validation.
- The sibling ES-family repositories cited above for the measured engine constraints that informed the implementation.
GPL-3.0-or-later. See LICENSE.
ESENV: ExtendScript Environment. Evidence-oriented host and capability facts without hidden probing.