Skip to content

fix: skip directory walk for single file uploads#57

Merged
Svtter merged 2 commits into
mainfrom
worktree-fix-issue-55-single-file-zip
May 15, 2026
Merged

fix: skip directory walk for single file uploads#57
Svtter merged 2 commits into
mainfrom
worktree-fix-issue-55-single-file-zip

Conversation

@Svtter
Copy link
Copy Markdown
Contributor

@Svtter Svtter commented May 15, 2026

Summary

  • Skip filepath.WalkDir when the entry file has no sibling web assets and no common web asset subdirectories (assets/, css/, js/, images/, etc.)
  • Add hasSiblingWebAssets() check: counts web asset files and detects web asset subdirectories
  • Add writeSingleFileEntry() for single-file fast path, avoiding full directory traversal
  • Rename writeFullDirArchivewriteDirEntries, archive lifecycle managed by writeZIPArchive
  • Add regression test TestSingleFileSkipsWalkOnLargeDirectory verifying only the entry file is archived when surrounded by 200 non-web files

Root Cause

writeZIPArchive always used filepath.WalkDir on filepath.Dir(entryFile). For a file like /home/user/report.html, this walks the entire home directory — thousands of files — even though only the single HTML file is relevant.

Test plan

  • All existing tests pass (22/22)
  • New test verifies single-file archive in large directory
  • TestSendUploadsMultipartArchive — multi-file project with assets/ subdir still walks correctly
  • TestSendOnlyIncludesWebAssetsInArchive — multiple web assets still filters correctly
  • TestWriteZIPArchiveSkipsPermissionDenied — single file + non-web subdir uses fast path

Closes #55

🤖 Generated with Claude Code

When a single HTML file has no sibling web assets or common web asset
subdirectories (assets/, css/, js/, etc.), zip only the file itself
instead of walking the entire parent directory. This fixes timeouts and
parse errors when the file is in a large directory like /home/user.

Closes #55

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

发现遗漏

Summary

经过对比 issue #55 的 spec 与实现代码,发现实现功能基本完整,但有 1 个 MEDIUM 严重度的遗漏

MEDIUM

hasSiblingWebAssets 未排除 entryFile 自身,导致某些场景下本应走 fast path 却走了全量 walk

  • 缺少内容: Issue Bug: writeZIPArchive walks entire parent directory for single file uploads #55 的 Suggested Fix 中明确提出 hasSiblingWebAssets(rootDir, entryFile) 需要排除 entry file 自身来计算 sibling web assets。但实际实现中 hasSiblingWebAssets(dir string) 未接收 entryFile 参数,将所有 web asset(包括 entry 文件)一起计数。这意味着当 entry 文件所在目录恰好有一个其他 web asset 文件(如 style.css)时,webCount 变为 2,函数返回 true,触发了全量 WalkDir,而本应只打包 entry + style.css 这两个文件即可。
  • 相关代码位置: internal/cli/cli.go:313-334hasSiblingWebAssets 函数未接收 entryFile 参数;internal/cli/cli.go:300 — 调用处 hasSiblingWebAssets(rootDir) 也未传递 entry file。
  • 建议修改: 将 hasSiblingWebAssets 签名改为 hasSiblingWebAssets(dir, entryFile string),在计数时跳过 filepath.Base(entryFile),使其正确识别"真正的"兄弟 asset 数量。

其余所有 spec 要求的功能(skip WalkDir for single file、writeSingleFileEntry fast path、重命名 writeFullDirArchivewriteDirEntries、边界情况 permission denied 处理、回归测试)均已完整实现,无其他遗漏。

New%20session%20-%202026-05-15T08%3A04%3A48.376Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

Condition: the hasSiblingWebAssets function only recognizes subdirectory names in the hardcoded isWebAssetDir list. 若用户将 web 资源放在非标准目录名(如 src/, components/, dist/, lib/, custom-css/)中,这些文件会被静默丢弃——因为 hasSiblingWebAssets 返回 false → 走 writeSingleFileEntry 只打包根级文件,而旧行为 WalkDir 会递归找到它们。这是一个有用户可见影响的回归(页面资源缺失)。

其他问题(非阻塞):

  • hasSiblingWebAssets 已经读了一次目录,writeSingleFileEntry 又读一次,可以传 []os.DirEntry 参数避免重复读盘。
  • writeSingleFileEntry 不检查 isHiddenPath,而 writeDirEntries 会跳过隐藏文件。虽然 .hidden.css 作为入口文件的情况极罕见,但两条路径的行为不一致。
  • 建议将 dist/, build/, out/ 等常见构建输出目录也加入 isWebAssetDir

  • 阻塞项:hasSiblingWebAssets 对非标准子目录名会静默丢文件,需修复为更保守的检测(如:存在非隐藏子目录且不在 isWebAssetDir 列表中时,也应返回 true 走 walk 路径)
  • 建议项:消除 hasSiblingWebAssetswriteSingleFileEntry 之间的重复 ReadDir;补齐隐藏文件过滤的一致性;扩展 isWebAssetDir 覆盖更多常见目录名

