Skip to content

XLS-352: pin every zip entry's mtime via an optional zipEntryDate option - #2

Closed
senoff wants to merge 1 commit into
masterfrom
xls352-pin-zip-mtimes-v2
Closed

XLS-352: pin every zip entry's mtime via an optional zipEntryDate option#2
senoff wants to merge 1 commit into
masterfrom
xls352-pin-zip-mtimes-v2

Conversation

@senoff

@senoff senoff commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Zip-entry mtime (date) is a per-entry archiver/JSZip option that defaults to new Date() at append time; a module-level zip: {date} option is a no-op, so writer output was non-deterministic at DOS-format 2-second granularity (XLS-350's determinism issue).
  • Adds an optional zipEntryDate?: Date writer option, threaded through both writer paths:
    • streaming stream.xlsx.WorkbookWriter, via a new _append(data, options) choke point every internal zip.append call now goes through.
    • buffered Workbook.xlsx.writeBuffer/writeFile, via a new ZipWriter._pinEntryDates() called at finalize() (right before zip.generateAsync), which sweeps ALL this.zip.files — including JSZip's auto-created folder entries (xl/, xl/worksheets/, ...) that never pass through .append and would otherwise keep the wall clock.
  • Left undefined (the default), every entry keeps the wall clock exactly as before — no behavior change for existing callers.
  • This is the durable, in-fork replacement for the server-side PinnedWorkbookWriter subclass (packages/shopify-app/app/export-xlsx-file.server.ts), which monkey-patches zip.append from outside the base constructor because there was no published option to pin entry dates. Once this lands and the server's @protobi/exceljs pin is bumped to this commit, that subclass becomes deletable.
  • Also fixes two pre-existing lint-config-drift issues on the touched files (unrelated to the zipEntryDate change, confirmed via a pristine origin/master copy of these exact files hitting the same prettier→eslint failure with zero of this diff's changes): one real import/extensions violation, and // prettier-ignore on 7 pre-existing statements Prettier's current version reformats into a shape ESLint's comma-dangle/space-before-function-paren rules reject. Out of scope here is the standing fix/prettier-eslint-config-drift branch, which reformats the whole repo (207 files) — not touched by this PR.

Test plan

  • New spec spec/integration/issues/issue-xls352-zip-entry-mtime-pin.spec.js: both writer paths, 3 arms each (byte-identity across a real >2s delay when pinned, per-entry mtime stamping, positive control proving the option is causally responsible) — 6/6 passing.
  • spec/unit --recursive: 886 passing / 1 pending (pre-existing), no regressions.
  • spec/integration --recursive: 208 passing, no regressions.
  • npm run lint on the touched files: clean (0 errors).

🤖 Generated with Claude Code

https://claude.ai/code/session_01QY55Be9GCxpDat9XQhzeGZ

Zip-entry mtime (`date`) is a per-entry archiver/JSZip option that defaults
to `new Date()` at append time; a module-level `zip: {date}` option is a
no-op (never forwarded), so output was non-deterministic at DOS-format
2-second granularity (XLS-350's "coin flip" determinism issue).

Threads a new `zipEntryDate?: Date` option through both writer paths:
- streaming `stream.xlsx.WorkbookWriter`, via a new `_append(data, options)`
  choke point every internal `zip.append` call now goes through.
- buffered `Workbook.xlsx.writeBuffer`/`writeFile`, via a new
  `ZipWriter._pinEntryDates()` called at `finalize()` (right before
  `zip.generateAsync`), which sweeps ALL `this.zip.files` — including
  JSZip's auto-created folder entries (`xl/`, `xl/worksheets/`, ...) that
  never pass through `.append` and would otherwise keep the wall clock.

Left undefined (the default), every entry keeps the wall clock exactly as
before — existing callers see no change.

This is the durable, in-fork replacement for the server-side
`PinnedWorkbookWriter` subclass, which reaches past the published exceljs
surface to monkey-patch `zip.append` from outside the base constructor.
Once this lands and the server's @protobi/exceljs pin is bumped, that
subclass becomes deletable in favor of passing `zipEntryDate` directly.

New spec (spec/integration/issues/issue-xls352-zip-entry-mtime-pin.spec.js)
covers both writer paths with 3 arms each: byte-identity across a real
>2s delay when pinned, per-entry mtime stamping, and a positive control
proving the option is causally responsible (bytes differ / wall clock
entries when omitted). 6/6 passing; full unit (886/1 pending) and
integration (208) suites pass with no regressions.

Also fixes two pre-existing lint-config-drift blockers this diff's
lint-staged run surfaced on the touched files (unrelated to the
zipEntryDate change itself, and out of scope for the standing
fix/prettier-eslint-config-drift branch which reformats the whole repo):
- workbook-writer.js: `require('../../xlsx/xml/theme1.js')` violated
  import/extensions (real, pre-existing eslint error independent of
  prettier).
- Added `// prettier-ignore` above 7 other pre-existing statements (in
  workbook-writer.js, zip-stream.js, xlsx.js) that Prettier's current
  version reformats into a shape ESLint's `comma-dangle`/
  `space-before-function-paren` rules reject — confirmed via a pristine
  origin/master copy that this reformat-then-reject failure is 100%
  pre-existing and unrelated to any line this diff touches.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QY55Be9GCxpDat9XQhzeGZ
@senoff

senoff commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

CI is red across every matrix leg, but not from this diff — the workflow uses actions/cache@704facf (a v1/v2-era pinned SHA), which GitHub now hard-fails on unconditionally per its Dec 2024 deprecation. Confirmed pre-existing and unrelated to this PR: master's own tip (8b2d62e, the exact commit the server's @protobi/exceljs dependency is currently pinned to) fails identically:

gh run list -R senoff/exceljs --branch master --limit 1 --json conclusion,headSha
# -> conclusion: failure, headSha: 8b2d62ead2e78f5acd50991ac1f46fad92a3d3c0
gh api repos/senoff/exceljs/actions/jobs/<any-job-id>/logs | tail
# -> ##[error]This request has been automatically failed because it uses a deprecated version of
#    `actions/cache: 704facf57e6136b1bc63b828d79edcd491f0ee84` ...

Local verification (this repo's own test scripts, not CI) is clean on this branch: targeted spec 6/6, spec/unit --recursive 886/1 pending, spec/integration --recursive 208 — all passing, no regressions. npm run lint on the touched files is clean.

Not fixing the workflow itself here (out of scope for XLS-352, and repo-wide — every branch is equally broken). Flagging for whoever owns this repo's CI to bump actions/cache to v3/v4.

@senoff

senoff commented Sep 4, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #3 — same fix, rebased onto 4294597 (Print_Area reconcile) instead of directly on master, since xlsx-for-ai-server pins this fork by commit SHA and its current pin already includes that fix. Pinning to this PR's commit (cb42cbb) would have silently dropped it (caught via a real CI regression). Closing in favor of #3.

@senoff senoff closed this Sep 4, 2026
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.

1 participant