fix(workspace): make pnpm monorepo actually verify clean

After the apps/packages split, several setup gaps surfaced when running
the full lint/typecheck/test/build chain:

Build & install
- .npmrc: switch to npmmirror + raise fetch-timeout (next/swc tarballs
  were timing out against npmjs.org)
- pnpm-workspace.yaml: approve esbuild build script (tsup needs it)
- pnpm-lock.yaml: regenerated for the new workspace layout
- .gitignore: ignore apps/*/.next, apps/*/out, packages/*/dist (the
  root-only globs no longer covered post-split)

Script ordering — playground typecheck/dev/build read types from the
library's dist/, so the lib must build first:
- root package.json: prepend build:lib to dev/typecheck/build:playground
- root package.json: add start/preview scripts for previewing prod build

TypeScript config
- tsconfig.base.json: drop incremental: true (broke tsup's DTS step;
  TS5074 — needs tsBuildInfoFile when emitted from tsup)
- apps/playground/tsconfig.json: re-add incremental locally (Next uses it)

Library typecheck fixes — needed to make tsup's DTS step pass:
- utils.ts, core/binary.ts: drop Uint8Array<ArrayBuffer> generic (5.7+
  syntax; we keep ^5 in deps so the source must compile on older TS too)
- XlsxPreview.tsx: cast bytes.buffer as ArrayBuffer (now ArrayBufferLike
  by default; exceljs's .xlsx.load expects ArrayBuffer)
- icons.tsx: wrap rest-children in Children.toArray() so multi-path
  icons (EyeIcon, Code2Icon, …) get auto-keys — was throwing the
  'unique key prop' warning on every page load
- vitest.setup.ts → src/__tests__/setup.ts + update vitest.config.ts:
  the setup file was outside rootDir 'src', breaking tsc --noEmit;
  moving it under src/__tests__ keeps tsup's entry filter happy

Cleanup
- apps/playground/src/lib/shiki.ts: orphan from the split (no importers;
  the real shiki module lives in packages/file-preview/src/shiki.ts)

After all this: lint / typecheck / test / build:lib / build:playground
all green.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
CoderLambert
2026-06-17 10:39:31 +08:00
co-authored by Claude
parent 4fe769d281
commit 88a96525ad
14 changed files with 9669 additions and 174 deletions
+6
View File
@@ -16,9 +16,15 @@ node_modules
# next.js
/.next/
/out/
apps/*/.next/
apps/*/out/
# production
/build
apps/*/build
# package builds
packages/*/dist/
# misc
.DS_Store
+6
View File
@@ -0,0 +1,6 @@
registry=https://registry.npmmirror.com
fetch-timeout=600000
fetch-retries=5
fetch-retry-mintimeout=20000
fetch-retry-maxtimeout=120000
network-concurrency=4
-164
View File
@@ -1,164 +0,0 @@
/**
* Shiki highlighter — lazy loading via main entry
*
* The `shiki` main entry automatically code-splits each language and theme
* into separate async chunks. Only the ones actually used are loaded.
*
* Loading sequence for first code preview:
* 0KB (React.lazy) → core + JS engine + 1 lang + 2 themes ≈ 49KB gzip
*
* Turbopack/webpack automatically splits each @shikijs/langs/* and
* @shikijs/themes/* into separate chunks.
*/
import { codeToHtml as shikiCodeToHtml } from "shiki";
import type { ShikiTransformer } from "shiki";
// Re-export for convenience
export { shikiCodeToHtml };
/**
* Highlight code with dual theme support (light/dark CSS variables)
*
* This is a wrapper around shiki's codeToHtml that:
* - Uses github-light/github-dark dual themes
* - Outputs CSS variables (defaultColor: false) for zero-cost theme switching
* - Adds line numbers via transformer
*/
export async function highlightCode(
content: string,
language: string
): Promise<string> {
return shikiCodeToHtml(content, {
lang: language,
themes: {
light: "github-light",
dark: "github-dark",
},
defaultColor: false,
transformers: [transformerLineNumbers()],
});
}
/**
* Map file extension → Shiki language ID
*/
export function getShikiLanguage(fileName: string): string {
const ext = fileName.toLowerCase().split(".").pop() || "";
const baseName = fileName.split("/").pop() || "";
const extMap: Record<string, string> = {
// Web
js: "javascript",
mjs: "javascript",
cjs: "javascript",
jsx: "jsx",
ts: "typescript",
tsx: "tsx",
html: "html",
htm: "html",
css: "css",
scss: "scss",
less: "less",
vue: "vue",
svelte: "svelte",
// Scripting
py: "python",
pyw: "python",
rb: "ruby",
php: "php",
pl: "perl",
pm: "perl",
lua: "lua",
r: "r",
// Systems
java: "java",
c: "c",
h: "c",
cpp: "cpp",
cc: "cpp",
cxx: "cpp",
hpp: "cpp",
cs: "csharp",
go: "go",
rs: "rust",
swift: "swift",
kt: "kotlin",
kts: "kotlin",
scala: "scala",
dart: "dart",
// Shell
sh: "bash",
bash: "bash",
zsh: "bash",
ps1: "powershell",
bat: "bat",
cmd: "bat",
// Data / Config
json: "json",
yml: "yaml",
yaml: "yaml",
toml: "toml",
ini: "ini",
cfg: "ini",
conf: "ini",
env: "ini",
sql: "sql",
graphql: "graphql",
gql: "graphql",
xml: "xml",
svg: "xml",
// DevOps
dockerfile: "dockerfile",
makefile: "makefile",
nginx: "nginx",
diff: "diff",
patch: "diff",
// Docs
md: "markdown",
mdx: "mdx",
tex: "latex",
adoc: "asciidoc",
// Functional / Other
ex: "elixir",
exs: "elixir",
clj: "clojure",
cljs: "clojure",
erl: "erlang",
hs: "haskell",
m: "matlab",
vim: "vim",
coffee: "coffeescript",
wasm: "wasm",
objectivec: "objectivec",
objectivecpp: "objectivec",
};
if (extMap[ext]) return extMap[ext];
// Check base filename (case-insensitive)
const lowerBase = baseName.toLowerCase();
if (lowerBase === "dockerfile") return "dockerfile";
if (lowerBase === "makefile" || lowerBase === "gnumakefile") return "makefile";
if (lowerBase === "gemfile") return "ruby";
if (lowerBase === "rakefile") return "ruby";
if (lowerBase === ".gitignore" || lowerBase === ".env") return "ini";
if (lowerBase === ".eslintrc" || lowerBase === ".prettierrc") return "json";
if (lowerBase === "vagrantfile") return "ruby";
return "text";
}
/**
* Transformer: add data-line attribute to each line for CSS line numbers
*/
export function transformerLineNumbers(): ShikiTransformer {
return {
name: "line-numbers",
line(lineProps, line) {
// lineProps is a hast element; properties are in .properties
if (!lineProps.properties) lineProps.properties = {};
lineProps.properties["data-line"] = String(line);
},
};
}
+1
View File
@@ -2,6 +2,7 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./src/*"]
+5 -3
View File
@@ -9,12 +9,14 @@
"pnpm": ">=11.0.0"
},
"scripts": {
"dev": "pnpm --filter @filevista/playground dev",
"dev": "pnpm --filter @filevista/file-preview build && pnpm --filter @filevista/playground dev",
"build": "pnpm --filter @filevista/file-preview build && pnpm --filter @filevista/playground build",
"build:lib": "pnpm --filter @filevista/file-preview build",
"build:playground": "pnpm --filter @filevista/playground build",
"build:playground": "pnpm --filter @filevista/file-preview build && pnpm --filter @filevista/playground build",
"start": "pnpm --filter @filevista/playground start",
"preview": "pnpm run build && pnpm run start",
"lint": "pnpm -r run lint",
"typecheck": "pnpm -r run typecheck",
"typecheck": "pnpm --filter @filevista/file-preview build && pnpm -r run typecheck",
"test": "pnpm --filter @filevista/file-preview test",
"check": "pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run build"
},
+1 -1
View File
@@ -371,7 +371,7 @@ async function parseXlsx(
// exceljs's typings ask for Node's Buffer, but its browser bundle
// accepts any ArrayBufferView at runtime. Pass the underlying ArrayBuffer
// to satisfy both layers without a structured cast.
await workbook.xlsx.load(bytes.buffer);
await workbook.xlsx.load(bytes.buffer as ArrayBuffer);
const sheets: SheetData[] = [];
const isFast = mode === "fast";
+1 -1
View File
@@ -29,7 +29,7 @@ export async function readBinaryPreviewAsArrayBuffer({
export async function readBinaryPreviewAsUint8Array(
input: BinaryPreviewInput
): Promise<Uint8Array<ArrayBuffer>> {
): Promise<Uint8Array> {
const buffer = await readBinaryPreviewAsArrayBuffer(input);
return new Uint8Array(buffer);
}
+3 -1
View File
@@ -5,6 +5,8 @@
* Consumers can override size via the `size` prop; color follows `currentColor`.
*/
import { Children } from "react";
interface IconProps extends React.SVGProps<SVGSVGElement> {
size?: number;
}
@@ -33,7 +35,7 @@ function icon(props: IconProps, ...children: React.ReactNode[]) {
strokeLinejoin={defaults.strokeLinejoin}
{...rest}
>
{children}
{Children.toArray(children)}
</svg>
);
}
+1 -1
View File
@@ -322,7 +322,7 @@ export function generateId(): string {
* Decode base64 string to Uint8Array.
* Shared utility used by PDF, DOCX, DOC, PPTX, XLSX, EPUB, ZIP preview components.
*/
export function base64ToUint8Array(base64: string): Uint8Array<ArrayBuffer> {
export function base64ToUint8Array(base64: string): Uint8Array {
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
+1 -1
View File
@@ -8,7 +8,7 @@ export default defineConfig({
test: {
environment: "node", // individual tests opt in to jsdom via `// @vitest-environment jsdom`
globals: false,
setupFiles: ["./vitest.setup.ts"],
setupFiles: ["./src/__tests__/setup.ts"],
include: [
"src/**/*.test.ts",
"src/**/*.test.tsx",
+9642
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -9,6 +9,7 @@ allowBuilds:
'@swc/core': true
canvas: false
es5-ext: true
esbuild: true
prisma: false
sharp: true
unrs-resolver: true
+1 -2
View File
@@ -11,7 +11,6 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true
"jsx": "react-jsx"
}
}