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 ESPATH?
- 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 has host-specific file objects but no deterministic, host-independent path module equivalent to Node's path.posix and path.win32. ESPATH provides a pure lexical path layer whose results do not depend on process.cwd(), Folder.current, the filesystem, or per-drive ambient state.
POSIX and Windows behavior stay separate. resolve() requires an explicit absolute cwd, relative-relative operations refuse to invent hidden parent segments, and file-URI conversion is a distinct RFC 8089 boundary rather than an alias for Adobe's File.fsName, fullName, or absoluteURI.
- Separate
posixandwin32APIs for normalize, resolve, relative, dirname, basename, extname, and file-URI conversion. resolve(cwd, ...paths)requires an explicit absolute cwd and never reads ambient process or host cwd state.- Seeded differential validation executes 38,769 assertions against
node:path.posixandnode:path.win32with zero divergences in the shared semantic domain. - RFC 8089 conversion handles percent-encoded UTF-8, Windows drive paths, UNC paths, and POSIX absolute paths.
- File-URI parsing rejects query/fragment components, userinfo/ports, malformed UTF-8 escapes, encoded path separators, U+0000, and unpaired surrogates.
- Lexical path scanning preserves embedded U+0000 because it uses
charCodeAt(); file-URI conversion deliberately rejects U+0000. - Runtime code performs no filesystem I/O, reads no host/process environment, patches no built-ins, and has no native/ExternalObject lane.
- The current ESTC artifact is 24,960 bytes and passes both static Acorn ES3 and live Illustrator parsing.
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 ESPATH in an Adobe ExtendScript host | Latest stable | ESPATH.jsx |
| Consuming ESPATH from Node or build tooling | Latest stable | espath-core.esm.mjs |
| Integrating the ESM surface with TypeScript | Latest stable | index.d.ts + path-core.d.ts |
Install development dependencies and build the ESM, declarations, and ExtendScript artifact:
npm install
npm run buildUse the ESM API from development/Node tooling:
import { ESPATH, posix, win32 } from "espath";For ExtendScript, load dist/ESPATH.jsx; it installs $.global.ESPATH. Generated TypeScript declarations are emitted to dist/types/.
Build/test tooling requires Node.js >=20. The runtime artifact itself targets ExtendScript ES3.
import { ESPATH, posix, win32 } from "espath";
posix.normalize("/work//art/../final.ai");
// "/work/final.ai"
win32.resolve("C:\\work\\project", "assets", "..", "final.ai");
// "C:\\work\\project\\final.ai"
win32.toFileURL("C:\\Users\\Ada Lovelace\\Résumé #1\\100%.ai");
// "file:///C:/Users/Ada%20Lovelace/R%C3%A9sum%C3%A9%20%231/100%25.ai"
ESPATH.win32.basename("C:\\work\\final.ai");
// "final.ai"The public PathDialect methods are available on both named APIs and on the default ESPATH facade.
posixrecognizes/;win32recognizes both/and\on input and emits\in normalized results.resolve(cwd, ...paths)never consultsprocess.cwd(),Folder.current, or another ambient cwd. POSIXcwdmust be absolute. Windowscwdmust be a fully qualified drive path or UNC path with a share.relative(from, to)is base-independent. Absolute paths must have compatible root styles; paths on different Windows devices return the normalized destination.- Relative operands are accepted only when their normalized leading
..segment counts match, including zero. A mismatch throwsTypeErrorwith guidance to resolve both paths against the same explicit cwd first. relative()rejects Windows drive-relative inputs.resolve()rejects a drive-relative input that does not match its explicit cwd instead of consulting an ambient per-drive cwd.dirname,basename, andextnameare lexical operations and do not query the filesystem.
posix.relative("../source", "../output/file.ai");
// "../output/file.ai"
posix.relative("../source", "../../output/file.ai");
// throws TypeError: leading parent depths differ; use resolve() with one explicit cwd first
posix.resolve("/work/project", "../output/file.ai");
// "/work/output/file.ai"toFileurl() accepts absolute POSIX paths and fully qualified Windows drive/UNC paths. fromFileurl() accepts file: URIs with percent-encoded UTF-8 path components.
Query/fragment components, userinfo/ports, malformed or invalid UTF-8 escapes, percent-encoded path separators, U+0000, and unpaired surrogates are rejected rather than normalized into ambiguous path data.
File-URI functions are separate from Adobe's File/Folder representations. ESPATH does not reinterpret .fsName, .fullName, .absoluteURI, File.encode(), or File.decode() as scheme-less path APIs.
The live verifier records those Adobe values for comparison without calling exists/open/read/write/copy/move/delete. The current Illustrator result for C:/ESPATH Probe/Résumé 100%/child.txt observed Windows fsName, /c/... fullName, percent-encoded absoluteURI, and a successful encode/decode round trip.
| Check | Command | Result |
|---|---|---|
| TypeScript | npm run typecheck |
clean |
| Fixed vectors + differential | npm test |
38,769 assertions, seed 1337, zero divergences; 224 mismatched-parent-depth cases rejected |
| Static ES3 artifact | npm run estc:static |
24,960-byte ESPATH.jsx passes Acorn ES3 |
| Live parse | npm run estc:live-parse |
passes on Illustrator 30.6.0 / ExtendScript 4.5.6 |
| Live behavior | npm run live-verify |
10/10 path/URI checks pass; reload replacement and varargs forwarding verified; no filesystem operations |
Differential comparisons are made only where ESPATH and Node share the same declared semantics. Relative-relative cases with unequal normalized parent depth assert ESPATH's explicit-cwd error rather than comparing against Node's hidden process cwd.
Node.js v22.23.2 on Windows x64, AMD Ryzen 9 5900X. The adversarial benchmark uses one warmup and five measured samples, reporting median and min/max microseconds per operation. These are Node measurements, not ExtendScript throughput claims.
The cancellation lane accumulates s segments and cancels them with .., exercising the current normalizeTail() lastIndexOf() + slice() pop behavior:
| Dialect | Input UTF-16 units | Canceled segments | Median µs/op (min–max) |
|---|---|---|---|
| POSIX | 1,021 | 204 | 17.49 (15.77–27.19) |
| POSIX | 16,381 | 3,276 | 274.65 (237.90–279.38) |
| POSIX | 65,536 | 13,107 | 973.10 (970.90–1,314.40) |
| Win32 | 1,023 | 204 | 16.57 (15.40–29.13) |
| Win32 | 16,383 | 3,276 | 269.73 (259.50–270.00) |
| Win32 | 65,533 | 13,106 | 1,056.70 (942.00–1,492.10) |
At 65K units, the POSIX deep no-dot path measured 604.87 µs, the already-normalized identity path 203.03 µs, and the separator-heavy path 402.07 µs. For a 65.5K-unit percent-heavy Unicode POSIX path, toFileurl() measured 2,275.00 µs and fromFileurl() 1,514.30 µs; Win32 measured 2,317.33 µs and 1,400.30 µs respectively. URI encode and decode are separate benchmark lanes; toFileurl() includes input normalization and fromFileurl() includes parsing plus final normalization.
The existing string-tail implementation is retained. No array-stack alternative was added or A/B-benchmarked; Node scaling alone is insufficient evidence to replace it given the measured ExtendScript array behavior described below.
The release-candidate live verifier runs on Adobe Illustrator 30.6.0 / ExtendScript 4.5.6. It uses two warmups and seven retained $.hiresTimer samples per lane, discarding non-positive and >10 s samples. The table below is one pre-publication measurement series; repeated release-gate runs are expected to vary with host load.
| Live lane | Input size | Loops/sample | Median µs/op (sampled series) |
|---|---|---|---|
| POSIX short lexical normalize | 1,095 | 300 | 103.72 |
| Win32 short lexical normalize | 1,097 | 300 | 91.64 |
| Win32 long UNC lexical normalize | 1,109 | 30 | 3,637.57 |
| POSIX long lexical normalize | 1,095 | 30 | 3,518.43 |
| Win32 file-URI encode API | 1,115 | 20 | 5,915.00 |
| Win32 file-URI decode API | 1,139 URI units | 20 | 5,100.55 |
The earlier same-host measurement series produced medians of 103.34, 89.76, 3,593.53, 3,548.83, 5,869.60, and 5,094.35 µs/op for the same lanes. Both sets are retained as host/version-specific observations rather than generalized performance guarantees.
ESPATH is a pure lexical data-transform library. It does not execute source, access the filesystem, inspect File.exists, read ambient environment/cwd state, load native code, perform network I/O, or mutate Illustrator documents. It does not patch globals or prototypes.
File-URI decoding validates syntax and UTF-8 rather than passing malformed escapes through. Encoded separators, U+0000, and unpaired surrogates are rejected at the URI boundary to avoid producing ambiguous path values.
| Target | Status |
|---|---|
| ExtendScript ES3 | ESTC-built JSX; static Acorn ES3 gate passes |
| Adobe Illustrator 30.6.0 / ExtendScript 4.5.6 | live parse and 10/10 path/URI behavior checks pass |
| RFC 8089 file URIs | deterministic supported subset with explicit rejection rules |
| Node.js 20+ | ESM/declaration build and differential harness; Node 22.23.2 measured |
| Other ExtendScript hosts | ES3-oriented source; not live-measured here |
The ESTC project profile uses the Illustrator/2022 Types-for-Adobe declarations plus the local src/globals.d.ts overlay as compile-time input; that profile is not a claim that Illustrator 2022 and 2026 are behaviorally identical.
These are inherited sibling-library measurements, not ESPATH measurements. They are scoped to their fixtures and hosts and motivate implementation choices without being extrapolated beyond those workloads.
| Evidence source | Relevant inherited observation |
|---|---|
| ESARR | On Illustrator 30.6.0 / ExtendScript 4.5.6, variable-index array reads scale superlinearly in the measured traversal fixture; a 32K traversal was about 800 ms. ESPATH therefore does not assume an indexed array stack is cheap. |
| ESB64 | In its codec fixtures, array writes measured about 15–25 µs each, charCodeAt about 0.76 µs, and small-piece concatenation about 0.2 µs. Its rope measurements were shape-sensitive. |
| ESON | ESON measured effectively quadratic concatenation in one workload and an ExtendScript regex hang for an anchored alternation/lookahead shape. ESPATH uses scanners rather than regex path parsing and does not generalize the concat result. |
| ESSTR | charAt() returned an empty string at U+0000 while charCodeAt() returned code unit 0; \s also differed from modern trim semantics. ESPATH uses code-unit scanners. |
| ESCHARS | charCodeAt measured about 0.95 µs/unit and one per-unit output transform wedged at 128K after 64K completed. ESPATH keeps those sibling limits separate from its own path scanner evidence. |
| ESTIMER | $.hiresTimer is a delta clock whose first read is not a timestamp. ESPATH's live harness primes the timer and reports multiple samples. |
The live verifier additionally demonstrates that Adobe File.fsName, fullName, and absoluteURI are distinct representations. That host behavior is why ESPATH does not conflate Adobe File properties with RFC 8089 conversion.
npm run build
npm run typecheck
npm test
npm run benchmark
npm run estc:static
npm run estc:live-parse
npm run live-verify
npm run verify
npm run verify:enginenpm run verify is the portable/static gate. npm run verify:engine attaches to the existing local Illustrator/COMTool environment for real-engine evidence.
espath/
├── src/ TypeScript path and URI implementation
├── tests/ fixed, differential, benchmark, and live verification
├── espath-build.mjs ESM/declaration/JSX build
├── extendscript.estc.config.mjs ESTC project configuration
├── package.json
├── tsconfig.json
└── README.md
Generated dist/ outputs are ignored by Git and reproduced by npm run build.
- RFC 8089 for file URI syntax and semantics.
- Node.js
pathdocumentation andnode:path.posix/node:path.win32as the differential oracle for the shared lexical behavior. - docsforadobe / ExtendScript documentation for Adobe
Filerepresentation behavior. - ESTC and the sibling ES-family evidence cited above.
GPL-3.0-or-later. See LICENSE.
ESPATH: ExtendScript Path. Explicit lexical paths and file URIs without ambient cwd or filesystem dependence.