Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [Unreleased]

- Implement `psdk-use version`, `commit`, `mr`, and `latest` sub-commands.

## [0.1.0] - 2025-11-12

- Initial release
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ GEM
PLATFORMS
arm64-darwin-24
ruby
x64-mingw-ucrt

DEPENDENCIES
fileutils (~> 1.5.0)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ gem install psdk-cli

## Usage

In a terminal you can run `psdk-cli` it will list all the available commands.
In a terminal you can run `psdk-cli` it will list all the available commands.

Please note that sub command (such as `psdk-cli plugin`) can be executed using the shortcut `psdk-<sub>` (eg. `psdk-plugin` for `psdk-cli plugin`).

Expand Down
24 changes: 12 additions & 12 deletions lib/psdk/cli/use.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,32 @@ def studio
PSDK.unuse_local_pokemonsdk(delete: options[:delete])
end

desc 'version PSDK_VERSION', 'make the project use a specific PSDK version'
desc 'version [PSDK_VERSION]', 'make the project use a specific PSDK version'
def version(psdk_version)
ensure_project
# TODO: ensure pokemonsdk is in the project, checkout the specific version commit (if found)
puts psdk_version
require_relative '../helpers/psdk'
PSDK.use_version(psdk_version)
end

desc 'commit SHA1', 'make the project use a specific PSDK commit'
desc 'commit [SHA1]', 'make the project use a specific PSDK commit'
def commit(sha1)
ensure_project
# TODO: ensure pokemonsdk is in the project, checkout the specific commit
puts sha1
require_relative '../helpers/psdk'
PSDK.use_commit(sha1)
end

desc 'mr URL', 'make the project use a specific MR'
def mr(url)
desc 'mr [ID]', 'make the project use a specific MR'
def mr(id)
ensure_project
# TODO: ensure pokemonsdk is in the project, checkout the specific commit from the MR
# (ensuring remotes are configured)
puts url
require_relative '../helpers/psdk'
PSDK.use_mr(id)
end

desc 'latest', 'make the project use the latest PSDK commit from development'
def latest
ensure_project
# TODO: ensure pokemonsdk is in the project, checkout development and pull
require_relative '../helpers/psdk'
PSDK.use_latest
end

private
Expand Down
104 changes: 103 additions & 1 deletion lib/psdk/helpers/psdk.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
module Psdk
module Cli
# Module holding all the utility to interact with PSDK repository
module PSDK
module PSDK # rubocop:disable Metrics/ModuleLength
# Default URL to the PSDK repository
MAIN_REPOSITORY_URL = 'https://gitlab.com/pokemonsdk/pokemonsdk.git'

Expand All @@ -29,6 +29,47 @@ def repository_path
return File.join(Configuration::PATH, 'pokemonsdk')
end

# Make the project use an official PSDK release
# @param version [String] release identifier (e.g., "26.58")
def use_version(version)
switch_project_repository("official release #{version}") do |path|
fetch_repository(path)
commit = run_git(path, 'log', '--format=%H', '--extended-regexp',
"--grep=^Release #{Regexp.escape(version)}$", '--max-count=1', 'origin/release')
raise "PSDK release #{version} was not found" if commit.empty?

run_git(path, 'checkout', '--detach', commit)
end
end

# Make the project use a specific PSDK commit
# @param commit [String] commit identifier (e.g., "deadcafe")
def use_commit(commit)
switch_project_repository("commit #{commit}") do |path|
fetch_repository(path)
resolved_commit = run_git(path, 'rev-parse', '--verify', '--end-of-options', "#{commit}^{commit}")
run_git(path, 'checkout', '--detach', resolved_commit)
end
end

# Make the project use the head of a GitLab merge request
# @param id [String] merge request ID (e.g., "123")
def use_mr(id)
switch_project_repository("merge request !#{id}") do |path|
ref = "refs/remotes/origin/merge-requests/#{id}"
run_git(path, 'fetch', 'origin', "+refs/merge-requests/#{id}/head:#{ref}")
run_git(path, 'checkout', '-B', "mr-#{id}", ref)
end
end

