aspera-cli 4.25.5 → 4.25.6

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: a886cb3a02f285c84728b216f1cd906b8440db041a8240ed09ed1969d81ec6c3
4
- data.tar.gz: a05a7d9fc94a5d748c52d659426a18cf567a9e303231c86d1ec2312abc3465ae
3
+ metadata.gz: 8e8b3ed2906ef470b3617e60b438089f57c5b3d0e1770649e2ba1a64d413d32d
4
+ data.tar.gz: e09b6b5d455c77ea355ba617577b82a1c51e60afab2e8431e62dcf8d9e7785c6
5
5
  SHA512:
6
- metadata.gz: 87988d7b4252134e772c7c5420e59471cfb89355627cb3bf49fe6f5cf19382644856dfee3494adb3db2a879fc42b77f5754f8fee995eae8ca45229fc68ccd769
7
- data.tar.gz: ace70306ca85a12b17d12dcf146f280d313dcbef491576c265a5a20f70199d1c03ef3834cc62de30b20949f2cfdea39821b09b232c8bd826709f5622c7f631c3
6
+ metadata.gz: eb3f5becd29ffc54c7746868a47b3e8841cde1566abb0b5ea97d9c4bb7768f9f85c4cf96cbd3e52b6a1724f4be0eb817ea03282a4070e5cf32a9d06fb6a3aab5
7
+ data.tar.gz: f8f03f1a45d50b92e01d39f734bdebebf94e1640c695751a3122106797d9c4d223e544050cd854cb026daa736589440666d5ccd2453b50c9a39a6df5fadccc8e
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  <!-- markdownlint-configure-file { "no-duplicate-heading": { "siblings_only": true } } -->
4
4
 
5
+ ## 4.25.6
6
+
7
+ Released: 2026-04-08
8
+
9
+ ### Issues Fixed
10
+
11
+ * `node`: #253 fix `node transfer list` infinite loop with `once_only`.
12
+
5
13
  ## 4.25.5
6
14
 
7
15
  Released: 2026-03-25
@@ -14,9 +22,7 @@ Released: 2026-03-25
14
22
 
15
23
  * `config`: Fixed `preset update` with dot-path options.
16
24
  * `sync`: #251 in `admin` command: Fixed database path on Windows: Use `~private-asp` instead of `.private-asp`.
17
- * `httpgw`: #252 HTTPGW transfer showed success even in case of error code `403`. Now raises error with message.
18
-
19
- ### Breaking Changes
25
+ * `httpgw`: #252 HTTPGW transfer showed success even when error code `403` was returned. Now raises error with message.
20
26
 
21
27
  ## 4.25.4
22
28
 
@@ -115,7 +121,7 @@ Released: 2026-01-21
115
121
  ### Issues Fixed
116
122
 
117
123
  * `aoc`: Restored command `admin workspace shared_folder :id list` which was since 4.11.0.
118
- * `direct`: When using `ascp4` do not set env var: `ASPERA_TEST_REDIS_DISABLE`, else it fails with: `Failed to initialize application`.
124
+ * `direct`: When using `ascp4`, do not set env var: `ASPERA_TEST_REDIS_DISABLE`, else it fails with: `Failed to initialize application`.
119
125
  * `shares`: Properly detect using `/node_api/ping` endpoint instead of `/app`.
120
126
 
121
127
  ### Breaking Changes
@@ -194,7 +200,7 @@ Released: 2025-09-30
194
200
  * `console`: Removed options `filter_from` and `filter_to`. Use standard option `query` instead.
195
201
  * `sync`: Removed option `sync_info`. Replaced with positional parameters. Streamlined command line interface. Applies to all plugins with `sync` command.
196
202
  * `async`: Removed option `sync_name`. Replaced with percent selector `%name:`.
197
- * `aoc`: `files download` using gen4 API do not require anymore to provide the containing folder in first position, and then only file names. Now, directly provide the path to all files.
203
+ * `aoc`: `files download` using gen4 API no longer requires providing the containing folder in first position, and then only file names. Now, directly provide the path to all files.
198
204
  * `logger`: Log is simplified, date is removed by default. Use `--log_format=standard` to revert to standard Ruby logger. See option `log_format` for details.
199
205
 
200
206
  ## 4.23.0
@@ -208,7 +214,7 @@ Released: 2025-08-11
208
214
 
209
215
  ### Issues Fixed
210
216
 
