psdk-cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e1c68b5b558c65d5b9befc52c67bb126daa9375154ee17a0c51ba7f849061c3
4
- data.tar.gz: 915b045d732b8eb562aee73ed431a0fb3f5ee195a4676876ca68d9140ddb726e
3
+ metadata.gz: 6295fefe865cbe37d27bb3f32e56a66ed1ce9a69501eb18c974101003a961e1d
4
+ data.tar.gz: 7478cdb74640fa0e52aecefce1fc3445cfc6ec78bccd1f5a29263de93550d960
5
5
  SHA512:
6
- metadata.gz: a70f48b02327d705364a89464f49666969707cd62c90aa2ee2f87997443433b95f06d0274c060fa8544646f2e9f1dec02c87ec3e2460183503290c1a75fee61a
7
- data.tar.gz: b7501d4824fde0501759909b134bb9731f29705b57d9c3319dbf2f8d74674009bb60801534bf219415b658f65acdd22546f144bf9cbe6bc1f01b590e5d2663a3
6
+ metadata.gz: 477617bf0fbef65be4a5f4c9a3088cd882df1a0abd0169384740ea77c5552b687a6d9a8dde58dab53841e70c7564054f6987ca89b599d1180244c24e23915ab7
7
+ data.tar.gz: c11218c05638142d4f6b7e36ce0620508d424f141791d1466ac089aadb9125aa955766d7d59fdfc9ed7671b6eab37fbc93b10ad38343ecee7f9a5da6926bc205
@@ -0,0 +1,65 @@
1
+ ## Story
2
+
3
+ Source: [Github Issue #10](https://github.com/PokemonWorkshop/psdk-cli/issues/10)
4
+
5
+ ### Description
6
+
7
+ As a developer, I want to use `psdk-plugin` to manage my PSDK plugins (create, test, update) to simplify maintenance.
8
+
9
+ > [!NOTE]
10
+ > We'll remove the plugin command from PSDK (aside the one for loading the plugin) and psdk cli will be responsible of plugin management (including build).
11
+ > It'll accept both workflow:
12
+ > - Plugin inside a PSDK project (meaning the resources are expected to be at the root of the project and scripts at the root of the plugin)
13
+ > - Plugin outside a PSDK project (meaning both resources and scripts are expected to be at the root of the plugin)
14
+ > The definition file remain the same for both workflow
15
+
16
+ ### Acceptance criteria
17
+
18
+ - [ ] `psdk-plugin` exposes the same arguments as the original plugin manager.
19
+ - [ ] The command detects the project or switches to standalone mode.
20
+ - [ ] A clear message lists available actions (list, build).
21
+
22
+ ## Information about `PluginManager.rb`
23
+
24
+ This file depends on Yuki::VD (defined in `lib/psdk/helpers/900 Yuki__VD.rb`)
25
+
26
+ This file was taken out of Pokémon SDK and currently is only working in the context of a PSDK project.
27
+
28
+ In the psdk-cli I want the following commands:
29
+
30
+ - `PluginManager.start(:build, 'plugin_name')`
31
+ - `PluginManager.start(:list)`
32
+
33
+ The `PluginManager.start(:load)` should never work in the psdk-cli as it's supposed to run in a game not in the CLI.
34
+
35
+ Currently, the build process expects the following structure:
36
+
37
+ - `scripts/{plugin_name}/config.yml` (plugin configuration file)
38
+ - `scripts/{plugin_name}/script/**/*.rb` (scripts, automatic)
39
+ - `graphics/**/*.*` (added_file)
40
+ - `Data/**/*.*` (added_files)
41
+ - `audio/**/*.*` (added_files)
42
+
43
+ I want this process to be preserved if psdk-cli is running in a PSDK project (`Configuration.project_path != nil`). And if I'm not in a project (or a --no-psdk-project flag is set). I want the following structure so developing plugins is easier:
44
+
45
+ - `config.yml` (plugin configuration file)
46
+ - `scripts/**/*.rb` (scripts, automatic)
47
+ - `graphics/**/*.*` (added_file)
48
+ - `Data/**/*.*` (added_files)
49
+ - `audio/**/*.*` (added_files)
50
+
51
+ In this structure, it's assumed that we are inside `{plugin_name}` (if we use `.` as plugin directory we should skip the plugin_name argument of `PluginManager.start(:build)` since the name is also inside `config.yml`)
52
+
53
+ ## Task to perform
54
+
55
+ 1. Explode `PluginManager.rb` in several ruby scripts that are able to perform the following operations:
56
+ - `list` all the plugins of current project (should fail if not in a project)
57
+ - `build` a plugin (name is optional, if not provided and not in a project, it's `.`)
58
+ 2. Rewrite the Build command so it's
59
+ 1. Verbose (explaining each stuff it does and where it is)
60
+ 2. Able to work outside of a psdk project (should also state it's not in a PSDK project)
61
+ 3. Move the exploded PluginManager files to `lib/psdk/helpers/plugin-manager`
62
+ 4. Write a script in `lib/psdk/cli` to expose the `psdk-cli plugin` cli commands
63
+ 5. Write a `psdk-plugin` file in `exe` (so I can run `psdk-plugin` instead of `psdk-cli plugin`)
64
+ 6. Write unit tests in `spec` for Exploded PluginManager files
65
+ 7. Write unit tests in `spec` for the `psdk-cli plugin` commands
data/.solargraph.yml CHANGED
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  include:
3
- - Rakefile
4
- - Gemfile
5
- - "*.gemspec"
6
- - "**/*.rb"
3
+ - Rakefile
4
+ - Gemfile
5
+ - "*.gemspec"
6
+ - "**/*.rb"
7
7
  exclude:
8
- - spec/**/*
9
- - test/**/*
10
- - vendor/**/*
11
- - ".bundle/**/*"
8
+ - spec/**/*
9
+ - test/**/*
10
+ - vendor/**/*
11
+ - ".bundle/**/*"
12
12
  require: []
13
13
  domains: []
14
14
  reporters:
15
- - rubocop
16
- - require_not_found
15
+ - rubocop
16
+ # - require_not_found
17
17
  formatter:
18
18
  rubocop:
19
19
  cops: safe
@@ -21,5 +21,6 @@ formatter:
21
21
  only: []
22
22
  extra_args: []
23
23
  require_paths: []
24
- plugins: []
24
+ plugins:
25
+ - solargraph-rspec
25
26
  max_files: 5000
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
+ # Changelog
2
+
1
3
  ## [Unreleased]
2
4
 
5
+ - Implement `psdk-use version`, `commit`, `mr`, and `latest` sub-commands.
6
+
3
7
  ## [0.1.0] - 2025-11-12
4
8
 
5
9
  - Initial release
data/README.md CHANGED
@@ -12,7 +12,7 @@ gem install psdk-cli
12
12
 
13
13
  ## Usage
14
14
 
15
- In a terminal you can run `psdk-cli` it will list all the available commands.
15
+ In a terminal you can run `psdk-cli` it will list all the available commands.
16
16
 
17
17
  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`).
18
18
 
data/exe/psdk-use ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'psdk/cli/use'
6
+
7
+ Psdk::Cli::Use.start
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Psdk
6
+ module Cli
7
+ # Class holding the configuration of the Cli
8
+ class Configuration
9
+ # Filename of the project configuration
10
+ PROJECT_CONFIGURATION_FILENAME = '.psdk-cli.yml'
11
+
12
+ # Path where all the global configuration are stored
13
+ PATH = File.join(ENV.fetch('PSDK_CLI_DIR', Dir.home || ENV['USERPROFILE'] || '~'), '.psdk-cli')
14
+
15
+ # Filename of the global configuration
16
+ GLOBAL_CONFIGURATION_FILENAME = File.join(PATH, 'config.yml')
17
+
18
+ # Create a new configuration
19
+ # @param hash [Hash] configuration hash
20
+ def initialize(hash)
21
+ hash = {} unless hash.is_a?(Hash)
22
+ # @type [String]
23
+ @studio_path = ''
24
+ # @type [Array<String>]
25
+ @project_paths = []
26
+
27
+ self.studio_path = hash[:studio_path] if hash.key?(:studio_path)
28
+ self.project_paths = hash[:project_paths] if hash.key?(:project_paths)
29
+ end
30
+
31
+ # Get the Pokémon Studio path
32
+ # @return [String]
33
+ attr_reader :studio_path
34
+
35
+ # Set the Pokémon Studio path
36
+ # @param path [String]
37
+ def studio_path=(path)
38
+ unless Dir.exist?(path) || path.empty?
39
+ puts "[Error] Invalid studio_path at `#{path}`, this path does not exists"
40
+ return
41
+ end
42
+ # TODO: add check for locating psdk-binaries
43
+ @studio_path = path
44
+ end
45
+
46
+ # Get the project paths
47
+ # @return [Array<String>]
48
+ attr_reader :project_paths
49
+
50
+ # Set the project_paths
51
+ # @param paths [Array<String>]
52
+ def project_paths=(paths)
53
+ unless paths.is_a?(Array)
54
+ puts '[Error] project_paths is not an array'
55
+ return
56
+ end
57
+ unless paths.all? { |path| path.is_a?(String) }
58
+ puts '[Error] some of the project paths are not path'
59
+ return
60
+ end
61
+
62
+ @project_paths = paths
63
+ end
64
+
65
+ def to_h
66
+ return {
67
+ studio_path: @studio_path,
68
+ project_paths: @project_paths
69
+ }
70
+ end
71
+
72
+ class << self
73
+ @global = nil
74
+ @local = nil
75
+
76
+ # Get the project path
77
+ # @return [String | nil]
78
+ attr_reader :project_path
79
+
80
+ # Get the configuration
81
+ # @param type [:global | :local]
82
+ # @return [Configuration]
83
+ def get(type)
84
+ @global ||= Configuration.new(load_hash(GLOBAL_CONFIGURATION_FILENAME))
85
+ return @global if type == :global
86
+
87
+ project_path = find_project_path
88
+ @local = nil if @project_path != project_path
89
+ @project_path = project_path
90
+ @local ||= Configuration.new(
91
+ @global.to_h.merge(load_hash(File.join(*@project_path, PROJECT_CONFIGURATION_FILENAME)))
92
+ )
93
+
94
+ return @local
95
+ end
96
+
97
+ # Save the configuration
98
+ def save
99
+ make_config_global_dir
100
+ File.write(GLOBAL_CONFIGURATION_FILENAME, YAML.dump(@global.to_h))
101
+ return unless @local && @project_path
102
+
103
+ local_configuration = @local.to_h
104
+ # Delete global configuration keys
105
+ local_configuration.delete(:project_paths)
106
+
107
+ File.write(File.join(@project_path, PROJECT_CONFIGURATION_FILENAME), YAML.dump(local_configuration))
108
+ end
109
+
110
+ private
111
+
112
+ def make_config_global_dir
113
+ dirname = File.dirname(GLOBAL_CONFIGURATION_FILENAME)
114
+ return if Dir.exist?(dirname)
115
+
116
+ Dir.mkdir(dirname)
117
+ end
118
+
119
+ def load_hash(path)
120
+ return {} unless File.exist?(path)
121
+
122
+ return YAML.load_file(path, symbolize_names: true, freeze: true)
123
+ rescue StandardError => e
124
+ puts "[Error] failed to load configuration (#{e.message})"
125
+ puts e.backtrace
126
+ return {}
127
+ end
128
+
129
+ # Get the project path
130
+ # @return [String | nil]
131
+ def find_project_path
132
+ current_path = Dir.pwd.gsub('\\', '/').split('/')
133
+ all_options = current_path.size.downto(2).map { |i| File.join(*current_path[0...i]) }
134
+ return all_options.find { |path| File.exist?(File.join(path, 'project.studio')) }
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -8,10 +8,39 @@ module Psdk
8
8
  class Plugin < Thor
9
9
  package_name 'psdk-plugin'
10
10
 
11
- # TODO: remove this and actually implement the plugin cli
12
- desc 'fake ARG1', 'run a fake command with ARG1 as first argument'
13
- def fake(arg1)
14
- puts "Fake called with ARG1=#{arg1}"
11
+ desc 'list', 'List all installed plugins in the current PSDK project'
12
+ def list
13
+ require_relative 'configuration'
14
+ require_relative '../helpers/plugin_manager'
15
+ Psdk::Cli::Configuration.get(:local)
16
+
17
+ unless Psdk::Cli::Configuration.project_path
18
+ puts 'Error: You must be inside a PSDK project to use `psdk-plugin list`.'
19
+ exit(1)
20
+ end
21
+
22
+ PluginManager.list
23
+ end
24
+
25
+ desc 'build [PLUGIN_NAME]', 'Build a plugin (requires to be at the root of the project or the plugin)'
26
+ option :no_psdk_project, type: :boolean, desc: 'Force standalone mode (build outside a PSDK project)'
27
+ option :out_dir, type: :string, default: '.', desc: 'Output directory for the generated .psdkplug file'
28
+ def build(plugin_name = nil) # rubocop:disable Metrics/MethodLength
29
+ require_relative 'configuration'
30
+ require_relative '../helpers/plugin_manager'
31
+ Psdk::Cli::Configuration.get(:local)
32
+
33
+ in_project = Psdk::Cli::Configuration.project_path
34
+ in_project = false if options[:no_psdk_project]
35
+
36
+ plugin_name = '.' if !in_project && plugin_name.nil?
37
+
38
+ unless plugin_name
39
+ puts 'Error: You must provide a plugin_name when building inside a PSDK project.'
40
+ exit(1)
41
+ end
42
+
43
+ PluginManager.build(plugin_name, in_project: in_project, out_dir: options[:out_dir])
15
44
  end
16
45
  end
17
46
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Psdk
6
+ module Cli
7
+ # Class responsible of handling the psdk-use cli commands
8
+ class Use < Thor
9
+ package_name 'psdk-use'
10
+
11
+ desc 'studio [--delete]', 'make the project use Pokemon Studio PSDK version.'
12
+ method_option :delete, type: :boolean, aliases: '--delete',
13
+ desc: 'delete local pokemonsdk folder'
14
+ def studio
15
+ ensure_project
16
+ require_relative '../helpers/psdk'
17
+ PSDK.unuse_local_pokemonsdk(delete: options[:delete])
18
+ end
19
+
20
+ desc 'version [PSDK_VERSION]', 'make the project use a specific PSDK version'
21
+ def version(psdk_version)
22
+ ensure_project
23
+ require_relative '../helpers/psdk'
24
+ PSDK.use_version(psdk_version)
25
+ end
26
+
27
+ desc 'commit [SHA1]', 'make the project use a specific PSDK commit'
28
+ def commit(sha1)
29
+ ensure_project
30
+ require_relative '../helpers/psdk'
31
+ PSDK.use_commit(sha1)
32
+ end
33
+
34
+ desc 'mr [ID]', 'make the project use a specific MR'
35
+ def mr(id)
36
+ ensure_project
37
+ require_relative '../helpers/psdk'
38
+ PSDK.use_mr(id)
39
+ end
40
+
41
+ desc 'latest', 'make the project use the latest PSDK commit from development'
42
+ def latest
43
+ ensure_project
44
+ require_relative '../helpers/psdk'
45
+ PSDK.use_latest
46
+ end
47
+
48
+ private
49
+
50
+ def ensure_project
51
+ require_relative 'configuration'
52
+ Configuration.get(:local)
53
+ return if Configuration.project_path
54
+
55
+ $stderr.puts 'Not in a project'
56
+ exit(1)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Psdk
4
4
  module Cli
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.1'
6
6
  end
7
7
  end
data/lib/psdk/cli.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'thor'
4
4
  require_relative 'cli/version'
5
5
  require_relative 'cli/plugin'
6
+ require_relative 'cli/use'
6
7
 
7
8
  module Psdk
8
9
  module Cli
@@ -12,13 +13,25 @@ module Psdk
12
13
  class Main < Thor
13
14
  package_name 'psdk-cli'
14
15
 
15
- desc('version', 'show the psdk-cli version')
16
+ desc 'version', 'show the psdk-cli version'
17
+ method_option :no_psdk_version, type: :boolean, aliases: '--no-psdk-version',
18
+ desc: 'do not search and show PSDK version'
16
19
  def version
17
- puts "psdk-cli v#{VERSION}"
20
+ require_relative 'helpers/version'
21
+ Version.run(options[:no_psdk_version])
22
+ end
23
+
24
+ desc 'update', 'update the psdk-cli'
25
+ def update
26
+ require_relative 'helpers/version_update'
27
+ VersionUpdate.check_and_update
18
28
  end
19
29
 
20
30
  desc 'plugin', 'manage PSDK plugins'
21
31
  subcommand 'plugin', Plugin
32
+
33
+ desc 'use', 'manage project\'s PSDK version'
34
+ subcommand 'use', Use
22
35
  end
23
36
  end
24
37
  end
@@ -0,0 +1,147 @@
1
+ # rubocop:disable all
2
+ module Yuki
3
+ # Class that helps to read Virtual Directories
4
+ #
5
+ # In reading mode, the Virtual Directories can be loaded to RAM if MAX_SIZE >= VD.size
6
+ #
7
+ # All the filenames inside the Yuki::VD has to be downcased filename in utf-8
8
+ #
9
+ # Note : Encryption is up to the developper and no longer supported on the basic script
10
+ class VD
11
+ # @return [String] the filename of the current Yuki::VD
12
+ attr_reader :filename
13
+
14
+ # Is the debug info on ?
15
+ DEBUG_ON = ARGV.include?('debug-yuki-vd')
16
+ # The max size of the file that can be loaded in memory
17
+ MAX_SIZE = 10 * 1024 * 1024 # 10Mo
18
+ # List of allowed modes
19
+ ALLOWED_MODES = %i[read write update]
20
+ # Size of the pointer at the begin of the file
21
+ POINTER_SIZE = 4
22
+ # Unpack method of the pointer at the begin of the file
23
+ UNPACK_METHOD = 'L'
24
+ # Create a new Yuki::VD file or load it
25
+ # @param filename [String] name of the Yuki::VD file
26
+ # @param mode [:read, :write, :update] if we read or write the virtual directory
27
+ def initialize(filename, mode)
28
+ @mode = mode = fix_mode(mode)
29
+ @filename = filename
30
+ send("initialize_#{mode}")
31
+ end
32
+
33
+ # Read a file data from the VD
34
+ # @param filename [String] the file we want to read its data
35
+ # @return [String, nil] the data of the file
36
+ def read_data(filename)
37
+ return nil unless @file
38
+
39
+ pos = @hash[filename]
40
+ return nil unless pos
41
+
42
+ @file.pos = pos
43
+ size = @file.read(POINTER_SIZE).unpack1(UNPACK_METHOD)
44
+ return @file.read(size)
45
+ end
46
+
47
+ # Test if a file exists in the VD
48
+ # @param filename [String]
49
+ # @return [Boolean]
50
+ def exists?(filename)
51
+ @hash[filename] != nil
52
+ end
53
+
54
+ # Write a file with its data in the VD
55
+ # @param filename [String] the file name
56
+ # @param data [String] the data of the file
57
+ def write_data(filename, data)
58
+ return unless @file
59
+
60
+ @hash[filename] = @file.pos
61
+ @file.write([data.bytesize].pack(UNPACK_METHOD))
62
+ @file.write(data)
63
+ end
64
+
65
+ # Add a file to the Yuki::VD
66
+ # @param filename [String] the file name
67
+ # @param ext_name [String, nil] the file extension
68
+ def add_file(filename, ext_name = nil)
69
+ sub_filename = ext_name ? "#{filename}.#{ext_name}" : filename
70
+ write_data(filename, File.binread(sub_filename))
71
+ end
72
+
73
+ # Get all the filename
74
+ # @return [Array<String>]
75
+ def get_filenames
76
+ @hash.keys
77
+ end
78
+
79
+ # Close the VD
80
+ def close
81
+ return unless @file
82
+
83
+ if @mode != :read
84
+ pos = [@file.pos].pack(UNPACK_METHOD)
85
+ @file.write(Marshal.dump(@hash))
86
+ @file.pos = 0
87
+ @file.write(pos)
88
+ end
89
+ @file.close
90
+ @file = nil
91
+ end
92
+
93
+ private
94
+
95
+ # Initialize the Yuki::VD in read mode
96
+ def initialize_read
97
+ @file = File.new(filename, 'rb')
98
+ pos = @file.pos = @file.read(POINTER_SIZE).unpack1(UNPACK_METHOD)
99
+ @hash = Marshal.load(@file)
100
+ load_whole_file(pos) if pos < MAX_SIZE
101
+ rescue Errno::ENOENT
102
+ @file = nil
103
+ @hash = {}
104
+ log_error(format('%<filename>s not found', filename: filename)) if DEBUG_ON
105
+ end
106
+
107
+ # Load the VD in the memory
108
+ # @param size [Integer] size of the VD memory
109
+ def load_whole_file(size)
110
+ @file.pos = 0
111
+ data = @file.read(size)
112
+ @file.close
113
+ @file = StringIO.new(data, 'rb+')
114
+ @file.pos = 0
115
+ end
116
+
117
+ # Initialize the Yuki::VD in write mode
118
+ def initialize_write
119
+ @file = File.new(filename, 'wb')
120
+ @file.pos = POINTER_SIZE
121
+ @hash = {}
122
+ end
123
+
124
+ # Initialize the Yuki::VD in update mode
125
+ def initialize_update
126
+ @file = File.new(filename, 'rb+')
127
+ pos = @file.pos = @file.read(POINTER_SIZE).unpack1(UNPACK_METHOD)
128
+ @hash = Marshal.load(@file)
129
+ @file.pos = pos
130
+ end
131
+
132
+ # Fix the input mode in case it's a String
133
+ # @param mode [Symbol, String]
134
+ # @return [Symbol] one of the value of ALLOWED_MODES
135
+ def fix_mode(mode)
136
+ return mode if ALLOWED_MODES.include?(mode)
137
+
138
+ r = (mode = mode.downcase).include?('r')
139
+ w = mode.include?('w')
140
+ plus = mode.include?('+')
141
+ return :update if plus || (r && w)
142
+ return :read if r
143
+
144
+ return :write
145
+ end
146
+ end
147
+ end