mirror of
https://github.com/Xevion/spotify-quickauth.git
synced 2025-12-06 09:16:32 -06:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0db9b6a795 | |||
| 3b8e27d9a4 | |||
| 31b5e6ee8b | |||
| 20c863e660 | |||
| 0d0fe8a9e5 | |||
| 3fb288f404 | |||
| 8e33506f61 | |||
| 48b0101dab | |||
| f583dac944 | |||
| dbfad44a51 | |||
| a636c6301a | |||
| 30d135a4dc | |||
| 62fb74a153 | |||
| c3e15fd9a5 | |||
| 0cc92b8978 | |||
| 2e15d81dc9 | |||
| 28faf71583 | |||
| 9ef2aac601 | |||
| 7e7af5920b | |||
| 978b0ab264 | |||
| a4568b2e72 | |||
| 7eaea3e2f4 | |||
| 22606aad30 | |||
| 70fd9da886 | |||
| 50de07ef28 | |||
| f8da0a647b | |||
| acf81f6bf6 | |||
| 2866c2fe4a | |||
| 98fc3c9902 | |||
| c52edc3640 | |||
| 3921dbda29 | |||
| 469c27aca0 | |||
| 6cea7fce2f | |||
| 115166c438 | |||
| 86820fba36 | |||
| 7e89218e31 | |||
| 398004daca | |||
| 87b36ec3f2 | |||
| fea498eb18 | |||
| 6a144177ac | |||
| 2d81bdcea5 | |||
| 3389a7f370 | |||
| 8fa4d010a3 | |||
| 500bf75f8f | |||
| d533a4318e | |||
| 6599f58933 | |||
| ca97ae2e15 | |||
| 18b010762e | |||
| 1e484dc3b9 | |||
| 04908d6682 | |||
| c5ebbb6d26 | |||
| c04dd7f6d9 | |||
| b63e033234 | |||
| c8ba73a3f2 | |||
| 15fca3adf0 | |||
| c284ca3c3a | |||
| f7a7a8185f | |||
| c544fe4802 | |||
| 6e7c6bbb66 |
@@ -1,6 +1,3 @@
|
|||||||
[target.x86_64-unknown-linux-musl]
|
|
||||||
runner = "musl-gcc"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
strip = true
|
strip = true
|
||||||
|
|||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.hooks/*.sh linguist-vendored
|
||||||
104
.github/workflows/build.yaml
vendored
104
.github/workflows/build.yaml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Build
|
name: build
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -7,27 +7,29 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
fail_fast:
|
fail_fast:
|
||||||
description: 'Fail fast strategy'
|
description: "Use fail-fast strategy"
|
||||||
required: false
|
required: false
|
||||||
default: 'true'
|
default: "true"
|
||||||
verbose:
|
verbose:
|
||||||
description: 'Verbose output'
|
description: "Verbose output"
|
||||||
required: false
|
required: false
|
||||||
default: 'false'
|
default: "false"
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- "v*.*.*"
|
||||||
paths-ignore:
|
# We don't filter by paths here, because we filter by tags, which means we're releasing. All releases should be built.
|
||||||
- README.md
|
|
||||||
- .gitignore
|
|
||||||
- LICENSE
|
|
||||||
- run.sh
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
|
- .hooks/**
|
||||||
|
- CARGO_README.md
|
||||||
- README.md
|
- README.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
- INTEGRATION.md
|
||||||
- .gitignore
|
- .gitignore
|
||||||
- LICENSE
|
- .gitattributes
|
||||||
|
- LICENSE*
|
||||||
- run.sh
|
- run.sh
|
||||||
|
- run.ps1
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
@@ -36,7 +38,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: ${{ startsWith(github.ref, 'refs/tags/') || github.event.inputs.fail_fast == 'true' }}
|
fail-fast: ${{ startsWith(github.ref, 'refs/tags/') || github.event.inputs.fail_fast == 'true' }}
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
@@ -72,7 +74,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: 1.79
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Cache Rust dependencies
|
- name: Cache Rust dependencies
|
||||||
@@ -86,7 +88,7 @@ jobs:
|
|||||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-${{ matrix.target }}-
|
${{ runner.os }}-${{ matrix.target }}-
|
||||||
|
|
||||||
- name: Install Linker Tools
|
- name: Install Linker Tools
|
||||||
if: matrix.tools
|
if: matrix.tools
|
||||||
run: |
|
run: |
|
||||||
@@ -109,17 +111,30 @@ jobs:
|
|||||||
cargo test ${{ github.event.inputs.verbose == 'true' && '--verbose' || '' }} --release --target ${{ matrix.target }}
|
cargo test ${{ github.event.inputs.verbose == 'true' && '--verbose' || '' }} --release --target ${{ matrix.target }}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Export Release Version
|
- name: Prepare Variables
|
||||||
run: echo "RELEASE_VERSION=$(grep '^version = ' Cargo.toml | sed 's/version = \"\(.*\)\"/\1/')" >> $GITHUB_ENV
|
shell: bash
|
||||||
shell: sh
|
run: |
|
||||||
|
RELEASE_VERSION=$(grep '^version = ' Cargo.toml | sed 's/version = \"\(.*\)\"/\1/')
|
||||||
|
ARCHIVE=spotify-quickauth-v$RELEASE_VERSION-${{ matrix.target }}
|
||||||
|
if ${{ contains(matrix.os, 'windows') }}; then
|
||||||
|
ARCHIVE=$ARCHIVE.zip
|
||||||
|
else
|
||||||
|
ARCHIVE=$ARCHIVE.tar.gz
|
||||||
|
fi
|
||||||
|
ARCHIVE_DIR=target/${{ matrix.target }}/release
|
||||||
|
|
||||||
|
# Export variables
|
||||||
|
echo "ARCHIVE_DIR=$ARCHIVE_DIR" >> $GITHUB_ENV
|
||||||
|
echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV
|
||||||
|
echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Archive
|
- name: Archive
|
||||||
if: ${{ !contains(matrix.os, 'windows') }}
|
if: ${{ !contains(matrix.os, 'windows') }}
|
||||||
uses: TheDoctor0/zip-release@0.7.6
|
uses: TheDoctor0/zip-release@0.7.6
|
||||||
with:
|
with:
|
||||||
type: tar
|
type: tar
|
||||||
filename: spotify-quickauth-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}.tar.gz
|
filename: ${{ env.ARCHIVE }}
|
||||||
directory: target/${{ matrix.target }}/release/
|
directory: ${{ env.ARCHIVE_DIR }}/
|
||||||
path: |
|
path: |
|
||||||
spotify-quickauth
|
spotify-quickauth
|
||||||
|
|
||||||
@@ -128,11 +143,32 @@ jobs:
|
|||||||
uses: TheDoctor0/zip-release@0.7.6
|
uses: TheDoctor0/zip-release@0.7.6
|
||||||
with:
|
with:
|
||||||
type: zip
|
type: zip
|
||||||
filename: spotify-quickauth-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}.zip
|
filename: ${{ env.ARCHIVE }}
|
||||||
directory: target/${{ matrix.target }}/release/
|
directory: ${{ env.ARCHIVE_DIR }}/
|
||||||
path: |
|
path: |
|
||||||
spotify-quickauth.exe
|
spotify-quickauth.exe
|
||||||
|
|
||||||
|
- name: Install rsign2
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: rsign2
|
||||||
|
|
||||||
|
- name: Sign Archive
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
env:
|
||||||
|
MINISIGN_KEY: ${{ secrets.MINISIGN_KEY }}
|
||||||
|
ARCHIVE_PATH: ${{ env.ARCHIVE_DIR }}/${{ env.ARCHIVE }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "$MINISIGN_KEY" > minisign.key
|
||||||
|
|
||||||
|
ts=$(node -e 'console.log((new Date).toISOString())')
|
||||||
|
git=$(git rev-parse HEAD)
|
||||||
|
comment="gh=$GITHUB_REPOSITORY git=$git ts=$ts run=$GITHUB_RUN_ID"
|
||||||
|
|
||||||
|
rsign sign -W -s minisign.key -x "${{ env.ARCHIVE_PATH }}.sig" -t "$comment" "${{ env.ARCHIVE_PATH }}"
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -140,19 +176,21 @@ jobs:
|
|||||||
name: spotify-quickauth-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}
|
name: spotify-quickauth-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
path: target/${{ matrix.target }}/release/spotify-quickauth-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}.*
|
path: ${{ env.ARCHIVE_DIR }}/${{ env.ARCHIVE }}
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
|
token: ${{ secrets.GH_RELEASE_TOKEN }}
|
||||||
files: |
|
files: |
|
||||||
target/${{ matrix.target }}/release/spotify-quickauth-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}.*
|
${{ env.ARCHIVE_DIR }}/${{ env.ARCHIVE }}
|
||||||
|
${{ env.ARCHIVE_DIR }}/${{ env.ARCHIVE }}.sig
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|
||||||
# Publish flow
|
- if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.target == 'x86_64-unknown-linux-musl' }}
|
||||||
# - name: cargo login
|
run: cargo login ${{ secrets.CRATES_IO_API_TOKEN }}
|
||||||
# run: cargo login ${{ secrets.CRATES_IO_API_TOKEN }}
|
|
||||||
|
- name: "Publish"
|
||||||
# - name: "cargo release publish"
|
if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.target == 'x86_64-unknown-linux-musl' }}
|
||||||
# run: cargo release publish --workspace --all-features --allow-branch HEAD --no-confirm --no-verify --execute
|
run: cargo publish --locked --allow-dirty
|
||||||
|
|||||||
10
.github/workflows/integration.yaml
vendored
10
.github/workflows/integration.yaml
vendored
@@ -1,11 +1,11 @@
|
|||||||
name: Integration
|
name: integration
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
workflow_run:
|
# workflow_run:
|
||||||
workflows: ["Build"]
|
# workflows: ["Build"]
|
||||||
types:
|
# types:
|
||||||
- completed
|
# - completed
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
integration-windows:
|
integration-windows:
|
||||||
|
|||||||
2
.github/workflows/pages.yaml
vendored
2
.github/workflows/pages.yaml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: GitHub Pages
|
name: github-pages
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pages: write
|
pages: write
|
||||||
|
|||||||
98
.github/workflows/test.yaml
vendored
Normal file
98
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- .hooks/**
|
||||||
|
- CARGO_README.md
|
||||||
|
- README.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
- INTEGRATION.md
|
||||||
|
- .gitignore
|
||||||
|
- .gitattributes
|
||||||
|
- LICENSE*
|
||||||
|
- run.sh
|
||||||
|
- run.ps1
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- .hooks/**
|
||||||
|
- CARGO_README.md
|
||||||
|
- README.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
- INTEGRATION.md
|
||||||
|
- .gitignore
|
||||||
|
- .gitattributes
|
||||||
|
- LICENSE*
|
||||||
|
- run.sh
|
||||||
|
- run.ps1
|
||||||
|
schedule:
|
||||||
|
- cron: "30 14 * * 1" # every Monday, 9:30 AM CDT
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
tools: musl-tools
|
||||||
|
artifact: true
|
||||||
|
- os: macos-13
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
# - os: macos-latest
|
||||||
|
# target: aarch64-apple-darwin
|
||||||
|
- os: windows-latest
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
name: test-${{ matrix.os }}-${{ matrix.target }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
toolchain: 1.79
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Install Linker Tools
|
||||||
|
if: matrix.tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install ${{ matrix.tools }}
|
||||||
|
# ensure has a newline at the end
|
||||||
|
[ "$(tail -c 1 .cargo/config.toml)" != "" ] && echo >> .cargo/config.toml
|
||||||
|
cat .cargo/config.github.toml >> .cargo/config.toml
|
||||||
|
|
||||||
|
- name: Cache Rust dependencies
|
||||||
|
uses: actions/cache@v4.0.2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry/index
|
||||||
|
~/.cargo/registry/cache
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: testing-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
testing-${{ runner.os }}-${{ matrix.target }}-
|
||||||
|
testing-${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --verbose --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
if: matrix.artifact
|
||||||
|
run: cargo build --verbose --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Upload Artifact
|
||||||
|
if: matrix.artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: spotify-quickauth-${{ github.sha }}-${{ matrix.target }}
|
||||||
|
path: target/${{ matrix.target }}/debug/spotify-quickauth
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1 +1,6 @@
|
|||||||
/target
|
/target
|
||||||
|
*.key
|
||||||
|
*.tar.gz
|
||||||
|
*.zip
|
||||||
|
credentials.json
|
||||||
|
spotify-quickauth
|
||||||
|
|||||||
10
.hooks/link.sh
vendored
Executable file
10
.hooks/link.sh
vendored
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
hooks=(
|
||||||
|
"pre-commit"
|
||||||
|
"pre-push"
|
||||||
|
)
|
||||||
|
|
||||||
|
for hook in "${hooks[@]}"; do
|
||||||
|
chmod +x .hooks/$hook.sh
|
||||||
|
ln -s -f ../../.hooks/$hook.sh .git/hooks/$hook
|
||||||
|
done
|
||||||
10
.hooks/pre-commit.sh
vendored
Executable file
10
.hooks/pre-commit.sh
vendored
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# TODO: Only run when README.md is modified
|
||||||
|
# TODO: Support partial staging, where the 'Staged' version is copied to CARGO_README.md, and then `sed` is ran.
|
||||||
|
|
||||||
|
# Remove markdown extension lines for Cargo publishing
|
||||||
|
sed '/^>\[!/d' README.md > CARGO_README.md
|
||||||
|
|
||||||
|
# Add the modified file to the commit
|
||||||
|
git add CARGO_README.md
|
||||||
96
.hooks/pre-push.sh
vendored
Executable file
96
.hooks/pre-push.sh
vendored
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to be run as part of the github pre-push hook.
|
||||||
|
#
|
||||||
|
# Checks that, if there is a "version-like" tag being pushed, all the files which are supposed to contain the tag do
|
||||||
|
# actually have the correct tag value in them. If they do not, the push is blocked.
|
||||||
|
# NB: this does _not_ automatically alter the source files and commit them with the correct tag value, nor prevent the
|
||||||
|
# tag to be added to the wrong git commit locally (ie. a commit in which the source files have the wrong tag value).
|
||||||
|
# All it does is prevent the developer from pushing the 'bad tags' to remote repositories, giving him/her the chance to
|
||||||
|
# manually rectify the situation on the local repo before retrying to push.
|
||||||
|
#
|
||||||
|
# @todo could this be run as pre-commit hook instead? We have to test if adding a tag does trigger pre-commit hook...
|
||||||
|
# @see https://stackoverflow.com/questions/56538621/git-hook-to-check-tag-name
|
||||||
|
# @see https://stackoverflow.com/questions/8418071/is-there-a-way-to-check-that-a-git-tag-matches-the-content-of-the-corresponding
|
||||||
|
# for an alternative take (enforcing this with a server-side hook)
|
||||||
|
#
|
||||||
|
# NB: remember that this can be run within a windows env too, via fe. the tortoisegit or the git-4-win on the cli!
|
||||||
|
# git for windows comes with its own copy of common unix utils such as bash, grep. But they are sometimes old and/or
|
||||||
|
# buggy compared to what one gets in current linux distros :-(
|
||||||
|
#
|
||||||
|
# This hook is called with the following parameters:
|
||||||
|
#
|
||||||
|
# $1 -- Name of the remote to which the push is being done
|
||||||
|
# $2 -- URL to which the push is being done
|
||||||
|
#
|
||||||
|
# If pushing without using a named remote those arguments will be equal.
|
||||||
|
#
|
||||||
|
# Information about the commits which are being pushed is supplied as lines to
|
||||||
|
# the standard input in the form:
|
||||||
|
#
|
||||||
|
# <local ref> <local oid> <remote ref> <remote oid>
|
||||||
|
|
||||||
|
# We do not abort the push in case there is an error in this script. No `set -e`
|
||||||
|
#set -e
|
||||||
|
|
||||||
|
# @todo detect if this is run outside git hook, and give a warning plus explain how to pass in $local_ref $local_oid $remote_ref $remote_oid
|
||||||
|
# @todo allow a git config parameter to switch on/off a 'verbose mode'
|
||||||
|
# @todo we could allow the variables `files` and `version_tag_regexp` to be set via git config parameters instead of hardcoded
|
||||||
|
|
||||||
|
# List of files which do contain the version tag
|
||||||
|
files='Cargo.toml'
|
||||||
|
|
||||||
|
# Regexp use to decide if a git tag is a version label
|
||||||
|
version_tag_regexp='^[0-9]{1,4}\.[0-9]{1,4}(\.[0-9]{1,4})?'
|
||||||
|
|
||||||
|
# Create a string of '0' chars of appropriate length for the current git version
|
||||||
|
zero="$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')"
|
||||||
|
|
||||||
|
echo "Checking commits for version tags before push..."
|
||||||
|
|
||||||
|
# check all commits which we are pushing
|
||||||
|
while read local_ref local_oid remote_ref remote_oid; do
|
||||||
|
#echo "Checking commit $local_oid ..."
|
||||||
|
# skip ref deletions
|
||||||
|
if [ "$local_oid" != "$zero" ]; then
|
||||||
|
#if [ "$remote_oid" = "$zero" ]; then
|
||||||
|
# # 'new branch'
|
||||||
|
# range="$local_oid"
|
||||||
|
#else
|
||||||
|
# # 'update to existing branch'
|
||||||
|
# range="$remote_oid..$local_oid"
|
||||||
|
#fi
|
||||||
|
# @todo in case we have a range (see commented out code 2 lines above), should we check more commits?
|
||||||
|
tags="$(git tag --points-at $local_oid)"
|
||||||
|
if [ -n "$tags" ]; then
|
||||||
|
# @todo this will not work predictably if there are 2 version tags attached to the same commit. Which probably
|
||||||
|
# there should not be anyway. Should we check for that too and abort in case?
|
||||||
|
while IFS= read -r tag; do
|
||||||
|
echo "Found tag: '$tag'..."
|
||||||
|
if [[ "$tag" =~ $version_tag_regexp ]]; then
|
||||||
|
echo "Tag looks like a version number. Checking if code is matching..."
|
||||||
|
for file in $files; do
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
echo "File is missing: '$file'. Please fix config of github hook script"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "Looking for '$tag' in '$file'"
|
||||||
|
# @todo atm if the version tag is f.e. v1.1, any file containing the string "clamav1.10' will
|
||||||
|
# match. We should improve this match to avoid such scenarios
|
||||||
|
# Note: we can not use `-i` as it crashes git-4-win's grep
|
||||||
|
if grep -F -q "$tag" "$file"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
echo "Tag is missing from file '$file'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "All files ok!"
|
||||||
|
break 2; # exit from both while loops: no need to check for further tags
|
||||||
|
fi
|
||||||
|
done <<< "$tags"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 0
|
||||||
130
CARGO_README.md
Normal file
130
CARGO_README.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
> [!IMPORTANT]
|
||||||
|
> This application is not 'real'; I mostly created it to learn about building a CLI application in Rust with high-end CI/CD pipelines and easy to execute multi-platform scripts.
|
||||||
|
> Interestingly enough, I never really developed the program itself, just the scripts and the CI/CD pipeline.
|
||||||
|
> And by the time I was ready to write it, the `spotify-player` project had already solved the [primary issue](https://github.com/aome510/spotify-player/issues/201#issuecomment-2439565162) I was trying to address.
|
||||||
|
> Thus, I'm archiving this project, as it's no longer necessary.
|
||||||
|
>
|
||||||
|
> This project is still pretty cool though in it's own right, with multi-platform ephemeral binaries over `curl` commands, Windows support, Linux MUSL targets (smaller binaries), ARM64 MacOS support, binary deployment to GitHub pages, automatic GitHub release uploads, and a pretty decent README - this project has it all.
|
||||||
|
|
||||||
|
# spotify-quickauth
|
||||||
|
|
||||||
|
[](https://github.com/Xevion/spotify-quickauth/actions)
|
||||||
|
[](https://github.com/Xevion/spotify-quickauth/actions)
|
||||||
|
[](https://crates.io/crates/spotify-quickauth)
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
<!-- TODO: Add testing status badge -->
|
||||||
|
|
||||||
|
A simple CLI-based application for creating a `credentials.json` file, used by `librespot` derived applications, such as [spotify-player][spotify-player], [spotifyd][spotifyd], and [raspotify][raspotify].
|
||||||
|
|
||||||
|
- One command, no compilation, all platforms (Windows, Linux, MacOS), ARM included
|
||||||
|
- Automatically places configuration files
|
||||||
|
- No dependencies, no installation, no fuss
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
You can run this application without installing anything by using the following commands.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://xevion.github.io/spotify-quickauth/run.sh | sh -s --
|
||||||
|
```
|
||||||
|
|
||||||
|
The default invocation is likely fine for most users, it will try to understand the available paths for `credentials.json` to be written to, and allow you to select them.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Automatic detection is dependent on the related software being installed and/or relevant configuration files being present.
|
||||||
|
|
||||||
|
For **Windows**, you can paste this command into PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
iex (irm "https://xevion.github.io/spotify-quickauth/run.ps1")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This application is dead simple to use. Just run the command, and it'll tell you to connect to a fake 'device' in your Spotify interface.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> You must be connected to the same network running `spotify-quickauth`, as the `zeroconf` technology **does not work** across **networks** nor **proxies**.
|
||||||
|
|
||||||
|
Once you connect, the credentials file will be created, and you'll be prompted to select which location(s) to place it in. Even if none of the relevant `librespot` applications are detected or installed, you can specify manual locations, or the current working directory.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Installation is not necessary to use this application, but if you're having trouble, want to compile it yourself, or are using it frequently, you might want to install it.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The scripts above can be given the `-K` or `--keep` flag to keep the downloaded binary. This will prevent repeated API calls to GitHub if you're using the script frequently within a short period.
|
||||||
|
|
||||||
|
### Pre-built Binaries
|
||||||
|
|
||||||
|
Binaries are always available for download from the [releases page][latestRelease], and they're the same ones used by the shell scripts above.
|
||||||
|
|
||||||
|
Currently, the following targets are available for download:
|
||||||
|
|
||||||
|
- x64 Linux (MUSL) `x86_64-unknown-linux-musl`
|
||||||
|
- ARM64 Linux (MUSL) `aarch64-unknown-linux-musl`
|
||||||
|
- ARMv7 Linux `armv7-unknown-linux-musleabihf`
|
||||||
|
- Intel MacOS `x86_64-apple-darwin`
|
||||||
|
- Apple Silicon MacOS `aarch64-apple-darwin`
|
||||||
|
- x64 Windows `x86_64-pc-windows-msvc`
|
||||||
|
- ARM64 Windows `aarch64-pc-windows-msvc`
|
||||||
|
|
||||||
|
Please [file an issue][new-issue] if you are on a platform that is not supported, or if you encounter issues with the binaries.
|
||||||
|
|
||||||
|
### Via `cargo-binstall`
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If the package cannot be found for your target or fails to be downloaded for any reason, `cargo-binstall` will automatically fall back to building the package from source.
|
||||||
|
|
||||||
|
`cargo-binstall` is a tool that allows you to install binaries from crates.io without needing to compile them yourself.
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo binstall spotify-quickauth
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're curious where the binary comes from, `cargo-binstall` will likely pull the binary directly from the [latest release][latestRelease] by this repository, selecting the most appropriate target for your host.
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
If you'd like to use the shell script above to install the binary, you can use the `-S/--stop` flag to prevent the script from running the binary after downloading it. It implicitly applies the `--keep` flag too.
|
||||||
|
|
||||||
|
You'll need to move the binary yourself though:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://xevion.github.io/spotify-quickauth/run.sh | sh -s -- -S
|
||||||
|
mv spotify-quickauth /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure your directory of choice is in your $PATH though!
|
||||||
|
|
||||||
|
### Building from Source
|
||||||
|
|
||||||
|
Don't want to run my funky shell script? No problem! You can build the application from source easily.
|
||||||
|
|
||||||
|
- You'll need `cargo`, the Rust build system and package manager. It's included with the Rust toolchain, which you can install from [rustup.rs][rustup]
|
||||||
|
- This is an early project, so the minimum supported version of Rust is not known. I'm developing on 1.81.0 though.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Xevion/spotify-quickauth.git
|
||||||
|
cd spotify-quickauth
|
||||||
|
cargo install --path .
|
||||||
|
spotify-quickauth --help
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have any troubles building the project
|
||||||
|
|
||||||
|
- Make sure you're using a target that's supported by the project (see above).
|
||||||
|
- Certain targets may require specfic linkers. For example,
|
||||||
|
|
||||||
|
[latestRelease]: https://github.com/Xevion/spotify-quickauth/releases/latest/
|
||||||
|
[spotify-player]: https://github.com/aome510/spotify-player
|
||||||
|
[spotifyd]: https://github.com/Spotifyd/spotifyd
|
||||||
|
[raspotify]: https://github.com/dtcooper/raspotify
|
||||||
|
[rustup]: https://rustup.rs
|
||||||
|
[git]: https://git-scm.com
|
||||||
|
[binstall]: https://github.com/cargo-bins/cargo-binstall
|
||||||
|
[quickinstall]: https://github.com/cargo-bins/cargo-quickinstall
|
||||||
|
[binstall-installation]: https://github.com/cargo-bins/cargo-binstall#installation
|
||||||
|
[new-issue]: https://github.com/Xevion/spotify-quickauth/issues/new
|
||||||
37
CHANGELOG.md
Normal file
37
CHANGELOG.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## v0.1.7
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Archival notice.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Bumped MSRV to 1.79 to access stabilized `std::path::absolute`
|
||||||
|
- Adjusted `github-linguist` statistics, ignoring `.hooks`
|
||||||
|
- Widened `paths-ignore` for `build` and `test` workflows.
|
||||||
|
- Switched release step token to use `secrets.GH_RELEASE_TOKEN`, so the author is bound to `Xevion` instead of `github-actions` (a bot).
|
||||||
|
- Switched changelog format to `keep-a-changelog` style.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed `paths-ignore` for `build` workflow on `push` trigger.
|
||||||
|
|
||||||
|
## v0.1.6
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Began tracking changes in the `CHANGELOG.md` file.
|
||||||
|
- Added testing workflow to the repository, targeting only major x64 platforms.
|
||||||
|
- Added testing badge to README
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- [breaking] Lowered MSRV to 1.74 to match `librespot`'s `0.5.0-dev` branch.
|
||||||
|
- Fixed `pre-commit` hook to properly process the `CARGO_README.md` file.
|
||||||
1843
Cargo.lock
generated
1843
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
@@ -1,11 +1,32 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "spotify-quickauth"
|
name = "spotify-quickauth"
|
||||||
version = "0.1.1"
|
version = "0.1.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
description = "Quickly authenticate librespot-based applications with Spotify"
|
||||||
|
rust-version = "1.79"
|
||||||
|
authors = ["Ryan Walters <xevion@xevion.dev>"]
|
||||||
|
homepage = "https://github.com/Xevion/spotify-quickauth"
|
||||||
|
repository = "https://github.com/Xevion/spotify-quickauth"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
readme = "CARGO_README.md"
|
||||||
|
|
||||||
|
[package.metadata.binstall]
|
||||||
|
pkg-url = "{ repo }/releases/download/v{ version }/spotify-quickauth-v{ version }-{ target }{ archive-suffix }"
|
||||||
|
pkg-format = "tgz"
|
||||||
|
|
||||||
|
[package.metadata.binstall.signing]
|
||||||
|
algorithm = "minisign"
|
||||||
|
pubkey = "RWSPQt0a5mbpICWV2Ft8iKOPv1F5xvknUeoZo38sKGUxOKbJmHUZE6UC"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||||
|
pkg-fmt = "zip"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.aarch64-pc-windows-msvc]
|
||||||
|
pkg-fmt = "zip"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
librespot-discovery = { git = "https://github.com/librespot-org/librespot", branch = "dev" }
|
librespot-discovery = { version = "0.4.2" }
|
||||||
librespot-core = { git = "https://github.com/librespot-org/librespot", branch = "dev" }
|
librespot-core = { version = "0.4.2" }
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
tokio = { version = "1.40.0", features = ["full"] }
|
tokio = { version = "1.40.0", features = ["full"] }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
|||||||
176
LICENSE-APACHE
Normal file
176
LICENSE-APACHE
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
23
LICENSE-MIT
Normal file
23
LICENSE-MIT
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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.
|
||||||
113
README.md
113
README.md
@@ -1,24 +1,105 @@
|
|||||||
|
> [!IMPORTANT]
|
||||||
|
> This application is not 'real'; I mostly created it to learn about building a CLI application in Rust with high-end CI/CD pipelines and easy to execute multi-platform scripts.
|
||||||
|
> Interestingly enough, I never really developed the program itself, just the scripts and the CI/CD pipeline.
|
||||||
|
> And by the time I was ready to write it, the `spotify-player` project had already solved the [primary issue](https://github.com/aome510/spotify-player/issues/201#issuecomment-2439565162) I was trying to address.
|
||||||
|
> Thus, I'm archiving this project, as it's no longer necessary.
|
||||||
|
>
|
||||||
|
> This project is still pretty cool though in it's own right, with multi-platform ephemeral binaries over `curl` commands, Windows support, Linux MUSL targets (smaller binaries), ARM64 MacOS support, binary deployment to GitHub pages, automatic GitHub release uploads, and a pretty decent README - this project has it all.
|
||||||
|
|
||||||
# spotify-quickauth
|
# spotify-quickauth
|
||||||
|
|
||||||
A simple CLI-based application for creating a `credentials.json` file, used by the [spotify-player][spotify-player] library, for authenticating with the Spotify API.
|
[](https://github.com/Xevion/spotify-quickauth/actions)
|
||||||
|
[](https://github.com/Xevion/spotify-quickauth/actions)
|
||||||
|
[](https://crates.io/crates/spotify-quickauth)
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## Usage
|
<!-- TODO: Add testing status badge -->
|
||||||
|
|
||||||
You can install this application, but most people will just need it once. The following commands will run the application without installing it.
|
A simple CLI-based application for creating a `credentials.json` file, used by `librespot` derived applications, such as [spotify-player][spotify-player], [spotifyd][spotifyd], and [raspotify][raspotify].
|
||||||
|
|
||||||
For Linux and macOS, you can paste this command into your terminal:
|
- One command, no compilation, all platforms (Windows, Linux, MacOS), ARM included
|
||||||
|
- Automatically places configuration files
|
||||||
|
- No dependencies, no installation, no fuss
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
You can run this application without installing anything by using the following commands.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sSL https://xevion.github.io/spotify-quickauth/run.sh | sh
|
curl -sSL https://xevion.github.io/spotify-quickauth/run.sh | sh -s --
|
||||||
```
|
```
|
||||||
|
|
||||||
For Windows, you can paste this command into PowerShell:
|
The default invocation is likely fine for most users, it will try to understand the available paths for `credentials.json` to be written to, and allow you to select them.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Automatic detection is dependent on the related software being installed and/or relevant configuration files being present.
|
||||||
|
|
||||||
|
For **Windows**, you can paste this command into PowerShell:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
iex (irm "https://xevion.github.io/spotify-quickauth/run.ps1")
|
iex (irm "https://xevion.github.io/spotify-quickauth/run.ps1")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building from Source
|
## Usage
|
||||||
|
|
||||||
|
This application is dead simple to use. Just run the command, and it'll tell you to connect to a fake 'device' in your Spotify interface.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> You must be connected to the same network running `spotify-quickauth`, as the `zeroconf` technology **does not work** across **networks** nor **proxies**.
|
||||||
|
|
||||||
|
Once you connect, the credentials file will be created, and you'll be prompted to select which location(s) to place it in. Even if none of the relevant `librespot` applications are detected or installed, you can specify manual locations, or the current working directory.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Installation is not necessary to use this application, but if you're having trouble, want to compile it yourself, or are using it frequently, you might want to install it.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The scripts above can be given the `-K` or `--keep` flag to keep the downloaded binary. This will prevent repeated API calls to GitHub if you're using the script frequently within a short period.
|
||||||
|
|
||||||
|
### Pre-built Binaries
|
||||||
|
|
||||||
|
Binaries are always available for download from the [releases page][latestRelease], and they're the same ones used by the shell scripts above.
|
||||||
|
|
||||||
|
Currently, the following targets are available for download:
|
||||||
|
|
||||||
|
- x64 Linux (MUSL) `x86_64-unknown-linux-musl`
|
||||||
|
- ARM64 Linux (MUSL) `aarch64-unknown-linux-musl`
|
||||||
|
- ARMv7 Linux `armv7-unknown-linux-musleabihf`
|
||||||
|
- Intel MacOS `x86_64-apple-darwin`
|
||||||
|
- Apple Silicon MacOS `aarch64-apple-darwin`
|
||||||
|
- x64 Windows `x86_64-pc-windows-msvc`
|
||||||
|
- ARM64 Windows `aarch64-pc-windows-msvc`
|
||||||
|
|
||||||
|
Please [file an issue][new-issue] if you are on a platform that is not supported, or if you encounter issues with the binaries.
|
||||||
|
|
||||||
|
### Via `cargo-binstall`
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If the package cannot be found for your target or fails to be downloaded for any reason, `cargo-binstall` will automatically fall back to building the package from source.
|
||||||
|
|
||||||
|
`cargo-binstall` is a tool that allows you to install binaries from crates.io without needing to compile them yourself.
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo binstall spotify-quickauth
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're curious where the binary comes from, `cargo-binstall` will likely pull the binary directly from the [latest release][latestRelease] by this repository, selecting the most appropriate target for your host.
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
If you'd like to use the shell script above to install the binary, you can use the `-S/--stop` flag to prevent the script from running the binary after downloading it. It implicitly applies the `--keep` flag too.
|
||||||
|
|
||||||
|
You'll need to move the binary yourself though:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://xevion.github.io/spotify-quickauth/run.sh | sh -s -- -S
|
||||||
|
mv spotify-quickauth /usr/local/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure your directory of choice is in your $PATH though!
|
||||||
|
|
||||||
|
### Building from Source
|
||||||
|
|
||||||
Don't want to run my funky shell script? No problem! You can build the application from source easily.
|
Don't want to run my funky shell script? No problem! You can build the application from source easily.
|
||||||
|
|
||||||
@@ -28,10 +109,22 @@ Don't want to run my funky shell script? No problem! You can build the applicati
|
|||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Xevion/spotify-quickauth.git
|
git clone https://github.com/Xevion/spotify-quickauth.git
|
||||||
cd spotify-quickauth
|
cd spotify-quickauth
|
||||||
cargo build --release
|
cargo install --path .
|
||||||
./target/release/spotify-quickauth
|
spotify-quickauth --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you have any troubles building the project
|
||||||
|
|
||||||
|
- Make sure you're using a target that's supported by the project (see above).
|
||||||
|
- Certain targets may require specfic linkers. For example,
|
||||||
|
|
||||||
|
[latestRelease]: https://github.com/Xevion/spotify-quickauth/releases/latest/
|
||||||
[spotify-player]: https://github.com/aome510/spotify-player
|
[spotify-player]: https://github.com/aome510/spotify-player
|
||||||
|
[spotifyd]: https://github.com/Spotifyd/spotifyd
|
||||||
|
[raspotify]: https://github.com/dtcooper/raspotify
|
||||||
[rustup]: https://rustup.rs
|
[rustup]: https://rustup.rs
|
||||||
[git]: https://git-scm.com
|
[git]: https://git-scm.com
|
||||||
|
[binstall]: https://github.com/cargo-bins/cargo-binstall
|
||||||
|
[quickinstall]: https://github.com/cargo-bins/cargo-quickinstall
|
||||||
|
[binstall-installation]: https://github.com/cargo-bins/cargo-binstall#installation
|
||||||
|
[new-issue]: https://github.com/Xevion/spotify-quickauth/issues/new
|
||||||
|
|||||||
28
run.sh
28
run.sh
@@ -1,5 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$OS" = "Windows_NT" ]; then
|
if [ "$OS" = "Windows_NT" ]; then
|
||||||
@@ -14,6 +13,23 @@ else
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Flags parsing
|
||||||
|
STOP=0
|
||||||
|
KEEP=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
-K|--keep)
|
||||||
|
# Keep the unzipped executable after running
|
||||||
|
KEEP=1
|
||||||
|
;;
|
||||||
|
-S|--stop)
|
||||||
|
# Don't run the executable after unzipping (also keeps it)
|
||||||
|
STOP=1
|
||||||
|
KEEP=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
# Fetch the latest release download URL
|
# Fetch the latest release download URL
|
||||||
REPO="Xevion/spotify-quickauth"
|
REPO="Xevion/spotify-quickauth"
|
||||||
API_URL="https://api.github.com/repos/$REPO/releases/latest"
|
API_URL="https://api.github.com/repos/$REPO/releases/latest"
|
||||||
@@ -29,8 +45,12 @@ fi
|
|||||||
|
|
||||||
EXECUTABLE="spotify-quickauth"
|
EXECUTABLE="spotify-quickauth"
|
||||||
curl -Lso $EXECUTABLE.tar.gz $DOWNLOAD_URL
|
curl -Lso $EXECUTABLE.tar.gz $DOWNLOAD_URL
|
||||||
tar -xvf $EXECUTABLE.tar.gz $EXECUTABLE
|
tar -xvf $EXECUTABLE.tar.gz $EXECUTABLE 1>/dev/null
|
||||||
rm $EXECUTABLE.tar.gz
|
rm $EXECUTABLE.tar.gz
|
||||||
chmod +x $EXECUTABLE
|
chmod +x $EXECUTABLE
|
||||||
trap "rm -f $EXECUTABLE" INT EXIT
|
if [ "$KEEP" -eq 0 ]; then
|
||||||
./$EXECUTABLE
|
trap "rm -f $EXECUTABLE" INT EXIT
|
||||||
|
fi
|
||||||
|
if [ "$STOP" -eq 0 ]; then
|
||||||
|
./$EXECUTABLE $@
|
||||||
|
fi
|
||||||
198
src/main.rs
198
src/main.rs
@@ -1,82 +1,208 @@
|
|||||||
use std::{env, fs::File, process::exit};
|
use std::{env, fs::File, path::PathBuf, process::exit};
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use librespot_core::config::DeviceType;
|
use librespot_core::config::DeviceType;
|
||||||
use librespot_discovery::Discovery;
|
use librespot_discovery::Discovery;
|
||||||
use log::{info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
use librespot_core::SessionConfig;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
force: bool,
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_arguments(args: &Vec<String>) -> Result<Arguments, String> {
|
||||||
|
let mut force: Option<bool> = None;
|
||||||
|
let mut path: Option<PathBuf> = None;
|
||||||
|
|
||||||
|
let mut skip = 1;
|
||||||
|
// If '--' is provided, all arguments before it are skipped
|
||||||
|
if let Some(index) = args.iter().position(|arg| arg == "--") {
|
||||||
|
skip = index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = &args[skip..];
|
||||||
|
debug!("Arguments: {:?}", args);
|
||||||
|
|
||||||
|
for arg in args.iter() {
|
||||||
|
match arg.as_str() {
|
||||||
|
"-f" | "--force" => {
|
||||||
|
if force.is_some() {
|
||||||
|
return Err("Force flag provided multiple times".to_string());
|
||||||
|
}
|
||||||
|
force = Some(true)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if path.is_some() {
|
||||||
|
return Err("Path provided multiple times".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the path, validate that the argument looks like a path
|
||||||
|
let parsed = PathBuf::from(arg);
|
||||||
|
debug!("Parsed path: {}", parsed.display());
|
||||||
|
|
||||||
|
if parsed.exists() {
|
||||||
|
if parsed.is_dir() {
|
||||||
|
path = Some(parsed.join("credentials.json"));
|
||||||
|
} else if parsed.is_file() {
|
||||||
|
if path.is_some() {
|
||||||
|
return Err("Path provided multiple times".to_string());
|
||||||
|
}
|
||||||
|
path = Some(parsed);
|
||||||
|
} else {
|
||||||
|
return Err("Path is not a file or directory".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// File does not exist, check if it looks like a directory
|
||||||
|
if parsed.ends_with("/") || parsed.ends_with("\\") {
|
||||||
|
// If the parent directory exists, it's okay to create a directory then the file in it
|
||||||
|
if parsed.parent().is_some_and(|p| p.exists()) {
|
||||||
|
path = Some(parsed.join("credentials.json"));
|
||||||
|
} else {
|
||||||
|
return Err(
|
||||||
|
"Cannot create more than one folder for output path".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No need to create a directory, just create the file
|
||||||
|
if parsed.parent().is_some_and(|p| p.exists()) {
|
||||||
|
path = Some(parsed);
|
||||||
|
} else {
|
||||||
|
return Err(
|
||||||
|
"Cannot create a file in a non-existent directory".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no path was provided, default to credentials.json in the current directory
|
||||||
|
let path = match path {
|
||||||
|
Some(p) => match std::path::absolute(p) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return Err(format!("Invalid path: {}", e)),
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// No path provided, try to identify a default in the current directory
|
||||||
|
let pwd = env::current_dir();
|
||||||
|
if pwd.is_err() {
|
||||||
|
// For some reason the current directory
|
||||||
|
return Err("Current directory is invalid or indeterminate, please provide an explicit output path".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to credentials.json in the current directory
|
||||||
|
pwd.unwrap().join("credentials.json")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the *file* already exists, check if the force flag was provided
|
||||||
|
if path.exists() && path.is_file() && !force.unwrap_or(false) {
|
||||||
|
return Err(format!(
|
||||||
|
"Output file already exists, use -f to overwrite ({})",
|
||||||
|
path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Arguments {
|
||||||
|
force: force.unwrap_or(false),
|
||||||
|
path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
// Initialize the logger
|
||||||
if env::var("RUST_LOG").is_err() {
|
if env::var("RUST_LOG").is_err() {
|
||||||
env::set_var("RUST_LOG", "info")
|
env::set_var("RUST_LOG", "info")
|
||||||
}
|
}
|
||||||
env_logger::builder().init();
|
env_logger::builder().init();
|
||||||
|
|
||||||
let credentials_file = match home::home_dir() {
|
// Parse the arguments
|
||||||
// ~/.cache/spotify_player/credentials.json
|
let args = match parse_arguments(&env::args().collect()) {
|
||||||
Some(path) => path.join(".cache/spotify_player/credentials.json"),
|
Ok(a) => a,
|
||||||
None => {
|
Err(e) => {
|
||||||
warn!("Cannot determine home directory for credentials file.");
|
error!("Error parsing arguments: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
info!("Credentials file: {}", &credentials_file.display());
|
|
||||||
|
|
||||||
// TODO: If credentials file exists, confirm overwrite
|
info!("Credentials file: {}", &args.path.display());
|
||||||
if credentials_file.exists() {
|
|
||||||
warn!("Credentials file already exists: {}", &credentials_file.display());
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: If spotifyd is running, ask if shutdown is desired
|
// TODO: If spotifyd is running, ask if shutdown is desired
|
||||||
|
|
||||||
let username = match env::consts::OS {
|
// Figure out the username
|
||||||
|
let mut username = match env::consts::OS {
|
||||||
"windows" => env::var("USERNAME"),
|
"windows" => env::var("USERNAME"),
|
||||||
_ => env::var("USER"),
|
_ => env::var("USER"),
|
||||||
}.unwrap_or_else(|_| "unknown".to_string());
|
}
|
||||||
|
// Trim whitespace from the username
|
||||||
|
.map(|u| u.trim().to_string())
|
||||||
|
.unwrap_or("unknown".to_string());
|
||||||
|
|
||||||
|
// Default the username to 'unknown' if it doesn't fit the expected format
|
||||||
|
if username != "unknown" {
|
||||||
|
let valid_characters = r"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
||||||
|
if match &username {
|
||||||
|
u if u.is_empty() => {
|
||||||
|
warn!("Cannot determine username, defaulting to 'unknown'");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
u if u.len() > 20 => {
|
||||||
|
warn!("Username is too long, defaulting to 'unknown'");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
u if u.len() < 2 => {
|
||||||
|
warn!("Username is too short, defaulting to 'unknown'");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
u if u.contains(|c| !valid_characters.contains(c)) => {
|
||||||
|
warn!("Username contains invalid characters, defaulting to 'unknown'");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
} {
|
||||||
|
username = "unknown".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the device metadata
|
||||||
let device_name = format!("spotify-quickauth-{}", username);
|
let device_name = format!("spotify-quickauth-{}", username);
|
||||||
let device_id = hex::encode(Sha1::digest(device_name.as_bytes()));
|
let device_id = hex::encode(Sha1::digest(device_name.as_bytes()));
|
||||||
let device_type = DeviceType::Computer;
|
let device_type = DeviceType::Computer;
|
||||||
|
|
||||||
let mut server = Discovery::builder(device_id, SessionConfig::default().client_id)
|
let mut server = Discovery::builder(device_id)
|
||||||
.name(device_name.clone())
|
.name(device_name.clone())
|
||||||
.device_type(device_type)
|
.device_type(device_type)
|
||||||
.launch()
|
.launch()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
println!("Open Spotify and select output device: {}", device_name);
|
info!("Open Spotify and select output device: {}", device_name);
|
||||||
|
|
||||||
let mut written = false;
|
|
||||||
while let Some(credentials) = server.next().await {
|
while let Some(credentials) = server.next().await {
|
||||||
let result = File::create(&credentials_file).and_then(|mut file| {
|
// Check if file exists
|
||||||
|
if args.path.exists() && !args.force {
|
||||||
|
warn!("Output file already exists (appeared after startup), use -f to overwrite");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the credentials to the file
|
||||||
|
let result = File::create(&args.path).and_then(|mut file| {
|
||||||
let data = serde_json::to_string(&credentials)?;
|
let data = serde_json::to_string(&credentials)?;
|
||||||
write!(file, "{data}")
|
write!(file, "{data}")
|
||||||
});
|
});
|
||||||
written = true;
|
|
||||||
|
|
||||||
|
// Check if the file was created successfully
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
warn!("Cannot save credentials to cache: {}", e);
|
warn!("Cannot save credentials to cache: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
} else {
|
} else {
|
||||||
println!("Credentials saved: {}", &credentials_file.display());
|
info!("Credentials saved: {}", &args.path.display());
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !written {
|
|
||||||
warn!("No credentials were written.");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod tests {}
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn test_nothing() {
|
|
||||||
assert_eq!(1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user