211
- * `server`: #209: missing home folder for transfer user shall not cause an error.
217
+ * `server`: #209: a missing home folder for the transfer user shall not cause an error.
212
218
  * `direct`: #205: `kill` blocks `cmd` on Windows.
213
219
 
214
220
  ### Breaking Changes
@@ -924,7 +930,7 @@ Released: 2021-02-03
924
930
 
925
931
  ## 0.11.2
926
932
 
927
- * Fixes on multi-session: Progress bat and transfer spec param for "direct".
933
+ * Fixes on multi-session: Progress bar and transfer spec param for `direct`.
928
934
 
929
935
  ## 0.11.1
930
936
 
@@ -995,7 +1001,7 @@ Released: 2021-02-03
995
1001
 
996
1002
  ## 0.10.6
997
1003
 
998
- * FaspManager: Transfer spec `authentication` no more needed for local transfer to use Aspera public keys. public keys will be used if there is a token and no key or password is provided.
1004
+ * FaspManager: Transfer spec `authentication` is no longer needed for local transfer to use Aspera public keys. Public keys will be used if there is a token and no key or password is provided.
999
1005
  * Gem version requirements made more open.
1000
1006
 
1001
1007
  ## 0.10.5
data/CONTRIBUTING.md CHANGED
@@ -114,6 +114,8 @@ The following environment variables and macros control specific build behaviors:
114
114
  | `ENABLE_COVERAGE` | set/unset | Enables test coverage analysis when defined. |
115
115
  | `SIGNING_KEY` | File path | Path to the signing key used for building the gem file. |
116
116
  | `SIGNING_KEY_PEM` | PEM Value | The PEM content of the signing key. |
117
+ | `DRY_RUN` | `1` | Simulates execution without performing actual operations (git, gh commands). |
118
+ | `DEBUG` | `1` | Shows stack trace on errors during documentation generation. |
117
119
 
118
120
  These values can be set as standard environment variables or passed directly to the `rake` command.
119
121
 
@@ -156,7 +158,7 @@ tlmgr install fvextra selnolig lualatex-math
156
158
  rake doc:check_links
157
159
  ```
158
160
 
159
- - Debug the generation process: `ASPERA_CLI_DOC_DEBUG=debug`.
161
+ - Debug the generation process: `DEBUG=1`.
160
162
 
161
163
  - Build the documentation:
162
164
 
@@ -254,6 +256,13 @@ bundle exec rake container:test
254
256
  bundle exec rake doc:check_links
255
257
  ```
256
258
 
259
+ - **Check release procedure**:
260
+
261
+ ```shell
262
+ bundle exec rake release:run DRY_RUN=1
263
+ git restore lib/aspera/cli/version.rb CHANGELOG.md docs/README.md
264
+ ```
265
+
257
266
  ### Automated Release Process
258
267
 
259
268
  Releases are managed through the GitHub Actions UI via the **New Release on GitHub** workflow (`.github/workflows/release.yml`).
