From 3aaa84047173df13be9a6d5d17a46aed43f1e18c Mon Sep 17 00:00:00 2001 From: bflatastic Date: Wed, 7 Oct 2026 14:31:37 +0700 Subject: [PATCH] Scripting: script.run reports .jsxbin scripts as unsupported (#176) `effectcraft-cli script FILE` passes the file's text to script.run, which ran a compiled .jsxbin script as JavaScript and failed with "SyntaxError: unexpected '@'" at 1:1, while file.runScript already says .jsxbin isn't supported. script.run now refuses a script named `.jsxbin` or whose text starts with the `@JSXBIN@` header, with file.runScript's message (one shared constant). The check is in the command, so the CLI with or without --bridge, MCP run_script and the Script Console all report it. Tests: engine_entry_points (by header and by name), script_file_and_eval (the CLI exits 1 with the message and no SyntaxError). Co-Authored-By: Claude Opus 5.5 --- apps/effectcraft-cli/tests/cli.rs | 7 +++++++ crates/engine/src/commands/file_more.rs | 18 +++++++++++++++--- crates/script/src/tests.rs | 6 ++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/apps/effectcraft-cli/tests/cli.rs b/apps/effectcraft-cli/tests/cli.rs index 6ac475c7..c2827617 100644 --- a/apps/effectcraft-cli/tests/cli.rs +++ b/apps/effectcraft-cli/tests/cli.rs @@ -239,4 +239,11 @@ fn script_file_and_eval() { let (code, v) = run_json(&["script", "--eval", "\nnope()"]); assert_eq!(code, 1); assert_eq!(v["error"]["line"], json!(2)); + // A compiled .jsxbin script is reported as unsupported, not as a SyntaxError (#176). + let bin_script = tmp("compiled.jsxbin"); + std::fs::write(&bin_script, "@JSXBIN@ES@2.0@MyBbyBn0ABJAnAEjzFjBjMjFjSjUBfRBFeFjIjFjMjMjPff0DzACByB\n").unwrap(); + let out = bin().args(["script", bin_script.to_str().unwrap()]).output().unwrap(); + assert_eq!(out.status.code(), Some(1)); + let err = String::from_utf8_lossy(&out.stderr); + assert!(err.contains(".jsxbin scripts are not supported: run the .jsx source") && !err.contains("SyntaxError"), "{err}"); } diff --git a/crates/engine/src/commands/file_more.rs b/crates/engine/src/commands/file_more.rs index ac636076..5b051265 100644 --- a/crates/engine/src/commands/file_more.rs +++ b/crates/engine/src/commands/file_more.rs @@ -438,8 +438,8 @@ fn run_script(s: &mut Session, p: &Value) -> Result { && p.get("steps").is_none() && is_js_script(path) { - if path.to_ascii_lowercase().ends_with(".jsxbin") { - return Err(bad("file.runScript", "binary .jsxbin scripts are not supported: run the .jsx source")); + if is_jsxbin(path) { + return Err(bad("file.runScript", JSXBIN)); } let bytes = s.services.read_file(path).map_err(|e| EngineError::Other(format!("cannot read {path}: {e}")))?; let code = String::from_utf8_lossy(&bytes).to_string(); @@ -487,7 +487,14 @@ fn run_script(s: &mut Session, p: &Value) -> Result { /// command scripts. fn is_js_script(path: &str) -> bool { let l = path.to_ascii_lowercase(); - l.ends_with(".jsx") || l.ends_with(".js") || l.ends_with(".jsxbin") + l.ends_with(".jsx") || l.ends_with(".js") || is_jsxbin(path) +} + +/// Compiled `.jsxbin` scripts are an undocumented binary encoding, not JavaScript. +const JSXBIN: &str = "binary .jsxbin scripts are not supported: run the .jsx source"; + +fn is_jsxbin(name: &str) -> bool { + name.to_ascii_lowercase().ends_with(".jsxbin") } /// A script error becomes a toast and a command error. @@ -519,6 +526,11 @@ fn run_js(s: &mut Session, code: &str, name: &str, console: bool) -> Result Result { let code = str_p(p, "code").ok_or_else(|| bad("script.run", "missing `code` (JavaScript)"))?.to_string(); let name = str_p(p, "name").unwrap_or("script").to_string(); + // A compiled script (named `.jsxbin`, or its text starts with the format's header) would only + // fail to parse at 1:1. + if is_jsxbin(&name) || code.trim_start_matches('\u{feff}').trim_start().starts_with("@JSXBIN@") { + return Err(bad("script.run", JSXBIN)); + } run_js(s, &code, &name, super::b_p(p, "console").unwrap_or(false)) } diff --git a/crates/script/src/tests.rs b/crates/script/src/tests.rs index 8fd6362d..10deb79b 100644 --- a/crates/script/src/tests.rs +++ b/crates/script/src/tests.rs @@ -376,6 +376,12 @@ fn engine_entry_points() { // Scripts can't run scripts. let r = s.execute("script.run", json!({"code": "app.run('script.run', {code: '1'})"})).unwrap(); assert!(r["error"]["message"].as_str().unwrap().contains("scripts can't run scripts"), "{r}"); + // Compiled .jsxbin scripts (by name or by their header) are unsupported, not a SyntaxError + // at 1:1 (#176). + for p in [json!({"code": "@JSXBIN@ES@2.0@MyBbyBn0AB", "name": "compiled.bin"}), json!({"code": "1", "name": "/x/compiled.JSXBIN"})] { + let e = s.execute("script.run", p.clone()).unwrap_err().to_string(); + assert!(e.contains(".jsxbin scripts are not supported: run the .jsx source"), "{p}: {e}"); + } // File ▸ Scripts ▸ Run Script File… runs .jsx as JavaScript. let dir = std::env::temp_dir().join(format!("ec-script-file-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap();