567ee63c7a
New agent tools (98 to 104): - export_site writes a website from frames in one call: plain HTML, Astro, Vue (Nuxt or Vite), React (Next or Vite) or Angular; css, scss or tailwind, and for React vanilla-extract, styled-components or emotion; static pages or a single-page app; a folder per language. Fonts are subset to WOFF2 with metric-matched fallbacks, images are resized, headings follow the outline, pages reflow below the designed width, and _headers, vercel.json, _redirects and .nojekyll cache hashed assets for a year. - get_strings and set_strings treat languages as variable modes: read a page's text for translation, fill a language or a pseudo-locale. - get_context says where the agent is, what is cheap to read next and what lint found, in one call; big reads take maxTokens and report estimatedTokens. - import_html opens a page from a prompt-to-design tool (.html or .zip) as layers. - install_font adds fonts on top of the bundled Inter; text can name one. App: - Present mode plays a page's frames as a prototype: links jump to frames, open overlays, go back or open an address. - Panics are kept in crash.log; release builds keep line tables so its traces name files and lines. - Settings > Canvas is kept between sessions; a keyboard shortcuts sheet; icons in the menus. - Chat: an opencode turn waiting out a rate limit moves on to the fallback model. - The updater checks each download against the SHA-256 GitHub publishes. Fixes: - A NaN or infinite tool argument no longer makes a project unopenable. - Agent paths cannot reach other machines, devices or NTFS streams; a tool writes only its own file type, and fills only a new, empty or earlier export folder. - delegate and imagegen pass each value in its own environment variable, never as shell syntax. - An instance placed inside its own component is refused. - A panic in a tool fails that call instead of closing the app. - An agent call during a drag waits for it, so the undo step stays whole. - Sync holds downloads while the open project has unsaved edits, writes pages atomically, and leaves a page saved while its download was in flight for the next round to merge. Merges repair the tree instead of giving a node two parents or orphaning it. - Share tokens come from the OS random generator. - Imports have a total memory cap; Excalidraw import is linear. - Unused images are freed when a project opens. - Pages that use no added font are still written as format 3, so 0.9.1 can open them. - Code export escapes text, attributes and CSS in one place. CI: a clippy job (warnings do not fail it yet). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J9PiVCQxc2QYmArFq9VBUX
65 lines
2.2 KiB
Rust
65 lines
2.2 KiB
Rust
use papercraft::update::{Asset, parse_release, validate_asset};
|
|
use serde_json::json;
|
|
|
|
fn feed(url: &str, tag: &str) -> String {
|
|
json!({"tag_name":tag,"html_url":url,"assets":[]}).to_string()
|
|
}
|
|
|
|
#[test]
|
|
fn similarly_named_repository_is_not_trusted() {
|
|
let json = feed(
|
|
"https://git.marcospaulo.dev.br/masi/papercraft-untrusted/releases/tag/v0.4.0",
|
|
"v0.4.0",
|
|
);
|
|
assert!(parse_release(&json, "0.3.1", "macos", "aarch64").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn normalized_download_paths_must_stay_in_official_repository() {
|
|
let asset = Asset {
|
|
name: "papercraft-0.4.0-macos-aarch64.dmg".into(),
|
|
url: "https://git.marcospaulo.dev.br/masi/papercraft/releases/download/v0.4.0/%2e%2e/%2e%2e/%2e%2e/%2e%2e/other/papercraft-0.4.0-macos-aarch64.dmg".into(),
|
|
size: 1024,
|
|
digest: "a".repeat(64),
|
|
};
|
|
assert!(validate_asset(&asset).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn prerelease_tag_is_refused_even_if_feed_flag_is_missing() {
|
|
let json = feed(
|
|
"https://git.marcospaulo.dev.br/masi/papercraft/releases/tag/v0.4.0-beta.1",
|
|
"v0.4.0-beta.1",
|
|
);
|
|
assert!(parse_release(&json, "0.3.1", "macos", "aarch64").is_err());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "downloads an installer from the official release server"]
|
|
async fn official_release_can_be_checked_and_downloaded() {
|
|
use papercraft::update::{RELEASES_URL, check, download};
|
|
check().await.expect("official update check succeeds");
|
|
let response = reqwest::Client::builder()
|
|
.timeout(std::time::Duration::from_secs(30))
|
|
.redirect(reqwest::redirect::Policy::none())
|
|
.build()
|
|
.unwrap()
|
|
.get(RELEASES_URL)
|
|
.send()
|
|
.await
|
|
.unwrap()
|
|
.error_for_status()
|
|
.unwrap();
|
|
let json = response.text().await.unwrap();
|
|
let release = parse_release(&json, "0.0.0", std::env::consts::OS, std::env::consts::ARCH)
|
|
.unwrap()
|
|
.expect("published release exists");
|
|
let asset = release
|
|
.asset
|
|
.expect("native installer published for this machine");
|
|
let path = download(&asset, |_, _| {})
|
|
.await
|
|
.expect("installer downloads completely");
|
|
assert_eq!(std::fs::metadata(path).unwrap().len(), asset.size);
|
|
}
|