@@ -313,4 +322,4 @@ This triggers the `.github/workflows/deploy.yml` action to publish to RubyGems.
313
322
  - [rest-client](https://github.com/rest-client/rest-client)
314
323
  - [oauth2](https://github.com/oauth-xx/oauth2)
315
324
  - Integrate `thor` <http://whatisthor.com/> or another standard Ruby CLI framework.
316
- - Explore [Traveling Ruby](https://github.com/phusion/traveling-ruby) for distribution.
325
+ - Explore [Traveling Ruby](https://github.com/phusion/traveling-ruby) for distribution, or [truby Traveling Ruby](https://github.com/trubygems/traveling-ruby).
@@ -530,32 +530,45 @@ module Aspera
530
530
  Aspera.assert_type(query, Hash, NilClass){'query'}
531
531
  Aspera.assert(!call_args.key?(:query))
532
532
  query = {} if query.nil?
533
- query[:iteration_token] = iteration[0] unless iteration.nil?
533
+ query[:iteration_token] = iteration[0] unless iteration.nil? || iteration[0].nil?
534
534
  max = query.delete(RestList::MAX_ITEMS)
535
+ # Return empty list immediately if max is 0
536
+ return [] if max&.zero?
535
537
  item_list = []
536
538
  loop do
537
539
  data, http = read(subpath, query, **call_args, ret: :both)
538
540
  Aspera.assert_type(data, Array){"Expected data to be an Array, got: #{data.class}"}
539
541
  # no data
540
542
  break if data.empty?
541
- # get next iteration token from link
542
- next_iteration_token = nil
543
- link_info = http['Link']
544
- unless link_info.nil?
545
- m = link_info.match(/<([^>]+)>/)
546
- Aspera.assert(m){"Cannot parse iteration in Link: #{link_info}"}
547
- next_iteration_token = Rest.query_to_h(URI.parse(m[1]).query)['iteration_token']
548
- end
549
- # same as last iteration: stop
550
- break if next_iteration_token&.eql?(query[:iteration_token])
551
- query[:iteration_token] = next_iteration_token
552
543
  item_list.concat(data)
544
+ # Check if we reached the max limit
553
545
  if max&.<=(item_list.length)
554
546
  item_list = item_list.slice(0, max)
555
547
  break
556
548
  end
549
+ # Update progress spinner
550
+ RestParameters.instance.spinner_cb.call(item_list.length)
551
+ # Parse Link header according to RFC 8288 to extract next iteration token
552
+ next_url = Rest.parse_link_header(http['Link'], rel: 'next')
553
+ next_iteration_token = nil
554
+ if next_url
555
+ begin
556
+ parsed_uri = URI.parse(next_url)
557
+ query_params = Rest.query_to_h(parsed_uri.query) if parsed_uri.query
558
+ next_iteration_token = query_params['iteration_token'] if query_params
559
+ rescue URI::InvalidURIError => e
560
+ Log.log.warn{"Invalid URI in Link header: #{next_url} - #{e.message}"}
561
+ end
562
+ end
563
+ # Stop if no next token
557
564
  break if next_iteration_token.nil?
565
+ # Stop if same token as current (infinite loop protection)
566
+ break if next_iteration_token.eql?(query[:iteration_token])
567
+ # Update token for next iteration
568
+ query[:iteration_token] = next_iteration_token
558
569
  end
570
+ # Signal completion
571
+ RestParameters.instance.spinner_cb.call(action: :success)
559
572
  # save iteration token if needed
560
573
  iteration[0] = query[:iteration_token] unless iteration.nil?
561
574
  item_list
@@ -178,33 +178,32 @@ module Aspera
178
178
  # Folder, PVCL, version, license information
179
179
  def ascp_info_from_log
180
180
  data = {}
181
+ _, stderr, status = Environment.secure_execute(path(:ascp), '-DDL-', mode: :capture, exception: false)
181
182
  # read PATHs from ascp directly, and pvcl modules as well
182
- Open3.popen3(path(:ascp), '-DDL-') do |_stdin, _stdout, stderr, thread|
183
- last_line = ''
184
- while (line = stderr.gets)
185
- line.chomp!
186
- # skip lines that may have accents
187
- next unless line.valid_encoding?
188
- last_line = line
189
- case line
190
- when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/
191
- data[Regexp.last_match(1)] = Regexp.last_match(3)
192
- when /^DBG Added module group:"(?<module>[^"]+)" name:"(?<scheme>[^"]+)", version:"(?<version>[^"]+)" interface:"(?<interface>[^"]+)"$/
193
- c = Regexp.last_match.named_captures.symbolize_keys
194
- data[c[:interface]] ||= {}
195
- data[c[:interface]][c[:module]] ||= []
196
- data[c[:interface]][c[:module]].push("#{c[:scheme]} v#{c[:version]}")
197
- when %r{^DBG License result \(/license/(\S+)\): (.+)$}
198
- data[Regexp.last_match(1)] = Regexp.last_match(2)
199
- when /^LOG (.+) version ([0-9.]+)$/
200
- data['product_name'] = Regexp.last_match(1)
201
- data['product_version'] = Regexp.last_match(2)
202
- when /^LOG Initializing FASP version ([^,]+),/
203
- data['ascp_version'] = Regexp.last_match(1)
204
- end
183
+ last_line = ''
184
+ stderr.lines do |line|
185
+ line.chomp!
186
+ # Skip lines that may have accents
187
+ next unless line.valid_encoding?
188
+ last_line = line
189
+ case line
190
+ when /^DBG Path ([^ ]+) (dir|file) +: (.*)$/
191
+ data[Regexp.last_match(1)] = Regexp.last_match(3)
192
+ when /^DBG Added module group:"(?<module>[^"]+)" name:"(?<scheme>[^"]+)", version:"(?<version>[^"]+)" interface:"(?<interface>[^"]+)"$/
193
+ c = Regexp.last_match.named_captures.symbolize_keys
194
+ data[c[:interface]] ||= {}
195
+ data[c[:interface]][c[:module]] ||= []
196
+ data[c[:interface]][c[:module]].push("#{c[:scheme]} v#{c[:version]}")
197
+ when %r{^DBG License result \(/license/(\S+)\): (.+)$}
198
+ data[Regexp.last_match(1)] = Regexp.last_match(2)
199
+ when /^LOG (.+) version ([0-9.]+)$/
200
+ data['product_name'] = Regexp.last_match(1)
201
+ data['product_version'] = Regexp.last_match(2)
202
+ when /^LOG Initializing FASP version ([^,]+),/
203
+ data['ascp_version'] = Regexp.last_match(1)
205
204
  end
206
- raise last_line if !thread.value.exitstatus.eql?(1) && !data.key?('root')
207
205
  end
206
+ raise last_line if !status.exitstatus.eql?(1) && !data.key?('root')
208
207
  return data
209
208
  end
210
209
 
@@ -32,7 +32,7 @@ module Aspera
32
32
 
33
33
  class LocalExecutor
34
34
  def execute(ascmd_path, input:)
35
- return Environment.secure_execute(ascmd_path, mode: :capture, stdin_data: input, binmode: true, exception: false)
35
+ Environment.secure_execute(ascmd_path, mode: :capture, stdin_data: input, binmode: true, exception: false).first
36
36
  end
37
37
  end
38
38
 
@@ -4,6 +4,6 @@ module Aspera
4
4
  module Cli
5
5
  # For beta add extension : .beta1
6
6
  # For dev version add extension : .pre
7
- VERSION = '4.25.5'
7
+ VERSION = '4.25.6'
8
8
  end
9
9
  end
@@ -84,22 +84,31 @@ module Aspera
84
84
  "'#{str.gsub("'", %q('\'\''))}'"
85
85
  end
86
86
 
87
- # Execute a process securely without shell.
87
+ # Executes a command without invoking a shell.
88
88
  #
89
- # Execution `mode` can be:
90
- # - :execute -> Kernel.system, return nil
91
- # - :background -> Process.spawn, return pid
92
- # - :capture -> Open3.capture3, return stdout
89
+ # The command is provided as an array to avoid shell interpolation and
90
+ # ensure safer execution.
93
91
  #
94
- # @param cmd [Array<String>] Executable and arguments (mapped "to_s")
95
- # @param mode [:execute,:background,:capture] Execution mode
96
- # @param kwargs [Hash] Additional arguments to underlying method
97
- # @option kwargs [Hash] :env Environment variables
98
- # @option kwargs [Boolean] :exception for :capture mode, raise error if process fails
99
- # @option kwargs [Boolean] :close_others for :background mode
100
- # @return [nil] for :execute mode
101
- # @return [Integer] pid for :background mode
102
- # @return [String] stdout for :capture mode
92
+ # @param cmd [Array<#to_s>] The executable and its arguments.
93
+ # @param mode [:execute, :background, :capture] The execution strategy:
94
+ # - `:execute` Uses {Kernel.system}. Returns `true`, `false`, or `nil`.
95
+ # - `:background` Uses {Process.spawn}. Returns the spawned process PID.
96
+ # - `:capture` Uses {Open3.capture3}. Returns captured output and status.
97
+ #
98
+ # @param kwargs [Hash] Additional options forwarded to the underlying call.
99
+ #
100
+ # @option kwargs [Hash{String => String}] :env Environment variables to set for the process.
101
+ # @option kwargs [Boolean] :exception (false) When `true` in `:capture` mode,
102
+ # raises an error if the command exits with a non-zero status.
103
+ # @option kwargs [Boolean] :close_others (true) When `true` in `:background` mode,
104
+ # closes all other file descriptors in the child process.
105
+ #
106
+ # @return [Boolean, nil] For `:execute` mode (`true`, `false`, or `nil`).
107
+ # @return [Integer] For `:background` mode (process ID).
108
+ # @return [Array(String, String, Process::Status)] For `:capture` mode
109
+ # (`stdout`, `stderr`, `status`).
110
+ #
111
+ # @raise [RuntimeError] If `:exception` is `true` and the process fails in `:capture` mode.
103
112
  def secure_execute(*cmd, mode: :execute, **kwargs)
104
113
  cmd = cmd.map(&:to_s)
105
114
  Aspera.assert(cmd.size.positive?, type: ArgumentError){'executable must be present'}
@@ -128,11 +137,12 @@ module Aspera
128
137
  # https://docs.ruby-lang.org/en/master/Process.html#module-Process-label-Execution+Options
129
138
  argv = [kwargs.delete(:env)].compact + cmd
130
139
  exception = kwargs.delete(:exception){true}
131
- stdout, stderr, status = Open3.capture3(*argv, **kwargs)
132
- Log.log.debug{"status=#{status}, stderr=#{stderr}"}
133
- Log.log.trace1{"stdout=#{stdout}"}
134
- raise "Process failed: #{status.exitstatus} (#{stderr})" if exception && !status.success?
135
- stdout
140
+ result = Open3.capture3(*argv, **kwargs)
141
+ Log.dump(:stdout, result[0], level: :trace1)
142
+ Log.dump(:stderr, result[1], level: :trace1)
143
+ Log.dump(:status, result[2])
144
+ raise "Process failed: #{result[2].exitstatus} (#{result[1]})" if exception && !result[2].success?
145
+ result
136
146
  else Aspera.error_unreachable_line
137
147
  end
138
148
  end
@@ -57,7 +57,7 @@ module Aspera
57
57
  command_args.push(v.shellescape) unless v.empty?
58
58
  end
59
59
  command_args.push(last_opt) unless last_opt.nil?
60
- return Environment.secure_execute(*command_args, mode: :capture)
60
+ Environment.secure_execute(*command_args, mode: :capture).first
61
61
  end
62
62
 
63
63
  def key_chains(output)
@@ -51,11 +51,11 @@ module Aspera
51
51
  Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), out: File::NULL, err: File::NULL)
