@ape-egg/vibe 2.3.0 → 3.0.0

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.
Files changed (57) hide show
  1. package/README.md +14 -4
  2. package/boot.js +4 -4
  3. package/component.js +27 -29
  4. package/hot-module-refresh.js +4 -4
  5. package/index.js +10 -15
  6. package/llms.txt +8 -6
  7. package/package.json +19 -14
  8. package/runtime/affected.js +159 -36
  9. package/runtime/cleanup.js +45 -1
  10. package/runtime/component.js +312 -99
  11. package/runtime/conditionals.js +111 -14
  12. package/runtime/debug.js +24 -0
  13. package/runtime/dispatch.js +172 -0
  14. package/runtime/hydrate.js +251 -111
  15. package/runtime/index.js +180 -71
  16. package/runtime/iterate.js +125 -50
  17. package/runtime/iteration-utils.js +59 -8
  18. package/runtime/manifest.js +77 -2
  19. package/runtime/parse.js +69 -5
  20. package/runtime/pre-compiled-iterations.js +19 -6
  21. package/runtime/pre-compiled-manifest.js +13 -4
  22. package/runtime/staging.js +153 -0
  23. package/runtime/state.js +31 -0
  24. package/runtime/tracking.js +173 -0
  25. package/runtime/utils.js +155 -78
  26. package/spa.js +77 -14
  27. package/vibe.css +8 -4
  28. package/CHANGELOG.md +0 -1196
  29. package/ROADMAP.md +0 -397
  30. package/compiler/bin/vibe-compile.js +0 -121
  31. package/compiler/native/.gitkeep +0 -0
  32. package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
  33. package/compiler/native/vibe-compiler-linux-x64 +0 -0
  34. package/compiler/src/Cargo.lock +0 -2023
  35. package/compiler/src/Cargo.toml +0 -38
  36. package/compiler/src/compiler/PRE-RENDERING-IMPLEMENTATION.md +0 -241
  37. package/compiler/src/compiler/binding_case.rs +0 -88
  38. package/compiler/src/compiler/compile.rs +0 -2880
  39. package/compiler/src/compiler/component_tagger.rs +0 -469
  40. package/compiler/src/compiler/iteration_optimizer.rs +0 -455
  41. package/compiler/src/compiler/js_analyzer.rs +0 -715
  42. package/compiler/src/compiler/manifest_builder.rs +0 -693
  43. package/compiler/src/compiler/mod.rs +0 -16
  44. package/compiler/src/compiler/name_binding_protect.rs +0 -207
  45. package/compiler/src/compiler/reassignment_analyzer.rs +0 -456
  46. package/compiler/src/compiler/spa.rs +0 -477
  47. package/compiler/src/compiler/state_extractor.rs +0 -263
  48. package/compiler/src/compiler/value_stamper.rs +0 -921
  49. package/compiler/src/compiler/watcher.rs +0 -1278
  50. package/compiler/src/config.rs +0 -279
  51. package/compiler/src/main.rs +0 -358
  52. package/compiler/src/parser/element.rs +0 -96
  53. package/compiler/src/parser/html.rs +0 -1004
  54. package/compiler/src/parser/mod.rs +0 -8
  55. package/runtime/pre-compiled-manifest.test.mjs +0 -58
  56. package/runtime/scope.js +0 -50
  57. package/test-results/.last-run.json +0 -4
