Windows: shippable cross-compiled .zip + console fix + release CI

The tree already links a working x86_64-pc-windows-gnu exe; turn that into
a shipped artifact:

- scripts/package.sh gains a `win` target (and `all` picks it up when the
  mingw toolchain is present) that cross-compiles and zips the exe +
  LICENSE + a README listing the yt-dlp/ffmpeg/mpv PATH deps.
- Forgejo release workflow installs mingw-w64 + zip + the rust target, so
  a tag push produces yt-offline-<ver>-x86_64-windows.zip from the same
  Linux container — no Windows runner needed.
- main.rs: attach_windows_console() (cfg(windows), windows-sys
  AttachConsole) reattaches a release build to the launching terminal so
  --web/CLI output is visible, while a double-click stays windowless
  (release sets windows_subsystem = "windows").
- ROADMAP 3.1 updated: Windows ships; macOS deferred (needs a Mac runner).

Native Linux build + 128 unit / 11 integration tests still green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Luna 2026-06-16 02:31:38 -07:00
parent 8b2578783a
commit 207013e957
6 changed files with 187 additions and 34 deletions

View file

@ -65,7 +65,34 @@ fn renderer_from_args(args: &[String]) -> eframe::Renderer {
}
}
/// Reattach stdout/stderr to the parent console on Windows.
///
/// Release builds set `windows_subsystem = "windows"`, so the process starts
/// detached from any console — correct for the GUI (no flashing console
/// window), but it means a `--web` or other CLI invocation launched from a
/// terminal would print nothing. `AttachConsole(ATTACH_PARENT_PROCESS)` hooks
/// us back onto the launching terminal's console when there is one (e.g. run
/// from PowerShell/cmd). If the process has no parent console (double-clicked
/// from Explorer) the call fails harmlessly and we stay silent, which is the
/// desired GUI behavior. No-op on every non-Windows platform.
#[cfg(windows)]
fn attach_windows_console() {
use windows_sys::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS};
// SAFETY: AttachConsole takes no pointers and is safe to call with an
// invalid/absent parent console — it just returns 0. We don't touch the
// returned handles; the CRT picks up the now-attached console on the next
// stdout/stderr write.
unsafe {
AttachConsole(ATTACH_PARENT_PROCESS);
}
}
#[cfg(not(windows))]
fn attach_windows_console() {}
fn main() -> eframe::Result<()> {
attach_windows_console();
let args: Vec<String> = std::env::args().collect();
// Load the config first — both modes share the channels_root path,