From 7a71d30e0cf282510e651f7940190a0b9af5a924 Mon Sep 17 00:00:00 2001 From: Wei Date: Fri, 29 Nov 2024 22:37:33 -0500 Subject: [PATCH 01/11] feat(totalPreviewChannelLimit): add option to manage preview channels and avoid hitting the channel quota --- README.md | 8 +++ action.yml | 4 ++ src/deploy.ts | 171 ++++++++++++++++++++++++++++++++++++++++++++ src/getChannelId.ts | 12 ++++ src/index.ts | 28 ++++++++ 5 files changed, 223 insertions(+) diff --git a/README.md b/README.md index c755c437..60bf7cd9 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,14 @@ The version of `firebase-tools` to use. If not specified, defaults to `latest`. Disable commenting in a PR with the preview URL. +### `totalPreviewChannelLimit` _{number}_ + +Specifies the maximum number of preview channels allowed to optimize resource usage or avoid exceeding Firebase Hosting’s quota. + +Once the limit is reached, the oldest channels are automatically removed to prevent errors like "Couldn't create channel on [project]: channel quota reached", ensuring smooth deployments. + +Currently, **50** preview channels are allowed per Firebase project, including the default "live" site and any additional "live" sites. _totalPreviewChannelLimit = 50 - number of "live sites"_ + ## Outputs Values emitted by this action that can be consumed by other actions later in your workflow diff --git a/action.yml b/action.yml index 52bff61b..e94e9b1f 100644 --- a/action.yml +++ b/action.yml @@ -64,6 +64,10 @@ inputs: Disable auto-commenting with the preview channel URL to the pull request default: "false" required: false + totalPreviewChannelLimit: + description: >- + Defines the maximum number of preview channels allowed in a Firebase project + required: false outputs: urls: description: The url(s) deployed to diff --git a/src/deploy.ts b/src/deploy.ts index 76c61858..ea8edbe7 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -15,6 +15,7 @@ */ import { exec } from "@actions/exec"; +import { extractChannelIdFromChannelName } from "./getChannelId"; export type SiteDeploy = { site: string; @@ -28,6 +29,60 @@ export type ErrorResult = { error: string; }; +export type ChannelTotalSuccessResult = { + status: "success"; + channels: Channel[]; +}; + +export interface Channel { + name: string; + url: string; + release: { + name: string; + version: { + name: string; + status: string; + config: { + headers: { + headers: { + "Cache-Control": string; + }; + glob: string; + }[]; + rewrites: { + glob: string; + path: string; + }[]; + }; + labels: { + "deployment-tool": string; + }; + createTime: string; + createUser: { + email: string; + }; + finalizeTime: string; + finalizeUser: { + email: string; + }; + fileCount: string; + versionBytes: string; + }; + type: string; + releaseTime: string; + releaseUser: { + email: string; + }; + }; + createTime: string; + updateTime: string; + retainedReleaseCount: number; + expireTime?: string; + labels?: { + type: "live"; + }; +} + export type ChannelSuccessResult = { status: "success"; result: { [key: string]: SiteDeploy }; @@ -45,6 +100,8 @@ type DeployConfig = { target?: string; // Optional version specification for firebase-tools. Defaults to `latest`. firebaseToolsVersion?: string; + // Optional for preview channels deployment + totalPreviewChannelLimit?: number; }; export type ChannelDeployConfig = DeployConfig & { @@ -125,6 +182,120 @@ async function execWithCredentials( : ""; // output from the CLI } +export async function getAllChannels( + gacFilename: string, + deployConfig: Omit +): Promise { + const { projectId, target, firebaseToolsVersion } = deployConfig; + + const allChannelsText = await execWithCredentials( + ["hosting:channel:list", ...(target ? ["--site", target] : [])], + projectId, + gacFilename, + { firebaseToolsVersion } + ); + + const channelResults = JSON.parse(allChannelsText.trim()) as + | ChannelTotalSuccessResult + | ErrorResult; + + if (channelResults.status === "error") { + throw Error((channelResults as ErrorResult).error); + } else { + return channelResults.channels || []; + } +} + +function getPreviewChannelToRemove( + channels: Channel[], + totalPreviewChannelLimit: DeployConfig["totalPreviewChannelLimit"] +): Channel[] { + // Filter out live channel(hosting default site) and channels without an expireTime(additional sites) + const previewChannelsOnly = channels.filter( + (channel) => channel?.labels?.type !== "live" && !!channel?.expireTime + ); + + if (previewChannelsOnly.length) { + // Sort preview channels by expireTime + const sortedPreviewChannels = previewChannelsOnly.sort( + (channelA, channelB) => { + return ( + new Date(channelA.expireTime).getTime() - + new Date(channelB.expireTime).getTime() + ); + } + ); + + // If the total number of preview channels exceeds the limit, return the ones with earlier expireTime + if (sortedPreviewChannels.length > totalPreviewChannelLimit) { + return sortedPreviewChannels.slice( + 0, + sortedPreviewChannels.length - totalPreviewChannelLimit + ); + } + } else { + return []; + } +} + +/** + * Removes preview channels from the list of active channels if the number exceeds the configured limit + * + * This function identifies the preview channels that need to be removed based on the total limit of + * preview channels allowed (`totalPreviewChannelLimit`). + * + * It then attempts to remove those channels using the `removeChannel` function. + * Errors encountered while removing channels are logged but do not stop the execution of removing other channels. + */ +export async function removePreviews({ + channels, + gacFilename, + deployConfig, +}: { + channels: Channel[]; + gacFilename: string; + deployConfig: Omit; +}) { + const toRemove = getPreviewChannelToRemove( + channels, + deployConfig.totalPreviewChannelLimit + ); + + if (toRemove.length) { + await Promise.all( + toRemove.map(async (channel) => { + try { + await removeChannel( + gacFilename, + deployConfig, + extractChannelIdFromChannelName(channel.name) + ); + } catch (error) { + console.error( + `Error removing preview channel ${channel.name}:`, + error + ); + } + }) + ); + } +} + +export function removeChannel( + gacFilename: string, + deployConfig: Omit, + channelId: string +): Promise { + const { projectId, firebaseToolsVersion } = deployConfig; + + return execWithCredentials( + ["hosting:channel:delete", channelId, "--force"], + projectId, + gacFilename, + { firebaseToolsVersion } + ); +} + export async function deployPreview( gacFilename: string, deployConfig: ChannelDeployConfig diff --git a/src/getChannelId.ts b/src/getChannelId.ts index 09629ab4..4ad3f6dd 100644 --- a/src/getChannelId.ts +++ b/src/getChannelId.ts @@ -37,3 +37,15 @@ export function getChannelId(configuredChannelId: string, ghContext: Context) { return correctedChannelId; } + +/** + * Extracts the channel ID from the channel name + * @param channelName + * @returns channelId + * Example channelName: projects/my-project/sites/test-staging/channels/pr123-my-branch + */ +export function extractChannelIdFromChannelName(channelName: string): string { + const parts = channelName.split("/"); + const channelIndex = parts.indexOf("channels") + 1; // The part after "channels" + return parts[channelIndex]; // Returns the channel name after "channels/" +} diff --git a/src/index.ts b/src/index.ts index fe34502b..505034c6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,7 +29,9 @@ import { deployPreview, deployProductionSite, ErrorResult, + getAllChannels, interpretChannelDeployResult, + removePreviews, } from "./deploy"; import { getChannelId } from "./getChannelId"; import { @@ -51,6 +53,9 @@ const entryPoint = getInput("entryPoint"); const target = getInput("target"); const firebaseToolsVersion = getInput("firebaseToolsVersion"); const disableComment = getInput("disableComment"); +const totalPreviewChannelLimit = Number( + getInput("totalPreviewChannelLimit") || "0" +); async function run() { const isPullRequest = !!context.payload.pull_request; @@ -115,6 +120,29 @@ async function run() { const channelId = getChannelId(configuredChannelId, context); + if (totalPreviewChannelLimit) { + startGroup(`Start counting total Firebase preview channel ${channelId}`); + + const allChannels = await getAllChannels(gacFilename, { + projectId, + target, + firebaseToolsVersion, + totalPreviewChannelLimit, + }); + + if (allChannels.length) { + await removePreviews({ + channels: allChannels, + gacFilename, + deployConfig: { + projectId, + target, + firebaseToolsVersion, + }, + }); + } + } + startGroup(`Deploying to Firebase preview channel ${channelId}`); const deployment = await deployPreview(gacFilename, { projectId, From 6ad403c5a4252fbc8af3d5a69d038b265ad2118b Mon Sep 17 00:00:00 2001 From: Wei Date: Fri, 29 Nov 2024 22:44:12 -0500 Subject: [PATCH 02/11] fix: update build --- bin/action.min.js | 166 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 133 insertions(+), 33 deletions(-) diff --git a/bin/action.min.js b/bin/action.min.js index 8324beb8..29e1b49d 100644 --- a/bin/action.min.js +++ b/bin/action.min.js @@ -92916,6 +92916,49 @@ exports.getExecOutput = getExecOutput; }); +/** + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +function getChannelId(configuredChannelId, ghContext) { + let tmpChannelId = ""; + if (!!configuredChannelId) { + tmpChannelId = configuredChannelId; + } else if (ghContext.payload.pull_request) { + const branchName = ghContext.payload.pull_request.head.ref.substr(0, 20); + tmpChannelId = `pr${ghContext.payload.pull_request.number}-${branchName}`; + } + // Channel IDs can only include letters, numbers, underscores, hyphens, and periods. + const invalidCharactersRegex = /[^a-zA-Z0-9_\-\.]/g; + const correctedChannelId = tmpChannelId.replace(invalidCharactersRegex, "_"); + if (correctedChannelId !== tmpChannelId) { + console.log(`ChannelId "${tmpChannelId}" contains unsupported characters. Using "${correctedChannelId}" instead.`); + } + return correctedChannelId; +} +/** + * Extracts the channel ID from the channel name + * @param channelName + * @returns channelId + * Example channelName: projects/my-project/sites/test-staging/channels/pr123-my-branch + */ +function extractChannelIdFromChannelName(channelName) { + const parts = channelName.split("/"); + const channelIndex = parts.indexOf("channels") + 1; // The part after "channels" + return parts[channelIndex]; // Returns the channel name after "channels/" +} + /** * Copyright 2020 Google LLC * @@ -92976,7 +93019,75 @@ async function execWithCredentials(args, projectId, gacFilename, opts) { } return deployOutputBuf.length ? deployOutputBuf[deployOutputBuf.length - 1].toString("utf-8") : ""; // output from the CLI } - +async function getAllChannels(gacFilename, deployConfig) { + const { + projectId, + target, + firebaseToolsVersion + } = deployConfig; + const allChannelsText = await execWithCredentials(["hosting:channel:list", ...(target ? ["--site", target] : [])], projectId, gacFilename, { + firebaseToolsVersion + }); + const channelResults = JSON.parse(allChannelsText.trim()); + if (channelResults.status === "error") { + throw Error(channelResults.error); + } else { + return channelResults.channels || []; + } +} +function getPreviewChannelToRemove(channels, totalPreviewChannelLimit) { + // Filter out live channel(hosting default site) and channels without an expireTime(additional sites) + const previewChannelsOnly = channels.filter(channel => { + var _channel$labels; + return (channel == null || (_channel$labels = channel.labels) == null ? void 0 : _channel$labels.type) !== "live" && !!(channel != null && channel.expireTime); + }); + if (previewChannelsOnly.length) { + // Sort preview channels by expireTime + const sortedPreviewChannels = previewChannelsOnly.sort((channelA, channelB) => { + return new Date(channelA.expireTime).getTime() - new Date(channelB.expireTime).getTime(); + }); + // If the total number of preview channels exceeds the limit, return the ones with earlier expireTime + if (sortedPreviewChannels.length > totalPreviewChannelLimit) { + return sortedPreviewChannels.slice(0, sortedPreviewChannels.length - totalPreviewChannelLimit); + } + } else { + return []; + } +} +/** + * Removes preview channels from the list of active channels if the number exceeds the configured limit + * + * This function identifies the preview channels that need to be removed based on the total limit of + * preview channels allowed (`totalPreviewChannelLimit`). + * + * It then attempts to remove those channels using the `removeChannel` function. + * Errors encountered while removing channels are logged but do not stop the execution of removing other channels. + */ +async function removePreviews({ + channels, + gacFilename, + deployConfig +}) { + const toRemove = getPreviewChannelToRemove(channels, deployConfig.totalPreviewChannelLimit); + if (toRemove.length) { + await Promise.all(toRemove.map(async channel => { + try { + await removeChannel(gacFilename, deployConfig, extractChannelIdFromChannelName(channel.name)); + } catch (error) { + console.error(`Error removing preview channel ${channel.name}:`, error); + } + })); + } +} +function removeChannel(gacFilename, deployConfig, channelId) { + const { + projectId, + firebaseToolsVersion + } = deployConfig; + return execWithCredentials(["hosting:channel:delete", channelId, "--force"], projectId, gacFilename, { + firebaseToolsVersion + }); +} async function deployPreview(gacFilename, deployConfig) { const { projectId, @@ -93004,38 +93115,6 @@ async function deployProductionSite(gacFilename, productionDeployConfig) { return deploymentResult; } -/** - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -function getChannelId(configuredChannelId, ghContext) { - let tmpChannelId = ""; - if (!!configuredChannelId) { - tmpChannelId = configuredChannelId; - } else if (ghContext.payload.pull_request) { - const branchName = ghContext.payload.pull_request.head.ref.substr(0, 20); - tmpChannelId = `pr${ghContext.payload.pull_request.number}-${branchName}`; - } - // Channel IDs can only include letters, numbers, underscores, hyphens, and periods. - const invalidCharactersRegex = /[^a-zA-Z0-9_\-\.]/g; - const correctedChannelId = tmpChannelId.replace(invalidCharactersRegex, "_"); - if (correctedChannelId !== tmpChannelId) { - console.log(`ChannelId "${tmpChannelId}" contains unsupported characters. Using "${correctedChannelId}" instead.`); - } - return correctedChannelId; -} - /** * Copyright 2020 Google LLC * @@ -93182,6 +93261,7 @@ const entryPoint = core.getInput("entryPoint"); const target = core.getInput("target"); const firebaseToolsVersion = core.getInput("firebaseToolsVersion"); const disableComment = core.getInput("disableComment"); +const totalPreviewChannelLimit = Number(core.getInput("totalPreviewChannelLimit") || "0"); async function run() { const isPullRequest = !!github.context.payload.pull_request; let finish = details => console.log(details); @@ -93232,6 +93312,26 @@ async function run() { return; } const channelId = getChannelId(configuredChannelId, github.context); + if (totalPreviewChannelLimit) { + core.startGroup(`Start counting total Firebase preview channel ${channelId}`); + const allChannels = await getAllChannels(gacFilename, { + projectId, + target, + firebaseToolsVersion, + totalPreviewChannelLimit + }); + if (allChannels.length) { + await removePreviews({ + channels: allChannels, + gacFilename, + deployConfig: { + projectId, + target, + firebaseToolsVersion + } + }); + } + } core.startGroup(`Deploying to Firebase preview channel ${channelId}`); const deployment = await deployPreview(gacFilename, { projectId, From d3d3023ebc128b456db3511979447360642b8522 Mon Sep 17 00:00:00 2001 From: Wei Date: Fri, 29 Nov 2024 23:01:14 -0500 Subject: [PATCH 03/11] Trigger CI From 0c7968b4f8f3eb0ad019a9034339fa7d5fb5a67d Mon Sep 17 00:00:00 2001 From: Wei Date: Fri, 29 Nov 2024 23:25:28 -0500 Subject: [PATCH 04/11] Trigger CI From 77e9bcf53857d002351ccd68397d08aae1625625 Mon Sep 17 00:00:00 2001 From: Wei Date: Fri, 29 Nov 2024 23:36:58 -0500 Subject: [PATCH 05/11] Trigger CI From 975aef7c90b7d4f99f735f85718a6a11434598a4 Mon Sep 17 00:00:00 2001 From: Wei Date: Sat, 30 Nov 2024 05:43:05 -0500 Subject: [PATCH 06/11] fix: add missing husky install --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 6456cd36..ae714cf0 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "typescript": "^5.0.4" }, "scripts": { + "prepare": "husky install", "format:check": "prettier . --list-different", "format": "prettier --write .", "build": "microbundle --format cjs --target node --no-compress --no-sourcemap src/index.ts", From b2e6390bafae59f013b3e5b10ec1e2676719c844 Mon Sep 17 00:00:00 2001 From: Wei Date: Sat, 30 Nov 2024 06:39:46 -0500 Subject: [PATCH 07/11] fix: update to proper slice logic with max quota --- README.md | 4 ++-- src/deploy.ts | 66 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 60bf7cd9..ce659c02 100644 --- a/README.md +++ b/README.md @@ -155,9 +155,9 @@ Disable commenting in a PR with the preview URL. Specifies the maximum number of preview channels allowed to optimize resource usage or avoid exceeding Firebase Hosting’s quota. -Once the limit is reached, the oldest channels are automatically removed to prevent errors like "Couldn't create channel on [project]: channel quota reached", ensuring smooth deployments. +Once the limit is reached, the oldest channels are automatically removed to prevent errors like "429, Couldn't create channel on [project]: channel quota reached", ensuring smooth deployments. -Currently, **50** preview channels are allowed per Firebase project, including the default "live" site and any additional "live" sites. _totalPreviewChannelLimit = 50 - number of "live sites"_ +Currently, **50** channels are allowed per Hosting **site**, including the default "live" site. ## Outputs diff --git a/src/deploy.ts b/src/deploy.ts index ea8edbe7..38109953 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -111,6 +111,9 @@ export type ChannelDeployConfig = DeployConfig & { export type ProductionDeployConfig = DeployConfig & {}; +const SITE_CHANNEL_QUOTA = 50; +const SITE_CHANNEL_LIVE_SITE = 1; + export function interpretChannelDeployResult( deployResult: ChannelSuccessResult ): { expireTime: string; expire_time_formatted: string; urls: string[] } { @@ -210,28 +213,55 @@ function getPreviewChannelToRemove( channels: Channel[], totalPreviewChannelLimit: DeployConfig["totalPreviewChannelLimit"] ): Channel[] { - // Filter out live channel(hosting default site) and channels without an expireTime(additional sites) - const previewChannelsOnly = channels.filter( - (channel) => channel?.labels?.type !== "live" && !!channel?.expireTime - ); + let totalAllowedPreviewChannels = totalPreviewChannelLimit; + let totalPreviewChannelToSlice = totalPreviewChannelLimit; + + if (totalPreviewChannelLimit >= SITE_CHANNEL_QUOTA - SITE_CHANNEL_LIVE_SITE) { + /** + * If the total number of preview channels is greater than or equal to the site channel quota, + * preview channels is the site channel quota minus the live site channel + * + * e.g. 49(total allowed preview channels) = 50(quota) - 1(live site channel) + */ + totalAllowedPreviewChannels = + totalPreviewChannelLimit - SITE_CHANNEL_LIVE_SITE; + + /** + * If the total number of preview channels is greater than or equal to the site channel quota, + * total preview channels to slice is the site channel quota plus the live site channel plus the current preview deploy + * + * e.g. 52(total preview channels to slice) = 50(site channel quota) + 1(live site channel) + 1 (current preview deploy) + */ + totalPreviewChannelToSlice = + SITE_CHANNEL_QUOTA + SITE_CHANNEL_LIVE_SITE + 1; + } - if (previewChannelsOnly.length) { - // Sort preview channels by expireTime - const sortedPreviewChannels = previewChannelsOnly.sort( - (channelA, channelB) => { - return ( - new Date(channelA.expireTime).getTime() - - new Date(channelB.expireTime).getTime() - ); - } + if (channels.length > totalAllowedPreviewChannels) { + // If the total number of channels exceeds the limit, remove the preview channels + // Filter out live channel(hosting default site) and channels without an expireTime(additional sites) + const previewChannelsOnly = channels.filter( + (channel) => channel?.labels?.type !== "live" && !!channel?.expireTime ); - // If the total number of preview channels exceeds the limit, return the ones with earlier expireTime - if (sortedPreviewChannels.length > totalPreviewChannelLimit) { - return sortedPreviewChannels.slice( - 0, - sortedPreviewChannels.length - totalPreviewChannelLimit + if (previewChannelsOnly.length) { + // Sort preview channels by expireTime + const sortedPreviewChannels = previewChannelsOnly.sort( + (channelA, channelB) => { + return ( + new Date(channelA.expireTime).getTime() - + new Date(channelB.expireTime).getTime() + ); + } ); + + // Calculate the number of preview channels to remove + const sliceEnd = + totalPreviewChannelToSlice > sortedPreviewChannels.length + ? totalPreviewChannelToSlice - sortedPreviewChannels.length + : sortedPreviewChannels.length - totalPreviewChannelToSlice; + + // Remove the oldest preview channels + return sortedPreviewChannels.slice(0, sliceEnd); } } else { return []; From 748ff6965a33851a675cf9bd947b8697d9638ccb Mon Sep 17 00:00:00 2001 From: Wei Date: Sat, 30 Nov 2024 06:49:08 -0500 Subject: [PATCH 08/11] fix: always add build(bin) while committing --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae714cf0..7e2792b1 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "husky": { "hooks": { - "pre-commit": "pretty-quick --staged && npm run build" + "pre-commit": "pretty-quick --staged && npm run build && git add bin/*" } }, "version": "0.8.0" From f1bfa0fe6b24a8174c226c62a2960840532fc5ea Mon Sep 17 00:00:00 2001 From: Wei Date: Sat, 30 Nov 2024 06:58:25 -0500 Subject: [PATCH 09/11] fix: pre-commit --- .husky/pre-commit | 4 ++++ package-lock.json | 16 ++++++++-------- package.json | 10 ++++------ 3 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..c37466e2 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx lint-staged \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7deba14e..cfc3cb27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@types/jest": "^29.5.1", "@types/tmp": "^0.2.3", "babel-jest": "^29.5.0", - "husky": "^9.0.11", + "husky": "^9.1.7", "jest": "^29.5.0", "microbundle": "^0.15.1", "prettier": "^2.8.7", @@ -5301,12 +5301,12 @@ } }, "node_modules/husky": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", - "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "bin": { - "husky": "bin.mjs" + "husky": "bin.js" }, "engines": { "node": ">=18" @@ -15765,9 +15765,9 @@ "dev": true }, "husky": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", - "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true }, "icss-replace-symbols": { diff --git a/package.json b/package.json index 7e2792b1..2ed051f4 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@types/jest": "^29.5.1", "@types/tmp": "^0.2.3", "babel-jest": "^29.5.0", - "husky": "^9.0.11", + "husky": "^9.1.7", "jest": "^29.5.0", "microbundle": "^0.15.1", "prettier": "^2.8.7", @@ -25,17 +25,15 @@ "typescript": "^5.0.4" }, "scripts": { - "prepare": "husky install", + "prepare": "husky", "format:check": "prettier . --list-different", "format": "prettier --write .", "build": "microbundle --format cjs --target node --no-compress --no-sourcemap src/index.ts", "build:watch": "microbundle watch --format cjs --target node --no-compress --no-sourcemap src/index.ts", "test": "jest" }, - "husky": { - "hooks": { - "pre-commit": "pretty-quick --staged && npm run build && git add bin/*" - } + "lint-staged": { + "**/*": "pretty-quick --staged && npm run build && git add bin/*" }, "version": "0.8.0" } From 0015d5942f61b068610499ca527bbe5f426e895e Mon Sep 17 00:00:00 2001 From: Wei Date: Sat, 30 Nov 2024 07:28:37 -0500 Subject: [PATCH 10/11] fix: pre-commit --- .husky/pre-commit | 5 +- bin/action.min.js | 47 ++++++++--- package-lock.json | 209 +++++++++++++++++++++------------------------- package.json | 10 +-- 4 files changed, 135 insertions(+), 136 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index c37466e2..fafd4386 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx lint-staged \ No newline at end of file +npm test && npm run lint-staged diff --git a/bin/action.min.js b/bin/action.min.js index 29e1b49d..12cad7f8 100644 --- a/bin/action.min.js +++ b/bin/action.min.js @@ -92974,6 +92974,8 @@ function extractChannelIdFromChannelName(channelName) { * See the License for the specific language governing permissions and * limitations under the License. */ +const SITE_CHANNEL_QUOTA = 50; +const SITE_CHANNEL_LIVE_SITE = 1; function interpretChannelDeployResult(deployResult) { const allSiteResults = Object.values(deployResult.result); const expireTime = allSiteResults[0].expireTime; @@ -93036,19 +93038,40 @@ async function getAllChannels(gacFilename, deployConfig) { } } function getPreviewChannelToRemove(channels, totalPreviewChannelLimit) { - // Filter out live channel(hosting default site) and channels without an expireTime(additional sites) - const previewChannelsOnly = channels.filter(channel => { - var _channel$labels; - return (channel == null || (_channel$labels = channel.labels) == null ? void 0 : _channel$labels.type) !== "live" && !!(channel != null && channel.expireTime); - }); - if (previewChannelsOnly.length) { - // Sort preview channels by expireTime - const sortedPreviewChannels = previewChannelsOnly.sort((channelA, channelB) => { - return new Date(channelA.expireTime).getTime() - new Date(channelB.expireTime).getTime(); + let totalAllowedPreviewChannels = totalPreviewChannelLimit; + let totalPreviewChannelToSlice = totalPreviewChannelLimit; + if (totalPreviewChannelLimit >= SITE_CHANNEL_QUOTA - SITE_CHANNEL_LIVE_SITE) { + /** + * If the total number of preview channels is greater than or equal to the site channel quota, + * preview channels is the site channel quota minus the live site channel + * + * e.g. 49(total allowed preview channels) = 50(quota) - 1(live site channel) + */ + totalAllowedPreviewChannels = totalPreviewChannelLimit - SITE_CHANNEL_LIVE_SITE; + /** + * If the total number of preview channels is greater than or equal to the site channel quota, + * total preview channels to slice is the site channel quota plus the live site channel plus the current preview deploy + * + * e.g. 52(total preview channels to slice) = 50(site channel quota) + 1(live site channel) + 1 (current preview deploy) + */ + totalPreviewChannelToSlice = SITE_CHANNEL_QUOTA + SITE_CHANNEL_LIVE_SITE + 1; + } + if (channels.length > totalAllowedPreviewChannels) { + // If the total number of channels exceeds the limit, remove the preview channels + // Filter out live channel(hosting default site) and channels without an expireTime(additional sites) + const previewChannelsOnly = channels.filter(channel => { + var _channel$labels; + return (channel == null || (_channel$labels = channel.labels) == null ? void 0 : _channel$labels.type) !== "live" && !!(channel != null && channel.expireTime); }); - // If the total number of preview channels exceeds the limit, return the ones with earlier expireTime - if (sortedPreviewChannels.length > totalPreviewChannelLimit) { - return sortedPreviewChannels.slice(0, sortedPreviewChannels.length - totalPreviewChannelLimit); + if (previewChannelsOnly.length) { + // Sort preview channels by expireTime + const sortedPreviewChannels = previewChannelsOnly.sort((channelA, channelB) => { + return new Date(channelA.expireTime).getTime() - new Date(channelB.expireTime).getTime(); + }); + // Calculate the number of preview channels to remove + const sliceEnd = totalPreviewChannelToSlice > sortedPreviewChannels.length ? totalPreviewChannelToSlice - sortedPreviewChannels.length : sortedPreviewChannels.length - totalPreviewChannelToSlice; + // Remove the oldest preview channels + return sortedPreviewChannels.slice(0, sliceEnd); } } else { return []; diff --git a/package-lock.json b/package-lock.json index cfc3cb27..690f9a2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,8 +21,8 @@ "husky": "^9.1.7", "jest": "^29.5.0", "microbundle": "^0.15.1", - "prettier": "^2.8.7", - "pretty-quick": "^3.3.1", + "prettier": "^3.4.1", + "pretty-quick": "^4.0.0", "tmp": "^0.2.1", "ts-jest": "^29.1.3", "ts-node": "^10.9.1", @@ -4577,15 +4577,6 @@ "node": ">= 4" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", @@ -5291,15 +5282,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -10036,15 +10018,15 @@ "dev": true }, "node_modules/prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -10089,13 +10071,13 @@ } }, "node_modules/pretty-quick": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-3.3.1.tgz", - "integrity": "sha512-3b36UXfYQ+IXXqex6mCca89jC8u0mYLqFAN5eTQKoXO6oCQYcIVYZEB/5AlBHI7JPYygReM2Vv6Vom/Gln7fBg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-4.0.0.tgz", + "integrity": "sha512-M+2MmeufXb/M7Xw3Afh1gxcYpj+sK0AxEfnfF958ktFeAyi5MsKY5brymVURQLgPLV1QaF5P4pb2oFJ54H3yzQ==", "dev": true, "dependencies": { - "execa": "^4.1.0", - "find-up": "^4.1.0", + "execa": "^5.1.1", + "find-up": "^5.0.0", "ignore": "^5.3.0", "mri": "^1.2.0", "picocolors": "^1.0.0", @@ -10103,48 +10085,71 @@ "tslib": "^2.6.2" }, "bin": { - "pretty-quick": "dist/cli.js" + "pretty-quick": "lib/cli.mjs" }, "engines": { - "node": ">=10.13" + "node": ">=14" }, "peerDependencies": { - "prettier": "^2.0.0" + "prettier": "^3.0.0" } }, - "node_modules/pretty-quick/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "node_modules/pretty-quick/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pretty-quick/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/pretty-quick/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "pump": "^3.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-quick/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-quick/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10184,16 +10189,6 @@ "node": ">= 6" } }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/pure-rand": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", @@ -15212,15 +15207,6 @@ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, "entities": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", @@ -15758,12 +15744,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, "husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -19279,9 +19259,9 @@ "dev": true }, "prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true }, "pretty-bytes": { @@ -19310,13 +19290,13 @@ } }, "pretty-quick": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-3.3.1.tgz", - "integrity": "sha512-3b36UXfYQ+IXXqex6mCca89jC8u0mYLqFAN5eTQKoXO6oCQYcIVYZEB/5AlBHI7JPYygReM2Vv6Vom/Gln7fBg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-4.0.0.tgz", + "integrity": "sha512-M+2MmeufXb/M7Xw3Afh1gxcYpj+sK0AxEfnfF958ktFeAyi5MsKY5brymVURQLgPLV1QaF5P4pb2oFJ54H3yzQ==", "dev": true, "requires": { - "execa": "^4.1.0", - "find-up": "^4.1.0", + "execa": "^5.1.1", + "find-up": "^5.0.0", "ignore": "^5.3.0", "mri": "^1.2.0", "picocolors": "^1.0.0", @@ -19324,30 +19304,41 @@ "tslib": "^2.6.2" }, "dependencies": { - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" } }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { - "pump": "^3.0.0" + "p-limit": "^3.0.2" } }, "picomatch": { @@ -19374,16 +19365,6 @@ "sisteransi": "^1.0.5" } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "pure-rand": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.1.tgz", diff --git a/package.json b/package.json index 2ed051f4..5941a3b3 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "husky": "^9.1.7", "jest": "^29.5.0", "microbundle": "^0.15.1", - "prettier": "^2.8.7", - "pretty-quick": "^3.3.1", + "prettier": "^3.4.1", + "pretty-quick": "^4.0.0", "tmp": "^0.2.1", "ts-jest": "^29.1.3", "ts-node": "^10.9.1", @@ -30,10 +30,8 @@ "format": "prettier --write .", "build": "microbundle --format cjs --target node --no-compress --no-sourcemap src/index.ts", "build:watch": "microbundle watch --format cjs --target node --no-compress --no-sourcemap src/index.ts", - "test": "jest" - }, - "lint-staged": { - "**/*": "pretty-quick --staged && npm run build && git add bin/*" + "test": "jest", + "lint-staged": "pretty-quick --staged && npm run build && git add bin/action.min.js" }, "version": "0.8.0" } From 76185cb3666a87740ebcdf2b689b9f3995505a71 Mon Sep 17 00:00:00 2001 From: Wei Date: Tue, 3 Dec 2024 10:02:31 -0500 Subject: [PATCH 11/11] fix: remove method with proper error handling --- .prettierrc.yaml | 1 + bin/action.min.js | 11 +++++++++-- src/deploy.ts | 43 +++++++++++++++++++++++++++++++------------ 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/.prettierrc.yaml b/.prettierrc.yaml index 76c9b034..b9dfb5e9 100644 --- a/.prettierrc.yaml +++ b/.prettierrc.yaml @@ -13,3 +13,4 @@ # limitations under the License. singleQuote: false +trailingComma: none diff --git a/bin/action.min.js b/bin/action.min.js index 12cad7f8..251f668b 100644 --- a/bin/action.min.js +++ b/bin/action.min.js @@ -93102,14 +93102,21 @@ async function removePreviews({ })); } } -function removeChannel(gacFilename, deployConfig, channelId) { +async function removeChannel(gacFilename, deployConfig, channelId) { const { projectId, + target, firebaseToolsVersion } = deployConfig; - return execWithCredentials(["hosting:channel:delete", channelId, "--force"], projectId, gacFilename, { + const deleteChannelText = await execWithCredentials(["hosting:channel:delete", channelId, ...(target ? ["--site", target] : []), "--force"], projectId, gacFilename, { firebaseToolsVersion }); + const channelResults = JSON.parse(deleteChannelText.trim()); + if (channelResults.status === "error") { + throw Error(channelResults.error); + } else { + return channelResults.status || "success"; + } } async function deployPreview(gacFilename, deployConfig) { const { diff --git a/src/deploy.ts b/src/deploy.ts index 38109953..6327a231 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -29,6 +29,10 @@ export type ErrorResult = { error: string; }; +export type ChannelDeleteSuccessResult = { + status: "success"; +}; + export type ChannelTotalSuccessResult = { status: "success"; channels: Channel[]; @@ -126,7 +130,7 @@ export function interpretChannelDeployResult( return { expireTime, expire_time_formatted, - urls, + urls }; } @@ -148,19 +152,19 @@ async function execWithCredentials( ...(projectId ? ["--project", projectId] : []), debug ? "--debug" // gives a more thorough error message - : "--json", // allows us to easily parse the output + : "--json" // allows us to easily parse the output ], { listeners: { stdout(data: Buffer) { deployOutputBuf.push(data); - }, + } }, env: { ...process.env, FIREBASE_DEPLOY_AGENT: "action-hosting-deploy", - GOOGLE_APPLICATION_CREDENTIALS: gacFilename, // the CLI will automatically authenticate with this env variable set - }, + GOOGLE_APPLICATION_CREDENTIALS: gacFilename // the CLI will automatically authenticate with this env variable set + } } ); } catch (e) { @@ -173,7 +177,7 @@ async function execWithCredentials( ); await execWithCredentials(args, projectId, gacFilename, { debug: true, - firebaseToolsVersion, + firebaseToolsVersion }); } else { throw e; @@ -280,7 +284,7 @@ function getPreviewChannelToRemove( export async function removePreviews({ channels, gacFilename, - deployConfig, + deployConfig }: { channels: Channel[]; gacFilename: string; @@ -311,19 +315,34 @@ export async function removePreviews({ } } -export function removeChannel( +export async function removeChannel( gacFilename: string, deployConfig: Omit, channelId: string ): Promise { - const { projectId, firebaseToolsVersion } = deployConfig; + const { projectId, target, firebaseToolsVersion } = deployConfig; - return execWithCredentials( - ["hosting:channel:delete", channelId, "--force"], + const deleteChannelText = await execWithCredentials( + [ + "hosting:channel:delete", + channelId, + ...(target ? ["--site", target] : []), + "--force" + ], projectId, gacFilename, { firebaseToolsVersion } ); + + const channelResults = JSON.parse(deleteChannelText.trim()) as + | ChannelDeleteSuccessResult + | ErrorResult; + + if (channelResults.status === "error") { + throw Error((channelResults as ErrorResult).error); + } else { + return channelResults.status || "success"; + } } export async function deployPreview( @@ -338,7 +357,7 @@ export async function deployPreview( "hosting:channel:deploy", channelId, ...(target ? ["--only", target] : []), - ...(expires ? ["--expires", expires] : []), + ...(expires ? ["--expires", expires] : []) ], projectId, gacFilename,