From 7bbf42a3c52b09784ef95dc63e3c86ca7d8529ee Mon Sep 17 00:00:00 2001 From: Giacomo Debidda Date: Sun, 11 Sep 2022 23:46:00 +0200 Subject: [PATCH 1/6] feat(webhooks): add route to handle Stripe webhook events --- docs/secret-manager.md | 11 + package-lock.json | 75 ++---- packages/webhooks/Dockerfile | 18 +- packages/webhooks/README.md | 44 ++- packages/webhooks/cloudbuild.yaml | 6 +- packages/webhooks/package.json | 16 +- packages/webhooks/src/app.ts | 68 ++++- packages/webhooks/src/clients/index.ts | 2 + packages/webhooks/src/clients/stripe.ts | 28 ++ packages/webhooks/src/constants.ts | 19 ++ packages/webhooks/src/interfaces.ts | 17 +- packages/webhooks/src/routes/stripe/get.ts | 32 +++ packages/webhooks/src/routes/stripe/post.ts | 283 ++++++++++++++++++++ packages/webhooks/src/schemas.ts | 27 +- 14 files changed, 525 insertions(+), 121 deletions(-) create mode 100644 packages/webhooks/src/clients/stripe.ts create mode 100644 packages/webhooks/src/routes/stripe/get.ts create mode 100644 packages/webhooks/src/routes/stripe/post.ts diff --git a/docs/secret-manager.md b/docs/secret-manager.md index f315531f..e72317c4 100644 --- a/docs/secret-manager.md +++ b/docs/secret-manager.md @@ -50,6 +50,17 @@ gcloud secrets create SENDGRID \ --labels customer=$CUSTOMER,environment=$ENVIRONMENT,resource=secret ``` +```sh +gcloud secrets create STRIPE_API_KEY_TEST \ + --labels customer=$CUSTOMER,environment=$ENVIRONMENT,resource=secret +``` + +```sh +gcloud secrets create STRIPE_WEBHOOKS_TEST \ + --data-file './secrets/stripe-webhooks-test.json' \ + --labels customer=$CUSTOMER,environment=$ENVIRONMENT,resource=secret +``` + ```sh gcloud secrets create TELEGRAM \ --labels customer=$CUSTOMER,environment=$ENVIRONMENT,resource=secret diff --git a/package-lock.json b/package-lock.json index a033e98c..021ea90b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20136,7 +20136,6 @@ "version": "10.8.0", "resolved": "https://registry.npmjs.org/stripe/-/stripe-10.8.0.tgz", "integrity": "sha512-/cQiJ7puqVMDrGDRGqtHn6zPB+nRNA97qyQU59msLQ1OWaQOmwW1IQR7PpRqeqkSv9SgPU7BVBlMKNwWtn1qNA==", - "dev": true, "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.10.3" @@ -21908,7 +21907,7 @@ }, "packages/checks": { "name": "@jackdbd/checks", - "version": "2.0.0-canary.1", + "version": "2.0.0", "license": "MIT", "devDependencies": {}, "engines": { @@ -21917,7 +21916,7 @@ }, "packages/cloud-scheduler-utils": { "name": "@jackdbd/cloud-scheduler-utils", - "version": "1.1.0-canary.1", + "version": "1.1.0", "license": "MIT", "devDependencies": {}, "engines": { @@ -21930,7 +21929,7 @@ }, "packages/cloud-tasks-utils": { "name": "@jackdbd/cloud-tasks-utils", - "version": "1.0.0-canary.2", + "version": "1.0.0", "license": "MIT", "devDependencies": {}, "engines": { @@ -21942,7 +21941,7 @@ }, "packages/fattureincloud-client": { "name": "@jackdbd/fattureincloud-client", - "version": "1.0.3-canary.1", + "version": "1.0.3", "license": "MIT", "dependencies": { "bottleneck": "^2.19.5", @@ -21956,10 +21955,10 @@ }, "packages/firestore-utils": { "name": "@jackdbd/firestore-utils", - "version": "1.3.0-canary.1", + "version": "1.3.0", "license": "MIT", "dependencies": { - "@jackdbd/utils": "^1.2.0", + "@jackdbd/utils": "1.3.0", "debug": "^4.3.4" }, "devDependencies": { @@ -21972,20 +21971,6 @@ "@google-cloud/firestore": ">=5.0.0" } }, - "packages/firestore-utils/node_modules/@jackdbd/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jackdbd/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-mwHjCoj+RZHaaZu9UKH4lwVOZ4fdRtzRnA3oMBqH7ceMdciRSTHjD6GxxraFrybeFV5uHZnm80kfcrevvnfsPw==", - "dependencies": { - "wait-port": "^0.2.9" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "debug": ">=4.0.0" - } - }, "packages/keap-client": { "name": "@jackdbd/keap-client", "version": "1.0.0", @@ -22046,7 +22031,7 @@ }, "packages/schemas": { "name": "@jackdbd/schemas", - "version": "1.0.0", + "version": "1.1.2", "license": "MIT", "devDependencies": {}, "engines": { @@ -22636,7 +22621,7 @@ }, "packages/secret-manager-utils": { "name": "@jackdbd/secret-manager-utils", - "version": "1.1.0-canary.1", + "version": "1.1.0", "license": "MIT", "devDependencies": { "@google-cloud/secret-manager": "3.12.0" @@ -22871,7 +22856,7 @@ }, "packages/stripe-utils": { "name": "@jackdbd/stripe-utils", - "version": "1.1.0-canary.1", + "version": "1.1.0", "license": "MIT", "devDependencies": { "stripe": "^10.8.0" @@ -22885,7 +22870,7 @@ }, "packages/tags-logger": { "name": "@jackdbd/tags-logger", - "version": "1.1.0-canary.1", + "version": "1.1.0", "license": "MIT", "dependencies": { "debug": "^4.3.4", @@ -23182,7 +23167,7 @@ }, "packages/telegram-text-messages": { "name": "@jackdbd/telegram-text-messages", - "version": "1.1.0-canary.2", + "version": "1.1.0", "license": "MIT", "devDependencies": {}, "engines": { @@ -23191,7 +23176,7 @@ }, "packages/utils": { "name": "@jackdbd/utils", - "version": "1.3.0-canary.1", + "version": "1.3.0", "license": "MIT", "dependencies": { "wait-port": "^0.2.9" @@ -23668,7 +23653,8 @@ "@jackdbd/hapi-telegram-plugin": "2.0.0-canary.1", "@jackdbd/notifications": "1.0.0", "@jackdbd/secret-manager-utils": "1.0.0", - "@jackdbd/telegram-text-messages": "1.0.0", + "@jackdbd/stripe-utils": "^1.1.0", + "@jackdbd/telegram-text-messages": "^1.1.0", "@jackdbd/utils": "^1.2.0", "debug": "4.3.4", "exiting": "6.1.0", @@ -23676,7 +23662,8 @@ "hapi-dev-errors": "4.0.0", "hapi-swagger": "14.5.1", "ipaddr.js": "2.0.1", - "joi": "17.6.0" + "joi": "17.6.0", + "stripe": "^10.8.0" }, "engines": { "node": ">=16.15.0" @@ -23780,14 +23767,6 @@ "debug": ">=4.0.0" } }, - "packages/webhooks/node_modules/@jackdbd/telegram-text-messages": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, "packages/webhooks/node_modules/@jackdbd/utils": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@jackdbd/utils/-/utils-1.2.0.tgz", @@ -25830,18 +25809,8 @@ "version": "file:packages/firestore-utils", "requires": { "@google-cloud/firestore": "^5.0.2", - "@jackdbd/utils": "^1.2.0", + "@jackdbd/utils": "1.3.0", "debug": "^4.3.4" - }, - "dependencies": { - "@jackdbd/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jackdbd/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-mwHjCoj+RZHaaZu9UKH4lwVOZ4fdRtzRnA3oMBqH7ceMdciRSTHjD6GxxraFrybeFV5uHZnm80kfcrevvnfsPw==", - "requires": { - "wait-port": "^0.2.9" - } - } } }, "@jackdbd/hapi-logger-plugin": { @@ -26918,7 +26887,8 @@ "@jackdbd/hapi-telegram-plugin": "2.0.0-canary.1", "@jackdbd/notifications": "1.0.0", "@jackdbd/secret-manager-utils": "1.0.0", - "@jackdbd/telegram-text-messages": "1.0.0", + "@jackdbd/stripe-utils": "^1.1.0", + "@jackdbd/telegram-text-messages": "^1.1.0", "@jackdbd/utils": "^1.2.0", "debug": "4.3.4", "exiting": "6.1.0", @@ -26926,7 +26896,8 @@ "hapi-dev-errors": "4.0.0", "hapi-swagger": "14.5.1", "ipaddr.js": "2.0.1", - "joi": "17.6.0" + "joi": "17.6.0", + "stripe": "^10.8.0" }, "dependencies": { "@google-cloud/secret-manager": { @@ -26985,9 +26956,6 @@ "version": "1.0.0", "requires": {} }, - "@jackdbd/telegram-text-messages": { - "version": "1.0.0" - }, "@jackdbd/utils": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@jackdbd/utils/-/utils-1.2.0.tgz", @@ -40348,7 +40316,6 @@ "version": "10.8.0", "resolved": "https://registry.npmjs.org/stripe/-/stripe-10.8.0.tgz", "integrity": "sha512-/cQiJ7puqVMDrGDRGqtHn6zPB+nRNA97qyQU59msLQ1OWaQOmwW1IQR7PpRqeqkSv9SgPU7BVBlMKNwWtn1qNA==", - "dev": true, "requires": { "@types/node": ">=8.1.0", "qs": "^6.10.3" diff --git a/packages/webhooks/Dockerfile b/packages/webhooks/Dockerfile index 67d15c34..36b66800 100644 --- a/packages/webhooks/Dockerfile +++ b/packages/webhooks/Dockerfile @@ -10,9 +10,9 @@ LABEL maintainer="giacomo@giacomodebidda.com" ARG APP_NAME RUN if [ -z "${APP_NAME}" ] ; then echo "The APP_NAME argument is missing!" ; exit 1; fi -RUN apt-get update && apt-get install --quiet --assume-yes sudo \ - lsb-release \ - tree +# RUN apt-get update && apt-get install --quiet --assume-yes sudo \ +# lsb-release \ +# tree ENV APP_DIR=/usr/src/app ENV TYPE_ROOTS="${APP_DIR}/node_modules/@types,${APP_DIR}/custom-types" @@ -55,9 +55,9 @@ FROM node:18.7-bullseye-slim # defined. That's why we have to repeat it here in this stage. ARG APP_NAME -RUN apt-get update && apt-get install --quiet --assume-yes sudo \ - lsb-release \ - tree +# RUN apt-get update && apt-get install --quiet --assume-yes sudo \ +# lsb-release \ +# tree ENV APP_GROUP=webhooks-group \ APP_USER=webhooks-user \ @@ -84,11 +84,11 @@ RUN chown -R ${APP_USER} ./ USER ${APP_USER} # check source code and installed dependencies -RUN tree -a -L 3 . +# RUN tree -a -L 3 . # check permissions -RUN ls -1la +# RUN ls -1la # I like to keep this line for troubleshooting -RUN echo "App ${APP_NAME} will be run by user $(whoami) on $(lsb_release -i -s) $(lsb_release -r -s) and will listen on port ${APP_PORT}" +# RUN echo "App ${APP_NAME} will be run by user $(whoami) on $(lsb_release -i -s) $(lsb_release -r -s) and will listen on port ${APP_PORT}" ENTRYPOINT ["node", "dist/main.js"] diff --git a/packages/webhooks/README.md b/packages/webhooks/README.md index fd8db7ab..77153829 100644 --- a/packages/webhooks/README.md +++ b/packages/webhooks/README.md @@ -4,53 +4,35 @@ Application that receives webhook events from several third parties: [Cloud Moni ## Development -Build and watch the web application with `tsc` in watch mode: - -```sh -npm run build:watch -w packages/webhooks -``` - -In another terminal, create a HTTPS => HTTP tunnel with [ngrok](https://ngrok.com/) on port 8080 and launch the application with `NODE_ENV = development`: +Start the **non-containerized** application and forward Stripe webhook events to localhost:8080: ```sh npm run dev -w packages/webhooks ``` -Then visit http://localhost:4040/status to know the public URL ngrok assigned you, and assign it the `WEBHOOKS_URL` environment variable in the `.envrc`. - -You can also visit to http://localhost:4040/inspect/http to inspect/replay past requests that were tunneled by ngrok. - -## Build - -### non-containerized application - -Build the web application: +Or do the same thing, but with the containerized application: ```sh -npm run build -w packages/webhooks +npm run container:dev -w packages/webhooks ``` -Start the application in a `development` / `test` environment: +Note that you'll have to build the container image first: ```sh -npm run start:development -w packages/webhooks -npm run start:test -w packages/webhooks +npm run container:build -w packages/webhooks ``` -### containerized application +### Older instructions, with ngrok -Build the container image: +In another terminal, create a HTTPS => HTTP tunnel with [ngrok](https://ngrok.com/) on port 8080: ```sh -npm run container:build -w packages/webhooks +npx ngrok http 8080 ``` -Start the containerized application in a `development` / `production` environment: +Then visit http://localhost:4040/status to know the public URL ngrok assigned you, and assign it the `WEBHOOKS_URL` environment variable in the `.envrc`. -```sh -npm run container:run:development -w packages/webhooks -npm run container:run:production -w packages/webhooks -``` +You can also visit to http://localhost:4040/inspect/http to inspect/replay past requests that were tunneled by ngrok. ## Deploy to GCP Cloud Run @@ -112,6 +94,12 @@ curl -X POST \ List the webhooks registered with npm.js with `npm hook ls`. +POST request made by a [Stripe webhook](https://stripe.com/docs/webhooks): + +```sh +stripe trigger --api-key $STRIPE_API_KEY_TEST customer.created +``` + POST request made by Cloud Monitoring when an [uptime check](https://cloud.google.com/monitoring/uptime-checks) fails: ```sh diff --git a/packages/webhooks/cloudbuild.yaml b/packages/webhooks/cloudbuild.yaml index d50737ef..c46369cf 100644 --- a/packages/webhooks/cloudbuild.yaml +++ b/packages/webhooks/cloudbuild.yaml @@ -8,9 +8,9 @@ steps: - '--build-arg=APP_NAME=${_CLOUD_RUN_SERVICE_ID}' - '--tag=${_CONTAINER_IMAGE}' - - id: '👀 List container images' - name: gcr.io/cloud-builders/docker - args: ['image', 'ls'] + # - id: '👀 List container images' + # name: gcr.io/cloud-builders/docker + # args: ['image', 'ls'] - id: '🛫 Push container image to Artifact Registry' name: gcr.io/cloud-builders/docker diff --git a/packages/webhooks/package.json b/packages/webhooks/package.json index d70def91..0c2bc13b 100644 --- a/packages/webhooks/package.json +++ b/packages/webhooks/package.json @@ -23,11 +23,13 @@ "build": "tsc -p tsconfig.json", "build:watch": "tsc -p tsconfig.json --watch", "clean": "rimraf coverage dist 'tsconfig.tsbuildinfo' 'tsconfig-container.json'", - "dev": "run-p tunnel start:development", + "dev": "run-p forward-webhook-events start:development", "precontainer:build": "run-s clean make-tsconfig", "container:build": "docker build ./ --file Dockerfile --build-arg APP_NAME=calderone-webhooks --tag calderone-webhooks:latest", + "container:dev": "run-p forward-webhook-events container:start:development", "container:inspect": "dive calderone-webhooks:latest", - "container:run:development": "docker run -it --rm -p 8080:8080 --env \"APP_CONFIG=$(cat ../../secrets/webhooks-config-development.json)\" --env NODE_ENV=development --env \"SA_JSON_KEY=$(cat ../../secrets/sa-webhooks.json)\" --env \"TELEGRAM=$(cat ../../secrets/telegram.json)\" calderone-webhooks:latest", + "container:start:development": "docker run -it --rm -p 8080:8080 --env \"APP_CONFIG=$(cat ../../secrets/webhooks-config-development.json)\" --env DEBUG=webhooks* --env NODE_ENV=development --env \"SA_JSON_KEY=$(cat ../../secrets/sa-webhooks.json)\" --env \"STRIPE_WEBHOOKS=$(cat ../../secrets/stripe-webhooks-test.json)\" --env \"TELEGRAM=$(cat ../../secrets/telegram.json)\" calderone-webhooks:latest", + "forward-webhook-events": "stripe listen --events customer.created,customer.deleted,payment_intent.succeeded,product.created --api-key=$STRIPE_API_KEY_TEST --forward-to localhost:8080/stripe --log-level info --device-name 'ThinkPad L380'", "make-tsconfig": "../../scripts/make-tsconfig.mjs", "predeploy": "run-s clean format lint make-tsconfig", "deploy": "gcloud beta builds submit ./ --config cloudbuild.yaml --async", @@ -35,8 +37,8 @@ "lint": "eslint --config ../../config/eslint.cjs", "nuke": "npm run clean && rimraf node_modules 'package-lock.json'", "precommit": "lint-staged --config ../../config/lint-staged.cjs", - "start:development": "APP_CONFIG=$(cat ../../secrets/webhooks-config-development.json) GOOGLE_APPLICATION_CREDENTIALS=../../secrets/sa-webhooks.json NODE_ENV=development node ./dist/main.js", - "test": "APP_CONFIG=$(cat ../../secrets/webhooks-config-test.json) GOOGLE_APPLICATION_CREDENTIALS=../../secrets/sa-webhooks.json NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules npx jest --selectProjects webhooks --config ../../config/jest.cjs --rootDir ../../ --runInBand --detectOpenHandles", + "start:development": "APP_CONFIG=$(cat ../../secrets/webhooks-config-development.json) GOOGLE_APPLICATION_CREDENTIALS=../../secrets/sa-webhooks.json NODE_ENV=development STRIPE_WEBHOOKS=$(cat ../../secrets/stripe-webhooks-test.json) tsm ./src/main.ts", + "test": "APP_CONFIG=$(cat ../../secrets/webhooks-config-test.json) DEBUG=webhooks* GOOGLE_APPLICATION_CREDENTIALS=../../secrets/sa-webhooks.json NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules npx jest --selectProjects webhooks --config ../../config/jest.cjs --rootDir ../../ --runInBand --detectOpenHandles", "test:ci": "npm run test -- --ci --coverage --coverageDirectory packages/webhooks/coverage", "tunnel": "ngrok http 8080" }, @@ -57,7 +59,8 @@ "@jackdbd/hapi-telegram-plugin": "2.0.0-canary.1", "@jackdbd/notifications": "1.0.0", "@jackdbd/secret-manager-utils": "1.0.0", - "@jackdbd/telegram-text-messages": "1.0.0", + "@jackdbd/stripe-utils": "^1.1.0", + "@jackdbd/telegram-text-messages": "^1.1.0", "@jackdbd/utils": "^1.2.0", "debug": "4.3.4", "exiting": "6.1.0", @@ -65,6 +68,7 @@ "hapi-dev-errors": "4.0.0", "hapi-swagger": "14.5.1", "ipaddr.js": "2.0.1", - "joi": "17.6.0" + "joi": "17.6.0", + "stripe": "^10.8.0" } } diff --git a/packages/webhooks/src/app.ts b/packages/webhooks/src/app.ts index 27ddf6d6..e7339595 100644 --- a/packages/webhooks/src/app.ts +++ b/packages/webhooks/src/app.ts @@ -13,25 +13,30 @@ import Vision from '@hapi/vision' import HapiSwagger from 'hapi-swagger' import { alertsPost } from './routes/alerts/post.js' import { npmPost } from './routes/npm/post.js' +import { stripeGet } from './routes/stripe/get.js' +import { stripePost } from './routes/stripe/post.js' import { webPageTestPingbackGet } from './routes/webpagetest/get.js' import { healthcheck } from '@jackdbd/hapi-healthcheck-plugin' import { errorReporting, // firestore as firestoreClient, googleSheets, - secretManager + secretManager, + stripe as stripeClient } from './clients/index.js' import { APP_NAME } from './constants.js' import { environment as environment_schema, app_config as app_config_schema, google_sheets_config as google_sheets_config_schema, + stripe_webhooks_config as stripe_webhooks_config_schema, telegram_credentials as telegram_credentials_schema } from './schemas.js' import { throwIfNotOnNodeJs, jsonFromEnvVarOrSecret, - jsonFromSecret + jsonFromSecret, + stringFromEnvVarOrSecret } from './utils.js' export const app = async () => { @@ -68,12 +73,12 @@ export const app = async () => { google_sheets_secret_version, service_account_webperf_audit_secret_name, service_account_webperf_audit_secret_version, - // stripe_api_key_environment_variable, - // stripe_api_key_secret_name, - // stripe_api_key_secret_version, - // stripe_webhooks_environment_variable, - // stripe_webhooks_secret_name, - // stripe_webhooks_secret_version, + stripe_api_key_environment_variable, + stripe_api_key_secret_name, + stripe_api_key_secret_version, + stripe_webhooks_environment_variable, + stripe_webhooks_secret_name, + stripe_webhooks_secret_version, telegram_environment_variable, telegram_secret_name, telegram_secret_version @@ -259,6 +264,53 @@ export const app = async () => { }) ) + const { value: stripe_api_key, message: stripe_api_key_message } = + await stringFromEnvVarOrSecret({ + description: 'Stripe API key', + environment_variable: stripe_api_key_environment_variable, + gcp_project_id, + secret_manager, + secret_name: stripe_api_key_secret_name, + secret_version: stripe_api_key_secret_version + }) + + server.log(['debug', 'configuration', 'stripe', 'api-key'], { + message: stripe_api_key_message + }) + + const { value: stripe_webhooks, message: stripe_webhooks_message } = + await jsonFromEnvVarOrSecret({ + description: 'Stripe webhooks', + environment_variable: stripe_webhooks_environment_variable, + gcp_project_id, + schema: stripe_webhooks_config_schema, + secret_manager, + secret_name: stripe_webhooks_secret_name, + secret_version: stripe_webhooks_secret_version + }) + const { + endpoint: stripe_webhook_endpoint, + signing_secret: stripe_webhook_signing_secret + } = stripe_webhooks + + server.log(['debug', 'configuration', 'stripe', 'webhook'], { + message: stripe_webhooks_message + }) + + const stripe = stripeClient({ api_key: stripe_api_key }) + + server.route(stripeGet({ stripe, webhook_endpoint: stripe_webhook_endpoint })) + + server.route( + stripePost({ + stripe, + telegram_chat_id, + telegram_token, + webhook_endpoint: stripe_webhook_endpoint, + webhook_secret: stripe_webhook_signing_secret + }) + ) + // The following note was for hapi-ip-whitelist-plugin, that I no longer use. // allow 127.0.0.1 in development, so the ngrok public URL can be set as // WebPageTest pingback. diff --git a/packages/webhooks/src/clients/index.ts b/packages/webhooks/src/clients/index.ts index 764abe63..6b5d31be 100644 --- a/packages/webhooks/src/clients/index.ts +++ b/packages/webhooks/src/clients/index.ts @@ -5,3 +5,5 @@ export { firestore } from './firestore.js' export { googleSheets } from './google-sheets.js' export { secretManager } from './secret-manager.js' + +export { stripe } from './stripe.js' diff --git a/packages/webhooks/src/clients/stripe.ts b/packages/webhooks/src/clients/stripe.ts new file mode 100644 index 00000000..4086596f --- /dev/null +++ b/packages/webhooks/src/clients/stripe.ts @@ -0,0 +1,28 @@ +import Stripe from 'stripe' +import { makeLog } from '@jackdbd/tags-logger' +import { APP_NAME } from '../constants.js' + +const log = makeLog({ + namespace: process.env.K_SERVICE ? undefined : `${APP_NAME}/clients/stripe` +}) + +interface Config { + api_key: string +} + +export const stripe = ({ api_key }: Config) => { + const stripe_config = { + // https://stripe.com/docs/api/versioning + apiVersion: '2022-08-01' as Stripe.LatestApiVersion, + maxNetworkRetries: 3, + timeout: 10000 // ms + } + + log({ + message: `initialize Stripe with this configuration (see JSON payload)`, + tags: ['debug', 'client', 'stripe'], + stripe_config + }) + + return new Stripe(api_key, stripe_config) +} diff --git a/packages/webhooks/src/constants.ts b/packages/webhooks/src/constants.ts index 2e401ffa..08978cad 100644 --- a/packages/webhooks/src/constants.ts +++ b/packages/webhooks/src/constants.ts @@ -1 +1,20 @@ export const APP_NAME = 'webhooks' + +// https://emojipedia.org/ +export enum Emoji { + CreditCard = '💳', + Customer = '👤', + Error = '🚨', + Failure = '❌', + Inspect = '🔍', + Invalid = '❌', + MoneyBag = '💰', + Notification = '💬', + ShoppingBags = '🛍️', + Ok = '✅', + Sparkles = '✨', + Success = '✅', + Timer = '⏱️', + User = '👤', + Warning = '⚠️' +} diff --git a/packages/webhooks/src/interfaces.ts b/packages/webhooks/src/interfaces.ts index d9596682..ae8cd378 100644 --- a/packages/webhooks/src/interfaces.ts +++ b/packages/webhooks/src/interfaces.ts @@ -8,19 +8,24 @@ export interface AppConfig { service_account_webperf_audit_secret_name: string service_account_webperf_audit_secret_version: string - // stripe_api_key_environment_variable: string - // stripe_api_key_secret_name: string - // stripe_api_key_secret_version: string + stripe_api_key_environment_variable: string + stripe_api_key_secret_name: string + stripe_api_key_secret_version: string - // stripe_webhooks_environment_variable: string - // stripe_webhooks_secret_name: string - // stripe_webhooks_secret_version: string + stripe_webhooks_environment_variable: string + stripe_webhooks_secret_name: string + stripe_webhooks_secret_version: string telegram_environment_variable: string telegram_secret_name: string telegram_secret_version: string } +export interface StripeWebhooksConfig { + endpoint: string + signing_secret: string +} + export interface TelegramCredentials { chat_id: string token: string diff --git a/packages/webhooks/src/routes/stripe/get.ts b/packages/webhooks/src/routes/stripe/get.ts new file mode 100644 index 00000000..9685fe43 --- /dev/null +++ b/packages/webhooks/src/routes/stripe/get.ts @@ -0,0 +1,32 @@ +import type Stripe from 'stripe' +import type Hapi from '@hapi/hapi' +import { enabledEventsForWebhookEndpoint } from '@jackdbd/stripe-utils/webhooks' + +interface Config { + stripe: Stripe + webhook_endpoint: string +} + +export const stripeGet = ({ stripe, webhook_endpoint }: Config) => { + const config = { method: 'GET', path: '/stripe' } + + return { + method: config.method, + path: config.path, + handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => { + const enabled_events = await enabledEventsForWebhookEndpoint({ + stripe, + url: webhook_endpoint + }) + + request.log(['debug', 'stripe', 'webhook'], { + message: `${enabled_events.length} webhook event/s allowed to POST to ${webhook_endpoint}` + }) + + return { + enabled_events, + message: `This Stripe account is configured to POST ${enabled_events.length} webhook event/s to ${webhook_endpoint}` + } + } + } +} diff --git a/packages/webhooks/src/routes/stripe/post.ts b/packages/webhooks/src/routes/stripe/post.ts new file mode 100644 index 00000000..9f388587 --- /dev/null +++ b/packages/webhooks/src/routes/stripe/post.ts @@ -0,0 +1,283 @@ +import type Stripe from 'stripe' +import Boom from '@hapi/boom' +import type Hapi from '@hapi/hapi' +import { sendTelegramMessage } from '@jackdbd/notifications' +import { enabledEventsForWebhookEndpoint } from '@jackdbd/stripe-utils/webhooks' +import { anchor } from '@jackdbd/telegram-text-messages/utils' +import { Emoji } from '../../constants.js' + +export const eventIsIgnoredMessage = (event_type: string, url: string) => + `This Stripe account is not configured to POST ${event_type} events to this endpoint [${url}] so the event is ignored.` + +export const incorrectRequestBody = + 'Incorrect request body. Received a request body that does not look like a Stripe event.' + +interface Config { + stripe: Stripe + telegram_chat_id: string + telegram_token: string + webhook_endpoint: string + webhook_secret: string +} + +const TAGS = ['api', 'handler', 'stripe', 'webhook'] + +interface SendConfig { + chat_id: string + token: string + text: string +} + +const trySendingTelegramMessage = async ({ + chat_id, + token, + text +}: SendConfig) => { + try { + const { message, delivered } = await sendTelegramMessage({ + chat_id, + token, + text + }) + return { value: { delivered, message } } + } catch (err: any) { + return { error: err as Error } + } +} + +interface TextDetailsConfig { + event: Stripe.Event + resource_type: string + resource_id: string +} + +const textDetails = ({ + event, + resource_id, + resource_type +}: TextDetailsConfig) => { + const href = event.livemode + ? `https://dashboard.stripe.com/${resource_type}/${resource_id}` + : `https://dashboard.stripe.com/test/${resource_type}/${resource_id}` + + const date_str = new Date(event.created * 1000).toUTCString() + + return [ + `API version: ${event.api_version}`, + `Created: ${event.created} (${date_str})`, + anchor({ href, text: resource_id }) + ] +} + +export const stripePost = ({ + stripe, + telegram_chat_id, + telegram_token, + webhook_endpoint, + webhook_secret +}: Config): Hapi.ServerRoute => { + return { + handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => { + const header = request.headers['stripe-signature'] + + if (!header) { + const message = `incoming request lacks required header` + // this is a client error, not an application error. So we log it as a warning + request.log(['warning', 'stripe', 'webhook'], { + message, + required_header: 'stripe-signature', + headers: request.headers + }) + + // we log the message but we don't respond to the client, so an eventual + // attacker only sees bad request, and does learn that we require the + // 'stripe-signature' header to be present. + throw Boom.badRequest() + } + + const payload = request.payload.toString() + + let event: Stripe.Event + if (process.env.BYPASS_WEBHOOK_VALIDATION) { + // hopefully this is clear enough + const warnings = [ + `Environment variable BYPASS_WEBHOOK_VALIDATION was set`, + `This should be used ONLY in development`, + `NEVER set BYPASS_WEBHOOK_VALIDATION in production`, + `ALWAYS validate incoming webhook events in production!` + ] + request.log(['warning', 'webhook', 'security'], { + message: warnings.join('. ') + }) + event = JSON.parse(payload) as Stripe.Event + } else { + try { + event = stripe.webhooks.constructEvent( + payload, + header, + webhook_secret + ) + } catch (err: any) { + const message = `could not construct event from request payload: ${err.message}` + + // this is a client error, not an application error. So we log it as a warning + request.log(['warning', 'stripe', 'webhook'], { message }) + throw Boom.badRequest() + } + } + + const enabled_events = await enabledEventsForWebhookEndpoint({ + stripe, + url: webhook_endpoint + }) + + if (!enabled_events.includes(event.type)) { + const message = eventIsIgnoredMessage(event.type, webhook_endpoint) + + // this is a client error, not an application error. So we log it as a warning + request.log(['warning', 'stripe', 'webhook'], { + message, + event_type: event.type, + enabled_events + }) + // TODO: also, send warning to Telegram? + throw Boom.badRequest() + } + + switch (event.type) { + case 'customer.created': + case 'customer.deleted': + case 'customer.updated': { + const resource_id = (event.data.object as any).id as string + + const text = [ + `${Emoji.Customer} Stripe webhook event ${event.type}`, + ...textDetails({ resource_type: 'customers', resource_id, event }) + ].join('\n\n') + + const { error, value } = await trySendingTelegramMessage({ + chat_id: telegram_chat_id, + token: telegram_token, + text + }) + if (error) { + request.log([...TAGS, 'telegram', 'error'], { + message: `could not send Telegram message`, + original_error_message: error.message + }) + } else { + const { delivered, message } = value + if (delivered) { + request.log([...TAGS, 'telegram'], { message, delivered }) + } else { + request.log([...TAGS, 'telegram', 'warning'], { message }) + } + } + + return { + message: `processed "${event.type}" webhook event (coming from Stripe)` + } + } + + case 'payment_intent.succeeded': { + const resource_id = (event.data.object as any).id as string + + const text = [ + `${Emoji.MoneyBag} Stripe webhook event ${event.type}`, + ...textDetails({ resource_type: 'payments', resource_id, event }) + ].join('\n\n') + + const { error, value } = await trySendingTelegramMessage({ + chat_id: telegram_chat_id, + token: telegram_token, + text + }) + if (error) { + request.log([...TAGS, 'telegram', 'error'], { + message: `could not send Telegram message`, + original_error_message: error.message + }) + } else { + const { delivered, message } = value + if (delivered) { + request.log([...TAGS, 'telegram'], { message, delivered }) + } else { + request.log([...TAGS, 'telegram', 'warning'], { message }) + } + } + + return { + message: `processed "${event.type}" webhook event (coming from Stripe)` + } + } + + case 'product.created': + case 'product.deleted': + case 'product.updated': { + const resource_id = (event.data.object as any).id as string + + const text = [ + `${Emoji.ShoppingBags} Stripe webhook event ${event.type}`, + ...textDetails({ resource_type: 'customers', resource_id, event }) + ].join('\n\n') + + const { error, value } = await trySendingTelegramMessage({ + chat_id: telegram_chat_id, + token: telegram_token, + text + }) + if (error) { + request.log([...TAGS, 'telegram', 'error'], { + message: `could not send Telegram message`, + original_error_message: error.message + }) + } else { + const { delivered, message } = value + if (delivered) { + request.log([...TAGS, 'telegram'], { message, delivered }) + } else { + request.log([...TAGS, 'telegram', 'warning'], { message }) + } + } + + return { + message: `processed "${event.type}" webhook event (coming from Stripe)` + } + } + + default: { + const event_type = (request.payload as any).type + + const message = event_type + ? `event '${event_type}' not handled (this Stripe account can POST it, but there isn't a handler in this application)` + : incorrectRequestBody + + request.log(['warning', 'stripe', 'webhook'], { + message, + params: request.params, + payload: request.payload + }) + + throw Boom.badRequest(message) + } + } + }, + + method: 'POST', + + options: { + auth: false, + description: 'webhook target for Stripe webhook events', + notes: + 'This route catches the webhook events sent by Stripe when something happens on the Stripe account', + payload: { parse: false, output: 'data' }, + tags: TAGS, + validate: { + options: { allowUnknown: true }, + query: false + } + }, + + path: '/stripe' + } +} diff --git a/packages/webhooks/src/schemas.ts b/packages/webhooks/src/schemas.ts index 7ec684d0..082cc6ae 100644 --- a/packages/webhooks/src/schemas.ts +++ b/packages/webhooks/src/schemas.ts @@ -1,5 +1,9 @@ import Joi from 'joi' -import type { AppConfig, TelegramCredentials } from './interfaces.js' +import type { + AppConfig, + StripeWebhooksConfig, + TelegramCredentials +} from './interfaces.js' export const app_business_name = Joi.string().min(1) @@ -27,19 +31,28 @@ export const app_config = Joi.object().keys({ service_account_webperf_audit_secret_version: secret_manager_secret_version.required(), - // stripe_api_key_environment_variable: environment_variable_name.required(), - // stripe_api_key_secret_name: secret_manager_secret_name.required(), - // stripe_api_key_secret_version: secret_manager_secret_version.required(), + stripe_api_key_environment_variable: environment_variable_name.required(), + stripe_api_key_secret_name: secret_manager_secret_name.required(), + stripe_api_key_secret_version: secret_manager_secret_version.required(), - // stripe_webhooks_environment_variable: environment_variable_name.required(), - // stripe_webhooks_secret_name: secret_manager_secret_name.required(), - // stripe_webhooks_secret_version: secret_manager_secret_version.required(), + stripe_webhooks_environment_variable: environment_variable_name.required(), + stripe_webhooks_secret_name: secret_manager_secret_name.required(), + stripe_webhooks_secret_version: secret_manager_secret_version.required(), telegram_environment_variable: environment_variable_name.required(), telegram_secret_name: secret_manager_secret_name.required(), telegram_secret_version: secret_manager_secret_version.required() }) +const stripe_webhooks_endpoint = Joi.string().min(1) + +const stripe_webhooks_signing_secret = Joi.string().min(1) + +export const stripe_webhooks_config = Joi.object().keys({ + endpoint: stripe_webhooks_endpoint.required(), + signing_secret: stripe_webhooks_signing_secret.required() +}) + interface NameToWorksheetTabIdMapping { [k: string]: string } From bdbe8dd853b8962c00c6267fd32e40ffc1aee81b Mon Sep 17 00:00:00 2001 From: Giacomo Debidda Date: Mon, 12 Sep 2022 00:06:04 +0200 Subject: [PATCH 2/6] fix(webhooks): disable webpage preview in Telegram texts --- packages/webhooks/src/routes/stripe/post.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/webhooks/src/routes/stripe/post.ts b/packages/webhooks/src/routes/stripe/post.ts index 9f388587..76fd24d1 100644 --- a/packages/webhooks/src/routes/stripe/post.ts +++ b/packages/webhooks/src/routes/stripe/post.ts @@ -34,11 +34,14 @@ const trySendingTelegramMessage = async ({ text }: SendConfig) => { try { - const { message, delivered } = await sendTelegramMessage({ - chat_id, - token, - text - }) + const { message, delivered } = await sendTelegramMessage( + { + chat_id, + token, + text + }, + { disable_web_page_preview: true } + ) return { value: { delivered, message } } } catch (err: any) { return { error: err as Error } From 1a1c63ae2aafc8ebf224da742a9eba8fb936eada Mon Sep 17 00:00:00 2001 From: Giacomo Debidda Date: Mon, 12 Sep 2022 16:08:04 +0200 Subject: [PATCH 3/6] chore: update IAM policy of the project --- config/api-extractor.json | 2 +- iam-policies/project.yaml | 27 +++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/config/api-extractor.json b/config/api-extractor.json index b5e61af4..31a93a24 100644 --- a/config/api-extractor.json +++ b/config/api-extractor.json @@ -33,7 +33,7 @@ /** * Configures how the doc model file (*.api.json) will be generated. - + * * I think the generated JSON file SHOULD NOT be tracked by Git. * https://api-extractor.com/pages/configs/api-extractor_json/#doc-model-section */ diff --git a/iam-policies/project.yaml b/iam-policies/project.yaml index cadba57c..6db64d7e 100644 --- a/iam-policies/project.yaml +++ b/iam-policies/project.yaml @@ -7,6 +7,9 @@ bindings: - members: - serviceAccount:sa-artifact-registry-writer@prj-kitchen-sink.iam.gserviceaccount.com role: roles/artifactregistry.writer +- members: + - serviceAccount:sa-workflows-runner@prj-kitchen-sink.iam.gserviceaccount.com + role: roles/bigquery.dataEditor - members: - serviceAccount:service-1051247446620@gcp-sa-bigquerydatatransfer.iam.gserviceaccount.com role: roles/bigquerydatatransfer.serviceAgent @@ -66,30 +69,46 @@ bindings: - members: - serviceAccount:service-1051247446620@containerregistry.iam.gserviceaccount.com role: roles/containerregistry.ServiceAgent +- members: + - serviceAccount:service-1051247446620@dataflow-service-producer-prod.iam.gserviceaccount.com + role: roles/dataflow.serviceAgent +- members: + - serviceAccount:sa-dataflow-worker@prj-kitchen-sink.iam.gserviceaccount.com + role: roles/dataflow.worker +- members: + - serviceAccount:service-1051247446620@gcp-sa-datapipelines.iam.gserviceaccount.com + role: roles/datapipelines.serviceAgent - members: - serviceAccount:sa-firestore-user-test@prj-kitchen-sink.iam.gserviceaccount.com role: roles/datastore.user - members: - serviceAccount:sa-firestore-viewer-test@prj-kitchen-sink.iam.gserviceaccount.com role: roles/datastore.viewer +- members: + - serviceAccount:1051247446620@cloudservices.gserviceaccount.com + role: roles/editor - members: - serviceAccount:sa-matsuri-demo-app@prj-kitchen-sink.iam.gserviceaccount.com - serviceAccount:sa-telegram-bot@prj-kitchen-sink.iam.gserviceaccount.com - serviceAccount:sa-wasm-news@prj-kitchen-sink.iam.gserviceaccount.com - serviceAccount:sa-webhooks@prj-kitchen-sink.iam.gserviceaccount.com role: roles/errorreporting.writer +- members: + - serviceAccount:service-1051247446620@gcp-sa-eventarc.iam.gserviceaccount.com + role: roles/eventarc.serviceAgent - members: - serviceAccount:service-1051247446620@gcp-sa-firestore.iam.gserviceaccount.com role: roles/firestore.serviceAgent - members: - - serviceAccount:sa-artifact-registry-writer@prj-kitchen-sink.iam.gserviceaccount.com - group:developers@giacomodebidda.com + - serviceAccount:sa-artifact-registry-writer@prj-kitchen-sink.iam.gserviceaccount.com + - serviceAccount:service-1051247446620@gcp-sa-pubsub.iam.gserviceaccount.com role: roles/iam.serviceAccountTokenCreator - members: + - group:developers@giacomodebidda.com - serviceAccount:1051247446620@cloudbuild.gserviceaccount.com - serviceAccount:sa-notifier@prj-kitchen-sink.iam.gserviceaccount.com - serviceAccount:sa-workflows-runner@prj-kitchen-sink.iam.gserviceaccount.com - - group:developers@giacomodebidda.com role: roles/iam.serviceAccountUser - members: - serviceAccount:sa-dash-earthquakes@prj-kitchen-sink.iam.gserviceaccount.com @@ -138,7 +157,7 @@ bindings: role: roles/storage.admin - members: - serviceAccount:sa-storage-uploader@prj-kitchen-sink.iam.gserviceaccount.com - role: roles/storage.objectCreator + role: roles/storage.objectAdmin - members: - serviceAccount:sa-compute-engine@prj-kitchen-sink.iam.gserviceaccount.com role: roles/storage.objectViewer @@ -156,5 +175,5 @@ bindings: - members: - serviceAccount:sa-workflows-runner@prj-kitchen-sink.iam.gserviceaccount.com role: roles/workflows.viewer -etag: BwXm9qe8mgc= +etag: BwXoe2STVgM= version: 1 \ No newline at end of file From 62fd086be09c7391cef12c2ebe28abd4668a6a5b Mon Sep 17 00:00:00 2001 From: Giacomo Debidda Date: Sun, 11 Sep 2022 19:42:56 +0200 Subject: [PATCH 4/6] feat: add example of data serverless pipeline with GCP workflows and Eventarc --- .../bigquery-schemas/weather_data_table.json | 49 +++++++++++++++ docs/service-accounts.md | 9 +++ iam-policies/project.yaml | 6 +- workflows/README.md | 29 +++++++++ workflows/serverless-data-pipeline.yaml | 59 +++++++++++++++++++ 5 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 assets/bigquery-schemas/weather_data_table.json create mode 100644 workflows/serverless-data-pipeline.yaml diff --git a/assets/bigquery-schemas/weather_data_table.json b/assets/bigquery-schemas/weather_data_table.json new file mode 100644 index 00000000..55b1d7a3 --- /dev/null +++ b/assets/bigquery-schemas/weather_data_table.json @@ -0,0 +1,49 @@ +[ + { + "name": "sensorId", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "timecollected", + "description": "UTC date string of when the sensor reading was collected", + "type": "TIMESTAMP", + "mode": "NULLABLE" + }, + { + "name": "zipcode", + "type": "INTEGER", + "mode": "NULLABLE" + }, + { + "name": "latitude", + "type": "FLOAT", + "mode": "NULLABLE" + }, + { + "name": "longitude", + "type": "FLOAT", + "mode": "NULLABLE" + }, + { + "name": "temperature", + "description": "temperature in Farhenheit", + "type": "FLOAT", + "mode": "NULLABLE" + }, + { + "name": "humidity", + "type": "FLOAT", + "mode": "NULLABLE" + }, + { + "name": "dewpoint", + "type": "FLOAT", + "mode": "NULLABLE" + }, + { + "name": "pressure", + "type": "FLOAT", + "mode": "NULLABLE" + } +] diff --git a/docs/service-accounts.md b/docs/service-accounts.md index 89a2a715..131512e0 100644 --- a/docs/service-accounts.md +++ b/docs/service-accounts.md @@ -33,6 +33,15 @@ gcloud iam service-accounts create sa-dash-earthquakes \ --display-name "dash-earthquakes SA" ``` +### sa-dataflow-worker + +Create a service account to run [Dataflow](https://cloud.google.com/dataflow/docs) jobs: + +```sh +gcloud iam service-accounts create sa-dataflow-worker \ + --display-name "SA Dataflow worker" +``` + ### sa-firestore-user-test Service account that I use in [firestore-utils](../packages/firestore-utils/README.md) tests. diff --git a/iam-policies/project.yaml b/iam-policies/project.yaml index 6db64d7e..15e2d54d 100644 --- a/iam-policies/project.yaml +++ b/iam-policies/project.yaml @@ -26,7 +26,7 @@ bindings: - members: - serviceAccount:sa-telegram-bot@prj-kitchen-sink.iam.gserviceaccount.com - serviceAccount:sa-wasm-news@prj-kitchen-sink.iam.gserviceaccount.com - - serviceAccount:sa-webhooks@prj-kitchen-sink.iam.gserviceaccount.com + - serviceAccount:sa-webhooks@prj-kitchen-sink.iam.gserviceaccount.com role: roles/clouddebugger.agent - members: - serviceAccount:1051247446620@cloudbuild.gserviceaccount.com @@ -146,9 +146,9 @@ bindings: - serviceAccount:1051247446620@cloudbuild.gserviceaccount.com - serviceAccount:sa-matsuri-demo-app@prj-kitchen-sink.iam.gserviceaccount.com - serviceAccount:sa-notifier@prj-kitchen-sink.iam.gserviceaccount.com - - serviceAccount:sa-telegram-bot@prj-kitchen-sink.iam.gserviceaccount.com + - serviceAccount:sa-telegram-bot@prj-kitchen-sink.iam.gserviceaccount.com - serviceAccount:sa-wasm-news@prj-kitchen-sink.iam.gserviceaccount.com - - serviceAccount:sa-webhooks@prj-kitchen-sink.iam.gserviceaccount.com + - serviceAccount:sa-webhooks@prj-kitchen-sink.iam.gserviceaccount.com - serviceAccount:sa-workflows-runner@prj-kitchen-sink.iam.gserviceaccount.com role: roles/secretmanager.secretAccessor - members: diff --git a/workflows/README.md b/workflows/README.md index 82acdef8..ba7881a8 100644 --- a/workflows/README.md +++ b/workflows/README.md @@ -40,6 +40,18 @@ gcloud workflows deploy lead-generation \ --labels customer=$CUSTOMER,environment=$ENVIRONMENT,resource=workflow ``` +### Serverless data pipeline + +```sh +gcloud workflows deploy serverless-data-pipeline \ + --project $GCP_PROJECT_ID \ + --location $WORKFLOW_LOCATION \ + --description "Serverless data pipeline (variation of the codelab 'Building a Serverless Data Pipeline: IoT to Analytics')" \ + --source workflows/serverless-data-pipeline.yaml \ + --service-account $SA_WORKFLOWS_RUNNER \ + --labels customer=$CUSTOMER,environment=$ENVIRONMENT,resource=workflow +``` + ### Web performance audit ```sh @@ -156,3 +168,20 @@ You can find the list of available [Compute Engine images](https://cloud.google. ```sh gcloud compute images list ``` + +### Publish message to PubSub topic + +```sh +gcloud pubsub topics publish weather-data \ + --message '{ + "sensorId": "sensor-xyz", + "zipcode": 55049, + "temperature": 98.5, + "timecollected": "2022-08-15 15:29:35", + "latitude": 43.8657, + "longitude": 10.2513, + "humidity": 1.23, + "dewpoint": 4.56, + "pressure": 7.89 + }' +``` \ No newline at end of file diff --git a/workflows/serverless-data-pipeline.yaml b/workflows/serverless-data-pipeline.yaml new file mode 100644 index 00000000..0b3f583f --- /dev/null +++ b/workflows/serverless-data-pipeline.yaml @@ -0,0 +1,59 @@ +# https://codelabs.developers.google.com/codelabs/iot-data-pipeline +main: + params: [args] + steps: + + - assign_variables: + assign: + - project_id: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")} + - dataset_id: weather_data + - table_id: weather_data_table + - base64_decoded: "${base64.decode(args.data.data)}" + - json_data: "${json.decode(base64_decoded)}" + + # - log_stuff: + # call: sys.log + # args: + # data: { + # "json_data": "${json_data}", + # "dataset_id": "${dataset_id}", + # "table_id": "${table_id}" + # } + # severity: "WARNING" + + # - list_table_data: + # # https://cloud.google.com/workflows/docs/reference/googleapis/bigquery/v2/tabledata/list + # call: googleapis.bigquery.v2.tabledata.list + # args: + # projectId: ${project_id} + # datasetId: ${dataset_id} + # tableId: ${table_id} + # maxResults: 30 + # result: tabledata_list_result + + - insert_all_table_data: + # https://cloud.google.com/workflows/docs/reference/googleapis/bigquery/v2/tabledata/insertAll + call: googleapis.bigquery.v2.tabledata.insertAll + args: + projectId: ${project_id} + datasetId: ${dataset_id} + tableId: ${table_id} + # https://cloud.google.com/workflows/docs/reference/googleapis/bigquery/v2/Overview#TableDataInsertAllRequest + body: { + "rows": [ + {"json": "${json_data}"} + ] + } + # body: { + # "rows": [ + # {"json": {"sensorId": "sensor-abc", "zipcode": 90210, "temperature": 80.5}}, + # {"json": {"sensorId": "sensor-def", "zipcode": 10048, "temperature": 101.75}} + # ] + # } + result: tabledata_insertAll_result + + - respond_to_caller: + return: { + "tabledata_insertAll_result": "${tabledata_insertAll_result}" + # "tabledata_list_result": "${tabledata_list_result}" + } \ No newline at end of file From 93a6671467d3cd1e5982fb458c76cffd159cecd1 Mon Sep 17 00:00:00 2001 From: Giacomo Debidda Date: Fri, 9 Sep 2022 18:56:12 +0200 Subject: [PATCH 5/6] feat(pkg-cloud-run-job-example): add package --- assets/templates/application/package.json | 2 +- package-lock.json | 979 ++++++++++++++++++ package.json | 1 + packages/pkg-cloud-run-job-example/Dockerfile | 72 ++ packages/pkg-cloud-run-job-example/README.md | 74 ++ .../__tests__/utils.test.js | 11 + .../pkg-cloud-run-job-example/cloudbuild.yaml | 73 ++ .../pkg-cloud-run-job-example/jest.config.cjs | 27 + .../pkg-cloud-run-job-example/package.json | 49 + .../src/constants.js | 3 + .../pkg-cloud-run-job-example/src/index.js | 33 + .../pkg-cloud-run-job-example/src/utils.js | 34 + 12 files changed, 1357 insertions(+), 1 deletion(-) create mode 100644 packages/pkg-cloud-run-job-example/Dockerfile create mode 100644 packages/pkg-cloud-run-job-example/README.md create mode 100644 packages/pkg-cloud-run-job-example/__tests__/utils.test.js create mode 100644 packages/pkg-cloud-run-job-example/cloudbuild.yaml create mode 100644 packages/pkg-cloud-run-job-example/jest.config.cjs create mode 100644 packages/pkg-cloud-run-job-example/package.json create mode 100644 packages/pkg-cloud-run-job-example/src/constants.js create mode 100644 packages/pkg-cloud-run-job-example/src/index.js create mode 100644 packages/pkg-cloud-run-job-example/src/utils.js diff --git a/assets/templates/application/package.json b/assets/templates/application/package.json index a5ca4cca..69394536 100644 --- a/assets/templates/application/package.json +++ b/assets/templates/application/package.json @@ -35,7 +35,7 @@ "container:start:test": "docker run -it -p 8080:8080 --env \"SA_JSON_KEY=$(cat ../../secrets/sa-webperf-PACKAGE_NAME.json)\" --env NODE_ENV=test --env DEBUG=utils/*,PACKAGE_NAME/* PACKAGE_NAME:latest", "predeploy": "run-s clean format lint build auth-artifact-registry", "deploy": "gcloud beta builds submit ./dist --config cloudbuild.yaml --project $GCP_PROJECT_ID --async", - "format": "prettier --config ../../config/prettier.cjs --write {__tests__,src}/**/*.{js,mjs,ts}", + "format": "../../scripts/format.mjs", "lint": "eslint --config ../../config/eslint.cjs", "nuke": "npm run clean && rimraf node_modules 'package-lock.json'", "precommit": "lint-staged --config ../../config/lint-staged.cjs", diff --git a/package-lock.json b/package-lock.json index 021ea90b..f37c8345 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "npm-check-updates": "^13.1.3", "npm-run-all": "^4.1.5", "nunjucks": "^3.2.3", + "pkg": "^5.8.0", "pkg-size": "^2.4.0", "prettier": "^2.6.2", "rimraf": "^3.0.2", @@ -2434,6 +2435,10 @@ "resolved": "packages/notifications", "link": true }, + "node_modules/@jackdbd/pkg-hello-world": { + "resolved": "packages/pkg-hello-world", + "link": true + }, "node_modules/@jackdbd/plausible-client": { "resolved": "packages/plausible-client", "link": true @@ -6355,6 +6360,15 @@ "node": ">= 0.12.0" } }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/coffeescript": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.7.0.tgz", @@ -13662,6 +13676,30 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "once": "^1.4.0", + "readable-stream": "^3.6.0" + } + }, "node_modules/mustache": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/mustache/-/mustache-3.2.1.tgz", @@ -17072,6 +17110,15 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nunjucks": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", @@ -17674,6 +17721,39 @@ "node": ">= 6" } }, + "node_modules/pkg": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.8.0.tgz", + "integrity": "sha512-8h9PUDYFi+LOMLbIyGRdP21g08mAtHidSpofSrf8LWhxUWGHymaRzcopEGiynB5EhQmZUKM6PQ9kCImV2TpdjQ==", + "dev": true, + "dependencies": { + "@babel/generator": "7.18.2", + "@babel/parser": "7.18.4", + "@babel/types": "7.18.4", + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "globby": "^11.1.0", + "into-stream": "^6.0.0", + "is-core-module": "2.9.0", + "minimist": "^1.2.6", + "multistream": "^4.1.0", + "pkg-fetch": "3.4.2", + "prebuild-install": "6.1.4", + "resolve": "^1.22.0", + "stream-meter": "^1.0.4" + }, + "bin": { + "pkg": "lib-es5/bin.js" + }, + "peerDependencies": { + "node-notifier": ">=9.0.1" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, "node_modules/pkg-conf": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", @@ -17818,6 +17898,87 @@ "node": ">=8" } }, + "node_modules/pkg-fetch": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz", + "integrity": "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.6", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^2.1.1", + "yargs": "^16.2.0" + }, + "bin": { + "pkg-fetch": "lib-es5/bin.js" + } + }, + "node_modules/pkg-fetch/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/pkg-fetch/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pkg-fetch/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-fetch/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-fetch/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/pkg-size": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/pkg-size/-/pkg-size-2.4.0.tgz", @@ -17830,6 +17991,345 @@ "url": "https://github.com/privatenumber/pkg-size?sponsor=1" } }, + "node_modules/pkg/node_modules/@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/pkg/node_modules/@babel/parser": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", + "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pkg/node_modules/@babel/types": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/pkg/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pkg/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg/node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "node_modules/pkg/node_modules/are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/pkg/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pkg/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pkg/node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/pkg/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg/node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/pkg/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg/node_modules/node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "dev": true, + "dependencies": { + "semver": "^5.4.1" + } + }, + "node_modules/pkg/node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/pkg/node_modules/prebuild-install": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.21.0", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/pkg/node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/pkg/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/pkg/node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/pkg/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/pkg/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -19949,6 +20449,45 @@ "stubs": "^3.0.0" } }, + "node_modules/stream-meter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", + "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", + "dev": true, + "dependencies": { + "readable-stream": "^2.1.4" + } + }, + "node_modules/stream-meter/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stream-meter/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/stream-meter/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", @@ -22015,6 +22554,18 @@ "debug": ">=4.0.0" } }, + "packages/pkg-hello-world": { + "name": "@jackdbd/pkg-hello-world", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@google-cloud/functions-framework": "^3.1.2" + }, + "devDependencies": {}, + "engines": { + "node": ">=16.15.0" + } + }, "packages/plausible-client": { "name": "@jackdbd/plausible-client", "version": "1.0.2", @@ -25873,6 +26424,12 @@ "phin": "^3.6.1" } }, + "@jackdbd/pkg-hello-world": { + "version": "file:packages/pkg-hello-world", + "requires": { + "@google-cloud/functions-framework": "^3.1.2" + } + }, "@jackdbd/plausible-client": { "version": "file:packages/plausible-client", "requires": { @@ -30205,6 +30762,12 @@ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true + }, "coffeescript": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.7.0.tgz", @@ -35586,6 +36149,16 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "dev": true, + "requires": { + "once": "^1.4.0", + "readable-stream": "^3.6.0" + } + }, "mustache": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/mustache/-/mustache-3.2.1.tgz", @@ -38029,6 +38602,12 @@ "boolbase": "^1.0.0" } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true + }, "nunjucks": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", @@ -38447,6 +39026,297 @@ "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true }, + "pkg": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.8.0.tgz", + "integrity": "sha512-8h9PUDYFi+LOMLbIyGRdP21g08mAtHidSpofSrf8LWhxUWGHymaRzcopEGiynB5EhQmZUKM6PQ9kCImV2TpdjQ==", + "dev": true, + "requires": { + "@babel/generator": "7.18.2", + "@babel/parser": "7.18.4", + "@babel/types": "7.18.4", + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "globby": "^11.1.0", + "into-stream": "^6.0.0", + "is-core-module": "2.9.0", + "minimist": "^1.2.6", + "multistream": "^4.1.0", + "pkg-fetch": "3.4.2", + "prebuild-install": "6.1.4", + "resolve": "^1.22.0", + "stream-meter": "^1.0.4" + }, + "dependencies": { + "@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "requires": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + } + }, + "@babel/parser": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", + "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", + "dev": true + }, + "@babel/types": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", + "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true + }, + "node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "dev": true, + "requires": { + "semver": "^5.4.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "prebuild-install": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", + "dev": true, + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.21.0", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "pkg-conf": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", @@ -38556,6 +39426,74 @@ } } }, + "pkg-fetch": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz", + "integrity": "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==", + "dev": true, + "requires": { + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.6", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^2.1.1", + "yargs": "^16.2.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } + } + }, "pkg-size": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/pkg-size/-/pkg-size-2.4.0.tgz", @@ -40184,6 +41122,47 @@ "stubs": "^3.0.0" } }, + "stream-meter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", + "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", + "dev": true, + "requires": { + "readable-stream": "^2.1.4" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", diff --git a/package.json b/package.json index 8bfed711..57615aaa 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "npm-check-updates": "^13.1.3", "npm-run-all": "^4.1.5", "nunjucks": "^3.2.3", + "pkg": "^5.8.0", "pkg-size": "^2.4.0", "prettier": "^2.6.2", "rimraf": "^3.0.2", diff --git a/packages/pkg-cloud-run-job-example/Dockerfile b/packages/pkg-cloud-run-job-example/Dockerfile new file mode 100644 index 00000000..9fc7b807 --- /dev/null +++ b/packages/pkg-cloud-run-job-example/Dockerfile @@ -0,0 +1,72 @@ +# === STAGE 1 ================================================================ # +FROM node:18.7-bullseye AS builder + +LABEL maintainer="giacomo@giacomodebidda.com" + +# An ARG instruction goes out of scope at the end of the build stage where it +# was defined. +# To use an arg in multiple stages, EACH STAGE must include the ARG instruction. +# https://docs.docker.com/engine/reference/builder/#scope +ARG APP_NAME +RUN if [ -z "${APP_NAME}" ] ; then echo "The APP_NAME argument is missing!" ; exit 1; fi + +# RUN apt-get update && apt-get install --quiet --assume-yes sudo \ +# lsb-release \ +# tree + +ENV APP_DIR=/usr/src/app + +WORKDIR ${APP_DIR} + +COPY package*.json ./ + +RUN npm install --location=global pkg + +RUN npm install + +COPY src ./src + +RUN pkg -t node16-linux-x64 src/index.js && \ + mkdir dist && \ + mv index dist/executable + +# RUN tree -I 'node_modules' -a -L 3 ${APP_DIR} + +# === STAGE 2 ================================================================ # +FROM node:18.7-bullseye-slim + +# Each ARG goes out of scope at the end of the build stage where it was +# defined. That's why we have to repeat it here in this stage. +ARG APP_NAME + +# RUN apt-get update && apt-get install --quiet --assume-yes sudo \ +# lsb-release \ +# tree + +ENV APP_GROUP=dk-group \ + APP_USER=dk-user \ + BUILDER_APP_DIR=/usr/src/app + +# add a non-privileged user +RUN groupadd --system ${APP_GROUP} && \ + useradd --system --gid ${APP_GROUP} --create-home ${APP_USER} --comment "container user account" && \ + mkdir -p /home/${APP_USER}/${APP_NAME} + +WORKDIR /home/${APP_USER}/${APP_NAME} + +COPY --from=builder ${BUILDER_APP_DIR}/dist/executable ./executable + +RUN chown -R ${APP_USER} ./ + +# Run everything AFTER as non-privileged user. +USER ${APP_USER} + +# check source code and installed dependencies +# RUN tree -a -L 3 . +# check permissions +# RUN ls -1la + +# I like to keep this line for troubleshooting +# RUN echo "App ${APP_NAME} will be run by user $(whoami) on $(lsb_release -i -s) $(lsb_release -r -s)" + +ENTRYPOINT ["./executable"] diff --git a/packages/pkg-cloud-run-job-example/README.md b/packages/pkg-cloud-run-job-example/README.md new file mode 100644 index 00000000..61ce6a57 --- /dev/null +++ b/packages/pkg-cloud-run-job-example/README.md @@ -0,0 +1,74 @@ +# @jackdbd/pkg-cloud-run-job-example + +Example that shows how to create a Node.js executable with [pkg](https://github.com/vercel/pkg) and to deploy it to [Cloud Run Jobs](https://cloud.google.com/run/docs/create-jobs). + +## Development + +```sh +npm run start:development -w packages/pkg-cloud-run-job-example +``` + +## Test + +Run integration tests with [SuperTest](https://github.com/visionmedia/supertest) and Jest: + +```sh +npm run test -w packages/pkg-cloud-run-job-example +``` + +## Executable + +Create an executable with pkg: + +```sh +npm run exe:build -w packages/pkg-cloud-run-job-example +``` + +Run the executable: + +```sh +npm run exe:start:development -w packages/pkg-cloud-run-job-example +``` + +Create a container image containing the executable: + +```sh +npm run container:build -w packages/pkg-cloud-run-job-example +``` + +Run the container: + +```sh +npm run container:start:development -w packages/pkg-cloud-run-job-example +# or +npm run container:start:production -w packages/pkg-cloud-run-job-example +``` + +## Deploy + +Deploy the containerized application to Cloud Run Jobs. + +```sh +npm run deploy -w packages/pkg-cloud-run-job-example +``` + +## Execute the Cloud Run job + +Executing a Cloud Run job means creating a Cloud Run job **execution**. + +Invoke the Cloud Run job using the currently authenticated **user account**. + +```sh +gcloud beta run jobs execute pkg-cloud-run-job-example \ + --region europe-west3 \ + --verbosity info +``` + +Invoke the Cloud Run job using a **service account** (thanks to service account impersonation): + +```sh +gcloud beta run jobs execute pkg-cloud-run-job-example \ + --region europe-west3 \ + --impersonate-service-account "sa-workflows-runner@prj-kitchen-sink.iam.gserviceaccount.com" \ + --verbosity info +``` diff --git a/packages/pkg-cloud-run-job-example/__tests__/utils.test.js b/packages/pkg-cloud-run-job-example/__tests__/utils.test.js new file mode 100644 index 00000000..610b6eb8 --- /dev/null +++ b/packages/pkg-cloud-run-job-example/__tests__/utils.test.js @@ -0,0 +1,11 @@ +// const supertest = require('supertest') +const { randomCocktail } = require('../src/utils.js') + +describe(`randomCocktail`, () => { + it('returns a drink`', async () => { + const cocktail = await randomCocktail() + + expect(cocktail.idDrink).toBeDefined() + expect(cocktail.strDrink).toBeDefined() + }) +}) diff --git a/packages/pkg-cloud-run-job-example/cloudbuild.yaml b/packages/pkg-cloud-run-job-example/cloudbuild.yaml new file mode 100644 index 00000000..5eb06f17 --- /dev/null +++ b/packages/pkg-cloud-run-job-example/cloudbuild.yaml @@ -0,0 +1,73 @@ +steps: + - id: '🐋 Build container image' + name: gcr.io/cloud-builders/docker + args: + - 'build' + - '.' + - '--file=Dockerfile' + - '--build-arg=APP_NAME=${_CLOUD_RUN_JOB_ID}' + - '--tag=${_CONTAINER_IMAGE}' + + - id: '🛫 Push container image to Artifact Registry' + name: gcr.io/cloud-builders/docker + args: ['push', "${_CONTAINER_IMAGE}"] + + # - id: '🚀 Create Cloud Run job' + # name: gcr.io/cloud-builders/gcloud:${_GCLOUD_VERSION} + # entrypoint: /bin/bash + # args: + # - -c + # - | + # gcloud beta run jobs create ${_CLOUD_RUN_JOB_ID} \ + # --image ${_CONTAINER_IMAGE} \ + # --labels customer=${_CUSTOMER},resource=cloud_run_job \ + # --max-retries ${_MAX_RETRIES_PER_FAILED_TASK} \ + # --memory ${_MEMORY} \ + # --region ${_CLOUD_RUN_JOB_REGION_ID} \ + # --service-account ${_SERVICE_ACCOUNT} \ + # --set-env-vars NODE_ENV=${_NODE_ENV} \ + # --tasks=1 \ + # --task-timeout=${_TASK_TIMEOUT} \ + # --verbosity warning + + - id: '🚀 Update Cloud Run job' + name: gcr.io/cloud-builders/gcloud:${_GCLOUD_VERSION} + entrypoint: /bin/bash + args: + - -c + - | + gcloud beta run jobs update ${_CLOUD_RUN_JOB_ID} \ + --image ${_CONTAINER_IMAGE} \ + --max-retries ${_MAX_RETRIES_PER_FAILED_TASK} \ + --memory ${_MEMORY} \ + --region ${_CLOUD_RUN_JOB_REGION_ID} \ + --service-account ${_SERVICE_ACCOUNT} \ + --set-env-vars NODE_ENV=${_NODE_ENV} \ + --tasks=1 \ + --task-timeout=${_TASK_TIMEOUT} \ + --update-labels customer=${_CUSTOMER},resource=cloud_run_job \ + --verbosity warning + +# user-defined substitutions and default values +substitutions: + _ARTIFACT_REGISTRY_DOCKER_REPOSITORY_ID: cloud-run-source-deploy + _ARTIFACT_REGISTRY_DOCKER_REPOSITORY_LOCATION_ID: europe-west3 + _CLOUD_RUN_JOB_ID: ${_CONTAINER_IMAGE_NAME} + _CLOUD_RUN_JOB_REGION_ID: europe-west3 + _CONTAINER_IMAGE: "${_ARTIFACT_REGISTRY_DOCKER_REPOSITORY_LOCATION_ID}-docker.pkg.dev/${PROJECT_ID}/${_ARTIFACT_REGISTRY_DOCKER_REPOSITORY_ID}/${_CONTAINER_IMAGE_NAME}:${_CONTAINER_IMAGE_TAG}" + _CONTAINER_IMAGE_NAME: pkg-cloud-run-job-example + _CONTAINER_IMAGE_TAG: latest + _CUSTOMER: personal + _GCLOUD_VERSION: latest + _MAX_RETRIES_PER_FAILED_TASK: '1' + # Cloud Run jobs must have at least 512MiB of memory. + # M = megabyte, Mi = mebibyte, G = gigabyte, Gi = gibibyte + # https://cloud.google.com/run/docs/configuring/memory-limits + _MEMORY: 512Mi + _NODE_ENV: production + _SERVICE_ACCOUNT: sa-workflows-runner@prj-kitchen-sink.iam.gserviceaccount.com + _TASK_TIMEOUT: 10s + +options: + # https://cloud.google.com/build/docs/configuring-builds/substitute-variable-values#dynamic_substitutions + dynamic_substitutions: true diff --git a/packages/pkg-cloud-run-job-example/jest.config.cjs b/packages/pkg-cloud-run-job-example/jest.config.cjs new file mode 100644 index 00000000..6abaf900 --- /dev/null +++ b/packages/pkg-cloud-run-job-example/jest.config.cjs @@ -0,0 +1,27 @@ +const config = { + // https://jestjs.io/docs/configuration#globals-object + globals: {}, + + testMatch: [`/__tests__/**/*.test.{js,mjs}`], + + // 5s is the default value for slowTestThreshold. + // https://jestjs.io/docs/configuration#slowtestthreshold-number + slowTestThreshold: 5, + + // https://jestjs.io/docs/configuration#testenvironment-string + testEnvironment: 'node', + + // jest-circus/runner is the default value for testRunner. + // https://jestjs.io/docs/configuration#testrunner-string + testRunner: 'jest-circus/runner', + + // 5000ms is the default value for testTimeout. + // https://jestjs.io/docs/configuration#testtimeout-number + testTimeout: 5000, + + transform: {} +} + +// console.log('=== Jest config ===', config) + +module.exports = config diff --git a/packages/pkg-cloud-run-job-example/package.json b/packages/pkg-cloud-run-job-example/package.json new file mode 100644 index 00000000..5c831b8a --- /dev/null +++ b/packages/pkg-cloud-run-job-example/package.json @@ -0,0 +1,49 @@ +{ + "name": "@jackdbd/pkg-cloud-run-job-example", + "version": "0.0.0", + "description": "example that shows how to create a Node.js executable with pkg and to deploy it to Cloud Run Jobs", + "author": { + "name": "Giacomo Debidda", + "email": "giacomo@giacomodebidda.com", + "url": "https://giacomodebidda.com/" + }, + "license": "MIT", + "private": true, + "keywords": [ + "job" + ], + "repository": { + "type": "git", + "url": "https://github.com/jackdbd/calderone" + }, + "engines": { + "node": ">=16.15.0" + }, + "type": "commonjs", + "main": "src/index.js", + "scripts": { + "clean": "rimraf dist", + "precontainer:build": "run-s clean", + "container:build": "docker build ./ --file Dockerfile --build-arg APP_NAME=pkg-cloud-run-job-example --tag calderone-pkg-cloud-run-job-example:latest", + "container:inspect": "dive calderone-pkg-cloud-run-job-example:latest", + "container:start:development": "docker run -it --rm -p 8080:8080 --env DEBUG=app/* --env NODE_ENV=development calderone-pkg-cloud-run-job-example:latest", + "container:start:production": "docker run -it --rm -p 8080:8080 --env NODE_ENV=production calderone-pkg-cloud-run-job-example:latest", + "predeploy": "run-s clean format lint test", + "deploy": "gcloud beta builds submit ./ --config cloudbuild.yaml --async", + "exe:build": "npm run clean && pkg -t node16-linux-x64 src/index.js && mkdir dist && mv index dist/executable", + "exe:start:development": "DEBUG=app/* NODE_ENV=development ./dist/executable", + "exe:start:production": "NODE_ENV=production ./dist/executable", + "format": "../../scripts/format.mjs", + "lint": "eslint --config ../../config/eslint.cjs", + "nuke": "npm run clean && rimraf node_modules 'package-lock.json'", + "__precommit": "lint-staged --config ../../config/lint-staged.cjs", + "start:development": "DEBUG=app/*, NODE_ENV=development node src/index.js", + "test": "npx jest --config jest.config.cjs --rootDir ./", + "test:ci": "npx jest --config jest.config.cjs --rootDir ./ --runInBand --ci" + }, + "dependencies": { + "debug": "^4.3.3", + "phin": "^3.6.1" + }, + "devDependencies": {} +} diff --git a/packages/pkg-cloud-run-job-example/src/constants.js b/packages/pkg-cloud-run-job-example/src/constants.js new file mode 100644 index 00000000..49c63152 --- /dev/null +++ b/packages/pkg-cloud-run-job-example/src/constants.js @@ -0,0 +1,3 @@ +const APP_NAME = 'app' + +module.exports = { APP_NAME } diff --git a/packages/pkg-cloud-run-job-example/src/index.js b/packages/pkg-cloud-run-job-example/src/index.js new file mode 100644 index 00000000..9a4b95fc --- /dev/null +++ b/packages/pkg-cloud-run-job-example/src/index.js @@ -0,0 +1,33 @@ +const makeDebug = require('debug') +const { APP_NAME } = require('./constants.js') +const { wait1000Ms, randomCocktail } = require('./utils.js') + +const debug = makeDebug(`${APP_NAME}/index`) + +Promise.resolve({ started: new Date().toUTCString() }) + .then(async (m) => { + const cocktail = await randomCocktail() + debug(`fetched cocktail ${cocktail.strDrink}`) + return { ...m, cocktail } + }) + .then(async (m) => { + await wait1000Ms() + debug(`waited 1000ms`) + return { ...m, waited: 1000 } + }) + .then(async (m) => { + await wait1000Ms() + debug(`waited 1000ms`) + return { ...m, waited: m.waited + 1000 } + }) + .then(async (m) => { + return { + ...m, + completed: new Date().toUTCString() + } + }) + .then((m) => { + debug(`%O`, m) + console.log(m) + }) + .catch(console.error) diff --git a/packages/pkg-cloud-run-job-example/src/utils.js b/packages/pkg-cloud-run-job-example/src/utils.js new file mode 100644 index 00000000..622fc9ab --- /dev/null +++ b/packages/pkg-cloud-run-job-example/src/utils.js @@ -0,0 +1,34 @@ +const makeDebug = require('debug') +const phin = require('phin') +const { APP_NAME } = require('./constants.js') + +const debug = makeDebug(`${APP_NAME}/utils`) + +const makeWaitMs = (ms) => { + return async function waitMs() { + return new Promise((resolve) => { + setTimeout(() => { + resolve(`finished waiting ${ms}ms`) + }, ms) + debug(`started waiting ${ms} ms`) + }) + } +} + +const wait1000Ms = makeWaitMs(1000) + +const randomCocktail = async () => { + debug(`fetch a random cocktail from thecocktaildb.com`) + const response = await phin({ + headers: { + 'Content-Type': 'application/json' + }, + method: 'GET', + parse: 'json', + url: 'https://www.thecocktaildb.com/api/json/v1/1/random.php' + }) + + return response.body.drinks[0] +} + +module.exports = { makeWaitMs, wait1000Ms, randomCocktail } From 25ca8838cb661b69b3f0eed3e183c6da0c2d4b2d Mon Sep 17 00:00:00 2001 From: Giacomo Debidda Date: Mon, 12 Sep 2022 17:04:48 +0200 Subject: [PATCH 6/6] chore: lock dependencies --- .github/workflows/release-to-npmjs.yaml | 3 ++- package-lock.json | 24 +++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-to-npmjs.yaml b/.github/workflows/release-to-npmjs.yaml index 2833687d..d6fb1d42 100644 --- a/.github/workflows/release-to-npmjs.yaml +++ b/.github/workflows/release-to-npmjs.yaml @@ -28,7 +28,8 @@ jobs: run: npm version - name: ⬇️ Install dependencies from npmjs - run: npm install --legacy-peer-deps + run: npm install + # run: npm install --legacy-peer-deps - name: 🛡️ Audit dependencies (audit-level high) # https://docs.npmjs.com/cli/v8/commands/npm-audit#audit-level diff --git a/package-lock.json b/package-lock.json index f37c8345..9b214a8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2435,8 +2435,8 @@ "resolved": "packages/notifications", "link": true }, - "node_modules/@jackdbd/pkg-hello-world": { - "resolved": "packages/pkg-hello-world", + "node_modules/@jackdbd/pkg-cloud-run-job-example": { + "resolved": "packages/pkg-cloud-run-job-example", "link": true }, "node_modules/@jackdbd/plausible-client": { @@ -22554,9 +22554,22 @@ "debug": ">=4.0.0" } }, + "packages/pkg-cloud-run-job-example": { + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "debug": "^4.3.3", + "phin": "^3.6.1" + }, + "devDependencies": {}, + "engines": { + "node": ">=16.15.0" + } + }, "packages/pkg-hello-world": { "name": "@jackdbd/pkg-hello-world", "version": "0.0.0", + "extraneous": true, "license": "MIT", "dependencies": { "@google-cloud/functions-framework": "^3.1.2" @@ -26424,10 +26437,11 @@ "phin": "^3.6.1" } }, - "@jackdbd/pkg-hello-world": { - "version": "file:packages/pkg-hello-world", + "@jackdbd/pkg-cloud-run-job-example": { + "version": "file:packages/pkg-cloud-run-job-example", "requires": { - "@google-cloud/functions-framework": "^3.1.2" + "debug": "^4.3.3", + "phin": "^3.6.1" } }, "@jackdbd/plausible-client": {