Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,5 +248,6 @@ PRs that modify `crates/tyutool-cli/src/main.rs` (command definitions) without u
### Testing

- Test files live next to their source, same name with `.test.ts` suffix; Rust uses inline `#[cfg(test)] mod tests`
- **Before creating a test file:** run `ls` in the source file's directory to check whether a co-located `.test.ts` already exists. If it does, append to it — never create a parallel file with suffixes like `-extended`, `-v2`, etc.
- Pure logic (utility functions, type conversions) must have unit tests; Vue components and stores as needed
- Frontend tests run in the `node` environment — no DOM
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/tyutool-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tyutool-cli"
version = "3.0.11"
version = "3.0.14"
edition = "2021"
description = "tyutool CLI — shared tyutool-core flash API (no Tauri)"

Expand Down
2 changes: 1 addition & 1 deletion crates/tyutool-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tyutool-core"
version = "3.0.11"
version = "3.0.14"
edition = "2021"
description = "Shared flash/erase plugin registry and serial helpers for tyutool CLI and GUI"

Expand Down
15 changes: 12 additions & 3 deletions crates/tyutool-core/src/authorize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,9 @@ pub enum BatchAuthSlotResult {
/// Device already had the exact credentials — nothing written.
AlreadyDone { mac: String },
/// Auth on device didn't match but conflict_policy=Skip — nothing written.
Skipped { mac: String },
/// `existing_uuid` is the UUID already on the device, so the caller can
/// find and confirm that Excel row.
Skipped { mac: String, existing_uuid: String },
/// Operation was cancelled.
Cancelled,
}
Expand Down Expand Up @@ -1005,6 +1007,7 @@ where
/// The caller pre-allocates `uuid`/`authkey` from an Excel row. On return:
/// - `Done`/`AlreadyDone` → caller should confirm the Excel row (mark USED).
/// - `Skipped`/`Err`/`Cancelled` → caller should release the Excel row.
#[allow(clippy::too_many_arguments)]
pub fn run_batch_auth_slot<F>(
port: &str,
chip_id: &str,
Expand Down Expand Up @@ -1079,7 +1082,10 @@ where
log::info!(
"[batch-auth] skipped port={port} mac={mac} existing_uuid={ex_uuid}"
);
return Ok(BatchAuthSlotResult::Skipped { mac });
return Ok(BatchAuthSlotResult::Skipped {
mac,
existing_uuid: ex_uuid.clone(),
});
}
}
}
Expand Down Expand Up @@ -1175,7 +1181,10 @@ where
}
if conflict_policy == ConflictPolicy::Skip {
log::info!("[batch-auth] skipped (old fw) port={port} mac={mac} existing_uuid={ex_uuid}");
return Ok(BatchAuthSlotResult::Skipped { mac });
return Ok(BatchAuthSlotResult::Skipped {
mac,
existing_uuid: ex_uuid.clone(),
});
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "tyutool",
"private": true,
"version": "3.0.11",
"version": "3.0.14",
"type": "module",
"scripts": {
"clean": "node -e \"try { require('fs').rmSync('dist', { recursive: true, force: true }); } catch (e) { if (e && e.code !== 'ENOENT') throw e; }\"",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tyutool_gui"
version = "3.0.11"
version = "3.0.14"
description = "tyutool — Tauri shell for tyutool-core (firmware / serial tooling)"
authors = ["tyutool contributors"]
edition = "2021"
Expand Down
34 changes: 34 additions & 0 deletions src-tauri/src/batch_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,40 @@ impl ExcelRowAllocator {
}
}

/// Find the row whose UUID matches `uuid` and confirm it as Used (mark MAC +
/// timestamp and persist). If the row is already Used this is a no-op.
/// Returns the row index that was confirmed, or None if uuid was not found.
pub fn find_and_confirm_by_uuid(
&self,
uuid: &str,
mac: String,
) -> Result<Option<usize>, String> {
let row_idx = {
let state = self.state.lock().unwrap();
state
.rows
.iter()
.enumerate()
.find(|(_, r)| r.uuid == uuid)
.map(|(i, _)| i)
};
match row_idx {
None => Ok(None),
Some(idx) => {
// Only confirm rows that haven't been marked Used yet.
let already_used = {
let state = self.state.lock().unwrap();
state.rows[idx].status == RowStatus::Used
};
if already_used {
return Ok(Some(idx));
}
self.confirm_row(idx, mac)?;
Ok(Some(idx))
}
}
}

pub fn confirm_row(&self, row_idx: usize, mac: String) -> Result<(), String> {
let mut state = self.state.lock().unwrap();

Expand Down
23 changes: 17 additions & 6 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,13 +592,24 @@ fn batch_auth_start(
}
let _ = app_clone.emit("batch-auth-progress", payload);
}
Ok(tyutool_core::BatchAuthSlotResult::Skipped { mac }) => {
log::info!("[batch-auth] slot skipped port={port_clone} mac={mac} uuid={uuid} excel_row={row_idx}");
Ok(tyutool_core::BatchAuthSlotResult::Skipped { mac, existing_uuid }) => {
log::info!("[batch-auth] slot skipped port={port_clone} mac={mac} existing_uuid={existing_uuid} new_row={row_idx}");
// Release the newly-allocated row (we won't write a new auth code).
alloc_clone.release_row(row_idx);
let _ = app_clone.emit(
"batch-auth-progress",
serde_json::json!({ "port": port_clone, "step": "skipped", "mac": mac }),
);
// Mark the device's existing auth-code row as Used so the same
// code isn't handed out to another device.
let excel_err = alloc_clone
.find_and_confirm_by_uuid(&existing_uuid, mac.clone())
.err();
if let Some(ref e) = excel_err {
log::error!("[batch-auth] excel-confirm-skipped-failed port={port_clone} existing_uuid={existing_uuid} error={e}");
}
let mut payload =
serde_json::json!({ "port": port_clone, "step": "skipped", "mac": mac });
if let Some(e) = excel_err {
payload["excelError"] = serde_json::Value::String(e);
}
let _ = app_clone.emit("batch-auth-progress", payload);
}
Ok(tyutool_core::BatchAuthSlotResult::Cancelled) => {
log::info!(
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "tyutool",
"version": "3.0.11",
"version": "3.0.14",
"identifier": "com.tyutool.desktop",
"build": {
"beforeDevCommand": "pnpm run dev",
Expand Down
19 changes: 18 additions & 1 deletion src/features/batch-flash-auth/auth-firmware.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { describe, it, expect } from "vitest";
import { filterByChip, AUTH_FIRMWARE_SOURCES } from "./auth-firmware";
import {
filterByChip,
AUTH_FIRMWARE_SOURCES,
downloadAuthFirmware,
} from "./auth-firmware";
import type { AuthFirmwareEntry } from "./types";

const mk = (version: string, chip: string): AuthFirmwareEntry => ({
Expand Down Expand Up @@ -69,3 +73,16 @@ describe("AUTH_FIRMWARE_SOURCES", () => {
}
});
});

describe("downloadAuthFirmware", () => {
it("throws in web mode (isTauriRuntime=false)", async () => {
await expect(
downloadAuthFirmware({
version: "1.1.0",
chip: "t5ai",
url: "https://example.com/fw.bin",
sha256: "abc123",
}),
).rejects.toThrow("download requires desktop runtime");
});
});
Loading