@tishlang/tish-format 3.11.0 → 3.12.1

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.
package/bin/tish-format CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang_fmt"
3
- version = "3.11.0"
3
+ version = "3.12.1"
4
4
  edition = "2021"
5
5
  description = "Opinionated formatter for Tish source (parse → pretty-print)"
6
6
  license-file = { workspace = true }
@@ -45,6 +45,13 @@ fn runtime_features_for_cargo(features: &[String]) -> Vec<String> {
45
45
  }
46
46
 
47
47
  /// `[profile.release]` for nested `cargo build` of generated crates.
48
+ ///
49
+ /// - default: fat LTO, one codegen unit — smallest and fastest binary, slowest to produce (the
50
+ /// final link is single-threaded and dominates a warm build of a large app).
51
+ /// - `TISH_FAST_NATIVE_BUILD=1`: iteration profile, no LTO, opt-level 1.
52
+ /// - `TISH_NATIVE_THIN_LTO=1`: thin LTO over 8 codegen units — keeps most of fat's size/speed win
53
+ /// while letting rustc parallelise the link. Same trade the GBA path offers via
54
+ /// `TISH_GBA_THIN_LTO`; for desktop apps this is the CI-friendly shipping profile.
48
55
  fn nested_release_profile_toml() -> &'static str {
