From 73ee09e5471bc1d028ead73ef7343b3f690e95a2 Mon Sep 17 00:00:00 2001 From: ohah Date: Tue, 28 Oct 2025 17:32:25 +0900 Subject: [PATCH 1/4] feat: create deno-runtime crate - Add independent deno-runtime crate for JavaScript execution - Include DenoExecutor with V8-based JavaScript runtime - Add bootstrap.js for custom JavaScript APIs (console, alert, require) - Support npm module simulation (lodash, moment, uuid) - Include comprehensive test suite for JavaScript execution --- crates/deno-runtime/Cargo.toml | 20 ++ crates/deno-runtime/src/bootstrap.js | 149 ++++++++++++++ crates/deno-runtime/src/lib.rs | 287 +++++++++++++++++++++++++++ 3 files changed, 456 insertions(+) create mode 100644 crates/deno-runtime/Cargo.toml create mode 100644 crates/deno-runtime/src/bootstrap.js create mode 100644 crates/deno-runtime/src/lib.rs diff --git a/crates/deno-runtime/Cargo.toml b/crates/deno-runtime/Cargo.toml new file mode 100644 index 0000000..9895d51 --- /dev/null +++ b/crates/deno-runtime/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "deno-runtime" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +description = "Deno Core 기반 JavaScript 런타임" + +[dependencies] +# Workspace dependencies +anyhow.workspace = true +tokio.workspace = true +tracing.workspace = true + +# Deno Core dependencies +deno_core = "0.323" + +[dev-dependencies] +tokio.workspace = true diff --git a/crates/deno-runtime/src/bootstrap.js b/crates/deno-runtime/src/bootstrap.js new file mode 100644 index 0000000..7300fa3 --- /dev/null +++ b/crates/deno-runtime/src/bootstrap.js @@ -0,0 +1,149 @@ +// ExecuteJS 커스텀 런타임 bootstrap +const { core } = Deno; +const { ops } = core; + +// console 객체 정의 +globalThis.console = { + log: (...args) => { + const message = args + .map((arg) => { + if (typeof arg === 'object') { + return JSON.stringify(arg, null, 2); + } + return String(arg); + }) + .join(' '); + ops.op_console_log(message); + }, + + error: (...args) => { + const message = args + .map((arg) => { + if (typeof arg === 'object') { + return JSON.stringify(arg, null, 2); + } + return String(arg); + }) + .join(' '); + ops.op_custom_print(message, true); + }, + + warn: (...args) => { + const message = args + .map((arg) => { + if (typeof arg === 'object') { + return JSON.stringify(arg, null, 2); + } + return String(arg); + }) + .join(' '); + ops.op_custom_print(`[WARN] ${message}`, false); + }, + + info: (...args) => { + const message = args + .map((arg) => { + if (typeof arg === 'object') { + return JSON.stringify(arg, null, 2); + } + return String(arg); + }) + .join(' '); + ops.op_custom_print(`[INFO] ${message}`, false); + }, +}; + +// alert 함수 정의 +globalThis.alert = (message) => { + ops.op_alert(String(message)); +}; + +// print 함수 정의 (Deno.core.print 대체) +globalThis.print = (message, isErr = false) => { + ops.op_custom_print(String(message), isErr); +}; + +// npm 모듈 지원을 위한 require 함수 정의 (시뮬레이션) +globalThis.require = (moduleName) => { + // 간단한 npm 모듈 시뮬레이션 + const modules = { + lodash: { + map: (array, iteratee) => { + if (!Array.isArray(array)) { + throw new Error('First argument must be an array'); + } + return array.map(iteratee); + }, + filter: (array, predicate) => { + if (!Array.isArray(array)) { + throw new Error('First argument must be an array'); + } + return array.filter(predicate); + }, + reduce: (array, iteratee, accumulator) => { + if (!Array.isArray(array)) { + throw new Error('First argument must be an array'); + } + return array.reduce(iteratee, accumulator); + }, + find: (array, predicate) => { + if (!Array.isArray(array)) { + throw new Error('First argument must be an array'); + } + return array.find(predicate); + }, + chunk: (array, size) => { + if (!Array.isArray(array)) { + throw new Error('First argument must be an array'); + } + const chunks = []; + for (let i = 0; i < array.length; i += size) { + chunks.push(array.slice(i, i + size)); + } + return chunks; + }, + }, + moment: { + now: () => new Date(), + format: (date, format) => { + if (!(date instanceof Date)) { + date = new Date(date); + } + return date.toISOString(); + }, + }, + uuid: { + v4: () => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0; + const v = c == 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); + }, + }, + }; + + if (modules[moduleName]) { + return modules[moduleName]; + } + + throw new Error(`Cannot find module '${moduleName}'. Available modules: ${Object.keys(modules).join(', ')}`); +}; + +// 기본적인 전역 객체들 정의 +if (typeof globalThis.window === 'undefined') { + globalThis.window = globalThis; +} + +if (typeof globalThis.global === 'undefined') { + globalThis.global = globalThis; +} + +// Node.js 스타일 모듈 시스템 지원 +if (typeof globalThis.module === 'undefined') { + globalThis.module = { exports: {} }; +} + +if (typeof globalThis.exports === 'undefined') { + globalThis.exports = globalThis.module.exports; +} diff --git a/crates/deno-runtime/src/lib.rs b/crates/deno-runtime/src/lib.rs new file mode 100644 index 0000000..8167c33 --- /dev/null +++ b/crates/deno-runtime/src/lib.rs @@ -0,0 +1,287 @@ +use anyhow::Result; +use deno_core::error::AnyError; +use deno_core::{extension, op2, FsModuleLoader, JsRuntime, RuntimeOptions}; +use std::collections::VecDeque; +use std::rc::Rc; +use std::sync::Arc; +use std::sync::Mutex; + +/// JavaScript 실행 결과를 저장하는 구조체 +#[derive(Debug, Clone)] +pub struct ExecutionOutput { + pub stdout: VecDeque, + pub stderr: VecDeque, +} + +impl ExecutionOutput { + pub fn new() -> Self { + Self { + stdout: VecDeque::new(), + stderr: VecDeque::new(), + } + } + + pub fn add_stdout(&mut self, message: String) { + self.stdout.push_back(message); + } + + pub fn add_stderr(&mut self, message: String) { + self.stderr.push_back(message); + } + + pub fn get_output(&self) -> String { + let mut output = Vec::new(); + + for line in &self.stdout { + output.push(line.clone()); + } + + for line in &self.stderr { + output.push(format!("[ERROR] {}", line)); + } + + output.join("\n") + } +} + +/// 전역 출력 버퍼 (스레드 안전) +static OUTPUT_BUFFER: Mutex>>> = Mutex::new(None); + +/// console.log를 위한 op 함수 +#[op2(fast)] +#[string] +fn op_console_log(#[string] message: String) -> Result<(), AnyError> { + if let Ok(buffer_guard) = OUTPUT_BUFFER.lock() { + if let Some(buffer) = buffer_guard.as_ref() { + if let Ok(mut output) = buffer.lock() { + output.add_stdout(message); + } + } + } + Ok(()) +} + +/// alert를 위한 op 함수 +#[op2(fast)] +#[string] +fn op_alert(#[string] message: String) -> Result<(), AnyError> { + if let Ok(buffer_guard) = OUTPUT_BUFFER.lock() { + if let Some(buffer) = buffer_guard.as_ref() { + if let Ok(mut output) = buffer.lock() { + output.add_stdout(format!("[ALERT] {}", message)); + } + } + } + Ok(()) +} + +/// print를 위한 op 함수 (Deno.core.print 대체) +#[op2(fast)] +#[string] +fn op_custom_print(#[string] message: String, is_err: bool) -> Result<(), AnyError> { + if let Ok(buffer_guard) = OUTPUT_BUFFER.lock() { + if let Some(buffer) = buffer_guard.as_ref() { + if let Ok(mut output) = buffer.lock() { + if is_err { + output.add_stderr(message); + } else { + output.add_stdout(message); + } + } + } + } + Ok(()) +} + +/// 커스텀 확장 정의 +extension!( + executejs_runtime, + ops = [op_console_log, op_alert, op_custom_print], +); + +/// JavaScript 실행기 (Deno Core 기반) +pub struct DenoExecutor { + output_buffer: Arc>, +} + +impl DenoExecutor { + /// 새로운 DenoExecutor 인스턴스 생성 + pub async fn new() -> Result { + // 출력 버퍼 생성 + let output_buffer = Arc::new(Mutex::new(ExecutionOutput::new())); + + // 전역 버퍼에 설정 + { + let mut global_buffer = OUTPUT_BUFFER.lock().unwrap(); + *global_buffer = Some(output_buffer.clone()); + } + + Ok(Self { output_buffer }) + } + + /// JavaScript 코드 실행 + pub async fn execute_script(&mut self, _filename: &str, code: &str) -> Result { + // 출력 버퍼 초기화 + { + let mut output = self.output_buffer.lock().unwrap(); + *output = ExecutionOutput::new(); + } + + // 코드를 클로저로 캡처 + let code = code.to_string(); + let output_buffer = self.output_buffer.clone(); + + // 별도 스레드에서 Deno Core 실행 (Send 트레이트 문제 해결) + let result = tokio::task::spawn_blocking(move || { + // JsRuntime 생성 + let mut js_runtime = JsRuntime::new(RuntimeOptions { + module_loader: Some(Rc::new(FsModuleLoader)), + extensions: vec![executejs_runtime::init_ops()], + ..Default::default() + }); + + // bootstrap.js 실행하여 커스텀 API 설정 + let bootstrap_code = include_str!("bootstrap.js"); + if let Err(e) = js_runtime.execute_script("[executejs:bootstrap.js]", bootstrap_code) { + return Err(anyhow::anyhow!("Bootstrap 실행 실패: {}", e)); + } + + // 코드 실행 + let result = js_runtime.execute_script("[executejs:user_code]", code)?; + + // 이벤트 루프 실행 (Promise 처리) - 블로킹 방식으로 변경 + let rt = tokio::runtime::Handle::current(); + rt.block_on(async { js_runtime.run_event_loop(Default::default()).await })?; + + // 결과 처리 + let _ = result; + + // 출력 버퍼에서 결과 가져오기 + let output = output_buffer.lock().unwrap(); + let result_text = output.get_output(); + + if result_text.is_empty() { + Ok("코드가 실행되었습니다.".to_string()) + } else { + Ok(result_text) + } + }) + .await + .map_err(|e| anyhow::anyhow!("스레드 실행 실패: {}", e))?; + + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + use tokio; + + // 테스트 간 격리를 위한 락 + static TEST_LOCK: Mutex<()> = Mutex::new(()); + + #[tokio::test] + async fn test_console_log() { + let _lock = TEST_LOCK.lock().unwrap(); + let mut executor = DenoExecutor::new().await.unwrap(); + let result = executor + .execute_script("test.js", "console.log('Hello World');") + .await; + assert!(result.is_ok()); + let output = result.unwrap(); + println!("실제 출력: '{}'", output); + assert!(output.contains("Hello World")); + } + + #[tokio::test] + async fn test_alert() { + let _lock = TEST_LOCK.lock().unwrap(); + let mut executor = DenoExecutor::new().await.unwrap(); + let result = executor + .execute_script("test.js", "alert('Hello Alert');") + .await; + assert!(result.is_ok()); + let output = result.unwrap(); + println!("실제 출력: '{}'", output); + assert!(output.contains("[ALERT] Hello Alert")); + } + + #[tokio::test] + async fn test_variable_assignment() { + let _lock = TEST_LOCK.lock().unwrap(); + let mut executor = DenoExecutor::new().await.unwrap(); + let result = executor + .execute_script("test.js", "let a = 5; console.log(a);") + .await; + assert!(result.is_ok()); + let output = result.unwrap(); + println!("실제 출력: '{}'", output); + assert!(output.contains("5")); + } + + #[tokio::test] + async fn test_calculation() { + let _lock = TEST_LOCK.lock().unwrap(); + let mut executor = DenoExecutor::new().await.unwrap(); + let result = executor + .execute_script("test.js", "let a = 1; let b = 2; console.log(a + b);") + .await; + assert!(result.is_ok()); + let output = result.unwrap(); + println!("실제 출력: '{}'", output); + assert!(output.contains("3")); + } + + #[tokio::test] + async fn test_syntax_error() { + let _lock = TEST_LOCK.lock().unwrap(); + let mut executor = DenoExecutor::new().await.unwrap(); + let result = executor.execute_script("test.js", "alert('adf'(;").await; + // 문법 오류는 실행 실패를 반환해야 함 + assert!(result.is_err()); + } + + #[tokio::test] + async fn test_multiple_statements() { + let _lock = TEST_LOCK.lock().unwrap(); + let mut executor = DenoExecutor::new().await.unwrap(); + let result = executor + .execute_script( + "test.js", + "let x = 5; let y = 3; console.log('result:', x + y);", + ) + .await; + assert!(result.is_ok()); + let output = result.unwrap(); + println!("실제 출력: '{}'", output); + assert!(output.contains("result: 8")); + } + + #[tokio::test] + async fn test_lodash_import() { + let _lock = TEST_LOCK.lock().unwrap(); + let mut executor = DenoExecutor::new().await.unwrap(); + let result = executor + .execute_script( + "test.js", + r#" + try { + const _ = require('lodash'); + const numbers = [1, 2, 3, 4, 5]; + const doubled = _.map(numbers, n => n * 2); + console.log('Lodash test:', doubled); + } catch (error) { + console.log('Lodash not available:', error.message); + } + "#, + ) + .await; + assert!(result.is_ok()); + let output = result.unwrap(); + println!("Lodash 테스트 출력: '{}'", output); + // lodash가 사용 가능한지 또는 오류 메시지가 나오는지 확인 + assert!(output.contains("Lodash test:") || output.contains("Lodash not available:")); + } +} From 7495319a3026f2fb2e64c442d89f31fd91b0c73d Mon Sep 17 00:00:00 2001 From: ohah Date: Tue, 28 Oct 2025 17:32:37 +0900 Subject: [PATCH 2/4] refactor: update Tauri app to use deno-runtime crate - Replace direct deno_core dependency with deno-runtime crate - Use local path dependency for better code organization - Maintain same JavaScript execution functionality --- apps/executeJS/src-tauri/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/executeJS/src-tauri/Cargo.toml b/apps/executeJS/src-tauri/Cargo.toml index 506b77c..e830059 100644 --- a/apps/executeJS/src-tauri/Cargo.toml +++ b/apps/executeJS/src-tauri/Cargo.toml @@ -45,7 +45,7 @@ tokio-stream = "0.1" flate2 = "1.1" # JavaScript 런타임 의존성 (Deno Core) -deno_core = "0.323" +deno-runtime = { path = "../../../crates/deno-runtime" } [features] # this feature is used for production builds or when `devPath` points to the filesystem From 6569c1de646960ef9d26d4e16e5781afb4f132cd Mon Sep 17 00:00:00 2001 From: ohah Date: Tue, 28 Oct 2025 17:32:42 +0900 Subject: [PATCH 3/4] refactor: remove local deno files and update imports - Remove deno_runtime.rs and bootstrap.js from Tauri app - Update js_executor.rs to import DenoExecutor from deno-runtime crate - Remove deno_runtime module declaration from lib.rs - Clean up unused imports in commands.rs and js_executor.rs - Maintain same JavaScript execution API --- apps/executeJS/src-tauri/src/bootstrap.js | 154 ---------- apps/executeJS/src-tauri/src/commands.rs | 1 - apps/executeJS/src-tauri/src/deno_runtime.rs | 287 ------------------- apps/executeJS/src-tauri/src/js_executor.rs | 4 +- apps/executeJS/src-tauri/src/lib.rs | 1 - 5 files changed, 1 insertion(+), 446 deletions(-) delete mode 100644 apps/executeJS/src-tauri/src/bootstrap.js delete mode 100644 apps/executeJS/src-tauri/src/deno_runtime.rs diff --git a/apps/executeJS/src-tauri/src/bootstrap.js b/apps/executeJS/src-tauri/src/bootstrap.js deleted file mode 100644 index 4299994..0000000 --- a/apps/executeJS/src-tauri/src/bootstrap.js +++ /dev/null @@ -1,154 +0,0 @@ -// ExecuteJS 커스텀 런타임 bootstrap -const { core } = Deno; -const { ops } = core; - -// console 객체 정의 -globalThis.console = { - log: (...args) => { - const message = args - .map((arg) => { - if (typeof arg === 'object') { - return JSON.stringify(arg, null, 2); - } - return String(arg); - }) - .join(' '); - ops.op_console_log(message); - }, - - error: (...args) => { - const message = args - .map((arg) => { - if (typeof arg === 'object') { - return JSON.stringify(arg, null, 2); - } - return String(arg); - }) - .join(' '); - ops.op_custom_print(message, true); - }, - - warn: (...args) => { - const message = args - .map((arg) => { - if (typeof arg === 'object') { - return JSON.stringify(arg, null, 2); - } - return String(arg); - }) - .join(' '); - ops.op_custom_print(`[WARN] ${message}`, false); - }, - - info: (...args) => { - const message = args - .map((arg) => { - if (typeof arg === 'object') { - return JSON.stringify(arg, null, 2); - } - return String(arg); - }) - .join(' '); - ops.op_custom_print(`[INFO] ${message}`, false); - }, -}; - -// alert 함수 정의 -globalThis.alert = (message) => { - ops.op_alert(String(message)); -}; - -// print 함수 정의 (Deno.core.print 대체) -globalThis.print = (message, isErr = false) => { - ops.op_custom_print(String(message), isErr); -}; - -// npm 모듈 지원을 위한 require 함수 정의 (시뮬레이션) -globalThis.require = (moduleName) => { - // 간단한 npm 모듈 시뮬레이션 - const modules = { - lodash: { - map: (array, iteratee) => { - if (!Array.isArray(array)) { - throw new Error('First argument must be an array'); - } - return array.map(iteratee); - }, - filter: (array, predicate) => { - if (!Array.isArray(array)) { - throw new Error('First argument must be an array'); - } - return array.filter(predicate); - }, - reduce: (array, iteratee, accumulator) => { - if (!Array.isArray(array)) { - throw new Error('First argument must be an array'); - } - return array.reduce(iteratee, accumulator); - }, - find: (array, predicate) => { - if (!Array.isArray(array)) { - throw new Error('First argument must be an array'); - } - return array.find(predicate); - }, - chunk: (array, size) => { - if (!Array.isArray(array)) { - throw new Error('First argument must be an array'); - } - const chunks = []; - for (let i = 0; i < array.length; i += size) { - chunks.push(array.slice(i, i + size)); - } - return chunks; - }, - }, - moment: { - now: () => new Date(), - format: (date, format) => { - if (!(date instanceof Date)) { - date = new Date(date); - } - return date.toISOString(); - }, - }, - uuid: { - v4: () => { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( - /[xy]/g, - function (c) { - const r = (Math.random() * 16) | 0; - const v = c == 'x' ? r : (r & 0x3) | 0x8; - return v.toString(16); - } - ); - }, - }, - }; - - if (modules[moduleName]) { - return modules[moduleName]; - } - - throw new Error( - `Cannot find module '${moduleName}'. Available modules: ${Object.keys(modules).join(', ')}` - ); -}; - -// 기본적인 전역 객체들 정의 -if (typeof globalThis.window === 'undefined') { - globalThis.window = globalThis; -} - -if (typeof globalThis.global === 'undefined') { - globalThis.global = globalThis; -} - -// Node.js 스타일 모듈 시스템 지원 -if (typeof globalThis.module === 'undefined') { - globalThis.module = { exports: {} }; -} - -if (typeof globalThis.exports === 'undefined') { - globalThis.exports = globalThis.module.exports; -} diff --git a/apps/executeJS/src-tauri/src/commands.rs b/apps/executeJS/src-tauri/src/commands.rs index 2cafdaa..dd8928e 100644 --- a/apps/executeJS/src-tauri/src/commands.rs +++ b/apps/executeJS/src-tauri/src/commands.rs @@ -1,6 +1,5 @@ use crate::js_executor::{execute_javascript_code, JsExecutionResult}; use serde::{Deserialize, Serialize}; -use tauri::State; #[derive(Debug, Serialize, Deserialize)] pub struct AppInfo { diff --git a/apps/executeJS/src-tauri/src/deno_runtime.rs b/apps/executeJS/src-tauri/src/deno_runtime.rs deleted file mode 100644 index 8167c33..0000000 --- a/apps/executeJS/src-tauri/src/deno_runtime.rs +++ /dev/null @@ -1,287 +0,0 @@ -use anyhow::Result; -use deno_core::error::AnyError; -use deno_core::{extension, op2, FsModuleLoader, JsRuntime, RuntimeOptions}; -use std::collections::VecDeque; -use std::rc::Rc; -use std::sync::Arc; -use std::sync::Mutex; - -/// JavaScript 실행 결과를 저장하는 구조체 -#[derive(Debug, Clone)] -pub struct ExecutionOutput { - pub stdout: VecDeque, - pub stderr: VecDeque, -} - -impl ExecutionOutput { - pub fn new() -> Self { - Self { - stdout: VecDeque::new(), - stderr: VecDeque::new(), - } - } - - pub fn add_stdout(&mut self, message: String) { - self.stdout.push_back(message); - } - - pub fn add_stderr(&mut self, message: String) { - self.stderr.push_back(message); - } - - pub fn get_output(&self) -> String { - let mut output = Vec::new(); - - for line in &self.stdout { - output.push(line.clone()); - } - - for line in &self.stderr { - output.push(format!("[ERROR] {}", line)); - } - - output.join("\n") - } -} - -/// 전역 출력 버퍼 (스레드 안전) -static OUTPUT_BUFFER: Mutex>>> = Mutex::new(None); - -/// console.log를 위한 op 함수 -#[op2(fast)] -#[string] -fn op_console_log(#[string] message: String) -> Result<(), AnyError> { - if let Ok(buffer_guard) = OUTPUT_BUFFER.lock() { - if let Some(buffer) = buffer_guard.as_ref() { - if let Ok(mut output) = buffer.lock() { - output.add_stdout(message); - } - } - } - Ok(()) -} - -/// alert를 위한 op 함수 -#[op2(fast)] -#[string] -fn op_alert(#[string] message: String) -> Result<(), AnyError> { - if let Ok(buffer_guard) = OUTPUT_BUFFER.lock() { - if let Some(buffer) = buffer_guard.as_ref() { - if let Ok(mut output) = buffer.lock() { - output.add_stdout(format!("[ALERT] {}", message)); - } - } - } - Ok(()) -} - -/// print를 위한 op 함수 (Deno.core.print 대체) -#[op2(fast)] -#[string] -fn op_custom_print(#[string] message: String, is_err: bool) -> Result<(), AnyError> { - if let Ok(buffer_guard) = OUTPUT_BUFFER.lock() { - if let Some(buffer) = buffer_guard.as_ref() { - if let Ok(mut output) = buffer.lock() { - if is_err { - output.add_stderr(message); - } else { - output.add_stdout(message); - } - } - } - } - Ok(()) -} - -/// 커스텀 확장 정의 -extension!( - executejs_runtime, - ops = [op_console_log, op_alert, op_custom_print], -); - -/// JavaScript 실행기 (Deno Core 기반) -pub struct DenoExecutor { - output_buffer: Arc>, -} - -impl DenoExecutor { - /// 새로운 DenoExecutor 인스턴스 생성 - pub async fn new() -> Result { - // 출력 버퍼 생성 - let output_buffer = Arc::new(Mutex::new(ExecutionOutput::new())); - - // 전역 버퍼에 설정 - { - let mut global_buffer = OUTPUT_BUFFER.lock().unwrap(); - *global_buffer = Some(output_buffer.clone()); - } - - Ok(Self { output_buffer }) - } - - /// JavaScript 코드 실행 - pub async fn execute_script(&mut self, _filename: &str, code: &str) -> Result { - // 출력 버퍼 초기화 - { - let mut output = self.output_buffer.lock().unwrap(); - *output = ExecutionOutput::new(); - } - - // 코드를 클로저로 캡처 - let code = code.to_string(); - let output_buffer = self.output_buffer.clone(); - - // 별도 스레드에서 Deno Core 실행 (Send 트레이트 문제 해결) - let result = tokio::task::spawn_blocking(move || { - // JsRuntime 생성 - let mut js_runtime = JsRuntime::new(RuntimeOptions { - module_loader: Some(Rc::new(FsModuleLoader)), - extensions: vec![executejs_runtime::init_ops()], - ..Default::default() - }); - - // bootstrap.js 실행하여 커스텀 API 설정 - let bootstrap_code = include_str!("bootstrap.js"); - if let Err(e) = js_runtime.execute_script("[executejs:bootstrap.js]", bootstrap_code) { - return Err(anyhow::anyhow!("Bootstrap 실행 실패: {}", e)); - } - - // 코드 실행 - let result = js_runtime.execute_script("[executejs:user_code]", code)?; - - // 이벤트 루프 실행 (Promise 처리) - 블로킹 방식으로 변경 - let rt = tokio::runtime::Handle::current(); - rt.block_on(async { js_runtime.run_event_loop(Default::default()).await })?; - - // 결과 처리 - let _ = result; - - // 출력 버퍼에서 결과 가져오기 - let output = output_buffer.lock().unwrap(); - let result_text = output.get_output(); - - if result_text.is_empty() { - Ok("코드가 실행되었습니다.".to_string()) - } else { - Ok(result_text) - } - }) - .await - .map_err(|e| anyhow::anyhow!("스레드 실행 실패: {}", e))?; - - result - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::Mutex; - use tokio; - - // 테스트 간 격리를 위한 락 - static TEST_LOCK: Mutex<()> = Mutex::new(()); - - #[tokio::test] - async fn test_console_log() { - let _lock = TEST_LOCK.lock().unwrap(); - let mut executor = DenoExecutor::new().await.unwrap(); - let result = executor - .execute_script("test.js", "console.log('Hello World');") - .await; - assert!(result.is_ok()); - let output = result.unwrap(); - println!("실제 출력: '{}'", output); - assert!(output.contains("Hello World")); - } - - #[tokio::test] - async fn test_alert() { - let _lock = TEST_LOCK.lock().unwrap(); - let mut executor = DenoExecutor::new().await.unwrap(); - let result = executor - .execute_script("test.js", "alert('Hello Alert');") - .await; - assert!(result.is_ok()); - let output = result.unwrap(); - println!("실제 출력: '{}'", output); - assert!(output.contains("[ALERT] Hello Alert")); - } - - #[tokio::test] - async fn test_variable_assignment() { - let _lock = TEST_LOCK.lock().unwrap(); - let mut executor = DenoExecutor::new().await.unwrap(); - let result = executor - .execute_script("test.js", "let a = 5; console.log(a);") - .await; - assert!(result.is_ok()); - let output = result.unwrap(); - println!("실제 출력: '{}'", output); - assert!(output.contains("5")); - } - - #[tokio::test] - async fn test_calculation() { - let _lock = TEST_LOCK.lock().unwrap(); - let mut executor = DenoExecutor::new().await.unwrap(); - let result = executor - .execute_script("test.js", "let a = 1; let b = 2; console.log(a + b);") - .await; - assert!(result.is_ok()); - let output = result.unwrap(); - println!("실제 출력: '{}'", output); - assert!(output.contains("3")); - } - - #[tokio::test] - async fn test_syntax_error() { - let _lock = TEST_LOCK.lock().unwrap(); - let mut executor = DenoExecutor::new().await.unwrap(); - let result = executor.execute_script("test.js", "alert('adf'(;").await; - // 문법 오류는 실행 실패를 반환해야 함 - assert!(result.is_err()); - } - - #[tokio::test] - async fn test_multiple_statements() { - let _lock = TEST_LOCK.lock().unwrap(); - let mut executor = DenoExecutor::new().await.unwrap(); - let result = executor - .execute_script( - "test.js", - "let x = 5; let y = 3; console.log('result:', x + y);", - ) - .await; - assert!(result.is_ok()); - let output = result.unwrap(); - println!("실제 출력: '{}'", output); - assert!(output.contains("result: 8")); - } - - #[tokio::test] - async fn test_lodash_import() { - let _lock = TEST_LOCK.lock().unwrap(); - let mut executor = DenoExecutor::new().await.unwrap(); - let result = executor - .execute_script( - "test.js", - r#" - try { - const _ = require('lodash'); - const numbers = [1, 2, 3, 4, 5]; - const doubled = _.map(numbers, n => n * 2); - console.log('Lodash test:', doubled); - } catch (error) { - console.log('Lodash not available:', error.message); - } - "#, - ) - .await; - assert!(result.is_ok()); - let output = result.unwrap(); - println!("Lodash 테스트 출력: '{}'", output); - // lodash가 사용 가능한지 또는 오류 메시지가 나오는지 확인 - assert!(output.contains("Lodash test:") || output.contains("Lodash not available:")); - } -} diff --git a/apps/executeJS/src-tauri/src/js_executor.rs b/apps/executeJS/src-tauri/src/js_executor.rs index 26a5606..a5997be 100644 --- a/apps/executeJS/src-tauri/src/js_executor.rs +++ b/apps/executeJS/src-tauri/src/js_executor.rs @@ -1,7 +1,5 @@ -use crate::deno_runtime::DenoExecutor; +use deno_runtime::DenoExecutor; use serde::{Deserialize, Serialize}; -use std::collections::VecDeque; -use std::sync::Mutex; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JsExecutionResult { diff --git a/apps/executeJS/src-tauri/src/lib.rs b/apps/executeJS/src-tauri/src/lib.rs index ee6f008..e06a972 100644 --- a/apps/executeJS/src-tauri/src/lib.rs +++ b/apps/executeJS/src-tauri/src/lib.rs @@ -4,7 +4,6 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod commands; -mod deno_runtime; mod js_executor; use commands::*; From a43d69b9bbbf61d69ac989be9f00a327764245ce Mon Sep 17 00:00:00 2001 From: ohah Date: Tue, 28 Oct 2025 17:32:47 +0900 Subject: [PATCH 4/4] chore: update Cargo.lock for deno-runtime crate - Update dependency lock file to reflect new crate structure - Ensure reproducible builds with new deno-runtime dependency --- Cargo.lock | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2e4afc1..9142d48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,6 +1010,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "deno-runtime" +version = "0.1.0" +dependencies = [ + "anyhow", + "deno_core", + "tokio", + "tracing", +] + [[package]] name = "deno_core" version = "0.323.0" @@ -1398,7 +1408,7 @@ dependencies = [ "anyhow", "bytes", "chrono", - "deno_core", + "deno-runtime", "flate2", "futures-util", "http-body-util",