From 53ae26f4c6a2542d4afd8b42909059e17be52dda Mon Sep 17 00:00:00 2001 From: Yansong Yang Date: Mon, 15 Apr 2024 17:37:11 -0500 Subject: [PATCH] [Release Notes Automation] Remove the use of the unstable Sonatype API & code refactoring (#572) * remove the use of the sonatype API; code refactoring * more code refactoring * add API docs * no message * rename files * no message * no message * address review comments --------- Co-authored-by: Praveen --- .../scripts/release_notes/SDKReleaseInfo.js | 224 ++++++++ .../scripts/release_notes/android-release.js | 195 ------- .github/scripts/release_notes/constants.js | 135 +++++ .../release_notes/fetchReleaseNotes.js | 50 ++ .../scripts/release_notes/github-release.js | 140 ----- .../scripts/release_notes/githubRelease.js | 70 +++ .github/scripts/release_notes/index.js | 57 +- .../scripts/release_notes/releaseNoteMap.json | 21 - .../release_notes/releaseNotesTemplates.js | 73 +++ ...release-notes.js => updateReleaseNotes.js} | 209 +------- .github/scripts/release_notes/utils.js | 73 ++- package.json | 1 + yarn.lock | 499 +++++++++++++++++- 13 files changed, 1143 insertions(+), 604 deletions(-) create mode 100644 .github/scripts/release_notes/SDKReleaseInfo.js delete mode 100644 .github/scripts/release_notes/android-release.js create mode 100644 .github/scripts/release_notes/constants.js create mode 100644 .github/scripts/release_notes/fetchReleaseNotes.js delete mode 100644 .github/scripts/release_notes/github-release.js create mode 100644 .github/scripts/release_notes/githubRelease.js delete mode 100644 .github/scripts/release_notes/releaseNoteMap.json create mode 100644 .github/scripts/release_notes/releaseNotesTemplates.js rename .github/scripts/release_notes/{update-release-notes.js => updateReleaseNotes.js} (51%) diff --git a/.github/scripts/release_notes/SDKReleaseInfo.js b/.github/scripts/release_notes/SDKReleaseInfo.js new file mode 100644 index 0000000000..94995c045c --- /dev/null +++ b/.github/scripts/release_notes/SDKReleaseInfo.js @@ -0,0 +1,224 @@ +/* +Copyright 2024 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { PLATFORM_ENUM, EXTENSION_ENUM, BOM_RELEASE_TAG_PREFIX, REACT_NATIVE_RELEASE_TAG_PREFIX, FLUTTER_RELEASE_TAG_PREFIX } = require('./constants') +const { GithubReleaseInfo } = require('./githubRelease'); + +/** + * @class Represents information about an SDK release. + */ +class SDKReleaseInfo { + /** + * Creates a new SDKReleaseInfo object. + * + * @param {GithubReleaseInfo} githubReleaseInfo - The [GithubReleaseInfo] object containing release information. + * @param {string} platform - The platform of the SDK release. + * @param {string} extension - The file extension of the SDK release. + * @param {string} version - The version of the SDK release. + * @throws {Error} Throws an error if the provided githubReleaseInfo is not an instance of [GithubReleaseInfo]. + */ + constructor(githubReleaseInfo, platform, extension, version) { + if (githubReleaseInfo instanceof GithubReleaseInfo) { + this.published_at = githubReleaseInfo.published_at; + this.body = githubReleaseInfo.body; + this.platform = platform; + this.extension = extension; + this.version = version; + } else { + throw Error("Invalid GithubReleaseInfo object."); + } + for (let property in this) { + if (this[property] === null) { + console.log(`The property ${property} is null`); + } + } + } +} + +/** + * Converts a [GithubReleaseInfo] object to an [SDKReleaseInfo] object. + * + * @param {GithubReleaseInfo} releaseInfo - The [GithubReleaseInfo] object to convert. + * @returns {SDKReleaseInfo|null} - The converted [SDKReleaseInfo] object or null if the repo_name is not supported or the tag_name is not supported. + * @throws {Error} - If the input is not a [GithubReleaseInfo] object. + */ +function convertToSDKReleaseInfo(releaseInfo) { + if (!(releaseInfo instanceof GithubReleaseInfo)) { + throw Error("Input is not a [GithubReleaseInfo] object.") + } + + const repoName = releaseInfo.repo_name + const tagName = releaseInfo.tag_name + switch (true) { + case isBOMRelease(repoName): + // aepsdk-commons is also used to release othe artifacts, only keep the release notes for BOM. + if (tagName.startsWith(BOM_RELEASE_TAG_PREFIX)) + return new SDKReleaseInfo(releaseInfo, PLATFORM_ENUM.ANDROID, EXTENSION_ENUM.BOM, tagName.replace(BOM_RELEASE_TAG_PREFIX, '')) + else + return null + case isRokuRelease(repoName): + return new SDKReleaseInfo(releaseInfo, PLATFORM_ENUM.ROKU, EXTENSION_ENUM.SDK, tagName) + case isReactNativeRelease(repoName): + // example: @adobe/react-native-aepuserprofile@6.0.0 + let array1 = tagName.replace(REACT_NATIVE_RELEASE_TAG_PREFIX, '').split('@') + // puserprofile@6.0.0 + verifyArray(array1, 2) + return new SDKReleaseInfo(releaseInfo, PLATFORM_ENUM.REACT_NATIVE, standardizeExtensionName(array1[0]), array1[1]) + case isFlutterRelease(repoName): + // example: flutter_aepuserprofile@3.0.0 + let array2 = tagName.replace(FLUTTER_RELEASE_TAG_PREFIX, '').split('@') + // userprofile@3.0.0 + verifyArray(array2, 2) + return new SDKReleaseInfo(releaseInfo, PLATFORM_ENUM.FLUTTER, standardizeExtensionName(array2[0]), array2[1]) + case isIOSRelease(repoName): + return new SDKReleaseInfo(releaseInfo, PLATFORM_ENUM.IOS, extractIOSExtensionName(repoName), tagName) + case isAndroidRelease(repoName): + if (isAndroidCoreRelease(repoName)) { + // Example: https://github.com/adobe/aepsdk-core-android/releases/tag/v3.0.0-signal + let array3 = tagName.split('-') + verifyArray(array3, 2) + return new SDKReleaseInfo(releaseInfo, PLATFORM_ENUM.ANDROID, standardizeExtensionName(array3[1]), extractAndroidVersion(array3[0])) + } + return new SDKReleaseInfo(releaseInfo, PLATFORM_ENUM.ANDROID, extractAndroidExtensionName(repoName), extractAndroidVersion(tagName)) + default: + throw Error("unsupported repoName: " + repoName) + } +} + +/** + * Verifies if the given object is an array of the specified size. + * + * @param {Array} obj - The object to be verified. + * @param {number} size - The expected size of the array. + * @throws {Error} If the object is not an array or if its length is not equal to the specified size. + */ +function verifyArray(obj, size) { + if (Array.isArray(obj) && obj.length === size) { + return + } + throw Error("Unexpected array: " + obj) +} + +/** + * Extracts the Android version from a given Github tag. + * + * Example: https://github.com/adobe/aepsdk-edge-android/releases/tag/v3.0.0 + * + * @param {string} tag - The tag to extract the Android version from. + * @returns {string} The extracted Android version. + */ +function extractAndroidVersion(tag) { + return tag.replace('v', '') +} + +/** + * Extracts the iOS extension name from the given repository name. + * + * @param {string} repoName - The name of the repository. + * @returns {string} The standardized iOS extension name. + */ +function extractIOSExtensionName(repoName) { + let iosExtensionName = repoName.replace('aepsdk-', '').replace('-ios', '') + return standardizeExtensionName(iosExtensionName) +} + +/** + * Extracts the Android extension name from the given repository name. + * + * @param {string} repoName - The repository name. + * @returns {string} The standardized Android extension name. + */ +function extractAndroidExtensionName(repoName) { + let androidExtensionName = repoName.replace('aepsdk-', '').replace('-android', '') + return standardizeExtensionName(androidExtensionName) +} + +function isBOMRelease(repoName) { + return repoName === "aepsdk-commons" +} +function isRokuRelease(repoName) { + return repoName === "aepsdk-roku" +} +function isReactNativeRelease(repoName) { + return repoName === "aepsdk-react-native" +} +function isFlutterRelease(repoName) { + return repoName === "aepsdk-flutter" +} +function isIOSRelease(repoName) { + return repoName.endsWith("-ios") +} + +function isAndroidRelease(repoName) { + return repoName.endsWith("-android") +} + +function isAndroidCoreRelease(repoName) { + return repoName === "aepsdk-core-android" +} + +/** + * Standardizes the extension name with the correct format. + * + * @param {string} extensionName - The name of the extension to be standardized. + * @returns {string} The standardized extension enum value. + * @throws {Error} If the extension name is not supported. + */ +function standardizeExtensionName(extensionName) { + extensionName = extensionName.trim().toLowerCase() + switch (true) { + case extensionName === "core": + return EXTENSION_ENUM.CORE; + case extensionName === "lifecycle": + return EXTENSION_ENUM.LIFECYCLE; + case extensionName === "identity": + return EXTENSION_ENUM.IDENTITY; + case extensionName === "signal": + return EXTENSION_ENUM.SIGNAL; + case extensionName === "assurance": + return EXTENSION_ENUM.ASSURANCE; + case extensionName === "userprofile": + return EXTENSION_ENUM.USERPROFILE; + case extensionName === "edgeconsent" || extensionName === "consent": + return EXTENSION_ENUM.EDGE_CONSENT; + case extensionName === "edge": + return EXTENSION_ENUM.EDGE; + case extensionName === "edgeidentity": + return EXTENSION_ENUM.EDGE_IDENTITY; + case extensionName === "edgemedia": + return EXTENSION_ENUM.EDGE_MEDIA; + case extensionName === "edgebridge": + return EXTENSION_ENUM.EDGE_BRIDGE; + case extensionName === "messaging": + return EXTENSION_ENUM.MESSAGING; + case extensionName === "optimize": + return EXTENSION_ENUM.OPTIMIZE; + case extensionName === "analytics": + return EXTENSION_ENUM.ANALYTICS; + case extensionName === "target": + return EXTENSION_ENUM.TARGET; + case extensionName === "places": + return EXTENSION_ENUM.PLACES; + case extensionName === "media": + return EXTENSION_ENUM.MEDIA; + case extensionName === "audience": + return EXTENSION_ENUM.AUDIENCE; + case extensionName.includes("campaign"): + return EXTENSION_ENUM.CAMPAIGN_STANDARD; + case extensionName.includes("campaign") && extensionName.includes("classic"): + return EXTENSION_ENUM.CAMPAIGN_CLASSIC; + default: + throw Error("unsupported extension name : " + extensionName) + } +} + +module.exports = { convertToSDKReleaseInfo, SDKReleaseInfo }; \ No newline at end of file diff --git a/.github/scripts/release_notes/android-release.js b/.github/scripts/release_notes/android-release.js deleted file mode 100644 index c6aebbb067..0000000000 --- a/.github/scripts/release_notes/android-release.js +++ /dev/null @@ -1,195 +0,0 @@ -/* -Copyright 2023 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const https = require('https'); -const { fetchReleaseInfoWithTagName } = require('./github-release'); -const { capitalizeFirstLetter } = require('./utils'); - -// Maven Search REST API -// https://central.sonatype.org/search/rest-api-guide/ - -// Fetch the latest Android artifacts from Maven Central -async function fetchMavenArtifactInfo(groupId, capacity, timestampInMilliseconds) { - let options = { - host: 'search.maven.org', - port: 443, - timeout: 5000, - path: `/solrsearch/select?q=g:${groupId}&core=gav&rows=${capacity}&wt=json`, - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'server-side', - } - }; - - console.log(`request options: ${JSON.stringify(options)}`) - - // TODO: condiser refactoring the code blow with libraries like axios. - return new Promise((resolve) => { - let reqGet = https.request(options, function (res) { - if (res.statusCode != 200) { - throw new Error(`response statusCode: ${res.statusCode}`) - } - console.log(`response statusCode: ${res.statusCode}`) - - let data = []; - res.on('data', function (chunk) { - data.push(chunk); - }).on('end', function () { - let buffer = Buffer.concat(data); - let str = new TextDecoder("utf-8").decode(buffer) - let responseJson = JSON.parse(str) - - let array = [] - responseJson.response.docs.forEach(element => { - if (timestampInMilliseconds < element.timestamp) { - array.push(artifactInfo(element.timestamp, element.a, element.v)) - } - }); - resolve(array) - }); - }); - reqGet.on('error', function (e) { - console.error(e) - throw new Error("Got error response.") - }); - reqGet.on('timeout', function () { - reqGet.destroy() - throw new Error("Request timeout.") - }); - reqGet.end(); - }) - -} - -function artifactInfo(timestamp, artifactId, version) { - return { - "timestamp": timestamp, - "artifactId": artifactId, - "version": version - } -} - -// Fetch the latest Android release info from GitHub -async function fetchAndroidReleaseInfo(token, groupId, timestampInMilliseconds, capacity = 25) { - let array = await fetchMavenArtifactInfo(groupId, capacity, timestampInMilliseconds); - console.log("fetchMavenArtifactInfo():") - console.log(array) - let releaseInfoArray = await Promise.all(array.map(async (item) => { - let artifactId = item.artifactId; - let version = item.version; - let extensionName = artifactIdToExtensionName(artifactId); - if (extensionName == null) { - return null; - } - let info = buildGitHubInfo(artifactId, version); - if (info == null) { - return null; - } - let releaseInfo = await fetchReleaseInfoWithTagName(token, "adobe", info.repoName, info.tagName); - // update release info with extension, version, and platform - releaseInfo.version = version; - releaseInfo.extension = extensionName; - releaseInfo.platform = 'Android'; - return releaseInfo; - })); - return releaseInfoArray -} - -// Convert artifactId to extension name. The extension name is used to generate the release title. -function artifactIdToExtensionName(artifactId) { - switch (artifactId) { - case "sdk-bom": - return "BOM" - case "campaignclassic": - return "Campaign Classic" - case "campaign": - return "Campaign Standard" - case "core": - case "lifecycle": - case "identity": - case "signal": - case "edge": - case "optimize": - case "places": - case "media": - case "target": - case "analytics": - case "assurance": - case "audience": - case "campaign": - case "messaging": - return capitalizeFirstLetter(artifactId) - case "userprofile": - return "UserProfile" - case "edgeconsent": - return "EdgeConsent" - case "edgeidentity": - return "EdgeIdentity" - case "edgebridge": - return "EdgeBridge" - case "edgemedia": - return "EdgeMedia" - default: - console.log("artifactId not supported: " + artifactId) - return null - } -} - -function buildGitHubInfo(artifactId, artifactVersion) { - switch (artifactId) { - case "sdk-bom": - return { - repoName: "aepsdk-commons", - tagName: "bom-" + artifactVersion - } - - case "core": - case "lifecycle": - case "identity": - case "signal": - return { - repoName: `aepsdk-core-android`, - tagName: `v${artifactVersion}-${artifactId}` - } - - case "edge": - case "optimize": - case "places": - case "media": - case "target": - case "edgeidentity": - case "analytics": - case "assurance": - case "edgebridge": - case "edgeconsent": - case "edgemedia": - case "audience": - case "userprofile": - case "messaging": - case "campaignclassic": - case "campaign": - return { - repoName: `aepsdk-${artifactId}-android`, - tagName: `v${artifactVersion}` - } - - default: - console.log("artifactId not supported: " + artifactId) - return null - } -} - -module.exports = { - fetchMavenArtifactInfo, - fetchAndroidReleaseInfo -} diff --git a/.github/scripts/release_notes/constants.js b/.github/scripts/release_notes/constants.js new file mode 100644 index 0000000000..473b10ccc7 --- /dev/null +++ b/.github/scripts/release_notes/constants.js @@ -0,0 +1,135 @@ +/* +Copyright 2024 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const repoNames = [ + "aepsdk-commons", + "aepsdk-roku", + "aepsdk-react-native", + "aepsdk_flutter", + "aepsdk-core-ios", + "aepsdk-core-android", + "aepsdk-assurance-ios", + "aepsdk-assurance-android", + "aepsdk-userprofile-ios", + "aepsdk-userprofile-android", + "aepsdk-edge-ios", + "aepsdk-edge-android", + "aepsdk-edgeconsent-ios", + "aepsdk-edgeconsent-android", + "aepsdk-edgeidentity-ios", + "aepsdk-edgeidentity-android", + "aepsdk-edgemedia-ios", + "aepsdk-edgemedia-android", + "aepsdk-edgebridge-ios", + "aepsdk-edgebridge-android", + "aepsdk-optimize-ios", + "aepsdk-optimize-android", + "aepsdk-messaging-ios", + "aepsdk-messaging-android", + "aepsdk-campaign-ios", + "aepsdk-campaign-android", + "aepsdk-campaignclassic-ios", + "aepsdk-campaignclassic-android", + "aepsdk-media-ios", + "aepsdk-media-android", + "aepsdk-audience-ios", + "aepsdk-audience-android", + "aepsdk-analytics-ios", + "aepsdk-analytics-android", + "aepsdk-places-ios", + "aepsdk-places-android", + "aepsdk-target-ios", + "aepsdk-target-android", +] + +const EXTENSION_ENUM = Object.freeze({ + SDK: "SDK", + BOM: "BOM", + CORE: "Core", + LIFECYCLE: "Lifecycle", + IDENTITY: "Identity", + SIGNAL: "Signal", + ASSURANCE: "Assurance", + USERPROFILE: "UserProfile", + EDGE_CONSENT: "EdgeConsent", + EDGE: "Edge", + EDGE_IDENTITY: "EdgeIdentity", + EDGE_MEDIA: "EdgeMedia", + EDGE_BRIDGE: "EdgeBridge", + MESSAGING: "Messaging", + OPTIMIZE: "Optimize", + ANALYTICS: "Analytics", + TARGET: "Target", + PLACES: "Places", + MEDIA: "Media", + AUDIENCE: "Audience", + CAMPAIGN_STANDARD: "Campaign Standard", + CAMPAIGN_CLASSIC: "Campaign Classic" +}) + +const PLATFORM_ENUM = Object.freeze({ + IOS: "iOS", + ANDROID: "Android", + FLUTTER: "Flutter", + ROKU: "Roku", + REACT_NATIVE: "React Native" +}) + +const releaseNotesLocation = (() => { + const obj = {} + obj[EXTENSION_ENUM.CORE] = "./src/pages/home/base/mobile-core/release-notes.md" + obj[EXTENSION_ENUM.LIFECYCLE] = "./src/pages/home/base/mobile-core/release-notes.md" + obj[EXTENSION_ENUM.IDENTITY] = "./src/pages/home/base/mobile-core/release-notes.md" + obj[EXTENSION_ENUM.SIGNAL] = "./src/pages/home/base/mobile-core/release-notes.md" + obj[EXTENSION_ENUM.ASSURANCE] = "./src/pages/home/base/assurance/release-notes.md" + obj[EXTENSION_ENUM.USERPROFILE] = "./src/pages/home/base/profile/release-notes.md" + obj[EXTENSION_ENUM.EDGE_CONSENT] = "./src/pages/edge/consent-for-edge-network/release-notes.md" + obj[EXTENSION_ENUM.EDGE] = "./src/pages/edge/edge-network/release-notes.md" + obj[EXTENSION_ENUM.EDGE_IDENTITY] = "./src/pages/edge/identity-for-edge-network/release-notes.md" + obj[EXTENSION_ENUM.EDGE_MEDIA] = "./src/pages/edge/media-for-edge-network/release-notes.md" + obj[EXTENSION_ENUM.MESSAGING] = "./src/pages/edge/adobe-journey-optimizer/release-notes.md" + obj[EXTENSION_ENUM.OPTIMIZE] = "./src/pages/edge/adobe-journey-optimizer-decisioning/release-notes.md" + obj[EXTENSION_ENUM.ANALYTICS] = "./src/pages/solution/adobe-analytics/release-notes.md" + obj[EXTENSION_ENUM.TARGET] = "./src/pages/solution/adobe-target/release-notes.md" + obj[EXTENSION_ENUM.PLACES] = "./src/pages/solution/places/release-notes.md" + obj[EXTENSION_ENUM.MEDIA] = "./src/pages/solution/adobe-media-analytics/release-notes.md" + obj[EXTENSION_ENUM.AUDIENCE] = "./src/pages/solution/adobe-audience-manager/release-notes.md" + obj[EXTENSION_ENUM.CAMPAIGN_STANDARD] = "./src/pages/solution/adobe-campaign-standard/release-notes.md" + obj[EXTENSION_ENUM.CAMPAIGN_CLASSIC] = "./src/pages/solution/adobe-campaign-classic/release-notes.md" + + return obj +})() + +const PST_TIMEZONE = "America/Los_Angeles" + +const PST_TIMEZONE_OFFSET = 420 + +const MAIN_RELEASE_NOTES_LOCATION = "./src/pages/home/release-notes/index.md" + +const BOM_RELEASE_TAG_PREFIX = "bom-" // BOM release tag prefix + +const REACT_NATIVE_RELEASE_TAG_PREFIX = "@adobe/react-native-aep" // React Native release tag prefix + +const FLUTTER_RELEASE_TAG_PREFIX = "flutter_aep" // Flutter release tag prefix + +module.exports = { + repoNames, + releaseNotesLocation, + PST_TIMEZONE, + PST_TIMEZONE_OFFSET, + MAIN_RELEASE_NOTES_LOCATION, + EXTENSION_ENUM, + PLATFORM_ENUM, + BOM_RELEASE_TAG_PREFIX, + REACT_NATIVE_RELEASE_TAG_PREFIX, + FLUTTER_RELEASE_TAG_PREFIX +} \ No newline at end of file diff --git a/.github/scripts/release_notes/fetchReleaseNotes.js b/.github/scripts/release_notes/fetchReleaseNotes.js new file mode 100644 index 0000000000..a1c66b1db6 --- /dev/null +++ b/.github/scripts/release_notes/fetchReleaseNotes.js @@ -0,0 +1,50 @@ +/* +Copyright 2024 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { fetchReleaseInfo, GithubReleaseInfo } = require('./githubRelease'); +const { convertToSDKReleaseInfo } = require('./SDKReleaseInfo'); + +/** + * Fetches release information from GitHub for the specified repositories. + * + * @param {string[]} repoNames - An array of repository names. + * @param {string} org - The organization name. + * @param {string} token - The GitHub access token. + * @param {number} timestampInMilliseconds - The timestamp in milliseconds. + * @returns {Promise} - A promise that resolves to an array of SDK release information objects, return an empty array if no new release is found. + */ +async function fetchReleaseInfoFromGitHub(repoNames, org, token, timestampInMilliseconds) { + let releaseInfoPromises = repoNames.map(repoName => fetchReleaseInfo(token, org, repoName)); + let releaseInfoLists = await Promise.all(releaseInfoPromises); + return releaseInfoLists.flat().flatMap(releaseInfo => { + let lastTimeStamp = Date.parse(releaseInfo.published_at) + if (timestampInMilliseconds < lastTimeStamp) { + let sdkReleaseInfo = convertToSDKReleaseInfo(releaseInfo) + if (sdkReleaseInfo != null) { + return [sdkReleaseInfo] + } + } + return [] + }) +} + +/** + * Sorts an array of release information objects by date in ascending order. + * + * @param {Array} releaseInfoArray - The array of release information objects to be sorted. + * @returns {Array} - The sorted array of release information objects. + */ +function sortReleaseInfoByDateASC(releaseInfoArray) { + return releaseInfoArray.sort((a, b) => (new Date(a.published_at) < new Date(b.published_at)) ? -1 : 1); +} + +module.exports = { fetchReleaseInfoFromGitHub, sortReleaseInfoByDateASC }; \ No newline at end of file diff --git a/.github/scripts/release_notes/github-release.js b/.github/scripts/release_notes/github-release.js deleted file mode 100644 index baa14a2a43..0000000000 --- a/.github/scripts/release_notes/github-release.js +++ /dev/null @@ -1,140 +0,0 @@ -/* -Copyright 2023 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -const https = require('https'); - -// REST API to retrieve release info -// https://docs.github.com/en/free-pro-team@latest/rest/releases/releases?apiVersion=2022-11-28#list-releases -// Example -// https://api.github.com/repos/adobe/aepsdk-react-native/releases?per_page=5 -// Example -// https://api.github.com/repos/adobe/aepsdk-react-native/releases/tags/@adobe/react-native-aepcore@5.0.1 - -// Alternative JS client: https://github.com/octokit/rest.js -// https://octokit.github.io/rest.js/v20#repos-get-release -// octokit.rest.repos.listReleases({ -// owner, -// repo, -// }); - -async function fetchReleaseInfo(token, owner, repo, capacity = 5) { - let options = { - host: 'api.github.com', - port: 443, - timeout: 5000, - path: `/repos/${owner}/${repo}/releases?per_page=${capacity}`, - method: 'GET', - headers: { - 'Accept': 'application/vnd.github+json', - 'User-Agent': 'server-side', - 'X-GitHub-Api-Version': '2022-11-28', - 'Authorization': `Bearer ${token}`, - } - }; - - console.log(`request options: ${JSON.stringify(options)}`) - - return new Promise((resolve) => { - let reqGet = https.request(options, function (res) { - if (res.statusCode != 200) { - throw new Error(`response statusCode: ${res.statusCode}`) - } - console.log(`response statusCode: ${res.statusCode}`) - - let data = []; - res.on('data', function (chunk) { - data.push(chunk); - }).on('end', function () { - let buffer = Buffer.concat(data); - let str = new TextDecoder("utf-8").decode(buffer) - let responseJson = JSON.parse(str) - if (Array.isArray(responseJson) == false) { - throw new Error("response JSON is not an array") - } - let array = [] - responseJson.forEach(element => { - array.push(releaseInfo(element.published_at, element.body, repo, element.tag_name)) - }); - resolve(array) - }); - }); - reqGet.on('error', function (e) { - console.error(e); - throw new Error("Got error response.") - }); - reqGet.on('timeout', function () { - reqGet.destroy() - throw new Error("Request timeout.") - }); - reqGet.end(); - }) -} - -async function fetchReleaseInfoWithTagName(token, owner, repo, tag) { - let options = { - host: 'api.github.com', - port: 443, - timeout: 5000, - path: `/repos/${owner}/${repo}/releases/tags/${tag}`, - method: 'GET', - headers: { - 'Accept': 'application/vnd.github+json', - 'User-Agent': 'server-side', - 'X-GitHub-Api-Version': '2022-11-28', - 'Authorization': `Bearer ${token}`, - } - }; - - console.log(`request options: ${JSON.stringify(options)}`) - return new Promise((resolve) => { - let reqGet = https.request(options, function (res) { - if (res.statusCode != 200) { - throw Error(`Error: response statusCode: ${res.statusCode}, please check if the tag (${tag}) exists in Github repo.`) - } - console.log(`response statusCode: ${res.statusCode}`) - - let data = []; - res.on('data', function (chunk) { - data.push(chunk); - }).on('end', function () { - let buffer = Buffer.concat(data); - let str = new TextDecoder("utf-8").decode(buffer) - let responseJson = JSON.parse(str) - resolve(releaseInfo(responseJson.published_at, responseJson.body, repo, responseJson.tag_name)) - }); - }); - reqGet.on('error', function (e) { - console.error(e); - throw new Error("Got error response.") - }); - reqGet.on('timeout', function () { - reqGet.destroy() - throw new Error("Request timeout.") - }); - reqGet.end(); - }) - -} - -function releaseInfo(published_at, body, repo_name, tag_name) { - return { - "published_at": published_at, - "body": body, - "repo_name": repo_name, - "tag_name": tag_name - } -} - -module.exports = { - fetchReleaseInfo, - fetchReleaseInfoWithTagName -} \ No newline at end of file diff --git a/.github/scripts/release_notes/githubRelease.js b/.github/scripts/release_notes/githubRelease.js new file mode 100644 index 0000000000..a799ae0e75 --- /dev/null +++ b/.github/scripts/release_notes/githubRelease.js @@ -0,0 +1,70 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { Octokit } = require("octokit"); +const { DEFAULT_TIMEZONE } = require('./utils'); + +/** + * Fetches release information from GitHub. + * + * @param {string} token - The GitHub authentication token. + * @param {string} owner - The owner of the repository. + * @param {string} repo - The name of the repository. + * @param {number} [capacity=5] - The maximum number of releases to fetch. + * @returns {Promise>} - A promise that resolves to an array of GithubReleaseInfo objects. + * @throws {Error} - Throw an error if failed to fetch the release info from GitHub. + */ +async function fetchReleaseInfo(token, owner, repo, capacity = 5) { + const octokit = new Octokit({ + auth: token, + timeZone: DEFAULT_TIMEZONE, + retry: { enabled: true }, + }); + // https://octokit.github.io/rest.js/v20#repos-get-release + try { + const response = await octokit.rest.repos.listReleases({ + owner, + repo, + capacity, + }); + return response.data.map((element) => new GithubReleaseInfo(element.published_at, element.body, repo, element.tag_name)); + } catch (e) { + console.error(e) + throw Error("Failed to fetch release info from GitHub") + } +} + + +/** + * Represents information about a GitHub release. + * @class + */ +class GithubReleaseInfo { + /** + * Creates a new instance of the GithubReleaseInfo class. + * @param {string} published_at - The date and time when the release was published. + * @param {string} body - The body content of the release. + * @param {string} repo_name - The name of the repository. + * @param {string} tag_name - The tag name of the release. + */ + constructor(published_at, body, repo_name, tag_name) { + this.published_at = published_at; + this.body = body; + this.repo_name = repo_name; + this.tag_name = tag_name; + } +} + +module.exports = { + fetchReleaseInfo, + GithubReleaseInfo +} \ No newline at end of file diff --git a/.github/scripts/release_notes/index.js b/.github/scripts/release_notes/index.js index 3c8291483b..cb06b01ae3 100644 --- a/.github/scripts/release_notes/index.js +++ b/.github/scripts/release_notes/index.js @@ -11,48 +11,63 @@ governing permissions and limitations under the License. */ const timestampObj = require('./timestamp.json') -const releaseNoteMap = require('./releaseNoteMap.json') -const { saveJsonObjToFile } = require('./utils') -const { updateReleaseNotesPage, fetchAllReleaseInfo, sortReleaseInfoByDateASC } = require('./update-release-notes'); +const { repoNames, releaseNotesLocation, MAIN_RELEASE_NOTES_LOCATION, PLATFORM_ENUM, EXTENSION_ENUM } = require('./constants') +const { saveJsonObjToFile, setTimeZoneToPST, convertToDateTime } = require('./utils') +const { updateReleaseNotesPage } = require('./updateReleaseNotes'); +const { fetchReleaseInfoFromGitHub, sortReleaseInfoByDateASC } = require('./fetchReleaseNotes'); -const token = process.argv[2]; +// Run the script on the root directory of the project: node .github/scripts/release_notes/index.js [--dry-run] +const GITHUB_TOKEN = process.argv[2]; -if (token == undefined) { +if (GITHUB_TOKEN === undefined) { throw new Error("token is undefined") } -//before running the script, make sure the default time zone is set to PST in the GitHub action -process.env.TZ = "America/Los_Angeles" +const DRY_RUN = process.argv.includes("--dry-run") -const offset = new Date().getTimezoneOffset() - -if (offset != 420) { +// Before running the script, make sure the default time zone is set to PST in the GitHub action +if (!setTimeZoneToPST()) { throw new Error("The default time zone is not set to PST") } -run() +console.log(`Start to fetch release info from GitHub created after [${convertToDateTime(timestampObj.ts)}]`); -async function run() { - const list = await fetchAllReleaseInfo(token, timestampObj.ts) +(async function (dryRun) { + const list = await fetchReleaseInfoFromGitHub(repoNames, "adobe", GITHUB_TOKEN, timestampObj.ts) const sortedList = sortReleaseInfoByDateASC(list) + if (dryRun) { + console.log("[Dry run mode] The following release notes need to be updated:") + console.dir(sortedList) + return + } // 1. Update the main release page - updateReleaseNotesPage("./src/pages/home/release-notes/index.md", sortedList) - const ignoreList = ['AEP React Native', 'Roku', 'AEP Flutter'] + updateReleaseNotesPage(MAIN_RELEASE_NOTES_LOCATION, sortedList) for (const releaseInfo of sortedList) { - // We don't have separate release note pages for AEP React Native, Roku, and AEP Flutter - if (ignoreList.includes(releaseInfo.platform) || releaseInfo.extension == "BOM") { + // We don't have separate release-notes pages for React Native, Roku, Flutter, and BOM artifacts. + if (releaseInfo.platform === PLATFORM_ENUM.REACT_NATIVE || + releaseInfo.platform === PLATFORM_ENUM.ROKU || + releaseInfo.platform === PLATFORM_ENUM.FLUTTER || + releaseInfo.extension === EXTENSION_ENUM.BOM || + releaseInfo.extension === EXTENSION_ENUM.EDGE_BRIDGE) { continue } - let filePath = releaseNoteMap[releaseInfo.extension] + + // 2. Update the extension's release note page + let filePath = releaseNotesLocation[releaseInfo.extension] if (filePath != undefined) { - // 2. Update the extension's release note page updateReleaseNotesPage(filePath, [releaseInfo]) } else { - console.error(`Error: no release note page found for ${releaseInfo.extension}`) + throw Error(`Not found the release note page for ${releaseInfo.extension}`) } } + + // 3. Save the timestamp to the file let jsonObj = { "ts": Date.now() } saveJsonObjToFile(jsonObj, `${__dirname}/timestamp.json`) -} +})(DRY_RUN).then( + () => console.log("Finished updating release notes") +).catch( + (error) => console.error(error) +); \ No newline at end of file diff --git a/.github/scripts/release_notes/releaseNoteMap.json b/.github/scripts/release_notes/releaseNoteMap.json deleted file mode 100644 index d477f41589..0000000000 --- a/.github/scripts/release_notes/releaseNoteMap.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Core": "./src/pages/home/base/mobile-core/release-notes.md", - "Lifecycle": "./src/pages/home/base/mobile-core/release-notes.md", - "Identity": "./src/pages/home/base/mobile-core/release-notes.md", - "Signal": "./src/pages/home/base/mobile-core/release-notes.md", - "Assurance": "./src/pages/home/base/assurance/release-notes.md", - "UserProfile": "./src/pages/home/base/profile/release-notes.md", - "EdgeConsent": "./src/pages/edge/consent-for-edge-network/release-notes.md", - "Edge": "./src/pages/edge/edge-network/release-notes.md", - "EdgeIdentity": "./src/pages/edge/identity-for-edge-network/release-notes.md", - "EdgeMedia": "./src/pages/edge/media-for-edge-network/release-notes.md", - "Messaging": "./src/pages/edge/adobe-journey-optimizer/release-notes.md", - "Optimize": "./src/pages/edge/adobe-journey-optimizer-decisioning/release-notes.md", - "Analytics": "./src/pages/solution/adobe-analytics/release-notes.md", - "Target": "./src/pages/solution/adobe-target/release-notes.md", - "Places": "./src/pages/solution/places/release-notes.md", - "Media": "./src/pages/solution/adobe-media-analytics/release-notes.md", - "Audience": "./src/pages/solution/adobe-audience-manager/release-notes.md", - "Campaign Standard": "./src/pages/solution/adobe-campaign-standard/release-notes.md", - "Campaign Classic": "./src/pages/solution/adobe-campaign-classic/release-notes.md" -} \ No newline at end of file diff --git a/.github/scripts/release_notes/releaseNotesTemplates.js b/.github/scripts/release_notes/releaseNotesTemplates.js new file mode 100644 index 0000000000..b5c21db1e7 --- /dev/null +++ b/.github/scripts/release_notes/releaseNotesTemplates.js @@ -0,0 +1,73 @@ +/* +Copyright 2024 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const lodashTemplate = require('lodash.template'); + + +const releaseNoteTemplateGenerator = lodashTemplate( + ` +## <%= date %> + +### <%= title %> + +<%= note %>` +) + +const releaseNoteWithoutDateTemplateGenerator = lodashTemplate( + ` +### <%= title %> + +<%= note %>` +) + +const BOMreleaseNoteTemplateGenerator = lodashTemplate( + ` +## <%= date %> + +### <%= title %> + +* This BOM ([Bill of Materials](https://central.sonatype.com/artifact/com.adobe.marketing.mobile/sdk-bom)) release includes changes to the following Android extensions. + + + + + +<%= note %> + + + +` +) + +const BOMreleaseNoteWithoutDateTemplateGenerator = lodashTemplate( + ` +### <%= title %> + +* This BOM ([Bill of Materials](https://central.sonatype.com/artifact/com.adobe.marketing.mobile/sdk-bom)) release includes changes to the following Android extensions. + + + + + +<%= note %> + + + +` +) + +module.exports = { + releaseNoteTemplateGenerator, + releaseNoteWithoutDateTemplateGenerator, + BOMreleaseNoteTemplateGenerator, + BOMreleaseNoteWithoutDateTemplateGenerator +}; \ No newline at end of file diff --git a/.github/scripts/release_notes/update-release-notes.js b/.github/scripts/release_notes/updateReleaseNotes.js similarity index 51% rename from .github/scripts/release_notes/update-release-notes.js rename to .github/scripts/release_notes/updateReleaseNotes.js index e8cb99654d..2d110a7553 100644 --- a/.github/scripts/release_notes/update-release-notes.js +++ b/.github/scripts/release_notes/updateReleaseNotes.js @@ -10,88 +10,11 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const { fetchReleaseInfo } = require('./github-release'); -const { fetchAndroidReleaseInfo } = require('./android-release'); -const { capitalizeFirstLetter, convertISODateToRleaseDateFormat, extractReleaseNotes } = require('./utils'); -const lodashTemplate = require('lodash.template'); +const { convertISODateToRleaseDateFormat, extractReleaseNotes } = require('./utils'); +const { releaseNoteTemplateGenerator, releaseNoteWithoutDateTemplateGenerator, BOMreleaseNoteTemplateGenerator, BOMreleaseNoteWithoutDateTemplateGenerator } = require('./releaseNotesTemplates'); const fs = require("fs"); const _ = require('lodash'); -const repoNames = [ - "aepsdk-roku", - "aepsdk-react-native", - "aepsdk_flutter", - "aepsdk-core-ios", - "aepsdk-assurance-ios", - "aepsdk-userprofile-ios", - "aepsdk-edge-ios", - "aepsdk-edgeconsent-ios", - "aepsdk-edgeidentity-ios", - "aepsdk-edgemedia-ios", - "aepsdk-edgebridge-ios", - "aepsdk-optimize-ios", - "aepsdk-messaging-ios", - "aepsdk-campaign-ios", - "aepsdk-campaignclassic-ios", - "aepsdk-media-ios", - "aepsdk-audience-ios", - "aepsdk-analytics-ios", - "aepsdk-places-ios", - "aepsdk-target-ios", -] - -const releaseNoteTemplateGenerator = lodashTemplate( - ` -## <%= date %> - -### <%= title %> - -<%= note %>` -) - -const releaseNoteWithoutDateTemplateGenerator = lodashTemplate( - ` -### <%= title %> - -<%= note %>` -) - -const BOMreleaseNoteTemplateGenerator = lodashTemplate( - ` -## <%= date %> - -### <%= title %> - -* This BOM ([Bill of Materials](https://central.sonatype.com/artifact/com.adobe.marketing.mobile/sdk-bom)) release includes changes to the following Android extensions. - - - - - -<%= note %> - - - -` -) - -const BOMreleaseNoteWithoutDateTemplateGenerator = lodashTemplate( - ` -### <%= title %> - -* This BOM ([Bill of Materials](https://central.sonatype.com/artifact/com.adobe.marketing.mobile/sdk-bom)) release includes changes to the following Android extensions. - - - - - -<%= note %> - - - -` -) - function extractBOMTableContent(releaseNote) { let lines = releaseNote.split('\n'); let newLines = [] @@ -179,7 +102,7 @@ function filterExistingReleaseInfo(releaseInfoArray, lines) { function generateReleaseNotesSection(releaseInfo) { let releaseNote = '' - if (releaseInfo.extension == 'BOM') { + if (releaseInfo.extension === 'BOM') { releaseNote = generateBOMReleaseNoteSection(releaseInfo.published_at, releaseInfo.platform, releaseInfo.extension, releaseInfo.version, releaseInfo.body) } else { releaseNote = generateReleaseNoteSection(releaseInfo.published_at, releaseInfo.platform, releaseInfo.extension, releaseInfo.version, releaseInfo.body) @@ -189,7 +112,7 @@ function generateReleaseNotesSection(releaseInfo) { function generateReleaseNotesSectionWithoutDateLine(releaseInfo) { let releaseNote = '' - if (releaseInfo.extension == 'BOM') { + if (releaseInfo.extension === 'BOM') { releaseNote = generateBOMReleaseNoteSectionWithoutDateLine(releaseInfo.platform, releaseInfo.extension, releaseInfo.version, releaseInfo.body) } else { releaseNote = generateReleaseNoteSectionWithoutDateLine(releaseInfo.platform, releaseInfo.extension, releaseInfo.version, releaseInfo.body) @@ -203,13 +126,13 @@ async function updateReleaseNotesPage(filePath, releaseInfoArray) { // Find the index of the release notes header. let releaseNotesHeader = "# Release notes" let releaseNotesHeaderIndex = contentLines.indexOf(releaseNotesHeader) - if (releaseNotesHeaderIndex == -1) { + if (releaseNotesHeaderIndex === -1) { console.error("Error: can't find the release notes header") return } let contentIsChanged = false for (const releaseInfo of releaseInfoArray) { - console.log("Updating release notes with:", releaseInfo) + // console.log("Updating release notes with:", releaseInfo) let title = generateReleaseTitle(releaseInfo.platform, releaseInfo.extension, releaseInfo.version) let titleLine = `### ${title}` if (hasLineStartWith(titleLine, contentLines)) { @@ -250,126 +173,6 @@ function hasLineStartWith(string, lineArray) { return false } -async function fetchNonAndroidReleaseInfo(token, timestampInMilliseconds) { - let releaseInofArray = [] - for (const repoName of repoNames) { - let releaseInfoList = await fetchReleaseInfo(token, "adobe", repoName) - for (const releaseInfo of releaseInfoList) { - let lastTimeStamp = Date.parse(releaseInfo.published_at) - if (timestampInMilliseconds < lastTimeStamp) { - releaseInofArray.push(releaseInfo) - } - } - } - return releaseInofArray -} - -function updateNonAndroidReleaseInfo(releaseInfo) { - switch (releaseInfo.repo_name) { - case "aepsdk-roku": - releaseInfo.platform = 'Roku' - releaseInfo.extension = 'SDK' - releaseInfo.version = releaseInfo.tag_name - break; - case "aepsdk-react-native": - releaseInfo.platform = 'React Native' - let tmArray = releaseInfo.tag_name.replace('@adobe/react-native-aep', '').split('@') - releaseInfo.extension = capitalizeFirstLetter(tmArray[0]) - releaseInfo.version = tmArray[1] - break; - case "aepsdk-flutter": - releaseInfo.platform = 'Flutter' - let array = releaseInfo.tag_name.replace('flutter_aep', '').split('@') - releaseInfo.extension = capitalizeFirstLetter(array[0]) - releaseInfo.version = array[1] - break; - case "aepsdk-edge-ios": - case "aepsdk-media-ios": - case "aepsdk-audience-ios": - case "aepsdk-analytics-ios": - case "aepsdk-places-ios": - case "aepsdk-target-ios": - case "aepsdk-optimize-ios": - case "aepsdk-core-ios": - case "aepsdk-messaging-ios": - case "aepsdk-assurance-ios": - releaseInfo.platform = 'iOS' - releaseInfo.extension = capitalizeFirstLetter(releaseInfo.repo_name.replace('aepsdk-', '').replace('-ios', '')) - releaseInfo.version = releaseInfo.tag_name - break; - case "aepsdk-edgeconsent-ios": - releaseInfo.platform = 'iOS' - releaseInfo.extension = 'EdgeConsent' - releaseInfo.version = releaseInfo.tag_name - break; - case "aepsdk-edgeidentity-ios": - releaseInfo.platform = 'iOS' - releaseInfo.extension = 'EdgeIdentity' - releaseInfo.version = releaseInfo.tag_name - break; - case "aepsdk-userprofile-ios": - releaseInfo.platform = 'iOS' - releaseInfo.extension = 'UserProfile' - releaseInfo.version = releaseInfo.tag_name - break; - case "aepsdk-edgebridge-ios": - releaseInfo.platform = 'iOS' - releaseInfo.extension = 'EdgeBridge' - releaseInfo.version = releaseInfo.tag_name - break; - case "aepsdk-edgemedia-ios": - releaseInfo.platform = 'iOS' - releaseInfo.extension = 'EdgeMedia' - releaseInfo.version = releaseInfo.tag_name - break; - case "aepsdk-campaignclassic-ios": - releaseInfo.platform = 'iOS' - releaseInfo.extension = 'Campaign Classic' - releaseInfo.version = releaseInfo.tag_name - break; - case "aepsdk-campaign-ios": - releaseInfo.platform = 'iOS' - releaseInfo.extension = 'Campaign Standard' - releaseInfo.version = releaseInfo.tag_name - break; - default: - console.log("unsupported repoName: " + releaseInfo.repoName) - } - return releaseInfo -} - -async function fetchAllReleaseInfo(token, timestampInMilliseconds) { - let releaseInfoArray = [] - let rawInfoArray = await fetchNonAndroidReleaseInfo(token, timestampInMilliseconds) - - for (const releaseInfo of rawInfoArray) { - releaseInfoArray.push(updateNonAndroidReleaseInfo(releaseInfo)) - } - - let AndroidReleaseInfoArray = await fetchAndroidReleaseInfo(token, "com.adobe.marketing.mobile", timestampInMilliseconds); - return releaseInfoArray.concat(AndroidReleaseInfoArray) -} - -function sortReleaseInfoByDateASC(releaseInfoArray) { - releaseInfoArray.sort((a, b) => { - let dateA = new Date(a.published_at) - let dateB = new Date(b.published_at) - if (dateA < dateB) { - return -1; - } - if (dateA > dateB) { - return 1; - } - return 0; - }) - return releaseInfoArray -} - module.exports = { - fetchAllReleaseInfo, - sortReleaseInfoByDateASC, - generateReleaseNoteSection, - generateBOMReleaseNoteSection, - filterExistingReleaseInfo, updateReleaseNotesPage } diff --git a/.github/scripts/release_notes/utils.js b/.github/scripts/release_notes/utils.js index 3f46095b12..50459bf53b 100644 --- a/.github/scripts/release_notes/utils.js +++ b/.github/scripts/release_notes/utils.js @@ -10,15 +10,28 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -const execSync = require('child_process').execSync; const fs = require("fs"); +const { PST_TIMEZONE, PST_TIMEZONE_OFFSET } = require('./constants') +/** + * Checks if a given timestamp is earlier than a specified number of hours from the current time. + * + * @param {number} hours - The number of hours to compare against. + * @param {number} timestampInMilliseconds - The timestamp to compare. + * @returns {boolean} Returns true if the timestamp is earlier than the specified number of hours from the current time, otherwise false. + */ function isEarlierThanXHours(hours, timestampInMilliseconds) { const timestamp = new Date(timestampInMilliseconds); const now = new Date(); return timestamp < now - (hours * 60 * 60 * 1000); } +/** + * Extracts the release notes from the given release text. + * + * @param {string} releaseText - The release text to extract the release notes from. + * @returns {string[]} - An array of release notes lines. + */ function extractReleaseNotes(releaseText) { if (!releaseText) { return [] @@ -45,41 +58,55 @@ function extractReleaseNotes(releaseText) { } } -function releaseFileContainsLineStartWith(lineStartWithString) { - try { - execSync(`grep -E "^${lineStartWithString}" ./src/pages/documentation/release-notes/index.md`, { stdio: 'ignore' }); - return true; - } catch (e) { - return false; - } -} - +/** + * Saves a JSON object to a file. + * + * @param {Object} jsonObj - The JSON object to be saved. + * @param {string} filePath - The path of the file to save the JSON object to. + * @throws {Error} If there is an error writing the JSON object to the file. + */ function saveJsonObjToFile(jsonObj, filePath) { fs.writeFile(filePath, JSON.stringify(jsonObj), err => { - if (err) console.log("Failed to write json object to file:", err); + if (err) { + console.error("Failed to write json object to file."); + throw err; + } }); } -function capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} - -// TODO: consider using dayjs to handle date format +/** + * Converts an ISO 8601 date string to a release date format. + * + * @param {string} iso8601DateStr - The ISO 8601 date string to convert. + * @returns {string} - The release date format. + */ function convertISODateToRleaseDateFormat(iso8601DateStr) { const date = new Date(iso8601DateStr) - const month = date.getMonth() - const day = date.getDate() - const year = date.getFullYear() const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] - const fullMonth = monthNames[month] - return `${fullMonth} ${day}, ${year}` + return `${monthNames[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}` +} + +/** + * Sets the time zone of the NodeJS runtime to PST (Pacific Standard Time). + * + * @returns {boolean} Returns true if the time zone is successfully set to PST, false otherwise. + */ +function setTimeZoneToPST() { + process.env.TZ = PST_TIMEZONE + const offset = new Date().getTimezoneOffset() + return (offset === PST_TIMEZONE_OFFSET) +} + +function convertToDateTime(timestamp) { + const date = new Date(timestamp); + return date.toLocaleString(); } module.exports = { isEarlierThanXHours, - releaseFileContainsLineStartWith, saveJsonObjToFile, extractReleaseNotes, convertISODateToRleaseDateFormat, - capitalizeFirstLetter + setTimeZoneToPST, + convertToDateTime, } \ No newline at end of file diff --git a/package.json b/package.json index 1cfd19c903..ed9695c7f6 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "packageManager": "yarn@3.2.1", "devDependencies": { "lodash.template": "^4.5.0", + "octokit": "^3.2.0", "remark-cli": "^11.0.0", "remark-validate-links": "^12.1.0" } diff --git a/yarn.lock b/yarn.lock index 699267959f..dfa0c0e1b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3309,6 +3309,305 @@ __metadata: languageName: node linkType: hard +"@octokit/app@npm:^14.0.2": + version: 14.1.0 + resolution: "@octokit/app@npm:14.1.0" + dependencies: + "@octokit/auth-app": ^6.0.0 + "@octokit/auth-unauthenticated": ^5.0.0 + "@octokit/core": ^5.0.0 + "@octokit/oauth-app": ^6.0.0 + "@octokit/plugin-paginate-rest": ^9.0.0 + "@octokit/types": ^12.0.0 + "@octokit/webhooks": ^12.0.4 + checksum: 2a27ea831d0367b07f3c4109bbc840c7ae7d5a52d3129593cd867364794eb51b16b0fc308b116a89af9a2f19553c72346e03dd07b952e82c222ed1e7880dfcac + languageName: node + linkType: hard + +"@octokit/auth-app@npm:^6.0.0": + version: 6.1.1 + resolution: "@octokit/auth-app@npm:6.1.1" + dependencies: + "@octokit/auth-oauth-app": ^7.1.0 + "@octokit/auth-oauth-user": ^4.1.0 + "@octokit/request": ^8.3.1 + "@octokit/request-error": ^5.1.0 + "@octokit/types": ^13.1.0 + deprecation: ^2.3.1 + lru-cache: ^10.0.0 + universal-github-app-jwt: ^1.1.2 + universal-user-agent: ^6.0.0 + checksum: 6b3b299865f4a612cf308b6c01ba38101930d1e3eb3444c4eaa5365bec9d62538d45b471e1ee3677244e26b899316bd4ad30ade821564f7f48ff9f51bb74c423 + languageName: node + linkType: hard + +"@octokit/auth-oauth-app@npm:^7.0.0, @octokit/auth-oauth-app@npm:^7.1.0": + version: 7.1.0 + resolution: "@octokit/auth-oauth-app@npm:7.1.0" + dependencies: + "@octokit/auth-oauth-device": ^6.1.0 + "@octokit/auth-oauth-user": ^4.1.0 + "@octokit/request": ^8.3.1 + "@octokit/types": ^13.0.0 + "@types/btoa-lite": ^1.0.0 + btoa-lite: ^1.0.0 + universal-user-agent: ^6.0.0 + checksum: 021e13c138279e9edd7d6dcdc484a2658ae07b834ec3f5f41158e3870b3413deb09024408d1615731c960243ba710ca638a868dcd2583f7eb80fa6204b70657b + languageName: node + linkType: hard + +"@octokit/auth-oauth-device@npm:^6.1.0": + version: 6.1.0 + resolution: "@octokit/auth-oauth-device@npm:6.1.0" + dependencies: + "@octokit/oauth-methods": ^4.1.0 + "@octokit/request": ^8.3.1 + "@octokit/types": ^13.0.0 + universal-user-agent: ^6.0.0 + checksum: 2824f74ea5eca3d8da9793f463ebca725c8a13a241085015f96f037771ef3e5fa82d5842f538353c683b709d8d32ccd481bfc0ba8cbcde708916ea95a78dd0d2 + languageName: node + linkType: hard + +"@octokit/auth-oauth-user@npm:^4.0.0, @octokit/auth-oauth-user@npm:^4.1.0": + version: 4.1.0 + resolution: "@octokit/auth-oauth-user@npm:4.1.0" + dependencies: + "@octokit/auth-oauth-device": ^6.1.0 + "@octokit/oauth-methods": ^4.1.0 + "@octokit/request": ^8.3.1 + "@octokit/types": ^13.0.0 + btoa-lite: ^1.0.0 + universal-user-agent: ^6.0.0 + checksum: 581197a427c1ef153350e46de7315c9da1a98904b67e5e13aed88d36e334d95d869f8f12a35ed70d7232c6afd6d3912200988e41959e30c83f880d072ee8b8ba + languageName: node + linkType: hard + +"@octokit/auth-token@npm:^4.0.0": + version: 4.0.0 + resolution: "@octokit/auth-token@npm:4.0.0" + checksum: d78f4dc48b214d374aeb39caec4fdbf5c1e4fd8b9fcb18f630b1fe2cbd5a880fca05445f32b4561f41262cb551746aeb0b49e89c95c6dd99299706684d0cae2f + languageName: node + linkType: hard + +"@octokit/auth-unauthenticated@npm:^5.0.0": + version: 5.0.1 + resolution: "@octokit/auth-unauthenticated@npm:5.0.1" + dependencies: + "@octokit/request-error": ^5.0.0 + "@octokit/types": ^12.0.0 + checksum: b6eed1fc15d47f45411c0229dd6613dd8fd4b79afbac23b8c47818da692a35d54f57e088294d9b71ce4dcc0f58ce0c77d12cd2700370d87770059248b9a8fbba + languageName: node + linkType: hard + +"@octokit/core@npm:^5.0.0": + version: 5.2.0 + resolution: "@octokit/core@npm:5.2.0" + dependencies: + "@octokit/auth-token": ^4.0.0 + "@octokit/graphql": ^7.1.0 + "@octokit/request": ^8.3.1 + "@octokit/request-error": ^5.1.0 + "@octokit/types": ^13.0.0 + before-after-hook: ^2.2.0 + universal-user-agent: ^6.0.0 + checksum: 57d5f02b759b569323dcb76cc72bf94ea7d0de58638c118ee14ec3e37d303c505893137dd72918328794844f35c74b3cd16999319c4b40d410a310d44a9b7566 + languageName: node + linkType: hard + +"@octokit/endpoint@npm:^9.0.1": + version: 9.0.5 + resolution: "@octokit/endpoint@npm:9.0.5" + dependencies: + "@octokit/types": ^13.1.0 + universal-user-agent: ^6.0.0 + checksum: d5cc2df9bd4603844c163eea05eec89c677cfe699c6f065fe86b83123e34554ec16d429e8142dec1e2b4cf56591ef0ce5b1763f250c87bc8e7bf6c74ba59ae82 + languageName: node + linkType: hard + +"@octokit/graphql@npm:^7.1.0": + version: 7.1.0 + resolution: "@octokit/graphql@npm:7.1.0" + dependencies: + "@octokit/request": ^8.3.0 + "@octokit/types": ^13.0.0 + universal-user-agent: ^6.0.0 + checksum: 7b2706796e0269fc033ed149ea211117bcacf53115fd142c1eeafc06ebc5b6290e4e48c03d6276c210d72e3695e8598f83caac556cd00714fc1f8e4707d77448 + languageName: node + linkType: hard + +"@octokit/oauth-app@npm:^6.0.0": + version: 6.1.0 + resolution: "@octokit/oauth-app@npm:6.1.0" + dependencies: + "@octokit/auth-oauth-app": ^7.0.0 + "@octokit/auth-oauth-user": ^4.0.0 + "@octokit/auth-unauthenticated": ^5.0.0 + "@octokit/core": ^5.0.0 + "@octokit/oauth-authorization-url": ^6.0.2 + "@octokit/oauth-methods": ^4.0.0 + "@types/aws-lambda": ^8.10.83 + universal-user-agent: ^6.0.0 + checksum: 4759ef41624928efee484802e3a6280d7a92205f435e0d299bc4b1e39661427d7f9ec33ef0d752dd6ee665e37d4afa81c8a6aea10ba53b8eb7da66167b0c52d4 + languageName: node + linkType: hard + +"@octokit/oauth-authorization-url@npm:^6.0.2": + version: 6.0.2 + resolution: "@octokit/oauth-authorization-url@npm:6.0.2" + checksum: 0f11169a3eeb782cc08312c923de1a702b25ae033b972ba40380b6d72cb3f684543c8b6a5cf6f05936fdc6b8892070d4f7581138d8efc1b4c4a55ae6d7762327 + languageName: node + linkType: hard + +"@octokit/oauth-methods@npm:^4.0.0, @octokit/oauth-methods@npm:^4.1.0": + version: 4.1.0 + resolution: "@octokit/oauth-methods@npm:4.1.0" + dependencies: + "@octokit/oauth-authorization-url": ^6.0.2 + "@octokit/request": ^8.3.1 + "@octokit/request-error": ^5.1.0 + "@octokit/types": ^13.0.0 + btoa-lite: ^1.0.0 + checksum: 2ca42f054a3b92f6f3fa9a984df7d75cc8c1f19aba5f6fc9636499dde3a8031e33148cbc936cace103b1eb7fe79d978aee7077aa6f69e0dd996ee345a10f2aa4 + languageName: node + linkType: hard + +"@octokit/openapi-types@npm:^20.0.0": + version: 20.0.0 + resolution: "@octokit/openapi-types@npm:20.0.0" + checksum: 23ff7613750f8b5790a0cbed5a2048728a7909e50d726932831044908357a932c7fc0613fb7b86430a49d31b3d03a180632ea5dd936535bfbc1176391a199e96 + languageName: node + linkType: hard + +"@octokit/openapi-types@npm:^22.0.1": + version: 22.0.1 + resolution: "@octokit/openapi-types@npm:22.0.1" + checksum: f361764bf965081bb94facc33a171a98c4d94285e5c218ca6355a5aea35d1ec732ab7fa7ac941034ca249601d768cfa5205bcbef0980c0c6faa2b842efeed2ec + languageName: node + linkType: hard + +"@octokit/plugin-paginate-graphql@npm:^4.0.0": + version: 4.0.1 + resolution: "@octokit/plugin-paginate-graphql@npm:4.0.1" + peerDependencies: + "@octokit/core": ">=5" + checksum: 109d895303d39c1ba362a260c71202f3c92798faa4f4e05638023685b5ac9191cee61759ea0eee43b9ce945cf8c52aebf2dbd54c392165e86448d6421e97b0f5 + languageName: node + linkType: hard + +"@octokit/plugin-paginate-rest@npm:^9.0.0": + version: 9.2.1 + resolution: "@octokit/plugin-paginate-rest@npm:9.2.1" + dependencies: + "@octokit/types": ^12.6.0 + peerDependencies: + "@octokit/core": 5 + checksum: 554ad17a7dcfd7028e321ffcae233f8ae7975569084f19d9b6217b47fb182e2604145108de7a9029777e6dc976b27b2dd7387e2e47a77532a72e6c195880576d + languageName: node + linkType: hard + +"@octokit/plugin-rest-endpoint-methods@npm:^10.0.0": + version: 10.4.1 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:10.4.1" + dependencies: + "@octokit/types": ^12.6.0 + peerDependencies: + "@octokit/core": 5 + checksum: 3e0e95515ccb7fdd5e5cff32a5e34a688fd275c6703caf786f7c49820e2bf2a66e7d845ba4eae4d03c307c1950ea417e34a17055b25b46e2019123b75b394c56 + languageName: node + linkType: hard + +"@octokit/plugin-retry@npm:^6.0.0": + version: 6.0.1 + resolution: "@octokit/plugin-retry@npm:6.0.1" + dependencies: + "@octokit/request-error": ^5.0.0 + "@octokit/types": ^12.0.0 + bottleneck: ^2.15.3 + peerDependencies: + "@octokit/core": ">=5" + checksum: 9c8663b5257cf4fa04cc737c064e9557501719d6d3af7cf8f46434a2117e1cf4b8d25d9eb4294ed255ad17a0ede853542649870612733f4b8ece97e24e391d22 + languageName: node + linkType: hard + +"@octokit/plugin-throttling@npm:^8.0.0": + version: 8.2.0 + resolution: "@octokit/plugin-throttling@npm:8.2.0" + dependencies: + "@octokit/types": ^12.2.0 + bottleneck: ^2.15.3 + peerDependencies: + "@octokit/core": ^5.0.0 + checksum: 12c357175783bcd0feea454ece57f033928948a0555dc97c79675b56d2cc79043d2a5e28a7554d3531f1de13583634df3b48fb9609f79e8bb3adad92820bd807 + languageName: node + linkType: hard + +"@octokit/request-error@npm:^5.0.0, @octokit/request-error@npm:^5.1.0": + version: 5.1.0 + resolution: "@octokit/request-error@npm:5.1.0" + dependencies: + "@octokit/types": ^13.1.0 + deprecation: ^2.0.0 + once: ^1.4.0 + checksum: 2cdbb8e44072323b5e1c8c385727af6700e3e492d55bc1e8d0549c4a3d9026914f915866323d371b1f1772326d6e902341c872679cc05c417ffc15cadf5f4a4e + languageName: node + linkType: hard + +"@octokit/request@npm:^8.3.0, @octokit/request@npm:^8.3.1": + version: 8.4.0 + resolution: "@octokit/request@npm:8.4.0" + dependencies: + "@octokit/endpoint": ^9.0.1 + "@octokit/request-error": ^5.1.0 + "@octokit/types": ^13.1.0 + universal-user-agent: ^6.0.0 + checksum: 3d937e817a85c0adf447ab46b428ccd702c31b2091e47adec90583ec2242bd64666306fe8188628fb139aa4752e19400eb7652b0f5ca33cd9e77bbb2c60b202a + languageName: node + linkType: hard + +"@octokit/types@npm:^12.0.0, @octokit/types@npm:^12.2.0, @octokit/types@npm:^12.6.0": + version: 12.6.0 + resolution: "@octokit/types@npm:12.6.0" + dependencies: + "@octokit/openapi-types": ^20.0.0 + checksum: 850235f425584499a2266d5c585c1c2462ae11e25c650567142f3342cb9ce589c8c8fed87705811ca93271fd28c68e1fa77b88b67b97015d7b63d269fa46ed05 + languageName: node + linkType: hard + +"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0": + version: 13.4.0 + resolution: "@octokit/types@npm:13.4.0" + dependencies: + "@octokit/openapi-types": ^22.0.1 + checksum: 71d1e61e82ca10cb7f9e79d15158b7a9e84a6fe815fa865e7adbafee225963a18919316d7f0d7c96bc9e6751cf0f1663da056da468e8d5cebcbdaf44316cafba + languageName: node + linkType: hard + +"@octokit/webhooks-methods@npm:^4.1.0": + version: 4.1.0 + resolution: "@octokit/webhooks-methods@npm:4.1.0" + checksum: 0ce67220156d554ae4bc6a7230ae62c0389b9bbee1f6d1077947e64645ee864f0702778e86427d59ae970176620753f54edb44665cedbeb9bc22b9348a074427 + languageName: node + linkType: hard + +"@octokit/webhooks-types@npm:7.4.0": + version: 7.4.0 + resolution: "@octokit/webhooks-types@npm:7.4.0" + checksum: bedb819a6ad944ea95cab56da69a0c158d5f689d7f24a45e9a45bcbc4a34550858b1ef0d80a5f4c2fe02a6fc8d14302ca07123fc16a7cce93bb175c11f6a68dc + languageName: node + linkType: hard + +"@octokit/webhooks@npm:^12.0.4": + version: 12.2.0 + resolution: "@octokit/webhooks@npm:12.2.0" + dependencies: + "@octokit/request-error": ^5.0.0 + "@octokit/webhooks-methods": ^4.1.0 + "@octokit/webhooks-types": 7.4.0 + aggregate-error: ^3.1.0 + checksum: 69d32fd24ea00f632d1ba3edb84c8e15852b47ad120fe7db938bc8fd1f2823dd7e61707b3280a29818925871b51e472c5f892f76eee0c6d0cee8d0e51c7b5f5d + languageName: node + linkType: hard + "@parcel/bundler-default@npm:2.6.2": version: 2.6.2 resolution: "@parcel/bundler-default@npm:2.6.2" @@ -4277,6 +4576,20 @@ __metadata: languageName: node linkType: hard +"@types/aws-lambda@npm:^8.10.83": + version: 8.10.137 + resolution: "@types/aws-lambda@npm:8.10.137" + checksum: 172238b8a5d1e4002d11517f4e6739836806b59844da336ce44e72cd544c97453071ffdf6bedd736858e96569123988dd451055bf41ea3876e7201255d5c7713 + languageName: node + linkType: hard + +"@types/btoa-lite@npm:^1.0.0": + version: 1.0.2 + resolution: "@types/btoa-lite@npm:1.0.2" + checksum: 4c46b163c881a75522c7556dd7a7df8a0d4c680a45e8bac34e50864e1c2d9df8dc90b99f75199154c60ef2faff90896b7e5f11df6936c94167a3e5e1c6f4d935 + languageName: node + linkType: hard + "@types/cacheable-request@npm:^6.0.1": version: 6.0.3 resolution: "@types/cacheable-request@npm:6.0.3" @@ -4468,6 +4781,15 @@ __metadata: languageName: node linkType: hard +"@types/jsonwebtoken@npm:^9.0.0": + version: 9.0.6 + resolution: "@types/jsonwebtoken@npm:9.0.6" + dependencies: + "@types/node": "*" + checksum: a568e7cb1c703bcb015eff8bf5996e276e748d2b39ddc47edf5ddccd1378f5792179c43302a1c803e47a54b0220f9ecaae445ec444d28bf81b88856f899e85b9 + languageName: node + linkType: hard + "@types/keyv@npm:^3.1.1, @types/keyv@npm:^3.1.4": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -5117,7 +5439,7 @@ __metadata: languageName: node linkType: hard -"aggregate-error@npm:^3.0.0": +"aggregate-error@npm:^3.0.0, aggregate-error@npm:^3.1.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" dependencies: @@ -5924,6 +6246,13 @@ __metadata: languageName: node linkType: hard +"before-after-hook@npm:^2.2.0": + version: 2.2.3 + resolution: "before-after-hook@npm:2.2.3" + checksum: a1a2430976d9bdab4cd89cb50d27fa86b19e2b41812bf1315923b0cba03371ebca99449809226425dd3bcef20e010db61abdaff549278e111d6480034bebae87 + languageName: node + linkType: hard + "better-opn@npm:^2.1.1": version: 2.1.1 resolution: "better-opn@npm:2.1.1" @@ -6013,6 +6342,13 @@ __metadata: languageName: node linkType: hard +"bottleneck@npm:^2.15.3": + version: 2.19.5 + resolution: "bottleneck@npm:2.19.5" + checksum: c5eef1bbea12cef1f1405e7306e7d24860568b0f7ac5eeab706a86762b3fc65ef6d1c641c8a166e4db90f412fc5c948fc5ce8008a8cd3d28c7212ef9c3482bda + languageName: node + linkType: hard + "boxen@npm:^4.2.0": version: 4.2.0 resolution: "boxen@npm:4.2.0" @@ -6183,6 +6519,20 @@ __metadata: languageName: node linkType: hard +"btoa-lite@npm:^1.0.0": + version: 1.0.0 + resolution: "btoa-lite@npm:1.0.0" + checksum: c2d61993b801f8e35a96f20692a45459c753d9baa29d86d1343e714f8d6bbe7069f1a20a5ae868488f3fb137d5bd0c560f6fbbc90b5a71050919d2d2c97c0475 + 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: 80bb945f5d782a56f374b292770901065bad21420e34936ecbe949e57724b4a13874f735850dd1cc61f078773c4fb5493a41391e7bda40d1fa388d6bd80daaab + languageName: node + linkType: hard + "buffer-equal@npm:0.0.1": version: 0.0.1 resolution: "buffer-equal@npm:0.0.1" @@ -7872,6 +8222,13 @@ __metadata: languageName: node linkType: hard +"deprecation@npm:^2.0.0, deprecation@npm:^2.3.1": + version: 2.3.1 + resolution: "deprecation@npm:2.3.1" + checksum: f56a05e182c2c195071385455956b0c4106fe14e36245b00c689ceef8e8ab639235176a96977ba7c74afb173317fac2e0ec6ec7a1c6d1e6eaa401c586c714132 + languageName: node + linkType: hard + "dequal@npm:^2.0.0": version: 2.0.3 resolution: "dequal@npm:2.0.3" @@ -7954,6 +8311,7 @@ __metadata: "@adobe/gatsby-theme-aio": ^4.11.12 gatsby: 4.22.0 lodash.template: ^4.5.0 + octokit: ^3.2.0 react: ^18.2.0 react-dom: ^17.0.2 remark-cli: ^11.0.0 @@ -8308,6 +8666,15 @@ __metadata: languageName: node linkType: hard +"ecdsa-sig-formatter@npm:1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: ^5.0.1 + checksum: 207f9ab1c2669b8e65540bce29506134613dd5f122cccf1e6a560f4d63f2732d427d938f8481df175505aad94583bcb32c688737bb39a6df0625f903d6d93c03 + languageName: node + linkType: hard + "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -12677,6 +13044,24 @@ __metadata: languageName: node linkType: hard +"jsonwebtoken@npm:^9.0.2": + version: 9.0.2 + resolution: "jsonwebtoken@npm:9.0.2" + dependencies: + jws: ^3.2.2 + lodash.includes: ^4.3.0 + lodash.isboolean: ^3.0.3 + lodash.isinteger: ^4.0.4 + lodash.isnumber: ^3.0.3 + lodash.isplainobject: ^4.0.6 + lodash.isstring: ^4.0.1 + lodash.once: ^4.0.0 + ms: ^2.1.1 + semver: ^7.5.4 + checksum: fc739a6a8b33f1974f9772dca7f8493ca8df4cc31c5a09dcfdb7cff77447dcf22f4236fb2774ef3fe50df0abeb8e1c6f4c41eba82f500a804ab101e2fbc9d61a + languageName: node + linkType: hard + "jsprim@npm:^1.2.2": version: 1.4.2 resolution: "jsprim@npm:1.4.2" @@ -12718,6 +13103,27 @@ __metadata: languageName: node linkType: hard +"jwa@npm:^1.4.1": + version: 1.4.1 + resolution: "jwa@npm:1.4.1" + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: ^5.0.1 + checksum: ff30ea7c2dcc61f3ed2098d868bf89d43701605090c5b21b5544b512843ec6fd9e028381a4dda466cbcdb885c2d1150f7c62e7168394ee07941b4098e1035e2f + languageName: node + linkType: hard + +"jws@npm:^3.2.2": + version: 3.2.2 + resolution: "jws@npm:3.2.2" + dependencies: + jwa: ^1.4.1 + safe-buffer: ^5.0.1 + checksum: f0213fe5b79344c56cd443428d8f65c16bf842dc8cb8f5aed693e1e91d79c20741663ad6eff07a6d2c433d1831acc9814e8d7bada6a0471fbb91d09ceb2bf5c2 + languageName: node + linkType: hard + "keypather@npm:^1.10.2": version: 1.10.2 resolution: "keypather@npm:1.10.2" @@ -13118,6 +13524,20 @@ __metadata: languageName: node linkType: hard +"lodash.includes@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.includes@npm:4.3.0" + checksum: 71092c130515a67ab3bd928f57f6018434797c94def7f46aafa417771e455ce3a4834889f4267b17887d7f75297dfabd96231bf704fd2b8c5096dc4a913568b6 + languageName: node + linkType: hard + +"lodash.isboolean@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isboolean@npm:3.0.3" + checksum: b70068b4a8b8837912b54052557b21fc4774174e3512ed3c5b94621e5aff5eb6c68089d0a386b7e801d679cd105d2e35417978a5e99071750aa2ed90bffd0250 + languageName: node + linkType: hard + "lodash.isequal@npm:^4.5.0": version: 4.5.0 resolution: "lodash.isequal@npm:4.5.0" @@ -13125,6 +13545,34 @@ __metadata: languageName: node linkType: hard +"lodash.isinteger@npm:^4.0.4": + version: 4.0.4 + resolution: "lodash.isinteger@npm:4.0.4" + checksum: 6034821b3fc61a2ffc34e7d5644bb50c5fd8f1c0121c554c21ac271911ee0c0502274852845005f8651d51e199ee2e0cfebfe40aaa49c7fe617f603a8a0b1691 + languageName: node + linkType: hard + +"lodash.isnumber@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isnumber@npm:3.0.3" + checksum: 913784275b565346255e6ae6a6e30b760a0da70abc29f3e1f409081585875105138cda4a429ff02577e1bc0a7ae2a90e0a3079a37f3a04c3d6c5aaa532f4cab2 + languageName: node + linkType: hard + +"lodash.isplainobject@npm:^4.0.6": + version: 4.0.6 + resolution: "lodash.isplainobject@npm:4.0.6" + checksum: 29c6351f281e0d9a1d58f1a4c8f4400924b4c79f18dfc4613624d7d54784df07efaff97c1ff2659f3e085ecf4fff493300adc4837553104cef2634110b0d5337 + languageName: node + linkType: hard + +"lodash.isstring@npm:^4.0.1": + version: 4.0.1 + resolution: "lodash.isstring@npm:4.0.1" + checksum: eaac87ae9636848af08021083d796e2eea3d02e80082ab8a9955309569cb3a463ce97fd281d7dc119e402b2e7d8c54a23914b15d2fc7fff56461511dc8937ba0 + languageName: node + linkType: hard + "lodash.map@npm:^4.4.0, lodash.map@npm:^4.6.0": version: 4.6.0 resolution: "lodash.map@npm:4.6.0" @@ -13153,6 +13601,13 @@ __metadata: languageName: node linkType: hard +"lodash.once@npm:^4.0.0": + version: 4.1.1 + resolution: "lodash.once@npm:4.1.1" + checksum: d768fa9f9b4e1dc6453be99b753906f58990e0c45e7b2ca5a3b40a33111e5d17f6edf2f768786e2716af90a8e78f8f91431ab8435f761fef00f9b0c256f6d245 + languageName: node + linkType: hard + "lodash.pick@npm:^4.2.1": version: 4.4.0 resolution: "lodash.pick@npm:4.4.0" @@ -13318,6 +13773,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^10.0.0": + version: 10.2.0 + resolution: "lru-cache@npm:10.2.0" + checksum: eee7ddda4a7475deac51ac81d7dd78709095c6fa46e8350dc2d22462559a1faa3b81ed931d5464b13d48cbd7e08b46100b6f768c76833912bc444b99c37e25db + languageName: node + linkType: hard + "lru-cache@npm:^4.0.0": version: 4.1.5 resolution: "lru-cache@npm:4.1.5" @@ -15189,6 +15651,24 @@ __metadata: languageName: node linkType: hard +"octokit@npm:^3.2.0": + version: 3.2.0 + resolution: "octokit@npm:3.2.0" + dependencies: + "@octokit/app": ^14.0.2 + "@octokit/core": ^5.0.0 + "@octokit/oauth-app": ^6.0.0 + "@octokit/plugin-paginate-graphql": ^4.0.0 + "@octokit/plugin-paginate-rest": ^9.0.0 + "@octokit/plugin-rest-endpoint-methods": ^10.0.0 + "@octokit/plugin-retry": ^6.0.0 + "@octokit/plugin-throttling": ^8.0.0 + "@octokit/request-error": ^5.0.0 + "@octokit/types": ^12.0.0 + checksum: 3ec8efe02144aa6210a5c846947245cbe58d31144fa8e6a1726782001d77fcd044b7e1498a56c5e47aba44c1f5726a37c261f474cb46b816579549f7a09db1f6 + languageName: node + linkType: hard + "omggif@npm:^1.0.10, omggif@npm:^1.0.9": version: 1.0.10 resolution: "omggif@npm:1.0.10" @@ -20182,6 +20662,23 @@ __metadata: languageName: node linkType: hard +"universal-github-app-jwt@npm:^1.1.2": + version: 1.1.2 + resolution: "universal-github-app-jwt@npm:1.1.2" + dependencies: + "@types/jsonwebtoken": ^9.0.0 + jsonwebtoken: ^9.0.2 + checksum: 1bc069c57d319607d4b52143ba89de18cdff2b6afb63107e6972dff9574c7fc453f1a6bb1714817c72898a55c37fa38783be965ebd1c61de661231ca061440d1 + languageName: node + linkType: hard + +"universal-user-agent@npm:^6.0.0": + version: 6.0.1 + resolution: "universal-user-agent@npm:6.0.1" + checksum: fdc8e1ae48a05decfc7ded09b62071f571c7fe0bd793d700704c80cea316101d4eac15cc27ed2bb64f4ce166d2684777c3198b9ab16034f547abea0d3aa1c93c + languageName: node + linkType: hard + "universalify@npm:^0.2.0": version: 0.2.0 resolution: "universalify@npm:0.2.0"