Files
coco-app/scripts/buildWeb.ts
ayangweb 4df541f7ce fix: resolve fst-no-std version conflict causing pizza-engine CI check failure (#1074)
* feat: add deep research report workflow and viewer

* docs: update  changelog

* ci: update pizza repository reference from infinilabs to pizza-rs org

* fix deep research completion state

* fix: prevent deep research stream timeout

* fix: update deep research cancel copy

* fix: apply rustfmt formatting to attachment.rs

* fix: resolve fst-no-std version conflict in pizza-engine CI check

The 'check (macos-latest)' CI job fails because pizza-engine (added as a
CI dependency) uses infinilabs/fst-no-std while its transitive dependency
fst-regex uses pizza-rs/fst-no-std. Cargo treats these as two separate
crates, causing:

1. Automaton trait incompatibility between the two fst_no_std instances
2. MapBuilder, Error::Io, and Set::from_iter not found because the
   infinilabs fork gates them behind 'std' feature while pizza-engine
   only enabled 'alloc'

Fix:
- Add [patch."https://github.com/infinilabs/fst-no-std"] to
  src-tauri/Cargo.toml to redirect the infinilabs fork to
  pizza-rs/fst-no-std, unifying both into a single crate instance.
  This patch is a no-op for normal builds without pizza-engine.
- Update the CI workflow to also add fst-no-std with std feature so that
  Error::Io (gated by std) and MapBuilder are available after the redirect.

* Update Cargo.toml

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Medcl <m@medcl.net>
2026-06-18 17:11:51 +08:00

156 lines
4.4 KiB
TypeScript

/// <reference types="node" />
import { spawn } from "child_process";
import { readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = join(__dirname, "..");
const adapterPath = join(projectRoot, "src/utils/platformAdapter.ts");
type AdapterMode = "tauri" | "web";
function stripLineComments(content: string) {
return content
.split(/\r?\n/)
.filter((line) => !line.trimStart().startsWith("//"))
.join("\n");
}
function detectAdapterMode(content: string): AdapterMode {
const activeContent = stripLineComments(content);
const usesTauriAdapter =
/import\s*\{\s*createTauriAdapter\s*\}\s*from\s*["']\.\/tauriAdapter["']/.test(
activeContent
) && /platformAdapter\s*=\s*createTauriAdapter\s*\(/.test(activeContent);
const usesWebAdapter =
/import\s*\{\s*createWebAdapter\s*\}\s*from\s*["']\.\/webAdapter["']/.test(
activeContent
) && /platformAdapter\s*=\s*createWebAdapter\s*\(/.test(activeContent);
if (usesTauriAdapter && !usesWebAdapter) {
return "tauri";
}
if (usesWebAdapter && !usesTauriAdapter) {
return "web";
}
throw new Error(
`Unable to detect active platform adapter in ${adapterPath}. ` +
`Expected either createTauriAdapter() or createWebAdapter() to be active.`
);
}
function setLineComment(line: string, commented: boolean) {
const indent = line.match(/^\s*/)?.[0] ?? "";
const content = line.slice(indent.length);
const uncommented = content.replace(/^\/\/\s?/, "");
return commented ? `${indent}// ${uncommented}` : `${indent}${uncommented}`;
}
function switchToWebAdapter(content: string) {
let hasTauriImport = false;
let hasTauriInit = false;
let hasWebImport = false;
let hasWebInit = false;
const nextContent = content
.split(/\r?\n/)
.map((line) => {
const normalized = line.trim().replace(/^\/\/\s?/, "");
if (
normalized === 'import { createTauriAdapter } from "./tauriAdapter";'
) {
hasTauriImport = true;
return setLineComment(line, true);
}
if (normalized === "let platformAdapter = createTauriAdapter();") {
hasTauriInit = true;
return setLineComment(line, true);
}
if (normalized === 'import { createWebAdapter } from "./webAdapter";') {
hasWebImport = true;
return setLineComment(line, false);
}
if (normalized === "let platformAdapter = createWebAdapter();") {
hasWebInit = true;
return setLineComment(line, false);
}
return line;
})
.join("\n");
if (!hasTauriImport || !hasTauriInit || !hasWebImport || !hasWebInit) {
throw new Error(
`Unable to switch ${adapterPath} to Web mode. ` +
`Expected both Tauri and Web adapter import/init lines to exist.`
);
}
detectAdapterMode(nextContent);
return nextContent;
}
function run(command: string, args: string[]) {
return new Promise<void>((resolve, reject) => {
const child = spawn(command, args, {
cwd: projectRoot,
env: {
...process.env,
BUILD_TARGET: "web",
},
shell: process.platform === "win32",
stdio: "inherit",
});
child.on("error", reject);
child.on("exit", (code) => {
if (code === 0) {
resolve();
return;
}
reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`));
});
});
}
async function main() {
const originalContent = readFileSync(adapterPath, "utf-8");
const originalMode = detectAdapterMode(originalContent);
const shouldRestore = originalMode !== "web";
if (shouldRestore) {
console.log("[build:web] Switching platform adapter to Web for packaging.");
writeFileSync(adapterPath, switchToWebAdapter(originalContent), "utf-8");
} else {
console.log("[build:web] Platform adapter is already Web; leaving it as-is.");
}
try {
await run("pnpm", ["exec", "tsc"]);
await run("pnpm", ["exec", "tsup", "--format", "esm"]);
} finally {
if (shouldRestore) {
writeFileSync(adapterPath, originalContent, "utf-8");
console.log("[build:web] Restored original platform adapter.");
} else {
console.log("[build:web] Kept platform adapter in original Web mode.");
}
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});