mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-23 16:14:53 +01:00
Compare commits
1 Commits
v2026.2.1-
...
v2026.2.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cab111965 |
23
.github/workflows/flathub.yml
vendored
23
.github/workflows/flathub.yml
vendored
@@ -16,6 +16,9 @@ jobs:
|
||||
- name: Checkout app repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run update-manifest.sh
|
||||
run: bash flatpak/update-manifest.sh "${{ github.event.release.tag_name }}"
|
||||
|
||||
- name: Checkout Flathub repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -23,23 +26,11 @@ jobs:
|
||||
token: ${{ secrets.FLATHUB_TOKEN }}
|
||||
path: flathub-repo
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Install source generators
|
||||
- name: Copy updated files to Flathub repo
|
||||
run: |
|
||||
pip install flatpak-node-generator tomlkit aiohttp
|
||||
git clone --depth 1 https://github.com/flatpak/flatpak-builder-tools flatpak/flatpak-builder-tools
|
||||
|
||||
- name: Run update-manifest.sh
|
||||
run: bash flatpak/update-manifest.sh "${{ github.event.release.tag_name }}" flathub-repo
|
||||
cp flatpak/app.yaak.Yaak.yml flathub-repo/
|
||||
cp LICENSE flathub-repo/
|
||||
sed -i 's|path: \.\./LICENSE|path: LICENSE|' flathub-repo/app.yaak.Yaak.yml
|
||||
|
||||
- name: Commit and push to Flathub
|
||||
working-directory: flathub-repo
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -48,6 +48,3 @@ crates-tauri/yaak-app/tauri.worktree.conf.json
|
||||
# Flatpak build artifacts
|
||||
flatpak-repo/
|
||||
.flatpak-builder/
|
||||
flatpak/flatpak-builder-tools/
|
||||
flatpak/cargo-sources.json
|
||||
flatpak/node-sources.json
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -8167,7 +8167,6 @@ dependencies = [
|
||||
"cookie",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"http-body",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"mime_guess",
|
||||
|
||||
@@ -414,7 +414,7 @@ async fn execute_transaction<R: Runtime>(
|
||||
sendable_request.body = Some(SendableBody::Bytes(bytes));
|
||||
None
|
||||
}
|
||||
Some(SendableBody::Stream { data: stream, content_length }) => {
|
||||
Some(SendableBody::Stream(stream)) => {
|
||||
// Wrap stream with TeeReader to capture data as it's read
|
||||
// Use unbounded channel to ensure all data is captured without blocking the HTTP request
|
||||
let (body_chunk_tx, body_chunk_rx) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
|
||||
@@ -448,7 +448,7 @@ async fn execute_transaction<R: Runtime>(
|
||||
None
|
||||
};
|
||||
|
||||
sendable_request.body = Some(SendableBody::Stream { data: pinned, content_length });
|
||||
sendable_request.body = Some(SendableBody::Stream(pinned));
|
||||
handle
|
||||
}
|
||||
None => {
|
||||
|
||||
@@ -1095,13 +1095,8 @@ async fn cmd_get_http_authentication_config<R: Runtime>(
|
||||
|
||||
// Convert HashMap<String, JsonPrimitive> to serde_json::Value for rendering
|
||||
let values_json: serde_json::Value = serde_json::to_value(&values)?;
|
||||
let rendered_json = render_json_value(
|
||||
values_json,
|
||||
environment_chain,
|
||||
&cb,
|
||||
&RenderOptions::return_empty(),
|
||||
)
|
||||
.await?;
|
||||
let rendered_json =
|
||||
render_json_value(values_json, environment_chain, &cb, &RenderOptions::throw()).await?;
|
||||
|
||||
// Convert back to HashMap<String, JsonPrimitive>
|
||||
let rendered_values: HashMap<String, JsonPrimitive> = serde_json::from_value(rendered_json)?;
|
||||
|
||||
@@ -46,13 +46,13 @@
|
||||
"deb": {
|
||||
"desktopTemplate": "./template.desktop",
|
||||
"files": {
|
||||
"/usr/share/metainfo/app.yaak.Yaak.metainfo.xml": "../../flatpak/app.yaak.Yaak.metainfo.xml"
|
||||
"usr/share/metainfo/app.yaak.Yaak.metainfo.xml": "../../flatpak/app.yaak.Yaak.metainfo.xml"
|
||||
}
|
||||
},
|
||||
"rpm": {
|
||||
"desktopTemplate": "./template.desktop",
|
||||
"files": {
|
||||
"/usr/share/metainfo/app.yaak.Yaak.metainfo.xml": "../../flatpak/app.yaak.Yaak.metainfo.xml"
|
||||
"usr/share/metainfo/app.yaak.Yaak.metainfo.xml": "../../flatpak/app.yaak.Yaak.metainfo.xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ bytes = "1.11.1"
|
||||
cookie = "0.18.1"
|
||||
flate2 = "1"
|
||||
futures-util = "0.3"
|
||||
http-body = "1"
|
||||
url = "2"
|
||||
zstd = "0.13"
|
||||
hyper-util = { version = "0.1.17", default-features = false, features = ["client-legacy"] }
|
||||
|
||||
@@ -2,9 +2,7 @@ use crate::decompress::{ContentEncoding, streaming_decoder};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::types::{SendableBody, SendableHttpRequest};
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use futures_util::StreamExt;
|
||||
use http_body::{Body as HttpBody, Frame, SizeHint};
|
||||
use reqwest::{Client, Method, Version};
|
||||
use std::fmt::Display;
|
||||
use std::pin::Pin;
|
||||
@@ -415,16 +413,10 @@ impl HttpSender for ReqwestSender {
|
||||
Some(SendableBody::Bytes(bytes)) => {
|
||||
req_builder = req_builder.body(bytes);
|
||||
}
|
||||
Some(SendableBody::Stream { data, content_length }) => {
|
||||
// Convert AsyncRead stream to reqwest Body. If content length is
|
||||
// known, wrap with a SizedBody so hyper can set Content-Length
|
||||
// automatically (for both HTTP/1.1 and HTTP/2).
|
||||
let stream = tokio_util::io::ReaderStream::new(data);
|
||||
let body = if let Some(len) = content_length {
|
||||
reqwest::Body::wrap(SizedBody::new(stream, len))
|
||||
} else {
|
||||
reqwest::Body::wrap_stream(stream)
|
||||
};
|
||||
Some(SendableBody::Stream(stream)) => {
|
||||
// Convert AsyncRead stream to reqwest Body
|
||||
let stream = tokio_util::io::ReaderStream::new(stream);
|
||||
let body = reqwest::Body::wrap_stream(stream);
|
||||
req_builder = req_builder.body(body);
|
||||
}
|
||||
}
|
||||
@@ -528,51 +520,6 @@ impl HttpSender for ReqwestSender {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a byte stream that reports a known content length via
|
||||
/// `size_hint()`. This lets hyper set the `Content-Length` header
|
||||
/// automatically based on the body size, without us having to add it as an
|
||||
/// explicit header — which can cause duplicate `Content-Length` headers and
|
||||
/// break HTTP/2.
|
||||
struct SizedBody<S> {
|
||||
stream: std::sync::Mutex<S>,
|
||||
remaining: u64,
|
||||
}
|
||||
|
||||
impl<S> SizedBody<S> {
|
||||
fn new(stream: S, content_length: u64) -> Self {
|
||||
Self { stream: std::sync::Mutex::new(stream), remaining: content_length }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> HttpBody for SizedBody<S>
|
||||
where
|
||||
S: futures_util::Stream<Item = std::result::Result<Bytes, std::io::Error>> + Send + Unpin + 'static,
|
||||
{
|
||||
type Data = Bytes;
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn poll_frame(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<std::result::Result<Frame<Self::Data>, Self::Error>>> {
|
||||
let this = self.get_mut();
|
||||
let mut stream = this.stream.lock().unwrap();
|
||||
match stream.poll_next_unpin(cx) {
|
||||
Poll::Ready(Some(Ok(chunk))) => {
|
||||
this.remaining = this.remaining.saturating_sub(chunk.len() as u64);
|
||||
Poll::Ready(Some(Ok(Frame::data(chunk))))
|
||||
}
|
||||
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
SizeHint::with_exact(self.remaining)
|
||||
}
|
||||
}
|
||||
|
||||
fn version_to_str(version: &Version) -> String {
|
||||
match *version {
|
||||
Version::HTTP_09 => "HTTP/0.9".to_string(),
|
||||
|
||||
@@ -16,13 +16,7 @@ pub(crate) const MULTIPART_BOUNDARY: &str = "------YaakFormBoundary";
|
||||
|
||||
pub enum SendableBody {
|
||||
Bytes(Bytes),
|
||||
Stream {
|
||||
data: Pin<Box<dyn AsyncRead + Send + 'static>>,
|
||||
/// Known content length for the stream, if available. This is used by
|
||||
/// the sender to set the body size hint so that hyper can set
|
||||
/// Content-Length automatically for both HTTP/1.1 and HTTP/2.
|
||||
content_length: Option<u64>,
|
||||
},
|
||||
Stream(Pin<Box<dyn AsyncRead + Send + 'static>>),
|
||||
}
|
||||
|
||||
enum SendableBodyWithMeta {
|
||||
@@ -37,10 +31,7 @@ impl From<SendableBodyWithMeta> for SendableBody {
|
||||
fn from(value: SendableBodyWithMeta) -> Self {
|
||||
match value {
|
||||
SendableBodyWithMeta::Bytes(b) => SendableBody::Bytes(b),
|
||||
SendableBodyWithMeta::Stream { data, content_length } => SendableBody::Stream {
|
||||
data,
|
||||
content_length: content_length.map(|l| l as u64),
|
||||
},
|
||||
SendableBodyWithMeta::Stream { data, .. } => SendableBody::Stream(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,11 +186,23 @@ async fn build_body(
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Content-Length is NOT set as an explicit header here. Instead, the
|
||||
// body's content length is carried via SendableBody::Stream { content_length }
|
||||
// and used by the sender to set the body size hint. This lets hyper handle
|
||||
// Content-Length automatically for both HTTP/1.1 and HTTP/2, avoiding the
|
||||
// duplicate Content-Length that breaks HTTP/2 servers.
|
||||
// Check if Transfer-Encoding: chunked is already set
|
||||
let has_chunked_encoding = headers.iter().any(|h| {
|
||||
h.0.to_lowercase() == "transfer-encoding" && h.1.to_lowercase().contains("chunked")
|
||||
});
|
||||
|
||||
// Add a Content-Length header only if chunked encoding is not being used
|
||||
if !has_chunked_encoding {
|
||||
let content_length = match body {
|
||||
Some(SendableBodyWithMeta::Bytes(ref bytes)) => Some(bytes.len()),
|
||||
Some(SendableBodyWithMeta::Stream { content_length, .. }) => content_length,
|
||||
None => None,
|
||||
};
|
||||
|
||||
if let Some(cl) = content_length {
|
||||
headers.push(("Content-Length".to_string(), cl.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((body.map(|b| b.into()), headers))
|
||||
}
|
||||
@@ -925,27 +928,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_no_content_length_header_added_by_build_body() -> Result<()> {
|
||||
let mut body = BTreeMap::new();
|
||||
body.insert("text".to_string(), json!("Hello, World!"));
|
||||
|
||||
let headers = vec![];
|
||||
|
||||
let (_, result_headers) =
|
||||
build_body("POST", &Some("text/plain".to_string()), &body, headers).await?;
|
||||
|
||||
// Content-Length should NOT be set as an explicit header. Instead, the
|
||||
// sender uses the body's size_hint to let hyper set it automatically,
|
||||
// which works correctly for both HTTP/1.1 and HTTP/2.
|
||||
let has_content_length =
|
||||
result_headers.iter().any(|h| h.0.to_lowercase() == "content-length");
|
||||
assert!(!has_content_length, "Content-Length should not be set as an explicit header");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_chunked_encoding_header_preserved() -> Result<()> {
|
||||
async fn test_no_content_length_with_chunked_encoding() -> Result<()> {
|
||||
let mut body = BTreeMap::new();
|
||||
body.insert("text".to_string(), json!("Hello, World!"));
|
||||
|
||||
@@ -955,6 +938,11 @@ mod tests {
|
||||
let (_, result_headers) =
|
||||
build_body("POST", &Some("text/plain".to_string()), &body, headers).await?;
|
||||
|
||||
// Verify that Content-Length is NOT present when Transfer-Encoding: chunked is set
|
||||
let has_content_length =
|
||||
result_headers.iter().any(|h| h.0.to_lowercase() == "content-length");
|
||||
assert!(!has_content_length, "Content-Length should not be present with chunked encoding");
|
||||
|
||||
// Verify that the Transfer-Encoding header is still present
|
||||
let has_chunked = result_headers.iter().any(|h| {
|
||||
h.0.to_lowercase() == "transfer-encoding" && h.1.to_lowercase().contains("chunked")
|
||||
@@ -963,4 +951,31 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_content_length_without_chunked_encoding() -> Result<()> {
|
||||
let mut body = BTreeMap::new();
|
||||
body.insert("text".to_string(), json!("Hello, World!"));
|
||||
|
||||
// Headers without Transfer-Encoding: chunked
|
||||
let headers = vec![];
|
||||
|
||||
let (_, result_headers) =
|
||||
build_body("POST", &Some("text/plain".to_string()), &body, headers).await?;
|
||||
|
||||
// Verify that Content-Length IS present when Transfer-Encoding: chunked is NOT set
|
||||
let content_length_header =
|
||||
result_headers.iter().find(|h| h.0.to_lowercase() == "content-length");
|
||||
assert!(
|
||||
content_length_header.is_some(),
|
||||
"Content-Length should be present without chunked encoding"
|
||||
);
|
||||
assert_eq!(
|
||||
content_length_header.unwrap().1,
|
||||
"13",
|
||||
"Content-Length should match the body size"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
const { execSync } = require('node:child_process');
|
||||
|
||||
if (process.env.SKIP_WASM_BUILD === '1') {
|
||||
console.log('Skipping wasm-pack build (SKIP_WASM_BUILD=1)');
|
||||
return;
|
||||
}
|
||||
|
||||
execSync('wasm-pack build --target bundler', { stdio: 'inherit' });
|
||||
@@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"bootstrap": "npm run build",
|
||||
"build": "run-s build:*",
|
||||
"build:pack": "node build-wasm.cjs",
|
||||
"build:pack": "wasm-pack build --target bundler",
|
||||
"build:clean": "rimraf ./pkg/.gitignore"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -81,10 +81,6 @@ impl RenderOptions {
|
||||
pub fn throw() -> Self {
|
||||
Self { error_behavior: RenderErrorBehavior::Throw }
|
||||
}
|
||||
|
||||
pub fn return_empty() -> Self {
|
||||
Self { error_behavior: RenderErrorBehavior::ReturnEmpty }
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderErrorBehavior {
|
||||
|
||||
@@ -52,6 +52,6 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2026.2.0" date="2026-02-10" />
|
||||
<release version="2026.1.2" date="2026-02-10" />
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
61
flatpak/app.yaak.Yaak.yml
Normal file
61
flatpak/app.yaak.Yaak.yml
Normal file
@@ -0,0 +1,61 @@
|
||||
id: app.yaak.Yaak
|
||||
runtime: org.gnome.Platform
|
||||
runtime-version: "49"
|
||||
sdk: org.gnome.Sdk
|
||||
command: yaak-app
|
||||
rename-desktop-file: yaak.desktop
|
||||
rename-icon: yaak-app
|
||||
|
||||
finish-args:
|
||||
- --socket=wayland
|
||||
- --socket=fallback-x11
|
||||
- --share=ipc
|
||||
- --device=dri
|
||||
- --share=network
|
||||
- --socket=pulseaudio # Preview audio responses
|
||||
- --socket=ssh-auth # Git SSH remotes
|
||||
- --socket=gpg-agent # Git commit signing
|
||||
- --talk-name=org.freedesktop.secrets # Keyring for encryption
|
||||
- --filesystem=home # Git repos, ~/.gitconfig, ~/.ssh, etc
|
||||
|
||||
modules:
|
||||
- name: git
|
||||
cleanup:
|
||||
- /share
|
||||
make-args:
|
||||
- NO_PERL=1
|
||||
- NO_TCLTK=1
|
||||
make-install-args:
|
||||
- INSTALL_SYMLINKS=1
|
||||
- NO_PERL=1
|
||||
- NO_TCLTK=1
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://www.kernel.org/pub/software/scm/git/git-2.48.1.tar.gz
|
||||
sha256: 51b4d03b1e311ba673591210f94f24a4c5781453e1eb188822e3d9cdc04c2212
|
||||
|
||||
- name: yaak
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- ar -x yaak.deb
|
||||
- tar -xf data.tar.gz
|
||||
- mv usr/bin/* /app/bin
|
||||
- mv usr/lib/* /app/lib
|
||||
- mv usr/share/* /app/share
|
||||
- install -Dm644 LICENSE /app/share/licenses/app.yaak.Yaak/LICENSE
|
||||
|
||||
sources:
|
||||
- type: file
|
||||
dest-filename: yaak.deb
|
||||
url: https://github.com/mountain-loop/yaak/releases/download/v2026.1.2/yaak_2026.1.2_amd64.deb
|
||||
sha256: "c4236b5bcf391e579dc79b71c3b5c58f6f9bfc6c175fc70426d0ca85799beba5"
|
||||
only-arches:
|
||||
- x86_64
|
||||
- type: file
|
||||
dest-filename: yaak.deb
|
||||
url: https://github.com/mountain-loop/yaak/releases/download/v2026.1.2/yaak_2026.1.2_arm64.deb
|
||||
sha256: "9ba9b7c9df56ffb9b801e40cb38685f1650cf7e2f9e85dad0ae3329f8e01ff6d"
|
||||
only-arches:
|
||||
- aarch64
|
||||
- type: file
|
||||
path: ../LICENSE
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Adds missing `resolved` and `integrity` fields to npm package-lock.json.
|
||||
//
|
||||
// npm sometimes omits these fields for nested dependencies inside workspace
|
||||
// packages. This breaks offline installs and tools like flatpak-node-generator
|
||||
// that need explicit tarball URLs for every package.
|
||||
//
|
||||
// Based on https://github.com/grant-dennison/npm-package-lock-add-resolved
|
||||
// (MIT License, Copyright (c) 2024 Grant Dennison)
|
||||
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { get } from "node:https";
|
||||
|
||||
const lockfilePath = process.argv[2] || "package-lock.json";
|
||||
|
||||
function fetchJson(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
get(url, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
res.on("end", () => {
|
||||
if (res.statusCode === 200) {
|
||||
resolve(JSON.parse(data));
|
||||
} else {
|
||||
reject(`${url} returned ${res.statusCode} ${res.statusMessage}`);
|
||||
}
|
||||
});
|
||||
res.on("error", reject);
|
||||
}).on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function fillResolved(name, p) {
|
||||
const version = p.version.replace(/^.*@/, "");
|
||||
console.log(`Retrieving metadata for ${name}@${version}`);
|
||||
const metadataUrl = `https://registry.npmjs.com/${name}/${version}`;
|
||||
const metadata = await fetchJson(metadataUrl);
|
||||
p.resolved = metadata.dist.tarball;
|
||||
p.integrity = metadata.dist.integrity;
|
||||
}
|
||||
|
||||
let changesMade = false;
|
||||
|
||||
async function fillAllResolved(packages) {
|
||||
for (const packagePath in packages) {
|
||||
if (packagePath === "") continue;
|
||||
if (!packagePath.includes("node_modules/")) continue;
|
||||
const p = packages[packagePath];
|
||||
if (p.link) continue;
|
||||
if (!p.inBundle && !p.bundled && (!p.resolved || !p.integrity)) {
|
||||
const packageName =
|
||||
p.name ||
|
||||
/^npm:(.+?)@.+$/.exec(p.version)?.[1] ||
|
||||
packagePath.replace(/^.*node_modules\/(?=.+?$)/, "");
|
||||
await fillResolved(packageName, p);
|
||||
changesMade = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const oldContents = await readFile(lockfilePath, "utf-8");
|
||||
const packageLock = JSON.parse(oldContents);
|
||||
|
||||
await fillAllResolved(packageLock.packages ?? []);
|
||||
|
||||
if (changesMade) {
|
||||
const newContents = JSON.stringify(packageLock, null, 2) + "\n";
|
||||
await writeFile(lockfilePath, newContents);
|
||||
console.log(`Updated ${lockfilePath}`);
|
||||
} else {
|
||||
console.log("No changes needed.");
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Generate offline dependency source files for Flatpak builds.
|
||||
#
|
||||
# Prerequisites:
|
||||
# pip install flatpak-node-generator tomlkit aiohttp
|
||||
# Clone https://github.com/flatpak/flatpak-builder-tools (for cargo generator)
|
||||
#
|
||||
# Usage:
|
||||
# ./flatpak/generate-sources.sh <flathub-repo-path>
|
||||
# ./flatpak/generate-sources.sh ../flathub-repo
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <flathub-repo-path>"
|
||||
echo "Example: $0 ../flathub-repo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FLATHUB_REPO="$(cd "$1" && pwd)"
|
||||
|
||||
python3 "$SCRIPT_DIR/flatpak-builder-tools/cargo/flatpak-cargo-generator.py" \
|
||||
-o "$FLATHUB_REPO/cargo-sources.json" "$REPO_ROOT/Cargo.lock"
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
cp "$REPO_ROOT/package-lock.json" "$TMPDIR/package-lock.json"
|
||||
cp "$REPO_ROOT/package.json" "$TMPDIR/package.json"
|
||||
|
||||
node "$SCRIPT_DIR/fix-lockfile.mjs" "$TMPDIR/package-lock.json"
|
||||
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const p = process.argv[1];
|
||||
const d = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
||||
for (const [name, info] of Object.entries(d.packages || {})) {
|
||||
if (name && (info.link || !info.resolved)) delete d.packages[name];
|
||||
}
|
||||
fs.writeFileSync(p, JSON.stringify(d, null, 2));
|
||||
" "$TMPDIR/package-lock.json"
|
||||
|
||||
flatpak-node-generator --no-requests-cache \
|
||||
-o "$FLATHUB_REPO/node-sources.json" npm "$TMPDIR/package-lock.json"
|
||||
@@ -1,86 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Update the Flathub repo for a new release.
|
||||
# Update the Flatpak manifest with URLs and SHA256 hashes for a given release.
|
||||
#
|
||||
# Usage:
|
||||
# ./flatpak/update-manifest.sh <version-tag> <flathub-repo-path>
|
||||
# ./flatpak/update-manifest.sh v2026.2.0 ../flathub-repo
|
||||
# ./flatpak/update-manifest.sh v2026.2.0
|
||||
#
|
||||
# This script:
|
||||
# 1. Downloads the x86_64 and aarch64 .deb files from the GitHub release
|
||||
# 2. Computes their SHA256 checksums
|
||||
# 3. Updates the manifest YAML with the correct URLs and hashes
|
||||
# 4. Updates the metainfo.xml with a new <release> entry
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MANIFEST="$SCRIPT_DIR/app.yaak.Yaak.yml"
|
||||
METAINFO="$SCRIPT_DIR/app.yaak.Yaak.metainfo.xml"
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 <version-tag> <flathub-repo-path>"
|
||||
echo "Example: $0 v2026.2.0 ../flathub-repo"
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <version-tag>"
|
||||
echo "Example: $0 v2026.2.0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION_TAG="$1"
|
||||
VERSION="${VERSION_TAG#v}"
|
||||
FLATHUB_REPO="$(cd "$2" && pwd)"
|
||||
MANIFEST="$FLATHUB_REPO/app.yaak.Yaak.yml"
|
||||
METAINFO="$SCRIPT_DIR/app.yaak.Yaak.metainfo.xml"
|
||||
|
||||
# Only allow stable releases (skip beta, alpha, rc, etc.)
|
||||
if [[ "$VERSION" == *-* ]]; then
|
||||
echo "Skipping pre-release version '$VERSION_TAG' (only stable releases are published to Flathub)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REPO="mountain-loop/yaak"
|
||||
COMMIT=$(git ls-remote "https://github.com/$REPO.git" "refs/tags/$VERSION_TAG" | cut -f1)
|
||||
BASE_URL="https://github.com/$REPO/releases/download/$VERSION_TAG"
|
||||
|
||||
if [ -z "$COMMIT" ]; then
|
||||
echo "Error: Could not resolve commit for tag $VERSION_TAG"
|
||||
exit 1
|
||||
fi
|
||||
DEB_AMD64="yaak_${VERSION}_amd64.deb"
|
||||
DEB_ARM64="yaak_${VERSION}_arm64.deb"
|
||||
|
||||
echo "Tag: $VERSION_TAG"
|
||||
echo "Commit: $COMMIT"
|
||||
|
||||
# Update git tag and commit in the manifest
|
||||
sed -i "s|tag: v.*|tag: $VERSION_TAG|" "$MANIFEST"
|
||||
sed -i "s|commit: .*|commit: $COMMIT|" "$MANIFEST"
|
||||
echo "Updated manifest tag and commit."
|
||||
|
||||
# Regenerate offline dependency sources from the tagged lockfiles
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
echo "Fetching lockfiles from $VERSION_TAG..."
|
||||
curl -fsSL "https://raw.githubusercontent.com/$REPO/$VERSION_TAG/Cargo.lock" -o "$TMPDIR/Cargo.lock"
|
||||
curl -fsSL "https://raw.githubusercontent.com/$REPO/$VERSION_TAG/package-lock.json" -o "$TMPDIR/package-lock.json"
|
||||
curl -fsSL "https://raw.githubusercontent.com/$REPO/$VERSION_TAG/package.json" -o "$TMPDIR/package.json"
|
||||
echo "Downloading $DEB_AMD64..."
|
||||
curl -fSL "$BASE_URL/$DEB_AMD64" -o "$TMPDIR/$DEB_AMD64"
|
||||
SHA_AMD64=$(sha256sum "$TMPDIR/$DEB_AMD64" | cut -d' ' -f1)
|
||||
echo " SHA256: $SHA_AMD64"
|
||||
|
||||
echo "Generating cargo-sources.json..."
|
||||
python3 "$SCRIPT_DIR/flatpak-builder-tools/cargo/flatpak-cargo-generator.py" \
|
||||
-o "$FLATHUB_REPO/cargo-sources.json" "$TMPDIR/Cargo.lock"
|
||||
echo "Downloading $DEB_ARM64..."
|
||||
curl -fSL "$BASE_URL/$DEB_ARM64" -o "$TMPDIR/$DEB_ARM64"
|
||||
SHA_ARM64=$(sha256sum "$TMPDIR/$DEB_ARM64" | cut -d' ' -f1)
|
||||
echo " SHA256: $SHA_ARM64"
|
||||
|
||||
echo "Generating node-sources.json..."
|
||||
node "$SCRIPT_DIR/fix-lockfile.mjs" "$TMPDIR/package-lock.json"
|
||||
echo ""
|
||||
echo "Updating manifest: $MANIFEST"
|
||||
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const p = process.argv[1];
|
||||
const d = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
||||
for (const [name, info] of Object.entries(d.packages || {})) {
|
||||
if (name && (info.link || !info.resolved)) delete d.packages[name];
|
||||
}
|
||||
fs.writeFileSync(p, JSON.stringify(d, null, 2));
|
||||
" "$TMPDIR/package-lock.json"
|
||||
# Update URLs by matching the arch-specific deb filename
|
||||
sed -i "s|url: .*amd64\.deb|url: $BASE_URL/$DEB_AMD64|" "$MANIFEST"
|
||||
sed -i "s|url: .*arm64\.deb|url: $BASE_URL/$DEB_ARM64|" "$MANIFEST"
|
||||
|
||||
flatpak-node-generator --no-requests-cache \
|
||||
-o "$FLATHUB_REPO/node-sources.json" npm "$TMPDIR/package-lock.json"
|
||||
# Update SHA256 hashes by finding the current ones and replacing
|
||||
OLD_SHA_AMD64=$(grep -A2 "amd64\.deb" "$MANIFEST" | grep sha256 | sed 's/.*"\(.*\)"/\1/')
|
||||
OLD_SHA_ARM64=$(grep -A2 "arm64\.deb" "$MANIFEST" | grep sha256 | sed 's/.*"\(.*\)"/\1/')
|
||||
|
||||
sed -i "s|$OLD_SHA_AMD64|$SHA_AMD64|" "$MANIFEST"
|
||||
sed -i "s|$OLD_SHA_ARM64|$SHA_ARM64|" "$MANIFEST"
|
||||
|
||||
echo " Manifest updated."
|
||||
|
||||
echo "Updating metainfo: $METAINFO"
|
||||
|
||||
# Update metainfo with new release
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
# Insert new release entry after <releases>
|
||||
sed -i "s| <releases>| <releases>\n <release version=\"$VERSION\" date=\"$TODAY\" />|" "$METAINFO"
|
||||
echo "Updated metainfo with release $VERSION."
|
||||
|
||||
echo " Metainfo updated."
|
||||
|
||||
echo ""
|
||||
echo "Done! Review the changes:"
|
||||
echo " $MANIFEST"
|
||||
echo " $METAINFO"
|
||||
echo " $FLATHUB_REPO/cargo-sources.json"
|
||||
echo " $FLATHUB_REPO/node-sources.json"
|
||||
|
||||
70
package-lock.json
generated
70
package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"packages/plugin-runtime",
|
||||
"packages/plugin-runtime-types",
|
||||
"plugins-external/mcp-server",
|
||||
"plugins-external/faker",
|
||||
"plugins/template-function-faker",
|
||||
"plugins-external/httpsnippet",
|
||||
"plugins/action-copy-curl",
|
||||
"plugins/action-copy-grpcurl",
|
||||
@@ -4154,7 +4154,7 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaak/faker": {
|
||||
"resolved": "plugins-external/faker",
|
||||
"resolved": "plugins/template-function-faker",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaak/filter-jsonpath": {
|
||||
@@ -15957,33 +15957,6 @@
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"plugins-external/faker": {
|
||||
"name": "@yaak/faker",
|
||||
"version": "1.1.1",
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^10.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"plugins-external/faker/node_modules/@faker-js/faker": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.3.0.tgz",
|
||||
"integrity": "sha512-It0Sne6P3szg7JIi6CgKbvTZoMjxBZhcv91ZrqrNuaZQfB5WoqYYbzCUOq89YR+VY8juY9M1vDWmDDa2TzfXCw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fakerjs"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0",
|
||||
"npm": ">=10"
|
||||
}
|
||||
},
|
||||
"plugins-external/httpsnippet": {
|
||||
"name": "@yaak/httpsnippet",
|
||||
"version": "1.0.3",
|
||||
@@ -16089,6 +16062,18 @@
|
||||
"name": "@yaak/auth-oauth2",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"plugins/faker": {
|
||||
"name": "@yaak/faker",
|
||||
"version": "1.1.1",
|
||||
"extraneous": true,
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^10.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"plugins/filter-jsonpath": {
|
||||
"name": "@yaak/filter-jsonpath",
|
||||
"version": "0.1.0",
|
||||
@@ -16166,6 +16151,33 @@
|
||||
"name": "@yaak/template-function-encode",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"plugins/template-function-faker": {
|
||||
"name": "@yaak/faker",
|
||||
"version": "1.1.1",
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^10.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"plugins/template-function-faker/node_modules/@faker-js/faker": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.3.0.tgz",
|
||||
"integrity": "sha512-It0Sne6P3szg7JIi6CgKbvTZoMjxBZhcv91ZrqrNuaZQfB5WoqYYbzCUOq89YR+VY8juY9M1vDWmDDa2TzfXCw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fakerjs"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0",
|
||||
"npm": ">=10"
|
||||
}
|
||||
},
|
||||
"plugins/template-function-fs": {
|
||||
"name": "@yaak/template-function-fs",
|
||||
"version": "0.1.0"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"packages/plugin-runtime",
|
||||
"packages/plugin-runtime-types",
|
||||
"plugins-external/mcp-server",
|
||||
"plugins-external/faker",
|
||||
"plugins/template-function-faker",
|
||||
"plugins-external/httpsnippet",
|
||||
"plugins/action-copy-curl",
|
||||
"plugins/action-copy-grpcurl",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mountain-loop/yaak.git",
|
||||
"directory": "plugins-external/faker"
|
||||
"directory": "plugins/template-function-faker"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
@@ -1,6 +1,4 @@
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const decompress = require('decompress');
|
||||
const Downloader = require('nodejs-file-downloader');
|
||||
const { rmSync, cpSync, mkdirSync, existsSync } = require('node:fs');
|
||||
@@ -43,15 +41,6 @@ const DST_BIN_MAP = {
|
||||
[WIN_ARM]: 'yaaknode.exe',
|
||||
};
|
||||
|
||||
const SHA256_MAP = {
|
||||
[MAC_ARM]: 'b05aa3a66efe680023f930bd5af3fdbbd542794da5644ca2ad711d68cbd4dc35',
|
||||
[MAC_X64]: '096081b6d6fcdd3f5ba0f5f1d44a47e83037ad2e78eada26671c252fe64dd111',
|
||||
[LNX_ARM]: '0dc93ec5c798b0d347f068db6d205d03dea9a71765e6a53922b682b91265d71f',
|
||||
[LNX_X64]: '58a5ff5cc8f2200e458bea22e329d5c1994aa1b111d499ca46ec2411d58239ca',
|
||||
[WIN_X64]: '5355ae6d7c49eddcfde7d34ac3486820600a831bf81dc3bdca5c8db6a9bb0e76',
|
||||
[WIN_ARM]: 'ce9ee4e547ebdff355beb48e309b166c24df6be0291c9eaf103ce15f3de9e5b4',
|
||||
};
|
||||
|
||||
const key = `${process.platform}_${process.env.YAAK_TARGET_ARCH ?? process.arch}`;
|
||||
|
||||
const destDir = path.join(__dirname, `..`, 'crates-tauri', 'yaak-app', 'vendored', 'node');
|
||||
@@ -79,15 +68,6 @@ rmSync(tmpDir, { recursive: true, force: true });
|
||||
timeout: 1000 * 60 * 2,
|
||||
}).download();
|
||||
|
||||
// Verify SHA256
|
||||
const expectedHash = SHA256_MAP[key];
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
const actualHash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||
if (actualHash !== expectedHash) {
|
||||
throw new Error(`SHA256 mismatch for ${path.basename(filePath)}\n expected: ${expectedHash}\n actual: ${actualHash}`);
|
||||
}
|
||||
console.log('SHA256 verified:', actualHash);
|
||||
|
||||
// Decompress to the same directory
|
||||
await decompress(filePath, tmpDir, {});
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const decompress = require('decompress');
|
||||
const Downloader = require('nodejs-file-downloader');
|
||||
const path = require('node:path');
|
||||
@@ -43,15 +41,6 @@ const DST_BIN_MAP = {
|
||||
[WIN_ARM]: 'yaakprotoc.exe',
|
||||
};
|
||||
|
||||
const SHA256_MAP = {
|
||||
[MAC_ARM]: 'db7e66ff7f9080614d0f5505a6b0ac488cf89a15621b6a361672d1332ec2e14e',
|
||||
[MAC_X64]: 'e20b5f930e886da85e7402776a4959efb1ed60c57e72794bcade765e67abaa82',
|
||||
[LNX_ARM]: '6018147740548e0e0f764408c87f4cd040e6e1c1203e13aeacaf811892b604f3',
|
||||
[LNX_X64]: 'f3340e28a83d1c637d8bafdeed92b9f7db6a384c26bca880a6e5217b40a4328b',
|
||||
[WIN_X64]: 'd7a207fb6eec0e4b1b6613be3b7d11905375b6fd1147a071116eb8e9f24ac53b',
|
||||
[WIN_ARM]: 'd7a207fb6eec0e4b1b6613be3b7d11905375b6fd1147a071116eb8e9f24ac53b',
|
||||
};
|
||||
|
||||
const dstDir = path.join(__dirname, `..`, 'crates-tauri', 'yaak-app', 'vendored', 'protoc');
|
||||
const key = `${process.platform}_${process.env.YAAK_TARGET_ARCH ?? process.arch}`;
|
||||
console.log(`Vendoring protoc ${VERSION} for ${key}`);
|
||||
@@ -74,15 +63,6 @@ mkdirSync(dstDir, { recursive: true });
|
||||
// Download GitHub release artifact
|
||||
const { filePath } = await new Downloader({ url, directory: tmpDir }).download();
|
||||
|
||||
// Verify SHA256
|
||||
const expectedHash = SHA256_MAP[key];
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
const actualHash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||
if (actualHash !== expectedHash) {
|
||||
throw new Error(`SHA256 mismatch for ${path.basename(filePath)}\n expected: ${expectedHash}\n actual: ${actualHash}`);
|
||||
}
|
||||
console.log('SHA256 verified:', actualHash);
|
||||
|
||||
// Decompress to the same directory
|
||||
await decompress(filePath, tmpDir, {});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user