diff --git a/schema/firebase-config.json b/schema/firebase-config.json index 4780e005206..1338124e049 100644 --- a/schema/firebase-config.json +++ b/schema/firebase-config.json @@ -1221,6 +1221,34 @@ ], "type": "object" }, + "RunSingle": { + "additionalProperties": false, + "properties": { + "ignore": { + "items": { + "type": "string" + }, + "type": "array" + }, + "localBuild": { + "type": "boolean" + }, + "region": { + "type": "string" + }, + "rootDir": { + "type": "string" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "region", + "serviceId" + ], + "type": "object" + }, "StorageSingle": { "additionalProperties": false, "properties": { @@ -2008,6 +2036,19 @@ "remoteconfig": { "$ref": "#/definitions/RemoteConfigConfig" }, + "run": { + "anyOf": [ + { + "$ref": "#/definitions/RunSingle" + }, + { + "items": { + "$ref": "#/definitions/RunSingle" + }, + "type": "array" + } + ] + }, "storage": { "anyOf": [ { diff --git a/src/config.ts b/src/config.ts index ffd9998c88b..cd20b9e1fa0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -34,6 +34,7 @@ export class Config { "apphosting", "auth", "ailogic", + "run", ]; public options: any; diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index c6061d37030..71943fd7faa 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -366,6 +366,18 @@ export type AppHostingMultiple = AppHostingSingle[]; export type AppHostingConfig = AppHostingSingle | AppHostingMultiple; +export interface RunSingle { + serviceId: string; + region: string; + rootDir?: string; + ignore?: string[]; + localBuild?: boolean; +} + +export type RunMultiple = RunSingle[]; + +export type RunConfig = RunSingle | RunMultiple; + export interface AuthConfig { providers?: { anonymous?: boolean; @@ -392,4 +404,5 @@ export type FirebaseConfig = { dataconnect?: DataConnectConfig; apphosting?: AppHostingConfig; auth?: AuthConfig; + run?: RunConfig; }; diff --git a/src/firebaseConfigValidate.spec.ts b/src/firebaseConfigValidate.spec.ts index 327011a577b..0ee2a2751fe 100644 --- a/src/firebaseConfigValidate.spec.ts +++ b/src/firebaseConfigValidate.spec.ts @@ -24,6 +24,87 @@ describe("firebaseConfigValidate", () => { expect(isValid).to.be.true; }); + function expectValidationError( + config: unknown, + keyword: string, + instancePath: string, + params: Record, + ): void { + const validator = getValidator(); + expect(validator(config)).to.be.false; + expect( + (validator.errors ?? []).some( + (e) => + e.keyword === keyword && + e.instancePath === instancePath && + Object.entries(params).every(([k, v]) => e.params[k] === v), + ), + ).to.be.true; + } + + it("should accept a valid run config", () => { + const config: FirebaseConfig = { + run: [ + { + serviceId: "my-service", + region: "us-central1", + rootDir: ".", + }, + ], + }; + + const validator = getValidator(); + expect(validator(config)).to.be.true; + }); + + it("should accept a single-object run config", () => { + const config: FirebaseConfig = { + run: { + serviceId: "my-service", + region: "us-central1", + localBuild: true, + }, + }; + + const validator = getValidator(); + expect(validator(config)).to.be.true; + }); + + it("should reject a run config missing serviceId", () => { + expectValidationError( + { run: [{ region: "us-central1", rootDir: "." }] }, + "required", + "/run/0", + { + missingProperty: "serviceId", + }, + ); + }); + + it("should reject a run config missing region", () => { + expectValidationError({ run: { serviceId: "my-service" } }, "required", "/run", { + missingProperty: "region", + }); + }); + + it("should reject a run config with an unknown field", () => { + expectValidationError( + { run: { serviceId: "my-service", region: "us-central1", bananas: true } }, + "additionalProperties", + "/run", + { additionalProperty: "bananas" }, + ); + }); + + it("should reject a run config with an incorrect type", () => { + expectValidationError( + { run: { serviceId: "my-service", region: "us-central1", localBuild: "yes" } }, + "type", + "/run/localBuild", + { type: "boolean" }, + ); + }); + it("should report an extra top-level field", () => { // This config has an extra 'bananas' top-level property const config = {