From 97c8e1d667b2776918d11aeb8b4c4b6d34aaad68 Mon Sep 17 00:00:00 2001 From: titus Date: Tue, 14 Jul 2026 22:48:30 -0500 Subject: [PATCH] Make release builds timezone reproducible --- .github/workflows/ci.yml | 8 ++++++++ CHANGELOG.md | 9 ++++++++- README.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- .../manifest.json | 2 +- scripts/lib/zip.mjs | 8 ++++---- test/zip.test.js | 7 +++++++ 8 files changed, 32 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68056f9..5ed8d29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,14 @@ jobs: unzip -t release/*.streamDeckPlugin test "$(unzip -Z1 release/*.streamDeckPlugin | cut -d/ -f1 | sort -u)" = "com.christitustech.codex-status.sdPlugin" + - name: Verify reproducible package + if: matrix.node == 24 + run: | + first=$(sha256sum -- release/*.streamDeckPlugin | cut -d' ' -f1) + TZ=Pacific/Auckland npm run package + second=$(sha256sum -- release/*.streamDeckPlugin | cut -d' ' -f1) + test "$first" = "$second" + - name: Upload packaged plugin if: matrix.node == 24 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b45c49..861ea54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project are documented here. The format is based on ## [Unreleased] +## [1.0.1] - 2026-07-14 + +### Fixed + +- Encode package timestamps in UTC so release archives are byte-for-byte reproducible across builder timezones. + ## [1.0.0] - 2026-07-14 ### Added @@ -14,5 +20,6 @@ All notable changes to this project are documented here. The format is based on - Reproducible `.streamDeckPlugin` packaging. - Automated tests, CI, release checksums, and build provenance attestations. -[Unreleased]: https://github.com/ChrisTitusTech/streamdeck-dashboard/compare/v1.0.0...HEAD +[Unreleased]: https://github.com/ChrisTitusTech/streamdeck-dashboard/compare/v1.0.1...HEAD +[1.0.1]: https://github.com/ChrisTitusTech/streamdeck-dashboard/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/ChrisTitusTech/streamdeck-dashboard/releases/tag/v1.0.0 diff --git a/README.md b/README.md index efc8e56..dd04438 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ npm run package The release command creates: ```text -release/codex-status-v1.0.0.streamDeckPlugin +release/codex-status-vX.Y.Z.streamDeckPlugin ``` For local development installation: diff --git a/package-lock.json b/package-lock.json index e4a1ee3..2c8914d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "streamdeck-codex-status", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "streamdeck-codex-status", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { "ws": "8.21.1" diff --git a/package.json b/package.json index 43578b8..5181a67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamdeck-codex-status", - "version": "1.0.0", + "version": "1.0.1", "private": true, "description": "OpenDeck plugin that shows whether local Codex sessions are working, complete, or offline.", "license": "MIT", diff --git a/plugin/com.christitustech.codex-status.sdPlugin/manifest.json b/plugin/com.christitustech.codex-status.sdPlugin/manifest.json index 563b5c2..dc1576f 100644 --- a/plugin/com.christitustech.codex-status.sdPlugin/manifest.json +++ b/plugin/com.christitustech.codex-status.sdPlugin/manifest.json @@ -28,5 +28,5 @@ "Platform": "linux" } ], - "Version": "1.0.0" + "Version": "1.0.1" } diff --git a/scripts/lib/zip.mjs b/scripts/lib/zip.mjs index b1ba87b..f7fcbb8 100644 --- a/scripts/lib/zip.mjs +++ b/scripts/lib/zip.mjs @@ -16,9 +16,9 @@ function crc32(buffer) { } function dosTimestamp(date) { - const year = Math.max(1980, date.getFullYear()); - const time = (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2); - const day = ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate(); + const year = Math.max(1980, date.getUTCFullYear()); + const time = (date.getUTCHours() << 11) | (date.getUTCMinutes() << 5) | Math.floor(date.getUTCSeconds() / 2); + const day = ((year - 1980) << 9) | ((date.getUTCMonth() + 1) << 5) | date.getUTCDate(); return { day, time }; } @@ -107,4 +107,4 @@ export async function createZip(sourceDirectory, destination, archiveRoot) { return files.map((file) => `${archiveRoot}/${file}`); } -export { crc32 }; +export { crc32, dosTimestamp }; diff --git a/test/zip.test.js b/test/zip.test.js index 856f544..c8d46c6 100644 --- a/test/zip.test.js +++ b/test/zip.test.js @@ -7,3 +7,10 @@ test('ZIP CRC-32 implementation matches the standard check value', async () => { const { crc32 } = await import('../scripts/lib/zip.mjs'); assert.equal(crc32(Buffer.from('123456789')), 0xcbf43926); }); + +test('ZIP timestamp encoding is fixed to UTC', async () => { + const { dosTimestamp } = await import('../scripts/lib/zip.mjs'); + const timestamp = dosTimestamp(new Date('1980-01-01T00:00:00Z')); + + assert.deepEqual(timestamp, { day: 33, time: 0 }); +});