Skip to content

Commit

Merge branch 'main' into worktree-agent-a0d19db53ce9b29d8

syntaqxcommitted Parents37ebb71513a088Browse files
15 files+85−230/15 viewed
+1−0
1+* @syntaqx
+1−1
10961096
10971097 [[package]]
10981098 name = "g1t-runner"
1099−version = "0.1.2"
1099+version = "0.1.3"
11001100 dependencies = [
11011101 "anyhow",
11021102 "base64 0.22.1",
+19−1
132132 matches!(url.scheme(), "http" | "https") && origin(url) == origin(&server(app))
133133 }
134134
135+/// Where the bundled pages are served under `tauri dev`: with no `devUrl`
136+/// in tauri.conf.json, the Tauri CLI serves `launcher/` itself, from
137+/// localhost, and the window starts there. Unset in a release build, which
138+/// serves them from inside the app.
139+static DEV_SERVER: std::sync::OnceLock<Option<Url>> = std::sync::OnceLock::new();
140+
141+/// Notes the dev server, once, as the app starts.
142+pub fn note_dev_server(app: &AppHandle) {
143+ let dev = if cfg!(debug_assertions) { app.config().build.dev_url.clone() } else { None };
144+ let _ = DEV_SERVER.set(dev);
145+}
146+
147+pub fn dev_server() -> Option<&'static Url> {
148+ DEV_SERVER.get().and_then(Option::as_ref)
149+}
150+
135151 /// Whether a page is the launcher, bundled with the app.
136152 pub fn is_launcher(url: &Url) -> bool {
137− url.scheme() == "tauri" || (matches!(url.scheme(), "http" | "https") && url.host_str() == Some("tauri.localhost"))
153+ url.scheme() == "tauri"
154+ || (matches!(url.scheme(), "http" | "https") && url.host_str() == Some("tauri.localhost"))
155+ || dev_server().is_some_and(|dev| origin(dev) == origin(url))
138156 }
139157
140158 /// What the server's pages may call: the bridge's commands (bridge.rs).
+17−3
2626 use tauri::{RunEvent, WebviewUrl, WebviewWindowBuilder, WindowEvent};
2727 use tauri_plugin_window_state::{StateFlags, WindowExt};
2828
29+/// What the window keeps between launches: its size, place, and whether it
30+/// was maximized or full screen; not its frame.
31+const KEPT: StateFlags = StateFlags::all().difference(StateFlags::DECORATIONS);
32+
2933 /// The one window's label.
3034 pub const WINDOW: &str = "main";
3135
4044 .plugin(tauri_plugin_opener::init())
4145 .plugin(tauri_plugin_updater::Builder::new().build())
4246 // The window's size and place come back in setup, without the plugin
43− // also showing it: at sign-in g1t may start in the tray.
44− .plugin(tauri_plugin_window_state::Builder::new().skip_initial_state(WINDOW).with_denylist(&[settings::SETTINGS]).build())
47+ // also showing it: at sign-in g1t may start in the tray. Whether it
48+ // has the system's frame is the title bar setting's (frame.rs), never
49+ // what an earlier run left: kept, it would put the frame back round
50+ // g1t's own title bar.
51+ .plugin(
52+ tauri_plugin_window_state::Builder::new()
53+ .with_state_flags(KEPT)
54+ .skip_initial_state(WINDOW)
55+ .with_denylist(&[settings::SETTINGS])
56+ .build(),
57+ )
4558 // Opening at sign-in, which settings turn on; the argument tells that start from someone's.
4659 .plugin(tauri_plugin_autostart::init(tauri_plugin_autostart::MacosLauncher::LaunchAgent, Some(vec![settings::AT_LOGIN])))
4760 .manage(config::State::default())
6679 .on_menu_event(|app, event| menu::handle(app, event.id().as_ref()))
6780 .setup(|app| {
6881 let handle = app.handle().clone();
82+ config::note_dev_server(&handle);
6983 config::load(&handle);
7084 config::allow_site(&handle)?;
7185 let hidden = settings::starts_hidden(&handle);
92106 let builder = builder.menu(menu::build(&handle)?);
93107 let window = frame::style(builder, custom).build()?;
94108 // Opened at sign-in, g1t waits in the tray; otherwise the window comes up where it was.
95− let _ = window.restore_state(StateFlags::all() - StateFlags::VISIBLE);
109+ let _ = window.restore_state(KEPT - StateFlags::VISIBLE);
96110 #[cfg(not(target_os = "macos"))]
97111 frame::menu_bar(&window, custom);
98112 if !hidden {
+1−1
9696 /// The address of the launcher page bundled with the app.
9797 pub fn launcher_url(query: &str) -> url::Url {
9898 let base = if cfg!(windows) { "http://tauri.localhost/" } else { "tauri://localhost/" };
99− let mut url = url::Url::parse(base).expect("the launcher's address is a URL");
99+ let mut url = config::dev_server().cloned().unwrap_or_else(|| url::Url::parse(base).expect("the launcher's address is a URL"));
100100 url.set_path("/index.html");
101101 if !query.is_empty() {
102102 url.set_query(Some(query));
+12−0
1+/**
2+ * When a Customize page reads its data again: for another page, the other
3+ * view, or a posted form (lib/customize.ts `needsReload`). A route export
4+ * the browser runs, so it lives apart from the server-only helpers.
5+ */
6+import type { ShouldRevalidateFunctionArgs } from "react-router";
7+
8+import { needsReload } from "./customize";
9+
10+export function shouldRevalidate({ currentUrl, nextUrl, formMethod, defaultShouldRevalidate }: ShouldRevalidateFunctionArgs): boolean {
11+ return needsReload(currentUrl, nextUrl, formMethod) ? defaultShouldRevalidate : false;
12+}
+2−7
44 * listing tools again, where it reaches, removing it), and what follows
55 * adding one: the browser goes to sign in, or to the connector's page.
66 */
7−import { type ShouldRevalidateFunctionArgs, redirect } from "react-router";
7+import { redirect } from "react-router";
88
99 import type { ConnectorInput, User, WorkspaceConnector } from "@g1t/contracts";
1010
1111 import { addresses } from "./addresses.server";
12−import { connectorCallbackPath, connectorPagePath, needsReload, splitTarget } from "./customize";
12+import { connectorCallbackPath, connectorPagePath, splitTarget } from "./customize";
1313 import { workspaceAgents } from "./services.server";
1414 import type { ActionResult } from "../components/agents/dialogs";
1515 import { answer } from "../components/agents/actions.server";
1717 /** Where a connector's sign-in comes back to: this g1t's own page. */
1818 export function callbackUri(slug: string): string {
1919 return `${addresses().site}${connectorCallbackPath(slug)}`;
20−}
21−
22−/** A Customize page reads its data again only for another page, the other view, or a posted form (lib/customize.ts). */
23−export function shouldRevalidate({ currentUrl, nextUrl, formMethod, defaultShouldRevalidate }: ShouldRevalidateFunctionArgs): boolean {
24− return needsReload(currentUrl, nextUrl, formMethod) ? defaultShouldRevalidate : false;
2520 }
2621
2722 /** The browser leaves for the connector's sign-in page, or lands on the connector's own page with what happened. */
+1−1
3636 import { skillLibrary, workspaceAgents } from "../../../lib/services.server";
3737 import { requireUser, roleIn } from "../../../lib/session.server";
3838
39−export { shouldRevalidate } from "../../../lib/customize.server";
39+export { shouldRevalidate } from "../../../lib/customize-revalidate";
4040
4141 export function meta({ params, ...args }: Route.MetaArgs) {
4242 return page(args, { title: `Connectors · Customize · ${params.owner} · g1t` });
+1−1
3434 import { workspaceAgents } from "../../../lib/services.server";
3535 import { requireUser, roleIn } from "../../../lib/session.server";
3636
37−export { shouldRevalidate } from "../../../lib/customize.server";
37+export { shouldRevalidate } from "../../../lib/customize-revalidate";
3838
3939 export function meta({ params, ...args }: Route.MetaArgs) {
4040 return page(args, { title: `Customize · ${params.owner} · g1t` });
+1−1
2525 import { workspaceAgents } from "../../../lib/services.server";
2626 import { requireUser, roleIn } from "../../../lib/session.server";
2727
28−export { shouldRevalidate } from "../../../lib/customize.server";
28+export { shouldRevalidate } from "../../../lib/customize-revalidate";
2929
3030 export function meta({ params, ...args }: Route.MetaArgs) {
3131 return page(args, { title: `Marketplaces · Customize · ${params.owner} · g1t` });
+1−1
2929 import { workspaceAgents } from "../../../lib/services.server";
3030 import { requireUser, roleIn } from "../../../lib/session.server";
3131
32−export { shouldRevalidate } from "../../../lib/customize.server";
32+export { shouldRevalidate } from "../../../lib/customize-revalidate";
3333
3434 export function meta({ params, ...args }: Route.MetaArgs) {
3535 return page(args, { title: `Plugins · Customize · ${params.owner} · g1t` });
+1−1
3030 import { skillLibrary, workspaceAgents } from "../../../lib/services.server";
3131 import { requireUser, roleIn } from "../../../lib/session.server";
3232
33−export { shouldRevalidate } from "../../../lib/customize.server";
33+export { shouldRevalidate } from "../../../lib/customize-revalidate";
3434
3535 export function meta({ params, ...args }: Route.MetaArgs) {
3636 return page(args, { title: `Skills · Customize · ${params.owner} · g1t` });
+1−1
11 [package]
22 name = "g1t-runner"
3−version = "0.1.2"
3+version = "0.1.3"
44 edition.workspace = true
55 license.workspace = true
66 description = "The program inside a g1t sandbox: runs an agent, checks, a catch-up merge, a review or a GitHub Actions job, and reports back. Also the self-hosted runner, g1t-runner register/run, on your own machines."
+21−3
315315 let fetched = self.fetch_to(artifact, &file).and_then(|format| {
316316 if format == "tgz" {
317317 std::fs::create_dir_all(into).map_err(|e| e.to_string())?;
318− let script = format!("tar -xzf {} -C {}", quote(&file.display().to_string()), quote(&into.display().to_string()));
318+ let script = format!("tar{} -xzf {} -C {}", tar_local(), quote(&file.display().to_string()), quote(&into.display().to_string()));
319319 if self.shell(&script) { Ok(()) } else { Err("it could not be unpacked".into()) }
320320 } else {
321321 zip::extract(&file, into).map(|_| ()).map_err(|e| format!("it could not be unpacked: {e}"))
558558 let lookup_only = with.get("lookup-only").is_some_and(|v| v == "true");
559559 let size = std::fs::metadata(&archive).map(|m| m.len()).unwrap_or(0);
560560 // tar finds out from the archive whether it is zstd or gzip.
561− if !lookup_only && !self.shell(&format!("tar -xPf {}", quote(&archive.display().to_string()))) {
561+ if !lookup_only && !self.shell(&format!("tar{} -xPf {}", tar_local(), quote(&archive.display().to_string()))) {
562562 self.log.line("##[warning]The cache was found but could not be unpacked.");
563563 }
564564 let _ = std::fs::remove_file(&archive);
896896 for f in \"${{found[@]}}\"; do if [ -e \"$f\" ]; then files+=(\"$f\"); fi; done; \
897897 if [ ${{#files[@]}} -eq 0 ]; then exit 3; fi; \
898898 if command -v zstd >/dev/null; then compress='zstd -T0 -3'; else compress=gzip; fi; \
899− tar -cPf {} -I \"$compress\" {} -- \"${{files[@]}}\"",
899+ tar{} -cPf {} -I \"$compress\" {} -- \"${{files[@]}}\"",
900900 includes.join(" "),
901+ tar_local(),
901902 quote(archive),
902903 excludes.join(" ")
903904 )
904905 }
905906
907+/// GNU tar on Windows reads `C:...` as a host and a file; `--force-local`
908+/// makes every path a local one. Elsewhere the flag is left off, since
909+/// bsdtar (macOS) doesn't know it.
910+pub(crate) fn tar_local() -> &'static str {
911+ if cfg!(windows) {
912+ " --force-local"
913+ } else {
914+ ""
915+ }
916+}
917+
906918 fn urlencode(text: &str) -> String {
907919 let mut out = String::new();
908920 for byte in text.bytes() {
940952 }
941953
942954 #[test]
955+ fn packing_keeps_windows_paths_local() {
956+ let script = pack_script(&["C:/w/target".into()], "C:/t/c.tar");
957+ assert_eq!(script.contains("--force-local"), cfg!(windows), "{script}");
958+ }
959+
960+ #[test]
943961 fn packing_expands_globs_and_leaves_exclusions_out() {
944962 let script = pack_script(&["/w/target/*/release".into(), "!/w/target/**/incremental".into()], "/t/c.tar");
945963 assert!(script.contains("found=( /w/target/*/release )"), "{script}");
+5−1
300300 let _ = std::fs::create_dir_all(&dir);
301301 let mut command = Command::new("tar");
302302 command.arg("-xzf").arg(&archive).arg("-C").arg(&dir).arg("--strip-components=1");
303+ if cfg!(windows) {
304+ // GNU tar on Windows reads `C:...` as a host and a file.
305+ command.arg("--force-local");
306+ }
303307 let mut commands = Commands::default();
304308 matches!(process::run(command, Duration::from_secs(300).min(self.remaining_time()), &mut self.log, &mut commands), Ok(Ended::Exited(0)))
305309 }
334338 }
335339 let _ = std::fs::create_dir_all(&dir);
336340 let url = format!("https://codeload.github.com/{owner}/{repo}/tar.gz/{}", sha.unwrap_or(git_ref));
337− let script = format!("set -o pipefail; curl -fsSL --retry 3 '{url}' | tar -xz -C '{}' --strip-components=1", dir.display());
341+ let script = format!("set -o pipefail; curl -fsSL --retry 3 '{url}' | tar{} -xz -C '{}' --strip-components=1", super::blobs::tar_local(), dir.display());
338342 let mut command = Command::new("bash");
339343 command.args(["-c", &script]);
340344 let mut commands = Commands::default();