Compare commits
44
Commits
v1.2.2
...
bc6d44f0d9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc6d44f0d9 | ||
|
|
9c3cb2115c | ||
|
|
25adff380c | ||
|
|
43c917e526 | ||
|
|
1f11818ecb | ||
|
|
976c225074 | ||
|
|
034aa631da | ||
|
|
57004edb17 | ||
|
|
9acad60d71 | ||
|
|
610706a64d | ||
|
|
cbd576b4de | ||
|
|
9138f03d35 | ||
|
|
e8a9b0906d | ||
|
|
544be86f08 | ||
|
|
97d2597552 | ||
|
|
4c45d89c83 | ||
|
|
d70ee1e8b8 | ||
|
|
bffd6ee278 | ||
|
|
0643b8ca01 | ||
|
|
26ef54930a | ||
|
|
e7e7ee53f6 | ||
|
|
d2f1cb9c1a | ||
|
|
1a01c8fa5f | ||
|
|
cbfadf3488 | ||
|
|
01d844f6d9 | ||
|
|
e50ae03336 | ||
|
|
1896949b27 | ||
|
|
d9d7f67247 | ||
|
|
0132ee6665 | ||
|
|
89ea0cecb0 | ||
|
|
3a75adedc6 | ||
|
|
dfa641afd4 | ||
|
|
483a1c5f13 | ||
|
|
90033e0b9e | ||
|
|
f8a559538e | ||
|
|
09f36edc01 | ||
|
|
55d6019d0f | ||
|
|
11a26bd176 | ||
|
|
32a4011b54 | ||
|
|
2bb60fc0ed | ||
|
|
fc727877e6 | ||
|
|
120b16b56e | ||
|
|
4ee62009bb | ||
|
|
b20a066030 |
@@ -16,26 +16,37 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Check Authentication
|
||||||
|
uses: ./check-token
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
- name: Go ACME Setup
|
- name: Go ACME Setup
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
repository: 'go-acme/lego'
|
repository: 'go-acme/lego'
|
||||||
|
file-type: 'archive'
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
update-cache: 'always'
|
||||||
|
|
||||||
- name: Setup Hugo
|
- name: Setup Hugo
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
repository: 'gohugoio/hugo'
|
repository: 'gohugoio/hugo'
|
||||||
file-name: '~hugo_extended_[^a-z]'
|
file-name: '~hugo_extended_[^a-z].*{{SYSTEM}}-{{ARCH}}'
|
||||||
|
file-type: 'archive'
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
update-cache: 'always'
|
||||||
|
|
||||||
- name: Setup RClone
|
- name: Setup RClone
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
repository: 'rclone/rclone'
|
repository: 'rclone/rclone'
|
||||||
|
file-type: 'archive'
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
update-cache: 'always'
|
||||||
|
|
||||||
- name: Verify Installation
|
- name: Verify Installation
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
name: Test Action
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/test.yml'
|
||||||
|
- 'action.yml'
|
||||||
|
- 'src/**'
|
||||||
|
- 'dist/**'
|
||||||
|
- 'package.json'
|
||||||
|
- 'package-lock.json'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Check Authentication
|
||||||
|
uses: ./check-token
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
|
- name: Go ACME Setup
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
repository: 'go-acme/lego'
|
||||||
|
file-type: 'archive'
|
||||||
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
update-cache: 'always'
|
||||||
|
|
||||||
|
- name: Setup Hugo
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
repository: 'gohugoio/hugo'
|
||||||
|
file-name: '~hugo_extended_[^a-z].*{{SYSTEM}}-{{ARCH}}'
|
||||||
|
file-type: 'archive'
|
||||||
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
update-cache: 'always'
|
||||||
|
|
||||||
|
- name: Setup RClone
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
repository: 'rclone/rclone'
|
||||||
|
file-type: 'archive'
|
||||||
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
update-cache: 'always'
|
||||||
|
|
||||||
|
- name: Verify Installation
|
||||||
|
run: |
|
||||||
|
echo "Verifying installed tools..."
|
||||||
|
printf "\nGo ACME Lego:\n"
|
||||||
|
lego -v
|
||||||
|
printf "\nHugo:\n"
|
||||||
|
hugo version
|
||||||
|
printf "\nRClone:\n"
|
||||||
|
rclone version
|
||||||
@@ -4,3 +4,6 @@ lib/
|
|||||||
*.map
|
*.map
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.github/*.md
|
.github/*.md
|
||||||
|
dist/cli.js
|
||||||
|
__pycache__
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Slawomir Koszewski
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Setup GitHub Release Action
|
# Setup GitHub Release Action
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
This project implements a GitHub Action (`setup-github-release`) and a CLI tool (`install-github-release`) that downloads a release asset from a specified GitHub repository, extracts it, searches for a binary within the extracted files, and prepares the runtime environment.
|
This project implements a GitHub Action (`setup-github-release`) and a CLI tool (`install-github-release`) that downloads a release asset from a specified GitHub repository, extracts it, searches for a binary within the extracted files, and prepares the runtime environment.
|
||||||
|
|
||||||
## Installation / Setup
|
## Installation / Setup
|
||||||
@@ -10,7 +12,7 @@ Add the action to your workflow. Authenticate with `github.token` (default) or a
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Install Tool
|
- name: Install Tool
|
||||||
uses: koszewscy/setup-github-release@v1
|
uses: skoszewski/setup-github-release@v1
|
||||||
with:
|
with:
|
||||||
repository: 'owner/repo'
|
repository: 'owner/repo'
|
||||||
```
|
```
|
||||||
@@ -24,7 +26,7 @@ Install the CLI tool on any destination system with Node.js 24 or newer.
|
|||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/koszewscy/setup-github-release
|
git clone https://github.com/skoszewski/setup-github-release
|
||||||
cd setup-github-release
|
cd setup-github-release
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -41,7 +43,11 @@ npm run build
|
|||||||
npm install -g .
|
npm install -g .
|
||||||
```
|
```
|
||||||
|
|
||||||
After installation, the tool will be available as `install-github-release`:
|
After installation, the tool will be available as `install-github-release`. By default, it installs binaries to:
|
||||||
|
|
||||||
|
- Linux/macOS (root): `/usr/local/bin`
|
||||||
|
- Linux/macOS (user): `~/bin` (if exists) or `/usr/local/bin`
|
||||||
|
- Windows: `%LOCALAPPDATA%\bin`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
install-github-release rclone/rclone
|
install-github-release rclone/rclone
|
||||||
@@ -61,8 +67,8 @@ install-github-release rclone/rclone
|
|||||||
The action will automatically detect your OS (Linux, Windows, macOS) and architecture (x64, ARM64) and look for a matching archive. It will search for a binary named after the repository.
|
The action will automatically detect your OS (Linux, Windows, macOS) and architecture (x64, ARM64) and look for a matching archive. It will search for a binary named after the repository.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Install Hugo
|
- name: Install LEGO
|
||||||
uses: koszewscy/setup-github-release@v1
|
uses: skoszewski/setup-github-release@v1
|
||||||
with:
|
with:
|
||||||
repository: 'go-acme/lego'
|
repository: 'go-acme/lego'
|
||||||
```
|
```
|
||||||
@@ -75,7 +81,7 @@ For projects with multiple binary versions, you can use a regex pattern (prefixe
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Install Extended Hugo
|
- name: Install Extended Hugo
|
||||||
uses: koszewscy/setup-github-release@v1
|
uses: skoszewski/setup-github-release@v1
|
||||||
with:
|
with:
|
||||||
repository: 'gohugoio/hugo'
|
repository: 'gohugoio/hugo'
|
||||||
file-name: '~hugo_extended_[^a-z]' # Regex to match extended version
|
file-name: '~hugo_extended_[^a-z]' # Regex to match extended version
|
||||||
@@ -87,7 +93,7 @@ If the binary name is different from the repository name, like in the example of
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Install GitHub CLI
|
- name: Install GitHub CLI
|
||||||
uses: koszewscy/setup-github-release@v1
|
uses: skoszewski/setup-github-release@v1
|
||||||
with:
|
with:
|
||||||
repository: 'cli/cli'
|
repository: 'cli/cli'
|
||||||
binary-name: 'gh' # Searches for 'gh' (or 'gh.exe') inside the extracted release
|
binary-name: 'gh' # Searches for 'gh' (or 'gh.exe') inside the extracted release
|
||||||
@@ -98,7 +104,7 @@ If the binary name is different from the repository name, like in the example of
|
|||||||
If you are unsure how the binary is named, use the `debug` flag to list all files in the unpacked asset, or download the asset manually to inspect its structure.
|
If you are unsure how the binary is named, use the `debug` flag to list all files in the unpacked asset, or download the asset manually to inspect its structure.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: koszewscy/setup-github-release@v1
|
- uses: skoszewski/setup-github-release@v1
|
||||||
with:
|
with:
|
||||||
repository: 'owner/repo'
|
repository: 'owner/repo'
|
||||||
debug: true
|
debug: true
|
||||||
@@ -111,10 +117,13 @@ The following inputs are available for the GitHub Action, and as options for the
|
|||||||
- `repository` (required): The GitHub repository in the format `owner/repo` from which to download the release.
|
- `repository` (required): The GitHub repository in the format `owner/repo` from which to download the release.
|
||||||
- `file-name` (optional): The name or the regex pattern (prefixed with `~`) of the asset file to download from the release.
|
- `file-name` (optional): The name or the regex pattern (prefixed with `~`) of the asset file to download from the release.
|
||||||
- `binary-name` (optional): The name or regex pattern (prefixed with `~`) of the binary to search for within the downloaded asset. Defaults to the repository name.
|
- `binary-name` (optional): The name or regex pattern (prefixed with `~`) of the binary to search for within the downloaded asset. Defaults to the repository name.
|
||||||
- `file-type` (optional, default: 'archive'): The regex pattern to identify the type of the file to be downloaded. There are two predefined keywords:
|
- `file-type` (optional): Asset type selector.
|
||||||
- 'archive': matches common archive file extensions like .zip, .tar.gz, .tar, .tgz, .7z.
|
|
||||||
- 'package': matches common package file extensions like .deb, .rpm, .pkg.
|
- known values: `archive`, `package`, `linux`, `macos`, `targz`.
|
||||||
- or a custom regex pattern can be provided to match specific file types.
|
- custom values:
|
||||||
|
`~<regex>` for regular expression matching (anchored to the end of file name), or
|
||||||
|
extension form such as `zip` or `.tar.gz`.
|
||||||
|
|
||||||
- `install-path` (optional, CLI only): Custom installation directory for the CLI tool.
|
- `install-path` (optional, CLI only): Custom installation directory for the CLI tool.
|
||||||
- `update-cache` (optional, default: 'false', Action only): When set to 'false', the action will use the cached version of the tool if it is already available. If set to 'true', the action will check the latest release and update the cache if a newer version is found. If set to 'always', it will always download and install, updating the cache regardless.
|
- `update-cache` (optional, default: 'false', Action only): When set to 'false', the action will use the cached version of the tool if it is already available. If set to 'true', the action will check the latest release and update the cache if a newer version is found. If set to 'always', it will always download and install, updating the cache regardless.
|
||||||
- `debug` (optional, default: 'false'): When set to `true`, the action will log the contents of the unpacked directory to the console.
|
- `debug` (optional, default: 'false'): When set to `true`, the action will log the contents of the unpacked directory to the console.
|
||||||
@@ -133,32 +142,77 @@ Arguments:
|
|||||||
repository The GitHub repository (owner/repo)
|
repository The GitHub repository (owner/repo)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
--dry-run [level] Run in test mode (default level: 1)
|
||||||
|
Or set TEST_MODE environment variable to a value > 0
|
||||||
|
-l, --list [repository] List available assets from latest release and exit
|
||||||
|
-a, --app-name <name> Application name (optional, for output messages)
|
||||||
-f, --file-name <name> Asset file name or regex pattern (prefixed with ~)
|
-f, --file-name <name> Asset file name or regex pattern (prefixed with ~)
|
||||||
-b, --binary-name <name> Binary to search for (prefixed with ~ for regex)
|
-b, --binary-name <name> Binary name (supports source:destination form)
|
||||||
-t, --file-type <type> 'archive', 'package', or custom regex (default: archive)
|
-t, --file-type <type> Known: archive|package|linux|macos|targz
|
||||||
|
Or custom: ~<regex> (end-of-string match) or extension (e.g. zip, .tar.gz)
|
||||||
-p, --install-path <path> Custom installation directory
|
-p, --install-path <path> Custom installation directory
|
||||||
|
-o, --output-directory <path>
|
||||||
|
Only download selected asset to the specified directory
|
||||||
|
-j, --releases-json Download latest release JSON only
|
||||||
|
--system <name> Override detected system for asset matching
|
||||||
|
--arch <name> Override detected architecture for asset matching
|
||||||
-k, --token <token> GitHub token
|
-k, --token <token> GitHub token
|
||||||
-d, --debug Enable debug logging
|
-d, --debug Enable debug logging
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## GitHub Token Verification
|
||||||
|
|
||||||
|
The project includes a utility to verify the validity of your GitHub token.
|
||||||
|
|
||||||
|
### CLI Utility
|
||||||
|
|
||||||
|
```bash
|
||||||
|
check-github-token <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
If no token is provided as an argument, it will attempt to read from the `GITHUB_TOKEN` environment variable.
|
||||||
|
|
||||||
|
### GitHub Action
|
||||||
|
|
||||||
|
You can also use the `check-token` subaction in your workflows:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Verify Token
|
||||||
|
uses: skoszewski/setup-github-release/check-token@v1
|
||||||
|
with:
|
||||||
|
repository: 'actions/checkout' # Optional, defaults to actions/checkout
|
||||||
|
token: ${{ secrets.MY_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the `token` input is not provided, it will read from the `GITHUB_TOKEN` environment variable.
|
||||||
|
|
||||||
## Asset Selection Procedure
|
## Asset Selection Procedure
|
||||||
|
|
||||||
The list of assets from the latest release is filtered based on the following rules:
|
The list of assets from the latest release is filtered based on the following rules:
|
||||||
|
|
||||||
1. If neither `file-name` nor `file-type` is provided, the tool defaults to selecting assets that match the following regular expression: `{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$`, where:
|
1. If neither `file-name` nor `file-type` is provided, the tool defaults to selecting assets with an OS-aware extension pattern and this regular expression shape: `{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}`, where:
|
||||||
- `{{SYSTEM}}` is replaced with the detected operating system regex.
|
|
||||||
- `{{ARCH}}` is replaced with the detected architecture regex.
|
- `{{SYSTEM}}` is replaced with the detected operating system regex.
|
||||||
- `{{EXT_PATTERN}}` is a regex pattern defined by the `file-type` input (defaulting to 'archive' if not specified).
|
- `{{ARCH}}` is replaced with the detected architecture regex.
|
||||||
|
- `{{EXT_PATTERN}}` is selected by OS:
|
||||||
|
Linux: `\.(deb|rpm|zip|tar\.gz|tgz)$`
|
||||||
|
macOS: `\.(pkg|zip|tar\.gz|tgz)$`
|
||||||
|
other: `\.(zip|tar\.gz|tgz)$`
|
||||||
|
|
||||||
2. If `file-name` is provided literally, the tool uses it directly to match the asset name by using exact string comparison.
|
2. If `file-name` is provided literally, the tool uses it directly to match the asset name by using exact string comparison.
|
||||||
|
|
||||||
3. If `file-name` is provided as a regex pattern (prefixed with `~`), then:
|
3. If `file-name` is provided as a regex pattern (prefixed with `~`), then:
|
||||||
|
|
||||||
- If the pattern does not end with `$` and does not include any placeholders, the tool appends `.*{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$` to the provided pattern.
|
- If the pattern does not end with `$` and does not include any placeholders, the tool appends `.*{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$` to the provided pattern.
|
||||||
- If it already ends with `$` or includes all three placeholders, the tool uses it as-is to match the asset name using regex.
|
- If it already ends with `$` or includes all three placeholders, the tool uses it as-is to match the asset name using regex.
|
||||||
- If only `{{SYSTEM}}` and `{{ARCH}}` placeholders are included, the tool appends `.*{{EXT_PATTERN}}$`.
|
- If only `{{SYSTEM}}` and `{{ARCH}}` placeholders are included, the tool appends `.*{{EXT_PATTERN}}$`.
|
||||||
|
|
||||||
4. If `file-type` is not equal to 'archive' or 'package', it is treated as a custom regex pattern to match the file extension.
|
4. If `file-type` is provided:
|
||||||
|
|
||||||
|
- known values are: `archive`, `package`, `linux`, `macos`, `targz`.
|
||||||
|
- `~<regex>` is treated as a regex and matched at the end of the file name.
|
||||||
|
- any other value is treated as an extension selector (for example `zip` or `.tar.gz`).
|
||||||
|
|
||||||
5. The tool applies the constructed regex pattern to filter the assets from the latest release.
|
5. The tool applies the constructed regex pattern to filter the assets from the latest release.
|
||||||
|
|
||||||
@@ -167,11 +221,13 @@ The list of assets from the latest release is filtered based on the following ru
|
|||||||
7. After download and extraction, the tool recursively searches for the binary specified by `binary-name` (or the repository name). If found, the directory containing the binary is used as the tool directory and added to the PATH (or used for installation). If the binary is not found, the tool fails.
|
7. After download and extraction, the tool recursively searches for the binary specified by `binary-name` (or the repository name). If found, the directory containing the binary is used as the tool directory and added to the PATH (or used for installation). If the binary is not found, the tool fails.
|
||||||
|
|
||||||
8. `{{SYSTEM}}` is replaced with the detected operating system regex:
|
8. `{{SYSTEM}}` is replaced with the detected operating system regex:
|
||||||
|
|
||||||
- For Linux: `linux`.
|
- For Linux: `linux`.
|
||||||
- For MacOS: `(darwin|macos|mac|osx)`.
|
- For MacOS: `(darwin|macos|mac)`.
|
||||||
- For Windows: `(windows|win)`.
|
- For Windows: `(windows|win)`.
|
||||||
|
|
||||||
9. `{{ARCH}}` is replaced with the detected architecture regex:
|
9. `{{ARCH}}` is replaced with the detected architecture regex:
|
||||||
|
|
||||||
- For x64: `(x86_64|x64|amd64)`.
|
- For x64: `(x86_64|x64|amd64)`.
|
||||||
- For arm64: `(aarch64|arm64)`.
|
- For arm64: `(aarch64|arm64)`.
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -12,9 +12,8 @@ inputs:
|
|||||||
description: 'The name or regex pattern (prefixed with ~) of the binary to search for within the asset. Defaults to the repository name.'
|
description: 'The name or regex pattern (prefixed with ~) of the binary to search for within the asset. Defaults to the repository name.'
|
||||||
required: false
|
required: false
|
||||||
file-type:
|
file-type:
|
||||||
description: 'The type of the file to be downloaded (archive, package, or custom regex).'
|
description: 'Asset type selector. Known values: archive, package, linux, macos, targz. Custom values: ~<regex> or extension (e.g. zip, .tar.gz).'
|
||||||
required: false
|
required: false
|
||||||
default: 'archive'
|
|
||||||
update-cache:
|
update-cache:
|
||||||
description: 'How to handle the tool cache (false, true, or always). Defaults to false.'
|
description: 'How to handle the tool cache (false, true, or always). Defaults to false.'
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
name: 'check-token'
|
||||||
|
description: 'Verify the validity of a GitHub token'
|
||||||
|
author: 'Slawomir Koszewski with GitHub Copilot assistance'
|
||||||
|
inputs:
|
||||||
|
repository:
|
||||||
|
description: 'The GitHub repository to check (e.g., owner/repo)'
|
||||||
|
required: false
|
||||||
|
default: 'actions/checkout'
|
||||||
|
token:
|
||||||
|
description: 'The GitHub token to verify'
|
||||||
|
required: false
|
||||||
|
default: ${{ github.token }}
|
||||||
|
runs:
|
||||||
|
using: 'node24'
|
||||||
|
main: '../dist/check-token-action.js'
|
||||||
Vendored
+19250
File diff suppressed because one or more lines are too long
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// src/check-token.ts
|
||||||
|
var import_util = require("util");
|
||||||
|
|
||||||
|
// src/core/downloader.ts
|
||||||
|
function getGithubApiHeaders(token) {
|
||||||
|
const headers = {
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "setup-github-release-action"
|
||||||
|
};
|
||||||
|
if (token) {
|
||||||
|
headers["Authorization"] = `token ${token}`;
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
async function fetchLatestRelease(repository, token) {
|
||||||
|
const url = `https://api.github.com/repos/${repository}/releases/latest`;
|
||||||
|
const headers = getGithubApiHeaders(token);
|
||||||
|
const response = await fetch(url, { headers });
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorBody = await response.text();
|
||||||
|
throw new Error(`Failed to fetch latest release for ${repository}: ${response.statusText}. ${errorBody}`);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/check-token.ts
|
||||||
|
async function run() {
|
||||||
|
const { positionals } = (0, import_util.parseArgs)({
|
||||||
|
allowPositionals: true
|
||||||
|
});
|
||||||
|
const token = positionals[0] || process.env.GITHUB_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
console.error("Error: No GitHub token provided as an argument or found in GITHUB_TOKEN environment variable.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
console.log("Verifying GitHub token...");
|
||||||
|
await fetchLatestRelease("actions/checkout", token);
|
||||||
|
console.log("\x1B[32mSuccess: The provided GitHub token is valid and has sufficient permissions to access public repositories.\x1B[0m");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("\x1B[31mError: GitHub token verification failed.\x1B[0m");
|
||||||
|
console.error(`Reason: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run();
|
||||||
Vendored
-16
@@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
"use strict";var F=Object.create;var N=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var U=Object.getPrototypeOf,X=Object.prototype.hasOwnProperty;var G=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of O(e))!X.call(t,s)&&s!==n&&N(t,s,{get:()=>e[s],enumerable:!(r=H(e,s))||r.enumerable});return t};var f=(t,e,n)=>(n=t!=null?F(U(t)):{},G(e||!t||!t.__esModule?N(n,"default",{value:t,enumerable:!0}):n,t));var B=require("util"),p=f(require("path")),c=f(require("fs")),P=f(require("os"));var S=f(require("os")),Y={linux:"linux",darwin:"(darwin|macos|mac|osx)",win32:"(windows|win)"},q={x64:"(x86_64|x64|amd64)",arm64:"(aarch64|arm64)"};function _(){let t=S.platform(),e=S.arch();return{system:t,arch:e,systemPattern:Y[t]||t,archPattern:q[e]||e}}function C(t,e,n){let{fileName:r,fileType:s="archive"}=n,o;if(s==="archive"?o="\\.(zip|tar\\.gz|tar|tgz|7z)":s==="package"?o="\\.(deb|rpm|pkg)":o=s,r)if(r.startsWith("~")){let i=r.substring(1),l=i.includes("{{SYSTEM}}"),a=i.includes("{{ARCH}}"),h=i.includes("{{EXT_PATTERN}}"),$=i.endsWith("$");!l&&!a&&!h&&!$?i+=".*{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$":l&&a&&!h&&!$&&(i+=".*{{EXT_PATTERN}}$");let m=i.replace(/{{SYSTEM}}/g,e.systemPattern).replace(/{{ARCH}}/g,e.archPattern).replace(/{{EXT_PATTERN}}/g,o),x=new RegExp(m,"i"),g=t.filter(w=>x.test(w.name));if(g.length===0)throw new Error(`No assets matched the regex: ${m}`);if(g.length>1)throw new Error(`Multiple assets matched the criteria: ${g.map(w=>w.name).join(", ")}`);return g[0]}else{let i=t.find(l=>l.name===r);if(!i)throw new Error(`No asset found matching the exact name: ${r}`);return i}else{let i=`${e.systemPattern}[_-]${e.archPattern}.*${o}$`,l=new RegExp(i,"i"),a=t.filter(h=>l.test(h.name));if(a.length===0)throw new Error(`No assets matched the default criteria: ${i}`);if(a.length>1)throw new Error(`Multiple assets matched the default criteria: ${a.map(h=>h.name).join(", ")}`);return a[0]}}var b=f(require("fs")),M=f(require("path"));function k(t,e,n,r){let s=b.readdirSync(t);n&&(r(`Searching for binary in ${t}...`),s.forEach(o=>r(` - ${o}`)));for(let o of s){let i=M.join(t,o);if(b.statSync(i).isDirectory()){let a=k(i,e,n,r);if(a)return a}else{let a=!1;if(e instanceof RegExp?a=e.test(o):(a=o===e,!a&&process.platform==="win32"&&!e.toLowerCase().endsWith(".exe")&&(a=o.toLowerCase()===`${e.toLowerCase()}.exe`)),a)return i}}}async function j(t,e){let n=`https://api.github.com/repos/${t}/releases/latest`,r={Accept:"application/vnd.github.v3+json","User-Agent":"setup-github-release-action"};e&&(r.Authorization=`token ${e}`);let s=await fetch(n,{headers:r});if(!s.ok){let o=await s.text();throw new Error(`Failed to fetch latest release for ${t}: ${s.statusText}. ${o}`)}return await s.json()}async function W(t,e,n){let r={"User-Agent":"setup-github-release-action"};n&&(r.Authorization=`token ${n}`);let s=await fetch(t,{headers:r});if(!s.ok)throw new Error(`Failed to download asset: ${s.statusText}`);let o=await import("fs"),{Readable:i}=await import("stream"),{finished:l}=await import("stream/promises"),a=o.createWriteStream(e);await l(i.fromWeb(s.body).pipe(a))}var E=require("child_process"),d=f(require("path")),y=f(require("fs"));async function L(t,e){let n=d.extname(t).toLowerCase(),r=d.basename(t).toLowerCase();if(y.existsSync(e)||y.mkdirSync(e,{recursive:!0}),r.endsWith(".tar.gz")||r.endsWith(".tgz")||r.endsWith(".tar")){let o=(0,E.spawnSync)("tar",["-xf",t,"-C",e]);if(o.status!==0)throw new Error(`tar failed with status ${o.status}: ${o.stderr.toString()}`)}else if(r.endsWith(".zip"))if(process.platform==="win32"){let s=`Expand-Archive -Path "${t}" -DestinationPath "${e}" -Force`,o=(0,E.spawnSync)("powershell",["-Command",s]);if(o.status!==0)throw new Error(`powershell Expand-Archive failed with status ${o.status}: ${o.stderr.toString()}`)}else{let s=(0,E.spawnSync)("unzip",["-q",t,"-d",e]);if(s.status!==0)throw new Error(`unzip failed with status ${s.status}: ${s.stderr.toString()}`)}else if(r.endsWith(".7z")){let s=(0,E.spawnSync)("7z",["x",t,`-o${e}`,"-y"]);if(s.status!==0)throw new Error(`7z failed with status ${s.status}. Make sure 7z is installed.`)}else{let s=d.join(e,d.basename(t));y.copyFileSync(t,s)}}async function K(){let{values:t,positionals:e}=(0,B.parseArgs)({options:{"file-name":{type:"string",short:"f"},"binary-name":{type:"string",short:"b"},"file-type":{type:"string",short:"t",default:"archive"},"install-path":{type:"string",short:"p"},token:{type:"string",short:"k"},debug:{type:"boolean",short:"d",default:!1},help:{type:"boolean",short:"h"}},allowPositionals:!0});(t.help||e.length===0)&&(console.log(`
|
|
||||||
Usage: install-github-release [options] <repository>
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
repository The GitHub repository (owner/repo)
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-f, --file-name <name> Asset file name or regex pattern (prefixed with ~)
|
|
||||||
-b, --binary-name <name> Binary to search for (prefixed with ~ for regex)
|
|
||||||
-t, --file-type <type> 'archive', 'package', or custom regex (default: archive)
|
|
||||||
-p, --install-path <path> Custom installation directory
|
|
||||||
-k, --token <token> GitHub token
|
|
||||||
-d, --debug Enable debug logging
|
|
||||||
-h, --help Show this help message
|
|
||||||
`),process.exit(0));let n=e[0];n||(console.error("Error: Repository is required."),process.exit(1));let r=t["file-name"],s=t["binary-name"],o=t["file-type"],i=!!t.debug,l=t.token||process.env.GITHUB_TOKEN;try{let a=_(),h=n.split("/").pop()||n;console.log(`Fetching latest release for ${n}...`);let $=await j(n,l),m=C($.assets,a,{fileName:r,fileType:o});console.log(`Selected asset: ${m.name}`);let x=c.mkdtempSync(p.join(P.tmpdir(),"setup-gh-release-")),g=p.join(x,m.name);console.log(`Downloading ${m.name}...`),await W(m.browser_download_url,g,l);let w=p.join(x,"extract");console.log(`Extracting ${m.name}...`),await L(g,w);let A=s||h,R;A.startsWith("~")?R=new RegExp(A.substring(1),"i"):R=A;let T=k(w,R,i,console.log);if(!T)throw new Error(`Could not find binary "${A}" in the extracted asset.`);let u;if(t["install-path"])u=p.resolve(t["install-path"]);else if(process.getuid&&process.getuid()===0)u="/usr/local/bin";else{let z=p.join(P.homedir(),"bin");c.existsSync(z)?u=z:u="/usr/local/bin"}c.existsSync(u)||c.mkdirSync(u,{recursive:!0});let I=p.basename(T),v=p.join(u,I);console.log(`Installing ${I} to ${v}...`),c.copyFileSync(T,v),process.platform!=="win32"&&c.chmodSync(v,"755"),c.rmSync(x,{recursive:!0,force:!0}),console.log("Installation successful!")}catch(a){console.error(`Error: ${a.message}`),process.exit(1)}}K();
|
|
||||||
Vendored
+25310
-61
File diff suppressed because one or more lines are too long
Generated
+210
-168
@@ -1,78 +1,84 @@
|
|||||||
{
|
{
|
||||||
"name": "setup-github-release",
|
"name": "@koszewscy/install-github-release",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "setup-github-release",
|
"name": "@koszewscy/install-github-release",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.11.0",
|
"@actions/core": "3.0.0",
|
||||||
"@actions/tool-cache": "^2.0.2"
|
"@actions/tool-cache": "4.0.0",
|
||||||
|
"commander": "14.0.3",
|
||||||
|
"minimatch": "10.2.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"check-github-token": "dist/check-token.js",
|
||||||
|
"install-github-release": "dist/cli.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.0.0",
|
"@types/node": "^24",
|
||||||
"esbuild": "^0.27.2",
|
"esbuild": "^0.28.1",
|
||||||
"typescript": "^5.9.0"
|
"typescript": "6.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24"
|
"node": ">=24.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "1.11.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz",
|
||||||
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
"integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^3.0.0",
|
||||||
"@actions/http-client": "^2.0.1"
|
"@actions/http-client": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/exec": {
|
"node_modules/@actions/exec": {
|
||||||
"version": "1.1.1",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz",
|
||||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
"integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/io": "^1.0.1"
|
"@actions/io": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "2.2.3",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.1.tgz",
|
||||||
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
|
"integrity": "sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tunnel": "^0.0.6",
|
"tunnel": "^0.0.6",
|
||||||
"undici": "^5.25.4"
|
"undici": "^6.23.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/io": {
|
"node_modules/@actions/io": {
|
||||||
"version": "1.1.3",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz",
|
||||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
"integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@actions/tool-cache": {
|
"node_modules/@actions/tool-cache": {
|
||||||
"version": "2.0.2",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-4.0.0.tgz",
|
||||||
"integrity": "sha512-fBhNNOWxuoLxztQebpOaWu6WeVmuwa77Z+DxIZ1B+OYvGkGQon6kTVg6Z32Cb13WCuw0szqonK+hh03mJV7Z6w==",
|
"integrity": "sha512-L8P9HbXvpvqjZDveb/fdsa55IVC0trfPgQ4ZwGo6r5af6YDVdM9vMGPZ7rgY2fAT9gGj4PSYd6bYlg3p3jD78A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.11.1",
|
"@actions/core": "^3.0.0",
|
||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^3.0.0",
|
||||||
"@actions/http-client": "^2.0.1",
|
"@actions/http-client": "^4.0.0",
|
||||||
"@actions/io": "^1.1.1",
|
"@actions/io": "^3.0.0",
|
||||||
"semver": "^6.1.0"
|
"semver": "^7.7.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
|
||||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -87,9 +93,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
|
||||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -104,9 +110,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -121,9 +127,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -138,9 +144,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -155,9 +161,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -172,9 +178,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -189,9 +195,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -206,9 +212,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
|
||||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -223,9 +229,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -240,9 +246,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
|
||||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -257,9 +263,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
|
||||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -274,9 +280,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
|
||||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -291,9 +297,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
|
||||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -308,9 +314,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
|
||||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -325,9 +331,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
|
||||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -342,9 +348,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -359,9 +365,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-arm64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -376,9 +382,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -393,9 +399,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-arm64": {
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -410,9 +416,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -427,9 +433,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openharmony-arm64": {
|
"node_modules/@esbuild/openharmony-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -444,9 +450,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -461,9 +467,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
|
||||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -478,9 +484,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
|
||||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -495,9 +501,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
|
||||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -511,29 +517,50 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fastify/busboy": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "25.0.6",
|
"version": "24.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
||||||
"integrity": "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==",
|
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/balanced-match": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/brace-expansion": {
|
||||||
|
"version": "5.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||||
|
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/commander": {
|
||||||
|
"version": "14.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
|
||||||
|
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.27.2",
|
"version": "0.28.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
|
||||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -544,41 +571,59 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.27.2",
|
"@esbuild/aix-ppc64": "0.28.1",
|
||||||
"@esbuild/android-arm": "0.27.2",
|
"@esbuild/android-arm": "0.28.1",
|
||||||
"@esbuild/android-arm64": "0.27.2",
|
"@esbuild/android-arm64": "0.28.1",
|
||||||
"@esbuild/android-x64": "0.27.2",
|
"@esbuild/android-x64": "0.28.1",
|
||||||
"@esbuild/darwin-arm64": "0.27.2",
|
"@esbuild/darwin-arm64": "0.28.1",
|
||||||
"@esbuild/darwin-x64": "0.27.2",
|
"@esbuild/darwin-x64": "0.28.1",
|
||||||
"@esbuild/freebsd-arm64": "0.27.2",
|
"@esbuild/freebsd-arm64": "0.28.1",
|
||||||
"@esbuild/freebsd-x64": "0.27.2",
|
"@esbuild/freebsd-x64": "0.28.1",
|
||||||
"@esbuild/linux-arm": "0.27.2",
|
"@esbuild/linux-arm": "0.28.1",
|
||||||
"@esbuild/linux-arm64": "0.27.2",
|
"@esbuild/linux-arm64": "0.28.1",
|
||||||
"@esbuild/linux-ia32": "0.27.2",
|
"@esbuild/linux-ia32": "0.28.1",
|
||||||
"@esbuild/linux-loong64": "0.27.2",
|
"@esbuild/linux-loong64": "0.28.1",
|
||||||
"@esbuild/linux-mips64el": "0.27.2",
|
"@esbuild/linux-mips64el": "0.28.1",
|
||||||
"@esbuild/linux-ppc64": "0.27.2",
|
"@esbuild/linux-ppc64": "0.28.1",
|
||||||
"@esbuild/linux-riscv64": "0.27.2",
|
"@esbuild/linux-riscv64": "0.28.1",
|
||||||
"@esbuild/linux-s390x": "0.27.2",
|
"@esbuild/linux-s390x": "0.28.1",
|
||||||
"@esbuild/linux-x64": "0.27.2",
|
"@esbuild/linux-x64": "0.28.1",
|
||||||
"@esbuild/netbsd-arm64": "0.27.2",
|
"@esbuild/netbsd-arm64": "0.28.1",
|
||||||
"@esbuild/netbsd-x64": "0.27.2",
|
"@esbuild/netbsd-x64": "0.28.1",
|
||||||
"@esbuild/openbsd-arm64": "0.27.2",
|
"@esbuild/openbsd-arm64": "0.28.1",
|
||||||
"@esbuild/openbsd-x64": "0.27.2",
|
"@esbuild/openbsd-x64": "0.28.1",
|
||||||
"@esbuild/openharmony-arm64": "0.27.2",
|
"@esbuild/openharmony-arm64": "0.28.1",
|
||||||
"@esbuild/sunos-x64": "0.27.2",
|
"@esbuild/sunos-x64": "0.28.1",
|
||||||
"@esbuild/win32-arm64": "0.27.2",
|
"@esbuild/win32-arm64": "0.28.1",
|
||||||
"@esbuild/win32-ia32": "0.27.2",
|
"@esbuild/win32-ia32": "0.28.1",
|
||||||
"@esbuild/win32-x64": "0.27.2"
|
"@esbuild/win32-x64": "0.28.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minimatch": {
|
||||||
|
"version": "10.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||||
|
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^5.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18 || 20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "6.3.1",
|
"version": "7.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tunnel": {
|
"node_modules/tunnel": {
|
||||||
@@ -591,9 +636,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -605,21 +650,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "5.29.0",
|
"version": "6.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
|
||||||
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"@fastify/busboy": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0"
|
"node": ">=18.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.16.0",
|
"version": "7.18.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-18
@@ -1,39 +1,41 @@
|
|||||||
{
|
{
|
||||||
"name": "install-github-release",
|
"name": "@koszewscy/install-github-release",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A GitHub Action and CLI tool to download and install binaries from GitHub releases",
|
"description": "A GitHub Action and CLI tool to download and install binaries from GitHub releases",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"install-github-release": "dist/cli.js"
|
"install-github-release": "dist/cli.js",
|
||||||
|
"check-github-token": "dist/check-token.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:action": "esbuild src/index.ts --bundle --platform=node --target=node24 --outfile=dist/index.js --minify",
|
"build:action": "esbuild src/index.ts --bundle --platform=node --target=node24 --outfile=dist/index.js",
|
||||||
"build:cli": "esbuild src/cli.ts --bundle --platform=node --target=node24 --outfile=dist/cli.js --minify --banner:js=\"#!/usr/bin/env node\"",
|
"build:cli": "esbuild src/cli.ts --bundle --platform=node --target=node24 --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\" && chmod +x dist/cli.js",
|
||||||
"build": "npm run build:action && npm run build:cli",
|
"build:check-token": "esbuild src/check-token.ts --bundle --platform=node --target=node24 --outfile=dist/check-token.js --banner:js=\"#!/usr/bin/env node\" && chmod +x dist/check-token.js",
|
||||||
"format": "prettier --write '**/*.ts'",
|
"build:check-token-action": "esbuild src/check-token-action.ts --bundle --platform=node --target=node24 --outfile=dist/check-token-action.js",
|
||||||
"format-check": "prettier --check '**/*.ts'",
|
"build": "npm run build:action && npm run build:cli && npm run build:check-token && npm run build:check-token-action"
|
||||||
"lint": "eslint src/**/*.ts",
|
|
||||||
"package": "npm run build",
|
|
||||||
"test": "jest",
|
|
||||||
"all": "npm run format && npm run lint && npm run test && npm run package"
|
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"actions",
|
"actions",
|
||||||
"node",
|
"node",
|
||||||
"setup"
|
"setup"
|
||||||
],
|
],
|
||||||
"author": "",
|
"author": "Sławomir Koszewski",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24"
|
"node": ">=24.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.11.0",
|
"@actions/core": "3.0.0",
|
||||||
"@actions/tool-cache": "^2.0.2"
|
"@actions/tool-cache": "4.0.0",
|
||||||
|
"commander": "14.0.3",
|
||||||
|
"minimatch": "10.2.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.0.0",
|
"@types/node": "^24",
|
||||||
"esbuild": "^0.27.2",
|
"esbuild": "^0.28.1",
|
||||||
"typescript": "^5.9.0"
|
"typescript": "6.0.2"
|
||||||
|
},
|
||||||
|
"allowScripts": {
|
||||||
|
"esbuild@0.28.1": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+387
@@ -0,0 +1,387 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SYSTEM_PATTERNS = {
|
||||||
|
'linux': 'linux',
|
||||||
|
'darwin': '(darwin|macos|mac|osx)',
|
||||||
|
'win32': '(windows|win)',
|
||||||
|
}
|
||||||
|
|
||||||
|
ARCH_PATTERNS = {
|
||||||
|
'x64': '(x86_64|x64|amd64|universal)',
|
||||||
|
'arm64': '(aarch64|arm64|universal)',
|
||||||
|
}
|
||||||
|
|
||||||
|
MACHINE_TO_ARCH = {
|
||||||
|
'x86_64': 'x64',
|
||||||
|
'amd64': 'x64',
|
||||||
|
'aarch64': 'arm64',
|
||||||
|
'arm64': 'arm64',
|
||||||
|
}
|
||||||
|
|
||||||
|
KNOWN_FILE_TYPES = {
|
||||||
|
'archive': ('.zip', '.tar.gz', '.tgz'),
|
||||||
|
'package': ('.deb', '.pkg', '.rpm'),
|
||||||
|
'linux': ('.deb', '.rpm'),
|
||||||
|
'macos': ('.pkg',),
|
||||||
|
'targz': ('.tgz', '.tar.gz'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformInfo:
|
||||||
|
"""Holds the detected OS/architecture and their regex patterns for asset matching."""
|
||||||
|
|
||||||
|
def __init__(self, system, arch):
|
||||||
|
self.system = system
|
||||||
|
self.arch = arch
|
||||||
|
self.system_pattern = SYSTEM_PATTERNS.get(system, system)
|
||||||
|
self.arch_pattern = ARCH_PATTERNS.get(arch, arch)
|
||||||
|
|
||||||
|
|
||||||
|
def replace_platform_placeholders(pattern, platform):
|
||||||
|
"""Substitute {{SYSTEM}} and {{ARCH}} in a pattern with the platform's regex fragments."""
|
||||||
|
return pattern.replace('{{SYSTEM}}', platform.system_pattern).replace('{{ARCH}}', platform.arch_pattern)
|
||||||
|
|
||||||
|
|
||||||
|
def get_github_api_headers(token=None):
|
||||||
|
"""Build the request headers for the GitHub REST API, adding an Authorization header if a token is given."""
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/vnd.github.v3+json',
|
||||||
|
'User-Agent': 'install-github-release-cli',
|
||||||
|
}
|
||||||
|
if token:
|
||||||
|
headers['Authorization'] = f'token {token}'
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_latest_release(repository, token=None):
|
||||||
|
"""Fetch and parse the latest release metadata for a GitHub repository."""
|
||||||
|
url = f'https://api.github.com/repos/{repository}/releases/latest'
|
||||||
|
request = urllib.request.Request(url, headers=get_github_api_headers(token))
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(request) as response:
|
||||||
|
return json.loads(response.read().decode('utf-8'))
|
||||||
|
except urllib.error.HTTPError as error:
|
||||||
|
body = error.read().decode('utf-8', errors='replace')
|
||||||
|
raise RuntimeError(f'Failed to fetch latest release for {repository}: {error.reason}. {body}') from error
|
||||||
|
|
||||||
|
|
||||||
|
def download_asset(url, dest_path, token=None):
|
||||||
|
"""Download a release asset from url and write it to dest_path."""
|
||||||
|
headers = {'User-Agent': 'install-github-release-cli'}
|
||||||
|
if token:
|
||||||
|
headers['Authorization'] = f'token {token}'
|
||||||
|
request = urllib.request.Request(url, headers=headers)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(request) as response, open(dest_path, 'wb') as out_file:
|
||||||
|
shutil.copyfileobj(response, out_file)
|
||||||
|
except urllib.error.HTTPError as error:
|
||||||
|
raise RuntimeError(f'Failed to download asset: {error.reason}') from error
|
||||||
|
|
||||||
|
|
||||||
|
def validate_output_directory(output_directory):
|
||||||
|
"""Resolve output_directory to an absolute path and confirm it already exists."""
|
||||||
|
resolved = Path(output_directory).resolve()
|
||||||
|
if not resolved.is_dir():
|
||||||
|
raise RuntimeError(f'Output directory "{resolved}" does not exist.')
|
||||||
|
return str(resolved)
|
||||||
|
|
||||||
|
|
||||||
|
def install_github_release(repository, *, list_only=False, app_name=None, file_name=None, binary_name=None,
|
||||||
|
file_type=None, install_path=None, output_directory=None, releases_json=False,
|
||||||
|
system=None, arch=None, token=None, debug=False, dry_run=False):
|
||||||
|
"""Download, extract, and install a binary from a repository's latest GitHub release."""
|
||||||
|
|
||||||
|
# Normalize and validate the file-type selector.
|
||||||
|
raw_file_type = file_type
|
||||||
|
file_type = raw_file_type.strip() if raw_file_type is not None else None
|
||||||
|
if raw_file_type is not None and not file_type:
|
||||||
|
raise RuntimeError(f'Unknown asset type: {raw_file_type}')
|
||||||
|
|
||||||
|
# List mode: print and return the release's assets without downloading anything.
|
||||||
|
if list_only:
|
||||||
|
release = fetch_latest_release(repository, token)
|
||||||
|
for asset in release['assets']:
|
||||||
|
print(f"- {asset['browser_download_url']}")
|
||||||
|
return release['assets']
|
||||||
|
|
||||||
|
# Derive the display name and the binary's source/destination names from the repository or overrides.
|
||||||
|
tool_name = repository.split('/')[-1] or repository
|
||||||
|
app_name = app_name or (tool_name[0].upper() + tool_name[1:] if tool_name else tool_name)
|
||||||
|
|
||||||
|
binary_option = binary_name or tool_name
|
||||||
|
binary_parts = binary_option.split(':')
|
||||||
|
if len(binary_parts) > 1:
|
||||||
|
binary_source, binary_destination = binary_parts[0], binary_parts[1]
|
||||||
|
else:
|
||||||
|
binary_source = binary_destination = binary_option
|
||||||
|
|
||||||
|
# Releases-JSON mode: save the raw release API response to a file and return its path.
|
||||||
|
if releases_json:
|
||||||
|
url = f'https://api.github.com/repos/{repository}/releases/latest'
|
||||||
|
request = urllib.request.Request(url, headers=get_github_api_headers(token))
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(request) as response:
|
||||||
|
raw_release = response.read().decode('utf-8')
|
||||||
|
except urllib.error.HTTPError as error:
|
||||||
|
body = error.read().decode('utf-8', errors='replace')
|
||||||
|
raise RuntimeError(f'Failed to fetch latest release for {repository}: {error.reason}. {body}') from error
|
||||||
|
|
||||||
|
output_base = binary_destination or tool_name
|
||||||
|
output_name = f'{output_base}.releases.json'
|
||||||
|
if output_directory:
|
||||||
|
output_path = os.path.join(validate_output_directory(output_directory), output_name)
|
||||||
|
else:
|
||||||
|
output_path = output_name
|
||||||
|
with open(output_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(raw_release)
|
||||||
|
print(f'Downloaded GitHub releases to {output_path}.')
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
# Detect (or apply overrides for) the target OS and architecture.
|
||||||
|
system = (system or sys.platform).lower()
|
||||||
|
raw_machine = arch or (os.uname().machine if hasattr(os, 'uname') else os.environ.get('PROCESSOR_ARCHITECTURE', ''))
|
||||||
|
machine = raw_machine.lower()
|
||||||
|
arch = MACHINE_TO_ARCH.get(machine, machine)
|
||||||
|
platform_info = PlatformInfo(system, arch)
|
||||||
|
|
||||||
|
# Fetch the latest release and select the one asset matching the file-name/file-type criteria.
|
||||||
|
print(f'Fetching latest release for {repository}...')
|
||||||
|
release = fetch_latest_release(repository, token)
|
||||||
|
assets = release['assets']
|
||||||
|
|
||||||
|
# A literal (non-regex) file-name is matched verbatim, bypassing file-type filtering entirely.
|
||||||
|
if file_name and not file_name.startswith('~'):
|
||||||
|
exact_matches = [a for a in assets if a['name'] == file_name]
|
||||||
|
if len(exact_matches) != 1:
|
||||||
|
raise RuntimeError(f'Expected exactly one asset to match the provided filename, matched: {len(exact_matches)}')
|
||||||
|
asset = exact_matches[0]
|
||||||
|
else:
|
||||||
|
# Narrow the candidates by file-type: a known type keyword, a trailing regex, or a plain extension.
|
||||||
|
file_type_filtered = assets
|
||||||
|
if file_type:
|
||||||
|
if file_type in KNOWN_FILE_TYPES:
|
||||||
|
suffixes = KNOWN_FILE_TYPES[file_type]
|
||||||
|
file_type_filtered = [a for a in assets if a['name'].lower().endswith(suffixes)]
|
||||||
|
elif file_type.startswith('~'):
|
||||||
|
type_regex = re.compile(file_type[1:] + '$', re.IGNORECASE)
|
||||||
|
file_type_filtered = [a for a in assets if type_regex.search(a['name'])]
|
||||||
|
else:
|
||||||
|
extension = file_type.lstrip('.')
|
||||||
|
file_type_filtered = [a for a in assets if a['name'].lower().endswith(f'.{extension}'.lower())]
|
||||||
|
|
||||||
|
# A regex file-name (with platform placeholders expanded) is matched against the type-filtered candidates.
|
||||||
|
if file_name and file_name.startswith('~'):
|
||||||
|
name_pattern = replace_platform_placeholders(file_name[1:], platform_info)
|
||||||
|
name_regex = re.compile(name_pattern, re.IGNORECASE)
|
||||||
|
matched = [a for a in file_type_filtered if name_regex.search(a['name'])]
|
||||||
|
if len(matched) != 1:
|
||||||
|
raise RuntimeError(f'Expected exactly one asset to match the filename regex, matched: {len(matched)}')
|
||||||
|
asset = matched[0]
|
||||||
|
else:
|
||||||
|
# With no file-name given, fall back to matching the detected system and architecture.
|
||||||
|
default_pattern = replace_platform_placeholders('{{SYSTEM}}[_-]{{ARCH}}', platform_info)
|
||||||
|
default_regex = re.compile(default_pattern, re.IGNORECASE)
|
||||||
|
matched = [a for a in file_type_filtered if default_regex.search(a['name'])]
|
||||||
|
if len(matched) != 1:
|
||||||
|
if len(matched) == 0:
|
||||||
|
raise RuntimeError(f'No assets matched the default criteria: {default_pattern}')
|
||||||
|
names = ', '.join(a['name'] for a in matched)
|
||||||
|
raise RuntimeError(f'Multiple assets matched the default criteria: {names}')
|
||||||
|
asset = matched[0]
|
||||||
|
|
||||||
|
version = re.sub(r'^v', '', release['tag_name'], flags=re.IGNORECASE)
|
||||||
|
download_url = asset['browser_download_url']
|
||||||
|
print(f"Will download '{app_name}' version: {version}")
|
||||||
|
print(f'Download URL: "{download_url}".')
|
||||||
|
|
||||||
|
# Dry-run mode: stop after reporting the selected asset.
|
||||||
|
if dry_run:
|
||||||
|
return {'version': version, 'download_url': download_url, 'asset': asset}
|
||||||
|
|
||||||
|
# Output-directory mode: download the asset without extracting or installing it.
|
||||||
|
if output_directory:
|
||||||
|
output_dir = validate_output_directory(output_directory)
|
||||||
|
output_path = os.path.join(output_dir, os.path.basename(download_url))
|
||||||
|
print(f"Downloading '{app_name}' version {version} to '{output_path}'...")
|
||||||
|
download_asset(download_url, output_path, token)
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
# Full install: download the asset into a temporary directory that is removed on exit.
|
||||||
|
temp_dir = tempfile.mkdtemp(prefix='setup-gh-release-')
|
||||||
|
try:
|
||||||
|
download_path = os.path.join(temp_dir, asset['name'])
|
||||||
|
download_asset(download_url, download_path, token)
|
||||||
|
asset_name_lower = asset['name'].lower()
|
||||||
|
|
||||||
|
# System-package asset: hand it to the OS package manager and stop, no binary search or copy needed.
|
||||||
|
if re.search(r'\.(deb|pkg|rpm)$', asset_name_lower):
|
||||||
|
if asset_name_lower.endswith('.deb'):
|
||||||
|
pkg_binary, pkg_args = 'dpkg', ['-i', download_path]
|
||||||
|
elif asset_name_lower.endswith('.pkg'):
|
||||||
|
pkg_binary, pkg_args = 'installer', ['-pkg', download_path, '-target', '/']
|
||||||
|
else:
|
||||||
|
pkg_binary, pkg_args = 'rpm', ['-i', download_path]
|
||||||
|
|
||||||
|
is_root = hasattr(os, 'getuid') and os.getuid() == 0
|
||||||
|
command = [pkg_binary, *pkg_args] if is_root else ['sudo', pkg_binary, *pkg_args]
|
||||||
|
result = subprocess.run(command)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(f'Failed to install package using {" ".join(command)}.')
|
||||||
|
|
||||||
|
print('Installation successful!')
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Archive asset: extract it into a subdirectory using the format implied by its extension.
|
||||||
|
extract_dir = os.path.join(temp_dir, 'extract')
|
||||||
|
print(f"Extracting {asset['name']}...")
|
||||||
|
os.makedirs(extract_dir, exist_ok=True)
|
||||||
|
|
||||||
|
if asset_name_lower.endswith('.tar.gz') or asset_name_lower.endswith('.tgz') or asset_name_lower.endswith('.tar'):
|
||||||
|
with tarfile.open(download_path, 'r:*') as archive:
|
||||||
|
archive.extractall(extract_dir, filter='data')
|
||||||
|
elif asset_name_lower.endswith('.zip'):
|
||||||
|
with zipfile.ZipFile(download_path) as archive:
|
||||||
|
archive.extractall(extract_dir)
|
||||||
|
elif asset_name_lower.endswith('.7z'):
|
||||||
|
result = subprocess.run(['7z', 'x', download_path, f'-o{extract_dir}', '-y'])
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError('7z failed. Make sure 7z is installed.')
|
||||||
|
else:
|
||||||
|
shutil.copy2(download_path, os.path.join(extract_dir, os.path.basename(download_path)))
|
||||||
|
|
||||||
|
# Build the binary's search pattern, expanding platform placeholders in a regex or literal name.
|
||||||
|
if binary_source.startswith('~'):
|
||||||
|
binary_regex_source = replace_platform_placeholders(binary_source[1:], platform_info)
|
||||||
|
binary_pattern = f'~{binary_regex_source}'
|
||||||
|
else:
|
||||||
|
binary_pattern = binary_source.replace('{{SYSTEM}}', platform_info.system).replace('{{ARCH}}', platform_info.arch)
|
||||||
|
|
||||||
|
binary_regex = re.compile(binary_pattern[1:], re.IGNORECASE) if binary_pattern.startswith('~') else None
|
||||||
|
|
||||||
|
# Walk the extracted tree in sorted order and stop at the first file matching the pattern.
|
||||||
|
binary_path = None
|
||||||
|
for dirpath, dirnames, filenames in os.walk(extract_dir):
|
||||||
|
dirnames.sort()
|
||||||
|
filenames.sort()
|
||||||
|
if debug:
|
||||||
|
print(f'Searching for binary in {dirpath}...')
|
||||||
|
for item in dirnames + filenames:
|
||||||
|
print(f' - {item}')
|
||||||
|
|
||||||
|
for item in filenames:
|
||||||
|
if binary_regex:
|
||||||
|
is_match = bool(binary_regex.search(item))
|
||||||
|
else:
|
||||||
|
is_match = item == binary_pattern
|
||||||
|
if not is_match and sys.platform == 'win32' and not binary_pattern.lower().endswith('.exe'):
|
||||||
|
is_match = item.lower() == f'{binary_pattern.lower()}.exe'
|
||||||
|
if is_match:
|
||||||
|
binary_path = os.path.join(dirpath, item)
|
||||||
|
break
|
||||||
|
if binary_path:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not binary_path:
|
||||||
|
raise RuntimeError(f'Could not find binary "{binary_source}" in the extracted asset.')
|
||||||
|
|
||||||
|
# Resolve the install directory: an explicit override, or an OS-appropriate default.
|
||||||
|
if install_path:
|
||||||
|
install_dir = str(Path(install_path).resolve())
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
local_app_data = os.environ.get('LOCALAPPDATA') or str(Path.home() / 'AppData' / 'Local')
|
||||||
|
install_dir = os.path.join(local_app_data, 'bin')
|
||||||
|
elif hasattr(os, 'getuid') and os.getuid() == 0:
|
||||||
|
install_dir = '/usr/local/bin'
|
||||||
|
else:
|
||||||
|
home_bin = str(Path.home() / 'bin')
|
||||||
|
install_dir = home_bin if os.path.isdir(home_bin) else '/usr/local/bin'
|
||||||
|
|
||||||
|
os.makedirs(install_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Copy the binary into place and make it executable.
|
||||||
|
final_name = binary_destination or os.path.basename(binary_path)
|
||||||
|
dest_path = os.path.join(install_dir, final_name)
|
||||||
|
|
||||||
|
print(f'Installing {final_name} to {dest_path}...')
|
||||||
|
shutil.copy2(binary_path, dest_path)
|
||||||
|
if sys.platform != 'win32':
|
||||||
|
os.chmod(dest_path, 0o755)
|
||||||
|
|
||||||
|
print('Installation successful!')
|
||||||
|
return dest_path
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Parse command-line arguments and run install_github_release, reporting errors via the exit code."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='install-github-release',
|
||||||
|
usage='%(prog)s [options] <repository>',
|
||||||
|
allow_abbrev=False,
|
||||||
|
)
|
||||||
|
parser.add_argument('repository', nargs='?', help='The GitHub repository (owner/repo)')
|
||||||
|
parser.add_argument('--dry-run', action='store_true', help='Run in test mode')
|
||||||
|
parser.add_argument('-l', '--list', nargs='?', const=True, default=None, metavar='repository',
|
||||||
|
help='List available assets from latest release and exit')
|
||||||
|
parser.add_argument('-a', '--app-name', metavar='name', help='Application name (optional, for output messages)')
|
||||||
|
parser.add_argument('-f', '--file-name', metavar='name', help='Asset file name or regex pattern (prefixed with ~)')
|
||||||
|
parser.add_argument('-b', '--binary-name', metavar='name', help='Binary name (supports source:destination form)')
|
||||||
|
parser.add_argument('-t', '--file-type', metavar='type',
|
||||||
|
help='Known: archive|package|linux|macos|targz; custom: ~<regex> or extension')
|
||||||
|
parser.add_argument('-p', '--install-path', metavar='path', help='Custom installation directory')
|
||||||
|
parser.add_argument('-o', '--output-directory', metavar='path',
|
||||||
|
help='Only download selected asset to the specified directory')
|
||||||
|
parser.add_argument('-j', '--releases-json', action='store_true', help='Download latest release JSON only')
|
||||||
|
parser.add_argument('--system', metavar='name', help='Override detected system for asset matching')
|
||||||
|
parser.add_argument('--arch', metavar='name', help='Override detected architecture for asset matching')
|
||||||
|
parser.add_argument('-k', '--token', metavar='token', help='GitHub token')
|
||||||
|
parser.add_argument('-d', '--debug', action='store_true', help='Enable debug logging')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
list_repo = args.list if isinstance(args.list, str) else None
|
||||||
|
repository = list_repo or args.repository
|
||||||
|
if not repository:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
install_github_release(
|
||||||
|
repository,
|
||||||
|
list_only=args.list is not None,
|
||||||
|
app_name=args.app_name,
|
||||||
|
file_name=args.file_name,
|
||||||
|
binary_name=args.binary_name,
|
||||||
|
file_type=args.file_type,
|
||||||
|
install_path=args.install_path,
|
||||||
|
output_directory=args.output_directory,
|
||||||
|
releases_json=args.releases_json,
|
||||||
|
system=args.system,
|
||||||
|
arch=args.arch,
|
||||||
|
token=args.token,
|
||||||
|
debug=args.debug,
|
||||||
|
dry_run=args.dry_run,
|
||||||
|
)
|
||||||
|
except Exception as error:
|
||||||
|
print(f'Error: {error}', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import * as core from '@actions/core';
|
||||||
|
import { fetchLatestRelease } from './core/downloader';
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
try {
|
||||||
|
const repository = core.getInput('repository') || 'actions/checkout';
|
||||||
|
const token = core.getInput('token') || process.env.GITHUB_TOKEN;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
core.setFailed('No GitHub token provided as an input or found in GITHUB_TOKEN environment variable.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info(`Verifying GitHub token using repository ${repository}...`);
|
||||||
|
// Attempt to list latest release of the specified repository as a test
|
||||||
|
await fetchLatestRelease(repository, token);
|
||||||
|
|
||||||
|
core.info('Success: The provided GitHub token is valid and has sufficient permissions to access the repository.');
|
||||||
|
} catch (error: any) {
|
||||||
|
core.setFailed(`GitHub token verification failed. Reason: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { parseArgs } from 'util';
|
||||||
|
import { fetchLatestRelease } from './core/downloader';
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const { positionals } = parseArgs({
|
||||||
|
allowPositionals: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = positionals[0] || process.env.GITHUB_TOKEN;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
console.error('Error: No GitHub token provided as an argument or found in GITHUB_TOKEN environment variable.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Verifying GitHub token...');
|
||||||
|
// Attempt to list latest release of actions/checkout as a test
|
||||||
|
await fetchLatestRelease('actions/checkout', token);
|
||||||
|
|
||||||
|
console.log('\x1b[32mSuccess: The provided GitHub token is valid and has sufficient permissions to access public repositories.\x1b[0m');
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('\x1b[31mError: GitHub token verification failed.\x1b[0m');
|
||||||
|
console.error(`Reason: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
+209
-110
@@ -1,137 +1,236 @@
|
|||||||
import { parseArgs } from 'util';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
|
import { Command } from 'commander';
|
||||||
import { getPlatformInfo } from './core/platform';
|
import { getPlatformInfo } from './core/platform';
|
||||||
import { getMatchingAsset } from './core/matcher';
|
import { getMatchingAsset } from './core/matcher';
|
||||||
import { findBinary } from './core/finder';
|
import { findBinary } from './core/finder';
|
||||||
import { fetchLatestRelease, downloadAsset } from './core/downloader';
|
import { fetchLatestRelease, fetchLatestReleaseRaw, downloadAsset } from './core/downloader';
|
||||||
import { extractAsset } from './core/extractor';
|
import { extractAsset } from './core/extractor';
|
||||||
|
import { installSystemPackage } from './core/installer';
|
||||||
|
|
||||||
|
interface CliOptions {
|
||||||
|
appName?: string;
|
||||||
|
fileName?: string;
|
||||||
|
binaryName?: string;
|
||||||
|
fileType?: string;
|
||||||
|
installPath?: string;
|
||||||
|
outputDirectory?: string;
|
||||||
|
releasesJsonOnly: boolean;
|
||||||
|
listOnly: boolean;
|
||||||
|
token?: string;
|
||||||
|
debug: boolean;
|
||||||
|
dryRun: boolean;
|
||||||
|
systemOverride?: string;
|
||||||
|
archOverride?: string;
|
||||||
|
listRepo?: string;
|
||||||
|
positionals: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateOutputDirectory(outputDirectory: string): string {
|
||||||
|
const resolvedPath = path.resolve(outputDirectory);
|
||||||
|
if (!fs.existsSync(resolvedPath) || !fs.statSync(resolvedPath).isDirectory()) {
|
||||||
|
throw new Error(`Output directory "${resolvedPath}" does not exist.`);
|
||||||
|
}
|
||||||
|
return resolvedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInstallDir(installPath?: string): string {
|
||||||
|
if (installPath) {
|
||||||
|
return path.resolve(installPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
||||||
|
return path.join(localAppData, 'bin');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRoot = process.getuid && process.getuid() === 0;
|
||||||
|
if (isRoot) {
|
||||||
|
return '/usr/local/bin';
|
||||||
|
}
|
||||||
|
|
||||||
|
const homeBin = path.join(os.homedir(), 'bin');
|
||||||
|
if (fs.existsSync(homeBin)) {
|
||||||
|
return homeBin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/usr/local/bin';
|
||||||
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
const { values, positionals } = parseArgs({
|
let tempDir: string | undefined;
|
||||||
options: {
|
const program = new Command();
|
||||||
'file-name': { type: 'string', short: 'f' },
|
program
|
||||||
'binary-name': { type: 'string', short: 'b' },
|
.name('install-github-release')
|
||||||
'file-type': { type: 'string', short: 't', default: 'archive' },
|
.usage('[options] <repository>')
|
||||||
'install-path': { type: 'string', short: 'p' },
|
.argument('[repository]', 'The GitHub repository (owner/repo)')
|
||||||
'token': { type: 'string', short: 'k' },
|
.option('--dry-run', 'Run in test mode')
|
||||||
'debug': { type: 'boolean', short: 'd', default: false },
|
.option('-l, --list [repository]', 'List available assets from latest release and exit')
|
||||||
'help': { type: 'boolean', short: 'h' }
|
.option('-a, --app-name <name>', 'Application name (optional, for output messages)')
|
||||||
},
|
.option('-f, --file-name <name>', 'Asset file name or regex pattern (prefixed with ~)')
|
||||||
allowPositionals: true
|
.option('-b, --binary-name <name>', 'Binary name (supports source:destination form)')
|
||||||
});
|
.option('-t, --file-type <type>', 'Known: archive|package|linux|macos|targz; custom: ~<regex> or extension')
|
||||||
|
.option('-p, --install-path <path>', 'Custom installation directory')
|
||||||
|
.option('-o, --output-directory <path>', 'Only download selected asset to the specified directory')
|
||||||
|
.option('-j, --releases-json', 'Download latest release JSON only')
|
||||||
|
.option('--system <name>', 'Override detected system for asset matching')
|
||||||
|
.option('--arch <name>', 'Override detected architecture for asset matching')
|
||||||
|
.option('-k, --token <token>', 'GitHub token')
|
||||||
|
.option('-d, --debug', 'Enable debug logging')
|
||||||
|
.allowUnknownOption(false);
|
||||||
|
|
||||||
if (values.help || positionals.length === 0) {
|
const cleanupTempDir = () => {
|
||||||
console.log(`
|
if (tempDir && fs.existsSync(tempDir)) {
|
||||||
Usage: install-github-release [options] <repository>
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Arguments:
|
program.parse(process.argv);
|
||||||
repository The GitHub repository (owner/repo)
|
const parsedOptions = program.opts();
|
||||||
|
const rawFileType = parsedOptions.fileType;
|
||||||
|
const fileType = typeof rawFileType === 'string' ? rawFileType.trim() : undefined;
|
||||||
|
if (rawFileType !== undefined && !fileType) {
|
||||||
|
throw new Error(`Unknown asset type: ${rawFileType}`);
|
||||||
|
}
|
||||||
|
|
||||||
Options:
|
const listValue = parsedOptions.list as string | boolean | undefined;
|
||||||
-f, --file-name <name> Asset file name or regex pattern (prefixed with ~)
|
const options: CliOptions = {
|
||||||
-b, --binary-name <name> Binary to search for (prefixed with ~ for regex)
|
appName: parsedOptions.appName as string | undefined,
|
||||||
-t, --file-type <type> 'archive', 'package', or custom regex (default: archive)
|
fileName: parsedOptions.fileName as string | undefined,
|
||||||
-p, --install-path <path> Custom installation directory
|
binaryName: parsedOptions.binaryName as string | undefined,
|
||||||
-k, --token <token> GitHub token
|
fileType,
|
||||||
-d, --debug Enable debug logging
|
installPath: parsedOptions.installPath as string | undefined,
|
||||||
-h, --help Show this help message
|
outputDirectory: parsedOptions.outputDirectory as string | undefined,
|
||||||
`);
|
releasesJsonOnly: Boolean(parsedOptions.releasesJson),
|
||||||
|
listOnly: listValue !== undefined,
|
||||||
|
token: parsedOptions.token as string | undefined,
|
||||||
|
debug: Boolean(parsedOptions.debug),
|
||||||
|
dryRun: Boolean(parsedOptions.dryRun),
|
||||||
|
systemOverride: parsedOptions.system as string | undefined,
|
||||||
|
archOverride: parsedOptions.arch as string | undefined,
|
||||||
|
listRepo: typeof listValue === 'string' ? listValue : undefined,
|
||||||
|
positionals: program.args
|
||||||
|
};
|
||||||
|
|
||||||
|
const repository = options.listRepo || options.positionals[0];
|
||||||
|
if (!repository) {
|
||||||
|
program.outputHelp();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = options.token;
|
||||||
|
|
||||||
|
if (options.listOnly) {
|
||||||
|
const release = await fetchLatestRelease(repository, token);
|
||||||
|
release.assets.forEach((asset) => console.log(`- ${asset.browser_download_url}`));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const repository = positionals[0];
|
const toolName = repository.split('/').pop() || repository;
|
||||||
if (!repository) {
|
const appName = options.appName || (toolName.charAt(0).toUpperCase() + toolName.slice(1));
|
||||||
console.error('Error: Repository is required.');
|
|
||||||
process.exit(1);
|
const binaryOption = options.binaryName || toolName;
|
||||||
|
const [binarySource, binaryDestination] = binaryOption.includes(':')
|
||||||
|
? [binaryOption.split(':')[0], binaryOption.split(':')[1]]
|
||||||
|
: [binaryOption, binaryOption];
|
||||||
|
|
||||||
|
if (options.releasesJsonOnly) {
|
||||||
|
const rawRelease = await fetchLatestReleaseRaw(repository, token);
|
||||||
|
const outputBase = binaryDestination || toolName;
|
||||||
|
const outputName = `${outputBase}.releases.json`;
|
||||||
|
const outputPath = options.outputDirectory
|
||||||
|
? path.join(validateOutputDirectory(options.outputDirectory), outputName)
|
||||||
|
: outputName;
|
||||||
|
|
||||||
|
fs.writeFileSync(outputPath, rawRelease, 'utf8');
|
||||||
|
console.log(`Downloaded GitHub releases to ${outputPath}.`);
|
||||||
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileNameInput = values['file-name'];
|
const platformInfo = getPlatformInfo({
|
||||||
const binaryInput = values['binary-name'];
|
system: options.systemOverride,
|
||||||
const fileType = values['file-type'];
|
arch: options.archOverride
|
||||||
const debug = !!values.debug;
|
});
|
||||||
const token = values.token || process.env.GITHUB_TOKEN;
|
|
||||||
|
|
||||||
try {
|
console.log(`Fetching latest release for ${repository}...`);
|
||||||
const platformInfo = getPlatformInfo();
|
const release = await fetchLatestRelease(repository, token);
|
||||||
const toolName = repository.split('/').pop() || repository;
|
const asset = getMatchingAsset(release.assets, platformInfo, options.fileName, options.fileType, options.dryRun);
|
||||||
|
|
||||||
console.log(`Fetching latest release for ${repository}...`);
|
const version = release.tag_name.replace(/^v/i, '');
|
||||||
const release = await fetchLatestRelease(repository, token);
|
const downloadUrl = asset.browser_download_url;
|
||||||
const asset = getMatchingAsset(release.assets, platformInfo, {
|
console.log(`Will download '${appName}' version: ${version}`);
|
||||||
fileName: fileNameInput,
|
console.log(`Download URL: "${downloadUrl}".`);
|
||||||
fileType: fileType
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Selected asset: ${asset.name}`);
|
if (options.dryRun) {
|
||||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-gh-release-'));
|
process.exit(0);
|
||||||
const downloadPath = path.join(tempDir, asset.name);
|
}
|
||||||
|
|
||||||
console.log(`Downloading ${asset.name}...`);
|
if (options.outputDirectory) {
|
||||||
await downloadAsset(asset.browser_download_url, downloadPath, token);
|
const outputDir = validateOutputDirectory(options.outputDirectory);
|
||||||
|
const outputPath = path.join(outputDir, path.basename(downloadUrl));
|
||||||
|
console.log(`Downloading '${appName}' version ${version} to '${outputPath}'...`);
|
||||||
|
await downloadAsset(downloadUrl, outputPath, token);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
const extractDir = path.join(tempDir, 'extract');
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-gh-release-'));
|
||||||
console.log(`Extracting ${asset.name}...`);
|
process.once('exit', cleanupTempDir);
|
||||||
await extractAsset(downloadPath, extractDir);
|
const downloadPath = path.join(tempDir, asset.name);
|
||||||
|
await downloadAsset(downloadUrl, downloadPath, token);
|
||||||
const binaryName = binaryInput || toolName;
|
|
||||||
let binaryPattern: string | RegExp;
|
|
||||||
if (binaryName.startsWith('~')) {
|
|
||||||
binaryPattern = new RegExp(binaryName.substring(1), 'i');
|
|
||||||
} else {
|
|
||||||
binaryPattern = binaryName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const binaryPath = findBinary(extractDir, binaryPattern, debug, console.log);
|
|
||||||
if (!binaryPath) {
|
|
||||||
throw new Error(`Could not find binary "${binaryName}" in the extracted asset.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine install directory
|
|
||||||
let installDir: string;
|
|
||||||
|
|
||||||
if (values['install-path']) {
|
|
||||||
installDir = path.resolve(values['install-path']);
|
|
||||||
} else {
|
|
||||||
const isRoot = process.getuid && process.getuid() === 0;
|
|
||||||
|
|
||||||
if (isRoot) {
|
|
||||||
installDir = '/usr/local/bin';
|
|
||||||
} else {
|
|
||||||
const homeBin = path.join(os.homedir(), 'bin');
|
|
||||||
if (fs.existsSync(homeBin)) {
|
|
||||||
installDir = homeBin;
|
|
||||||
} else {
|
|
||||||
// Fallback or error? Let's use a local bin if possible or /usr/local/bin (might fail)
|
|
||||||
installDir = '/usr/local/bin';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(installDir)) {
|
|
||||||
fs.mkdirSync(installDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalName = path.basename(binaryPath);
|
|
||||||
const destPath = path.join(installDir, finalName);
|
|
||||||
|
|
||||||
console.log(`Installing ${finalName} to ${destPath}...`);
|
|
||||||
fs.copyFileSync(binaryPath, destPath);
|
|
||||||
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
fs.chmodSync(destPath, '755');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
|
|
||||||
|
if (/\.(deb|pkg|rpm)$/i.test(asset.name)) {
|
||||||
|
installSystemPackage(downloadPath);
|
||||||
console.log('Installation successful!');
|
console.log('Installation successful!');
|
||||||
|
process.exit(0);
|
||||||
} catch (error: any) {
|
|
||||||
console.error(`Error: ${error.message}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extractDir = path.join(tempDir, 'extract');
|
||||||
|
console.log(`Extracting ${asset.name}...`);
|
||||||
|
await extractAsset(downloadPath, extractDir);
|
||||||
|
|
||||||
|
let binaryPattern: string;
|
||||||
|
if (binarySource.startsWith('~')) {
|
||||||
|
const binaryRegex = binarySource
|
||||||
|
.substring(1)
|
||||||
|
.replace(/{{SYSTEM}}/g, platformInfo.systemPattern)
|
||||||
|
.replace(/{{ARCH}}/g, platformInfo.archPattern);
|
||||||
|
binaryPattern = `~${binaryRegex}`;
|
||||||
|
} else {
|
||||||
|
binaryPattern = binarySource
|
||||||
|
.replace(/{{SYSTEM}}/g, platformInfo.system)
|
||||||
|
.replace(/{{ARCH}}/g, platformInfo.arch);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryPath = findBinary(extractDir, binaryPattern, options.debug, console.log);
|
||||||
|
if (!binaryPath) {
|
||||||
|
throw new Error(`Could not find binary "${binarySource}" in the extracted asset.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const installDir = getInstallDir(options.installPath);
|
||||||
|
if (!fs.existsSync(installDir)) {
|
||||||
|
fs.mkdirSync(installDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalName = binaryDestination || path.basename(binaryPath);
|
||||||
|
const destPath = path.join(installDir, finalName);
|
||||||
|
|
||||||
|
console.log(`Installing ${finalName} to ${destPath}...`);
|
||||||
|
fs.copyFileSync(binaryPath, destPath);
|
||||||
|
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
fs.chmodSync(destPath, '755');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Installation successful!');
|
||||||
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
run();
|
void run().catch((error: unknown) => {
|
||||||
|
if (error instanceof Error && error.message) {
|
||||||
|
console.error(`Error: ${error.message}`);
|
||||||
|
} else {
|
||||||
|
console.error('Error: Unknown failure.');
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
+19
-5
@@ -1,6 +1,3 @@
|
|||||||
import { getMatchingAsset } from './matcher';
|
|
||||||
import { PlatformInfo } from './platform';
|
|
||||||
|
|
||||||
export interface ReleaseAsset {
|
export interface ReleaseAsset {
|
||||||
name: string;
|
name: string;
|
||||||
browser_download_url: string;
|
browser_download_url: string;
|
||||||
@@ -11,8 +8,7 @@ export interface ReleaseInfo {
|
|||||||
assets: ReleaseAsset[];
|
assets: ReleaseAsset[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchLatestRelease(repository: string, token?: string): Promise<ReleaseInfo> {
|
function getGithubApiHeaders(token?: string): Record<string, string> {
|
||||||
const url = `https://api.github.com/repos/${repository}/releases/latest`;
|
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Accept': 'application/vnd.github.v3+json',
|
'Accept': 'application/vnd.github.v3+json',
|
||||||
'User-Agent': 'setup-github-release-action'
|
'User-Agent': 'setup-github-release-action'
|
||||||
@@ -20,6 +16,12 @@ export async function fetchLatestRelease(repository: string, token?: string): Pr
|
|||||||
if (token) {
|
if (token) {
|
||||||
headers['Authorization'] = `token ${token}`;
|
headers['Authorization'] = `token ${token}`;
|
||||||
}
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchLatestRelease(repository: string, token?: string): Promise<ReleaseInfo> {
|
||||||
|
const url = `https://api.github.com/repos/${repository}/releases/latest`;
|
||||||
|
const headers = getGithubApiHeaders(token);
|
||||||
|
|
||||||
const response = await fetch(url, { headers });
|
const response = await fetch(url, { headers });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -30,6 +32,18 @@ export async function fetchLatestRelease(repository: string, token?: string): Pr
|
|||||||
return await response.json() as ReleaseInfo;
|
return await response.json() as ReleaseInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchLatestReleaseRaw(repository: string, token?: string): Promise<string> {
|
||||||
|
const url = `https://api.github.com/repos/${repository}/releases/latest`;
|
||||||
|
const headers = getGithubApiHeaders(token);
|
||||||
|
|
||||||
|
const response = await fetch(url, { headers });
|
||||||
|
const body = await response.text();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch latest release for ${repository}: ${response.statusText}. ${body}`);
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
export async function downloadAsset(url: string, destPath: string, token?: string): Promise<void> {
|
export async function downloadAsset(url: string, destPath: string, token?: string): Promise<void> {
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'User-Agent': 'setup-github-release-action'
|
'User-Agent': 'setup-github-release-action'
|
||||||
|
|||||||
+16
-4
@@ -18,11 +18,23 @@ export async function extractAsset(filePath: string, destDir: string): Promise<v
|
|||||||
}
|
}
|
||||||
} else if (name.endsWith('.zip')) {
|
} else if (name.endsWith('.zip')) {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
const command = `Expand-Archive -Path "${filePath}" -DestinationPath "${destDir}" -Force`;
|
// Modern Windows 10/11 has tar that handles zip
|
||||||
const result = spawnSync('powershell', ['-Command', command]);
|
const tarResult = spawnSync('tar', ['-xf', filePath, '-C', destDir]);
|
||||||
if (result.status !== 0) {
|
if (tarResult.status === 0) return;
|
||||||
throw new Error(`powershell Expand-Archive failed with status ${result.status}: ${result.stderr.toString()}`);
|
|
||||||
|
// Fallback: Use .NET ZipFile class to bypass PowerShell module trust issues (Microsoft.PowerShell.Archive)
|
||||||
|
// We escape single quotes for PowerShell.
|
||||||
|
const escapedFilePath = filePath.replace(/'/g, "''");
|
||||||
|
const escapedDestDir = destDir.replace(/'/g, "''");
|
||||||
|
const dotNetCommand = `Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFilePath}', '${escapedDestDir}')`;
|
||||||
|
|
||||||
|
// Try pwsh (PowerShell 7) then powershell (Windows PowerShell)
|
||||||
|
for (const shell of ['pwsh', 'powershell']) {
|
||||||
|
const result = spawnSync(shell, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', dotNetCommand]);
|
||||||
|
if (result.status === 0) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Error(`Extraction failed: Both tar and PowerShell fallback failed. Make sure your system can extract ZIP files.`);
|
||||||
} else {
|
} else {
|
||||||
const result = spawnSync('unzip', ['-q', filePath, '-d', destDir]);
|
const result = spawnSync('unzip', ['-q', filePath, '-d', destDir]);
|
||||||
if (result.status !== 0) {
|
if (result.status !== 0) {
|
||||||
|
|||||||
+4
-3
@@ -1,7 +1,8 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
export function findBinary(dir: string, pattern: string | RegExp, debug: boolean, logger: (msg: string) => void): string | undefined {
|
export function findBinary(dir: string, pattern: string, debug: boolean, logger: (msg: string) => void): string | undefined {
|
||||||
|
const regex = pattern.startsWith('~') ? new RegExp(pattern.substring(1), 'i') : undefined;
|
||||||
const items = fs.readdirSync(dir);
|
const items = fs.readdirSync(dir);
|
||||||
if (debug) {
|
if (debug) {
|
||||||
logger(`Searching for binary in ${dir}...`);
|
logger(`Searching for binary in ${dir}...`);
|
||||||
@@ -16,8 +17,8 @@ export function findBinary(dir: string, pattern: string | RegExp, debug: boolean
|
|||||||
if (found) return found;
|
if (found) return found;
|
||||||
} else {
|
} else {
|
||||||
let isMatch = false;
|
let isMatch = false;
|
||||||
if (pattern instanceof RegExp) {
|
if (regex) {
|
||||||
isMatch = pattern.test(item);
|
isMatch = regex.test(item);
|
||||||
} else {
|
} else {
|
||||||
isMatch = item === pattern;
|
isMatch = item === pattern;
|
||||||
// On Windows, also check for .exe extension if the pattern doesn't have it
|
// On Windows, also check for .exe extension if the pattern doesn't have it
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import { spawnSync } from 'child_process';
|
||||||
|
|
||||||
|
export function installSystemPackage(downloadPath: string): void {
|
||||||
|
const fileName = path.basename(downloadPath).toLowerCase();
|
||||||
|
|
||||||
|
const command: { binary: string; args: string[] } | undefined = fileName.endsWith('.deb')
|
||||||
|
? { binary: 'dpkg', args: ['-i', downloadPath] }
|
||||||
|
: fileName.endsWith('.pkg')
|
||||||
|
? { binary: 'installer', args: ['-pkg', downloadPath, '-target', '/'] }
|
||||||
|
: fileName.endsWith('.rpm')
|
||||||
|
? { binary: 'rpm', args: ['-i', downloadPath] }
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
throw new Error(`Unsupported package type: ${fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRoot = process.getuid && process.getuid() === 0;
|
||||||
|
const commandToRun = isRoot ? command.binary : 'sudo';
|
||||||
|
const argsToRun = isRoot ? command.args : [command.binary, ...command.args];
|
||||||
|
|
||||||
|
const result = spawnSync(commandToRun, argsToRun, { stdio: 'inherit' });
|
||||||
|
if (result.status !== 0) {
|
||||||
|
throw new Error(`Failed to install package using ${commandToRun} ${argsToRun.join(' ')}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
-61
@@ -1,67 +1,89 @@
|
|||||||
import { PlatformInfo } from './platform';
|
import { PlatformInfo } from './platform';
|
||||||
|
import { minimatch } from 'minimatch';
|
||||||
|
|
||||||
export interface MatchOptions {
|
type ReleaseAsset = { name: string; browser_download_url: string };
|
||||||
fileName?: string;
|
|
||||||
fileType?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMatchingAsset(assets: any[], platform: PlatformInfo, options: MatchOptions): any {
|
const knownFileTypes: Record<string, string> = {
|
||||||
const { fileName, fileType = 'archive' } = options;
|
archive: '*.{zip,tar.gz,tgz}',
|
||||||
let extPattern: string;
|
package: '*.{deb,pkg,rpm}',
|
||||||
if (fileType === 'archive') {
|
linux: '*.{deb,rpm}',
|
||||||
extPattern = '\\.(zip|tar\\.gz|tar|tgz|7z)';
|
macos: '*.pkg',
|
||||||
} else if (fileType === 'package') {
|
targz: '*.{tgz,tar.gz}',
|
||||||
extPattern = '\\.(deb|rpm|pkg)';
|
};
|
||||||
} else {
|
|
||||||
extPattern = fileType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fileName) {
|
function debugAssetList(label: string, assets: ReleaseAsset[]): void {
|
||||||
// Rule 1: Default matching rule
|
console.debug(label);
|
||||||
const pattern = `${platform.systemPattern}[_-]${platform.archPattern}.*${extPattern}$`;
|
for (const asset of assets) {
|
||||||
const regex = new RegExp(pattern, 'i');
|
console.debug(`- ${asset.name}`);
|
||||||
const matchingAssets = assets.filter((a: any) => regex.test(a.name));
|
|
||||||
if (matchingAssets.length === 0) {
|
|
||||||
throw new Error(`No assets matched the default criteria: ${pattern}`);
|
|
||||||
}
|
|
||||||
if (matchingAssets.length > 1) {
|
|
||||||
throw new Error(`Multiple assets matched the default criteria: ${matchingAssets.map((a: any) => a.name).join(', ')}`);
|
|
||||||
}
|
|
||||||
return matchingAssets[0];
|
|
||||||
} else if (fileName.startsWith('~')) {
|
|
||||||
// Rule 3: Regex matching rule
|
|
||||||
let pattern = fileName.substring(1);
|
|
||||||
const hasSystem = pattern.includes('{{SYSTEM}}');
|
|
||||||
const hasArch = pattern.includes('{{ARCH}}');
|
|
||||||
const hasExt = pattern.includes('{{EXT_PATTERN}}');
|
|
||||||
const hasEnd = pattern.endsWith('$');
|
|
||||||
|
|
||||||
if (!hasSystem && !hasArch && !hasExt && !hasEnd) {
|
|
||||||
pattern += `.*{{SYSTEM}}[_-]{{ARCH}}.*{{EXT_PATTERN}}$`;
|
|
||||||
} else if (hasSystem && hasArch && !hasExt && !hasEnd) {
|
|
||||||
pattern += `.*{{EXT_PATTERN}}$`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalPattern = pattern
|
|
||||||
.replace(/{{SYSTEM}}/g, platform.systemPattern)
|
|
||||||
.replace(/{{ARCH}}/g, platform.archPattern)
|
|
||||||
.replace(/{{EXT_PATTERN}}/g, extPattern);
|
|
||||||
|
|
||||||
const regex = new RegExp(finalPattern, 'i');
|
|
||||||
const matchingAssets = assets.filter((a: any) => regex.test(a.name));
|
|
||||||
if (matchingAssets.length === 0) {
|
|
||||||
throw new Error(`No assets matched the regex: ${finalPattern}`);
|
|
||||||
}
|
|
||||||
if (matchingAssets.length > 1) {
|
|
||||||
throw new Error(`Multiple assets matched the criteria: ${matchingAssets.map((a: any) => a.name).join(', ')}`);
|
|
||||||
}
|
|
||||||
return matchingAssets[0];
|
|
||||||
} else {
|
|
||||||
// Rule 2: Literal matching rule
|
|
||||||
const asset = assets.find((a: any) => a.name === fileName);
|
|
||||||
if (!asset) {
|
|
||||||
throw new Error(`No asset found matching the exact name: ${fileName}`);
|
|
||||||
}
|
|
||||||
return asset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterByRegex(assets: ReleaseAsset[], pattern: string, dryRun?: boolean): ReleaseAsset[] {
|
||||||
|
const regex = new RegExp(pattern, 'i');
|
||||||
|
const filtered = assets.filter((asset) => regex.test(asset.name));
|
||||||
|
if (dryRun) {
|
||||||
|
console.debug(`Filtering assets with regex: ${pattern}`);
|
||||||
|
debugAssetList('Assets:', assets);
|
||||||
|
debugAssetList('Matched assets:', filtered);
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replacePlatformPlaceholders(pattern: string, platform: PlatformInfo): string {
|
||||||
|
return pattern
|
||||||
|
.replace(/{{SYSTEM}}/g, platform.systemPattern)
|
||||||
|
.replace(/{{ARCH}}/g, platform.archPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMatchingAsset(assets: ReleaseAsset[], platform: PlatformInfo, fileName?: string, fileType?: string, dryRun?: boolean): ReleaseAsset {
|
||||||
|
// Filename provided as literal string (no ~): exact match.
|
||||||
|
if (fileName && !fileName.startsWith('~')) {
|
||||||
|
const exactMatches = assets.filter((asset) => asset.name === fileName);
|
||||||
|
if (exactMatches.length !== 1) {
|
||||||
|
throw new Error(`Expected exactly one asset to match the provided filename, matched: ${exactMatches.length}`);
|
||||||
|
}
|
||||||
|
return exactMatches[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filetype filtering stage (or passthrough when not provided).
|
||||||
|
let fileTypeFilteredAssets: ReleaseAsset[] = assets;
|
||||||
|
if (fileType) {
|
||||||
|
if (Object.hasOwn(knownFileTypes, fileType)) {
|
||||||
|
// 2. Known fileType key: use predefined glob.
|
||||||
|
const fileTypeGlob = knownFileTypes[fileType];
|
||||||
|
fileTypeFilteredAssets = assets.filter((asset) => minimatch(asset.name, fileTypeGlob, { nocase: true }));
|
||||||
|
} else if (fileType.startsWith('~')) {
|
||||||
|
// 3. Custom regex fileType: match regex at end of string.
|
||||||
|
const fileTypeRegex = `${fileType.substring(1)}$`;
|
||||||
|
fileTypeFilteredAssets = filterByRegex(assets, fileTypeRegex, dryRun);
|
||||||
|
} else {
|
||||||
|
// 4. Custom extension fileType: treat as plain extension glob.
|
||||||
|
const extension = fileType.replace(/^\./, '');
|
||||||
|
const fileTypeGlob = `*.${extension}`;
|
||||||
|
fileTypeFilteredAssets = assets.filter((asset) => minimatch(asset.name, fileTypeGlob, { nocase: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Filename provided with ~: platform placeholder expansion and regex filtering.
|
||||||
|
if (fileName && fileName.startsWith('~')) {
|
||||||
|
const fileNamePattern = replacePlatformPlaceholders(fileName.substring(1), platform);
|
||||||
|
const fileNameFilteredAssets = filterByRegex(fileTypeFilteredAssets, fileNamePattern, dryRun);
|
||||||
|
if (fileNameFilteredAssets.length !== 1) {
|
||||||
|
throw new Error(`Expected exactly one asset to match the filename regex, matched: ${fileNameFilteredAssets.length}`);
|
||||||
|
}
|
||||||
|
return fileNameFilteredAssets[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. No filename: use default {{SYSTEM}}-{{ARCH}} regex.
|
||||||
|
const defaultPattern = replacePlatformPlaceholders('{{SYSTEM}}[_-]{{ARCH}}', platform);
|
||||||
|
const defaultFilteredAssets = filterByRegex(fileTypeFilteredAssets, defaultPattern, dryRun);
|
||||||
|
|
||||||
|
// 6. Zero or multiple matches are errors.
|
||||||
|
if (defaultFilteredAssets.length !== 1) {
|
||||||
|
const errorMessage = defaultFilteredAssets.length === 0
|
||||||
|
? `No assets matched the default criteria: ${defaultPattern}`
|
||||||
|
: `Multiple assets matched the default criteria: ${defaultFilteredAssets.map((asset) => asset.name).join(', ')}`;
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
return defaultFilteredAssets[0];
|
||||||
|
}
|
||||||
|
|||||||
+10
-5
@@ -7,6 +7,11 @@ export interface PlatformInfo {
|
|||||||
archPattern: string;
|
archPattern: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlatformOverrides {
|
||||||
|
system?: string;
|
||||||
|
arch?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const systemPatterns: Record<string, string> = {
|
export const systemPatterns: Record<string, string> = {
|
||||||
linux: 'linux',
|
linux: 'linux',
|
||||||
darwin: '(darwin|macos|mac|osx)',
|
darwin: '(darwin|macos|mac|osx)',
|
||||||
@@ -14,13 +19,13 @@ export const systemPatterns: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const archPatterns: Record<string, string> = {
|
export const archPatterns: Record<string, string> = {
|
||||||
x64: '(x86_64|x64|amd64)',
|
x64: '(x86_64|x64|amd64|universal)',
|
||||||
arm64: '(aarch64|arm64)'
|
arm64: '(aarch64|arm64|universal)'
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getPlatformInfo(): PlatformInfo {
|
export function getPlatformInfo(overrides?: PlatformOverrides): PlatformInfo {
|
||||||
const system = os.platform();
|
const system = (overrides?.system || os.platform()).toLowerCase();
|
||||||
const arch = os.arch();
|
const arch = (overrides?.arch || os.arch()).toLowerCase();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
system,
|
system,
|
||||||
|
|||||||
+50
-10
@@ -1,19 +1,50 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import * as tc from '@actions/tool-cache';
|
import * as tc from '@actions/tool-cache';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as os from 'os';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import { spawnSync } from 'child_process';
|
||||||
import { getPlatformInfo } from './core/platform';
|
import { getPlatformInfo } from './core/platform';
|
||||||
import { getMatchingAsset } from './core/matcher';
|
import { getMatchingAsset } from './core/matcher';
|
||||||
import { findBinary } from './core/finder';
|
import { findBinary } from './core/finder';
|
||||||
import { fetchLatestRelease } from './core/downloader';
|
import { fetchLatestRelease } from './core/downloader';
|
||||||
|
import { installSystemPackage } from './core/installer';
|
||||||
|
|
||||||
|
function findInstalledBinary(binaryName: string): string | undefined {
|
||||||
|
|
||||||
|
if (!binaryName.startsWith('~')) {
|
||||||
|
binaryName = `~^${binaryName}$`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirs = [
|
||||||
|
'/usr/local/bin',
|
||||||
|
'/usr/local/sbin',
|
||||||
|
'/usr/bin',
|
||||||
|
'/usr/sbin',
|
||||||
|
'/bin',
|
||||||
|
'/sbin',
|
||||||
|
path.join(process.env.HOME || '/', 'bin'),
|
||||||
|
'/opt/homebrew/bin'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const d of dirs) {
|
||||||
|
if (!fs.existsSync(d)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const p = findBinary(d, binaryName, false, () => undefined);
|
||||||
|
if (p) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
const repository = core.getInput('repository', { required: true });
|
const repository = core.getInput('repository', { required: true });
|
||||||
const fileNameInput = core.getInput('file-name');
|
const fileNameInput = core.getInput('file-name');
|
||||||
const binaryInput = core.getInput('binary-name');
|
const binaryInput = core.getInput('binary-name');
|
||||||
const fileType = core.getInput('file-type') || 'archive';
|
const fileType = core.getInput('file-type');
|
||||||
const updateCache = core.getInput('update-cache') || 'false';
|
const updateCache = core.getInput('update-cache') || 'false';
|
||||||
const debug = core.getBooleanInput('debug');
|
const debug = core.getBooleanInput('debug');
|
||||||
const token = core.getInput('token') || process.env.GITHUB_TOKEN;
|
const token = core.getInput('token') || process.env.GITHUB_TOKEN;
|
||||||
@@ -39,10 +70,7 @@ async function run() {
|
|||||||
|
|
||||||
core.info(`Fetching latest release information for ${repository}...`);
|
core.info(`Fetching latest release information for ${repository}...`);
|
||||||
const release = await fetchLatestRelease(repository, token);
|
const release = await fetchLatestRelease(repository, token);
|
||||||
const asset = getMatchingAsset(release.assets, platformInfo, {
|
const asset = getMatchingAsset(release.assets, platformInfo, fileNameInput, fileType);
|
||||||
fileName: fileNameInput,
|
|
||||||
fileType: fileType
|
|
||||||
});
|
|
||||||
|
|
||||||
core.info(`Selected asset: ${asset.name}`);
|
core.info(`Selected asset: ${asset.name}`);
|
||||||
|
|
||||||
@@ -67,6 +95,21 @@ async function run() {
|
|||||||
const nameLower = asset.name.toLowerCase();
|
const nameLower = asset.name.toLowerCase();
|
||||||
let toolDir: string;
|
let toolDir: string;
|
||||||
|
|
||||||
|
if (/\.(deb|pkg|rpm)$/i.test(nameLower)) {
|
||||||
|
core.info(`Installing package asset ${asset.name}...`);
|
||||||
|
installSystemPackage(downloadPath);
|
||||||
|
|
||||||
|
const binaryPath = findInstalledBinary(binaryName);
|
||||||
|
if (!binaryPath) {
|
||||||
|
throw new Error(`Package installed, but binary "${binaryName}" could not be located in common executable paths.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryDir = path.dirname(binaryPath);
|
||||||
|
core.addPath(binaryDir);
|
||||||
|
core.info(`Binary found at ${binaryPath}. Added ${binaryDir} to PATH.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine extraction method based on extension
|
// Determine extraction method based on extension
|
||||||
if (/\.(tar\.gz|tar|tgz)$/i.test(nameLower)) {
|
if (/\.(tar\.gz|tar|tgz)$/i.test(nameLower)) {
|
||||||
toolDir = await tc.extractTar(downloadPath);
|
toolDir = await tc.extractTar(downloadPath);
|
||||||
@@ -92,16 +135,13 @@ async function run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find the binary within the extracted/prepared directory
|
// Find the binary within the extracted/prepared directory
|
||||||
let binaryPattern: string | RegExp;
|
|
||||||
if (binaryName.startsWith('~')) {
|
if (binaryName.startsWith('~')) {
|
||||||
binaryPattern = new RegExp(binaryName.substring(1), 'i');
|
|
||||||
core.info(`Searching for binary matching regex: ${binaryName.substring(1)}`);
|
core.info(`Searching for binary matching regex: ${binaryName.substring(1)}`);
|
||||||
} else {
|
} else {
|
||||||
binaryPattern = binaryName;
|
|
||||||
core.info(`Searching for binary named: ${binaryName}`);
|
core.info(`Searching for binary named: ${binaryName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const binaryPath = findBinary(toolDir, binaryPattern, debug, (msg) => core.info(msg));
|
const binaryPath = findBinary(toolDir, binaryName, debug, (msg) => core.info(msg));
|
||||||
if (!binaryPath) {
|
if (!binaryPath) {
|
||||||
throw new Error(`Could not find binary "${binaryName}" in the extracted asset.`);
|
throw new Error(`Could not find binary "${binaryName}" in the extracted asset.`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
|
"types": ["node"],
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user