diff --git a/.github/workflows/test.yml b/.gitea/workflows/test.yml similarity index 59% rename from .github/workflows/test.yml rename to .gitea/workflows/test.yml index ac35f12..e6d22c3 100644 --- a/.github/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -8,12 +8,18 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Test Download Hugo + - name: Test Download Hugo (Regex) + uses: ./ + with: + repo-name: 'gohugoio/hugo' + file-name: '~hugo_extended' + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: Test Download Hugo (Default) uses: ./ with: repo-name: 'gohugoio/hugo' - file-name: 'hugo_extended_.*_{{SYSTEM}}-{{ARCH}}\.tar\.gz' - use-regex: 'true' env: GITHUB_TOKEN: ${{ github.token }} diff --git a/README.md b/README.md index e71f4ca..0627a1c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,37 @@ -# Setup GitHub Release Action +# setup-github-release -This GitHub/Gitea Action downloads a release from a specified GitHub repository and adds it to the runners tool cache. +This GitHub/Gitea Action downloads a tool from a GitHub release based on platform-aware selection rules and adds it to the system PATH. ## Usage +### Simple (Automatic Selection) +The action will automatically detect your OS and ARCH and look for a matching archive. ```yaml - uses: ./ with: - repo-name: 'owner/repo' - file-name: 'tool-linux.*' - use-regex: 'true' + repo-name: 'gohugoio/hugo' ``` + +### Regex Search +You can use a regex pattern (prefixed with `~`) to narrow down the asset. +```yaml +- uses: ./ + with: + repo-name: 'gohugoio/hugo' + file-name: '~hugo_extended' +``` + +### Custom File Type +```yaml +- uses: ./ + with: + repo-name: 'some/repo' + file-type: 'package' # Matches .deb, .rpm, .pkg +``` + +## Inputs + +- `repo-name` (required): GitHub repository in `owner/repo` format. +- `file-name` (optional): Literal name or regex pattern (if starts with `~`) to match the asset. +- `file-type` (optional, default: `archive`): Predefined keywords `archive`, `package`, or a custom regex extension pattern. +- `token` (optional): GitHub token for authentication. diff --git a/action.yml b/action.yml index 2c0035f..b754457 100644 --- a/action.yml +++ b/action.yml @@ -1,17 +1,17 @@ name: 'setup-github-release' description: 'Install a tool from a GitHub release and add it to the PATH' -author: 'GitHub Copilot' +author: 'Slawomir Koszewski with GitHub Copilot assistance' inputs: repo-name: description: 'The GitHub repository name (e.g., owner/repo)' required: true file-name: - description: 'The name of the asset to download (or a regex pattern if use-regex is true)' + description: 'The name or regex pattern (prefixed with ~) of the asset file to download.' required: false - use-regex: - description: 'Whether to treat file-name as a regular expression' + file-type: + description: 'The type of the file to be downloaded (archive, package, or custom regex).' required: false - default: 'false' + default: 'archive' token: description: 'The GitHub token to use for authentication' required: false diff --git a/src/index.ts b/src/index.ts index d8ba6ca..39080a7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,26 +7,35 @@ async function run() { try { const repoName = core.getInput('repo-name', { required: true }); let fileName = core.getInput('file-name'); - const useRegex = core.getInput('use-regex') === 'true'; + const fileType = core.getInput('file-type') || 'archive'; const token = core.getInput('token') || process.env.GITHUB_TOKEN; - if (!fileName) { - throw new Error('file-name must be provided (either as a string or regex pattern)'); - } - - // Platform detection + // Detect system and architecture const platform = os.platform(); // 'linux', 'darwin', 'win32' - let arch = os.arch(); // 'x64', 'arm64' + const arch = os.arch(); // 'x64', 'arm64' - // Normalize OS/Arch names for common release patterns - const osMap: Record = { 'win32': 'windows', 'darwin': 'darwin' }; - const archMap: Record = { 'x64': 'amd64' }; + const systemPatterns: Record = { + linux: 'linux', + darwin: '(darwin|macos|mac)', + win32: '(windows|win)' + }; - const currentOS = osMap[platform] || platform; - const currentArch = archMap[arch] || arch; + const archPatterns: Record = { + x64: '(x86_64|x64|amd64)', + arm64: '(aarch64|arm64)' + }; - // Replace placeholders in fileName - fileName = fileName.replace(/{{SYSTEM}}/g, currentOS).replace(/{{ARCH}}/g, currentArch); + const systemPattern = systemPatterns[platform] || platform; + const archPattern = archPatterns[arch] || arch; + + let extPattern: string; + if (fileType === 'archive') { + extPattern = '\\.(zip|tar\\.gz|tar|tgz|7z)'; + } else if (fileType === 'package') { + extPattern = '\\.(deb|rpm|pkg)'; + } else { + extPattern = fileType; + } const url = `https://api.github.com/repos/${repoName}/releases/latest`; const headers: Record = { @@ -45,15 +54,46 @@ async function run() { const data: any = await response.json(); let asset; - if (useRegex) { - const regex = new RegExp(fileName); - asset = data.assets.find((a: any) => regex.test(a.name)); + + if (!fileName) { + // Default matching rule + const pattern = `${systemPattern}[_-]${archPattern}.*${extPattern}$`; + const regex = new RegExp(pattern, 'i'); + core.info(`No file-name provided. Using default pattern: ${pattern}`); + const matchingAssets = data.assets.filter((a: any) => regex.test(a.name)); + if (matchingAssets.length > 1) { + throw new Error(`Multiple assets matched the default criteria: ${matchingAssets.map((a: any) => a.name).join(', ')}`); + } + asset = matchingAssets[0]; + } else if (fileName.startsWith('~')) { + // Regex matching rule + let pattern = fileName.substring(1); + const hasSystem = pattern.includes('{{SYSTEM}}'); + const hasArch = pattern.includes('{{ARCH}}'); + const hasEnd = pattern.endsWith('$'); + + if (!hasSystem && !hasArch && !hasEnd) { + pattern += `.*${systemPattern}[_-]${archPattern}.*${extPattern}$`; + } else if (hasSystem && hasArch && !hasEnd) { + pattern += `.*${extPattern}$`; + } + + pattern = pattern.replace(/{{SYSTEM}}/g, systemPattern).replace(/{{ARCH}}/g, archPattern); + const regex = new RegExp(pattern, 'i'); + core.info(`Using regex pattern: ${pattern}`); + const matchingAssets = data.assets.filter((a: any) => regex.test(a.name)); + if (matchingAssets.length > 1) { + throw new Error(`Multiple assets matched the criteria: ${matchingAssets.map((a: any) => a.name).join(', ')}`); + } + asset = matchingAssets[0]; } else { + // Literal matching rule + core.info(`Using literal match for: ${fileName}`); asset = data.assets.find((a: any) => a.name === fileName); } if (!asset) { - throw new Error(`Asset matching "${fileName}" not found in release ${data.tag_name}`); + throw new Error(`No asset found matching the criteria in release ${data.tag_name}`); } const downloadUrl = asset.browser_download_url;