From 5f8edb79248a7eaccda9600137128331fb0b19b3 Mon Sep 17 00:00:00 2001 From: Saadi Myftija Date: Thu, 11 Dec 2025 14:58:03 +0100 Subject: [PATCH] Make the image builds deterministic --- packages/cli-v3/package.json | 3 +- packages/cli-v3/src/build/buildWorker.ts | 27 ++++++----- packages/cli-v3/src/build/bundle.ts | 4 +- packages/cli-v3/src/deploy/buildImage.ts | 26 +++++------ .../entryPoints/managed-index-controller.ts | 4 +- .../cli-v3/src/entryPoints/managed/env.ts | 4 +- .../cli-v3/src/utilities/buildManifest.ts | 4 +- packages/cli-v3/src/utilities/fileSystem.ts | 3 +- pnpm-lock.yaml | 45 ++++++++++++++----- 9 files changed, 74 insertions(+), 46 deletions(-) diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 81f374099..3223f332c 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -92,10 +92,10 @@ "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-trace-node": "2.0.1", "@opentelemetry/semantic-conventions": "1.36.0", + "@s2-dev/streamstore": "^0.17.6", "@trigger.dev/build": "workspace:4.2.0", "@trigger.dev/core": "workspace:4.2.0", "@trigger.dev/schema-to-json": "workspace:4.2.0", - "@s2-dev/streamstore": "^0.17.6", "ansi-escapes": "^7.0.0", "braces": "^3.0.3", "c12": "^1.11.1", @@ -117,6 +117,7 @@ "import-in-the-middle": "1.11.0", "import-meta-resolve": "^4.1.0", "ini": "^5.0.0", + "json-stable-stringify": "^1.3.0", "jsonc-parser": "3.2.1", "magicast": "^0.3.4", "minimatch": "^10.0.1", diff --git a/packages/cli-v3/src/build/buildWorker.ts b/packages/cli-v3/src/build/buildWorker.ts index 6e818a0b1..b23b802f5 100644 --- a/packages/cli-v3/src/build/buildWorker.ts +++ b/packages/cli-v3/src/build/buildWorker.ts @@ -12,7 +12,7 @@ import { join, relative, sep } from "node:path"; import { generateContainerfile } from "../deploy/buildImage.js"; import { writeFile } from "node:fs/promises"; import { buildManifestToJSON } from "../utilities/buildManifest.js"; -import { readPackageJSON, writePackageJSON } from "pkg-types"; +import { readPackageJSON } from "pkg-types"; import { writeJSONFile } from "../utilities/fileSystem.js"; import { isWindows } from "std-env"; import { pathToFileURL } from "node:url"; @@ -192,20 +192,23 @@ async function writeDeployFiles({ ) ?? {}; // Step 3: Write the resolved dependencies to the package.json file - await writePackageJSON(join(outputPath, "package.json"), { - ...packageJson, - name: packageJson.name ?? "trigger-project", - dependencies: { - ...dependencies, + await writeJSONFile( + join(outputPath, "package.json"), + { + ...packageJson, + name: packageJson.name ?? "trigger-project", + dependencies: { + ...dependencies, + }, + trustedDependencies: Object.keys(dependencies).sort(), + devDependencies: {}, + peerDependencies: {}, + scripts: {}, }, - trustedDependencies: Object.keys(dependencies), - devDependencies: {}, - peerDependencies: {}, - scripts: {}, - }); + true + ); await writeJSONFile(join(outputPath, "build.json"), buildManifestToJSON(buildManifest)); - await writeJSONFile(join(outputPath, "metafile.json"), bundleResult.metafile); await writeContainerfile(outputPath, buildManifest); } diff --git a/packages/cli-v3/src/build/bundle.ts b/packages/cli-v3/src/build/bundle.ts index c085937f6..4d1cfd53f 100644 --- a/packages/cli-v3/src/build/bundle.ts +++ b/packages/cli-v3/src/build/bundle.ts @@ -414,7 +414,9 @@ export async function createBuildManifestFromBundle({ otelImportHook: { include: resolvedConfig.instrumentedPackageNames ?? [], }, - outputHashes: bundle.outputHashes, + // `outputHashes` is only needed for dev builds for the deduplication mechanism during rebuilds. + // For deploys builds, we omit it to ensure deterministic builds + outputHashes: target === "dev" ? bundle.outputHashes : {}, }; if (!workerDir) { diff --git a/packages/cli-v3/src/deploy/buildImage.ts b/packages/cli-v3/src/deploy/buildImage.ts index e325b58d8..f060ce030 100644 --- a/packages/cli-v3/src/deploy/buildImage.ts +++ b/packages/cli-v3/src/deploy/buildImage.ts @@ -193,12 +193,12 @@ async function remoteBuildImage(options: DepotBuildImageOptions): Promise a.name.localeCompare(b.name)), deploy: {}, build: {}, }; diff --git a/packages/cli-v3/src/utilities/fileSystem.ts b/packages/cli-v3/src/utilities/fileSystem.ts index 28178881b..87d74732e 100644 --- a/packages/cli-v3/src/utilities/fileSystem.ts +++ b/packages/cli-v3/src/utilities/fileSystem.ts @@ -1,4 +1,5 @@ import fsSync from "fs"; +import stringify from "json-stable-stringify"; import fsModule, { writeFile } from "fs/promises"; import fs from "node:fs"; import { homedir, tmpdir } from "node:os"; @@ -159,7 +160,7 @@ export async function safeReadJSONFile(path: string) { } export async function writeJSONFile(path: string, json: any, pretty = false) { - await safeWriteFile(path, JSON.stringify(json, undefined, pretty ? 2 : undefined)); + await safeWriteFile(path, stringify(json, pretty ? { space: 2 } : undefined) ?? ""); } // Will create the directory if it doesn't exist diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 000ad8ab3..4102c4373 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1444,6 +1444,9 @@ importers: ini: specifier: ^5.0.0 version: 5.0.0 + json-stable-stringify: + specifier: ^1.3.0 + version: 1.3.0 jsonc-parser: specifier: 3.2.1 version: 3.2.1 @@ -11803,13 +11806,14 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} - call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -14834,6 +14838,10 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stable-stringify@1.3.0: + resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} + engines: {node: '>= 0.4'} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -14860,6 +14868,9 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonify@0.0.1: + resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} + jsonpath-plus@10.3.0: resolution: {integrity: sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==} engines: {node: '>=18.0.0'} @@ -31881,7 +31892,7 @@ snapshots: array.prototype.flat@1.3.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 es-shim-unscopables: 1.0.0 @@ -31895,7 +31906,7 @@ snapshots: array.prototype.flatmap@1.3.2: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 es-shim-unscopables: 1.0.0 @@ -32328,11 +32339,6 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.2: - dependencies: - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - call-bind@1.0.7: dependencies: es-define-property: 1.0.1 @@ -32341,6 +32347,13 @@ snapshots: get-intrinsic: 1.3.0 set-function-length: 1.2.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -35816,6 +35829,14 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stable-stringify@1.3.0: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + isarray: 2.0.5 + jsonify: 0.0.1 + object-keys: 1.1.1 + json-stringify-safe@5.0.1: {} json5@1.0.2: @@ -35842,6 +35863,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonify@0.0.1: {} + jsonpath-plus@10.3.0: dependencies: '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) @@ -40313,7 +40336,7 @@ snapshots: string.prototype.padend@3.1.4: dependencies: - call-bind: 1.0.2 + call-bind: 1.0.7 define-properties: 1.1.4 es-abstract: 1.21.1