Add CYD browser flasher with Next.js, i18n, and Web Serial install.

Replace the starter app with a public/local flasher that discovers sibling firmwares, supports en/zh, and documents HTTPS deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-19 19:38:53 +08:00
parent 0bf10091db
commit ae63378fdb
39 changed files with 2889 additions and 196 deletions

View File

@@ -0,0 +1,37 @@
import fs from "fs";
import path from "path";
import { NextResponse } from "next/server";
import { REQUIRED_BINS, resolveFirmwareDir } from "@/lib/firmware";
export const dynamic = "force-dynamic";
type Params = {
params: Promise<{ project: string; variant: string; file: string }>;
};
export async function GET(_req: Request, { params }: Params) {
const { project, variant, file } = await params;
if (!(REQUIRED_BINS as readonly string[]).includes(file)) {
return NextResponse.json({ error: "invalid file" }, { status: 400 });
}
const dir = resolveFirmwareDir(project, variant);
if (!dir) {
return NextResponse.json({ error: "not found" }, { status: 404 });
}
const abs = path.join(dir, file);
if (!fs.existsSync(abs)) {
return NextResponse.json({ error: "not found" }, { status: 404 });
}
const buf = fs.readFileSync(abs);
return new NextResponse(buf, {
headers: {
"Content-Type": "application/octet-stream",
"Content-Length": String(buf.length),
"Cache-Control": "no-store",
"Content-Disposition": `attachment; filename="${file}"`,
},
});
}

View File

@@ -0,0 +1,39 @@
import { NextResponse } from "next/server";
import {
FLASH_PARTS,
readFirmwareMeta,
resolveFirmwareDir,
} from "@/lib/firmware";
export const dynamic = "force-dynamic";
type Params = { params: Promise<{ project: string; variant: string }> };
export async function GET(_req: Request, { params }: Params) {
const { project, variant } = await params;
const dir = resolveFirmwareDir(project, variant);
if (!dir) {
return NextResponse.json({ error: "firmware not found" }, { status: 404 });
}
const meta = readFirmwareMeta(dir);
const name = meta.name || `${project} (${variant})`;
const version =
[meta.date, meta.git].filter(Boolean).join(" / ") || "local";
const base = `/api/firmware/${encodeURIComponent(project)}/${encodeURIComponent(variant)}`;
return NextResponse.json({
name,
version,
new_install_prompt_erase: true,
builds: [
{
chipFamily: "ESP32",
parts: FLASH_PARTS.map((p) => ({
path: `${base}/${p.file}`,
offset: p.offset,
})),
},
],
});
}

13
app/api/firmware/route.ts Normal file
View File

@@ -0,0 +1,13 @@
import { NextResponse } from "next/server";
import { discoverFirmware } from "@/lib/firmware";
export const dynamic = "force-dynamic";
export async function GET() {
const firmwares = discoverFirmware();
return NextResponse.json({
chipFamily: "ESP32",
count: firmwares.length,
firmwares,
});
}