# Make the project use the latest development commit
def use_latest
switch_project_repository('latest development commit') do |path|
fetch_repository(path)
run_git(path, 'checkout', '-B', 'development', 'origin/development')
end
end

# Unuse the local pokemonsdk folder (meaning we want the project to fallback on Pokémon Studio's PSDK)
# @param delete [Boolean] if the folder should be deleted
def unuse_local_pokemonsdk(delete:)
Expand Down Expand Up @@ -107,6 +148,67 @@ def rename_pokemonsdk_folder(psdk_path)
File.rename(psdk_path, new_path)
end
end

# Run a repository switch: resolve the project's pokemonsdk path, apply the given block,
# then refresh submodules and report the active target
# @param target [String] human-readable description of the switch target, used in the confirmation message
# @yieldparam path [String] path to the project's pokemonsdk repository
def switch_project_repository(target)
path = ensure_project_repository
yield(path)
run_git(path, 'submodule', 'update', '--init', '--recursive')
show_active_target(path, target)
rescue StandardError => e
show_switch_error(e)
end

# Ensure the project's pokemonsdk repository exists, cloning it if necessary
# @return [String] path to the project's pokemonsdk repository
def ensure_project_repository
path = File.join(Configuration.project_path, 'pokemonsdk')
return path if File.exist?(File.join(path, '.git'))

raise "#{path} exists but is not a Git repository" if Dir.exist?(path)

success = system('git', 'clone', MAIN_REPOSITORY_URL, path)
raise "Failed to clone pokemonsdk into `#{path}`" unless success

return path
end

# Fetch the latest refs from origin
# @param path [String] path to the repository
def fetch_repository(path)
run_git(path, 'fetch', 'origin')
end

# Run a git command in the given repository and return its output
# @param path [String] path to the repository
# @param arguments [Array<String>] git subcommand and its arguments
# @return [String] the stripped stdout of the command
def run_git(path, *arguments)
output = IO.popen(['git', *arguments], chdir: path, &:read)
return output.strip if $?.success? # rubocop:disable Style/SpecialGlobalVars

raise "git #{arguments.join(' ')} failed"
end

# Show the confirmation message for the currently active PSDK target
# @param path [String] path to the repository
# @param target [String] human-readable description of the active target
def show_active_target(path, target)
commit = run_git(path, 'rev-parse', '--short', 'HEAD')
version = File.read(File.join(path, 'version.txt')).to_i
version_string = [version].pack('I>').unpack('C*').join('.').gsub(/^(0\.)+/, '')
puts "Active PSDK: #{target} (version #{version_string}, commit #{commit})"
end

# Show the error message when a PSDK switch operation fails
# @param error [StandardError] the error that was raised
def show_switch_error(error)
puts "[Error] Failed to switch PSDK: #{error.message}"
exit(1)
end
end
end
end
22 changes: 14 additions & 8 deletions spec/psdk/cli/use_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
describe 'public methods' do
before do
allow(subject).to receive(:ensure_project)
# Suppress puts output for cleaner test runs
allow(subject).to receive(:puts)
allow(Psdk::Cli::PSDK).to receive(:use_version)
allow(Psdk::Cli::PSDK).to receive(:use_commit)
allow(Psdk::Cli::PSDK).to receive(:use_mr)
allow(Psdk::Cli::PSDK).to receive(:use_latest)
end

describe '#studio' do
Expand All @@ -42,28 +44,32 @@
end

describe '#version' do
it 'calls ensure_project' do
it 'targets the requested official release' do
expect(Psdk::Cli::PSDK).to receive(:use_version).with('24.15')
subject.version('24.15')
expect(subject).to have_received(:ensure_project)
end
end

describe '#commit' do
it 'calls ensure_project' do
subject.commit('sha1')
it 'targets the requested commit' do
expect(Psdk::Cli::PSDK).to receive(:use_commit).with('deadcafe')
subject.commit('deadcafe')
expect(subject).to have_received(:ensure_project)
end
end

