From f0aa5f98530cc907a1626a015182ebfde21a7ec8 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 5 Feb 2025 15:13:43 -0600 Subject: [PATCH 01/28] slack-events --- .github/scripts/slack-release-testing.mjs | 34 ++++ .github/scripts/update-gsheet.mjs | 196 ++++++++++++++++++++++ package.json | 3 + 3 files changed, 233 insertions(+) create mode 100644 .github/scripts/slack-release-testing.mjs create mode 100644 .github/scripts/update-gsheet.mjs diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs new file mode 100644 index 00000000..263c49bb --- /dev/null +++ b/.github/scripts/slack-release-testing.mjs @@ -0,0 +1,34 @@ +import { WebClient } from '@slack/web-api'; + +// Create a new instance of the WebClient class with the token read from your environment variables +const token = process.env.SLACK_TOKEN; // Ensure you have your Slack bot token in your environment variables +const web = new WebClient(token); + + +console.log(`Token: ${token}`); +// The name of the channel you want to send the message to +const channel = '#release-mobile-7-38-1'; + + +// DETERMINE ACTIVE RELEASES + +// FOR EACH RELEASE ( MAJ/MIN ) + // Pull J Column from google spreadsheet + + +async function sendMessage(text) { + try { + // Use the `chat.postMessage` method to send a message to the channel + const response = await web.chat.postMessage({ + channel: channel, + text: text + }); + + console.log('Message sent: ', response.ts); + } catch (error) { + console.error('API error:', error); + } +} + +// Example message +sendMessage('Hello world! This is a notification about the mobile release 7.40.0.'); diff --git a/.github/scripts/update-gsheet.mjs b/.github/scripts/update-gsheet.mjs new file mode 100644 index 00000000..8c4bd33c --- /dev/null +++ b/.github/scripts/update-gsheet.mjs @@ -0,0 +1,196 @@ +import { google } from 'googleapis'; +import { WebClient } from '@slack/web-api'; + + +const sheets = google.sheets('v4'); +const token = process.env.SLACK_TOKEN; +const slackClient = new WebClient(token); + +async function getGoogleAuth() { + const auth = new google.auth.GoogleAuth({ + keyFile: '/Users/jakeperkins/Documents/AtomDocs/metamask-wallet-platform-f56ae6b41931.json', + scopes: ['https://www.googleapis.com/auth/spreadsheets'], + }); + + return auth.getClient(); + +} + +async function GetActiveReleases(spreadsheetId) { + const authClient = await getGoogleAuth(); + + try { + const response = await sheets.spreadsheets.get({ + spreadsheetId: spreadsheetId, + auth: authClient, + fields: 'sheets(properties(title,sheetId,hidden))', + }); + + const sheetsData = response.data.sheets; + if (!sheetsData) { + console.log('No sheets found in the spreadsheet.'); + return []; + } + + // Create a list of promises for each sheet using map + const promises = sheetsData.filter(sheet => !sheet.properties.hidden).map(async (sheet) => { + const { title } = sheet.properties; + const versionMatch = title.match(/v(\d+\.\d+\.\d+)/); + const platformMatch = title.match(/\(([^)]+)\)/); + + if (!versionMatch) { + console.log(`Skipping sheet: ${title} - Semantic version not found.`); + return null; // Skip this sheet because we couldn't determine the semantic version + } + + // Await inside async map callback + const testingStatusData = await readSheetData(spreadsheetId, title, 'J1:J1'); + + return { + SemanticVersion: versionMatch[1], + Platform: platformMatch ? platformMatch[1] : 'extension', + sheetId: sheet.properties.sheetId, + testingStatus: testingStatusData ? testingStatusData[0][0] : 'Unknown' + }; + }); + + // Filter out null values (sheets that were skipped) and resolve all promises + const results = await Promise.all(promises); + return results.filter(result => result !== null); + + } catch (err) { + console.error('Failed to retrieve spreadsheet data:', err); + return []; + } +} + + +async function listSheets(spreadsheetId) { + const authClient = await getGoogleAuth(); + + try { + const response = await sheets.spreadsheets.get({ + spreadsheetId: spreadsheetId, + auth: authClient, + fields: 'sheets.properties', // Correctly specify the fields to fetch properties of sheets + }); + + // Extract and log the names of all sheets (tabs) + const sheetData = response.data.sheets; + if (sheetData) { + console.log('List of sheets (tabs) in the spreadsheet:'); + sheetData.forEach((sheet, index) => { // Use sheetData here, not sheets + const hidden = sheet.properties.hidden ? "Hidden" : "Visible"; + console.log(`${index + 1}: ${sheet.properties.title} (Sheet ID: ${sheet.properties.sheetId}) - ${hidden}`); + }); + } else { + console.log('No sheets found in the spreadsheet.'); + } + } catch (err) { + console.error('Failed to retrieve spreadsheet data:', err); + } +} + +/** + * Reads data from a specified cell or range in a single sheet within a Google Spreadsheet. + * @param {string} spreadsheetId - The ID of the Google Spreadsheet. + * @param {string} sheetName - The name of the sheet within the spreadsheet. + * @param {string} cellRange - The A1 notation of the range to read (e.g., 'A1', 'A1:B2'). + * @returns {Promise} The data read from the specified range, or undefined if no data. + */ +async function readSheetData(spreadsheetId, sheetName, cellRange) { + const authClient = await getGoogleAuth(); + + try { + const range = `${sheetName}!${cellRange}`; + const result = await sheets.spreadsheets.values.get({ + spreadsheetId, + range, + auth: authClient, + }); + + return result.data.values; + + } catch (err) { + console.error('Failed to read data from the sheet:', err); + return undefined; + } +} + +async function publishReleaseTestingStatus(release) { + + // convert the version to a format that can be used in a channel name + const formattedVersion = release.SemanticVersion.replace(/\./g, '-'); + + // TODO : Remove -testonly from channel name + const channel = `#release-${release.Platform}-${formattedVersion}-testonly`; + + console.log(`Publishing testing status for release ${release.SemanticVersion} on platform ${release.Platform} to channel ${channel}`); + + + // Construct the message + const message = `[${release.Platform}] - ${release.SemanticVersion} Release Validation. + Release Cut: ${release.ReleaseCut} + Release Tracker Available: ${release.TrackerAvailable} + Target Date for Release Sign-Off: ${release.SignOffTarget} + Deadline Date for Release Submission: ${release.SubmissionDeadline} + :bell: Status Update: ${release.StatusUpdate} + Testing Plan and Progress Tracker Summary (${release.SemanticVersion}): + Teams Sign Off ${release.SemanticVersion} Release on GH: ${release.TeamsSignOffDetails} + Important Reminder: + Please be aware of the importance of starting your testing immediately to ensure there is sufficient time to address any unexpected defects. This proactive approach will help prevent release delays and minimize the impact on other teams’ deliveries. + cc + ${ccHandles}`; + + + + try { + // Use the `chat.postMessage` method to send a message to the channel + const response = await slackClient.chat.postMessage({ + channel: channel, + text: release.testingStatus, + }); + + console.log(`Message successfully sent to channel ${channel} for release ${release.SemanticVersion} on platform ${release.Platform}.`); + } catch (error) { + console.error('API error:', error); + } +} + +async function publishReleasesTestingStatus(activeReleases) { + + console.log('Publishing testing status for all active releases...'); + + try { + const promises = activeReleases.map(release => publishReleaseTestingStatus(release)); + await Promise.all(promises); + } catch (error) { + console.error('An error occurred:', error); + throw error; + } +} + + + +async function main() { + //REAL SHEET + const spreadsheetId = "1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ"; // Use the environment variable + //const spreadsheetId = "1G-9A3cYVQbsE0z5FJUNMkKi5E4tPBoaUuvZmJn3LsVE"; // Example spreadsheet ID + if (!spreadsheetId) { + console.error("Spreadsheet ID is not set. Please set the GOOG_SHEET_ID environment variable."); + return; + } + + await listSheets(spreadsheetId); + + const activeReleases = await GetActiveReleases(spreadsheetId); + console.log('Active Releases:'); + + activeReleases.forEach(release => { + console.log(`Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId}`); + }); + + await publishReleasesTestingStatus(activeReleases); +} + +main(); diff --git a/package.json b/package.json index 1c7a8a28..1970fed2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "lint:fix": "yarn lint:eslint --fix && yarn lint:constraints --fix && yarn lint:misc --write && yarn lint:dependencies", "lint:misc": "prettier '**/*.json' '**/*.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern", "lint:tsc": "tsc", + "slack:release-testing": "node .github/scripts/update-gsheet.mjs", "test": "jest && jest-it-up", "test:watch": "jest --watch" }, @@ -22,8 +23,10 @@ "@octokit/graphql": "^7.0.1", "@octokit/request": "^8.1.1", "@octokit/rest": "^19.0.13", + "@slack/web-api": "^6.0.0", "@types/luxon": "^3.3.0", "axios": "^0.24.0", + "googleapis": "144.0.0", "luxon": "^3.3.0", "ora": "^5.4.1", "simple-git": "3.27.0" From eb2b0ee697e5e71cc5ef044d1bb3ce6d8a19ca22 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 5 Feb 2025 15:13:59 -0600 Subject: [PATCH 02/28] yarn-deps --- yarn.lock | 576 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 572 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index edc3ddbb..f379febd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1005,6 +1005,7 @@ __metadata: "@octokit/graphql": "npm:^7.0.1" "@octokit/request": "npm:^8.1.1" "@octokit/rest": "npm:^19.0.13" + "@slack/web-api": "npm:^6.0.0" "@swc/cli": "npm:^0.1.62" "@swc/core": "npm:^1.3.80" "@types/jest": "npm:^28.1.6" @@ -1022,6 +1023,7 @@ __metadata: eslint-plugin-n: "npm:^15.7.0" eslint-plugin-prettier: "npm:^4.2.1" eslint-plugin-promise: "npm:^6.1.1" + googleapis: "npm:144.0.0" jest: "npm:^28.1.3" jest-it-up: "npm:^2.0.2" luxon: "npm:^3.3.0" @@ -1424,6 +1426,41 @@ __metadata: languageName: node linkType: hard +"@slack/logger@npm:^3.0.0": + version: 3.0.0 + resolution: "@slack/logger@npm:3.0.0" + dependencies: + "@types/node": "npm:>=12.0.0" + checksum: 10/6512d0e9e4be47ea465705ab9b6e6901f36fa981da0d4a657fde649d452b567b351002049b5ee0a22569b5119bf6c2f61befd5b8022d878addb7a99c91b03389 + languageName: node + linkType: hard + +"@slack/types@npm:^2.11.0": + version: 2.14.0 + resolution: "@slack/types@npm:2.14.0" + checksum: 10/fa24a113b88e087f899078504c2ba50ab9795f7c2dd1a2d95b28217a3af20e554494f9cc3b8c8ce173120990d98e19400c95369f9067cecfcc46c08b59d2a46f + languageName: node + linkType: hard + +"@slack/web-api@npm:^6.0.0": + version: 6.13.0 + resolution: "@slack/web-api@npm:6.13.0" + dependencies: + "@slack/logger": "npm:^3.0.0" + "@slack/types": "npm:^2.11.0" + "@types/is-stream": "npm:^1.1.0" + "@types/node": "npm:>=12.0.0" + axios: "npm:^1.7.4" + eventemitter3: "npm:^3.1.0" + form-data: "npm:^2.5.0" + is-electron: "npm:2.2.2" + is-stream: "npm:^1.1.0" + p-queue: "npm:^6.6.1" + p-retry: "npm:^4.0.0" + checksum: 10/f98ccfcab1e82473f14bfbbcd886d52d93c20cc01871bb4a77e49712e1d2e2e686a93bd96a853f7e99d9a6dc6bd8b408ee922b57b1954a49490364c8697357ed + languageName: node + linkType: hard + "@swc/cli@npm:^0.1.62": version: 0.1.62 resolution: "@swc/cli@npm:0.1.62" @@ -1716,6 +1753,15 @@ __metadata: languageName: node linkType: hard +"@types/is-stream@npm:^1.1.0": + version: 1.1.0 + resolution: "@types/is-stream@npm:1.1.0" + dependencies: + "@types/node": "npm:*" + checksum: 10/03ca9635bdea282da17135d297085c325e965af09fecaa0678bb9c5302b9ffa61d089a5056ad4980b6b1871dfd9e6420f0d35da9729fcffeee6bd6348e7c9010 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.3 resolution: "@types/istanbul-lib-coverage@npm:2.0.3" @@ -1802,6 +1848,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:>=12.0.0": + version: 22.13.1 + resolution: "@types/node@npm:22.13.1" + dependencies: + undici-types: "npm:~6.20.0" + checksum: 10/d8ba7068b0445643c0fa6e4917cdb7a90e8756a9daff8c8a332689cd5b2eaa01e4cd07de42e3cd7e6a6f465eeda803d5a1363d00b5ab3f6cea7950350a159497 + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.0 resolution: "@types/parse-json@npm:4.0.0" @@ -1825,6 +1880,13 @@ __metadata: languageName: node linkType: hard +"@types/retry@npm:0.12.0": + version: 0.12.0 + resolution: "@types/retry@npm:0.12.0" + checksum: 10/bbd0b88f4b3eba7b7acfc55ed09c65ef6f2e1bcb4ec9b4dca82c66566934351534317d294a770a7cc6c0468d5573c5350abab6e37c65f8ef254443e1b028e44d + languageName: node + linkType: hard + "@types/semver@npm:^7.3.12": version: 7.3.13 resolution: "@types/semver@npm:7.3.13" @@ -2086,6 +2148,13 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:^7.1.2": + version: 7.1.3 + resolution: "agent-base@npm:7.1.3" + checksum: 10/3db6d8d4651f2aa1a9e4af35b96ab11a7607af57a24f3bc721a387eaa3b5f674e901f0a648b0caefd48f3fd117c7761b79a3b55854e2aebaa96c3f32cf76af84 + languageName: node + linkType: hard + "agentkeepalive@npm:^4.2.1": version: 4.2.1 resolution: "agentkeepalive@npm:4.2.1" @@ -2320,6 +2389,13 @@ __metadata: languageName: node linkType: hard +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10/3ce727cbc78f69d6a4722517a58ee926c8c21083633b1d3fdf66fd688f6c127a53a592141bd4866f9b63240a86e9d8e974b13919450bd17fa33c2d22c4558ad8 + languageName: node + linkType: hard + "available-typed-arrays@npm:^1.0.5": version: 1.0.5 resolution: "available-typed-arrays@npm:1.0.5" @@ -2336,6 +2412,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.7.4": + version: 1.7.9 + resolution: "axios@npm:1.7.9" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10/b7a5f660ea53ba9c2a745bf5ad77ad8bf4f1338e13ccc3f9f09f810267d6c638c03dac88b55dae8dc98b79c57d2d6835be651d58d2af97c174f43d289a9fd007 + languageName: node + linkType: hard + "babel-jest@npm:^28.1.3": version: 28.1.3 resolution: "babel-jest@npm:28.1.3" @@ -2419,7 +2506,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.1": +"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 10/669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -2433,6 +2520,13 @@ __metadata: languageName: node linkType: hard +"bignumber.js@npm:^9.0.0": + version: 9.1.2 + resolution: "bignumber.js@npm:9.1.2" + checksum: 10/d89b8800a987225d2c00dcbf8a69dc08e92aa0880157c851c287b307d31ceb2fc2acb0c62c3e3a3d42b6c5fcae9b004035f13eb4386e56d529d7edac18d5c9d8 + languageName: node + linkType: hard + "bin-check@npm:^4.1.0": version: 4.1.0 resolution: "bin-check@npm:4.1.0" @@ -2554,6 +2648,13 @@ __metadata: languageName: node linkType: hard +"buffer-equal-constant-time@npm:1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10/80bb945f5d782a56f374b292770901065bad21420e34936ecbe949e57724b4a13874f735850dd1cc61f078773c4fb5493a41391e7bda40d1fa388d6bd80daaab + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.1 resolution: "buffer-from@npm:1.1.1" @@ -2628,6 +2729,16 @@ __metadata: languageName: node linkType: hard +"call-bind-apply-helpers@npm:^1.0.1": + version: 1.0.1 + resolution: "call-bind-apply-helpers@npm:1.0.1" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10/6e30c621170e45f1fd6735e84d02ee8e02a3ab95cb109499d5308cbe5d1e84d0cd0e10b48cc43c76aa61450ae1b03a7f89c37c10fc0de8d4998b42aab0f268cc + languageName: node + linkType: hard + "call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": version: 1.0.2 resolution: "call-bind@npm:1.0.2" @@ -2638,6 +2749,16 @@ __metadata: languageName: node linkType: hard +"call-bound@npm:^1.0.2": + version: 1.0.3 + resolution: "call-bound@npm:1.0.3" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + get-intrinsic: "npm:^1.2.6" + checksum: 10/c39a8245f68cdb7c1f5eea7b3b1e3a7a90084ea6efebb78ebc454d698ade2c2bb42ec033abc35f1e596d62496b6100e9f4cdfad1956476c510130e2cda03266d + languageName: node + linkType: hard + "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -2864,6 +2985,15 @@ __metadata: languageName: node linkType: hard +"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10/2e969e637d05d09fa50b02d74c83a1186f6914aae89e6653b62595cc75a221464f884f55f231b8f4df7a49537fba60bdc0427acd2bf324c09a1dbb84837e36e4 + languageName: node + linkType: hard + "commander@npm:^7.1.0": version: 7.2.0 resolution: "commander@npm:7.2.0" @@ -3045,6 +3175,13 @@ __metadata: languageName: node linkType: hard +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10/46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020 + languageName: node + linkType: hard + "delegates@npm:^1.0.0": version: 1.0.0 resolution: "delegates@npm:1.0.0" @@ -3161,6 +3298,26 @@ __metadata: languageName: node linkType: hard +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10/5add88a3d68d42d6e6130a0cac450b7c2edbe73364bbd2fc334564418569bea97c6943a8fcd70e27130bf32afc236f30982fc4905039b703f23e9e0433c29934 + languageName: node + linkType: hard + +"ecdsa-sig-formatter@npm:1.0.11, ecdsa-sig-formatter@npm:^1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10/878e1aab8a42773320bc04c6de420bee21aebd71810e40b1799880a8a1c4594bcd6adc3d4213a0fb8147d4c3f529d8f9a618d7f59ad5a9a41b142058aceda23f + languageName: node + linkType: hard + "electron-to-chromium@npm:^1.4.202": version: 1.4.228 resolution: "electron-to-chromium@npm:1.4.228" @@ -3270,6 +3427,29 @@ __metadata: languageName: node linkType: hard +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10/54fe77de288451dae51c37bfbfe3ec86732dc3778f98f3eb3bdb4bf48063b2c0b8f9c93542656986149d08aa5be3204286e2276053d19582b76753f1a2728867 + languageName: node + linkType: hard + "es-set-tostringtag@npm:^2.0.1": version: 2.0.1 resolution: "es-set-tostringtag@npm:2.0.1" @@ -3673,6 +3853,20 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^3.1.0": + version: 3.1.2 + resolution: "eventemitter3@npm:3.1.2" + checksum: 10/e2886001beb52cd2fe47d2470fd6266b7c70bd3ac356c0041a7e64336ed57bb1fc9b07bc9043d34b39913488a8d81bfcde62d3af597974980aa01b50844d869b + languageName: node + linkType: hard + +"eventemitter3@npm:^4.0.4": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 10/8030029382404942c01d0037079f1b1bc8fed524b5849c237b80549b01e2fc49709e1d0c557fa65ca4498fc9e24cff1475ef7b855121fcc15f9d61f93e282346 + languageName: node + linkType: hard + "execa@npm:^0.7.0": version: 0.7.0 resolution: "execa@npm:0.7.0" @@ -3753,6 +3947,13 @@ __metadata: languageName: node linkType: hard +"extend@npm:^3.0.2": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: 10/59e89e2dc798ec0f54b36d82f32a27d5f6472c53974f61ca098db5d4648430b725387b53449a34df38fd0392045434426b012f302b3cc049a6500ccf82877e4e + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -3905,7 +4106,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.14.4": +"follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.15.6": version: 1.15.9 resolution: "follow-redirects@npm:1.15.9" peerDependenciesMeta: @@ -3924,6 +4125,29 @@ __metadata: languageName: node linkType: hard +"form-data@npm:^2.5.0": + version: 2.5.2 + resolution: "form-data@npm:2.5.2" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.6" + mime-types: "npm:^2.1.12" + safe-buffer: "npm:^5.2.1" + checksum: 10/ef602e52f0bfcc8f8c346b8783f6dbd2fb271596788d42cf929dddaa50bd61e97da21f01464b4524e77872682264765e53c75ac1ab1466ea23f5c96de585faff + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.1 + resolution: "form-data@npm:4.0.1" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 10/6adb1cff557328bc6eb8a68da205f9ae44ab0e88d4d9237aaf91eed591ffc64f77411efb9016af7d87f23d0a038c45a788aa1c6634e51175c4efa36c2bc53774 + languageName: node + linkType: hard + "fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -3966,6 +4190,13 @@ __metadata: languageName: node linkType: hard +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 + languageName: node + linkType: hard + "function.prototype.name@npm:^1.1.5": version: 1.1.5 resolution: "function.prototype.name@npm:1.1.5" @@ -4001,6 +4232,30 @@ __metadata: languageName: node linkType: hard +"gaxios@npm:^6.0.0, gaxios@npm:^6.0.3, gaxios@npm:^6.1.1": + version: 6.7.1 + resolution: "gaxios@npm:6.7.1" + dependencies: + extend: "npm:^3.0.2" + https-proxy-agent: "npm:^7.0.1" + is-stream: "npm:^2.0.0" + node-fetch: "npm:^2.6.9" + uuid: "npm:^9.0.1" + checksum: 10/c85599162208884eadee91215ebbfa1faa412551df4044626cb561300e15193726e8f23d63b486533e066dadad130f58ed872a23acab455238d8d48b531a0695 + languageName: node + linkType: hard + +"gcp-metadata@npm:^6.1.0": + version: 6.1.1 + resolution: "gcp-metadata@npm:6.1.1" + dependencies: + gaxios: "npm:^6.1.1" + google-logging-utils: "npm:^0.0.2" + json-bigint: "npm:^1.0.0" + checksum: 10/f6b1a604d5888db261a9a3ca0a494338b5cdbf815efa393aa38051d814387545bbfd9f25874bf8ea36441f2052625add42658e8973648e53f9b90f151b4bad1b + languageName: node + linkType: hard + "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -4027,6 +4282,24 @@ __metadata: languageName: node linkType: hard +"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6": + version: 1.2.7 + resolution: "get-intrinsic@npm:1.2.7" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.0" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10/4f7149c9a826723f94c6d49f70bcb3df1d3f9213994fab3668f12f09fa72074681460fb29ebb6f135556ec6372992d63802386098791a8f09cfa6f27090fa67b + languageName: node + linkType: hard + "get-package-type@npm:^0.1.0": version: 0.1.0 resolution: "get-package-type@npm:0.1.0" @@ -4034,6 +4307,16 @@ __metadata: languageName: node linkType: hard +"get-proto@npm:^1.0.0": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b + languageName: node + linkType: hard + "get-stream@npm:^3.0.0": version: 3.0.0 resolution: "get-stream@npm:3.0.0" @@ -4174,6 +4457,51 @@ __metadata: languageName: node linkType: hard +"google-auth-library@npm:^9.0.0, google-auth-library@npm:^9.7.0": + version: 9.15.1 + resolution: "google-auth-library@npm:9.15.1" + dependencies: + base64-js: "npm:^1.3.0" + ecdsa-sig-formatter: "npm:^1.0.11" + gaxios: "npm:^6.1.1" + gcp-metadata: "npm:^6.1.0" + gtoken: "npm:^7.0.0" + jws: "npm:^4.0.0" + checksum: 10/6b977dd20f4f1ab6b2d2b78650d1e1c79ca84b951720b1064b85ebbb32af469547db7505a6609265e806be11c823bd6e07323b5073a98729b43b29fe34f05717 + languageName: node + linkType: hard + +"google-logging-utils@npm:^0.0.2": + version: 0.0.2 + resolution: "google-logging-utils@npm:0.0.2" + checksum: 10/f8f5ec3087ef4563d12ee1afc603e6b42b4d703c1f10c9f37b3080e6f4a2e9554e0fd9dcdce97ded5a46ead465c706ff2bc791ad2ca478ed8dc62fdc4b06cac6 + languageName: node + linkType: hard + +"googleapis-common@npm:^7.0.0": + version: 7.2.0 + resolution: "googleapis-common@npm:7.2.0" + dependencies: + extend: "npm:^3.0.2" + gaxios: "npm:^6.0.3" + google-auth-library: "npm:^9.7.0" + qs: "npm:^6.7.0" + url-template: "npm:^2.0.8" + uuid: "npm:^9.0.0" + checksum: 10/4b914be6681f2a5a02bd0954a4a5cee1725d8623cb9d0a7c2fd7132de110e8d5707566cba39784e58147be39e74bc5513ad30fdcdaa6edcbb47ecf687003cb6c + languageName: node + linkType: hard + +"googleapis@npm:144.0.0": + version: 144.0.0 + resolution: "googleapis@npm:144.0.0" + dependencies: + google-auth-library: "npm:^9.0.0" + googleapis-common: "npm:^7.0.0" + checksum: 10/cb6f622a9023653138f9e10903cb0c8c38ab22a12809de6144e796c08e441266b0bca83ab46390b467203e5d2fc14646329e225cc01a09717a694c4b25645376 + languageName: node + linkType: hard + "gopd@npm:^1.0.1": version: 1.0.1 resolution: "gopd@npm:1.0.1" @@ -4183,6 +4511,13 @@ __metadata: languageName: node linkType: hard +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 + languageName: node + linkType: hard + "got@npm:^11.8.5": version: 11.8.6 resolution: "got@npm:11.8.6" @@ -4216,6 +4551,16 @@ __metadata: languageName: node linkType: hard +"gtoken@npm:^7.0.0": + version: 7.1.0 + resolution: "gtoken@npm:7.1.0" + dependencies: + gaxios: "npm:^6.0.0" + jws: "npm:^4.0.0" + checksum: 10/640392261e55c9242137a81a4af8feb053b57061762cedddcbb6a0d62c2314316161808ac2529eea67d06d69fdc56d82361af50f2d840a04a87ea29e124d7382 + languageName: node + linkType: hard + "has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -4260,6 +4605,13 @@ __metadata: languageName: node linkType: hard +"has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa + languageName: node + linkType: hard + "has-tostringtag@npm:^1.0.0": version: 1.0.0 resolution: "has-tostringtag@npm:1.0.0" @@ -4285,6 +4637,15 @@ __metadata: languageName: node linkType: hard +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10/7898a9c1788b2862cf0f9c345a6bec77ba4a0c0983c7f19d610c382343d4f98fa260686b225dfb1f88393a66679d2ec58ee310c1d6868c081eda7918f32cc70a + languageName: node + linkType: hard + "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -4330,6 +4691,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10/784b628cbd55b25542a9d85033bdfd03d4eda630fb8b3c9477959367f3be95dc476ed2ecbb9836c359c7c698027fc7b45723a302324433590f45d6c1706e8c13 + languageName: node + linkType: hard + "human-signals@npm:^2.1.0": version: 2.1.0 resolution: "human-signals@npm:2.1.0" @@ -4532,6 +4903,13 @@ __metadata: languageName: node linkType: hard +"is-electron@npm:2.2.2": + version: 2.2.2 + resolution: "is-electron@npm:2.2.2" + checksum: 10/de5aa8bd8d72c96675b8d0f93fab4cc21f62be5440f65bc05c61338ca27bd851a64200f31f1bf9facbaa01b3dbfed7997b2186741d84b93b63e0aff1db6a9494 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -5272,6 +5650,15 @@ __metadata: languageName: node linkType: hard +"json-bigint@npm:^1.0.0": + version: 1.0.0 + resolution: "json-bigint@npm:1.0.0" + dependencies: + bignumber.js: "npm:^9.0.0" + checksum: 10/cd3973b88e5706f8f89d2a9c9431f206ef385bd5c584db1b258891a5e6642507c32316b82745239088c697f5ddfe967351e1731f5789ba7855aed56ad5f70e1f + languageName: node + linkType: hard + "json-buffer@npm:3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" @@ -5327,6 +5714,27 @@ __metadata: languageName: node linkType: hard +"jwa@npm:^2.0.0": + version: 2.0.0 + resolution: "jwa@npm:2.0.0" + dependencies: + buffer-equal-constant-time: "npm:1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10/ab983f6685d99d13ddfbffef9b1c66309a536362a8412d49ba6e687d834a1240ce39290f30ac7dbe241e0ab6c76fee7ff795776ce534e11d148158c9b7193498 + languageName: node + linkType: hard + +"jws@npm:^4.0.0": + version: 4.0.0 + resolution: "jws@npm:4.0.0" + dependencies: + jwa: "npm:^2.0.0" + safe-buffer: "npm:^5.0.1" + checksum: 10/1d15f4cdea376c6bd6a81002bd2cb0bf3d51d83da8f0727947b5ba3e10cf366721b8c0d099bf8c1eb99eb036e2c55e5fd5efd378ccff75a2b4e0bd10002348b9 + languageName: node + linkType: hard + "keyv@npm:^4.0.0": version: 4.5.3 resolution: "keyv@npm:4.5.3" @@ -5525,6 +5933,13 @@ __metadata: languageName: node linkType: hard +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -5556,13 +5971,22 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:^1.28.0": +"mime-db@npm:1.52.0, mime-db@npm:^1.28.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" checksum: 10/54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 languageName: node linkType: hard +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10/89aa9651b67644035de2784a6e665fc685d79aba61857e02b9c8758da874a754aed4a9aced9265f5ed1171fd934331e5516b84a7f0218031b6fa0270eca1e51a + languageName: node + linkType: hard + "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -5766,6 +6190,20 @@ __metadata: languageName: node linkType: hard +"node-fetch@npm:^2.6.9": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10/b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676 + languageName: node + linkType: hard + "node-gyp@npm:^9.0.0": version: 9.3.1 resolution: "node-gyp@npm:9.3.1" @@ -5900,6 +6338,13 @@ __metadata: languageName: node linkType: hard +"object-inspect@npm:^1.13.3": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb + languageName: node + linkType: hard + "object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -6070,6 +6515,35 @@ __metadata: languageName: node linkType: hard +"p-queue@npm:^6.6.1": + version: 6.6.2 + resolution: "p-queue@npm:6.6.2" + dependencies: + eventemitter3: "npm:^4.0.4" + p-timeout: "npm:^3.2.0" + checksum: 10/60fe227ffce59fbc5b1b081305b61a2f283ff145005853702b7d4d3f99a0176bd21bb126c99a962e51fe1e01cb8aa10f0488b7bbe73b5dc2e84b5cc650b8ffd2 + languageName: node + linkType: hard + +"p-retry@npm:^4.0.0": + version: 4.6.2 + resolution: "p-retry@npm:4.6.2" + dependencies: + "@types/retry": "npm:0.12.0" + retry: "npm:^0.13.1" + checksum: 10/45c270bfddaffb4a895cea16cb760dcc72bdecb6cb45fef1971fa6ea2e91ddeafddefe01e444ac73e33b1b3d5d29fb0dd18a7effb294262437221ddc03ce0f2e + languageName: node + linkType: hard + +"p-timeout@npm:^3.2.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: "npm:^1.0.0" + checksum: 10/3dd0eaa048780a6f23e5855df3dd45c7beacff1f820476c1d0d1bcd6648e3298752ba2c877aa1c92f6453c7dd23faaf13d9f5149fc14c0598a142e2c5e8d649c + languageName: node + linkType: hard + "p-try@npm:^2.0.0": version: 2.2.0 resolution: "p-try@npm:2.2.0" @@ -6282,6 +6756,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10/f0bb4a87cfd18f77bc2fba23ae49c3b378fb35143af16cc478171c623eebe181678f09439707ad80081d340d1593cd54a33a0113f3ccb3f4bc9451488780ee23 + languageName: node + linkType: hard + "pseudomap@npm:^1.0.2": version: 1.0.2 resolution: "pseudomap@npm:1.0.2" @@ -6306,6 +6787,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.7.0": + version: 6.14.0 + resolution: "qs@npm:6.14.0" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 + languageName: node + linkType: hard + "query-ast@npm:^1.0.3": version: 1.0.5 resolution: "query-ast@npm:1.0.5" @@ -6504,6 +6994,13 @@ __metadata: languageName: node linkType: hard +"retry@npm:^0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 10/6125ec2e06d6e47e9201539c887defba4e47f63471db304c59e4b82fc63c8e89ca06a77e9d34939a9a42a76f00774b2f46c0d4a4cbb3e287268bd018ed69426d + languageName: node + linkType: hard + "reusify@npm:^1.0.4": version: 1.0.4 resolution: "reusify@npm:1.0.4" @@ -6550,7 +7047,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 @@ -6687,6 +7184,41 @@ __metadata: languageName: node linkType: hard +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10/603b928997abd21c5a5f02ae6b9cc36b72e3176ad6827fab0417ead74580cc4fb4d5c7d0a8a2ff4ead34d0f9e35701ed7a41853dac8a6d1a664fcce1a044f86f + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10/5771861f77feefe44f6195ed077a9e4f389acc188f895f570d56445e251b861754b547ea9ef73ecee4e01fdada6568bfe9020d2ec2dfc5571e9fa1bbc4a10615 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10/a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736 + languageName: node + linkType: hard + "side-channel@npm:^1.0.4": version: 1.0.4 resolution: "side-channel@npm:1.0.4" @@ -6698,6 +7230,19 @@ __metadata: languageName: node linkType: hard +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10/7d53b9db292c6262f326b6ff3bc1611db84ece36c2c7dc0e937954c13c73185b0406c56589e2bb8d071d6fee468e14c39fb5d203ee39be66b7b8174f179afaba + languageName: node + linkType: hard + "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -7376,6 +7921,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.20.0": + version: 6.20.0 + resolution: "undici-types@npm:6.20.0" + checksum: 10/583ac7bbf4ff69931d3985f4762cde2690bb607844c16a5e2fbb92ed312fe4fa1b365e953032d469fa28ba8b224e88a595f0b10a449332f83fa77c695e567dbe + languageName: node + linkType: hard + "unique-filename@npm:^1.1.1": version: 1.1.1 resolution: "unique-filename@npm:1.1.1" @@ -7424,6 +7976,13 @@ __metadata: languageName: node linkType: hard +"url-template@npm:^2.0.8": + version: 2.0.8 + resolution: "url-template@npm:2.0.8" + checksum: 10/fc6a4cf6c3c3c3d7f0a0bb4405c41b81934e583b454e52ace7b2e5d7ed32ec9c2970ff1826d240c5823955fcb13531a1fc4ff6ba4569b1886a2976665353e952 + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -7431,6 +7990,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.0, uuid@npm:^9.0.1": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10/9d0b6adb72b736e36f2b1b53da0d559125ba3e39d913b6072f6f033e0c87835b414f0836b45bcfaf2bdf698f92297fea1c3cc19b0b258bc182c9c43cc0fab9f2 + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" From f35d48e969a9662d314b43276f36d37fbbedadb4 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 13:23:37 -0600 Subject: [PATCH 03/28] slack-rls-autobot --- .github/scripts/slack-release-testing.mjs | 419 +++++++++++++++++- .github/scripts/team-lookup.mjs | 55 +++ .github/scripts/update-gsheet.mjs | 196 -------- .../publish-slack-release-testing-status.yml | 54 +++ README.md | 2 + package.json | 2 +- yarn.lock | 230 +++++----- 7 files changed, 612 insertions(+), 346 deletions(-) create mode 100644 .github/scripts/team-lookup.mjs delete mode 100644 .github/scripts/update-gsheet.mjs create mode 100644 .github/workflows/publish-slack-release-testing-status.yml diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index 263c49bb..c4e6cbad 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -1,34 +1,413 @@ +import { google } from 'googleapis'; import { WebClient } from '@slack/web-api'; +import { Octokit } from '@octokit/rest'; -// Create a new instance of the WebClient class with the token read from your environment variables -const token = process.env.SLACK_TOKEN; // Ensure you have your Slack bot token in your environment variables -const web = new WebClient(token); +// Clients +const sheets = google.sheets('v4'); +const token = process.env.SLACK_TOKEN; +const githubToken = process.env.GITHUB_TOKEN; +const slackClient = new WebClient(token); +const octokit = new Octokit({ + auth: githubToken + }); +let slackTeamsMap = null; // This will store the mapping of slack team names to IDs -console.log(`Token: ${token}`); -// The name of the channel you want to send the message to -const channel = '#release-mobile-7-38-1'; + /** + * Retrieves and returns a Google authentication client. + * + * This function initializes a GoogleAuth object with a specific key file + * and predefined scopes necessary for accessing Google Sheets API. It + * returns a client instance that can be used to authenticate API requests. + * + * @returns {Promise} Returns a promise that resolves + * to an instance of OAuth2Client which can be used to authenticate + * Google API requests. + */ +async function getGoogleAuth() { -// DETERMINE ACTIVE RELEASES + // Decode base64 string from the environment variable + const credentialsJson = Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIALS_BASE64, 'base64').toString('utf8'); + + // Parse the JSON string to an object + const credentials = JSON.parse(credentialsJson); -// FOR EACH RELEASE ( MAJ/MIN ) - // Pull J Column from google spreadsheet + // Initialize GoogleAuth with credentials object directly + const auth = new google.auth.GoogleAuth({ + credentials: credentials, + scopes: ['https://www.googleapis.com/auth/spreadsheets'], + }); + + return auth.getClient(); +} + +/** + * Initializes the group map by fetching user groups from Slack and mapping their names to IDs. + */ +async function initializeSlackTeams() { + try { + const response = await slackClient.usergroups.list({ include_disabled: false }); + + if (response.ok && response.usergroups) { + slackTeamsMap = response.usergroups.reduce((map, group) => { + map[group.name] = group.id; + return map; + }, {}); + } else { + throw new Error(`Failed to load user groups: ${response.error}`); + } + } catch (error) { + console.error('Error initializing group map:', error); + throw error; + } + console.log('Slack Teams initialized with size of', Object.keys(slackTeamsMap).length); +} + +/** + * Parses release update data from a structured text input into a structured JSON array. + * The expected line format is: + * Emoji: *Team Name* - @SlackHandle There are X total changes. *Pending validation:* Y. *Status:* Z + * + * @param {string} data Multiline string containing release update information for multiple teams. + * @returns {Array} An array of objects, each representing the parsed data of a team's release update. + * Each object includes properties for emoji, team name, Slack handle, changes, pending validations, + * and status, all extracted and converted from the string data. + */ +function parseReleaseUpdates(data) { + const lines = data.split('\n'); + const result = []; + + const regex = /(.*?):\s+\*(.*?)\*\s+-\s+@(.*?)\s+There (?:is|are) (\d+) .*? changes\. \*Pending validation:\* (\d+)\. \*Status:\* (.*)/; -async function sendMessage(text) { - try { - // Use the `chat.postMessage` method to send a message to the channel - const response = await web.chat.postMessage({ - channel: channel, - text: text + lines.forEach(line => { + const match = line.match(regex); + if (match) { + const [_, emoji, team, slackHandle, changes, pendingValidations, status] = match; + result.push({ + emoji: emoji.trim(), + team: team.trim(), + slackHandle: slackHandle.trim(), + changes: parseInt(changes), + pendingValidations: parseInt(pendingValidations), + status: status.trim() + }); + } }); - console.log('Message sent: ', response.ts); - } catch (error) { - console.error('API error:', error); + return result; +} + + +/** + * Determines the release branch for a given platform/version + * @param {string} platform 'mobile' or 'extension' + * @param {string} version semantic version + * @returns + */ +function getReleaseBranchName(platform, version) { + let releaseBranchName; + + if (platform === "mobile") { + releaseBranchName = `release/${version}`; + } else if (platform === "extension") { + releaseBranchName = `Version-v${version}`; + } else { + throw new Error(`Unknown platform '${platform}'. Must be 'mobile' or 'extension'.`); + } + + return releaseBranchName; +} + +/** + * Retrieves the URL of the first pull request for a given branch in a specified GitHub repository. + * + * @param {string} owner - The GitHub username or organization name that owns the repository. + * @param {string} repo - The name of the repository. + * @param {string} branchName - The name of the branch for which to find pull requests. + * @returns {Promise} A promise that resolves to the URL of the first pull request matching the branch name. + * @throws {Error} Throws an error if no pull requests are found for the branch or if there is a problem fetching pull requests. + */ +async function findPullRequestUrlByBranch(owner, repo, branchName) { + try { + // Fetch pull requests that match the branch name + const { data } = await octokit.pulls.list({ + owner, + repo, + head: `${owner}:${branchName}`, // Ensure to include the owner prefix if needed + state: 'all' + }); + + // Check if there are any pull requests returned + if (data.length > 0) { + // Assuming you want the first PR that matches + return data[0].html_url; // Return the URL of the first matching PR + } else { + throw new Error(`No pull requests found for branch ${branchName}`); + } + } catch (error) { + console.error('Error fetching pull requests:', error); + throw error; + } } + +async function GetActiveReleases(documentId) { + const authClient = await getGoogleAuth(); + + try { + const response = await sheets.spreadsheets.get({ + spreadsheetId: documentId, + auth: authClient, + fields: 'sheets(properties(title,sheetId,hidden))', + }); + + const sheetsData = response.data.sheets; + if (!sheetsData) { + console.log('No sheets found in the spreadsheet.'); + return []; + } + + // Create a list of promises for each sheet using map + const promises = sheetsData.filter(sheet => !sheet.properties.hidden).map(async (sheet) => { + const { title } = sheet.properties; + const versionMatch = title.match(/v(\d+\.\d+\.\d+)/); + const platformMatch = title.match(/\(([^)]+)\)/); + + if (!versionMatch) { + console.log(`Skipping sheet: ${title} - Semantic version not found.`); + return null; // Skip this sheet because we couldn't determine the semantic version + } + + // Await inside async map callback + const testingStatusData = await readSheetData(documentId, title, 'J1:J1'); + + return { + DocumentId: documentId, + SemanticVersion: versionMatch[1], + Platform: platformMatch ? platformMatch[1] : 'extension', + sheetId: sheet.properties.sheetId, + testingStatus: testingStatusData ? testingStatusData[0][0] : 'Unknown' + }; + }); + + // Filter out null values (sheets that were skipped) and resolve all promises + const results = await Promise.all(promises); + return results.filter(result => result !== null); + + } catch (err) { + console.error('Failed to retrieve spreadsheet data:', err); + throw err; + } +} + +/** + * Reads data from a specified cell or range in a single sheet within a Google Spreadsheet. + * @param {string} spreadsheetId - The ID of the Google Spreadsheet. + * @param {string} sheetName - The name of the sheet within the spreadsheet. + * @param {string} cellRange - The A1 notation of the range to read (e.g., 'A1', 'A1:B2'). + * @returns {Promise} The data read from the specified range, or undefined if no data. + */ +async function readSheetData(spreadsheetId, sheetName, cellRange) { + const authClient = await getGoogleAuth(); + + try { + const range = `${sheetName}!${cellRange}`; + const result = await sheets.spreadsheets.values.get({ + spreadsheetId, + range, + auth: authClient, + }); + + return result.data.values; + + } catch (err) { + console.error('Failed to read data from the sheet:', err); + throw err; + } +} + +/** + * Determines the number of release blockers for a given release and team + * @param {*} release + * @param {*} team + * @returns + */ +async function getReleaseBlockers(release, team) { + + const versionLabel = `regression-RC-${release.SemanticVersion}`; + + const teamLabel = `team-${team}`.toLowerCase(); + const owner = 'MetaMask'; // Replace with the GitHub owner + const repo = `metamask-${release.Platform}` + + const labels = `${versionLabel},${teamLabel},release-blocker`; + try { + const { data } = await octokit.rest.issues.listForRepo({ + owner, + repo, + labels: labels, + state: 'open' // Optionally, filter by state (open, closed, all) + }); + + const issuesCount = data.length; + const issuesUrl = `https://github.com/${owner}/${repo}/issues?q=is:issue+is:open+label:${encodeURIComponent(versionLabel)}+label:${encodeURIComponent(teamLabel)}+label:release-blocker`; + + return { + count: issuesCount, + url: issuesUrl, + issues: data // Optionally include this if you want the issue data + }; + + } catch (error) { + console.error('Failed to fetch issues:', error); + return error; + } +} + +function testOnly() { + return process.env.TEST_ONLY === 'true'; +} + +/** + * Determine the Slack channel name to publish to based on the release + * @param {*} release + */ +async function getPublishChannelName(release) { + + // convert the version to a format that can be used in a channel name + const formattedVersion = release.SemanticVersion.replace(/\./g, '-'); + + const channel = `#release-${release.Platform}-${formattedVersion}`; + + // Allows for local testing without publishing actual release channels + if (testOnly()) { + return `${channel}-testonly`; + } else { + return channel; + } + } -// Example message -sendMessage('Hello world! This is a notification about the mobile release 7.40.0.'); +async function fmtSlackHandle(team) { + + //Notify if they have pending validations or have not completed signoff + const shouldNotify = team.pendingValidations > 0 || team.status.trim().toLowerCase() !== 'completed'; + console.log(`Team: ${team.team}, Should Notify: ${shouldNotify} because pending validations: ${team.pendingValidations} and status: ${team.status}`); + //Don't notify teams when in testOnly mode + if (testOnly()) { + return shouldNotify ? ` - @${team.slackHandle}` : ''; + } + + //Lookup Slack Team Id for real notifications + const slackTeamId = slackTeamsMap[teamName]; + return shouldNotify ? ` - ` : ''; +} + +/** + * Publishes the testing status for a release to the appropriate Slack channel + * @param {*} release + */ +async function publishReleaseTestingStatus(release) { + + const fmtPlatform = formatTitle(release.Platform); + const teamResults = parseReleaseUpdates(release.testingStatus); + const releasePrUrl = await findPullRequestUrlByBranch('MetaMask', `metamask-${release.Platform}`, getReleaseBranchName(release.Platform, release.SemanticVersion)); + const channel = await getPublishChannelName(release); + + console.log(`Publishing testing status for release ${release.SemanticVersion} on platform ${release.Platform} to channel ${channel}`); + + //Determine notification counts for this release + //const notificationCount = await getNotificationCount(release); + const testingDocumentLink = createSheetUrl(release.DocumentId, release.sheetId); + + // Format the Slack message + var header = `:blablablocker:* [${fmtPlatform}] - ${release.SemanticVersion} Release Validation.*\n` + + `_*Testing Plan and Progress Tracker Summary*_ ():`; + + // Construct the message + var body = `*Teams Sign Off ${release.SemanticVersion} Release on <${releasePrUrl}|GH>:*\n` + + const hasPendingSignoffs = teamResults.some(team => team.status !== "Completed"); + + let releaseBlockerCount = 0; + + for (const team of teamResults) { + let slackHandlePart = await fmtSlackHandle(team); + console.log(`Team: ${team.team}, Slack Handle Part: ${slackHandlePart}`); + const releaseBlockers = await getReleaseBlockers(release, team.team); + //Accumulate the release blocker count + releaseBlockerCount += releaseBlockers.count; + let releaseBlockerParts = releaseBlockers.count > 0 ? ` - <${releaseBlockers.url}|${releaseBlockers.count} Release Blockers>` : ''; + + body += `${team.emoji}: *${team.team}*${slackHandlePart}${releaseBlockerParts}\n`; + } + + if (hasPendingSignoffs) { + header += `\n:bell: *Status Update*: Several Release Signs Offs are still Pending. There are ${releaseBlockerCount} open Release Blockers.\n`; + } + + const footer = `*Important Reminder:*\nPlease be aware of the importance of starting your testing immediately to ensure there is sufficient time to address any unexpected defects. This proactive approach will help prevent release delays and minimize the impact on other teams’ deliveries.`; + + const slackMessage = `${header}\n${body}\n${footer}`; + + + try { + // Use the `chat.postMessage` method to send a message to the channel + const response = await slackClient.chat.postMessage({ + channel: channel, + text: slackMessage, + unfurl_links: false, + unfurl_media: false, + }); + + console.log(`Message successfully sent to channel ${channel} for release ${release.SemanticVersion} on platform ${release.Platform}.`); + } catch (error) { + console.error('API error:', error); + throw error; + } +} + +async function publishReleasesTestingStatus(activeReleases) { + + console.log('Publishing testing status for all active releases...'); + + try { + const promises = activeReleases.map(release => publishReleaseTestingStatus(release)); + await Promise.all(promises); + } catch (error) { + console.error('An error occurred:', error); + throw error; + } +} + +async function main() { + //REAL SHEET + const documentId = process.env.GOOG_DOCUMENT_ID; // Use the environment variable + + if (!documentId) { + console.error("Document ID is not set. Please set the GOOG_DOCUMENT_ID environment variable."); + return; + } + + await initializeSlackTeams(); + + const activeReleases = await GetActiveReleases(documentId); + console.log('Active Releases:'); + + activeReleases.forEach(release => { + console.log(`Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId}`); + }); + + await publishReleasesTestingStatus(activeReleases); +} + +function createSheetUrl(documentId, sheetId) { + return `https://docs.google.com/spreadsheets/d/${documentId}/edit#gid=${sheetId}`; +} + +main(); + +function formatTitle(val) { + return String(val).charAt(0).toUpperCase() + String(val).slice(1); +} diff --git a/.github/scripts/team-lookup.mjs b/.github/scripts/team-lookup.mjs new file mode 100644 index 00000000..fa13b732 --- /dev/null +++ b/.github/scripts/team-lookup.mjs @@ -0,0 +1,55 @@ +import { WebClient } from '@slack/web-api'; + +// Create a new instance of the WebClient class with the token read from your environment variables +const token = process.env.SLACK_TOKEN; // Ensure you have your Slack bot token in your environment variables +const web = new WebClient(token); + +let groupMap = null; // This will store the mapping of team names to IDs + +async function initializeGroupMap() { + try { + const response = await web.usergroups.list({ include_disabled: false }); + + if (response.ok && response.usergroups) { + groupMap = response.usergroups.reduce((map, group) => { + map[group.name] = group.id; + return map; + }, {}); + } else { + throw new Error(`Failed to load user groups: ${response.error}`); + } + } catch (error) { + console.error('Error initializing group map:', error); + } + + console.log('Group map initialized with size of', Object.keys(groupMap).length); +} + +function getGroupIdByName(groupName) { + if (!groupMap) { + console.error('Group map is not initialized.'); + return null; + } + return groupMap[groupName] || null; +} + + +console.log(`Token: ${token}`); +// The name of the channel you want to send the message to +const channel = '#release-mobile-7-38-1'; + + + +async function getSlackTeamId(teamName) { + if (!groupMap) { + console.error('Group map is not initialized.'); + return null; + } + return groupMap[teamName] || null; +} + +await initializeGroupMap(); + +// Example message +const teamId = await getSlackTeamId('assets-team'); +console.log(`Team ID for 'assets-team': ${teamId}`); diff --git a/.github/scripts/update-gsheet.mjs b/.github/scripts/update-gsheet.mjs deleted file mode 100644 index 8c4bd33c..00000000 --- a/.github/scripts/update-gsheet.mjs +++ /dev/null @@ -1,196 +0,0 @@ -import { google } from 'googleapis'; -import { WebClient } from '@slack/web-api'; - - -const sheets = google.sheets('v4'); -const token = process.env.SLACK_TOKEN; -const slackClient = new WebClient(token); - -async function getGoogleAuth() { - const auth = new google.auth.GoogleAuth({ - keyFile: '/Users/jakeperkins/Documents/AtomDocs/metamask-wallet-platform-f56ae6b41931.json', - scopes: ['https://www.googleapis.com/auth/spreadsheets'], - }); - - return auth.getClient(); - -} - -async function GetActiveReleases(spreadsheetId) { - const authClient = await getGoogleAuth(); - - try { - const response = await sheets.spreadsheets.get({ - spreadsheetId: spreadsheetId, - auth: authClient, - fields: 'sheets(properties(title,sheetId,hidden))', - }); - - const sheetsData = response.data.sheets; - if (!sheetsData) { - console.log('No sheets found in the spreadsheet.'); - return []; - } - - // Create a list of promises for each sheet using map - const promises = sheetsData.filter(sheet => !sheet.properties.hidden).map(async (sheet) => { - const { title } = sheet.properties; - const versionMatch = title.match(/v(\d+\.\d+\.\d+)/); - const platformMatch = title.match(/\(([^)]+)\)/); - - if (!versionMatch) { - console.log(`Skipping sheet: ${title} - Semantic version not found.`); - return null; // Skip this sheet because we couldn't determine the semantic version - } - - // Await inside async map callback - const testingStatusData = await readSheetData(spreadsheetId, title, 'J1:J1'); - - return { - SemanticVersion: versionMatch[1], - Platform: platformMatch ? platformMatch[1] : 'extension', - sheetId: sheet.properties.sheetId, - testingStatus: testingStatusData ? testingStatusData[0][0] : 'Unknown' - }; - }); - - // Filter out null values (sheets that were skipped) and resolve all promises - const results = await Promise.all(promises); - return results.filter(result => result !== null); - - } catch (err) { - console.error('Failed to retrieve spreadsheet data:', err); - return []; - } -} - - -async function listSheets(spreadsheetId) { - const authClient = await getGoogleAuth(); - - try { - const response = await sheets.spreadsheets.get({ - spreadsheetId: spreadsheetId, - auth: authClient, - fields: 'sheets.properties', // Correctly specify the fields to fetch properties of sheets - }); - - // Extract and log the names of all sheets (tabs) - const sheetData = response.data.sheets; - if (sheetData) { - console.log('List of sheets (tabs) in the spreadsheet:'); - sheetData.forEach((sheet, index) => { // Use sheetData here, not sheets - const hidden = sheet.properties.hidden ? "Hidden" : "Visible"; - console.log(`${index + 1}: ${sheet.properties.title} (Sheet ID: ${sheet.properties.sheetId}) - ${hidden}`); - }); - } else { - console.log('No sheets found in the spreadsheet.'); - } - } catch (err) { - console.error('Failed to retrieve spreadsheet data:', err); - } -} - -/** - * Reads data from a specified cell or range in a single sheet within a Google Spreadsheet. - * @param {string} spreadsheetId - The ID of the Google Spreadsheet. - * @param {string} sheetName - The name of the sheet within the spreadsheet. - * @param {string} cellRange - The A1 notation of the range to read (e.g., 'A1', 'A1:B2'). - * @returns {Promise} The data read from the specified range, or undefined if no data. - */ -async function readSheetData(spreadsheetId, sheetName, cellRange) { - const authClient = await getGoogleAuth(); - - try { - const range = `${sheetName}!${cellRange}`; - const result = await sheets.spreadsheets.values.get({ - spreadsheetId, - range, - auth: authClient, - }); - - return result.data.values; - - } catch (err) { - console.error('Failed to read data from the sheet:', err); - return undefined; - } -} - -async function publishReleaseTestingStatus(release) { - - // convert the version to a format that can be used in a channel name - const formattedVersion = release.SemanticVersion.replace(/\./g, '-'); - - // TODO : Remove -testonly from channel name - const channel = `#release-${release.Platform}-${formattedVersion}-testonly`; - - console.log(`Publishing testing status for release ${release.SemanticVersion} on platform ${release.Platform} to channel ${channel}`); - - - // Construct the message - const message = `[${release.Platform}] - ${release.SemanticVersion} Release Validation. - Release Cut: ${release.ReleaseCut} - Release Tracker Available: ${release.TrackerAvailable} - Target Date for Release Sign-Off: ${release.SignOffTarget} - Deadline Date for Release Submission: ${release.SubmissionDeadline} - :bell: Status Update: ${release.StatusUpdate} - Testing Plan and Progress Tracker Summary (${release.SemanticVersion}): - Teams Sign Off ${release.SemanticVersion} Release on GH: ${release.TeamsSignOffDetails} - Important Reminder: - Please be aware of the importance of starting your testing immediately to ensure there is sufficient time to address any unexpected defects. This proactive approach will help prevent release delays and minimize the impact on other teams’ deliveries. - cc - ${ccHandles}`; - - - - try { - // Use the `chat.postMessage` method to send a message to the channel - const response = await slackClient.chat.postMessage({ - channel: channel, - text: release.testingStatus, - }); - - console.log(`Message successfully sent to channel ${channel} for release ${release.SemanticVersion} on platform ${release.Platform}.`); - } catch (error) { - console.error('API error:', error); - } -} - -async function publishReleasesTestingStatus(activeReleases) { - - console.log('Publishing testing status for all active releases...'); - - try { - const promises = activeReleases.map(release => publishReleaseTestingStatus(release)); - await Promise.all(promises); - } catch (error) { - console.error('An error occurred:', error); - throw error; - } -} - - - -async function main() { - //REAL SHEET - const spreadsheetId = "1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ"; // Use the environment variable - //const spreadsheetId = "1G-9A3cYVQbsE0z5FJUNMkKi5E4tPBoaUuvZmJn3LsVE"; // Example spreadsheet ID - if (!spreadsheetId) { - console.error("Spreadsheet ID is not set. Please set the GOOG_SHEET_ID environment variable."); - return; - } - - await listSheets(spreadsheetId); - - const activeReleases = await GetActiveReleases(spreadsheetId); - console.log('Active Releases:'); - - activeReleases.forEach(release => { - console.log(`Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId}`); - }); - - await publishReleasesTestingStatus(activeReleases); -} - -main(); diff --git a/.github/workflows/publish-slack-release-testing-status.yml b/.github/workflows/publish-slack-release-testing-status.yml new file mode 100644 index 00000000..e4da6b57 --- /dev/null +++ b/.github/workflows/publish-slack-release-testing-status.yml @@ -0,0 +1,54 @@ +name: Publish Slack Release Testing Status + +on: + workflow_call: + inputs: + platform: # possible values are [ mobile, extension ] + required: true + type: string + slack-api-key: + required: true + type: string + github-token: + required: true + type: string + google-credentials-creds-base64: + required: true + type: string + google-document-id: + required: true + type: string + default: '1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ' + +jobs: + publish-status: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + # Step 1: Checkout github-tools repository + - name: Checkout github-tools repository + uses: actions/checkout@v4 + with: + repository: MetaMask/github-tools + ref: slack-release-testing #TODO Update to main + path: github-tools + + # Step 2: Setup environment from github-tools + - name: Setup environment + uses: ./github-tools/.github/actions/setup-environment + + # Step 3: Run Script + - name: Create Release + id: create-release-pr + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + SLACK_API_KEY: ${{ inputs.slack-api-key }} + GOOG_DOCUMENT_ID: ${{ inputs.google-document-id }} + GOOGLE_CREDENTIALS_CREDS_BASE64: ${{ inputs.google-credentials-creds-base64 }} + TEST_ONLY: 'true' #TODO Remove this line + working-directory: ./github-tools + run: | + yarn run slack:release-testing diff --git a/README.md b/README.md index 03d82dc4..4c8c4ead 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ This repository holds a collection of scripts which are intended to be run local - `yarn get-review-metrics`: Gets the PR load of the extension platform team. - `yarn count-references-to-contributor-docs`: Counts the number of references to the `contributor-docs` repo in pull request comments. +- `yarn run slack:release-testing`: Publishes a notification to slack for active releases regarding the release testing statuses. + ### Authentication Some scripts require a GitHub token in order to run fully. diff --git a/package.json b/package.json index 1970fed2..f4da78d6 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "lint:fix": "yarn lint:eslint --fix && yarn lint:constraints --fix && yarn lint:misc --write && yarn lint:dependencies", "lint:misc": "prettier '**/*.json' '**/*.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern", "lint:tsc": "tsc", - "slack:release-testing": "node .github/scripts/update-gsheet.mjs", + "slack:release-testing": "node .github/scripts/slack-release-testing.mjs", "test": "jest && jest-it-up", "test:watch": "jest --watch" }, diff --git a/yarn.lock b/yarn.lock index f379febd..882dcadd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1841,14 +1841,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^20.3.2": - version: 20.3.2 - resolution: "@types/node@npm:20.3.2" - checksum: 10/e40a7dbfa00ed34cb46b0b9f96e04f51d676d3a719c2763ead831f7875e6a3411356973e99741821f8857dba5c26c00cad79f5c5b9b62af5c9f5ff7eafb02a21 - languageName: node - linkType: hard - -"@types/node@npm:>=12.0.0": +"@types/node@npm:*, @types/node@npm:>=12.0.0": version: 22.13.1 resolution: "@types/node@npm:22.13.1" dependencies: @@ -1857,6 +1850,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.3.2": + version: 20.3.2 + resolution: "@types/node@npm:20.3.2" + checksum: 10/e40a7dbfa00ed34cb46b0b9f96e04f51d676d3a719c2763ead831f7875e6a3411356973e99741821f8857dba5c26c00cad79f5c5b9b62af5c9f5ff7eafb02a21 + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.0 resolution: "@types/parse-json@npm:4.0.0" @@ -2396,10 +2396,12 @@ __metadata: languageName: node linkType: hard -"available-typed-arrays@npm:^1.0.5": - version: 1.0.5 - resolution: "available-typed-arrays@npm:1.0.5" - checksum: 10/4d4d5e86ea0425696f40717882f66a570647b94ac8d273ddc7549a9b61e5da099e149bf431530ccbd776bd74e02039eb8b5edf426e3e2211ee61af16698a9064 +"available-typed-arrays@npm:^1.0.5, available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab languageName: node linkType: hard @@ -2729,7 +2731,7 @@ __metadata: languageName: node linkType: hard -"call-bind-apply-helpers@npm:^1.0.1": +"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1": version: 1.0.1 resolution: "call-bind-apply-helpers@npm:1.0.1" dependencies: @@ -2739,17 +2741,19 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": - version: 1.0.2 - resolution: "call-bind@npm:1.0.2" +"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.8": + version: 1.0.8 + resolution: "call-bind@npm:1.0.8" dependencies: - function-bind: "npm:^1.1.1" - get-intrinsic: "npm:^1.0.2" - checksum: 10/ca787179c1cbe09e1697b56ad499fd05dc0ae6febe5081d728176ade699ea6b1589240cb1ff1fe11fcf9f61538c1af60ad37e8eb2ceb4ef21cd6085dfd3ccedd + call-bind-apply-helpers: "npm:^1.0.0" + es-define-property: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.2" + checksum: 10/659b03c79bbfccf0cde3a79e7d52570724d7290209823e1ca5088f94b52192dc1836b82a324d0144612f816abb2f1734447438e38d9dafe0b3f82c2a1b9e3bce languageName: node linkType: hard -"call-bound@npm:^1.0.2": +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3": version: 1.0.3 resolution: "call-bound@npm:1.0.3" dependencies: @@ -3165,6 +3169,17 @@ __metadata: languageName: node linkType: hard +"define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10/abdcb2505d80a53524ba871273e5da75e77e52af9e15b3aa65d8aad82b8a3a424dad7aee2cc0b71470ac7acf501e08defac362e8b6a73cdb4309f028061df4ae + languageName: node + linkType: hard + "define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0": version: 1.2.0 resolution: "define-properties@npm:1.2.0" @@ -3427,7 +3442,7 @@ __metadata: languageName: node linkType: hard -"es-define-property@npm:^1.0.1": +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": version: 1.0.1 resolution: "es-define-property@npm:1.0.1" checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 @@ -4183,14 +4198,7 @@ __metadata: languageName: node linkType: hard -"function-bind@npm:^1.1.1": - version: 1.1.1 - resolution: "function-bind@npm:1.1.1" - checksum: 10/d83f2968030678f0b8c3f2183d63dcd969344eb8b55b4eb826a94ccac6de8b87c95bebffda37a6386c74f152284eb02956ff2c496897f35d32bdc2628ac68ac5 - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": +"function-bind@npm:^1.1.1, function-bind@npm:^1.1.2": version: 1.1.2 resolution: "function-bind@npm:1.1.2" checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 @@ -4270,19 +4278,7 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1": - version: 1.2.1 - resolution: "get-intrinsic@npm:1.2.1" - dependencies: - function-bind: "npm:^1.1.1" - has: "npm:^1.0.3" - has-proto: "npm:^1.0.1" - has-symbols: "npm:^1.0.3" - checksum: 10/aee631852063f8ad0d4a374970694b5c17c2fb5c92bd1929476d7eb8798ce7aebafbf9a34022c05fd1adaa2ce846d5877a627ce1986f81fc65adf3b81824bd54 - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6": +"get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6": version: 1.2.7 resolution: "get-intrinsic@npm:1.2.7" dependencies: @@ -4502,16 +4498,7 @@ __metadata: languageName: node linkType: hard -"gopd@npm:^1.0.1": - version: 1.0.1 - resolution: "gopd@npm:1.0.1" - dependencies: - get-intrinsic: "npm:^1.1.3" - checksum: 10/5fbc7ad57b368ae4cd2f41214bd947b045c1a4be2f194a7be1778d71f8af9dbf4004221f3b6f23e30820eb0d052b4f819fe6ebe8221e2a3c6f0ee4ef173421ca - languageName: node - linkType: hard - -"gopd@npm:^1.2.0": +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": version: 1.2.0 resolution: "gopd@npm:1.2.0" checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 @@ -4582,12 +4569,12 @@ __metadata: languageName: node linkType: hard -"has-property-descriptors@npm:^1.0.0": - version: 1.0.0 - resolution: "has-property-descriptors@npm:1.0.0" +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" dependencies: - get-intrinsic: "npm:^1.1.1" - checksum: 10/a6d3f0a266d0294d972e354782e872e2fe1b6495b321e6ef678c9b7a06a40408a6891817350c62e752adced73a94ac903c54734fee05bf65b1905ee1368194bb + es-define-property: "npm:^1.0.0" + checksum: 10/2d8c9ab8cebb572e3362f7d06139a4592105983d4317e68f7adba320fe6ddfc8874581e0971e899e633fd5f72e262830edce36d5a0bc863dad17ad20572484b2 languageName: node linkType: hard @@ -4598,26 +4585,19 @@ __metadata: languageName: node linkType: hard -"has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": - version: 1.0.3 - resolution: "has-symbols@npm:1.0.3" - checksum: 10/464f97a8202a7690dadd026e6d73b1ceeddd60fe6acfd06151106f050303eaa75855aaa94969df8015c11ff7c505f196114d22f7386b4a471038da5874cf5e9b - languageName: node - linkType: hard - -"has-symbols@npm:^1.1.0": +"has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": version: 1.1.0 resolution: "has-symbols@npm:1.1.0" checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa languageName: node linkType: hard -"has-tostringtag@npm:^1.0.0": - version: 1.0.0 - resolution: "has-tostringtag@npm:1.0.0" +"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" dependencies: - has-symbols: "npm:^1.0.2" - checksum: 10/95546e7132efc895a9ae64a8a7cf52588601fc3d52e0304ed228f336992cdf0baaba6f3519d2655e560467db35a1ed79f6420c286cc91a13aa0647a31ed92570 + has-symbols: "npm:^1.0.3" + checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe languageName: node linkType: hard @@ -5003,13 +4983,15 @@ __metadata: languageName: node linkType: hard -"is-regex@npm:^1.1.4": - version: 1.1.4 - resolution: "is-regex@npm:1.1.4" +"is-regex@npm:^1.1.4, is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" dependencies: - call-bind: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.0" - checksum: 10/36d9174d16d520b489a5e9001d7d8d8624103b387be300c50f860d9414556d0485d74a612fdafc6ebbd5c89213d947dcc6b6bff6b2312093f71ea03cbb19e564 + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/c42b7efc5868a5c9a4d8e6d3e9816e8815c611b09535c00fead18a1138455c5cb5e1887f0023a467ad3f9c419d62ba4dc3d9ba8bafe55053914d6d6454a945d2 languageName: node linkType: hard @@ -5055,11 +5037,11 @@ __metadata: linkType: hard "is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.9": - version: 1.1.12 - resolution: "is-typed-array@npm:1.1.12" + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" dependencies: - which-typed-array: "npm:^1.1.11" - checksum: 10/d953adfd3c41618d5e01b2a10f21817e4cdc9572772fa17211100aebb3811b6e3c2e308a0558cc87d218a30504cb90154b833013437776551bfb70606fb088ca + which-typed-array: "npm:^1.1.16" + checksum: 10/e8cf60b9ea85667097a6ad68c209c9722cfe8c8edf04d6218366469e51944c5cc25bae45ffb845c23f811d262e4314d3b0168748eb16711aa34d12724cdf0735 languageName: node linkType: hard @@ -6176,21 +6158,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.7": - version: 2.6.11 - resolution: "node-fetch@npm:2.6.11" - dependencies: - whatwg-url: "npm:^5.0.0" - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 10/de59f077d419ecb7889c2fda6c641af99ab7d4131e7a90803b68b2911c81f77483f15d515096603a6dd3dc738b53d8c28b68d47d38c7c41770c0dbf4238fa6fe - languageName: node - linkType: hard - -"node-fetch@npm:^2.6.9": +"node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -6331,14 +6299,7 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.12.3, object-inspect@npm:^1.9.0": - version: 1.12.3 - resolution: "object-inspect@npm:1.12.3" - checksum: 10/532b0036f0472f561180fac0d04fe328ee01f57637624c83fb054f81b5bfe966cdf4200612a499ed391a7ca3c46b20a0bc3a55fc8241d944abe687c556a32b39 - languageName: node - linkType: hard - -"object-inspect@npm:^1.13.3": +"object-inspect@npm:^1.12.3, object-inspect@npm:^1.13.3": version: 1.13.4 resolution: "object-inspect@npm:1.13.4" checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb @@ -6667,6 +6628,13 @@ __metadata: languageName: node linkType: hard +"possible-typed-array-names@npm:^1.0.0": + version: 1.0.0 + resolution: "possible-typed-array-names@npm:1.0.0" + checksum: 10/8ed3e96dfeea1c5880c1f4c9cb707e5fb26e8be22f14f82ef92df20fd2004e635c62ba47fbe8f2bb63bfd80dac1474be2fb39798da8c2feba2815435d1f749af + languageName: node + linkType: hard + "postcss@npm:^8.1.10": version: 8.4.38 resolution: "postcss@npm:8.4.38" @@ -7062,13 +7030,13 @@ __metadata: linkType: hard "safe-regex-test@npm:^1.0.0": - version: 1.0.0 - resolution: "safe-regex-test@npm:1.0.0" + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" dependencies: - call-bind: "npm:^1.0.2" - get-intrinsic: "npm:^1.1.3" - is-regex: "npm:^1.1.4" - checksum: 10/c7248dfa07891aa634c8b9c55da696e246f8589ca50e7fd14b22b154a106e83209ddf061baf2fa45ebfbd485b094dc7297325acfc50724de6afe7138451b42a9 + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10/ebdb61f305bf4756a5b023ad86067df5a11b26898573afe9e52a548a63c3bd594825d9b0e2dde2eb3c94e57e0e04ac9929d4107c394f7b8e56a4613bed46c69a languageName: node linkType: hard @@ -7152,6 +7120,20 @@ __metadata: languageName: node linkType: hard +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/505d62b8e088468917ca4e3f8f39d0e29f9a563b97dbebf92f4bd2c3172ccfb3c5b8e4566d5fcd00784a00433900e7cb8fbc404e2dbd8c3818ba05bb9d4a8a6d + languageName: node + linkType: hard + "shebang-command@npm:^1.2.0": version: 1.2.0 resolution: "shebang-command@npm:1.2.0" @@ -7219,18 +7201,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.4": - version: 1.0.4 - resolution: "side-channel@npm:1.0.4" - dependencies: - call-bind: "npm:^1.0.0" - get-intrinsic: "npm:^1.0.2" - object-inspect: "npm:^1.9.0" - checksum: 10/c4998d9fc530b0e75a7fd791ad868fdc42846f072734f9080ff55cc8dc7d3899abcda24fd896aa6648c3ab7021b4bb478073eb4f44dfd55bce9714bc1a7c5d45 - languageName: node - linkType: hard - -"side-channel@npm:^1.1.0": +"side-channel@npm:^1.0.4, side-channel@npm:^1.1.0": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -8065,16 +8036,17 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.10, which-typed-array@npm:^1.1.11": - version: 1.1.11 - resolution: "which-typed-array@npm:1.1.11" +"which-typed-array@npm:^1.1.10, which-typed-array@npm:^1.1.16": + version: 1.1.18 + resolution: "which-typed-array@npm:1.1.18" dependencies: - available-typed-arrays: "npm:^1.0.5" - call-bind: "npm:^1.0.2" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" - has-tostringtag: "npm:^1.0.0" - checksum: 10/bc9e8690e71d6c64893c9d88a7daca33af45918861003013faf77574a6a49cc6194d32ca7826e90de341d2f9ef3ac9e3acbe332a8ae73cadf07f59b9c6c6ecad + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10/11eed801b2bd08cdbaecb17aff381e0fb03526532f61acc06e6c7b9370e08062c33763a51f27825f13fdf34aabd0df6104007f4e8f96e6eaef7db0ce17a26d6e languageName: node linkType: hard From eafee8483f575d1dc3d30378e68b32dc5709e488 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 13:36:06 -0600 Subject: [PATCH 04/28] add platform filter --- .github/scripts/slack-release-testing.mjs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index c4e6cbad..d83978fb 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -389,13 +389,17 @@ async function main() { console.error("Document ID is not set. Please set the GOOG_DOCUMENT_ID environment variable."); return; } - + + const platform = process.env.PLATFORM; + await initializeSlackTeams(); const activeReleases = await GetActiveReleases(documentId); - console.log('Active Releases:'); - activeReleases.forEach(release => { + // Filter active releases based on the platform + const filteredReleases = activeReleases.filter(release => release.Platform === platform); + + filteredReleases.forEach(release => { console.log(`Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId}`); }); From b0d75a19a24a632b5c2289e06ca9b2e1c5b833f4 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 14:39:56 -0600 Subject: [PATCH 05/28] updates --- .../publish-slack-release-testing-status.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish-slack-release-testing-status.yml b/.github/workflows/publish-slack-release-testing-status.yml index e4da6b57..693b9667 100644 --- a/.github/workflows/publish-slack-release-testing-status.yml +++ b/.github/workflows/publish-slack-release-testing-status.yml @@ -6,19 +6,17 @@ on: platform: # possible values are [ mobile, extension ] required: true type: string - slack-api-key: + google-document-id: required: true type: string + default: '1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ' + secrets: + slack-api-key: + required: true github-token: required: true - type: string google-credentials-creds-base64: required: true - type: string - google-document-id: - required: true - type: string - default: '1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ' jobs: publish-status: From 3269566ec91ea60d5571dec4d574c38721ee9443 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 14:47:19 -0600 Subject: [PATCH 06/28] rls testing --- .../publish-slack-release-testing-status.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-slack-release-testing-status.yml b/.github/workflows/publish-slack-release-testing-status.yml index 693b9667..5d3e5b54 100644 --- a/.github/workflows/publish-slack-release-testing-status.yml +++ b/.github/workflows/publish-slack-release-testing-status.yml @@ -25,7 +25,14 @@ jobs: contents: write pull-requests: write steps: - # Step 1: Checkout github-tools repository + # Step 1: Checkout invoking repository (metamask-mobile | metamask-extension ) + - name: Checkout invoking repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.github-token }} + + # Step 2: Checkout github-tools repository - name: Checkout github-tools repository uses: actions/checkout@v4 with: @@ -33,13 +40,13 @@ jobs: ref: slack-release-testing #TODO Update to main path: github-tools - # Step 2: Setup environment from github-tools + # Step 3: Setup environment from github-tools - name: Setup environment uses: ./github-tools/.github/actions/setup-environment - # Step 3: Run Script - - name: Create Release - id: create-release-pr + # Step 4: Run Script + - name: Publish Slack Release Testing Status + id: publish-slack-release-testing-status shell: bash env: GITHUB_TOKEN: ${{ inputs.github-token }} From 97a093cbcd8b4a980d6e01bf66f0b42bcd0af1a3 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 14:57:40 -0600 Subject: [PATCH 07/28] testing --- .../publish-slack-release-testing-status.yml | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/.github/workflows/publish-slack-release-testing-status.yml b/.github/workflows/publish-slack-release-testing-status.yml index 5d3e5b54..f34ba464 100644 --- a/.github/workflows/publish-slack-release-testing-status.yml +++ b/.github/workflows/publish-slack-release-testing-status.yml @@ -10,6 +10,11 @@ on: required: true type: string default: '1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ' + # Controls whether to actually publish to production slack channels, true will publish to prod slack channels + test-only: + required: false + type: string + default: 'false' secrets: slack-api-key: required: true @@ -25,13 +30,6 @@ jobs: contents: write pull-requests: write steps: - # Step 1: Checkout invoking repository (metamask-mobile | metamask-extension ) - - name: Checkout invoking repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.github-token }} - # Step 2: Checkout github-tools repository - name: Checkout github-tools repository uses: actions/checkout@v4 @@ -40,20 +38,32 @@ jobs: ref: slack-release-testing #TODO Update to main path: github-tools - # Step 3: Setup environment from github-tools - - name: Setup environment - uses: ./github-tools/.github/actions/setup-environment + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: ./github-tools/.nvmrc + cache: yarn + + - name: Enable Corepack + run: corepack enable + shell: bash + working-directory: ./github-tools + + - name: Install dependencies + run: yarn --immutable + shell: bash + working-directory: ./github-tools # Step 4: Run Script - name: Publish Slack Release Testing Status id: publish-slack-release-testing-status shell: bash env: - GITHUB_TOKEN: ${{ inputs.github-token }} - SLACK_API_KEY: ${{ inputs.slack-api-key }} + GITHUB_TOKEN: ${{ secrets.github-token }} + SLACK_API_KEY: ${{ secrets.slack-api-key }} GOOG_DOCUMENT_ID: ${{ inputs.google-document-id }} - GOOGLE_CREDENTIALS_CREDS_BASE64: ${{ inputs.google-credentials-creds-base64 }} - TEST_ONLY: 'true' #TODO Remove this line + GOOGLE_CREDENTIALS_CREDS_BASE64: ${{ secrets.google-credentials-creds-base64 }} + TEST_ONLY: ${{inputs.test-only}} working-directory: ./github-tools run: | yarn run slack:release-testing From e41f43b5e6a468303b17ae077fbe84ee546964d5 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 15:02:24 -0600 Subject: [PATCH 08/28] setup --- .github/workflows/publish-slack-release-testing-status.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-slack-release-testing-status.yml b/.github/workflows/publish-slack-release-testing-status.yml index f34ba464..5e6df4d6 100644 --- a/.github/workflows/publish-slack-release-testing-status.yml +++ b/.github/workflows/publish-slack-release-testing-status.yml @@ -42,6 +42,7 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: ./github-tools/.nvmrc + cache-dependency-path: ./github-tools/yarn.lock cache: yarn - name: Enable Corepack From 1906c19a684c16ae6e7017e51a784f72f4448ddb Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 15:07:06 -0600 Subject: [PATCH 09/28] slack --- .github/scripts/slack-release-testing.mjs | 2 +- .github/workflows/publish-slack-release-testing-status.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index d83978fb..d8f40f34 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -4,7 +4,7 @@ import { Octokit } from '@octokit/rest'; // Clients const sheets = google.sheets('v4'); -const token = process.env.SLACK_TOKEN; +const token = process.env.SLACK_API_KEY; const githubToken = process.env.GITHUB_TOKEN; const slackClient = new WebClient(token); const octokit = new Octokit({ diff --git a/.github/workflows/publish-slack-release-testing-status.yml b/.github/workflows/publish-slack-release-testing-status.yml index 5e6df4d6..e13c2091 100644 --- a/.github/workflows/publish-slack-release-testing-status.yml +++ b/.github/workflows/publish-slack-release-testing-status.yml @@ -20,7 +20,7 @@ on: required: true github-token: required: true - google-credentials-creds-base64: + google-application-creds-base64: required: true jobs: @@ -49,7 +49,7 @@ jobs: run: corepack enable shell: bash working-directory: ./github-tools - + - name: Install dependencies run: yarn --immutable shell: bash @@ -63,7 +63,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.github-token }} SLACK_API_KEY: ${{ secrets.slack-api-key }} GOOG_DOCUMENT_ID: ${{ inputs.google-document-id }} - GOOGLE_CREDENTIALS_CREDS_BASE64: ${{ secrets.google-credentials-creds-base64 }} + GOOGLE_APPLICATION_CREDENTIALS_BASE64: ${{ secrets.google-application-creds-base64 }} TEST_ONLY: ${{inputs.test-only}} working-directory: ./github-tools run: | From 6cedd07648f7bad5960ed0eb83feb3c23a8eaa55 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 15:09:52 -0600 Subject: [PATCH 10/28] remove extra log --- .github/scripts/slack-release-testing.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index d8f40f34..6d2ad3f4 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -293,7 +293,6 @@ async function fmtSlackHandle(team) { //Notify if they have pending validations or have not completed signoff const shouldNotify = team.pendingValidations > 0 || team.status.trim().toLowerCase() !== 'completed'; - console.log(`Team: ${team.team}, Should Notify: ${shouldNotify} because pending validations: ${team.pendingValidations} and status: ${team.status}`); //Don't notify teams when in testOnly mode if (testOnly()) { return shouldNotify ? ` - @${team.slackHandle}` : ''; From 2c1c4aa784772b7d245c11ca04fad2187e4ade09 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 15:12:10 -0600 Subject: [PATCH 11/28] rls-testing --- .github/scripts/slack-release-testing.mjs | 5 +++++ .github/workflows/publish-slack-release-testing-status.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index 6d2ad3f4..77ab0d94 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -391,6 +391,11 @@ async function main() { const platform = process.env.PLATFORM; + if (!platform) { + console.error("Platform is not set. Please set the PLATFORM environment variable."); + return; + } + await initializeSlackTeams(); const activeReleases = await GetActiveReleases(documentId); diff --git a/.github/workflows/publish-slack-release-testing-status.yml b/.github/workflows/publish-slack-release-testing-status.yml index e13c2091..97b23ae2 100644 --- a/.github/workflows/publish-slack-release-testing-status.yml +++ b/.github/workflows/publish-slack-release-testing-status.yml @@ -65,6 +65,7 @@ jobs: GOOG_DOCUMENT_ID: ${{ inputs.google-document-id }} GOOGLE_APPLICATION_CREDENTIALS_BASE64: ${{ secrets.google-application-creds-base64 }} TEST_ONLY: ${{inputs.test-only}} + PLATFORM: ${{inputs.platform}} working-directory: ./github-tools run: | yarn run slack:release-testing From f4dc34d114399e9b8d1048e24bdf104328178d63 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 15:16:09 -0600 Subject: [PATCH 12/28] cleanup --- .github/scripts/slack-release-testing.mjs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index 77ab0d94..0a385ce7 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -333,7 +333,6 @@ async function publishReleaseTestingStatus(release) { for (const team of teamResults) { let slackHandlePart = await fmtSlackHandle(team); - console.log(`Team: ${team.team}, Slack Handle Part: ${slackHandlePart}`); const releaseBlockers = await getReleaseBlockers(release, team.team); //Accumulate the release blocker count releaseBlockerCount += releaseBlockers.count; @@ -352,7 +351,6 @@ async function publishReleaseTestingStatus(release) { try { - // Use the `chat.postMessage` method to send a message to the channel const response = await slackClient.chat.postMessage({ channel: channel, text: slackMessage, @@ -361,6 +359,7 @@ async function publishReleaseTestingStatus(release) { }); console.log(`Message successfully sent to channel ${channel} for release ${release.SemanticVersion} on platform ${release.Platform}.`); + } catch (error) { console.error('API error:', error); throw error; @@ -381,8 +380,8 @@ async function publishReleasesTestingStatus(activeReleases) { } async function main() { - //REAL SHEET - const documentId = process.env.GOOG_DOCUMENT_ID; // Use the environment variable + + const documentId = process.env.GOOG_DOCUMENT_ID; if (!documentId) { console.error("Document ID is not set. Please set the GOOG_DOCUMENT_ID environment variable."); From 94b60315a456d5caf9887af129c651b5b466a517 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 15:21:51 -0600 Subject: [PATCH 13/28] filtered rls --- .github/scripts/slack-release-testing.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index 0a385ce7..4b661afe 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -406,7 +406,7 @@ async function main() { console.log(`Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId}`); }); - await publishReleasesTestingStatus(activeReleases); + await publishReleasesTestingStatus(filteredReleases); } function createSheetUrl(documentId, sheetId) { From efa2622ed917f7ecef4b3e179a5e63ca5fdc7fdf Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 15:49:30 -0600 Subject: [PATCH 14/28] comments/fmt --- .github/scripts/slack-release-testing.mjs | 73 ++++++++++++++++------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index 4b661afe..a3f9c75d 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -152,6 +152,20 @@ async function findPullRequestUrlByBranch(owner, repo, branchName) { } } + /** + * retrieves a list of active releases from a Google Sheets document. + * Each sheet in the document represents a different release. Only sheets with visible + * titles containing a semantic version and optionally a platform within parentheses + * are considered. Each active release is identified by parsing the sheet's title + * for semantic versioning and platform details. + * + * @param {string} documentId - The ID of the Google Sheets document to query. + * @returns {Promise} A promise that resolves to an array of objects, each representing + * an active release with properties for the document ID, semantic version, platform, + * sheet ID, and the testing status. + * @throws {Error} Throws an error if there is an issue retrieving data from the spreadsheet. + * + */ async function GetActiveReleases(documentId) { const authClient = await getGoogleAuth(); @@ -206,7 +220,7 @@ async function GetActiveReleases(documentId) { * @param {string} spreadsheetId - The ID of the Google Spreadsheet. * @param {string} sheetName - The name of the sheet within the spreadsheet. * @param {string} cellRange - The A1 notation of the range to read (e.g., 'A1', 'A1:B2'). - * @returns {Promise} The data read from the specified range, or undefined if no data. + * @returns {Promise} The data read from the specified range, or undefined if no data. */ async function readSheetData(spreadsheetId, sheetName, cellRange) { const authClient = await getGoogleAuth(); @@ -228,10 +242,16 @@ async function readSheetData(spreadsheetId, sheetName, cellRange) { } /** - * Determines the number of release blockers for a given release and team - * @param {*} release - * @param {*} team - * @returns + * fetches the count and details of GitHub issues marked as release blockers + * for a specific version and team. This function queries GitHub issues that are tagged with + * specific labels related to the release version, team, and a "release-blocker" label. + * + * @param {Object} release - An object representing the release + * @param{Object} team - An object representing the team + * @returns {Promise} A promise that resolves to an object containing the count of open release-blocking issues, + * a URL to view these issues on GitHub, and optionally an array of issue objects. + * @throws {Error} Throws an error if the GitHub API call fails. + * */ async function getReleaseBlockers(release, team) { @@ -265,10 +285,6 @@ async function getReleaseBlockers(release, team) { } } -function testOnly() { - return process.env.TEST_ONLY === 'true'; -} - /** * Determine the Slack channel name to publish to based on the release * @param {*} release @@ -305,7 +321,7 @@ async function fmtSlackHandle(team) { /** * Publishes the testing status for a release to the appropriate Slack channel - * @param {*} release + * @param {Object} release represents a release */ async function publishReleaseTestingStatus(release) { @@ -317,14 +333,12 @@ async function publishReleaseTestingStatus(release) { console.log(`Publishing testing status for release ${release.SemanticVersion} on platform ${release.Platform} to channel ${channel}`); //Determine notification counts for this release - //const notificationCount = await getNotificationCount(release); const testingDocumentLink = createSheetUrl(release.DocumentId, release.sheetId); - // Format the Slack message + var header = `:blablablocker:* [${fmtPlatform}] - ${release.SemanticVersion} Release Validation.*\n` + `_*Testing Plan and Progress Tracker Summary*_ ():`; - // Construct the message var body = `*Teams Sign Off ${release.SemanticVersion} Release on <${releasePrUrl}|GH>:*\n` const hasPendingSignoffs = teamResults.some(team => team.status !== "Completed"); @@ -333,8 +347,9 @@ async function publishReleaseTestingStatus(release) { for (const team of teamResults) { let slackHandlePart = await fmtSlackHandle(team); + //Grab RCs for a specific team/release const releaseBlockers = await getReleaseBlockers(release, team.team); - //Accumulate the release blocker count + //Accumulate the total release blocker count releaseBlockerCount += releaseBlockers.count; let releaseBlockerParts = releaseBlockers.count > 0 ? ` - <${releaseBlockers.url}|${releaseBlockers.count} Release Blockers>` : ''; @@ -351,7 +366,7 @@ async function publishReleaseTestingStatus(release) { try { - const response = await slackClient.chat.postMessage({ + await slackClient.chat.postMessage({ channel: channel, text: slackMessage, unfurl_links: false, @@ -366,12 +381,21 @@ async function publishReleaseTestingStatus(release) { } } -async function publishReleasesTestingStatus(activeReleases) { + +/** + * publishes the testing status for a list of releases. + * + * @param {Object[]} releases - An array of release objects. Each release object should be suitable + * for use with the `publishReleaseTestingStatus` function. + * @throws {Error} Throws an error if the publishing process fails for one or more releases. + * + */ +async function publishReleasesTestingStatus(releases) { console.log('Publishing testing status for all active releases...'); try { - const promises = activeReleases.map(release => publishReleaseTestingStatus(release)); + const promises = releases.map(release => publishReleaseTestingStatus(release)); await Promise.all(promises); } catch (error) { console.error('An error occurred:', error); @@ -409,12 +433,19 @@ async function main() { await publishReleasesTestingStatus(filteredReleases); } -function createSheetUrl(documentId, sheetId) { - return `https://docs.google.com/spreadsheets/d/${documentId}/edit#gid=${sheetId}`; -} - +//Entrypoint main(); + +// Helper functions function formatTitle(val) { return String(val).charAt(0).toUpperCase() + String(val).slice(1); } + +function testOnly() { + return process.env.TEST_ONLY === 'true'; +} + +function createSheetUrl(documentId, sheetId) { + return `https://docs.google.com/spreadsheets/d/${documentId}/edit#gid=${sheetId}`; +} From 8fac453095a10e367a90ce3b57ea74b4e960a325 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 12 Feb 2025 15:52:57 -0600 Subject: [PATCH 15/28] fmt --- .github/workflows/publish-slack-release-testing-status.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish-slack-release-testing-status.yml b/.github/workflows/publish-slack-release-testing-status.yml index 97b23ae2..568e7366 100644 --- a/.github/workflows/publish-slack-release-testing-status.yml +++ b/.github/workflows/publish-slack-release-testing-status.yml @@ -9,7 +9,6 @@ on: google-document-id: required: true type: string - default: '1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ' # Controls whether to actually publish to production slack channels, true will publish to prod slack channels test-only: required: false From 784e51b9e9ab4c398e09526b46c2b49572199f99 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Tue, 18 Feb 2025 14:02:18 -0600 Subject: [PATCH 16/28] update-rls-sheet --- .github/scripts/create-platform-release-pr.sh | 21 +- .github/scripts/slack-release-testing.mjs | 2 +- .github/scripts/update-release-sheet.mjs | 259 ++++++++++++++++++ .github/workflows/create-release-pr.yml | 20 +- package.json | 3 +- 5 files changed, 300 insertions(+), 5 deletions(-) create mode 100644 .github/scripts/update-release-sheet.mjs diff --git a/.github/scripts/create-platform-release-pr.sh b/.github/scripts/create-platform-release-pr.sh index b11d2884..618fbfd7 100755 --- a/.github/scripts/create-platform-release-pr.sh +++ b/.github/scripts/create-platform-release-pr.sh @@ -47,10 +47,18 @@ get_release_branch_name() { local platform="$1" # Platform can be 'mobile' or 'extension' local new_version="$2" # Semantic version, e.g., '12.9.2' - if platform == "mobile"; then + # Check if TEST_ONLY environment variable is set to "true" + # This is to run a development version of the release process without impacting downstream automation + if [ "$TEST_ONLY" == "true" ]; then + echo "release-testing/${new_version}" + return 0 + fi + + # Determine the release branch name based on platform + if [ "$platform" == "mobile" ]; then RELEASE_BRANCH_NAME="release/${new_version}" echo "${RELEASE_BRANCH_NAME}" - elif platform == "extension"; then + elif [ "$platform" == "extension" ]; then RELEASE_BRANCH_NAME="Version-v${new_version}" echo "${RELEASE_BRANCH_NAME}" else @@ -60,6 +68,7 @@ get_release_branch_name() { } + RELEASE_BRANCH_NAME=$(get_release_branch_name $PLATFORM $NEW_VERSION) CHANGELOG_BRANCH_NAME="chore/${NEW_VERSION}-Changelog" @@ -157,11 +166,19 @@ corepack prepare yarn@4.5.1 --activate yarn --cwd install echo "Generating test plan csv.." yarn run gen:commits "${PLATFORM}" "${PREVIOUS_VERSION}" "${RELEASE_BRANCH_NAME}" "${PROJECT_GIT_DIR}" + +# TODO Remove +cat ../commits.csv + +echo "Updating release sheet.." +# Create a new Release Sheet Page for the new version with our commits.csv content +yarn yarn run update-release-sheet "${PLATFORM}" "${NEW_VERSION}" "${GOOG_DOCUMENT_ID}" "./commits.csv" "${PROJECT_GIT_DIR}" cd ../ echo "Adding and committing changes.." git add ./commits.csv + if ! (git commit -am "updated changelog and generated feature test plan"); then echo "Error: No changes detected." diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index a3f9c75d..57f0cb28 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -366,7 +366,7 @@ async function publishReleaseTestingStatus(release) { try { - await slackClient.chat.postMessage({ + await slackClient.chat.postMessage({ channel: channel, text: slackMessage, unfurl_links: false, diff --git a/.github/scripts/update-release-sheet.mjs b/.github/scripts/update-release-sheet.mjs new file mode 100644 index 00000000..7d7ebdab --- /dev/null +++ b/.github/scripts/update-release-sheet.mjs @@ -0,0 +1,259 @@ +import { google } from 'googleapis'; + +// Clients +const sheets = google.sheets('v4'); + + /** + * Retrieves and returns a Google authentication client. + * + * This function initializes a GoogleAuth object with a specific key file + * and predefined scopes necessary for accessing Google Sheets API. It + * returns a client instance that can be used to authenticate API requests. + * + * @returns {Promise} Returns a promise that resolves + * to an instance of OAuth2Client which can be used to authenticate + * Google API requests. + */ +async function getGoogleAuth() { + + // Decode base64 string from the environment variable + const credentialsJson = Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIALS_BASE64, 'base64').toString('utf8'); + + // Parse the JSON string to an object + const credentials = JSON.parse(credentialsJson); + + // Initialize GoogleAuth with credentials object directly + const auth = new google.auth.GoogleAuth({ + credentials: credentials, + scopes: ['https://www.googleapis.com/auth/spreadsheets'], + }); + + return auth.getClient(); +} + +/** + * Reads data from a specified cell or range in a single sheet within a Google Spreadsheet. + * @param {string} spreadsheetId - The ID of the Google Spreadsheet. + * @param {string} sheetName - The name of the sheet within the spreadsheet. + * @param {string} cellRange - The A1 notation of the range to read (e.g., 'A1', 'A1:B2'). + * @returns {Promise} The data read from the specified range, or undefined if no data. + */ +async function readSheetData(spreadsheetId, sheetName, cellRange) { + const authClient = await getGoogleAuth(); + + try { + const range = `${sheetName}!${cellRange}`; + const result = await sheets.spreadsheets.values.get({ + spreadsheetId, + range, + auth: authClient, + }); + + return result.data.values; + + } catch (err) { + console.error('Failed to read data from the sheet:', err); + throw err; + } +} + +/** + * Creates a new release sheet by duplicating an existing template sheet and then overwriting + * specific data rows in the new sheet. + * + * @param {string} documentId - The ID of the Google Spreadsheet. + * @param {string} platform - The platform for which the release is being prepared. + * @param {string} semanticVersion - The semantic version of the release. + * @param {number} templateSheetId - The sheet ID of the template to be duplicated. + * @returns {Promise} A promise that resolves when the sheet has been created and modified. + */ +async function createReleaseSheet(documentId, platform, semanticVersion, templateSheetId) { + const authClient = await getGoogleAuth(); + const sheetTitle = `v${semanticVersion} (${platform})`; + + try { + // Step 1: Duplicate the template sheet + const duplicateSheetResponse = await sheets.spreadsheets.batchUpdate({ + spreadsheetId: documentId, + resource: { + requests: [{ + duplicateSheet: { + sourceSheetId: templateSheetId, + newSheetName: sheetTitle, + }, + }], + }, + auth: authClient, + }); + + const newSheetId = duplicateSheetResponse.data.replies[0].duplicateSheet.properties.sheetId; + + console.log(`Sheet duplicated successfully. New sheet ID: ${newSheetId}`); + + + // Optionally, make the new sheet the active sheet + const sheetActivationResponse = await sheets.spreadsheets.batchUpdate({ + spreadsheetId: documentId, + resource: { + requests: [ + { + updateSheetProperties: { + properties: { + sheetId: newSheetId, + hidden: false, + }, + fields: 'hidden', + }, + } + ], + }, + auth: authClient, + }); + + console.log(`Sheet activated successfully.`); + + // Step 3: Update the necessary rows in the new sheet + const values = [ + // Assuming you want to update the first row; adjust range and values as necessary + ["Updated Data 1", "Updated Data 2", "Updated Data 3"], + ]; + const valueRange = `${sheetTitle}!A1:C1`; // Adjust the range according to your needs + + await sheets.spreadsheets.values.update({ + spreadsheetId: documentId, + range: valueRange, + valueInputOption: 'USER_ENTERED', + resource: { + values: values, + }, + auth: authClient, + }); + + console.log('Sheet duplicated and updated successfully.'); + + } catch (error) { + console.error('Error creating release sheet:', error); + throw error; + } +} + + + + + async function GetAllReleases(documentId) { + const authClient = await getGoogleAuth(); + + try { + const response = await sheets.spreadsheets.get({ + spreadsheetId: documentId, + auth: authClient, + fields: 'sheets(properties(title,sheetId,hidden))', + }); + + const sheetsData = response.data.sheets; + if (!sheetsData) { + console.log('No sheets found in the spreadsheet.'); + return []; + } + + // Create a list of promises for each sheet using map + const promises = sheetsData.map(async (sheet) => { + const { title } = sheet.properties; + const versionMatch = title.match(/v(\d+\.\d+\.\d+)/); + const platformMatch = title.match(/\(([^)]+)\)/); + + if (!versionMatch) { + console.log(`Skipping sheet: ${title} - Semantic version not found.`); + return null; // Skip this sheet because we couldn't determine the semantic version + } + + + return { + DocumentId: documentId, + SemanticVersion: versionMatch[1], + Platform: platformMatch ? platformMatch[1] : 'extension', + sheetId: sheet.properties.sheetId, + title: sheet.properties.title, + }; + }); + + // Filter out null values (sheets that were skipped) and resolve all promises + const results = await Promise.all(promises); + return results.filter(result => result !== null); + + } catch (err) { + console.error('Failed to retrieve spreadsheet data:', err); + throw err; + } +} + + async function main() { + + const args = process.argv.slice(2); + if (args.length !== 4) { + console.error( + 'Usage: node update-release-sheet.mjs mobile 7.10.0 documentId ./commits.csv .', + ); + console.error('Received:', args, ' with length:', args.length); + process.exit(1); + } + + const platform = args[0]; + const semanticVersion = args[1]; + const documentId = args[2]; + const commitsFile = args[3]; + const gitDir = args[4]; + + // Change the working directory to the git repository path + // Since this is invoked by a shared workflow, the working directory is not guaranteed to be the repository root + process.chdir(gitDir); + + if (!documentId) { + console.error("Document ID is not set."); + return; + } + + if (!platform) { + console.error("Platform is not set."); + return; + } + + if (!commitsFile) { + console.error("Commits file is not set."); + return; + } + + + if (!semanticVersion) { + console.error("Semantic version is not set."); + return; + } + + const releases = await GetAllReleases(documentId); + + var sheetExists = false; + releases.forEach(release => { + console.log(`Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId} Title: ${release.title}`); + if (release.SemanticVersion === semanticVersion && release.Platform === platform) { + sheetExists = true; + } + }); + + console.log(`Release sheets exists for platform: ${platform}: version: ${semanticVersion} - ${sheetExists}`); + + + if (sheetExists){ + console.log(`Release sheet already exists for platform: ${platform}: version: ${semanticVersion}`); + return; + } + + const templateSheetId = "1885469311"; + + createReleaseSheet(documentId, platform, semanticVersion, templateSheetId); + + + + } + + await main() + diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index f36ec16b..5e2b27bf 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -19,7 +19,18 @@ on: required: true type: string description: 'Previous release version tag. eg: v7.7.0' + # Flag to indicate if the release is a test release for development purposes only + test-only: + required: false + type: string + description: 'If true, the release will be marked as a test release.' + default: 'false' # possible values are [ mobile, extension ] + release-sheet-google-document-id: + required: false + type: string + description: 'The Google Document ID for the release notes.' + default: '1_aJ-cqS5ngrXzAIly3HOyl7CY_lqIoV2T7_OU83AD50' # Release Testing Document platform: required: true type: string @@ -28,6 +39,9 @@ on: github-token: required: true description: 'GitHub token used for authentication.' + google-application-creds-base64: + required: true + description: 'Google application credentials base64 encoded.' jobs: create-release-pr: @@ -50,7 +64,7 @@ jobs: with: repository: MetaMask/github-tools ref: platform-shared-workflows - path: github-tools + path: slack-release-testing # Step 3: Setup environment from github-tools - name: Setup environment @@ -64,6 +78,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.github-token }} BASE_BRANCH: ${{ inputs.base-branch }} GITHUB_REPOSITORY_URL: '${{ github.server_url }}/${{ github.repository }}' + TEST_ONLY: ${{ inputs.test-only }} + GOOGLE_DOCUMENT_ID: ${{ inputs.release-sheet-google-document-id }} + GOOGLE_APPLICATION_CREDENTIALS_BASE64: ${{ secrets.google-application-creds-base64 }} + NEW_VERSION: ${{ inputs.semver-version }} working-directory: ${{ github.workspace }} run: | # Execute the script from github-tools diff --git a/package.json b/package.json index f4da78d6..86b9dbee 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "lint:tsc": "tsc", "slack:release-testing": "node .github/scripts/slack-release-testing.mjs", "test": "jest && jest-it-up", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "update-release-sheet": "node .github/scripts/update-release-sheet.mjs" }, "dependencies": { "@metamask/utils": "^7.1.0", From 0efee9508184f5f36bf5c7614030d700acb7aea2 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Tue, 18 Feb 2025 14:24:03 -0600 Subject: [PATCH 17/28] update ref --- .github/workflows/create-release-pr.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index 5e2b27bf..04c8a2d2 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -24,7 +24,7 @@ on: required: false type: string description: 'If true, the release will be marked as a test release.' - default: 'false' + default: "false" # possible values are [ mobile, extension ] release-sheet-google-document-id: required: false @@ -63,8 +63,8 @@ jobs: uses: actions/checkout@v4 with: repository: MetaMask/github-tools - ref: platform-shared-workflows - path: slack-release-testing + ref: slack-release-testing + path: github-tools # Step 3: Setup environment from github-tools - name: Setup environment From 2e8e1c8d633d27137eb29a50dba66f356157af30 Mon Sep 17 00:00:00 2001 From: jake-perkins <128608287+jake-perkins@users.noreply.github.com> Date: Wed, 19 Feb 2025 10:52:10 -0600 Subject: [PATCH 18/28] Delete .github/scripts/team-lookup.mjs --- .github/scripts/team-lookup.mjs | 55 --------------------------------- 1 file changed, 55 deletions(-) delete mode 100644 .github/scripts/team-lookup.mjs diff --git a/.github/scripts/team-lookup.mjs b/.github/scripts/team-lookup.mjs deleted file mode 100644 index fa13b732..00000000 --- a/.github/scripts/team-lookup.mjs +++ /dev/null @@ -1,55 +0,0 @@ -import { WebClient } from '@slack/web-api'; - -// Create a new instance of the WebClient class with the token read from your environment variables -const token = process.env.SLACK_TOKEN; // Ensure you have your Slack bot token in your environment variables -const web = new WebClient(token); - -let groupMap = null; // This will store the mapping of team names to IDs - -async function initializeGroupMap() { - try { - const response = await web.usergroups.list({ include_disabled: false }); - - if (response.ok && response.usergroups) { - groupMap = response.usergroups.reduce((map, group) => { - map[group.name] = group.id; - return map; - }, {}); - } else { - throw new Error(`Failed to load user groups: ${response.error}`); - } - } catch (error) { - console.error('Error initializing group map:', error); - } - - console.log('Group map initialized with size of', Object.keys(groupMap).length); -} - -function getGroupIdByName(groupName) { - if (!groupMap) { - console.error('Group map is not initialized.'); - return null; - } - return groupMap[groupName] || null; -} - - -console.log(`Token: ${token}`); -// The name of the channel you want to send the message to -const channel = '#release-mobile-7-38-1'; - - - -async function getSlackTeamId(teamName) { - if (!groupMap) { - console.error('Group map is not initialized.'); - return null; - } - return groupMap[teamName] || null; -} - -await initializeGroupMap(); - -// Example message -const teamId = await getSlackTeamId('assets-team'); -console.log(`Team ID for 'assets-team': ${teamId}`); From 5daa460b46d36ff449ffacb8ae25112a9112a85a Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 11:11:32 -0600 Subject: [PATCH 19/28] release-sheet-integration --- .github/scripts/create-platform-release-pr.sh | 3 - .github/scripts/slack-release-testing.mjs | 4 +- .github/scripts/update-release-sheet.mjs | 172 +++++++++++++----- .github/workflows/create-release-pr.yml | 2 +- package.json | 1 + yarn.lock | 8 + 6 files changed, 141 insertions(+), 49 deletions(-) diff --git a/.github/scripts/create-platform-release-pr.sh b/.github/scripts/create-platform-release-pr.sh index 618fbfd7..3cce4eaf 100755 --- a/.github/scripts/create-platform-release-pr.sh +++ b/.github/scripts/create-platform-release-pr.sh @@ -167,9 +167,6 @@ yarn --cwd install echo "Generating test plan csv.." yarn run gen:commits "${PLATFORM}" "${PREVIOUS_VERSION}" "${RELEASE_BRANCH_NAME}" "${PROJECT_GIT_DIR}" -# TODO Remove -cat ../commits.csv - echo "Updating release sheet.." # Create a new Release Sheet Page for the new version with our commits.csv content yarn yarn run update-release-sheet "${PLATFORM}" "${NEW_VERSION}" "${GOOG_DOCUMENT_ID}" "./commits.csv" "${PROJECT_GIT_DIR}" diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index 57f0cb28..e1c6f80f 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -166,7 +166,7 @@ async function findPullRequestUrlByBranch(owner, repo, branchName) { * @throws {Error} Throws an error if there is an issue retrieving data from the spreadsheet. * */ -async function GetActiveReleases(documentId) { +async function getActiveReleases(documentId) { const authClient = await getGoogleAuth(); try { @@ -421,7 +421,7 @@ async function main() { await initializeSlackTeams(); - const activeReleases = await GetActiveReleases(documentId); + const activeReleases = await getActiveReleases(documentId); // Filter active releases based on the platform const filteredReleases = activeReleases.filter(release => release.Platform === platform); diff --git a/.github/scripts/update-release-sheet.mjs b/.github/scripts/update-release-sheet.mjs index 7d7ebdab..9a2997a4 100644 --- a/.github/scripts/update-release-sheet.mjs +++ b/.github/scripts/update-release-sheet.mjs @@ -1,5 +1,9 @@ import { google } from 'googleapis'; +import fs from 'fs'; +import { parse } from 'csv-parse/sync'; + + // Clients const sheets = google.sheets('v4'); @@ -31,32 +35,6 @@ async function getGoogleAuth() { return auth.getClient(); } -/** - * Reads data from a specified cell or range in a single sheet within a Google Spreadsheet. - * @param {string} spreadsheetId - The ID of the Google Spreadsheet. - * @param {string} sheetName - The name of the sheet within the spreadsheet. - * @param {string} cellRange - The A1 notation of the range to read (e.g., 'A1', 'A1:B2'). - * @returns {Promise} The data read from the specified range, or undefined if no data. - */ -async function readSheetData(spreadsheetId, sheetName, cellRange) { - const authClient = await getGoogleAuth(); - - try { - const range = `${sheetName}!${cellRange}`; - const result = await sheets.spreadsheets.values.get({ - spreadsheetId, - range, - auth: authClient, - }); - - return result.data.values; - - } catch (err) { - console.error('Failed to read data from the sheet:', err); - throw err; - } -} - /** * Creates a new release sheet by duplicating an existing template sheet and then overwriting * specific data rows in the new sheet. @@ -67,10 +45,14 @@ async function readSheetData(spreadsheetId, sheetName, cellRange) { * @param {number} templateSheetId - The sheet ID of the template to be duplicated. * @returns {Promise} A promise that resolves when the sheet has been created and modified. */ -async function createReleaseSheet(documentId, platform, semanticVersion, templateSheetId) { +async function createReleaseSheet(documentId, platform, semanticVersion, templateSheetId, sheetData) { const authClient = await getGoogleAuth(); const sheetTitle = `v${semanticVersion} (${platform})`; + const existingSheetCount = await getTotalSheetCount(documentId); + + console.log(`Existing sheet count: ${existingSheetCount}`); + try { // Step 1: Duplicate the template sheet const duplicateSheetResponse = await sheets.spreadsheets.batchUpdate({ @@ -91,7 +73,7 @@ async function createReleaseSheet(documentId, platform, semanticVersion, templat console.log(`Sheet duplicated successfully. New sheet ID: ${newSheetId}`); - // Optionally, make the new sheet the active sheet + // Step 2. Make the new sheet the active sheet const sheetActivationResponse = await sheets.spreadsheets.batchUpdate({ spreadsheetId: documentId, resource: { @@ -101,6 +83,7 @@ async function createReleaseSheet(documentId, platform, semanticVersion, templat properties: { sheetId: newSheetId, hidden: false, + index: existingSheetCount, }, fields: 'hidden', }, @@ -112,35 +95,84 @@ async function createReleaseSheet(documentId, platform, semanticVersion, templat console.log(`Sheet activated successfully.`); - // Step 3: Update the necessary rows in the new sheet - const values = [ - // Assuming you want to update the first row; adjust range and values as necessary - ["Updated Data 1", "Updated Data 2", "Updated Data 3"], - ]; - const valueRange = `${sheetTitle}!A1:C1`; // Adjust the range according to your needs + const dataStartRow = 3; + const dataEndRow = dataStartRow + sheetData.length - 1; + const columnStart = 'A'; + const columnEnd = 'G'; + + const valueRange = `${sheetTitle}!${columnStart}${dataStartRow}:${columnEnd}${dataEndRow}`; + + console.log(`Updating newly provisioned sheet with commit data.`); await sheets.spreadsheets.values.update({ spreadsheetId: documentId, range: valueRange, valueInputOption: 'USER_ENTERED', resource: { - values: values, + values: sheetData, }, auth: authClient, }); console.log('Sheet duplicated and updated successfully.'); + const newSheetCount = await getTotalSheetCount(documentId); + + console.log(`New sheet count: ${newSheetCount}`); + } catch (error) { console.error('Error creating release sheet:', error); throw error; } } +/** + * Retriev1es the total number of sheets in a given Google Sheets document. + * + * @param {string} documentId - The ID of the Google Sheets document from which to retrieve the sheet count. + * @returns {Promise} A promise that resolves with the number of sheets in the specified document. + * @throws {Error} Throws an error if the Google Sheets API call fails or if the authentication process fails. + */ +async function getTotalSheetCount(documentId) { + const authClient = await getGoogleAuth(); + + try { + const response = await sheets.spreadsheets.get({ + spreadsheetId: documentId, + auth: authClient, + fields: 'sheets(properties(title,sheetId))', + }); + + const sheetsData = response.data.sheets; + if (!sheetsData) { + console.log('No sheets found in the spreadsheet.'); + return 0; + } + + return sheetsData.length; + } catch (err) { + console.error('Failed to retrieve spreadsheet data:', err); + throw err; + } +} - async function GetAllReleases(documentId) { + /** + * retrieves a list of all releases from a Google Sheets document. + * Each sheet in the document represents a different release. Only sheets with visible + * titles containing a semantic version and optionally a platform within parentheses + * are considered. Each active release is identified by parsing the sheet's title + * for semantic versioning and platform details. + * + * @param {string} documentId - The ID of the Google Sheets document to query. + * @returns {Promise} A promise that resolves to an array of objects, each representing + * an active release with properties for the document ID, semantic version, platform, + * sheet ID, and the testing status. + * @throws {Error} Throws an error if there is an issue retrieving data from the spreadsheet. + * + */ + async function getAllReleases(documentId) { const authClient = await getGoogleAuth(); try { @@ -163,11 +195,10 @@ async function createReleaseSheet(documentId, platform, semanticVersion, templat const platformMatch = title.match(/\(([^)]+)\)/); if (!versionMatch) { - console.log(`Skipping sheet: ${title} - Semantic version not found.`); + console.log(`Skipping sheet: ${title} with id ${sheet.properties.sheetId} - Semantic version not found.`); return null; // Skip this sheet because we couldn't determine the semantic version } - return { DocumentId: documentId, SemanticVersion: versionMatch[1], @@ -190,9 +221,9 @@ async function createReleaseSheet(documentId, platform, semanticVersion, templat async function main() { const args = process.argv.slice(2); - if (args.length !== 4) { + if (args.length !== 5) { console.error( - 'Usage: node update-release-sheet.mjs mobile 7.10.0 documentId ./commits.csv .', + 'Incorrect argument count. Example Usage: node update-release-sheet.mjs mobile 7.10.0 documentId ./commits.csv .', ); console.error('Received:', args, ' with length:', args.length); process.exit(1); @@ -229,9 +260,12 @@ async function createReleaseSheet(documentId, platform, semanticVersion, templat return; } - const releases = await GetAllReleases(documentId); + const commits = parseCSVv2(commitsFile); + + const releases = await getAllReleases(documentId); var sheetExists = false; + releases.forEach(release => { console.log(`Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId} Title: ${release.title}`); if (release.SemanticVersion === semanticVersion && release.Platform === platform) { @@ -247,12 +281,64 @@ async function createReleaseSheet(documentId, platform, semanticVersion, templat return; } - const templateSheetId = "1885469311"; + const templateSheetId = getTemplateSheetId(platform); - createReleaseSheet(documentId, platform, semanticVersion, templateSheetId); + createReleaseSheet(documentId, platform, semanticVersion, templateSheetId, commits); + } +// TODO These need to be provisioned/updated on the prod worksheet +function getTemplateSheetId(platform) { + switch (platform) { + case 'mobile': + return '514823427'; + case 'extension': + return '599904091'; + default: + throw new Error(`Unknown platform: ${platform}`); + } +} + // Function to parse a CSV file into a 2D array with specific modifications + function parseCSVv2(filePath) { + try { + console.log(`Parsing CSV file: ${filePath}`); + + // Read the entire file content + const fileContent = fs.readFileSync(filePath, { encoding: 'utf8' }); + // Split the content into lines + const lines = fileContent.split('\n'); + + // Initialize the 2D array to hold our processed data + const data2D = []; + + // Start from the second line to skip headers + for (let i = 1; i < lines.length; i++) { + if (lines[i].trim() === '') continue; // Skip empty lines + + // Split the line into columns based on commas + const columns = lines[i].split(','); + + const modifiedColumns = [ + columns[0], // Commit Message + columns[1], // Author + columns[2], // PR Link + '', // Blank string 'd' + '', // Blank string 'e' + columns[3], // Team + columns[4] // Change Type + ]; + + // Add this row to the 2D array + data2D.push(modifiedColumns); + } + + return data2D; + + } catch (error) { + console.error('Failed to parse CSV:', error); + return []; // Return an empty array in case of error + } } await main() diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index 04c8a2d2..04406465 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -24,7 +24,7 @@ on: required: false type: string description: 'If true, the release will be marked as a test release.' - default: "false" + default: 'false' # possible values are [ mobile, extension ] release-sheet-google-document-id: required: false diff --git a/package.json b/package.json index 86b9dbee..5b77bc9c 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@slack/web-api": "^6.0.0", "@types/luxon": "^3.3.0", "axios": "^0.24.0", + "csv-parse": "5.6.0", "googleapis": "144.0.0", "luxon": "^3.3.0", "ora": "^5.4.1", diff --git a/yarn.lock b/yarn.lock index 882dcadd..9a7e42e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1014,6 +1014,7 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:^5.43.0" "@typescript-eslint/parser": "npm:^5.43.0" axios: "npm:^0.24.0" + csv-parse: "npm:5.6.0" depcheck: "npm:^1.4.3" eslint: "npm:^8.44.0" eslint-config-prettier: "npm:^8.8.0" @@ -3102,6 +3103,13 @@ __metadata: languageName: node linkType: hard +"csv-parse@npm:5.6.0": + version: 5.6.0 + resolution: "csv-parse@npm:5.6.0" + checksum: 10/4c82e11f50ae0ccbac2aed716ef2502d0468bf96552083561db789fc0258ee4bb0a30106fcfb2684f153cb4042f0413e0eac3645d5466874803b7ccdeba67ac8 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5": version: 4.4.0 resolution: "debug@npm:4.4.0" From 8903c93734f5f39e2cffa0a87e8fbb471d21bcb4 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 15:24:52 -0600 Subject: [PATCH 20/28] fmt --- .github/scripts/slack-release-testing.mjs | 589 +++++++++++----------- .github/scripts/update-release-sheet.mjs | 508 ++++++++++--------- .yarnrc.yml | 2 +- 3 files changed, 570 insertions(+), 529 deletions(-) diff --git a/.github/scripts/slack-release-testing.mjs b/.github/scripts/slack-release-testing.mjs index e1c6f80f..73a68d0f 100644 --- a/.github/scripts/slack-release-testing.mjs +++ b/.github/scripts/slack-release-testing.mjs @@ -8,61 +8,67 @@ const token = process.env.SLACK_API_KEY; const githubToken = process.env.GITHUB_TOKEN; const slackClient = new WebClient(token); const octokit = new Octokit({ - auth: githubToken - }); + auth: githubToken, +}); let slackTeamsMap = null; // This will store the mapping of slack team names to IDs - - /** +/** * Retrieves and returns a Google authentication client. * * This function initializes a GoogleAuth object with a specific key file - * and predefined scopes necessary for accessing Google Sheets API. It + * and predefined scopes necessary for accessing Google Sheets API. It * returns a client instance that can be used to authenticate API requests. * - * @returns {Promise} Returns a promise that resolves - * to an instance of OAuth2Client which can be used to authenticate + * @returns {Promise} Returns a promise that resolves + * to an instance of OAuth2Client which can be used to authenticate * Google API requests. */ async function getGoogleAuth() { + // Decode base64 string from the environment variable + const credentialsJson = Buffer.from( + process.env.GOOGLE_APPLICATION_CREDENTIALS_BASE64, + 'base64', + ).toString('utf8'); + + // Parse the JSON string to an object + const credentials = JSON.parse(credentialsJson); + + // Initialize GoogleAuth with credentials object directly + const auth = new google.auth.GoogleAuth({ + credentials: credentials, + scopes: ['https://www.googleapis.com/auth/spreadsheets'], + }); - // Decode base64 string from the environment variable - const credentialsJson = Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIALS_BASE64, 'base64').toString('utf8'); - - // Parse the JSON string to an object - const credentials = JSON.parse(credentialsJson); - - // Initialize GoogleAuth with credentials object directly - const auth = new google.auth.GoogleAuth({ - credentials: credentials, - scopes: ['https://www.googleapis.com/auth/spreadsheets'], - }); - - return auth.getClient(); + return auth.getClient(); } /** * Initializes the group map by fetching user groups from Slack and mapping their names to IDs. */ async function initializeSlackTeams() { - try { - const response = await slackClient.usergroups.list({ include_disabled: false }); - - if (response.ok && response.usergroups) { - slackTeamsMap = response.usergroups.reduce((map, group) => { - map[group.name] = group.id; - return map; - }, {}); - } else { - throw new Error(`Failed to load user groups: ${response.error}`); - } - } catch (error) { - console.error('Error initializing group map:', error); - throw error; + try { + const response = await slackClient.usergroups.list({ + include_disabled: false, + }); + + if (response.ok && response.usergroups) { + slackTeamsMap = response.usergroups.reduce((map, group) => { + map[group.name] = group.id; + return map; + }, {}); + } else { + throw new Error(`Failed to load user groups: ${response.error}`); } + } catch (error) { + console.error('Error initializing group map:', error); + throw error; + } - console.log('Slack Teams initialized with size of', Object.keys(slackTeamsMap).length); + console.log( + 'Slack Teams initialized with size of', + Object.keys(slackTeamsMap).length, + ); } /** @@ -76,48 +82,51 @@ async function initializeSlackTeams() { * and status, all extracted and converted from the string data. */ function parseReleaseUpdates(data) { - const lines = data.split('\n'); - const result = []; - - const regex = /(.*?):\s+\*(.*?)\*\s+-\s+@(.*?)\s+There (?:is|are) (\d+) .*? changes\. \*Pending validation:\* (\d+)\. \*Status:\* (.*)/; - - lines.forEach(line => { - const match = line.match(regex); - if (match) { - const [_, emoji, team, slackHandle, changes, pendingValidations, status] = match; - result.push({ - emoji: emoji.trim(), - team: team.trim(), - slackHandle: slackHandle.trim(), - changes: parseInt(changes), - pendingValidations: parseInt(pendingValidations), - status: status.trim() - }); - } - }); + const lines = data.split('\n'); + const result = []; + + const regex = + /(.*?):\s+\*(.*?)\*\s+-\s+@(.*?)\s+There (?:is|are) (\d+) .*? changes\. \*Pending validation:\* (\d+)\. \*Status:\* (.*)/; + + lines.forEach((line) => { + const match = line.match(regex); + if (match) { + const [_, emoji, team, slackHandle, changes, pendingValidations, status] = + match; + result.push({ + emoji: emoji.trim(), + team: team.trim(), + slackHandle: slackHandle.trim(), + changes: parseInt(changes), + pendingValidations: parseInt(pendingValidations), + status: status.trim(), + }); + } + }); - return result; + return result; } - /** * Determines the release branch for a given platform/version * @param {string} platform 'mobile' or 'extension' * @param {string} version semantic version - * @returns + * @returns */ function getReleaseBranchName(platform, version) { - let releaseBranchName; - - if (platform === "mobile") { - releaseBranchName = `release/${version}`; - } else if (platform === "extension") { - releaseBranchName = `Version-v${version}`; - } else { - throw new Error(`Unknown platform '${platform}'. Must be 'mobile' or 'extension'.`); - } + let releaseBranchName; + + if (platform === 'mobile') { + releaseBranchName = `release/${version}`; + } else if (platform === 'extension') { + releaseBranchName = `Version-v${version}`; + } else { + throw new Error( + `Unknown platform '${platform}'. Must be 'mobile' or 'extension'.`, + ); + } - return releaseBranchName; + return releaseBranchName; } /** @@ -130,29 +139,29 @@ function getReleaseBranchName(platform, version) { * @throws {Error} Throws an error if no pull requests are found for the branch or if there is a problem fetching pull requests. */ async function findPullRequestUrlByBranch(owner, repo, branchName) { - try { - // Fetch pull requests that match the branch name - const { data } = await octokit.pulls.list({ - owner, - repo, - head: `${owner}:${branchName}`, // Ensure to include the owner prefix if needed - state: 'all' - }); - - // Check if there are any pull requests returned - if (data.length > 0) { - // Assuming you want the first PR that matches - return data[0].html_url; // Return the URL of the first matching PR - } else { - throw new Error(`No pull requests found for branch ${branchName}`); - } - } catch (error) { - console.error('Error fetching pull requests:', error); - throw error; + try { + // Fetch pull requests that match the branch name + const { data } = await octokit.pulls.list({ + owner, + repo, + head: `${owner}:${branchName}`, // Ensure to include the owner prefix if needed + state: 'all', + }); + + // Check if there are any pull requests returned + if (data.length > 0) { + // Assuming you want the first PR that matches + return data[0].html_url; // Return the URL of the first matching PR + } else { + throw new Error(`No pull requests found for branch ${branchName}`); } + } catch (error) { + console.error('Error fetching pull requests:', error); + throw error; } +} - /** +/** * retrieves a list of active releases from a Google Sheets document. * Each sheet in the document represents a different release. Only sheets with visible * titles containing a semantic version and optionally a platform within parentheses @@ -167,52 +176,59 @@ async function findPullRequestUrlByBranch(owner, repo, branchName) { * */ async function getActiveReleases(documentId) { - const authClient = await getGoogleAuth(); - - try { - const response = await sheets.spreadsheets.get({ - spreadsheetId: documentId, - auth: authClient, - fields: 'sheets(properties(title,sheetId,hidden))', - }); - - const sheetsData = response.data.sheets; - if (!sheetsData) { - console.log('No sheets found in the spreadsheet.'); - return []; - } + const authClient = await getGoogleAuth(); + + try { + const response = await sheets.spreadsheets.get({ + spreadsheetId: documentId, + auth: authClient, + fields: 'sheets(properties(title,sheetId,hidden))', + }); - // Create a list of promises for each sheet using map - const promises = sheetsData.filter(sheet => !sheet.properties.hidden).map(async (sheet) => { - const { title } = sheet.properties; - const versionMatch = title.match(/v(\d+\.\d+\.\d+)/); - const platformMatch = title.match(/\(([^)]+)\)/); - - if (!versionMatch) { - console.log(`Skipping sheet: ${title} - Semantic version not found.`); - return null; // Skip this sheet because we couldn't determine the semantic version - } - - // Await inside async map callback - const testingStatusData = await readSheetData(documentId, title, 'J1:J1'); - - return { - DocumentId: documentId, - SemanticVersion: versionMatch[1], - Platform: platformMatch ? platformMatch[1] : 'extension', - sheetId: sheet.properties.sheetId, - testingStatus: testingStatusData ? testingStatusData[0][0] : 'Unknown' - }; - }); - - // Filter out null values (sheets that were skipped) and resolve all promises - const results = await Promise.all(promises); - return results.filter(result => result !== null); - - } catch (err) { - console.error('Failed to retrieve spreadsheet data:', err); - throw err; + const sheetsData = response.data.sheets; + if (!sheetsData) { + console.log('No sheets found in the spreadsheet.'); + return []; } + + // Create a list of promises for each sheet using map + const promises = sheetsData + .filter((sheet) => !sheet.properties.hidden) + .map(async (sheet) => { + const { title } = sheet.properties; + const versionMatch = title.match(/v(\d+\.\d+\.\d+)/); + const platformMatch = title.match(/\(([^)]+)\)/); + + if (!versionMatch) { + console.log(`Skipping sheet: ${title} - Semantic version not found.`); + return null; // Skip this sheet because we couldn't determine the semantic version + } + + // Await inside async map callback + const testingStatusData = await readSheetData( + documentId, + title, + 'J1:J1', + ); + + return { + DocumentId: documentId, + SemanticVersion: versionMatch[1], + Platform: platformMatch ? platformMatch[1] : 'extension', + sheetId: sheet.properties.sheetId, + testingStatus: testingStatusData + ? testingStatusData[0][0] + : 'Unknown', + }; + }); + + // Filter out null values (sheets that were skipped) and resolve all promises + const results = await Promise.all(promises); + return results.filter((result) => result !== null); + } catch (err) { + console.error('Failed to retrieve spreadsheet data:', err); + throw err; + } } /** @@ -223,22 +239,21 @@ async function getActiveReleases(documentId) { * @returns {Promise} The data read from the specified range, or undefined if no data. */ async function readSheetData(spreadsheetId, sheetName, cellRange) { - const authClient = await getGoogleAuth(); - - try { - const range = `${sheetName}!${cellRange}`; - const result = await sheets.spreadsheets.values.get({ - spreadsheetId, - range, - auth: authClient, - }); - - return result.data.values; + const authClient = await getGoogleAuth(); + + try { + const range = `${sheetName}!${cellRange}`; + const result = await sheets.spreadsheets.values.get({ + spreadsheetId, + range, + auth: authClient, + }); - } catch (err) { - console.error('Failed to read data from the sheet:', err); - throw err; - } + return result.data.values; + } catch (err) { + console.error('Failed to read data from the sheet:', err); + throw err; + } } /** @@ -254,69 +269,68 @@ async function readSheetData(spreadsheetId, sheetName, cellRange) { * */ async function getReleaseBlockers(release, team) { + const versionLabel = `regression-RC-${release.SemanticVersion}`; + + const teamLabel = `team-${team}`.toLowerCase(); + const owner = 'MetaMask'; // Replace with the GitHub owner + const repo = `metamask-${release.Platform}`; + + const labels = `${versionLabel},${teamLabel},release-blocker`; + try { + const { data } = await octokit.rest.issues.listForRepo({ + owner, + repo, + labels: labels, + state: 'open', // Optionally, filter by state (open, closed, all) + }); - const versionLabel = `regression-RC-${release.SemanticVersion}`; - - const teamLabel = `team-${team}`.toLowerCase(); - const owner = 'MetaMask'; // Replace with the GitHub owner - const repo = `metamask-${release.Platform}` - - const labels = `${versionLabel},${teamLabel},release-blocker`; - try { - const { data } = await octokit.rest.issues.listForRepo({ - owner, - repo, - labels: labels, - state: 'open' // Optionally, filter by state (open, closed, all) - }); - - const issuesCount = data.length; - const issuesUrl = `https://github.com/${owner}/${repo}/issues?q=is:issue+is:open+label:${encodeURIComponent(versionLabel)}+label:${encodeURIComponent(teamLabel)}+label:release-blocker`; - - return { - count: issuesCount, - url: issuesUrl, - issues: data // Optionally include this if you want the issue data - }; - - } catch (error) { - console.error('Failed to fetch issues:', error); - return error; - } + const issuesCount = data.length; + const issuesUrl = `https://github.com/${owner}/${repo}/issues?q=is:issue+is:open+label:${encodeURIComponent( + versionLabel, + )}+label:${encodeURIComponent(teamLabel)}+label:release-blocker`; + + return { + count: issuesCount, + url: issuesUrl, + issues: data, // Optionally include this if you want the issue data + }; + } catch (error) { + console.error('Failed to fetch issues:', error); + return error; + } } /** * Determine the Slack channel name to publish to based on the release - * @param {*} release + * @param {*} release */ async function getPublishChannelName(release) { + // convert the version to a format that can be used in a channel name + const formattedVersion = release.SemanticVersion.replace(/\./g, '-'); - // convert the version to a format that can be used in a channel name - const formattedVersion = release.SemanticVersion.replace(/\./g, '-'); - - const channel = `#release-${release.Platform}-${formattedVersion}`; - - // Allows for local testing without publishing actual release channels - if (testOnly()) { - return `${channel}-testonly`; - } else { - return channel; - } + const channel = `#release-${release.Platform}-${formattedVersion}`; + // Allows for local testing without publishing actual release channels + if (testOnly()) { + return `${channel}-testonly`; + } else { + return channel; + } } async function fmtSlackHandle(team) { + //Notify if they have pending validations or have not completed signoff + const shouldNotify = + team.pendingValidations > 0 || + team.status.trim().toLowerCase() !== 'completed'; + //Don't notify teams when in testOnly mode + if (testOnly()) { + return shouldNotify ? ` - @${team.slackHandle}` : ''; + } - //Notify if they have pending validations or have not completed signoff - const shouldNotify = team.pendingValidations > 0 || team.status.trim().toLowerCase() !== 'completed'; - //Don't notify teams when in testOnly mode - if (testOnly()) { - return shouldNotify ? ` - @${team.slackHandle}` : ''; - } - - //Lookup Slack Team Id for real notifications - const slackTeamId = slackTeamsMap[teamName]; - return shouldNotify ? ` - ` : ''; + //Lookup Slack Team Id for real notifications + const slackTeamId = slackTeamsMap[teamName]; + return shouldNotify ? ` - ` : ''; } /** @@ -324,66 +338,72 @@ async function fmtSlackHandle(team) { * @param {Object} release represents a release */ async function publishReleaseTestingStatus(release) { + const fmtPlatform = formatTitle(release.Platform); + const teamResults = parseReleaseUpdates(release.testingStatus); + const releasePrUrl = await findPullRequestUrlByBranch( + 'MetaMask', + `metamask-${release.Platform}`, + getReleaseBranchName(release.Platform, release.SemanticVersion), + ); + const channel = await getPublishChannelName(release); + + console.log( + `Publishing testing status for release ${release.SemanticVersion} on platform ${release.Platform} to channel ${channel}`, + ); + + var header = + `:blablablocker:* [${fmtPlatform}] - ${release.SemanticVersion} Release Validation.*\n` + + `_*Testing Plan and Progress Tracker Summary*_ ():`; + + var body = `*Teams Sign Off ${release.SemanticVersion} Release on <${releasePrUrl}|GH>:*\n`; + + const hasPendingSignoffs = teamResults.some( + (team) => team.status !== 'Completed', + ); + + let releaseBlockerCount = 0; + + for (const team of teamResults) { + let slackHandlePart = await fmtSlackHandle(team); + //Grab RCs for a specific team/release + const releaseBlockers = await getReleaseBlockers(release, team.team); + //Accumulate the total release blocker count + releaseBlockerCount += releaseBlockers.count; + let releaseBlockerParts = + releaseBlockers.count > 0 + ? ` - <${releaseBlockers.url}|${releaseBlockers.count} Release Blockers>` + : ''; + + body += `${team.emoji}: *${team.team}*${slackHandlePart}${releaseBlockerParts}\n`; + } - const fmtPlatform = formatTitle(release.Platform); - const teamResults = parseReleaseUpdates(release.testingStatus); - const releasePrUrl = await findPullRequestUrlByBranch('MetaMask', `metamask-${release.Platform}`, getReleaseBranchName(release.Platform, release.SemanticVersion)); - const channel = await getPublishChannelName(release); - - console.log(`Publishing testing status for release ${release.SemanticVersion} on platform ${release.Platform} to channel ${channel}`); - - //Determine notification counts for this release - const testingDocumentLink = createSheetUrl(release.DocumentId, release.sheetId); - - - var header = `:blablablocker:* [${fmtPlatform}] - ${release.SemanticVersion} Release Validation.*\n` - + `_*Testing Plan and Progress Tracker Summary*_ ():`; - - var body = `*Teams Sign Off ${release.SemanticVersion} Release on <${releasePrUrl}|GH>:*\n` - - const hasPendingSignoffs = teamResults.some(team => team.status !== "Completed"); + if (hasPendingSignoffs) { + header += `\n:bell: *Status Update*: Several Release Signs Offs are still Pending. There are ${releaseBlockerCount} open Release Blockers.\n`; + } - let releaseBlockerCount = 0; + const footer = `*Important Reminder:*\nPlease be aware of the importance of starting your testing immediately to ensure there is sufficient time to address any unexpected defects. This proactive approach will help prevent release delays and minimize the impact on other teams’ deliveries.`; - for (const team of teamResults) { - let slackHandlePart = await fmtSlackHandle(team); - //Grab RCs for a specific team/release - const releaseBlockers = await getReleaseBlockers(release, team.team); - //Accumulate the total release blocker count - releaseBlockerCount += releaseBlockers.count; - let releaseBlockerParts = releaseBlockers.count > 0 ? ` - <${releaseBlockers.url}|${releaseBlockers.count} Release Blockers>` : ''; - - body += `${team.emoji}: *${team.team}*${slackHandlePart}${releaseBlockerParts}\n`; - } + const slackMessage = `${header}\n${body}\n${footer}`; - if (hasPendingSignoffs) { - header += `\n:bell: *Status Update*: Several Release Signs Offs are still Pending. There are ${releaseBlockerCount} open Release Blockers.\n`; - } + try { + await slackClient.chat.postMessage({ + channel: channel, + text: slackMessage, + unfurl_links: false, + unfurl_media: false, + }); - const footer = `*Important Reminder:*\nPlease be aware of the importance of starting your testing immediately to ensure there is sufficient time to address any unexpected defects. This proactive approach will help prevent release delays and minimize the impact on other teams’ deliveries.`; - - const slackMessage = `${header}\n${body}\n${footer}`; - - - try { - await slackClient.chat.postMessage({ - channel: channel, - text: slackMessage, - unfurl_links: false, - unfurl_media: false, - }); - - console.log(`Message successfully sent to channel ${channel} for release ${release.SemanticVersion} on platform ${release.Platform}.`); - - } catch (error) { - console.error('API error:', error); - throw error; - } + console.log( + `Message successfully sent to channel ${channel} for release ${release.SemanticVersion} on platform ${release.Platform}.`, + ); + } catch (error) { + console.error('API error:', error); + throw error; + } } - /** - * publishes the testing status for a list of releases. + * publishes the testing status for a list of releases. * * @param {Object[]} releases - An array of release objects. Each release object should be suitable * for use with the `publishReleaseTestingStatus` function. @@ -391,61 +411,68 @@ async function publishReleaseTestingStatus(release) { * */ async function publishReleasesTestingStatus(releases) { - - console.log('Publishing testing status for all active releases...'); - - try { - const promises = releases.map(release => publishReleaseTestingStatus(release)); - await Promise.all(promises); - } catch (error) { - console.error('An error occurred:', error); - throw error; - } + console.log('Publishing testing status for all active releases...'); + + try { + const promises = releases.map((release) => + publishReleaseTestingStatus(release), + ); + await Promise.all(promises); + } catch (error) { + console.error('An error occurred:', error); + throw error; + } } async function main() { + const documentId = process.env.GOOG_DOCUMENT_ID; - const documentId = process.env.GOOG_DOCUMENT_ID; - - if (!documentId) { - console.error("Document ID is not set. Please set the GOOG_DOCUMENT_ID environment variable."); - return; - } + if (!documentId) { + console.error( + 'Document ID is not set. Please set the GOOG_DOCUMENT_ID environment variable.', + ); + return; + } - const platform = process.env.PLATFORM; + const platform = process.env.PLATFORM; - if (!platform) { - console.error("Platform is not set. Please set the PLATFORM environment variable."); - return; - } + if (!platform) { + console.error( + 'Platform is not set. Please set the PLATFORM environment variable.', + ); + return; + } - await initializeSlackTeams(); + await initializeSlackTeams(); - const activeReleases = await getActiveReleases(documentId); + const activeReleases = await getActiveReleases(documentId); - // Filter active releases based on the platform - const filteredReleases = activeReleases.filter(release => release.Platform === platform); + // Filter active releases based on the platform + const filteredReleases = activeReleases.filter( + (release) => release.Platform === platform, + ); - filteredReleases.forEach(release => { - console.log(`Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId}`); - }); + filteredReleases.forEach((release) => { + console.log( + `Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId}`, + ); + }); - await publishReleasesTestingStatus(filteredReleases); + await publishReleasesTestingStatus(filteredReleases); } //Entrypoint main(); - // Helper functions function formatTitle(val) { - return String(val).charAt(0).toUpperCase() + String(val).slice(1); + return String(val).charAt(0).toUpperCase() + String(val).slice(1); } function testOnly() { - return process.env.TEST_ONLY === 'true'; + return process.env.TEST_ONLY === 'true'; } function createSheetUrl(documentId, sheetId) { - return `https://docs.google.com/spreadsheets/d/${documentId}/edit#gid=${sheetId}`; + return `https://docs.google.com/spreadsheets/d/${documentId}/edit#gid=${sheetId}`; } diff --git a/.github/scripts/update-release-sheet.mjs b/.github/scripts/update-release-sheet.mjs index 9a2997a4..6afddf54 100644 --- a/.github/scripts/update-release-sheet.mjs +++ b/.github/scripts/update-release-sheet.mjs @@ -3,127 +3,135 @@ import { google } from 'googleapis'; import fs from 'fs'; import { parse } from 'csv-parse/sync'; - // Clients const sheets = google.sheets('v4'); - /** +/** * Retrieves and returns a Google authentication client. * * This function initializes a GoogleAuth object with a specific key file - * and predefined scopes necessary for accessing Google Sheets API. It + * and predefined scopes necessary for accessing Google Sheets API. It * returns a client instance that can be used to authenticate API requests. * - * @returns {Promise} Returns a promise that resolves - * to an instance of OAuth2Client which can be used to authenticate + * @returns {Promise} Returns a promise that resolves + * to an instance of OAuth2Client which can be used to authenticate * Google API requests. */ async function getGoogleAuth() { - - // Decode base64 string from the environment variable - const credentialsJson = Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIALS_BASE64, 'base64').toString('utf8'); - - // Parse the JSON string to an object - const credentials = JSON.parse(credentialsJson); - - // Initialize GoogleAuth with credentials object directly - const auth = new google.auth.GoogleAuth({ - credentials: credentials, - scopes: ['https://www.googleapis.com/auth/spreadsheets'], - }); - - return auth.getClient(); + // Decode base64 string from the environment variable + const credentialsJson = Buffer.from( + process.env.GOOGLE_APPLICATION_CREDENTIALS_BASE64, + 'base64', + ).toString('utf8'); + + // Parse the JSON string to an object + const credentials = JSON.parse(credentialsJson); + + // Initialize GoogleAuth with credentials object directly + const auth = new google.auth.GoogleAuth({ + credentials: credentials, + scopes: ['https://www.googleapis.com/auth/spreadsheets'], + }); + + return auth.getClient(); } /** * Creates a new release sheet by duplicating an existing template sheet and then overwriting * specific data rows in the new sheet. - * + * * @param {string} documentId - The ID of the Google Spreadsheet. * @param {string} platform - The platform for which the release is being prepared. * @param {string} semanticVersion - The semantic version of the release. * @param {number} templateSheetId - The sheet ID of the template to be duplicated. * @returns {Promise} A promise that resolves when the sheet has been created and modified. */ -async function createReleaseSheet(documentId, platform, semanticVersion, templateSheetId, sheetData) { - const authClient = await getGoogleAuth(); - const sheetTitle = `v${semanticVersion} (${platform})`; - - const existingSheetCount = await getTotalSheetCount(documentId); - - console.log(`Existing sheet count: ${existingSheetCount}`); - - try { - // Step 1: Duplicate the template sheet - const duplicateSheetResponse = await sheets.spreadsheets.batchUpdate({ - spreadsheetId: documentId, - resource: { - requests: [{ - duplicateSheet: { - sourceSheetId: templateSheetId, - newSheetName: sheetTitle, - }, - }], - }, - auth: authClient, - }); - - const newSheetId = duplicateSheetResponse.data.replies[0].duplicateSheet.properties.sheetId; - - console.log(`Sheet duplicated successfully. New sheet ID: ${newSheetId}`); - - - // Step 2. Make the new sheet the active sheet - const sheetActivationResponse = await sheets.spreadsheets.batchUpdate({ - spreadsheetId: documentId, - resource: { - requests: [ - { - updateSheetProperties: { - properties: { - sheetId: newSheetId, - hidden: false, - index: existingSheetCount, - }, - fields: 'hidden', - }, - } - ], +async function createReleaseSheet( + documentId, + platform, + semanticVersion, + templateSheetId, + sheetData, +) { + const authClient = await getGoogleAuth(); + const sheetTitle = `v${semanticVersion} (${platform})`; + + const existingSheetCount = await getTotalSheetCount(documentId); + + console.log(`Existing sheet count: ${existingSheetCount}`); + + try { + // Step 1: Duplicate the template sheet + const duplicateSheetResponse = sheets.spreadsheets.batchUpdate({ + spreadsheetId: documentId, + resource: { + requests: [ + { + duplicateSheet: { + sourceSheetId: templateSheetId, + newSheetName: sheetTitle, }, - auth: authClient, - }); - - console.log(`Sheet activated successfully.`); - - const dataStartRow = 3; - const dataEndRow = dataStartRow + sheetData.length - 1; - const columnStart = 'A'; - const columnEnd = 'G'; - - const valueRange = `${sheetTitle}!${columnStart}${dataStartRow}:${columnEnd}${dataEndRow}`; - - console.log(`Updating newly provisioned sheet with commit data.`); - - await sheets.spreadsheets.values.update({ - spreadsheetId: documentId, - range: valueRange, - valueInputOption: 'USER_ENTERED', - resource: { - values: sheetData, + }, + ], + }, + auth: authClient, + }); + + const newSheetId = + duplicateSheetResponse.data.replies[0].duplicateSheet.properties.sheetId; + + console.log(`Sheet duplicated successfully. New sheet ID: ${newSheetId}`); + + // Step 2. Make the new sheet the active sheet + const sheetActivationResponse = sheets.spreadsheets.batchUpdate({ + spreadsheetId: documentId, + resource: { + requests: [ + { + updateSheetProperties: { + properties: { + sheetId: newSheetId, + hidden: false, + index: existingSheetCount, + }, + fields: 'hidden', }, - auth: authClient, - }); + }, + ], + }, + auth: authClient, + }); - console.log('Sheet duplicated and updated successfully.'); + console.log(`Sheet activated successfully.`); - const newSheetCount = await getTotalSheetCount(documentId); + const dataStartRow = 3; + const dataEndRow = dataStartRow + sheetData.length - 1; + const columnStart = 'A'; + const columnEnd = 'G'; - console.log(`New sheet count: ${newSheetCount}`); + const valueRange = `${sheetTitle}!${columnStart}${dataStartRow}:${columnEnd}${dataEndRow}`; - } catch (error) { - console.error('Error creating release sheet:', error); - throw error; - } + console.log(`Updating newly provisioned sheet with commit data.`); + + sheets.spreadsheets.values.update({ + spreadsheetId: documentId, + range: valueRange, + valueInputOption: 'USER_ENTERED', + resource: { + values: sheetData, + }, + auth: authClient, + }); + + console.log('Sheet duplicated and updated successfully.'); + + const newSheetCount = await getTotalSheetCount(documentId); + + console.log(`New sheet count: ${newSheetCount}`); + } catch (error) { + console.error('Error creating release sheet:', error); + throw error; + } } /** @@ -138,27 +146,26 @@ async function getTotalSheetCount(documentId) { try { const response = await sheets.spreadsheets.get({ - spreadsheetId: documentId, - auth: authClient, - fields: 'sheets(properties(title,sheetId))', + spreadsheetId: documentId, + auth: authClient, + fields: 'sheets(properties(title,sheetId))', }); const sheetsData = response.data.sheets; if (!sheetsData) { - console.log('No sheets found in the spreadsheet.'); - return 0; + console.log('No sheets found in the spreadsheet.'); + return 0; } return sheetsData.length; - } catch (err) { console.error('Failed to retrieve spreadsheet data:', err); throw err; } } - /** +/** * retrieves a list of all releases from a Google Sheets document. * Each sheet in the document represents a different release. Only sheets with visible * titles containing a semantic version and optionally a platform within parentheses @@ -172,174 +179,181 @@ async function getTotalSheetCount(documentId) { * @throws {Error} Throws an error if there is an issue retrieving data from the spreadsheet. * */ - async function getAllReleases(documentId) { - const authClient = await getGoogleAuth(); - - try { - const response = await sheets.spreadsheets.get({ - spreadsheetId: documentId, - auth: authClient, - fields: 'sheets(properties(title,sheetId,hidden))', - }); - - const sheetsData = response.data.sheets; - if (!sheetsData) { - console.log('No sheets found in the spreadsheet.'); - return []; - } - - // Create a list of promises for each sheet using map - const promises = sheetsData.map(async (sheet) => { - const { title } = sheet.properties; - const versionMatch = title.match(/v(\d+\.\d+\.\d+)/); - const platformMatch = title.match(/\(([^)]+)\)/); - - if (!versionMatch) { - console.log(`Skipping sheet: ${title} with id ${sheet.properties.sheetId} - Semantic version not found.`); - return null; // Skip this sheet because we couldn't determine the semantic version - } - - return { - DocumentId: documentId, - SemanticVersion: versionMatch[1], - Platform: platformMatch ? platformMatch[1] : 'extension', - sheetId: sheet.properties.sheetId, - title: sheet.properties.title, - }; - }); - - // Filter out null values (sheets that were skipped) and resolve all promises - const results = await Promise.all(promises); - return results.filter(result => result !== null); - - } catch (err) { - console.error('Failed to retrieve spreadsheet data:', err); - throw err; - } -} +async function getAllReleases(documentId) { + const authClient = await getGoogleAuth(); - async function main() { + try { + const response = await sheets.spreadsheets.get({ + spreadsheetId: documentId, + auth: authClient, + fields: 'sheets(properties(title,sheetId,hidden))', + }); - const args = process.argv.slice(2); - if (args.length !== 5) { - console.error( - 'Incorrect argument count. Example Usage: node update-release-sheet.mjs mobile 7.10.0 documentId ./commits.csv .', - ); - console.error('Received:', args, ' with length:', args.length); - process.exit(1); - } - - const platform = args[0]; - const semanticVersion = args[1]; - const documentId = args[2]; - const commitsFile = args[3]; - const gitDir = args[4]; - - // Change the working directory to the git repository path - // Since this is invoked by a shared workflow, the working directory is not guaranteed to be the repository root - process.chdir(gitDir); - - if (!documentId) { - console.error("Document ID is not set."); - return; + const sheetsData = response.data.sheets; + if (!sheetsData) { + console.log('No sheets found in the spreadsheet.'); + return []; } - if (!platform) { - console.error("Platform is not set."); - return; - } + // Create a list of promises for each sheet using map + const promises = sheetsData.map(async (sheet) => { + const { title } = sheet.properties; + const versionMatch = title.match(/v(\d+\.\d+\.\d+)/); + const platformMatch = title.match(/\(([^)]+)\)/); + + if (!versionMatch) { + console.log( + `Skipping sheet: ${title} with id ${sheet.properties.sheetId} - Semantic version not found.`, + ); + return null; // Skip this sheet because we couldn't determine the semantic version + } - if (!commitsFile) { - console.error("Commits file is not set."); - return; - } + return { + DocumentId: documentId, + SemanticVersion: versionMatch[1], + Platform: platformMatch ? platformMatch[1] : 'extension', + sheetId: sheet.properties.sheetId, + title: sheet.properties.title, + }; + }); + // Filter out null values (sheets that were skipped) and resolve all promises + const results = await Promise.all(promises); + return results.filter((result) => result !== null); + } catch (err) { + console.error('Failed to retrieve spreadsheet data:', err); + throw err; + } +} - if (!semanticVersion) { - console.error("Semantic version is not set."); - return; - } +async function main() { + const args = process.argv.slice(2); + if (args.length !== 5) { + console.error( + 'Incorrect argument count. Example Usage: node update-release-sheet.mjs mobile 7.10.0 documentId ./commits.csv .', + ); + console.error('Received:', args, ' with length:', args.length); + process.exit(1); + } - const commits = parseCSVv2(commitsFile); + const platform = args[0]; + const semanticVersion = args[1]; + const documentId = args[2]; + const commitsFile = args[3]; + const gitDir = args[4]; - const releases = await getAllReleases(documentId); + // Change the working directory to the git repository path + // Since this is invoked by a shared workflow, the working directory is not guaranteed to be the repository root + process.chdir(gitDir); - var sheetExists = false; + if (!documentId) { + console.error('Document ID is not set.'); + return; + } - releases.forEach(release => { - console.log(`Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId} Title: ${release.title}`); - if (release.SemanticVersion === semanticVersion && release.Platform === platform) { - sheetExists = true; - } - }); - - console.log(`Release sheets exists for platform: ${platform}: version: ${semanticVersion} - ${sheetExists}`); + if (!platform) { + console.error('Platform is not set.'); + return; + } + if (!commitsFile) { + console.error('Commits file is not set.'); + return; + } - if (sheetExists){ - console.log(`Release sheet already exists for platform: ${platform}: version: ${semanticVersion}`); - return; - } + if (!semanticVersion) { + console.error('Semantic version is not set.'); + return; + } + + const commits = parseCSVv2(commitsFile); + + const releases = await getAllReleases(documentId); + + var sheetExists = false; - const templateSheetId = getTemplateSheetId(platform); + releases.forEach((release) => { + console.log( + `Version: ${release.SemanticVersion}, Platform: ${release.Platform}, Sheet ID: ${release.sheetId} Title: ${release.title}`, + ); + if ( + release.SemanticVersion === semanticVersion && + release.Platform === platform + ) { + sheetExists = true; + } + }); - createReleaseSheet(documentId, platform, semanticVersion, templateSheetId, commits); + console.log( + `Release sheets exists for platform: ${platform}: version: ${semanticVersion} - ${sheetExists}`, + ); + if (sheetExists) { + return; } + const templateSheetId = getTemplateSheetId(platform); + + createReleaseSheet( + documentId, + platform, + semanticVersion, + templateSheetId, + commits, + ); +} + // TODO These need to be provisioned/updated on the prod worksheet function getTemplateSheetId(platform) { - switch (platform) { - case 'mobile': - return '514823427'; - case 'extension': - return '599904091'; - default: - throw new Error(`Unknown platform: ${platform}`); - } + switch (platform) { + case 'mobile': + return '514823427'; + case 'extension': + return '599904091'; + default: + throw new Error(`Unknown platform: ${platform}`); + } } - // Function to parse a CSV file into a 2D array with specific modifications - function parseCSVv2(filePath) { - try { - console.log(`Parsing CSV file: ${filePath}`); - - // Read the entire file content - const fileContent = fs.readFileSync(filePath, { encoding: 'utf8' }); - // Split the content into lines - const lines = fileContent.split('\n'); - - // Initialize the 2D array to hold our processed data - const data2D = []; - - // Start from the second line to skip headers - for (let i = 1; i < lines.length; i++) { - if (lines[i].trim() === '') continue; // Skip empty lines - - // Split the line into columns based on commas - const columns = lines[i].split(','); - - const modifiedColumns = [ - columns[0], // Commit Message - columns[1], // Author - columns[2], // PR Link - '', // Blank string 'd' - '', // Blank string 'e' - columns[3], // Team - columns[4] // Change Type - ]; - - // Add this row to the 2D array - data2D.push(modifiedColumns); - } - - return data2D; - - } catch (error) { - console.error('Failed to parse CSV:', error); - return []; // Return an empty array in case of error - } - } +// Function to parse a CSV file into a 2D array with specific modifications +function parseCSVv2(filePath) { + try { + console.log(`Parsing CSV file: ${filePath}`); + + // Read the entire file content + const fileContent = fs.readFileSync(filePath, { encoding: 'utf8' }); + // Split the content into lines + const lines = fileContent.split('\n'); + + // Initialize the 2D array to hold our processed data + const data2D = []; + + // Start from the second line to skip headers + for (let i = 1; i < lines.length; i++) { + if (lines[i].trim() === '') continue; // Skip empty lines + + // Split the line into columns based on commas + const columns = lines[i].split(','); + + const modifiedColumns = [ + columns[0], // Commit Message + columns[1], // Author + columns[2], // PR Link + '', // Blank string 'd' + '', // Blank string 'e' + columns[3], // Team + columns[4], // Change Type + ]; + + // Add this row to the 2D array + data2D.push(modifiedColumns); + } - await main() + return data2D; + } catch (error) { + console.error('Failed to parse CSV:', error); + return []; // Return an empty array in case of error + } +} +await main(); diff --git a/.yarnrc.yml b/.yarnrc.yml index bcdc1a8a..9864e4e4 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -14,6 +14,6 @@ nodeLinker: node-modules plugins: - path: .yarn/plugins/@yarnpkg/plugin-allow-scripts.cjs - spec: "https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js" + spec: 'https://raw.githubusercontent.com/LavaMoat/LavaMoat/main/packages/yarn-plugin-allow-scripts/bundles/@yarnpkg/plugin-allow-scripts.js' yarnPath: .yarn/releases/yarn-4.5.1.cjs From 2033666e38d12e2f900da277993c59df36aca6b8 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 16:06:44 -0600 Subject: [PATCH 21/28] integrate testing sheet --- .github/scripts/update-release-sheet.mjs | 35 ++++++++++++++++++------ .github/workflows/create-release-pr.yml | 12 ++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/.github/scripts/update-release-sheet.mjs b/.github/scripts/update-release-sheet.mjs index 6afddf54..5850dbd0 100644 --- a/.github/scripts/update-release-sheet.mjs +++ b/.github/scripts/update-release-sheet.mjs @@ -62,7 +62,7 @@ async function createReleaseSheet( try { // Step 1: Duplicate the template sheet - const duplicateSheetResponse = sheets.spreadsheets.batchUpdate({ + const duplicateSheetResponse = await sheets.spreadsheets.batchUpdate({ spreadsheetId: documentId, resource: { requests: [ @@ -83,7 +83,7 @@ async function createReleaseSheet( console.log(`Sheet duplicated successfully. New sheet ID: ${newSheetId}`); // Step 2. Make the new sheet the active sheet - const sheetActivationResponse = sheets.spreadsheets.batchUpdate({ + const sheetActivationResponse = await sheets.spreadsheets.batchUpdate({ spreadsheetId: documentId, resource: { requests: [ @@ -228,7 +228,7 @@ async function getAllReleases(documentId) { async function main() { const args = process.argv.slice(2); - if (args.length !== 5) { + if (args.length !== 7) { console.error( 'Incorrect argument count. Example Usage: node update-release-sheet.mjs mobile 7.10.0 documentId ./commits.csv .', ); @@ -241,6 +241,8 @@ async function main() { const documentId = args[2]; const commitsFile = args[3]; const gitDir = args[4]; + const mobileTemplateSheetId = args[5]; + const extensionTemplateSheetId = args[6]; // Change the working directory to the git repository path // Since this is invoked by a shared workflow, the working directory is not guaranteed to be the repository root @@ -266,6 +268,16 @@ async function main() { return; } + if (!mobileTemplateSheetId) { + console.error('Mobile template sheet ID is not set.'); + return; + } + + if (!extensionTemplateSheetId) { + console.error('Extension template sheet ID is not set.'); + return; + } + const commits = parseCSVv2(commitsFile); const releases = await getAllReleases(documentId); @@ -292,7 +304,11 @@ async function main() { return; } - const templateSheetId = getTemplateSheetId(platform); + const templateSheetId = determineTemplateId( + platform, + mobileTemplateSheetId, + extensionTemplateSheetId, + ); createReleaseSheet( documentId, @@ -303,13 +319,16 @@ async function main() { ); } -// TODO These need to be provisioned/updated on the prod worksheet -function getTemplateSheetId(platform) { +function determineTemplateId( + platform, + mobileTemplateSheetId, + extensionTemplateSheetId, +) { switch (platform) { case 'mobile': - return '514823427'; + return mobileTemplateSheetId; case 'extension': - return '599904091'; + return extensionTemplateSheetId; default: throw new Error(`Unknown platform: ${platform}`); } diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index 04406465..c1b5e0ca 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -20,6 +20,16 @@ on: type: string description: 'Previous release version tag. eg: v7.7.0' # Flag to indicate if the release is a test release for development purposes only + mobile-template-sheet-id: + required: true + type: string + description: 'The Mobile testing sheet template id.' + default: '1012668681' + extension-template-sheet-id: + required: true + type: string + description: 'The Extension testing sheet template id.' + default: '720412332' test-only: required: false type: string @@ -82,6 +92,8 @@ jobs: GOOGLE_DOCUMENT_ID: ${{ inputs.release-sheet-google-document-id }} GOOGLE_APPLICATION_CREDENTIALS_BASE64: ${{ secrets.google-application-creds-base64 }} NEW_VERSION: ${{ inputs.semver-version }} + MOBILE_TEMPLATE_SHEET_ID: ${{ secrets.mobile-template-sheet-id }} + EXTENSION_TEMPLATE_SHEET_ID: ${{ secrets.extension-template-sheet-id }} working-directory: ${{ github.workspace }} run: | # Execute the script from github-tools From 3d1dd3807e6c2d6cde008b69e15e1da6b476cef1 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 16:13:16 -0600 Subject: [PATCH 22/28] fix defaults --- .github/scripts/create-platform-release-pr.sh | 2 +- .github/workflows/create-release-pr.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/create-platform-release-pr.sh b/.github/scripts/create-platform-release-pr.sh index 3cce4eaf..d6b9e047 100755 --- a/.github/scripts/create-platform-release-pr.sh +++ b/.github/scripts/create-platform-release-pr.sh @@ -169,7 +169,7 @@ yarn run gen:commits "${PLATFORM}" "${PREVIOUS_VERSION}" "${RELEASE_BRANCH_NAME} echo "Updating release sheet.." # Create a new Release Sheet Page for the new version with our commits.csv content -yarn yarn run update-release-sheet "${PLATFORM}" "${NEW_VERSION}" "${GOOG_DOCUMENT_ID}" "./commits.csv" "${PROJECT_GIT_DIR}" +yarn yarn run update-release-sheet "${PLATFORM}" "${NEW_VERSION}" "${GOOG_DOCUMENT_ID}" "./commits.csv" "${PROJECT_GIT_DIR}" "${MOBILE_TEMPLATE_SHEET_ID}" "${EXTENSION_TEMPLATE_SHEET_ID}" cd ../ echo "Adding and committing changes.." diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index c1b5e0ca..270cb6db 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -21,12 +21,12 @@ on: description: 'Previous release version tag. eg: v7.7.0' # Flag to indicate if the release is a test release for development purposes only mobile-template-sheet-id: - required: true + required: false type: string description: 'The Mobile testing sheet template id.' default: '1012668681' extension-template-sheet-id: - required: true + required: false type: string description: 'The Extension testing sheet template id.' default: '720412332' From c6b5389b35f94f1cda52ef4ac13ecf73182b1148 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 16:26:46 -0600 Subject: [PATCH 23/28] fix goog refs --- .github/scripts/create-platform-release-pr.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/create-platform-release-pr.sh b/.github/scripts/create-platform-release-pr.sh index d6b9e047..652ca48a 100755 --- a/.github/scripts/create-platform-release-pr.sh +++ b/.github/scripts/create-platform-release-pr.sh @@ -169,7 +169,7 @@ yarn run gen:commits "${PLATFORM}" "${PREVIOUS_VERSION}" "${RELEASE_BRANCH_NAME} echo "Updating release sheet.." # Create a new Release Sheet Page for the new version with our commits.csv content -yarn yarn run update-release-sheet "${PLATFORM}" "${NEW_VERSION}" "${GOOG_DOCUMENT_ID}" "./commits.csv" "${PROJECT_GIT_DIR}" "${MOBILE_TEMPLATE_SHEET_ID}" "${EXTENSION_TEMPLATE_SHEET_ID}" +yarn yarn run update-release-sheet "${PLATFORM}" "${NEW_VERSION}" "${GOOGLE_DOCUMENT_ID}" "./commits.csv" "${PROJECT_GIT_DIR}" "${MOBILE_TEMPLATE_SHEET_ID}" "${EXTENSION_TEMPLATE_SHEET_ID}" cd ../ echo "Adding and committing changes.." From d6098c3e8061086b3ab570b25b8180d9b221b890 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 16:33:08 -0600 Subject: [PATCH 24/28] yarn ref --- .github/scripts/create-platform-release-pr.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/scripts/create-platform-release-pr.sh b/.github/scripts/create-platform-release-pr.sh index 652ca48a..8d47e749 100755 --- a/.github/scripts/create-platform-release-pr.sh +++ b/.github/scripts/create-platform-release-pr.sh @@ -158,7 +158,6 @@ npx @metamask/auto-changelog@4.1.0 update --rc --repo "${GITHUB_REPOSITORY_URL}" # Need to run from .github-tools context to inherit it's dependencies/environment echo "Current Directory: $(pwd)" PROJECT_GIT_DIR=$(pwd) -ls -ltra cd ./github-tools/ ls -ltra corepack prepare yarn@4.5.1 --activate @@ -169,7 +168,7 @@ yarn run gen:commits "${PLATFORM}" "${PREVIOUS_VERSION}" "${RELEASE_BRANCH_NAME} echo "Updating release sheet.." # Create a new Release Sheet Page for the new version with our commits.csv content -yarn yarn run update-release-sheet "${PLATFORM}" "${NEW_VERSION}" "${GOOGLE_DOCUMENT_ID}" "./commits.csv" "${PROJECT_GIT_DIR}" "${MOBILE_TEMPLATE_SHEET_ID}" "${EXTENSION_TEMPLATE_SHEET_ID}" +yarn run update-release-sheet "${PLATFORM}" "${NEW_VERSION}" "${GOOGLE_DOCUMENT_ID}" "./commits.csv" "${PROJECT_GIT_DIR}" "${MOBILE_TEMPLATE_SHEET_ID}" "${EXTENSION_TEMPLATE_SHEET_ID}" cd ../ echo "Adding and committing changes.." From 311f9462c5e17f775431ac2bf73515364df58292 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 16:43:28 -0600 Subject: [PATCH 25/28] fix sheet refs --- .github/workflows/create-release-pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index 270cb6db..87ca3c2f 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -92,8 +92,8 @@ jobs: GOOGLE_DOCUMENT_ID: ${{ inputs.release-sheet-google-document-id }} GOOGLE_APPLICATION_CREDENTIALS_BASE64: ${{ secrets.google-application-creds-base64 }} NEW_VERSION: ${{ inputs.semver-version }} - MOBILE_TEMPLATE_SHEET_ID: ${{ secrets.mobile-template-sheet-id }} - EXTENSION_TEMPLATE_SHEET_ID: ${{ secrets.extension-template-sheet-id }} + MOBILE_TEMPLATE_SHEET_ID: ${{ inputs.mobile-template-sheet-id }} + EXTENSION_TEMPLATE_SHEET_ID: ${{ inputs.extension-template-sheet-id }} working-directory: ${{ github.workspace }} run: | # Execute the script from github-tools From 417a5aaf628d16a63a558acb2290a0355a506f3a Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 18:00:38 -0600 Subject: [PATCH 26/28] final cleanup --- .github/workflows/create-release-pr.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index 87ca3c2f..a74adddc 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -24,12 +24,12 @@ on: required: false type: string description: 'The Mobile testing sheet template id.' - default: '1012668681' + default: '1012668681' # prod sheet template extension-template-sheet-id: required: false type: string description: 'The Extension testing sheet template id.' - default: '720412332' + default: '720412332' # prod sheet template test-only: required: false type: string @@ -40,7 +40,7 @@ on: required: false type: string description: 'The Google Document ID for the release notes.' - default: '1_aJ-cqS5ngrXzAIly3HOyl7CY_lqIoV2T7_OU83AD50' # Release Testing Document + default: '1tsoodlAlyvEUpkkcNcbZ4PM9HuC9cEM80RZeoVv5OCQ' # Prod Release Document platform: required: true type: string From b19ffd2263910ab539df90514d15bf911de5b800 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 18:03:08 -0600 Subject: [PATCH 27/28] point to main --- .github/workflows/create-release-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index a74adddc..2b4350d5 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -73,7 +73,7 @@ jobs: uses: actions/checkout@v4 with: repository: MetaMask/github-tools - ref: slack-release-testing + ref: main path: github-tools # Step 3: Setup environment from github-tools From 57cc728f3a8b8ca0e217716af81922481e8465b0 Mon Sep 17 00:00:00 2001 From: Jake Perkins Date: Wed, 19 Feb 2025 18:04:10 -0600 Subject: [PATCH 28/28] main ref --- .github/workflows/publish-slack-release-testing-status.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-slack-release-testing-status.yml b/.github/workflows/publish-slack-release-testing-status.yml index 568e7366..c1d29385 100644 --- a/.github/workflows/publish-slack-release-testing-status.yml +++ b/.github/workflows/publish-slack-release-testing-status.yml @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@v4 with: repository: MetaMask/github-tools - ref: slack-release-testing #TODO Update to main + ref: main path: github-tools - name: Set up Node.js