fix: stabilize automation script import

This commit is contained in:
ant-black
2026-06-23 15:56:49 +08:00
parent 65232db7ec
commit 50996445cd
5 changed files with 84 additions and 80 deletions
+6 -4
View File
@@ -17,8 +17,8 @@ func TestAutomationScriptListSeedsDefaultScriptsOnFreshApp(t *testing.T) {
if err != nil {
t.Fatalf("AutomationScriptList returned error: %v", err)
}
if len(items) != 3 {
t.Fatalf("expected three default scripts, got %d", len(items))
if len(items) != 4 {
t.Fatalf("expected four default scripts, got %d", len(items))
}
byID := make(map[string]automation.ScriptRecord, len(items))
@@ -29,6 +29,7 @@ func TestAutomationScriptListSeedsDefaultScriptsOnFreshApp(t *testing.T) {
expectedNames := map[string]string{
"dual-instance-runtime-switch": "双实例启动与 Runtime 切换",
"news-query-txt": "查询新闻并写 TXT",
"proton-mail-first-message": "Proton 邮件搜索并读取最新邮件",
"web-image-generate-download": "网页图片生成并下载",
}
@@ -105,13 +106,14 @@ func TestAutomationScriptListAddsMissingBuiltinWhenLegacyMarkerExists(t *testing
if err != nil {
t.Fatalf("AutomationScriptList returned error: %v", err)
}
if len(items) != 4 {
t.Fatalf("expected custom script plus three defaults, got %d items", len(items))
if len(items) != 5 {
t.Fatalf("expected custom script plus four defaults, got %d items", len(items))
}
expectedDefaultIDs := []string{
automation.DualInstanceRuntimeScriptID,
automation.NewsQueryTXTScriptID,
automation.ProtonMailFirstMessageID,
automation.WebImageGenerateScriptID,
}
for _, scriptID := range expectedDefaultIDs {
+10 -7
View File
@@ -74,10 +74,17 @@ func (a *App) ensureAutomationScriptDefaults(store *automation.ScriptStore) erro
return a.markAutomationScriptDefaultsInitialized()
}
importedCount := 0
var lastImportErr error
for _, bundle := range defaults {
if _, err := store.ImportBundle(bundle); err != nil {
return err
lastImportErr = err
continue
}
importedCount++
}
if importedCount == 0 && lastImportErr != nil {
return lastImportErr
}
return a.markAutomationScriptDefaultsInitialized()
}
@@ -92,15 +99,11 @@ func (a *App) ensureAutomationScriptDefaults(store *automation.ScriptStore) erro
if existing, exists := existingByID[bundle.Record.ID]; exists {
if existing.Source.Type == "builtin" {
bundle.Record = mergeBuiltinDefaultScriptForMigration(existing, bundle.Record)
if _, err := store.ImportBundle(bundle); err != nil {
return err
}
_, _ = store.ImportBundle(bundle)
}
continue
}
if _, err := store.ImportBundle(bundle); err != nil {
return err
}
_, _ = store.ImportBundle(bundle)
}
}
return a.markAutomationScriptDefaultsInitialized()
@@ -12,7 +12,6 @@ import type { BrowserProfile } from "../types";
export type ImportMode =
| "local"
| "git";
export type LocalImportKind = "file" | "directory";
export const DUAL_INSTANCE_SCRIPT_ID = "dual-instance-runtime-switch";
export const NEWS_SCRIPT_ID = "news-query-txt";
@@ -34,7 +34,6 @@ import {
resolveDualLaunchCodes,
type AutomationCardPresentation,
type ImportMode,
type LocalImportKind,
} from "./AutomationPage.helpers";
export function AutomationPage() {
const navigate = useNavigate();
@@ -55,7 +54,6 @@ export function AutomationPage() {
useState<AutomationScriptType>("playwright-cdp");
const [createName, setCreateName] = useState("");
const [importMode, setImportMode] = useState<ImportMode>("local");
const [localImportKind, setLocalImportKind] = useState<LocalImportKind>("file");
const [gitURL, setGitURL] = useState("");
const [gitRef, setGitRef] = useState("");
const [gitScriptPath, setGitScriptPath] = useState("");
@@ -200,7 +198,6 @@ export function AutomationPage() {
const resetImportModal = () => {
setImportMode("local");
setLocalImportKind("file");
setGitURL("");
setGitRef("");
setGitScriptPath("");
@@ -239,44 +236,52 @@ export function AutomationPage() {
}
};
const finishImport = (imported: AutomationScriptRecord[], failedCount = 0) => {
if (imported.length === 0) {
throw new Error("未导入任何脚本");
}
setScripts((current) => mergeImportedScripts(current, imported));
setImportOpen(false);
resetImportModal();
if (imported.length === 1 && failedCount === 0) {
toast.success("脚本已导入");
openScript(imported[0].id);
return;
}
toast.success(`已导入 ${imported.length} 个脚本`);
if (failedCount > 0) {
toast.warning(`${failedCount} 个脚本包导入失败`);
}
};
const handleLocalImport = async (kind: "file" | "directory") => {
setBusyAction("import");
try {
const imported = kind === "directory"
? await importAutomationScriptFromLocalDirectory()
: await importAutomationScriptFromLocalFile();
finishImport([imported]);
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "脚本导入失败";
toast.error(message);
} finally {
setBusyAction("none");
}
};
const handleImport = async () => {
setBusyAction("import");
try {
let imported: AutomationScriptRecord[] = [];
let failedCount = 0;
switch (importMode) {
case "local":
imported = [
localImportKind === "directory"
? await importAutomationScriptFromLocalDirectory()
: await importAutomationScriptFromLocalFile(),
];
break;
case "git":
imported = [
finishImport([
await importAutomationScriptFromGit(gitURL, gitRef, gitScriptPath),
];
]);
break;
default:
throw new Error("不支持的导入方式");
}
if (imported.length === 0) {
throw new Error("未导入任何脚本");
}
setScripts((current) => mergeImportedScripts(current, imported));
setImportOpen(false);
resetImportModal();
if (imported.length === 1 && failedCount === 0) {
toast.success("脚本已导入");
openScript(imported[0].id);
} else {
toast.success(`已导入 ${imported.length} 个脚本`);
if (failedCount > 0) {
toast.warning(`${failedCount} 个脚本包导入失败`);
}
throw new Error("请选择要导入的文件或文件夹");
}
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "脚本导入失败";
@@ -391,14 +396,14 @@ export function AutomationPage() {
open={importOpen}
busyAction={modalBusyAction}
importMode={importMode}
localImportKind={localImportKind}
gitURL={gitURL}
gitRef={gitRef}
gitScriptPath={gitScriptPath}
onClose={closeImportModal}
onImport={handleImport}
onImportLocalFile={() => handleLocalImport("file")}
onImportLocalDirectory={() => handleLocalImport("directory")}
onImportModeChange={setImportMode}
onLocalImportKindChange={setLocalImportKind}
onGitURLChange={setGitURL}
onGitRefChange={setGitRef}
onGitScriptPathChange={setGitScriptPath}
@@ -1,6 +1,6 @@
import { Button, FormItem, Input, Modal, Select } from "../../../shared/components";
import { AUTOMATION_SCRIPT_TYPE_OPTIONS, type AutomationScriptType } from "../automationScripts";
import type { ImportMode, LocalImportKind } from "./AutomationPage.helpers";
import type { ImportMode } from "./AutomationPage.helpers";
interface CreateAutomationScriptModalProps {
open: boolean;
@@ -73,14 +73,14 @@ interface ImportAutomationScriptModalProps {
open: boolean;
busyAction: "none" | "create" | "import";
importMode: ImportMode;
localImportKind: LocalImportKind;
gitURL: string;
gitRef: string;
gitScriptPath: string;
onClose: () => void;
onImport: () => Promise<void>;
onImportLocalFile: () => Promise<void>;
onImportLocalDirectory: () => Promise<void>;
onImportModeChange: (value: ImportMode) => void;
onLocalImportKindChange: (value: LocalImportKind) => void;
onGitURLChange: (value: string) => void;
onGitRefChange: (value: string) => void;
onGitScriptPathChange: (value: string) => void;
@@ -90,14 +90,14 @@ export function ImportAutomationScriptModal({
open,
busyAction,
importMode,
localImportKind,
gitURL,
gitRef,
gitScriptPath,
onClose,
onImport,
onImportLocalFile,
onImportLocalDirectory,
onImportModeChange,
onLocalImportKindChange,
onGitURLChange,
onGitRefChange,
onGitScriptPathChange,
@@ -117,12 +117,14 @@ export function ImportAutomationScriptModal({
>
</Button>
<Button
onClick={() => void onImport()}
loading={busyAction === "import"}
>
</Button>
{importMode === "git" ? (
<Button
onClick={() => void onImport()}
loading={busyAction === "import"}
>
</Button>
) : null}
</>
}
>
@@ -151,28 +153,21 @@ export function ImportAutomationScriptModal({
</div>
{importMode === "local" ? (
<div className="space-y-3">
<div className="flex flex-wrap gap-2">
{[
{ value: "file", label: "ZIP / 文件" },
{ value: "directory", label: "文件夹" },
].map((item) => (
<Button
key={item.value}
size="sm"
variant={localImportKind === item.value ? "primary" : "secondary"}
onClick={() => onLocalImportKindChange(item.value as LocalImportKind)}
disabled={busyAction !== "none"}
>
{item.label}
</Button>
))}
</div>
<div className="rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-secondary)] px-4 py-4 text-sm text-[var(--color-text-secondary)]">
{localImportKind === "directory"
? "点击导入后选择脚本文件夹,适合一整套本地脚本目录。"
: "点击导入后选择本地文件,支持标准 ZIP 脚本包、JSON 模板和单文件脚本。"}
</div>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
<Button
variant="secondary"
onClick={() => void onImportLocalFile()}
loading={busyAction === "import"}
>
ZIP /
</Button>
<Button
variant="secondary"
onClick={() => void onImportLocalDirectory()}
loading={busyAction === "import"}
>
</Button>
</div>
) : null}