Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 574fca633a | |||
| e07c0e6150 |
@@ -19,6 +19,8 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
build-linux:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.version.outputs.VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Node.js 22
|
- name: Install Node.js 22
|
||||||
run: |
|
run: |
|
||||||
@@ -374,3 +376,96 @@ jobs:
|
|||||||
echo Uploading %%~nxf...
|
echo Uploading %%~nxf...
|
||||||
curl -s -X POST -H "Authorization: token %TOKEN%" -H "Content-Type: application/octet-stream" --data-binary "@%%f" "%GITEA_URL%/api/v1/repos/%REPO%/releases/%RELEASE_ID%/assets?name=%%~nxf"
|
curl -s -X POST -H "Authorization: token %TOKEN%" -H "Content-Type: application/octet-stream" --data-binary "@%%f" "%GITEA_URL%/api/v1/repos/%REPO%/releases/%RELEASE_ID%/assets?name=%%~nxf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sync-to-github:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build-linux, build-macos, build-windows]
|
||||||
|
if: gitea.event_name == 'push'
|
||||||
|
env:
|
||||||
|
GH_PAT: ${{ secrets.GH_PAT }}
|
||||||
|
GITHUB_REPO: shadowdao/triple-c
|
||||||
|
steps:
|
||||||
|
- name: Download artifacts from Gitea releases
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
VERSION: ${{ needs.build-linux.outputs.version }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
mkdir -p artifacts
|
||||||
|
|
||||||
|
# Download assets from all 3 platform releases
|
||||||
|
for TAG_SUFFIX in "" "-mac" "-win"; do
|
||||||
|
TAG="v${VERSION}${TAG_SUFFIX}"
|
||||||
|
echo "==> Fetching assets for release ${TAG}..."
|
||||||
|
|
||||||
|
RELEASE_JSON=$(curl -sf \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${REPO}/releases/tags/${TAG}" 2>/dev/null || echo "{}")
|
||||||
|
|
||||||
|
echo "$RELEASE_JSON" | jq -r '.assets[]? | "\(.name) \(.browser_download_url)"' | while read -r NAME URL; do
|
||||||
|
[ -z "$NAME" ] && continue
|
||||||
|
echo " Downloading ${NAME}..."
|
||||||
|
curl -sfL \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-o "artifacts/${NAME}" \
|
||||||
|
"$URL"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "==> All downloaded artifacts:"
|
||||||
|
ls -la artifacts/
|
||||||
|
|
||||||
|
- name: Create GitHub release and upload artifacts
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.build-linux.outputs.version }}
|
||||||
|
COMMIT_SHA: ${{ gitea.sha }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
TAG="v${VERSION}"
|
||||||
|
|
||||||
|
echo "==> Creating unified release ${TAG} on GitHub..."
|
||||||
|
|
||||||
|
# Delete existing release if present (idempotent re-runs)
|
||||||
|
EXISTING=$(curl -sf \
|
||||||
|
-H "Authorization: Bearer ${GH_PAT}" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG}" 2>/dev/null || echo "{}")
|
||||||
|
EXISTING_ID=$(echo "$EXISTING" | jq -r '.id // empty')
|
||||||
|
if [ -n "$EXISTING_ID" ]; then
|
||||||
|
echo " Deleting existing GitHub release ${TAG} (id: ${EXISTING_ID})..."
|
||||||
|
curl -sf -X DELETE \
|
||||||
|
-H "Authorization: Bearer ${GH_PAT}" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"https://api.github.com/repos/${GITHUB_REPO}/releases/${EXISTING_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RESPONSE=$(curl -sf -X POST \
|
||||||
|
-H "Authorization: Bearer ${GH_PAT}" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"https://api.github.com/repos/${GITHUB_REPO}/releases" \
|
||||||
|
-d "{
|
||||||
|
\"tag_name\": \"${TAG}\",
|
||||||
|
\"name\": \"Triple-C ${TAG}\",
|
||||||
|
\"body\": \"Automated build from commit ${COMMIT_SHA}\n\nIncludes Linux, macOS, and Windows artifacts.\",
|
||||||
|
\"draft\": false,
|
||||||
|
\"prerelease\": false
|
||||||
|
}")
|
||||||
|
|
||||||
|
UPLOAD_URL=$(echo "$RESPONSE" | jq -r '.upload_url' | sed 's/{?name,label}//')
|
||||||
|
echo "==> Upload URL: ${UPLOAD_URL}"
|
||||||
|
|
||||||
|
for file in artifacts/*; do
|
||||||
|
[ -f "$file" ] || continue
|
||||||
|
FILENAME=$(basename "$file")
|
||||||
|
MIME="application/octet-stream"
|
||||||
|
echo "==> Uploading ${FILENAME}..."
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: Bearer ${GH_PAT}" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
-H "Content-Type: ${MIME}" \
|
||||||
|
--data-binary "@${file}" \
|
||||||
|
"${UPLOAD_URL}?name=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "${FILENAME}")"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "==> GitHub release sync complete."
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
name: Sync Release to GitHub
|
name: Sync Release to GitHub
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
workflow_dispatch:
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync-release:
|
sync-release:
|
||||||
|
|||||||
1
app/src-tauri/Cargo.lock
generated
1
app/src-tauri/Cargo.lock
generated
@@ -4681,6 +4681,7 @@ dependencies = [
|
|||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"tar",
|
"tar",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ fern = { version = "0.7", features = ["date-based"] }
|
|||||||
tar = "0.4"
|
tar = "0.4"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||||
iana-time-zone = "0.1"
|
iana-time-zone = "0.1"
|
||||||
|
sha2 = "0.10"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ use bollard::container::{
|
|||||||
use bollard::image::{CommitContainerOptions, RemoveImageOptions};
|
use bollard::image::{CommitContainerOptions, RemoveImageOptions};
|
||||||
use bollard::models::{ContainerSummary, HostConfig, Mount, MountTypeEnum, PortBinding};
|
use bollard::models::{ContainerSummary, HostConfig, Mount, MountTypeEnum, PortBinding};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use sha2::{Sha256, Digest};
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
use super::client::get_docker;
|
use super::client::get_docker;
|
||||||
use crate::models::{AuthMode, BedrockAuthMethod, ContainerInfo, EnvVar, GlobalAwsSettings, McpServer, McpTransportType, PortMapping, Project, ProjectPath};
|
use crate::models::{AuthMode, BedrockAuthMethod, ContainerInfo, EnvVar, GlobalAwsSettings, McpServer, McpTransportType, PortMapping, Project, ProjectPath};
|
||||||
@@ -129,20 +128,28 @@ fn merge_claude_instructions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Hash a string with SHA-256 and return the hex digest.
|
||||||
|
fn sha256_hex(input: &str) -> String {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(input.as_bytes());
|
||||||
|
format!("{:x}", hasher.finalize())
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute a fingerprint for the Bedrock configuration so we can detect changes.
|
/// Compute a fingerprint for the Bedrock configuration so we can detect changes.
|
||||||
fn compute_bedrock_fingerprint(project: &Project) -> String {
|
fn compute_bedrock_fingerprint(project: &Project) -> String {
|
||||||
if let Some(ref bedrock) = project.bedrock_config {
|
if let Some(ref bedrock) = project.bedrock_config {
|
||||||
let mut hasher = DefaultHasher::new();
|
let parts = vec![
|
||||||
format!("{:?}", bedrock.auth_method).hash(&mut hasher);
|
format!("{:?}", bedrock.auth_method),
|
||||||
bedrock.aws_region.hash(&mut hasher);
|
bedrock.aws_region.clone(),
|
||||||
bedrock.aws_access_key_id.hash(&mut hasher);
|
bedrock.aws_access_key_id.as_deref().unwrap_or("").to_string(),
|
||||||
bedrock.aws_secret_access_key.hash(&mut hasher);
|
bedrock.aws_secret_access_key.as_deref().unwrap_or("").to_string(),
|
||||||
bedrock.aws_session_token.hash(&mut hasher);
|
bedrock.aws_session_token.as_deref().unwrap_or("").to_string(),
|
||||||
bedrock.aws_profile.hash(&mut hasher);
|
bedrock.aws_profile.as_deref().unwrap_or("").to_string(),
|
||||||
bedrock.aws_bearer_token.hash(&mut hasher);
|
bedrock.aws_bearer_token.as_deref().unwrap_or("").to_string(),
|
||||||
bedrock.model_id.hash(&mut hasher);
|
bedrock.model_id.as_deref().unwrap_or("").to_string(),
|
||||||
bedrock.disable_prompt_caching.hash(&mut hasher);
|
format!("{}", bedrock.disable_prompt_caching),
|
||||||
format!("{:x}", hasher.finish())
|
];
|
||||||
|
sha256_hex(&parts.join("|"))
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
@@ -157,9 +164,7 @@ fn compute_paths_fingerprint(paths: &[ProjectPath]) -> String {
|
|||||||
.collect();
|
.collect();
|
||||||
parts.sort();
|
parts.sort();
|
||||||
let joined = parts.join(",");
|
let joined = parts.join(",");
|
||||||
let mut hasher = DefaultHasher::new();
|
sha256_hex(&joined)
|
||||||
joined.hash(&mut hasher);
|
|
||||||
format!("{:x}", hasher.finish())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute a fingerprint for port mappings so we can detect changes.
|
/// Compute a fingerprint for port mappings so we can detect changes.
|
||||||
@@ -171,9 +176,7 @@ fn compute_ports_fingerprint(port_mappings: &[PortMapping]) -> String {
|
|||||||
.collect();
|
.collect();
|
||||||
parts.sort();
|
parts.sort();
|
||||||
let joined = parts.join(",");
|
let joined = parts.join(",");
|
||||||
let mut hasher = DefaultHasher::new();
|
sha256_hex(&joined)
|
||||||
joined.hash(&mut hasher);
|
|
||||||
format!("{:x}", hasher.finish())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the JSON value for MCP servers config to be injected into ~/.claude.json.
|
/// Build the JSON value for MCP servers config to be injected into ~/.claude.json.
|
||||||
@@ -250,9 +253,7 @@ fn compute_mcp_fingerprint(servers: &[McpServer]) -> String {
|
|||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
let json = build_mcp_servers_json(servers);
|
let json = build_mcp_servers_json(servers);
|
||||||
let mut hasher = DefaultHasher::new();
|
sha256_hex(&json)
|
||||||
json.hash(&mut hasher);
|
|
||||||
format!("{:x}", hasher.finish())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_existing_container(project: &Project) -> Result<Option<String>, String> {
|
pub async fn find_existing_container(project: &Project) -> Result<Option<String>, String> {
|
||||||
|
|||||||
Reference in New Issue
Block a user