diff --git a/CHANGELOG.md b/CHANGELOG.md index a9dca357275..828dbc7a0f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Fix bug where CLI was unable to deploy Firebase Functions in some monorepo setups (#5391) - Upgrade Storage Rules Runtime to v1.1.3 to support ternary operators (#5370) +- Fixes an issue where already deployed functions with the same remote configuration do not get skipped (#5354) diff --git a/src/deploy/functions/prepareFunctionsUpload.ts b/src/deploy/functions/prepareFunctionsUpload.ts index 7337fb8ac32..bb65f92c235 100644 --- a/src/deploy/functions/prepareFunctionsUpload.ts +++ b/src/deploy/functions/prepareFunctionsUpload.ts @@ -21,6 +21,8 @@ interface PackagedSourceInfo { hash: string; } +type SortedConfig = string | { key: string; value: SortedConfig }[]; + // TODO(inlined): move to a file that's not about uploading source code export async function getFunctionsConfig(projectId: string): Promise> { try { @@ -88,8 +90,11 @@ async function packageSource( }); } if (typeof runtimeConfig !== "undefined") { + // In order for hash to be consistent, configuration object tree must be sorted by key, only possible with arrays. + const runtimeConfigHashString = JSON.stringify(convertToSortedKeyValueArray(runtimeConfig)); + hashes.push(runtimeConfigHashString); + const runtimeConfigString = JSON.stringify(runtimeConfig, null, 2); - hashes.push(runtimeConfigString); archive.append(runtimeConfigString, { name: CONFIG_DEST_FILE, mode: 420 /* 0o644 */, @@ -125,3 +130,13 @@ export async function prepareFunctionsUpload( ): Promise { return packageSource(sourceDir, config, runtimeConfig); } + +export function convertToSortedKeyValueArray(config: any): SortedConfig { + if (typeof config !== "object" || config === null) return config; + + return Object.keys(config) + .sort() + .map((key) => { + return { key, value: convertToSortedKeyValueArray(config[key]) }; + }); +} diff --git a/src/test/deploy/functions/prepareFunctionsUpload.spec.ts b/src/test/deploy/functions/prepareFunctionsUpload.spec.ts new file mode 100644 index 00000000000..35d14600033 --- /dev/null +++ b/src/test/deploy/functions/prepareFunctionsUpload.spec.ts @@ -0,0 +1,48 @@ +import { expect } from "chai"; +import * as prepareFunctionsUpload from "../../../deploy/functions/prepareFunctionsUpload"; + +describe("prepareFunctionsUpload", () => { + describe("convertToSortedKeyValueArray", () => { + it("should deep sort the resulting array when an input config object is not sorted", () => { + const config = { + b: "b", + a: { + b: { + c: "c", + a: "a", + }, + a: "a", + }, + }; + const expected = [ + { + key: "a", + value: [ + { key: "a", value: "a" }, + { + key: "b", + value: [ + { + key: "a", + value: "a", + }, + { + key: "c", + value: "c", + }, + ], + }, + ], + }, + { key: "b", value: "b" }, + ]; + expect(prepareFunctionsUpload.convertToSortedKeyValueArray(config)).to.deep.equal(expected); + }); + it("should return null when config input is null", () => { + expect(prepareFunctionsUpload.convertToSortedKeyValueArray(null)).to.be.equal(null); + }); + it("should return an empty array when config input is an empty object", () => { + expect(prepareFunctionsUpload.convertToSortedKeyValueArray({})).to.deep.equal([]); + }); + }); +});