Fix GitHub tree deletion and migrate to React + MUI with Vite build#15
Fix GitHub tree deletion and migrate to React + MUI with Vite build#15
Conversation
Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
file-share | 9f69ffa | Commit Preview URL Branch Preview URL |
Feb 20 2026, 06:17 AM |
Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
…ty and validation Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
Co-authored-by: langningchen <81672884+langningchen@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes GitHub-backed file deletion in the Worker and replaces the legacy static Bootstrap UI with a React + MUI frontend built via Vite and served as Worker static assets.
Changes:
- Reworked
/deleteto rebuild a GitHub tree from a recursive listing rather than usingsha: null. - Added a React + MUI frontend under
frontend/, with Vite building output topublic/. - Updated Wrangler assets configuration and npm scripts to build before deploy.
Reviewed changes
Copilot reviewed 10 out of 12 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
wrangler.toml |
Switches to [assets] binding (ASSETS) and updates compatibility date. |
src/index.ts |
Serves static assets on GET and rewrites GitHub tree deletion logic. |
vite.config.js |
Adds Vite build config targeting public/ and sets frontend as root. |
package.json |
Adds React/MUI/Vite deps and build/dev/preview/deploy scripts. |
pnpm-lock.yaml |
Locks newly added frontend/build dependencies. |
frontend/index.html |
Vite HTML entry template for the React app. |
frontend/src/main.jsx |
React entry wiring MUI theme + dark mode. |
frontend/src/App.jsx |
New MUI-based UI implementing upload/list/download/delete flows. |
public/index.html |
Replaces old static page with a built (hashed) Vite output HTML. |
README.md |
Updates docs for the new build/deploy workflow and features. |
IMPROVEMENTS.md |
Adds a Chinese/English summary of the migration and deletion fix. |
.gitignore |
Ignores build outputs (public/, dist) and backups. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export default defineConfig({ | ||
| plugins: [react()], | ||
| build: { | ||
| outDir: path.resolve(__dirname, 'public'), |
There was a problem hiding this comment.
outDir: path.resolve(__dirname, ...) relies on __dirname, which is not available in native ESM. Since this config file uses ESM import syntax, builds can break depending on how the config is loaded. Consider resolving from process.cwd() / import.meta.url, or using a relative outDir like ../public (given root: 'frontend').
| outDir: path.resolve(__dirname, 'public'), | |
| outDir: '../public', |
| "@cloudflare/workers-types": "^4.20260219.0", | ||
| "@types/node": "^25.3.0", | ||
| "@vitejs/plugin-react": "^5.1.4", | ||
| "typescript": "^5.9.3", | ||
| "vite": "^7.3.1", |
There was a problem hiding this comment.
Vite 7 / @vitejs/plugin-react require Node ^20.19.0 || >=22.12.0 (per their engines). Without declaring this in package.json (or README), installs/builds on older Node versions will fail unexpectedly. Consider adding an explicit engines.node constraint.
| const { done, value } = await reader.read(); | ||
| if (done) break; | ||
| readLength += value.length; | ||
| setDownloadProgress(((chunk + readLength / length) / file.chunks) * 100); | ||
| fileData = new Blob([fileData, value]); |
There was a problem hiding this comment.
Creating the download blob via fileData = new Blob([fileData, value]) inside the read loop is O(n²) in time/memory for large files. Accumulate chunks into an array (or stream to disk) and construct a single Blob once at the end.
| if (!env.GithubPAT || !env.GithubOwner || !env.GithubRepo || !env.GithubBranch) { | ||
| return new ResJson(false, 'Please set the environment variables', {}); | ||
| } | ||
| if (req.method === 'GET') { return env.ASSETS.fetch(req); } | ||
| if (req.method !== 'POST') { return new ResJson(false, 'Method not allowed', {}); } |
There was a problem hiding this comment.
The GET static asset handling is placed after the GitHub env-var check. As a result, a missing GithubPAT/Owner/Repo/Branch will prevent serving the UI (and any other GET asset) even though those secrets are only needed for API POST routes. Consider handling GET requests (or at least / and /assets/*) before validating GitHub env vars.
| if (!env.GithubPAT || !env.GithubOwner || !env.GithubRepo || !env.GithubBranch) { | |
| return new ResJson(false, 'Please set the environment variables', {}); | |
| } | |
| if (req.method === 'GET') { return env.ASSETS.fetch(req); } | |
| if (req.method !== 'POST') { return new ResJson(false, 'Method not allowed', {}); } | |
| if (req.method === 'GET') { return env.ASSETS.fetch(req); } | |
| if (req.method !== 'POST') { return new ResJson(false, 'Method not allowed', {}); } | |
| if (!env.GithubPAT || !env.GithubOwner || !env.GithubRepo || !env.GithubBranch) { | |
| return new ResJson(false, 'Please set the environment variables', {}); | |
| } |
| if (req.method === 'GET') { return env.ASSETS.fetch(req); } | ||
| if (req.method !== 'POST') { return new ResJson(false, 'Method not allowed', {}); } |
There was a problem hiding this comment.
env.ASSETS is used here, but the Env interface in this file does not declare an ASSETS binding. This causes a TypeScript type error and makes the binding contract unclear. Add the ASSETS binding to Env (e.g., Fetcher) to match wrangler.toml.
| } | ||
| return json.Data; | ||
| } | ||
| return { reader: res.body.getReader(), length: parseInt(res.headers.get('Content-Length')) }; |
There was a problem hiding this comment.
Content-Length may be missing for streamed responses, so parseInt(res.headers.get('Content-Length')) can yield NaN. Downstream progress calculations then become NaN as well. Add a fallback when the header is absent/invalid (e.g., only use received-bytes progress).
| return { reader: res.body.getReader(), length: parseInt(res.headers.get('Content-Length')) }; | |
| const contentLengthHeader = res.headers.get('Content-Length'); | |
| const parsedLength = contentLengthHeader != null ? parseInt(contentLengthHeader, 10) : null; | |
| const length = Number.isFinite(parsedLength) ? parsedLength : null; | |
| return { reader: res.body.getReader(), length }; |
| const oldTree = (await github.git.getTree({ owner, repo, tree_sha: folderSha, })).data.tree; | ||
| const newTree = oldTree.map(({ path, mode, type }) => ({ path: `${fileId}/${path}`, sha: null, mode, type })); | ||
| const newTreeSha = (await github.git.createTree({ owner, repo, base_tree: treeSha, tree: newTree as any, })).data.sha; | ||
| const fullTree = (await github.git.getTree({ owner, repo, tree_sha: treeSha, recursive: true })).data.tree; |
There was a problem hiding this comment.
git.getTree(..., recursive: true) responses can be truncated. Since you build a brand-new tree (no base_tree) from the returned entries, a truncated response would silently drop unrelated files from the repo in the new commit. Check fullTreeResponse.data.truncated and fail/handle pagination (or use a safer strategy) before calling createTree.
| const fullTree = (await github.git.getTree({ owner, repo, tree_sha: treeSha, recursive: true })).data.tree; | |
| const fullTreeResponse = await github.git.getTree({ owner, repo, tree_sha: treeSha, recursive: true }); | |
| if (fullTreeResponse.data.truncated) { | |
| return new ResJson(false, 'Repository tree is too large to process delete operation safely', {}); | |
| } | |
| const fullTree = fullTreeResponse.data.tree; |
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <title>File Share</title> | ||
| <script type="module" crossorigin src="/assets/index-CvnL0yOX.js"></script> |
There was a problem hiding this comment.
This public/index.html looks like a Vite build output (hashed /assets/index-*.js). Committing it will likely become stale on the next build and may reference an asset file that isn’t present in the repo (especially since public/ is gitignored). Prefer not tracking build outputs in git; let vite build generate public/index.html during deploy.
| <script type="module" crossorigin src="/assets/index-CvnL0yOX.js"></script> | |
| <script type="module" crossorigin src="/src/main.tsx"></script> |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Fixes GitHub API 404 error when deleting files. Replaces static HTML with React + Material-UI components. Adds Vite build pipeline for optimized bundle delivery.
Delete Fix
GitHub's tree API doesn't support
sha: nullfor deletion. Changed to fetch full tree recursively, filter out target paths, and create new tree:Frontend Migration
public/directory, tree-shaking enabledBuild Pipeline
Structure
Built assets served via Cloudflare Workers + edge network. Single JS bundle eliminates CDN latency for China region.
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
sparrow.cloudflare.com/opt/hostedtoolcache/node/24.13.0/x64/bin/node /opt/hostedtoolcache/node/24.13.0/x64/bin/node --no-warnings --experimental-vm-modules /home/REDACTED/work/FileShare/FileShare/node_modules/wrangler/wrangler-dist/cli.js deploy --dry-run(dns block)/opt/hostedtoolcache/node/24.13.0/x64/bin/node /opt/hostedtoolcache/node/24.13.0/x64/bin/node --no-warnings --experimental-vm-modules /home/REDACTED/work/FileShare/FileShare/node_modules/wrangler/wrangler-dist/cli.js deploy --dry-run --local tnet/tools/sh credential.helpegit(dns block)If you need me to access, download, or install something from one of these locations, you can either:
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.