feat(dev-variables): 项目开发变量与测试账户补给(V1–V11) #23
@ -24,6 +24,7 @@ import {
|
||||
gitPush,
|
||||
type GiteaInstance,
|
||||
} from "../../lib/commands";
|
||||
import { confirm } from "@tauri-apps/plugin-dialog";
|
||||
import { useUIStore } from "../../store/ui";
|
||||
|
||||
type BindMode = "create" | "migrate" | "link";
|
||||
@ -98,7 +99,7 @@ export function GiteaInstancesSection() {
|
||||
};
|
||||
|
||||
const handleDelete = async (inst: GiteaInstance) => {
|
||||
if (!confirm(`删除实例「${inst.name}」?其下所有项目绑定会一并删除。`)) return;
|
||||
if (!(await confirm(`删除实例「${inst.name}」?其下所有项目绑定会一并删除。`, { kind: "warning" }))) return;
|
||||
try {
|
||||
await giteaDeleteInstance(inst.id);
|
||||
qc.invalidateQueries({ queryKey: ["gitea-instances"] });
|
||||
@ -308,7 +309,7 @@ export function GiteaPanel({ projectId, projectName, projectPath }: Props) {
|
||||
};
|
||||
|
||||
const handleUnlink = async () => {
|
||||
if (!confirm("解绑当前项目与 Gitea 仓库?(Gitea 侧仓库不会删除)")) return;
|
||||
if (!(await confirm("解绑当前项目与 Gitea 仓库?(Gitea 侧仓库不会删除)", { kind: "warning" }))) return;
|
||||
try {
|
||||
await giteaUnlinkRepo(projectId);
|
||||
refresh();
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
saveProjectTestAccount,
|
||||
revokeProjectDevVariable,
|
||||
} from "../../lib/commands";
|
||||
import { confirm } from "@tauri-apps/plugin-dialog";
|
||||
import { useUIStore } from "../../store/ui";
|
||||
import { Dialog } from "../ui/Dialog";
|
||||
|
||||
@ -142,8 +143,8 @@ export function ProjectDevVariablesPanel({ projectId, projectName, onClose }: Pr
|
||||
{item.configured && (
|
||||
<button
|
||||
disabled={revokeMutation.isPending}
|
||||
onClick={() => {
|
||||
if (window.confirm(`撤销 ${item.name}?Agent 将无法再读取该值。`)) {
|
||||
onClick={async () => {
|
||||
if (await confirm(`撤销 ${item.name}?Agent 将无法再读取该值。`, { kind: "warning" })) {
|
||||
revokeMutation.mutate(item.name);
|
||||
}
|
||||
}}
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
type RuntimeToolStatus,
|
||||
type ToolchainStatus,
|
||||
} from "../../lib/commands";
|
||||
import { confirm } from "@tauri-apps/plugin-dialog";
|
||||
import { useUIStore } from "../../store/ui";
|
||||
|
||||
// ── 层元信息 ───────────────────────────────────────────────────────────────────
|
||||
@ -113,8 +114,9 @@ export function ProjectEnvModal({ project, onClose }: Props) {
|
||||
};
|
||||
|
||||
const handlePrepareToolchain = async () => {
|
||||
const confirmed = window.confirm(
|
||||
const confirmed = await confirm(
|
||||
"炼境将通过 mise install 下载并准备项目已声明的 Node/pnpm 版本。不会修改 package.json 或 lockfile,是否继续?",
|
||||
{ kind: "warning" },
|
||||
);
|
||||
if (!confirmed) return;
|
||||
setPreparing(true);
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
type GitRepo,
|
||||
type UpdateGitRepoDeployInput,
|
||||
} from "../../lib/commands";
|
||||
import { confirm } from "@tauri-apps/plugin-dialog";
|
||||
import { useUIStore } from "../../store/ui";
|
||||
|
||||
// ── 部署信息编辑表单 ──────────────────────────────────────────────────────────
|
||||
@ -127,7 +128,7 @@ export function RegistryTab() {
|
||||
});
|
||||
|
||||
const handleDelete = async (id: string, name: string) => {
|
||||
if (!confirm(`确定删除仓库「${name}」?关联的工作区也会被删除。`)) return;
|
||||
if (!(await confirm(`确定删除仓库「${name}」?关联的工作区也会被删除。`, { kind: "warning" }))) return;
|
||||
try {
|
||||
await deleteGitRepo(id);
|
||||
qc.invalidateQueries({ queryKey: ["git-repos"] });
|
||||
|
||||
26
src/lib/confirmUsage.test.ts
Normal file
26
src/lib/confirmUsage.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
// tauri-plugin-dialog 把 window.confirm 替换成 async 函数:同步写法拿到的是永远为真的 Promise,
|
||||
// 确认框形同虚设(2026-09-30 撤销/删除未弹框即执行)。破坏性操作必须 await plugin-dialog 的 confirm。
|
||||
const sources = import.meta.glob<string>(["../**/*.{ts,tsx}", "!../**/*.test.ts"], {
|
||||
query: "?raw",
|
||||
import: "default",
|
||||
eager: true,
|
||||
});
|
||||
|
||||
describe("confirm usage", () => {
|
||||
it("scans the frontend sources", () => {
|
||||
expect(Object.keys(sources).length).toBeGreaterThan(10);
|
||||
});
|
||||
|
||||
it("never calls confirm without await", () => {
|
||||
const offenders = Object.entries(sources).flatMap(([file, text]) =>
|
||||
text
|
||||
.split("\n")
|
||||
.map((line, index) => ({ line, at: `${file}:${index + 1}` }))
|
||||
.filter(({ line }) => /(^|[^\w.])(window\.)?confirm\(/.test(line) && !/await\s+(window\.)?confirm\(/.test(line))
|
||||
.map(({ at }) => at),
|
||||
);
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user