describe '#mr' do
it 'calls ensure_project' do
subject.mr('url')
it 'targets the requested merge request' do
expect(Psdk::Cli::PSDK).to receive(:use_mr).with('42')
subject.mr('42')
expect(subject).to have_received(:ensure_project)
end
end

describe '#latest' do
it 'calls ensure_project' do
it 'targets the latest development commit' do
expect(Psdk::Cli::PSDK).to receive(:use_latest)
subject.latest
expect(subject).to have_received(:ensure_project)
end
Expand Down
68 changes: 68 additions & 0 deletions spec/psdk/helpers/psdk_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,74 @@
require 'psdk/helpers/psdk'

RSpec.describe Psdk::Cli::PSDK do # rubocop:disable Metrics/BlockLength
describe 'project version switching' do # rubocop:disable Metrics/BlockLength
let(:psdk_path) { '/path/to/project/pokemonsdk' }

before do
allow(described_class).to receive(:ensure_project_repository).and_return(psdk_path)
allow(described_class).to receive(:fetch_repository)
allow(described_class).to receive(:run_git)
allow(described_class).to receive(:show_active_target)
end

it 'checks out the release commit and refreshes submodules' do
expect(described_class).to receive(:run_git).with(
psdk_path, 'log', '--format=%H', '--extended-regexp', '--grep=^Release 26\\.58$',
'--max-count=1', 'origin/release'
).and_return('release-sha')
expect(described_class).to receive(:run_git).with(psdk_path, 'checkout', '--detach', 'release-sha')
expect(described_class).to receive(:run_git).with(psdk_path, 'submodule', 'update', '--init', '--recursive')
expect(described_class).to receive(:show_active_target).with(psdk_path, 'official release 26.58')

described_class.use_version('26.58')
end

it 'resolves and checks out a specific commit' do
expect(described_class).to receive(:run_git).with(
psdk_path, 'rev-parse', '--verify', '--end-of-options', 'deadcafe^{commit}'
).and_return('full-sha')
expect(described_class).to receive(:run_git).with(psdk_path, 'checkout', '--detach', 'full-sha')
expect(described_class).to receive(:show_active_target).with(psdk_path, 'commit deadcafe')

described_class.use_commit('deadcafe')
end

it 'fetches an MR by ID and checks out its local branch' do
expect(described_class).to receive(:run_git).with(
psdk_path, 'fetch', 'origin',
'+refs/merge-requests/42/head:refs/remotes/origin/merge-requests/42'
)
expect(described_class).to receive(:run_git).with(
psdk_path, 'checkout', '-B', 'mr-42', 'refs/remotes/origin/merge-requests/42'
)
expect(described_class).to receive(:show_active_target).with(psdk_path, 'merge request !42')

described_class.use_mr('42')
end

it 'checks out the latest development commit' do
expect(described_class).to receive(:run_git).with(
psdk_path, 'checkout', '-B', 'development', 'origin/development'
)
expect(described_class).to receive(:show_active_target).with(psdk_path, 'latest development commit')

described_class.use_latest
end

it 'confirms the active version and commit' do
allow(described_class).to receive(:show_active_target).and_call_original
allow(described_class).to receive(:run_git).with(
psdk_path, 'rev-parse', '--short', 'HEAD'
).and_return('deadcafe')
allow(File).to receive(:read).with(File.join(psdk_path, 'version.txt')).and_return('6714')
expect(described_class).to receive(:puts).with(
'Active PSDK: official release 26.58 (version 26.58, commit deadcafe)'
)

described_class.send(:show_active_target, psdk_path, 'official release 26.58')
end
end

describe '.unuse_local_pokemonsdk' do # rubocop:disable Metrics/BlockLength
let(:project_path) { '/path/to/project' }
let(:psdk_path) { File.join(project_path, 'pokemonsdk') }
Expand Down
Loading