diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59b4fc9..7750ce5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,15 @@ concurrency: jobs: build: - name: Build - runs-on: ubuntu-latest + strategy: + matrix: + include: + - { name: Windows, os: windows-latest } + - { name: Ubuntu, os: ubuntu-latest } + - { name: MacOS, os: macos-latest } + + name: Build (${{ matrix.name }}) + runs-on: ${{ matrix.os }} steps: - name: Clone repository uses: actions/checkout@v4 @@ -33,7 +40,13 @@ jobs: - name: Tests run: npm test + - name: Setup Ninja + if: runner.os == 'Windows' + uses: seanmiddleditch/gha-setup-ninja@v5 + - name: Example + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npm run example publish-npm: diff --git a/lib/extension.js b/lib/extension.js index fe7fb5e..deb0917 100644 --- a/lib/extension.js +++ b/lib/extension.js @@ -263,6 +263,85 @@ class CppReferenceExtension { } } + /** + * Finds the most appropriate download URL for MrDocs binaries + * from the list of releases. + * + * Each object in the `releasesInfo` array represents a release. + * Each release object contains a `tag_name` string and an `assets` array. + * + * Each asset in the `assets` array has an `updated_at` string and + * a `browser_download_url` string. + * The `updated_at` string is in the format "2024-01-01T00:00:00Z". + * + * This function sorts the releases in `releasesInfo` by the date + * of their latest asset, with the latest release first. + * + * Then it iterates over the releases and finds a download URL + * that's appropriate for the current platform. This iteration + * also goes from the latest asset to the oldest asset in the release. + * + * The first asset we find that matches the platform is the one for which + * we return the URL of the asset and the tag name of the release. + * + * We can identify if the asset is appropriate for the platform by checking + * if the asset URL ends with the appropriate suffix for the platform: + * + * - win64.7z + * - Linux.tar.gz + * - Darwin.tar.gz + * + * @param {Array} releasesInfo - The list of releases. + * @return {{downloadUrl, downloadTag}} + */ + static findDownloadUrl(releasesInfo) { + // Sort the releases by the date of their latest asset + releasesInfo.sort((a, b) => { + const latestAssetA = a.assets.reduce((latest, asset) => { + return new Date(asset.updated_at) > new Date(latest.updated_at) ? asset : latest; + }, a.assets[0]); + + const latestAssetB = b.assets.reduce((latest, asset) => { + return new Date(asset.updated_at) > new Date(latest.updated_at) ? asset : latest; + }, b.assets[0]); + + return new Date(latestAssetB.updated_at) - new Date(latestAssetA.updated_at); + }); + + // Determine the appropriate suffix for the current platform + let platformSuffix; + switch (process.platform) { + case 'win32': + platformSuffix = 'win64.7z'; + break; + case 'linux': + platformSuffix = 'Linux.tar.gz'; + break; + case 'darwin': + platformSuffix = 'Darwin.tar.gz'; + break; + default: + throw new Error(`Unsupported platform: ${process.platform}. Supported platforms are win32, linux, and darwin.`); + } + + // Iterate over the releases + for (const release of releasesInfo) { + // Sort assets descending by updated_at + release.assets.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)); + // Iterate over the assets of the release + for (const asset of release.assets) { + // Check if the asset URL ends with the appropriate suffix for the platform + if (asset.browser_download_url.endsWith(platformSuffix)) { + // Return the URL of the asset and the tag name of the release + return {downloadUrl: asset.browser_download_url, downloadTag: release.tag_name}; + } + } + } + + // If no appropriate asset is found, return an empty object + return {downloadUrl: null, downloadTag: null} + } + /** * Sets up MrDocs for the playbook. * @@ -305,31 +384,16 @@ class CppReferenceExtension { const releasesResponse = await axios.get('https://api.github.com/repos/cppalliance/mrdocs/releases', {headers: apiHeaders}) const releasesInfo = releasesResponse.data this.logger.debug(`Found ${releasesInfo.length} MrDocs releases`) - let downloadUrl = undefined - let downloadRelease = undefined - for (const latestRelease of releasesInfo) { - this.logger.debug(`Latest release: ${latestRelease['tag_name']}`) - const latestAssets = latestRelease['assets'].map(asset => asset['browser_download_url']) - this.logger.debug(`Latest assets: ${latestAssets.join(', ')}`) - const releaseFileSuffix = process.platform === "win32" ? 'win64.7z' : 'Linux.tar.gz' - downloadUrl = latestAssets.find(asset => asset.endsWith(releaseFileSuffix)) - downloadRelease = latestRelease - if (downloadUrl) { - break - } - this.logger.warn(`Could not find MrDocs binaries in ${latestRelease['tag_name']} release for ${process.platform}`) - } + const { downloadUrl, downloadTag } = CppReferenceExtension.findDownloadUrl(releasesInfo) if (!downloadUrl) { this.logger.error(`Could not find MrDocs binaries for ${process.platform}`) process.exit(1) } const mrdocsDownloadDir = path.join(mrDocsTreeDir, process.platform) - const releaseTagname = downloadRelease['tag_name'] - const versionSubdir = releaseTagname.endsWith('-release') ? releaseTagname.slice(0, -8) : downloadRelease['tag_name'] + const versionSubdir = downloadTag.endsWith('-release') ? downloadTag.slice(0, -8) : downloadTag const mrdocsExtractDir = path.join(mrdocsDownloadDir, versionSubdir) const platformExtension = process.platform === 'win32' ? '.exe' : '' const mrdocsExecPath = path.join(mrdocsExtractDir, 'bin', 'mrdocs') + platformExtension - if (await CppReferenceExtension.fileExists(mrdocsExecPath)) { this.logger.debug(`MrDocs already exists at ${mrdocsExtractDir}`) } else {