49
56
  if std::env::var("TISH_FAST_NATIVE_BUILD").as_deref() == Ok("1") {
50
57
  r#"[profile.release]
@@ -55,6 +62,13 @@ incremental = true
55
62
  strip = false
56
63
  debug = 0
57
64
  panic = "abort"
65
+ "#
66
+ } else if std::env::var("TISH_NATIVE_THIN_LTO").as_deref() == Ok("1") {
67
+ r#"[profile.release]
68
+ strip = true
69
+ panic = "abort"
70
+ codegen-units = 8
71
+ lto = "thin"
58
72
  "#
59
73
  } else {
60
74
  r#"[profile.release]
@@ -1179,6 +1193,7 @@ edition = "2021"
1179
1193
 
1180
1194
  #[cfg(test)]
1181
1195
  mod tests {
1196
+ use super::nested_release_profile_toml;
1182
1197
  use super::runtime_features_for_cargo;
1183
1198
 
1184
1199
  /// The generated ROM crate must inherit the project's pin, or GBA builds float against whatever
@@ -1219,6 +1234,27 @@ mod tests {
1219
1234
  let _ = fs::remove_dir_all(&base);
1220
1235
  }
1221
1236
 
1237
+ /// `TISH_NATIVE_THIN_LTO=1` selects thin LTO for native release builds; fast wins over it.
1238
+ #[test]
1239
+ fn native_release_profile_follows_env() {
1240
+ use std::sync::Mutex;
1241
+ static ENV_LOCK: Mutex<()> = Mutex::new(());
1242
+ let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
1243
+ std::env::remove_var("TISH_FAST_NATIVE_BUILD");
1244
+ std::env::remove_var("TISH_NATIVE_THIN_LTO");
1245
+ assert!(nested_release_profile_toml().contains("lto = \"fat\""));
1246
+ std::env::set_var("TISH_NATIVE_THIN_LTO", "1");
1247
+ let thin = nested_release_profile_toml();
1248
+ assert!(thin.contains("lto = \"thin\"") && thin.contains("codegen-units = 8"));
1249
+ std::env::set_var("TISH_FAST_NATIVE_BUILD", "1");
1250
+ assert!(
1251
+ nested_release_profile_toml().contains("lto = false"),
1252
+ "fast wins over thin"
1253
+ );
1254
+ std::env::remove_var("TISH_FAST_NATIVE_BUILD");
1255
+ std::env::remove_var("TISH_NATIVE_THIN_LTO");
1256
+ }
1257
+
1222
1258
  /// `TISH_NATIVE_TARGET_DIR`: unset/empty → None (temp-dir build); set → that dir, created.
1223
1259
  #[test]
1224
1260
  fn shared_native_target_dir_follows_env() {
@@ -218,9 +218,25 @@ pub fn copy_file_core(args: &[Value]) -> Result<Value, Value> {
218
218
  }
219
219
  pub fn realpath_core(args: &[Value]) -> Result<Value, Value> {
220
220
  std::fs::canonicalize(path_arg(args, 0))
221
- .map(|p| Value::String(p.to_string_lossy().into()))
221
+ .map(|p| Value::String(strip_verbatim_prefix(&p.to_string_lossy()).into()))
222
222
  .map_err(io_err)
223
223
  }
224
+
225
+ /// `std::fs::canonicalize` on Windows returns the verbatim form — `\\?\C:\Users\…` for a drive
226
+ /// path, `\\?\UNC\server\share\…` for a network one. Node's `fs.realpath` never does: it hands
227
+ /// back `C:\Users\…` / `\\server\share\…`, and programs treat the result as a display / URL /
228
+ /// comparison string, not just an OS handle (Dune showed `\\?\C:\…` as its workspace title, and a
229
+ /// `?` inside a URL path starts the query string). Match Node. No-op on POSIX and for paths
230
+ /// that carry no prefix.
231
+ pub(crate) fn strip_verbatim_prefix(p: &str) -> String {
232
+ if let Some(rest) = p.strip_prefix("\\\\?\\UNC\\") {
233
+ format!("\\\\{rest}")
234
+ } else if let Some(rest) = p.strip_prefix("\\\\?\\") {
235
+ rest.to_string()
236
+ } else {
237
+ p.to_string()
238
+ }
239
+ }
224
240
  pub fn readlink_core(args: &[Value]) -> Result<Value, Value> {
225
241
  std::fs::read_link(path_arg(args, 0))
226
242
  .map(|p| Value::String(p.to_string_lossy().into()))
@@ -393,3 +409,16 @@ pub fn constants() -> Value {
393
409
  m.insert("X_OK".into(), Value::Number(1.0));
394
410
  Value::object(m)
395
411
  }
412
+
413
+ #[cfg(test)]
414
+ mod verbatim_prefix_tests {
415
+ use super::strip_verbatim_prefix;
416
+
417
+ #[test]
418
+ fn strips_windows_verbatim_prefixes_like_node() {
419
+ assert_eq!(strip_verbatim_prefix("\\\\?\\C:\\Users\\x\\ui"), "C:\\Users\\x\\ui");
420
+ assert_eq!(strip_verbatim_prefix("\\\\?\\UNC\\srv\\share\\d"), "\\\\srv\\share\\d");
421
+ assert_eq!(strip_verbatim_prefix("C:\\Users\\x"), "C:\\Users\\x");
422
+ assert_eq!(strip_verbatim_prefix("/Users/x/ui"), "/Users/x/ui");
423
+ }
424
+ }
@@ -108,6 +108,7 @@ pub fn spawn_children(n: usize) -> io::Result<Vec<Child>> {
108
108
  let mut out = Vec::with_capacity(n - 1);
109
109
  for i in 1..n {
110
110
  let mut cmd = Command::new(&exe);
111
+ crate::no_console_window(&mut cmd);
111
112
  cmd.args(&args);
112
113
  cmd.env("TISH_WORKER_ID", i.to_string());
113
114
  cmd.env("TISH_HTTP_PREFORK", "child");
@@ -1013,6 +1013,20 @@ pub fn process_cwd(_args: &[Value]) -> Value {
1013
1013
  }
1014
1014
 
1015
1015
  #[cfg(feature = "process")]
1016
+ /// Windows: a child spawned from a GUI-subsystem parent (a desktop app) gets its own console
1017
+ /// window unless created with CREATE_NO_WINDOW — a packaged app spawning git / node / shell
1018
+ /// helpers at boot flashed a terminal window per child and stole focus. No-op elsewhere.
1019
+ #[cfg(windows)]
1020
+ pub fn no_console_window(cmd: &mut std::process::Command) -> &mut std::process::Command {
1021
+ use std::os::windows::process::CommandExt;
1022
+ const CREATE_NO_WINDOW: u32 = 0x0800_0000;
1023
+ cmd.creation_flags(CREATE_NO_WINDOW)
1024
+ }
1025
+ #[cfg(not(windows))]
1026
+ pub fn no_console_window(cmd: &mut std::process::Command) -> &mut std::process::Command {
1027
+ cmd
1028
+ }
1029
+
1016
1030
  pub fn process_exec(args: &[Value]) -> Value {
1017
1031
  use std::process::Command;
1018
1032
  let cmd = args
@@ -1022,7 +1036,7 @@ pub fn process_exec(args: &[Value]) -> Value {
1022
1036
  if cmd.is_empty() {
1023
1037
  return Value::Number(0.0);
1024
1038
  }
1025
- match Command::new("sh").arg("-c").arg(&cmd).status() {
1039
+ match no_console_window(Command::new("sh").arg("-c").arg(&cmd)).status() {
1026
1040
  Ok(status) => Value::Number(status.code().unwrap_or(1) as f64),
1027
1041
  Err(_) => Value::Number(1.0),
1028
1042
  }
@@ -1046,7 +1060,7 @@ pub fn process_exec_file(args: &[Value]) -> Value {
1046
1060
  Some(Value::Array(a)) => a.borrow().iter().map(|v| v.to_display_string()).collect(),
1047
1061
  _ => Vec::new(),
1048
1062
  };
1049
- match Command::new(&program).args(&argv).status() {
1063
+ match no_console_window(Command::new(&program).args(&argv)).status() {
1050
1064
  Ok(status) => Value::Number(status.code().unwrap_or(1) as f64),
1051
1065
  Err(_) => Value::Number(1.0),
1052
1066
  }
@@ -1093,7 +1107,7 @@ pub fn process_exec_capture(args: &[Value]) -> Value {
1093
1107
  if cmd.is_empty() {
1094
1108
  return process_capture_obj(0, String::new(), String::new());
1095
1109
  }
1096
- process_capture_result(Command::new("sh").arg("-c").arg(&cmd).output())
1110
+ process_capture_result(no_console_window(Command::new("sh").arg("-c").arg(&cmd)).output())
1097
1111
  }
1098
1112
 
1099
1113
  /// `process.execFileCapture(program, [args])` — run a program directly, WITHOUT a shell, capturing
@@ -1115,7 +1129,7 @@ pub fn process_exec_file_capture(args: &[Value]) -> Value {
1115
1129
  Some(Value::Array(a)) => a.borrow().iter().map(|v| v.to_display_string()).collect(),
1116
1130
  _ => Vec::new(),
1117
1131
  };
1118
- process_capture_result(Command::new(&program).args(&argv).output())
1132
+ process_capture_result(no_console_window(Command::new(&program).args(&argv)).output())
1119
1133
  }
1120
1134
 
1121
1135
  #[cfg(all(test, feature = "process", unix))]
@@ -198,6 +198,7 @@ pub fn process_spawn(args: &[Value]) -> Value {
198
198
  };
199
199
 
200
200
  let mut cmd = Command::new(program);
201
+ crate::no_console_window(&mut cmd);
201
202
  if let Some(Value::Array(a)) = obj_field(opts, "args") {
202
203
  for v in a.borrow().iter() {
203
204
  cmd.arg(v.to_display_string());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tishlang/tish-format",
3
- "version": "3.11.0",
3
+ "version": "3.12.1",
4
4
  "description": "Tish code formatter",
5
5
  "license": "PIF",
6
6
  "repository": {
Binary file
Binary file
Binary file
Binary file
Binary file