@@ -1,279 +0,0 @@
1
- use serde::{Deserialize, Serialize};
2
- use std::fs;
3
- use std::path::PathBuf;
4
- use thiserror::Error;
5
-
6
- #[derive(Error, Debug)]
7
- pub enum ConfigError {
8
- #[error("Failed to read package.json: {0}")]
9
- ReadError(#[from] std::io::Error),
10
- #[error("Failed to parse package.json: {0}")]
11
- ParseError(#[from] serde_json::Error),
12
- }
13
-
14
- #[derive(Debug, Clone, Serialize, Deserialize)]
15
- #[serde(rename_all = "camelCase")]
16
- pub struct VibeCompilerConfig {
17
- #[serde(default = "default_source")]
18
- pub source: String,
19
- #[serde(default = "default_output")]
20
- pub output: String,
21
- #[serde(default = "default_components")]
22
- pub components: String,
23
- #[serde(default = "default_pages")]
24
- pub pages: String,
25
- #[serde(default = "default_assets")]
26
- pub assets: String,
27
- #[serde(default)]
28
- pub root: Option<String>,
29
- #[serde(default)]
30
- pub minify: bool,
31
- #[serde(default)]
32
- pub elements_as_is: bool,
33
- #[serde(default)]
34
- pub source_maps: bool,
35
- #[serde(default)]
36
- pub reserved_elements: Vec<String>,
37
- #[serde(default)]
38
- pub skip_files: Vec<String>,
39
- #[serde(default)]
40
- pub node_modules_as_is: bool,
41
- #[serde(default)]
42
- pub components_as_is: bool,
43
- #[serde(default)]
44
- pub runtime_as_is: bool,
45
- #[serde(default)]
46
- pub iterations_as_is: bool,
47
- #[serde(default)]
48
- pub no_clean: bool,
49
- #[serde(default)]
50
- pub fouc_as_is: bool,
51
- #[serde(default, deserialize_with = "deserialize_spa")]
52
- pub spa: bool,
53
- }
54
-
55
- // "spa": true is a boolean today; the future per-page selection form is an
56
- // object. Parse tolerantly so a newer config never silently resets the whole
57
- // vibe-compiler section to defaults on an older compiler.
58
- fn deserialize_spa<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
59
- Ok(match serde_json::Value::deserialize(deserializer)? {
60
- serde_json::Value::Bool(enabled) => enabled,
61
- serde_json::Value::Object(_) => true,
62
- _ => false,
63
- })
64
- }
65
-
66
- fn default_source() -> String { "./".to_string() }
67
- fn default_output() -> String { "./compiled".to_string() }
68
- fn default_components() -> String { "components".to_string() }
69
- fn default_pages() -> String { "pages".to_string() }
70
- fn default_assets() -> String { "assets".to_string() }
71
-
72
- /// Get list of standard HTML5 elements + "component"
73
- /// Component names matching these are reserved and not allowed
74
- pub fn get_default_reserved_elements() -> Vec<String> {
75
- vec![
76
- "a", "abbr", "address", "area", "article", "aside", "audio",
77
- "b", "base", "bdi", "bdo", "blockquote", "body", "br", "button",
78
- "canvas", "caption", "cite", "code", "col", "colgroup",
79
- "data", "datalist", "dd", "del", "details", "dfn", "dialog", "div", "dl", "dt",
80
- "em", "embed",
81
- "fieldset", "figcaption", "figure", "footer", "form",
82
- "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html",
83
- "i", "iframe", "img", "input", "ins",
84
- "kbd",
85
- "label", "legend", "li", "link",
86
- "main", "map", "mark", "menu", "meta", "meter",
87
- "nav", "noscript",
88
- "object", "ol", "optgroup", "option", "output",
89
- "p", "param", "picture", "pre", "progress",
90
- "q",
91
- "rp", "rt", "ruby",
92
- "s", "samp", "script", "search", "section", "select", "slot", "small", "source", "span", "strong", "style", "sub", "summary", "sup", "svg",
93
- "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track",
94
- "u", "ul",
95
- "var", "video",
96
- "wbr",
97
- "component", // Always reserved
98
- ].into_iter().map(|s| s.to_string()).collect()
99
- }
100
-
101
- impl Default for VibeCompilerConfig {
102
- fn default() -> Self {
103
- Self {
104
- source: default_source(),
105
- output: default_output(),
106
- components: default_components(),
107
- pages: default_pages(),
108
- assets: default_assets(),
109
- root: None,
110
- minify: false,
111
- elements_as_is: false,
112
- source_maps: false,
113
- reserved_elements: vec![],
114
- skip_files: vec![],
115
- node_modules_as_is: false,
116
- components_as_is: false,
117
- runtime_as_is: false,
118
- iterations_as_is: false,
119
- no_clean: false,
120
- fouc_as_is: false,
121
- spa: false,
122
- }
123
- }
124
- }
125
-
126
- #[derive(Debug, Clone, PartialEq)]
127
- pub struct Config {
128
- pub source: PathBuf,
129
- pub output: PathBuf,
130
- pub _source_str: String, // Original config value for display
131
- pub _output_str: String, // Original config value for display
132
- pub components: String,
133
- pub pages: String,
134
- pub _assets: String,
135
- pub root: Option<String>,
136
- pub minify: bool,
137
- pub elements_as_is: bool,
138
- pub source_maps: bool,
139
- pub reserved_elements: Vec<String>,
140
- pub skip_files: Vec<String>,
141
- pub node_modules_as_is: bool,
142
- pub components_as_is: bool,
143
- pub runtime_as_is: bool,
144
- pub iterations_as_is: bool,
145
- pub no_clean: bool,
146
- pub fouc_as_is: bool,
147
- pub spa: bool,
148
- pub working_dir: PathBuf,
149
- }
150
-
151
- impl Config {
152
- /// Load config from package.json, or use defaults if not found
153
- pub fn load(working_dir: PathBuf) -> Self {
154
- // A relative --cwd must become absolute HERE: every derived path
155
- // (source, output, pages) joins from working_dir, and downstream
156
- // strip_prefix against filesystem-event paths (always absolute)
157
- // otherwise fails — in watch mode that once routed a manifest write
158
- // and a stamped page INTO the source tree.
159
- let working_dir = working_dir.canonicalize().unwrap_or(working_dir);
160
- let package_path = working_dir.join("package.json");
161
-
162
- let config = if package_path.exists() {
163
- fs::read_to_string(&package_path)
164
- .ok()
165
- .and_then(|content| serde_json::from_str::<serde_json::Value>(&content).ok())
166
- .and_then(|package| package.get("vibe-compiler").cloned())
167
- .and_then(|vibe_config| serde_json::from_value::<VibeCompilerConfig>(vibe_config).ok())
168
- .unwrap_or_default()
169
- } else {
170
- VibeCompilerConfig::default()
171
- };
172
-
173
- let source_str = config.source.clone();
174
- let output_str = config.output.clone();
175
- let source = working_dir.join(&config.source);
176
- let output = working_dir.join(&config.output);
177
-
178
- // Combine default reserved elements with user-provided ones
179
- let mut reserved_elements = get_default_reserved_elements();
180
- reserved_elements.extend(config.reserved_elements);
181
-
182
- // Combine built-in skip patterns with user-provided ones (append, not replace)
183
- let mut skip_files: Vec<String> = crate::compiler::compile::SKIP_FILES
184
- .iter()
185
- .map(|s| s.to_string())
186
- .collect();
187
- skip_files.extend(config.skip_files);
188
-
189
- Self {
190
- source,
191
- output,
192
- _source_str: source_str,
193
- _output_str: output_str,
194
- components: config.components,
195
- pages: config.pages,
196
- _assets: config.assets,
197
- root: config.root,
198
- minify: config.minify,
199
- elements_as_is: config.elements_as_is,
200
- source_maps: config.source_maps,
201
- reserved_elements,
202
- skip_files,
203
- node_modules_as_is: config.node_modules_as_is,
204
- components_as_is: config.components_as_is,
205
- runtime_as_is: config.runtime_as_is,
206
- iterations_as_is: config.iterations_as_is,
207
- no_clean: config.no_clean,
208
- fouc_as_is: config.fouc_as_is,
209
- spa: config.spa,
210
- working_dir,
211
- }
212
- }
213
-
214
- pub fn components_path(&self) -> PathBuf {
215
- self.source.join(&self.components)
216
- }
217
-
218
- pub fn _pages_path(&self) -> PathBuf {
219
- self.source.join(&self.pages)
220
- }
221
-
222
- pub fn _assets_path(&self) -> PathBuf {
223
- self.source.join(&self._assets)
224
- }
225
- }
226
-
227
- #[cfg(test)]
228
- mod tests {
229
- use super::*;
230
-
231
- fn parse(json: &str) -> VibeCompilerConfig {
232
- serde_json::from_str(json).unwrap()
233
- }
234
-
235
- #[test]
236
- fn spa_parses_bool_and_tolerates_the_future_object_form() {
237
- assert!(!parse(r#"{}"#).spa);
238
- assert!(parse(r#"{"spa": true}"#).spa);
239
- assert!(!parse(r#"{"spa": false}"#).spa);
240
- // Future per-page selection form must not break older compilers.
241
- assert!(parse(r#"{"spa": {"pages": ["docs"]}}"#).spa);
242
- }
243
- }
244
-
245
- pub fn init_config(working_dir: &PathBuf) -> Result<(), ConfigError> {
246
- let package_path = working_dir.join("package.json");
247
-
248
- if !package_path.exists() {
249
- // Create minimal package.json with vibe-compiler config
250
- let config = VibeCompilerConfig::default();
251
- let package = serde_json::json!({
252
- "vibe-compiler": config
253
- });
254
- let formatted = serde_json::to_string_pretty(&package)?;
255
- fs::write(&package_path, formatted)?;
256
- println!("Created package.json with vibe-compiler config");
257
- return Ok(());
258
- }
259
-
260
- let content = fs::read_to_string(&package_path)?;
261
- let mut package: serde_json::Value = serde_json::from_str(&content)?;
262
-
263
- // Don't overwrite existing config
264
- if package.get("vibe-compiler").is_some() {
265
- println!("vibe-compiler config already exists in package.json");
266
- return Ok(());
267
- }
268
-
269
- let default_config = VibeCompilerConfig::default();
270
- let config_value = serde_json::to_value(&default_config)?;
271
-
272
- package["vibe-compiler"] = config_value;
273
-
274
- let formatted = serde_json::to_string_pretty(&package)?;
275
- fs::write(&package_path, formatted)?;
276
-
277
- println!("Added vibe-compiler config to package.json");
278
- Ok(())
279
- }
@@ -1,358 +0,0 @@
1
- mod config;
2
- mod parser;
3
- mod compiler;
4
-
5
- use clap::Parser as ClapParser;
6
- use colored::Colorize;
7
- use std::path::PathBuf;
8
-
9
- use config::Config;
10
- use compiler::Compiler;
11
-
12
- /// Track which config values were overridden by flags
13
- #[derive(Debug, Default)]
14
- struct ConfigOverrides {
15
- minify: bool,
16
- elements_as_is: bool,
17
- source_maps: bool,
18
- node_modules_as_is: bool,
19
- components_as_is: bool,
20
- runtime_as_is: bool,
21
- iterations_as_is: bool,
22
- spa: bool,
23
- }
24
-
25
- /// Vibe Compiler - Compiles Vibe source files into optimized output
26
- #[derive(ClapParser, Debug)]
27
- #[command(name = "vibe-compile")]
28
- #[command(author = "Kim Korte")]
29
- #[command(version)]
30
- #[command(about = "Compiles Vibe source files into optimized output")]
31
- struct Args {
32
- /// Working directory (defaults to current directory)
33
- #[arg(long)]
34
- cwd: Option<PathBuf>,
35
-
36
- /// Watch for file changes
37
- #[arg(long)]
38
- watch: bool,
39
-
40
- /// Minify output
41
- #[arg(long)]
42
- minify: bool,
43
-
44
- /// Enable verbose logging with step-by-step output
45
- #[arg(long)]
46
- verbose: bool,
47
-
48
- /// Keep custom HTML elements as-is (don't transform to divs for accessibility)
49
- #[arg(long, name = "elements-as-is")]
50
- elements_as_is: bool,
51
-
52
- /// Generate source maps
53
- #[arg(long, name = "source-maps")]
54
- source_maps: bool,
55
-
56
- /// Initialize configuration in package.json
57
- #[arg(long)]
58
- init: bool,
59
-
60
- /// Custom source directory (overrides config)
61
- #[arg(long)]
62
- source: Option<PathBuf>,
63
-
64
- /// Custom output directory (overrides config)
65
- #[arg(long)]
66
- output: Option<PathBuf>,
67
-
68
- /// Copy node_modules as-is without production-only install (overrides config)
69
- #[arg(long, name = "node-modules-as-is")]
70
- node_modules_as_is: bool,
71
-
72
- /// Keep components directory and <component> tags as-is (don't inline components)
73
- #[arg(long, name = "components-as-is")]
74
- components_as_is: bool,
75
-
76
- /// Skip manifest generation - output will use runtime-only mode (no FOUC prevention)
77
- #[arg(long, name = "runtime-as-is")]
78
- runtime_as_is: bool,
79
-
80
- /// Skip iteration optimization - use runtime DOM cloning instead of compiled batch functions
81
- #[arg(long, name = "iterations-as-is")]
82
- iterations_as_is: bool,
83
-
84
- /// Skip cleaning output directory before compilation
85
- #[arg(long, name = "no-clean", hide = true)]
86
- no_clean: bool,
87
-
88
- /// Keep FOUC prevention class/attribute in compiled output
89
- #[arg(long, name = "fouc-as-is")]
90
- fouc_as_is: bool,
91
-
92
- /// Compile the pages tree to SPA output: fragments + route table + shell
93
- #[arg(long)]
94
- spa: bool,
95
- }
96
-
97
- fn main() {
98
- let args = Args::parse();
99
-
100
- // Determine working directory
101
- let working_dir = args.cwd
102
- .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
103
-
104
- if args.init {
105
- match config::init_config(&working_dir) {
106
- Ok(_) => {
107
- return;
108
- }
109
- Err(e) => {
110
- eprintln!("{}: {}", "Error".red(), e);
111
- std::process::exit(1);
112
- }
113
- }
114
- }
115
-
116
- // Load config from package.json (uses defaults if not found)
117
- let mut config = Config::load(working_dir);
118
-
119
- // Override config with CLI args
120
- if let Some(source) = args.source {
121
- config.source = config.working_dir.join(source);
122
- }
123
- if let Some(output) = args.output {
124
- config.output = config.working_dir.join(output);
125
- }
126
-
127
- // Store original config values before applying flag overrides
128
- let original_minify = config.minify;
129
- let original_elements_as_is = config.elements_as_is;
130
- let original_source_maps = config.source_maps;
131
- let original_node_modules_as_is = config.node_modules_as_is;
132
- let original_components_as_is = config.components_as_is;
133
- let original_runtime_as_is = config.runtime_as_is;
134
- let original_iterations_as_is = config.iterations_as_is;
135
- let original_spa = config.spa;
136
-
137
- // Track overrides for verbose output
138
- let mut overrides = ConfigOverrides::default();
139
-
140
- if args.minify {
141
- overrides.minify = !original_minify;
142
- config.minify = true;
143
- }
144
- if args.elements_as_is {
145
- overrides.elements_as_is = !original_elements_as_is;
146
- config.elements_as_is = true;
147
- }
148
- if args.source_maps {
149
- overrides.source_maps = !original_source_maps;
150
- config.source_maps = true;
151
- }
152
- if args.node_modules_as_is {
153
- overrides.node_modules_as_is = !original_node_modules_as_is;
154
- config.node_modules_as_is = true;
155
- }
156
- if args.components_as_is {
157
- overrides.components_as_is = !original_components_as_is;
158
- config.components_as_is = true;
159
- }
160
- if args.runtime_as_is {
161
- overrides.runtime_as_is = !original_runtime_as_is;
162
- config.runtime_as_is = true;
163
- }
164
- if args.iterations_as_is {
165
- overrides.iterations_as_is = !original_iterations_as_is;
166
- config.iterations_as_is = true;
167
- }
168
- if args.no_clean {
169
- config.no_clean = true;
170
- }
171
- if args.fouc_as_is {
172
- config.fouc_as_is = true;
173
- }
174
- if args.spa {
175
- overrides.spa = !original_spa;
176
- config.spa = true;
177
- }
178
-
179
- if args.verbose {
180
- println!("{}", format!("Vibe Compiler v{}", env!("CARGO_PKG_VERSION")).cyan().bold());
181
- println!();
182
- println!(" {}: {}", "Working dir".cyan(), config.working_dir.display());
183
- println!(" {}: {}", "Source".cyan(), config.source.display());
184
- println!(" {}: {}", "Output".cyan(), config.output.display());
185
- println!(" {}: {}", "Components".cyan(), config.components);
186
- println!(" {}: {}", "Pages".cyan(), config.pages);
187
- println!(" {}: {}", "Assets".cyan(), config._assets);
188
- println!();
189
-
190
- // Show config values (alphabetically ordered)
191
- println!("{}", "Compiler config".cyan().bold());
192
- println!();
193
- println!(" CLI flags override package.json config");
194
- println!();
195
-
196
- fn format_bool_with_flag(final_value: bool, overridden: bool, original_value: bool) -> String {
197
- if overridden {
198
- format!("{} (config: {}, flag: {})", final_value.to_string().green(), original_value.to_string().yellow(), final_value.to_string().green())
199
- } else {
200
- format!("{} (config: {})", final_value.to_string().green(), original_value.to_string().yellow())
201
- }
202
- }
203
-
204
- fn format_value_no_flag<T: std::fmt::Display>(value: T) -> String {
205
- format!("{} (config: {})", value.to_string().green(), value.to_string().yellow())
206
- }
207
-
208
- fn format_reserved_elements(elements: &[String]) -> String {
209
- if elements.len() <= 2 {
210
- format!("{:?}", elements)
211
- } else {
212
- let remaining = elements.len() - 2;
213
- format!("[\"component\", \"div\", ... + {} more]", remaining)
214
- }
215
- }
216
-
217
- fn format_list_preview(items: &[String]) -> String {
218
- if items.len() <= 2 {
219
- format!("{:?}", items)
220
- } else {
221
- format!("[{:?}, {:?}, ... + {} more]", items[0], items[1], items.len() - 2)
222
- }
223
- }
224
-
225
- // Alphabetically ordered with padding (longest key is "reservedElements" = 16 chars)
226
- println!(" {}: {}", format!("{:<16}", "assets").cyan(), format_value_no_flag(&config._assets));
227
- println!(" {}: {}", format!("{:<16}", "components").cyan(), format_value_no_flag(&config.components));
228
- println!(" {}: {}", format!("{:<16}", "componentsAsIs").cyan(), format_bool_with_flag(config.components_as_is, overrides.components_as_is, original_components_as_is));
229
- println!(" {}: {}", format!("{:<16}", "elementsAsIs").cyan(), format_bool_with_flag(config.elements_as_is, overrides.elements_as_is, original_elements_as_is));
230
- println!(" {}: {}", format!("{:<16}", "iterationsAsIs").cyan(), format_bool_with_flag(config.iterations_as_is, overrides.iterations_as_is, original_iterations_as_is));
231
- println!(" {}: {}", format!("{:<16}", "minify").cyan(), format_bool_with_flag(config.minify, overrides.minify, original_minify));
232
- println!(" {}: {}", format!("{:<16}", "nodeModulesAsIs").cyan(), format_bool_with_flag(config.node_modules_as_is, overrides.node_modules_as_is, original_node_modules_as_is));
233
- println!(" {}: {}", format!("{:<16}", "output").cyan(), format_value_no_flag(&config._output_str));
234
- println!(" {}: {}", format!("{:<16}", "pages").cyan(), format_value_no_flag(&config.pages));
235
- println!(" {}: {}", format!("{:<16}", "reservedElements").cyan(), format_value_no_flag(format_reserved_elements(&config.reserved_elements)));
236
- println!(" {}: {}", format!("{:<16}", "root").cyan(), format_value_no_flag(config.root.as_ref().map(|s| s.as_str()).unwrap_or("null")));
237
- println!(" {}: {}", format!("{:<16}", "runtimeAsIs").cyan(), format_bool_with_flag(config.runtime_as_is, overrides.runtime_as_is, original_runtime_as_is));
238
- println!(" {}: {}", format!("{:<16}", "skipFiles").cyan(), format_value_no_flag(format_list_preview(&config.skip_files)));
239
- println!(" {}: {}", format!("{:<16}", "source").cyan(), format_value_no_flag(&config._source_str));
240
- println!(" {}: {}", format!("{:<16}", "sourceMaps").cyan(), format_bool_with_flag(config.source_maps, overrides.source_maps, original_source_maps));
241
- println!(" {}: {}", format!("{:<16}", "spa").cyan(), format_bool_with_flag(config.spa, overrides.spa, original_spa));
242
- println!();
243
- } else {
244
- // Show version in non-verbose mode
245
- println!("{}", format!("Vibe Compiler v{}", env!("CARGO_PKG_VERSION")).cyan());
246
- }
247
-
248
- // Handle watch mode
249
- if args.watch {
250
- if let Err(e) = compiler::watcher::watch(config, args.verbose) {
251
- eprintln!("{}: {}", "Error".red(), e);
252
- std::process::exit(1);
253
- }
254
- return;
255
- }
256
-
257
- // Save runtime_as_is before moving config
258
- let runtime_as_is = config.runtime_as_is;
259
-
260
- let mut compiler = Compiler::new(config, args.verbose);
261
-
262
- match compiler.compile() {
263
- Ok(stats) => {
264
- // Generate manifests BEFORE showing success (unless runtime-as-is is enabled)
265
- let mut manifest_time_ms = 0.0;
266
- let mut manifest_stats_result = None;
267
- if !runtime_as_is {
268
- match compiler.generate_manifests() {
269
- Ok(manifest_stats) => {
270
- manifest_time_ms = manifest_stats.total_time_ms;
271
- manifest_stats_result = Some(manifest_stats);
272
- }
273
- Err(e) => {
274
- eprintln!("\n{}: Manifest generation failed: {}", "Warning".yellow(), e);
275
- eprintln!("Compilation succeeded but manifests were not generated.");
276
- eprintln!("Use --runtime-as-is to skip manifest generation.");
277
- }
278
- }
279
- }
280
-
281
- // Show success headline
282
- println!("\n{}", format!("Compilation successful! (v{}) ✅", env!("CARGO_PKG_VERSION")).green().bold());
283
- println!();
284
-
285
- // Show individual phase timings (validation first, then components, HTML, manifests, copied)
286
-
287
- // Show validation time if it happened
288
- if let Some(validation_time) = stats.validation_time_ms {
289
- println!("* Validated components in {:.0}ms", validation_time);
290
- }
291
-
292
- let total_components_unique = stats.internal_components_unique + stats.external_components_unique;
293
-
294
- // Always show components line
295
- if stats.components_as_is {
296
- println!("* Compiled components (0 internal, 0 external) - \"components-as-is\": true");
297
- } else if total_components_unique > 0 {
298
- println!("* Compiled components ({} internal, {} external) in {:.0}ms",
299
- stats.internal_components_unique,
300
- stats.external_components_unique,
301
- stats.components_time_ms
302
- );
303
- }
304
-
305
- if stats.files_compiled > 0 {
306
- println!("* Compiled HTML ({} file{}) in {:.0}ms",
307
- stats.files_compiled,
308
- if stats.files_compiled == 1 { "" } else { "s" },
309
- stats.compile_time_ms
310
- );
311
- }
312
-
313
- // Show manifest stats between HTML and Copied files
314
- if let Some(manifest_stats) = manifest_stats_result {
315
- println!("* Generated manifests ({} file{}, {} skipped) in {:.0}ms",
316
- manifest_stats.pages_processed,
317
- if manifest_stats.pages_processed == 1 { "" } else { "s" },
318
- manifest_stats.pages_skipped,
319
- manifest_stats.total_time_ms
320
- );
321
- }
322
-
323
- if stats.files_copied > 0 {
324
- println!("* Copied files ({} file{}) in {:.0}ms",
325
- stats.files_copied,
326
- if stats.files_copied == 1 { "" } else { "s" },
327
- stats.copy_time_ms
328
- );
329
- }
330
-
331
- // Show node_modules handling
332
- if let Some(nm_time) = stats.node_modules_time_ms {
333
- if stats.node_modules_copied_as_is {
334
- println!("* copied node_modules in {:.0}ms", nm_time);
335
- } else if let Some(ref pkg_manager) = stats.package_manager {
336
- println!("* {} install in {:.0}ms", pkg_manager, nm_time);
337
- }
338
- }
339
-
340
- // Calculate total duration as sum of all individual operations
341
- let total_duration_ms = stats.validation_time_ms.unwrap_or(0.0)
342
- + stats.components_time_ms
343
- + stats.compile_time_ms
344
- + stats.copy_time_ms
345
- + stats.node_modules_time_ms.unwrap_or(0.0)
346
- + manifest_time_ms;
347
-
348
- println!("\n{} in {:.0}ms", "Compiled".green(), total_duration_ms);
349
-
350
- // Exit explicitly to kill background server thread
351
- std::process::exit(0);
352
- }
353
- Err(e) => {
354
- eprintln!("{}: {}", "Error".red(), e);
355
- std::process::exit(1);
356
- }
357
- }
358
- }