52
52
  end
53
53
 
54
- # Execute external command and capture output
54
+ # Execute external command and get stdout
55
55
  # @return [String]
56
56
  def external_capture(command_sym, command_args)
57
57
  Aspera.assert_values(command_sym, EXTERNAL_TOOLS){'command'}
58
- return Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), mode: :capture)
58
+ Environment.secure_execute(command_sym.to_s, *command_args.map(&:to_s), mode: :capture).first
59
59
  end
60
60
 
61
61
  def ffmpeg(gl_p: FFMPEG_DEFAULT_PARAMS, in_p: [], in_f:, out_p: [], out_f:)
data/lib/aspera/rest.rb CHANGED
@@ -150,6 +150,37 @@ module Aspera
150
150
  end
151
151
  end
152
152
 
153
+ # Parse Link header according to RFC 8288 to extract a specific relation
154
+ # @param link_header [String, nil] The Link header value
155
+ # @param rel [String] The relation to look for (default: 'next')
156
+ # @return [String, nil] The URL of the link with the specified relation, or nil
157
+ def parse_link_header(link_header, rel: 'next')
158
+ return if link_header.nil? || link_header.empty?
159
+ # RFC 8288: Link header format is: <URI>; param1=value1; param2=value2, <URI2>; ...
160
+ # We look for the link with the specified rel
161
+ link_header.split(',').each do |link_part|
162
+ link_part = link_part.strip
163
+ # Extract URL between < and >
164
+ url_match = link_part.match(/<([^>]+)>/)
165
+ next unless url_match
166
+ url = url_match[1]
167
+ # Extract parameters after the URL
168
+ params_str = link_part[url_match.end(0)..-1]
169
+ # Check if this link has the specified rel (with or without quotes, case insensitive)
170
+ next unless /;\s*rel\s*=\s*"?#{Regexp.escape(rel)}"?/i.match?(params_str)
171
+ return url
172
+ end
173
+ # Fallback: if no rel found and looking for 'next', try the first link (backward compatibility)
174
+ if rel.eql?('next')
175
+ first_link = link_header.split(',').first&.strip
176
+ if first_link
177
+ url_match = first_link.match(/<([^>]+)>/)
178
+ return url_match[1] if url_match
179
+ end
180
+ end
181
+ nil
182
+ end
183
+
153
184
  # Start a HTTP/S session, also used for web sockets
154
185
  # @param base_url [String] Base url of HTTP/S session
155
186
  # @return [Net::HTTP] A started HTTP session
@@ -197,7 +197,7 @@ module Aspera
197
197
  raise Error, 'Missing either local_db_dir or local_dir'
198
198
  end
199
199
  end
200
- stdout = Environment.secure_execute(*arguments, mode: :capture)
200
+ stdout = Environment.secure_execute(*arguments, mode: :capture).first
201
201
  return parse_status(stdout)
202
202
  end
203
203
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aspera-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.25.5
4
+ version: 4.25.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Laurent Martin
metadata.gz.sig CHANGED
Binary file