Support reproducible builds using `SOURCE_DATE_EPOCH`
Nobody has claimed this yet.
- 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:
datefield innitro.json: https://github.com/unjs/nitro/blob/6f1fa4bfe8cd184b3c8607e0de669c6346b4996f/src/core/build/prod.ts#L38mtimefield inserver-assets: https://github.com/unjs/nitro/blob/6f1fa4bfe8cd184b3c8607e0de669c6346b4996f/src/rollup/plugins/server-assets.ts#L51mtimefield inpublic-assets: https://github.com/unjs/nitro/blob/6f1fa4bfe8cd184b3c8607e0de669c6346b4996f/src/rollup/plugins/public-assets.ts#L58
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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