Skip to content

Fix downloaded project ZIP using absolute paths (#147)#356

Open
cscheid wants to merge 2 commits into
mainfrom
feature/bd-esnxtcoy-zip-absolute-paths
Open

Fix downloaded project ZIP using absolute paths (#147)#356
cscheid wants to merge 2 commits into
mainfrom
feature/bd-esnxtcoy-zip-absolute-paths

Conversation

@cscheid

@cscheid cscheid commented Jul 1, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #147. The hub-client Export ZIP feature produced archives whose entries were absolute (e.g. /cscheid/columns.qmd), because exportProjectAsZip used the stored index paths verbatim and those carry a leading slash. unzip refuses absolute paths and prints warning: stripped absolute path spec … for every entry.

Now entries are relative and nested under a single project-name folder, matching the download filename:

$ unzip -l Demo-Playground.zip
    Demo-Playground/cscheid/columns.qmd
    Demo-Playground/cscheid/crossrefs.qmd
    Demo-Playground/index.qmd

No warnings, one tidy Demo-Playground/ root.

Approach

The fix lives at the ZIP serialization boundary, not the storage convention (the leading-slash index paths are load-bearing across the sync client and index, so changing them is out of scope and risky):

  • export-zip.tsexportProjectAsZip(client, rootDir?) strips leading slashes so entries are relative, and nests every entry under a sanitized top-level folder when rootDir is given. The importer (parseProjectZip) already strips a common leading directory, so export/import round-trip cleanly.
  • project-folder-name.ts (new, exported from quarto-sync-client) — derives a safe single path segment from the project name: Windows-hostile chars (< > : " / \ | ? *), path separators, and control chars collapse to hyphens; trailing dots/spaces are trimmed; empty falls back to project.
  • automergeSync.ts — wrapper forwards an optional rootDir.
  • ProjectTab.tsx — computes one slug via projectFolderName(project.description) and uses it for both the in-archive folder and the .zip download filename, so they can never drift.

Tests

  • project-folder-name.test.ts — 9 cases (sanitization, fallback, leading/trailing trim).
  • export-zip.test.ts — leading-slash stripping, rootDir nesting, no absolute entries, rootDir normalization, and an export→import round-trip.
  • ProjectTab.test.tsx — jsdom wiring: same sanitized slug drives both onExportZip and the download filename.

All pass: quarto-sync-client 117, preview-runtime 74, hub-client 675 unit + 76 integration + 121 wasm. Production build (tsc -b && vite build) clean.

End-to-end verification

Ran the real exportProjectAsZip(client, 'Demo Playground') with absolute stored paths and inspected the bytes with the system unzip (the bug reporter's exact command) — entries are relative, nested under Demo-Playground/, and extraction emits no "stripped absolute path spec" warnings. The space in "Demo Playground" is sanitized to Demo-Playground, matching the download filename.

Not run this session (noted as optional follow-ups in the plan): a live-browser hub session and a Playwright export e2e (there is no export e2e today).

🤖 Generated with Claude Code

cscheid and others added 2 commits July 1, 2026 13:01
The hub-client "Export ZIP" feature produced archives whose entries were
absolute (e.g. /cscheid/columns.qmd), because exportProjectAsZip used the
stored index paths verbatim and those carry a leading slash. unzip refuses
absolute paths and prints "stripped absolute path spec" for every entry.

Fix at the ZIP serialization boundary (not the storage convention, which is
load-bearing across the sync client and index):

- export-zip.ts: exportProjectAsZip(client, rootDir?) now strips leading
  slashes so entries are relative, and nests every entry under a single
  sanitized top-level folder when rootDir is given. The importer already
  strips a common leading directory, so export/import round-trip cleanly.
- New project-folder-name.ts helper (exported from quarto-sync-client):
  derives a safe single path segment from the project name — Windows-hostile
  chars, path separators, and control chars collapse to hyphens; trailing
  dots/spaces are trimmed; empty falls back to "project".
- automergeSync.ts wrapper forwards an optional rootDir.
- ProjectTab.tsx computes one slug via projectFolderName(project.description)
  and uses it for both the in-archive folder and the .zip download filename,
  so the two can never drift.

Tests: new project-folder-name.test.ts (9 cases), export-zip round-trip and
absolute-path cases, and a jsdom ProjectTab.test.tsx for the slug wiring.
Verified end-to-end with the real function + system `unzip -l`: entries now
read Demo-Playground/cscheid/columns.qmd with no warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

downloaded project zipfile uses absolute paths

1 participant