New%20session%20-%202026-05-15T08%3A05%3A45.981Z
opencode session  |  github run

…ctory assets

Address AI code review feedback:

- Replace hasSiblingWebAssets + isWebAssetDir with simpler isSingleFileDir:
  only use fast path when directory has exactly one non-hidden web asset
  file and zero non-hidden subdirectories. Any subdirectory presence
  triggers the full WalkDir to prevent silent resource loss.
- Pass []os.DirEntry to writeSingleFileEntry to avoid redundant ReadDir.
- Add hidden file check in writeSingleFileEntry for consistency with
  writeDirEntries behavior.
- Add regression test TestSubdirectoryWithAssetsStillWalks verifying that
  non-standard subdirectory names (src/, components/, etc.) are walked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

无遗漏

分析

将 issue #55 的 spec 与 PR 实现逐项对比后,确认所有功能均已完整实现:

Spec 要求 1:Skip filepath.WalkDir 当 entry file 没有兄弟 web asset

  • 实现writeZIPArchive (cli.go:296) 通过 os.ReadDir + isSingleFileDir 判断,仅在目录只有一个非隐藏 web asset 且无子目录时走 writeSingleFileEntry 快速路径,否则走 writeDirEntries(WalkDir)。已覆盖

Spec 要求 2:仅打包 entry 文件自身、避免遍历上级目录

  • 实现writeSingleFileEntry (cli.go:339) 只读取根级 web asset 写入 zip,不递归。已覆盖

Spec 要求 3:边界情况与回归测试

  • 200 个非 web 文件包围单个 HTMLTestSingleFileSkipsWalkOnLargeDirectory (cli_test.go:408) 验证。已覆盖
  • 多文件项目 + assets/ 子目录仍然 WalkDirTestSendUploadsMultipartArchive (cli_test.go:104) 验证。已覆盖
  • 多个 web asset 仍然 WalkDirTestSendOnlyIncludesWebAssetsInArchive (cli_test.go:275) 验证。已覆盖
  • 权限拒绝 + 非 web 子目录走快速路径TestWriteZIPArchiveSkipsPermissionDenied (cli_test.go:372) 验证——目录含有 no-permission/ 子目录时 isSingleFileDir 返回 false 走 WalkDir,WalkDir 优雅跳过权限拒绝。已覆盖
  • 非标准子目录名依然包含 web assetTestSubdirectoryWithAssetsStillWalks (cli_test.go:470) 验证 src/components/ 中的 CSS 被正确打包。已覆盖

Spec 建议的 hasSiblingWebAssets(rootDir, entryFile) 签名

  • 实现使用等价的 isSingleFileDir(entries),不接收 entryFile。语义等价:entry 本身即为 web asset(HTML),webCount == 1 等价于"无兄弟 web asset";webCount > 1 等价于"有兄弟 web asset"触发 WalkDir。等效实现,无遗漏

重命名 & 重构

  • writeFullDirArchivewriteDirEntries,archive 生命周期统一由 writeZIPArchive 管理。已实现

所有 spec 要求均已完整覆盖,无遗漏。

New%20session%20-%202026-05-15T08%3A36%3A11.077Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

可合并

总结

PR 通过两次提交正确实现了单文件上传时跳过 filepath.WalkDir 的优化。第一次提交 (32bfcff) 的 hasSiblingWebAssets + isWebAssetDir 方案确实存在硬编码目录列表导致静默丢失非标准目录中资源的问题;第二次提交 (ac7e13c) 已将检测策略彻底重构为 isSingleFileDir:只要目录中存在任意子目录(无论名称),就返回 false 并回退到 full WalkDir。这一策略是保守且正确的,前序 review 指出的两类问题在当前 HEAD 代码中均已不复存在。

阻塞项

建议项

  1. isHiddenName... 的检查是多余的——os.ReadDir 从不返回这两项,可移除以简化代码。
  2. TestSubdirectoryWithAssetsStillWalks 直接测试了 writeZIPArchive 这个 unexported 函数,验证逻辑正确;但若通过 Run(完整 send 命令链路)测试会更具端到端覆盖力。
  3. writeSingleFileEntry 的 fallback 错误信息 "no web asset found in directory" 未包含目录路径,调试时可附上 dir 参数值。

New%20session%20-%202026-05-15T08%3A36%3A12.841Z
opencode session  |  github run

@Svtter Svtter merged commit 00c8359 into main May 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: writeZIPArchive walks entire parent directory for single file uploads

1 participant