refactor: convert to bun-workspaces monorepo (desktop/ + www/)

- Move the Tauri app (React frontend + Rust backend) into desktop/ via
  git mv — configs unchanged (relative paths: tauri.conf.json, vite.config.ts)
- Root package.json becomes a private bun workspace container with
  orchestration scripts; desktop package renamed gridline-desktop
- Scaffold www/ with Astro 7 + Tailwind v4 (hand-wired vite plugin,
  static output, ready for Dokploy)
- Update release.yml for the new layout: projectPath: desktop,
  desktop/src-tauri resource paths, rust-cache workspace path
- Update README + AGENTS.md structure trees and dev commands
- Verified: vitest (1074), cargo test (354), desktop build, tauri dev
  (window launches), tauri build (dmg + app bundles), Astro build
This commit is contained in:
2026-08-16 19:31:53 +08:00
parent d752642b4d
commit 6854d889c5
400 changed files with 857 additions and 149 deletions
+63
View File
@@ -0,0 +1,63 @@
import { zipSync, strToU8 } from "fflate";
import type { ColumnInfo } from "./types";
/** Minimal OOXML spreadsheet: one sheet, every cell as an inline string
* (t="inlineStr") so Excel never evaluates a cell as a formula — the
* formula-injection mitigation required by the spec. */
const XML_ESCAPES: [RegExp, string][] = [
[/&/g, "&"],
[/</g, "&lt;"],
[/>/g, "&gt;"],
[/"/g, "&quot;"],
];
function esc(s: string): string {
for (const [re, w] of XML_ESCAPES) s = s.replace(re, w);
return s;
}
function colLetter(n: number): string {
let s = "";
for (let i = n; i > 0; i = Math.floor((i - 1) / 26)) s = String.fromCharCode(65 + ((i - 1) % 26)) + s;
return s;
}
function buildColsXML(columns: ColumnInfo[]): string {
const maxW = columns.map((c) => Math.min(60, Math.max(8, c.name.length + 2)));
return `<cols>${maxW.map((w, i) => `\n <col min="${i + 1}" max="${i + 1}" width="${w}" customWidth="1"/>`).join("")}\n</cols>`;
}
function buildCellsXML(rows: unknown[][], columns: ColumnInfo[]): string {
let cells = "";
cells += `<row r="1">` + columns
.map((c, i) => `<c r="${colLetter(i + 1)}1" t="inlineStr"><is><t>${esc(c.name)}</t></is></c>`)
.join("") + `</row>`;
rows.forEach((row, rIdx) => {
const r = rIdx + 2;
cells += `<row r="${r}">` + row
.map((v, i) => {
const ref = `${colLetter(i + 1)}${r}`;
const t = v === null || v === undefined ? "" : String(v);
return `<c r="${ref}" t="inlineStr"><is><t>${esc(t)}</t></is></c>`;
})
.join("") + `</row>`;
});
return cells;
}
export function buildXlsx(rows: unknown[][], columns: ColumnInfo[]): Uint8Array {
const colsXML = buildColsXML(columns);
const cells = buildCellsXML(rows, columns);
const sheet1 = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">${colsXML}<sheetData>${cells}</sheetData></worksheet>`;
const workbook = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="Sheet1" sheetId="1" r:id="rId1"/></sheets></workbook>`;
const ct = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>`;
const rels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/></Relationships>`;
const rootRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`;
return zipSync({
"[Content_Types].xml": strToU8(ct),
"_rels/.rels": strToU8(rootRels),
"xl/workbook.xml": strToU8(workbook),
"xl/_rels/workbook.xml.rels": strToU8(rels),
"xl/worksheets/sheet1.xml": strToU8(sheet1),
});
}