@lithia-js/native 1.0.0-canary.2 → 1.0.0-canary.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,126 +0,0 @@
1
- //! Native builder entrypoints and orchestration.
2
- //!
3
- //! This module exposes the `build_project` function which is invoked from the
4
- //! host (Node) via N-API. It wires together scanning, compilation and route
5
- //! manifest generation using the Rust-based SWC compiler integration.
6
- //!
7
- //! Exposes the native `build_project` entrypoint used by the host.
8
-
9
- use napi_derive::napi;
10
- use rayon::prelude::*;
11
- use std::{fs, time::Instant};
12
-
13
- pub mod compiler;
14
- pub mod config;
15
- pub mod sourcemap;
16
- pub mod tsconfig;
17
- pub mod types;
18
-
19
- #[cfg(test)]
20
- mod tests;
21
-
22
- use compiler::TypeScriptCompiler;
23
- use config::BuildConfig;
24
- use types::{BuildResult, CompileResult};
25
-
26
- #[napi]
27
- /// Build the project located at `source_root` and emit outputs to `out_root`.
28
- ///
29
- /// This function is exported to the host via N-API and performs the full
30
- /// native compilation pipeline:
31
- /// 1. Reads build configuration from `source_root`.
32
- /// 2. Scans for TypeScript files matching `.ts`.
33
- /// 3. Compiles files (in parallel) using the embedded SWC-based compiler.
34
- /// 4. Aggregates compilation results and fails the build if there are errors.
35
- /// 5. If route files exist in the output, produces a `routes.json` manifest
36
- /// containing route metadata consumed by the runtime.
37
- ///
38
- /// Errors are returned as `napi::Error` to be propagated to the host.
39
- /// High-level build entrypoint for the native TypeScript builder.
40
- ///
41
- /// `build_project` coordinates scanning the source tree, applying route
42
- /// conventions, and producing a `RoutesManifest` that can be consumed by the
43
- /// runtime. Currently this function is a thin wrapper and may be expanded to
44
- /// run parallel compilation and emit artifacts to disk.
45
- pub fn build_project(source_root: String, out_root: String) -> napi::Result<()> {
46
- let start = Instant::now();
47
-
48
- // Load configuration
49
- let config =
50
- BuildConfig::new(source_root, out_root).map_err(napi::Error::from_reason)?;
51
-
52
- fs::remove_dir_all(&config.out_root).ok();
53
-
54
- // Scan TypeScript files using glob patterns
55
- use crate::scanner::FileScanner;
56
- let ts_files = crate::scanner::NativeFileScanner::new()
57
- .scan_dir(
58
- &[config.source_root_str()],
59
- Some(crate::scanner::ScanOptions {
60
- include: Some(vec!["**/*.ts".to_string()]),
61
- ignore: Some(config.ignore_patterns.clone()),
62
- }),
63
- )
64
- .map_err(|e| napi::Error::from_reason(format!("scan failed: {}", e)))?;
65
-
66
- // Compile files in parallel
67
- let compiler = TypeScriptCompiler::new(config.ts_config.clone());
68
-
69
- let results: Vec<Result<CompileResult, String>> = ts_files
70
- .par_iter()
71
- .map(|file| {
72
- let output_path = config.compute_output_path(&file.path);
73
-
74
- if let Some(parent) = output_path.parent() {
75
- std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
76
- }
77
-
78
- let file_start = Instant::now();
79
- compiler
80
- .compile_file(std::path::Path::new(&file.full_path), &output_path)
81
- .map(|_| CompileResult {
82
- output_path: output_path.to_string_lossy().to_string(),
83
- duration_ms: file_start.elapsed().as_secs_f64() * 1000.0,
84
- })
85
- })
86
- .collect();
87
-
88
- // Aggregate results
89
- let mut build_result = BuildResult::new(start.elapsed().as_secs_f64() * 1000.0);
90
- build_result.files_compiled = ts_files.len();
91
-
92
- for result in results {
93
- match result {
94
- Ok(timing) => build_result.timings.push(timing),
95
- Err(e) => build_result.failures.push(e),
96
- }
97
- }
98
-
99
- // Build summary is emitted to the host (Node) via the native API; avoid printing here.
100
-
101
- if build_result.has_failures() {
102
- let failures_msg = build_result
103
- .failures
104
- .iter()
105
- .take(5)
106
- .map(|e| e.as_str())
107
- .collect::<Vec<_>>()
108
- .join("\n\n");
109
-
110
- return Err(napi::Error::from_reason(format!(
111
- "Build completed with {} failures:\n\n{}",
112
- build_result.failures.len(),
113
- failures_msg
114
- )));
115
- }
116
-
117
- build_result.total_duration_ms = start.elapsed().as_secs_f64() * 1000.0;
118
-
119
- // Generate routes manifest using helper
120
- crate::router::write_routes_manifest(&config).map_err(napi::Error::from_reason)?;
121
-
122
- // Events manifest: build from already-scanned `ts_files` (no extra scan)
123
- crate::events::write_events_manifest(&config).map_err(napi::Error::from_reason)?;
124
-
125
- Ok(())
126
- }
@@ -1,36 +0,0 @@
1
- use std::path::Path;
2
-
3
- /// Write compiled `code` and its optional `source_map` to disk.
4
- ///
5
- /// When `source_map` is `Some`, this function writes both the `.js` file and
6
- /// the corresponding `.js.map` alongside it. The `.js` file will include a
7
- /// `//# sourceMappingURL=<file>.map` comment so runtimes and devtools can
8
- /// automatically discover the map. If `source_map` is `None`, only the
9
- /// `.js` file is written.
10
- pub fn write_sourcemap_and_code(output: &Path, code: &str, source_map: Option<String>) -> Result<(), String> {
11
- let map_file_name = format!(
12
- "{}.map",
13
- output
14
- .file_name()
15
- .ok_or("Invalid output file name")?
16
- .to_string_lossy()
17
- );
18
-
19
- // When a sourcemap is present, append the sourceMappingURL comment.
20
- let code_with_map = if source_map.is_some() {
21
- format!("{}\n//# sourceMappingURL={}\n", code, map_file_name)
22
- } else {
23
- code.to_string()
24
- };
25
-
26
- std::fs::write(output, code_with_map)
27
- .map_err(|e| format!("Failed to write output file: {}", e))?;
28
-
29
- if let Some(map) = source_map {
30
- let map_path = output.with_extension("js.map");
31
- std::fs::write(&map_path, map)
32
- .map_err(|e| format!("Failed to write sourcemap file: {}", e))?;
33
- }
34
-
35
- Ok(())
36
- }
@@ -1,78 +0,0 @@
1
- use super::compiler::TypeScriptCompiler;
2
- use super::tsconfig::TsConfigOptions;
3
- use std::fs;
4
- use swc_ecma_ast::EsVersion;
5
- use tempfile::TempDir;
6
-
7
- #[test]
8
- fn test_compile_with_paths() {
9
- let dir = TempDir::new().unwrap();
10
- let root = dir.path();
11
-
12
- let src = root.join("src");
13
- fs::create_dir(&src).unwrap();
14
-
15
- let services = src.join("services");
16
- fs::create_dir(&services).unwrap();
17
-
18
- // Define target file that the alias points to
19
- fs::write(services.join("user-service.ts"), "export class UserService {}").unwrap();
20
-
21
- // Define source file interacting with alias
22
- let input_path = src.join("main.ts");
23
- fs::write(&input_path, "import { UserService } from '@/services/user-service'; console.log(UserService);").unwrap();
24
-
25
- let dist = root.join("dist");
26
- fs::create_dir(&dist).unwrap();
27
- let output_path = dist.join("main.js");
28
-
29
- // Setup compiler with path mapping
30
- // Mapping: "@/*" -> ["./src/*"]
31
- // BaseUrl: root
32
- let ts_config = TsConfigOptions {
33
- emit_sourcemap: false,
34
- target: EsVersion::Es2020,
35
- base_url: Some(root.to_path_buf()),
36
- paths: vec![("@/*".to_string(), vec!["./src/*".to_string()])],
37
- };
38
-
39
- let compiler = TypeScriptCompiler::new(ts_config);
40
-
41
- // Compile
42
- compiler.compile_file(&input_path, &output_path).expect("compile failed");
43
-
44
- // Check output based on behavior
45
- let js = fs::read_to_string(&output_path).unwrap();
46
-
47
- assert!(js.contains(r#"require("./services/user-service")"#), "Output JS did not contain expected relative require. Got:\n{}", js);
48
- }
49
-
50
- #[test]
51
- fn test_compile_no_paths() {
52
- let dir = TempDir::new().unwrap();
53
- let root = dir.path();
54
- let src = root.join("src");
55
- fs::create_dir(&src).unwrap();
56
-
57
- let input_path = src.join("index.ts");
58
- // Normal relative import shouldn't change
59
- fs::write(&input_path, "import { x } from './utils'; console.log(x);").unwrap();
60
- fs::write(src.join("utils.ts"), "export const x = 1;").unwrap();
61
-
62
- let dist = root.join("dist");
63
- fs::create_dir(&dist).unwrap();
64
- let output_path = dist.join("index.js");
65
-
66
- let ts_config = TsConfigOptions {
67
- emit_sourcemap: false,
68
- target: EsVersion::Es2020,
69
- base_url: None,
70
- paths: vec![],
71
- };
72
-
73
- let compiler = TypeScriptCompiler::new(ts_config);
74
- compiler.compile_file(&input_path, &output_path).expect("compile failed");
75
-
76
- let js = fs::read_to_string(&output_path).unwrap();
77
- assert!(js.contains(r#"require("./utils")"#));
78
- }
@@ -1,100 +0,0 @@
1
- use serde_json::Value;
2
- use std::path::Path;
3
-
4
- use swc_ecma_ast::EsVersion;
5
-
6
- /// Parsed TypeScript configuration options relevant for the native builder.
7
- /// This struct captures only the small subset of `tsconfig.json` that the
8
- /// builder needs: whether to emit source maps and the target ECMAScript
9
- /// version.
10
- #[derive(Debug, Clone)]
11
- pub struct TsConfigOptions {
12
- /// Whether to generate source maps during compilation.
13
- pub emit_sourcemap: bool,
14
-
15
- /// SWC `EsVersion` target inferred from `compilerOptions.target`.
16
- pub target: EsVersion,
17
-
18
- /// Base URL for non-relative module resolution.
19
- pub base_url: Option<std::path::PathBuf>,
20
-
21
- /// Path mappings for module resolution.
22
- pub paths: Vec<(String, Vec<String>)>,
23
- }
24
-
25
- /// Parse a `tsconfig.json` file from `path` (optional) and return
26
- /// `TsConfigOptions` used by the builder.
27
- ///
28
- /// If `path` is `None`, the function will attempt to read `tsconfig.json`
29
- /// from the current working directory. If the file does not exist or cannot be
30
- /// parsed, reasonable defaults are returned and an `Ok` result is produced
31
- /// (the builder treats missing or invalid config as non-fatal by design).
32
- pub fn parse_tsconfig(path: Option<&Path>) -> Result<TsConfigOptions, String> {
33
- let default_config = TsConfigOptions {
34
- emit_sourcemap: true,
35
- target: EsVersion::Es2022,
36
- base_url: None,
37
- paths: Vec::new(),
38
- };
39
-
40
- let path = match path {
41
- Some(p) => p.to_path_buf(),
42
- None => {
43
- let cwd = std::env::current_dir().unwrap();
44
- cwd.join("tsconfig.json")
45
- }
46
- };
47
-
48
- if !path.exists() {
49
- // do not print from native side; using default config
50
- return Ok(default_config);
51
- }
52
-
53
- let s = std::fs::read_to_string(&path)
54
- .map_err(|e| format!("failed to read tsconfig {}: {}", path.display(), e))?;
55
-
56
- let v: Value = serde_json::from_str(&s)
57
- .map_err(|e| format!("failed to parse tsconfig {}: {}", path.display(), e))?;
58
-
59
- // Hardcoded options as per requirements
60
- let emit_sourcemap = true;
61
- let target = EsVersion::Es2022;
62
-
63
- let mut base_url = None;
64
- let mut paths = Vec::new();
65
-
66
- if let Some(opts) = v.get("compilerOptions") {
67
- // Enforce sourceMap and target:
68
- // We do *not* read sourceMap or target from tsconfig anymore.
69
- // They are always true and ES2022 respectively.
70
-
71
- if let Some(base) = opts.get("baseUrl").and_then(|v| v.as_str()) {
72
- let tsconfig_dir = path.parent().unwrap_or_else(|| Path::new("."));
73
- base_url = Some(tsconfig_dir.join(base));
74
- }
75
-
76
- if let Some(p) = opts.get("paths").and_then(|v| v.as_object()) {
77
- // implicit baseUrl if paths are present but baseUrl is missing
78
- if base_url.is_none() {
79
- let tsconfig_dir = path.parent().unwrap_or_else(|| Path::new("."));
80
- base_url = Some(tsconfig_dir.to_path_buf());
81
- }
82
-
83
- for (k, v) in p {
84
- let targets = if let Some(arr) = v.as_array() {
85
- arr.iter().filter_map(|x| x.as_str().map(|s| s.to_string())).collect()
86
- } else {
87
- vec![]
88
- };
89
- paths.push((k.clone(), targets));
90
- }
91
- }
92
- }
93
-
94
- Ok(TsConfigOptions {
95
- emit_sourcemap,
96
- target,
97
- base_url,
98
- paths,
99
- })
100
- }
@@ -1,53 +0,0 @@
1
- /// Result information for a single compiled file.
2
- /// `CompileResult` contains small statistics about an individual compilation
3
- /// unit such as the `output_path` produced and the time taken in
4
- /// milliseconds. Fields are public to allow the caller to serialize or log
5
- /// results as needed.
6
- #[derive(Debug, Clone)]
7
- pub struct CompileResult {
8
- #[allow(dead_code)]
9
- /// Absolute or relative filesystem path to the compiled output.
10
- pub output_path: String,
11
- #[allow(dead_code)]
12
- /// Duration of the compilation in milliseconds.
13
- pub duration_ms: f64,
14
- }
15
-
16
- /// Overall build result with statistics.
17
- /// `BuildResult` aggregates per-file `CompileResult` entries and provides a
18
- /// simple API to inspect whether failures occurred, and totals such as the
19
- /// number of files compiled and total duration.
20
- #[derive(Debug)]
21
- pub struct BuildResult {
22
- /// Number of files that were processed during the build.
23
- pub files_compiled: usize,
24
-
25
- /// Collected failure messages for files that failed to compile.
26
- pub failures: Vec<String>,
27
-
28
- /// Timeline entries for successful compilations.
29
- pub timings: Vec<CompileResult>,
30
-
31
- /// Total elapsed build time in milliseconds.
32
- pub total_duration_ms: f64,
33
- }
34
-
35
- impl BuildResult {
36
- pub fn new(total_duration_ms: f64) -> Self {
37
- Self {
38
- files_compiled: 0,
39
- failures: Vec::new(),
40
- timings: Vec::new(),
41
- total_duration_ms,
42
- }
43
- }
44
-
45
- pub fn has_failures(&self) -> bool {
46
- !self.failures.is_empty()
47
- }
48
-
49
- #[allow(dead_code)]
50
- pub fn success_count(&self) -> usize {
51
- self.files_compiled - self.failures.len()
52
- }
53
- }
@@ -1,36 +0,0 @@
1
- use crate::events::transformer::{NativeEventTransformer, PathTransformer};
2
-
3
- /// Trait that defines how filesystem event filenames are converted into a
4
- /// normalized event identifier used by the runtime.
5
- pub trait EventConvention {
6
- /// Transform a filesystem path (relative, may include `app/events/...`)
7
- /// into a normalized event path (no extension, forward slashes).
8
- fn transform_path(&self, path: &str) -> String;
9
- }
10
-
11
- /// Native event convention implementation that reuses the router's
12
- /// `PathTransformer` for common filesystem transformations and then
13
- /// returns a normalized path used to derive the event name.
14
- pub struct NativeEventConvention {
15
- transformer: Box<dyn PathTransformer>,
16
- }
17
-
18
- impl NativeEventConvention {
19
- pub fn new(transformer: Option<Box<dyn PathTransformer>>) -> Self {
20
- let transformer = transformer.unwrap_or_else(|| Box::new(NativeEventTransformer::new()));
21
- Self { transformer }
22
- }
23
- }
24
-
25
- impl EventConvention for NativeEventConvention {
26
- fn transform_path(&self, path: &str) -> String {
27
- // Remove leading prefix if present
28
- let mut p = path.trim_start_matches("app/events/").to_string();
29
-
30
- // Delegate file -> normalized path transformations to the event transformer
31
- p = self.transformer.normalize(&p);
32
-
33
- // Ensure forward slashes
34
- p.replace('\\', "/")
35
- }
36
- }
package/src/events/mod.rs DELETED
@@ -1,87 +0,0 @@
1
- use napi_derive::napi;
2
- use serde::Serialize;
3
-
4
- use crate::builder::config::BuildConfig;
5
- use crate::scanner::FileScanner;
6
- use crate::schema_version;
7
- use std::fs;
8
-
9
- pub mod convention;
10
- pub mod processor;
11
- pub mod transformer;
12
-
13
- /// Serializable event representation sent to the host (N-API).
14
- #[napi(object)]
15
- #[derive(Serialize, Debug, Clone)]
16
- #[serde(rename_all = "camelCase")]
17
- pub struct Event {
18
- /// Event name (e.g., "chat:message" or "connection")
19
- pub name: String,
20
-
21
- /// Absolute filesystem path to the compiled handler (JS) file.
22
- pub file_path: String,
23
-
24
- /// Optional namespace (e.g., "chat" for "chat:message").
25
- pub namespace: Option<String>,
26
- }
27
-
28
- /// Manifest containing all discovered events, serializable to the host.
29
- #[napi(object)]
30
- #[derive(Serialize, Debug)]
31
- pub struct EventsManifest {
32
- /// Manifest version string.
33
- pub version: String,
34
-
35
- /// List of events.
36
- pub events: Vec<Event>,
37
- }
38
-
39
- /// Generate and write `events.json` manifest based on scanned TypeScript files.
40
- ///
41
- /// Uses the already-scanned `ts_files` (source files) and the provided
42
- /// `BuildConfig` to map source files to their compiled output and produce the
43
- /// final manifest next to `routes.json`.
44
- pub fn write_events_manifest(config: &BuildConfig) -> Result<(), String> {
45
- let events_path = config.out_root.join("app").join("events");
46
- if !events_path.exists() {
47
- return Ok(());
48
- }
49
-
50
- let event_files = crate::scanner::NativeFileScanner::new()
51
- .scan_dir(
52
- &[
53
- config.output_path_str(),
54
- "app".to_string(),
55
- "events".to_string(),
56
- ],
57
- Some(crate::scanner::ScanOptions {
58
- include: Some(vec!["**/*.js".to_string()]),
59
- ignore: None,
60
- }),
61
- )
62
- .map_err(|e| format!("scan failed: {}", e))?;
63
-
64
- let processor = processor::NativeEventProcessor::new(None, None);
65
- let events = processor.process(&event_files);
66
-
67
- if events.is_empty() {
68
- return Ok(());
69
- }
70
-
71
- let manifest = EventsManifest {
72
- version: schema_version().to_string(),
73
- events,
74
- };
75
-
76
- let json = serde_json::to_string(&manifest)
77
- .map_err(|e| format!("Failed to serialize events: {}", e))?;
78
-
79
- let out = config.out_root.join("events.json");
80
- fs::write(&out, json)
81
- .map_err(|e| format!("Failed to write events manifest {}: {}", out.display(), e))?;
82
-
83
- Ok(())
84
- }
85
-
86
- #[cfg(test)]
87
- mod tests;
@@ -1,113 +0,0 @@
1
- //! Event file processor utilities mirroring the router processor design.
2
- //!
3
- //! Converts scanned `FileInfo` entries for `app/events` into `Event`
4
- //! structures suitable for serialization into `events.json`.
5
-
6
- use crate::scanner::FileInfo;
7
-
8
- use crate::events::convention::{EventConvention, NativeEventConvention};
9
- use crate::events::transformer::{NativeEventTransformer, PathTransformer};
10
- use crate::events::Event;
11
-
12
- /// Trait that converts a discovered `FileInfo` into an `Event` structure.
13
- pub trait EventProcessor {
14
- fn process_event_file(&self, file: &FileInfo) -> Event;
15
- }
16
-
17
- /// Native implementation of `EventProcessor` that composes a
18
- /// `PathTransformer` and an `EventConvention` similar to the route
19
- /// processor.
20
- pub struct NativeEventProcessor {
21
- transformer: Box<dyn PathTransformer>,
22
- convention: Box<dyn EventConvention>,
23
- }
24
-
25
- impl NativeEventProcessor {
26
- /// Create a new `NativeEventProcessor`.
27
- /// Optional components may be provided for testing.
28
- pub fn new(
29
- opt_transformer: Option<Box<dyn PathTransformer>>,
30
- opt_convention: Option<Box<dyn EventConvention>>,
31
- ) -> Self {
32
- let transformer =
33
- opt_transformer.unwrap_or_else(|| Box::new(NativeEventTransformer::new()));
34
- let convention = opt_convention
35
- .unwrap_or_else(|| Box::new(NativeEventConvention::new(Some(transformer.clone_box()))));
36
-
37
- Self {
38
- transformer,
39
- convention,
40
- }
41
- }
42
-
43
- /// Convenience: process a collection of files into events.
44
- pub fn process(&self, files: &[FileInfo]) -> Vec<Event> {
45
- files.iter().map(|f| self.process_event_file(f)).collect()
46
- }
47
- }
48
-
49
- impl EventProcessor for NativeEventProcessor {
50
- fn process_event_file(&self, file: &FileInfo) -> Event {
51
- // Convert the scanned file path into a normalized event path using
52
- // the convention and then apply the PathTransformer normalization
53
- // (mirrors the route processor pipeline).
54
- let intermediate = self.convention.transform_path(&file.path);
55
- let normalized = self.transformer.normalize_path(&intermediate, "");
56
-
57
- // Build event name: `a/b/c` -> `a:b:c` except for standalone names
58
- let parts: Vec<&str> = normalized
59
- .trim_start_matches('/')
60
- .split('/')
61
- .filter(|s| !s.is_empty())
62
- .collect();
63
- let event_name = if parts.is_empty() {
64
- "".to_string()
65
- } else if parts.len() == 1 {
66
- parts[0].to_string()
67
- } else {
68
- let last = parts.last().unwrap();
69
- if *last == "connection" || *last == "disconnect" {
70
- last.to_string()
71
- } else {
72
- format!("{}:{}", parts[..parts.len() - 1].join(":"), last)
73
- }
74
- };
75
-
76
- let namespace = if event_name.contains(":") {
77
- Some(event_name.split(':').next().unwrap().to_string())
78
- } else {
79
- None
80
- };
81
-
82
- Event {
83
- name: event_name,
84
- file_path: file.full_path.clone(),
85
- namespace,
86
- }
87
- }
88
- }
89
-
90
- #[cfg(test)]
91
- mod tests {
92
- use super::*;
93
- use crate::scanner::FileInfo;
94
-
95
- fn file_info(path: &str, full: &str) -> FileInfo {
96
- FileInfo {
97
- path: path.to_string(),
98
- full_path: full.to_string(),
99
- }
100
- }
101
-
102
- #[test]
103
- fn processor_creates_event() {
104
- let p = NativeEventProcessor::new(None, None);
105
- let f = file_info(
106
- "app/events/chat/message.js",
107
- "/out/app/events/chat/message.js",
108
- );
109
- let e = p.process_event_file(&f);
110
- assert_eq!(e.name, "chat:message");
111
- assert_eq!(e.namespace.unwrap(), "chat");
112
- }
113
- }
@@ -1,41 +0,0 @@
1
- #[cfg(test)]
2
- mod tests {
3
- use super::super::processor::NativeEventProcessor;
4
- use crate::{events::processor::EventProcessor, scanner::FileInfo};
5
- use std::fs;
6
- use tempfile::TempDir;
7
-
8
- #[test]
9
- fn processor_generates_event_manifest_items() {
10
- let temp = TempDir::new().unwrap();
11
- let src_root = temp.path().join("src");
12
- let out_root = temp.path().join("out");
13
- fs::create_dir_all(&src_root).unwrap();
14
- fs::create_dir_all(&out_root).unwrap();
15
-
16
- // create a dummy tsconfig so BuildConfig::new can parse
17
- fs::write(temp.path().join("tsconfig.json"), "{}").unwrap();
18
-
19
- // Create a sample event file path entry
20
- // Use compiled JS path as the processor expects files from the output
21
- let relative = "app/events/chat/message.js".to_string();
22
- let full = out_root.join(&relative).to_string_lossy().to_string();
23
-
24
- // touch the compiled file on disk
25
- fs::create_dir_all(std::path::Path::new(&full).parent().unwrap()).unwrap();
26
- fs::write(&full, "module.exports = {};").unwrap();
27
-
28
- let file_info = FileInfo {
29
- path: relative.clone(),
30
- full_path: full.clone(),
31
- };
32
-
33
- let processor = NativeEventProcessor::new(None, None);
34
- let e = processor.process_event_file(&file_info);
35
-
36
- assert_eq!(e.name, "chat:message");
37
- assert_eq!(e.namespace.as_ref().unwrap(), "chat");
38
- assert!(e.file_path.ends_with("message.js"));
39
- assert_eq!(e.file_path, full);
40
- }
41
- }