From 002b2eab5172141f143fb254fab2ce3ef6a3eac2 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 30 Jan 2024 18:00:01 -0800 Subject: [PATCH 1/8] Make storage bucket parameterizeable. Create const for bucket picker --- src/params/index.ts | 2 ++ src/params/types.ts | 16 +++++++++--- src/v2/providers/storage.ts | 51 ++++++++++++++++++++++++++----------- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/params/index.ts b/src/params/index.ts index 550ab9db9..10fa9211d 100644 --- a/src/params/index.ts +++ b/src/params/index.ts @@ -38,6 +38,8 @@ import { InternalExpression, } from "./types"; +export { BUCKET_PICKER } from "./types"; + export { ParamOptions, Expression }; type SecretOrExpr = Param | SecretParam; diff --git a/src/params/types.ts b/src/params/types.ts index 1eaf553f2..a38a6774e 100644 --- a/src/params/types.ts +++ b/src/params/types.ts @@ -174,8 +174,8 @@ type ParamValueType = "string" | "list" | "boolean" | "int" | "float" | "secret" type ParamInput = | { text: TextInput } | { select: SelectInput } - | { multiSelect: MultiSelectInput } - | { resource: ResourceInput }; + | (T extends unknown[] ? MultiSelectInput : never) + | (T extends string ? ResourceInput : never); /** * Specifies that a Param's value should be determined by prompting the user @@ -205,10 +205,16 @@ export interface TextInput { */ export interface ResourceInput { resource: { - type: string; + type: "storage.googleapis.com/Bucket"; }; } +export const BUCKET_PICKER: ParamInput = { + resource: { + type: "storage.googleapis.com/Bucket", + }, +}; + /** * Specifies that a Param's value should be determined by having the user select * from a list of pre-canned options interactively at deploy-time. @@ -223,7 +229,9 @@ export interface SelectInput { * Will result in errors if used on Params of type other than string[]. */ export interface MultiSelectInput { - options: Array>; + multiSelect: { + options: Array>; + } } /** diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index 451a257b5..e66f6b813 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -201,7 +201,7 @@ export const metadataUpdatedEvent = "google.cloud.storage.object.v1.metadataUpda /** StorageOptions extend EventHandlerOptions with a bucket name */ export interface StorageOptions extends options.EventHandlerOptions { /** The name of the bucket containing this object. */ - bucket?: string; + bucket?: string | Expression; /** * If true, do not deploy or emulate this function. @@ -324,7 +324,7 @@ export function onObjectArchived( * @param handler - Event handler which is run every time a Google Cloud Storage archival occurs. */ export function onObjectArchived( - bucket: string, + bucket: string | Expression, handler: (event: StorageEvent) => any | Promise ): CloudFunction; @@ -352,7 +352,11 @@ export function onObjectArchived( * @param handler - Event handler which is run every time a Google Cloud Storage archival occurs. */ export function onObjectArchived( - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler?: (event: StorageEvent) => any | Promise ): CloudFunction { return onOperation(archivedEvent, bucketOrOptsOrHandler, handler); @@ -384,7 +388,7 @@ export function onObjectFinalized( * @param handler - Event handler which is run every time a Google Cloud Storage object creation occurs. */ export function onObjectFinalized( - bucket: string, + bucket: string | Expression, handler: (event: StorageEvent) => any | Promise ): CloudFunction; @@ -416,7 +420,11 @@ export function onObjectFinalized( * @param handler - Event handler which is run every time a Google Cloud Storage object creation occurs. */ export function onObjectFinalized( - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler?: (event: StorageEvent) => any | Promise ): CloudFunction { return onOperation(finalizedEvent, bucketOrOptsOrHandler, handler); @@ -450,7 +458,7 @@ export function onObjectDeleted( * @param handler - Event handler which is run every time a Google Cloud Storage object deletion occurs. */ export function onObjectDeleted( - bucket: string, + bucket: string | Expression, handler: (event: StorageEvent) => any | Promise ): CloudFunction; @@ -484,7 +492,11 @@ export function onObjectDeleted( * @param handler - Event handler which is run every time a Google Cloud Storage object deletion occurs. */ export function onObjectDeleted( - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler?: (event: StorageEvent) => any | Promise ): CloudFunction { return onOperation(deletedEvent, bucketOrOptsOrHandler, handler); @@ -509,7 +521,7 @@ export function onObjectMetadataUpdated( * @param handler - Event handler which is run every time a Google Cloud Storage object metadata update occurs. */ export function onObjectMetadataUpdated( - bucket: string, + bucket: string | Expression, handler: (event: StorageEvent) => any | Promise ): CloudFunction; @@ -533,7 +545,11 @@ export function onObjectMetadataUpdated( * @param handler - Event handler which is run every time a Google Cloud Storage object metadata update occurs. */ export function onObjectMetadataUpdated( - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler?: (event: StorageEvent) => any | Promise ): CloudFunction { return onOperation(metadataUpdatedEvent, bucketOrOptsOrHandler, handler); @@ -542,7 +558,11 @@ export function onObjectMetadataUpdated( /** @internal */ export function onOperation( eventType: string, - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler: (event: StorageEvent) => any | Promise ): CloudFunction { if (typeof bucketOrOptsOrHandler === "function") { @@ -616,11 +636,12 @@ export function onOperation( /** @internal */ export function getOptsAndBucket( - bucketOrOpts: string | StorageOptions -): [options.EventHandlerOptions, string] { - let bucket: string; + bucketOrOpts: string | Expression | StorageOptions +): [options.EventHandlerOptions, string | Expression] { + let bucket: string | Expression; let opts: options.EventHandlerOptions; - if (typeof bucketOrOpts === "string") { + // If bucket is a string or Expression + if (typeof bucketOrOpts === "string" || "value" in bucketOrOpts) { bucket = bucketOrOpts; opts = {}; } else { @@ -635,7 +656,7 @@ export function getOptsAndBucket( " by providing bucket name directly in the event handler or by setting process.env.FIREBASE_CONFIG." ); } - if (!/^[a-z\d][a-z\d\\._-]{1,230}[a-z\d]$/.test(bucket)) { + if (typeof bucket === "string" && !/^[a-z\d][a-z\d\\._-]{1,230}[a-z\d]$/.test(bucket)) { throw new Error(`Invalid bucket name ${bucket}`); } From e2a221a9133153451ad56d2dee4c74fb2ca83a4c Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 30 Jan 2024 21:49:42 -0800 Subject: [PATCH 2/8] Fix linter error --- src/params/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/params/types.ts b/src/params/types.ts index a38a6774e..bc703919a 100644 --- a/src/params/types.ts +++ b/src/params/types.ts @@ -231,7 +231,7 @@ export interface SelectInput { export interface MultiSelectInput { multiSelect: { options: Array>; - } + }; } /** From 7cfd89e04155b69ce7908fb64e1a12468debe74f Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 30 Jan 2024 21:55:02 -0800 Subject: [PATCH 3/8] Use more specific type --- src/params/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/params/types.ts b/src/params/types.ts index bc703919a..dd74781d5 100644 --- a/src/params/types.ts +++ b/src/params/types.ts @@ -21,6 +21,7 @@ // SOFTWARE. import * as logger from "../logger"; +import { Resource } from "../v1"; /* * A CEL expression which can be evaluated during function deployment, and @@ -209,7 +210,7 @@ export interface ResourceInput { }; } -export const BUCKET_PICKER: ParamInput = { +export const BUCKET_PICKER: ResourceInput = { resource: { type: "storage.googleapis.com/Bucket", }, From 87cf3b4f6394e5dcc2e19c0b7727924f1aac8783 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 31 Jan 2024 07:19:22 -0800 Subject: [PATCH 4/8] Remove unused import --- src/params/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/params/types.ts b/src/params/types.ts index dd74781d5..a3311faf8 100644 --- a/src/params/types.ts +++ b/src/params/types.ts @@ -21,7 +21,6 @@ // SOFTWARE. import * as logger from "../logger"; -import { Resource } from "../v1"; /* * A CEL expression which can be evaluated during function deployment, and From 040415dc0a7b0690c134c6741f3969333cc64c82 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 31 Jan 2024 07:23:20 -0800 Subject: [PATCH 5/8] Export types that were part of an old spec but are not available on main package --- src/params/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/params/index.ts b/src/params/index.ts index 10fa9211d..42792d109 100644 --- a/src/params/index.ts +++ b/src/params/index.ts @@ -38,7 +38,7 @@ import { InternalExpression, } from "./types"; -export { BUCKET_PICKER } from "./types"; +export { BUCKET_PICKER, SelectInput, SelectOptions, MultiSelectInput } from "./types"; export { ParamOptions, Expression }; From 8e442b90d63a38655483329e91f0bd1b5330932e Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 31 Jan 2024 08:45:25 -0800 Subject: [PATCH 6/8] In for a penny, in for a pound --- src/params/index.ts | 10 +++++- src/params/types.ts | 76 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/params/index.ts b/src/params/index.ts index 42792d109..6f19c6d4f 100644 --- a/src/params/index.ts +++ b/src/params/index.ts @@ -38,7 +38,15 @@ import { InternalExpression, } from "./types"; -export { BUCKET_PICKER, SelectInput, SelectOptions, MultiSelectInput } from "./types"; +export { + BUCKET_PICKER, + TextInput, + SelectInput, + SelectOptions, + MultiSelectInput, + select, + multiSelect, +} from "./types"; export { ParamOptions, Expression }; diff --git a/src/params/types.ts b/src/params/types.ts index a3311faf8..5a93da833 100644 --- a/src/params/types.ts +++ b/src/params/types.ts @@ -171,9 +171,51 @@ export class CompareExpression< /** @hidden */ type ParamValueType = "string" | "list" | "boolean" | "int" | "float" | "secret"; +/** Create a select input from a series of values. */ +export function select(options: T[]): SelectInput; + +/** Create a select input from a map of labels to vaues. */ +export function select(optionsWithLabels: Record): SelectInput; + +/** Create a select input from a series of values or a map of labels to values */ +export function select(options: T[] | Record): SelectInput { + let wireOpts: SelectOptions[]; + if (Array.isArray(options)) { + wireOpts = options.map((opt) => ({ value: opt })); + } else { + wireOpts = Object.entries(options).map(([label, value]) => ({ label, value })); + } + return { + select: { + options: wireOpts, + }, + }; +} + +/** Create a multi-select input from a series of values. */ +export function multiSelect(options: string[]): MultiSelectInput; + +/** Create a multi-select input from map of labels to values. */ +export function multiSelect(options: Record): MultiSelectInput; + +/** Create a multi-select input from a series of values or map of labels to values. */ +export function multiSelect(options: string[] | Record): MultiSelectInput { + let wireOpts: SelectOptions[]; + if (Array.isArray(options)) { + wireOpts = options.map((opt) => ({ value: opt })); + } else { + wireOpts = Object.entries(options).map(([label, value]) => ({ label, value })); + } + return { + multiSelect: { + options: wireOpts, + }, + }; +} + type ParamInput = - | { text: TextInput } - | { select: SelectInput } + | TextInput + | SelectInput | (T extends unknown[] ? MultiSelectInput : never) | (T extends string ? ResourceInput : never); @@ -184,18 +226,20 @@ type ParamInput = */ // eslint-disable-next-line @typescript-eslint/no-unused-vars export interface TextInput { - example?: string; - /** - * A regular expression (or an escaped string to compile into a regular - * expression) which the prompted text must satisfy; the prompt will retry - * until input matching the regex is provided. - */ - validationRegex?: string | RegExp; - /** - * A custom error message to display when retrying the prompt based on input - * failing to conform to the validationRegex, - */ - validationErrorMessage?: string; + text: { + example?: string; + /** + * A regular expression (or an escaped string to compile into a regular + * expression) which the prompted text must satisfy; the prompt will retry + * until input matching the regex is provided. + */ + validationRegex?: string | RegExp; + /** + * A custom error message to display when retrying the prompt based on input + * failing to conform to the validationRegex, + */ + validationErrorMessage?: string; + }; } /** @@ -220,7 +264,9 @@ export const BUCKET_PICKER: ResourceInput = { * from a list of pre-canned options interactively at deploy-time. */ export interface SelectInput { - options: Array>; + select: { + options: Array>; + }; } /** From a7838f5c526474f44978ede7dac5ab41a67ba50c Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 7 Feb 2024 07:19:40 -0800 Subject: [PATCH 7/8] Clarify types of select/multiSelect --- src/params/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/params/types.ts b/src/params/types.ts index 5a93da833..ca6040059 100644 --- a/src/params/types.ts +++ b/src/params/types.ts @@ -172,13 +172,13 @@ export class CompareExpression< type ParamValueType = "string" | "list" | "boolean" | "int" | "float" | "secret"; /** Create a select input from a series of values. */ -export function select(options: T[]): SelectInput; +export function select(options: T[]): SelectInput; /** Create a select input from a map of labels to vaues. */ -export function select(optionsWithLabels: Record): SelectInput; +export function select(optionsWithLabels: Record): SelectInput; /** Create a select input from a series of values or a map of labels to values */ -export function select(options: T[] | Record): SelectInput { +export function select(options: T[] | Record): SelectInput { let wireOpts: SelectOptions[]; if (Array.isArray(options)) { wireOpts = options.map((opt) => ({ value: opt })); @@ -216,7 +216,7 @@ export function multiSelect(options: string[] | Record): MultiSe type ParamInput = | TextInput | SelectInput - | (T extends unknown[] ? MultiSelectInput : never) + | (T extends string[] ? MultiSelectInput : never) | (T extends string ? ResourceInput : never); /** From a3e47bcc0af2bee4d2cdbb142d7968df4cf7ef6d Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 7 Feb 2024 07:28:40 -0800 Subject: [PATCH 8/8] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f5f3d22f..bc2ff3550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,4 @@ - Fixes access on deeply nested, nonexistent property. (#1432) - Add IteratedDataSnapshot interface to match with firebase admin v12 (#1517). +- Make bucket parameterizeable in storage functions (#1518) +- Introduce helper library for select and multi-select input (#1518)