nitrojs / nitrojs/nitro

Support reproducible builds using `SOURCE_DATE_EPOCH`

Open
#2,645 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement v2
Dominant language
TypeScript
Stars
11.2k
Forks
899
Avg merge
2d 24m
Merged PRs (30d)
40

Description

Describe the feature

I'd like to create reproducible builds using a framework based on Nitro.

I discovered three instances where Nitro injects non-reproducible timestamps into the output:

Using kaniko --reproducible I was able to create a fully reproducible build (ie. identical container sha256) after applying this patch with pnpm patch

patches/nitropack.patch

diff --git a/dist/nitro.mjs b/dist/nitro.mjs
index d54f501a2e63b68520868b8a40ec923cb73122ac..d00ac0d99091306fb3f9f6932dcd830bae60dcc9 100644
--- a/dist/nitro.mjs
+++ b/dist/nitro.mjs
@@ -1094,7 +1094,7 @@ function publicAssets(nitro) {
             type: nitro._prerenderMeta?.[assetId]?.contentType || mimeType,
             encoding,
             etag,
-            mtime: stat.mtime.toJSON(),
+            mtime: (process.env.SOURCE_DATE_EPOCH ? new Date(process.env.SOURCE_DATE_EPOCH * 1000) : stat.mtime).toJSON(),
             size: stat.size,
             path: relative(nitro.options.output.serverDir, fullPath),
             data: nitro.options.serveStatic === "inline" ? assetData.toString("base64") : void 0
@@ -1209,7 +1209,10 @@ function serverAssets(nitro) {
               type += "; charset=utf-8";
             }
             const etag = createEtag(await promises.readFile(fsPath));
-            const mtime = await promises.stat(fsPath).then((s) => s.mtime.toJSON());
+            const mtime =
+              process.env.SOURCE_DATE_EPOCH
+                  ? new Date(process.env.SOURCE_DATE_EPOCH * 1000).toJSON()
+                  : await promises.stat(fsPath).then((s) => s.mtime.toJSON());
             assets[id].meta = { type, etag, mtime };
           }
         }
@@ -2638,7 +2641,7 @@ async function _build(nitro, rollupConfig) {
   }
   const buildInfoPath = resolve(nitro.options.output.dir, "nitro.json");
   const buildInfo = {
-    date: (/* @__PURE__ */ new Date()).toJSON(),
+    date: (/* @__PURE__ */ process.env.SOURCE_DATE_EPOCH ? new Date(process.env.SOURCE_DATE_EPOCH * 1000) : new Date()).toJSON(),
     preset: nitro.options.preset,
     framework: nitro.options.framework,
     versions: {

This uses the recommended environment variable SOURCE_DATE_EPOCH, see https://reproducible-builds.org/docs/source-date-epoch/

Additional information
  • Would you be willing to help implement this feature?
Notes

Initially I tried to use libfaketime with a fixed timestamp. This did work for nitro.json (ie. new Date()), but Node.js stats.mtime somehow always returns the current time, even though other cli tools returned the fake time (eg. ls, stat, date). Adding native support for SOURCE_DATE_EPOCH seems to be the preferred solution anyways: https://reproducible-builds.org/docs/timestamps/

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read src/core/build/prod.ts, src/rollup/plugins/server-assets.ts, and src/rollup/plugins/public-assets.ts at the referenced timestamp fields, then review the SOURCE_DATE_EPOCH guidance linked in the issue. The work is done when the nitro.json date and both asset mtime values use SOURCE_DATE_EPOCH while retaining current-time behavior when it is unset.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
build-system
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.