squared 0.5.9 → 0.5.11

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: fb4a8d7a97fde66c49f38e36f6f4ae0325f7bf54b7e73e6c14febb5b01814cc4
4
- data.tar.gz: 64385a46e4260b50fd1a7dbc4c69558cb9ba15324b3263a2f3d7fd4c48a23322
3
+ metadata.gz: 56af2850e96c00e48e1511b20fc61c5f0d9969220b4b070a6c61c02ce6489e97
4
+ data.tar.gz: 83749c3414fa0496dfa64fb3ec0846af2205b1c4483ecbd44e72ed2d976c7852
5
5
  SHA512:
6
- metadata.gz: b8a6195350bf86b98c5dda0509b57304e69f7f2d04ebb2d16b4bc7a59f26bedd1d0f53adca066768101273270f10dac31f90c069ea9379f30ca437acaa73a134
7
- data.tar.gz: dad1693756f55833558187af4a8d51864ca2b6387c51955720146425c58f2416102f3d0e119269c095dd159801c8a9d850a9b5c879016ba44fbd7d1766ce5cee
6
+ metadata.gz: 4fbc69f9efe06c1d2ceba44c58854ca31b0a7e50a6fe89c01e3aeff05830efae2f7bf07f068163352f6c54f9d347a5db11b308a8f0b3f3b64914ac6b0035743b
7
+ data.tar.gz: 7aada08d98786700e652f0a78e5ddeda72ada420ec9d89cffc937f706b27da4800374231d086b67be8ae748199d0229a02fb95a9dfaac349fff6c40997ab5099
data/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.11] - 2025-10-18
4
+
5
+ ### Fixed
6
+
7
+ - Ruby command version did not parse asdf output in Go.
8
+ - Git command submodule action branch did not provide name argument.
9
+
10
+ ## [0.4.25] - 2025-10-18
11
+
12
+ ### Fixed
13
+
14
+ - Project groups did not have their own copy of base events.
15
+
16
+ ## [0.4.24] - 2025-10-17
17
+
18
+ ### Added
19
+
20
+ - Repo task [init|all] can bypass dev? copy requirement with REPO_STAGE=4.
21
+
22
+ ### Changed
23
+
24
+ - Node command outdated does not check PNPM minimum version.
25
+
26
+ ### Fixed
27
+
28
+ - Project base rescue error handling used reversed parameters.
29
+ - Project absolute paths did not append wildcard with trailing slash.
30
+ - Powershell commands did not escape nested double quotes.
31
+ - OptionPartition did not strip flags without a value.
32
+ - Project base method add did not use parent context.
33
+ - Application property pipe did not parse numeric values.
34
+
35
+ ## [0.5.10] - 2025-10-11
36
+
37
+ ### Fixed
38
+
39
+ - See `0.4.23`.
40
+
41
+ ## [0.4.23] - 2025-10-11
42
+
43
+ ### Fixed
44
+
45
+ - Node task outdated did not compare wanted and latest by semver.
46
+ - Project base method append_hash did not have target when joined.
47
+ - Docker build for compose and bake was completely incapacitated.
48
+
3
49
  ## [0.5.9] - 2025-10-08
4
50
 
5
51
  ### Fixed
@@ -1107,6 +1153,8 @@
1107
1153
 
1108
1154
  - Changelog was created.
1109
1155
 
1156
+ [0.5.11]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.11
1157
+ [0.5.10]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.10
1110
1158
  [0.5.9]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.9
1111
1159
  [0.5.8]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.8
1112
1160
  [0.5.7]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.7
@@ -1117,6 +1165,9 @@
1117
1165
  [0.5.2]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.2-ruby
1118
1166
  [0.5.1]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.1-ruby
1119
1167
  [0.5.0]: https://github.com/anpham6/squared-ruby/releases/tag/v0.5.0-ruby
1168
+ [0.4.25]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.25
1169
+ [0.4.24]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.24
1170
+ [0.4.23]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.23
1120
1171
  [0.4.22]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.22
1121
1172
  [0.4.21]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.21
1122
1173
  [0.4.20]: https://github.com/anpham6/squared-ruby/releases/tag/v0.4.20
data/README.md CHANGED
@@ -745,7 +745,7 @@ REPO_SYNC # 0,1
745
745
  REPO_URL # manifest repository
746
746
  REPO_MANIFEST # e.g. latest,nightly,prod
747
747
  REPO_GROUPS # e.g. base,prod,docs
748
- REPO_STAGE # 0,1,2,3
748
+ REPO_STAGE # 0,1,2,3,4
749
749
  REPO_SUBMODULLES # 0,1
750
750
  REPO_TIMEOUT # confirm dialog (seconds)
751
751
  ```
@@ -180,8 +180,9 @@ module Squared
180
180
  args = args.map(&:to_s)
181
181
  if level.is_a?(::Numeric)
182
182
  if append && respond_to?(:log)
183
- ref = log rescue nil
184
- ref.add(level, message(subject, *args, hint: hint, space: ', ')) if ref.is_a?(Logger)
183
+ (log rescue nil).tap do |ref|
184
+ ref.add(level, message(subject, *args, hint: hint, space: ', ')) if ref.is_a?(::Logger)
185
+ end
185
186
  end
186
187
  return false if !pass && level < ARG[:LEVEL]
187
188
  end
@@ -200,7 +201,7 @@ module Squared
200
201
  def log_console(*args, pipe: 1)
201
202
  return if args.first == false && args.size == 1
202
203
 
203
- if pipe.is_a?(Pathname)
204
+ if pipe.is_a?(::Pathname)
204
205
  begin
205
206
  File.open(pipe, 'a') do |f|
206
207
  br = File::SEPARATOR == '\\' ? "\r\n" : "\n"
@@ -219,7 +220,8 @@ module Squared
219
220
  module_function
220
221
 
221
222
  def message(*args, hint: nil, empty: false, space: ARG[:SPACE])
222
- (empty ? args.reject { |val| val.nil? || val.empty? } : args).join(space) + (hint ? " (#{hint})" : '')
223
+ (empty ? args.reject { |val| val.nil? || (val.respond_to?(:empty?) && val.empty?) } : args)
224
+ .join(space) + (hint ? " (#{hint})" : '')
223
225
  end
224
226
 
225
227
  def emphasize(val, title: nil, footer: nil, right: false, cols: nil, sub: nil, pipe: nil,
@@ -233,7 +235,7 @@ module Squared
233
235
  lines = val.map(&:to_s)
234
236
  else
235
237
  lines = val.to_s.lines(chomp: true)
236
- lines[0] = "#{val.class}: #{lines.first}" if (err = val.is_a?(StandardError))
238
+ lines[0] = "#{val.class}: #{lines.first}" if (err = val.is_a?(::StandardError))
237
239
  end
238
240
  n = cols || max.call(lines)
239
241
  if $stdout.tty?
@@ -56,7 +56,7 @@ module Squared
56
56
  min = [min, max].min
57
57
  if auto
58
58
  msg = "#{msg}: [1-#{max}#{if multiple
59
- "|,#{multiple.is_a?(Numeric) ? "{#{multiple}}" : ''}"
59
+ "|,#{multiple.is_a?(::Numeric) ? "{#{multiple}}" : ''}"
60
60
  end}] "
61
61
  end
62
62
  end
@@ -113,18 +113,18 @@ module Squared
113
113
  elsif block_given?
114
114
  Readline.readmultiline(msg, history, &blk)
115
115
  else
116
- Readline.readmultiline(msg, history) { |line| multiline.any? { |val| line.split.last.end_with?(val) } }
116
+ Readline.readmultiline(msg, history) { |line| multiline.any? { |val| line.split.last.end_with?(val.to_s) } }
117
117
  end
118
118
  end
119
119
  case force
120
120
  when ::TrueClass, ::FalseClass
121
121
  msg = "#{msg} %s " % if multiline
122
- multiline.is_a?(::Enumerable) ? "{#{multiline.join('|')}}" : multiline
122
+ multiline.is_a?(::Enumerable) ? "{#{multiline.to_a.join('|')}}" : multiline
123
123
  else
124
124
  "(#{force ? 'required' : 'optional'}):"
125
125
  end
126
126
  ret = (prompt.call || '').strip
127
- multiline.each { |val| break if ret.delete_suffix!(val) } if multiline.is_a?(::Enumerable)
127
+ multiline.each { |val| break if ret.delete_suffix!(val.to_s) } if multiline.is_a?(::Enumerable)
128
128
  exit 1 if force && ret.empty?
129
129
  ret
130
130
  else
@@ -16,15 +16,15 @@ module Squared
16
16
  elsif !r[3] || r[6]
17
17
  return val
18
18
  end
19
- if r[7].match?(/\A["']/)
20
- opt = "#{r[7]}#{r[7][0]}"
21
- elsif r[7].match?(/["']\z/)
22
- opt = "#{r[7][-1]}#{r[7]}"
23
- else
24
- return val unless r[7].match?(/\s/)
25
-
26
- opt = r[7]
27
- end
19
+ opt = if r[7].match?(/\A["']/)
20
+ "#{r[7]}#{r[7][0]}"
21
+ elsif r[7].match?(/["']\z/)
22
+ "#{r[7][-1]}#{r[7]}"
23
+ else
24
+ return val unless r[7].match?(/\s/)
25
+
26
+ r[7]
27
+ end
28
28
  r[1] + (data ? data[2] : r[2]) + r[4] + shell_quote(opt, double: double, force: force, override: override)
29
29
  elsif option && val =~ /\A([^=]+)=(.+)\z/m
30
30
  return val if $2.match?(/\A(["']).+\1\z/m)
@@ -69,15 +69,15 @@ module Squared
69
69
  escape = false
70
70
  override = true
71
71
  end
72
- if flag[0] == '-'
73
- b = flag[1] == '-' ? '=' : ' '
74
- elsif flag.size == 1
75
- a = '-'
76
- b = merge ? '' : ' '
77
- else
78
- a = '--'
79
- b = '='
80
- end
72
+ b = if flag[0] == '-'
73
+ flag[1] == '-' ? '=' : ' '
74
+ elsif flag.size == 1
75
+ a = '-'
76
+ merge ? '' : ' '
77
+ else
78
+ a = '--'
79
+ '='
80
+ end
81
81
  "#{a}#{flag}#{unless val.nil?
82
82
  "#{b}#{if escape
83
83
  shell_escape(val, quote: quote, double: double, override: override)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.5.9'
4
+ VERSION = '0.5.11'
5
5
  end
@@ -140,7 +140,7 @@ module Squared
140
140
  return unless @pipe.is_a?(Pathname)
141
141
 
142
142
  msg = "Session started on #{Time.now} by #{@main}"
143
- bord = '#' * s.size
143
+ bord = '#' * msg.size
144
144
  puts bord, msg, bord
145
145
  end
146
146
 
@@ -323,17 +323,9 @@ module Squared
323
323
  end
324
324
 
325
325
  def add(path, project = nil, **kwargs, &blk)
326
- kwargs = @withargs.yield_self { |data| data.dup.update(kwargs) } if @withargs
327
- ref = if kwargs.key?(:ref)
328
- kwargs = kwargs.dup unless @withargs
329
- kwargs.delete(:ref)
330
- elsif @ref.is_a?(Symbol)
331
- @ref
332
- end
333
- if @group.is_a?(String) && !kwargs.key?(:group)
334
- kwargs = kwargs.dup unless @withargs
335
- kwargs[:group] = @group
336
- end
326
+ kwargs = Support.hashdup(@withargs).update(kwargs) if @withargs
327
+ ref = kwargs.key?(:ref) ? kwargs.delete(:ref) : @ref
328
+ kwargs[:group] = @group if @group && !kwargs.key?(:group)
337
329
  path = root + path
338
330
  project = (project || path.basename).to_s
339
331
  name = task_name project
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'json'
4
4
  require 'date'
5
- require 'time'
6
5
  require 'logger'
7
6
 
8
7
  module Squared
@@ -66,11 +65,13 @@ module Squared
66
65
 
67
66
  @@tasks = {}
68
67
  @@graph = { _: [] }
69
- @@asdf = if File.exist?("#{Dir.home}/.asdf/asdf.sh")
70
- [Pathname.new("#{Dir.home}/.asdf"), 15]
71
- elsif ENV['ASDF_DATA_DIR']
72
- [Pathname.new(ENV['ASDF_DATA_DIR']), 16]
73
- end
68
+ @@asdf = Pathname.new("#{Dir.home}/.asdf").yield_self do |path|
69
+ if path.join('asdf.sh').exist?
70
+ [path, 15]
71
+ elsif ENV['ASDF_DATA_DIR']
72
+ [Pathname.new(ENV['ASDF_DATA_DIR']), 16]
73
+ end
74
+ end
74
75
  @@print_order = 0
75
76
 
76
77
  subtasks({
@@ -221,7 +222,7 @@ module Squared
221
222
  def initialize_env(dev: nil, prod: nil, **)
222
223
  @dev = env_match('BUILD', dev, suffix: 'DEV', strict: true)
223
224
  @prod = env_match('BUILD', prod, suffix: 'PROD', strict: true)
224
- unless @output[2] == false || !(val = env('BUILD', suffix: 'ENV'))
225
+ if (val = env('BUILD', suffix: 'ENV')) && @output[2] != false
225
226
  @output[2] = parse_json(val, hint: "BUILD_#{@envname}_ENV") || @output[2]
226
227
  end
227
228
  unless @output[0] == false || @output[0].is_a?(Array)
@@ -390,6 +391,7 @@ module Squared
390
391
  version, opts = choice_index('Select a version',
391
392
  @asdf[1].children
392
393
  .map(&:basename)
394
+ .sort { |a, b| b <=> a }
393
395
  .append('latest', 'system'),
394
396
  force: true, accept: [['Confirm?', false, true]],
395
397
  values: ['Options'])
@@ -438,10 +440,11 @@ module Squared
438
440
  end
439
441
  return self unless projectpath?(path = basepath(path)) && checkdir?(path)
440
442
 
441
- kwargs = @withargs.yield_self { |data| data.dup.update(kwargs) } if @withargs
442
- kwargs[:group] = group unless kwargs.key?(:group)
443
+ kwargs = hashdup(@withargs).update(kwargs) if @withargs
444
+ kwargs[:group] = group if group && !kwargs.key?(:group)
443
445
  kwargs[:ref] = ref unless kwargs.key?(:ref)
444
446
  parent = self
447
+ proj = nil
445
448
  name = case name
446
449
  when String, Symbol
447
450
  name.to_s
@@ -450,9 +453,10 @@ module Squared
450
453
  end
451
454
  workspace.add(path, name, **kwargs) do
452
455
  __send__ :parent_set, parent
453
- @children << self
456
+ proj = self
454
457
  instance_eval(&blk) if block_given?
455
458
  end
459
+ @children << proj
456
460
  self
457
461
  end
458
462
 
@@ -478,9 +482,9 @@ module Squared
478
482
  if args.empty?
479
483
  return unless from == :run
480
484
 
481
- run_b(@run, sync: sync, from: from) if series?(@run)
482
- args = @output
483
485
  banner = verbosetype > 1 if from_base?('build')
486
+ run_b(@run, sync: sync, from: from, banner: banner) if series?(@run)
487
+ args = @output
484
488
  end
485
489
  if args.first.is_a?(Struct)
486
490
  f, blk = args.first.to_a
@@ -496,11 +500,11 @@ module Squared
496
500
  a, b, c, d, e = val
497
501
  case b
498
502
  when Hash
499
- b = append_hash(b, build: true).join(' ')
503
+ b = append_hash(b, target: [], build: true).join(' ')
500
504
  when Enumerable
501
505
  b = b.to_a.join(' ')
502
506
  end
503
- d = append_hash(d).join(' ') if d.is_a?(Hash)
507
+ d = append_hash(d, target: []).join(' ') if d.is_a?(Hash)
504
508
  if a
505
509
  cmd << [a, d, b].compact.join(' ')
506
510
  else
@@ -514,18 +518,18 @@ module Squared
514
518
  else
515
519
  cmd, opts, var, flags, extra = args
516
520
  end
517
- if cmd.is_a?(Proc) || cmd.is_a?(Method)
518
- run_b(cmd, sync: sync, from: from)
519
- return
520
- end
521
521
  if cmd
522
+ return run_b(cmd, sync: sync, from: from) if cmd.is_a?(Proc) || cmd.is_a?(Method)
523
+
522
524
  cmd = as_get(cmd, from)
523
525
  opts = compose(opts, script: false) if opts && respond_to?(:compose)
524
- flags = append_hash(flags).join(' ') if flags.is_a?(Hash)
526
+ flags = append_hash(flags, target: []).join(' ') if flags.is_a?(Hash)
525
527
  case opts
526
528
  when Hash
527
- opts = append_hash(opts, build: true)
528
- cmd = Array(cmd).append(flags).concat(opts).compact.join(' ')
529
+ cmd = Array(cmd).append(flags)
530
+ .concat(append_hash(opts, target: [], build: true))
531
+ .compact
532
+ .join(' ')
529
533
  when Enumerable
530
534
  cmd = Array(cmd).concat(opts.to_a)
531
535
  cmd.map! { |val| "#{val} #{flags}" } if flags
@@ -558,7 +562,7 @@ module Squared
558
562
  proj.__send__(meth, sync: sync)
559
563
  next
560
564
  rescue StandardError => e
561
- on_error(:prereqs, e, exception: true)
565
+ on_error(e, :prereqs, exception: true)
562
566
  end
563
567
  end
564
568
  print_error(name, 'method not found', subject: 'prereqs', hint: meth)
@@ -578,7 +582,7 @@ module Squared
578
582
  end
579
583
 
580
584
  def doc(*, sync: invoked_sync?('doc'), **)
581
- run_b(@doc, sync: sync, from: :doc, banner: from_base?('doc') ? verbosetype > 1 : verbose)
585
+ run_b(@doc, sync: sync, banner: from_base?('doc') ? verbose? : verbosetype > 0, from: :doc)
582
586
  end
583
587
 
584
588
  def lint(*, sync: invoked_sync?('lint'), **)
@@ -599,9 +603,9 @@ module Squared
599
603
  on :first, :clean unless pass
600
604
  case @clean
601
605
  when Struct
602
- if (any = instance_eval(&@clean.block) || @clean.run)
606
+ if (val = instance_eval(&@clean.block) || @clean.run)
603
607
  temp = @clean
604
- @clean = any
608
+ @clean = val
605
609
  clean(*args, sync: sync, pass: true, **kwargs)
606
610
  @clean = temp
607
611
  end
@@ -619,7 +623,7 @@ module Squared
619
623
  entry = basepath(val = val.to_s)
620
624
  if entry.directory? && val.match?(%r{[\\/]\z})
621
625
  log&.warn "rm -rf #{entry}"
622
- rm_rf(entry, verbose: verbose)
626
+ rm_rf(entry, verbose: verbosetype > 0)
623
627
  else
624
628
  log&.warn "rm #{entry}"
625
629
  (val.include?('*') ? Dir[entry] : [entry]).each do |file|
@@ -658,7 +662,7 @@ module Squared
658
662
  end
659
663
  ret = graph_branch(self, data, tasks, out, sync: sync, pass: pass)
660
664
  rescue StandardError => e
661
- on_error(:graph, e, exception: true)
665
+ on_error(e, :graph, exception: true)
662
666
  else
663
667
  if out
664
668
  [out, ret]
@@ -717,7 +721,7 @@ module Squared
717
721
  case f.content_type
718
722
  when 'application/zip'
719
723
  ext = 'zip'
720
- when 'application/x-gzip'
724
+ when %r{application/(?:x-)?gzip}
721
725
  ext = 'tgz'
722
726
  when 'application/x-xz'
723
727
  ext = 'txz'
@@ -779,9 +783,7 @@ module Squared
779
783
  break unless entry.directory?
780
784
 
781
785
  i = 0
782
- while (dest = target + "#{File.basename(file)}-#{i}").exist?
783
- i += 1
784
- end
786
+ i += 1 while (dest = target + "#{File.basename(file)}-#{i}").exist?
785
787
  FileUtils.mv(entry, dest)
786
788
  dest.each_child { |child| FileUtils.mv(child, target) }
787
789
  dest.rmdir
@@ -821,7 +823,7 @@ module Squared
821
823
  cmd << '--no-header' unless legacy
822
824
  cmd << name
823
825
  end
824
- print_success if success?(run(from: :"asdf:#{flag}")) && flag == :set
826
+ print_success if success?(run(from: :"asdf:#{flag}"), flag == :set)
825
827
  end
826
828
 
827
829
  def first(key, *args, **kwargs, &blk)
@@ -869,7 +871,7 @@ module Squared
869
871
  def run(cmd = @session, var = nil, exception: self.exception, sync: true, from: nil, banner: true, chdir: path,
870
872
  interactive: nil, hint: nil, **)
871
873
  unless cmd
872
- print_error('no command given', subject: project, hint: from || 'unknown', pass: true)
874
+ print_error('no command session started', subject: project, hint: from, pass: true)
873
875
  return
874
876
  end
875
877
  cmd = cmd.target if cmd.is_a?(OptionPartition)
@@ -907,7 +909,7 @@ module Squared
907
909
  ret = shell(*args, chdir: chdir, exception: exception)
908
910
  end
909
911
  rescue StandardError => e
910
- on_error(from, e, exception: true)
912
+ on_error(e, from, exception: true)
911
913
  false
912
914
  else
913
915
  on :last, from
@@ -1119,8 +1121,8 @@ module Squared
1119
1121
  def run_b(obj, **kwargs)
1120
1122
  case obj
1121
1123
  when Struct
1122
- if (any = instance_eval(&obj.block) || obj.run)
1123
- run_b(any, **kwargs)
1124
+ if (val = instance_eval(&obj.block) || obj.run)
1125
+ run_b(val, **kwargs)
1124
1126
  end
1125
1127
  when Proc
1126
1128
  instance_eval(&obj)
@@ -1325,12 +1327,12 @@ module Squared
1325
1327
  end
1326
1328
 
1327
1329
  def option(*args, target: @session, prefix: target&.first, **kwargs)
1328
- if prefix
1329
- args.each do |val|
1330
- next unless (ret = env(env_key(stripext(prefix), val), **kwargs))
1330
+ return unless prefix
1331
1331
 
1332
- return block_given? ? yield(ret) : ret
1333
- end
1332
+ args.each do |val|
1333
+ next unless (ret = env(env_key(stripext(prefix), val), **kwargs))
1334
+
1335
+ return block_given? ? yield(ret) : ret
1334
1336
  end
1335
1337
  nil
1336
1338
  end
@@ -1503,7 +1505,7 @@ module Squared
1503
1505
  opts.each { |val| target << shell_option(flag, val) }
1504
1506
  end
1505
1507
 
1506
- def append_hash(data, target: @session, build: false)
1508
+ def append_hash(data, target: @session || [], build: false)
1507
1509
  if build && (type = env('BUILD', suffix: 'TYPE') || ENV['BUILD_TYPE'])
1508
1510
  type = "__#{type}__"
1509
1511
  if (extra = data[type] || data[type.to_sym]).is_a?(Hash)
@@ -1608,7 +1610,7 @@ module Squared
1608
1610
  when String
1609
1611
  "#{base} #{data}"
1610
1612
  when Hash
1611
- "#{append_hash(base).join(' ')} #{data}"
1613
+ "#{append_hash(base, target: []).join(' ')} #{data}"
1612
1614
  when Enumerable
1613
1615
  "#{base.to_a.join(' ')} #{data}"
1614
1616
  else
@@ -1617,11 +1619,11 @@ module Squared
1617
1619
  when Hash
1618
1620
  case base
1619
1621
  when String
1620
- "#{base} #{append_hash(data).join(' ')}"
1622
+ "#{base} #{append_hash(data, target: []).join(' ')}"
1621
1623
  when Hash
1622
1624
  base.merge(data)
1623
1625
  when Enumerable
1624
- Set.new(base.to_a + append_hash(data)).to_a
1626
+ Set.new(base.to_a + append_hash(data, target: [])).to_a
1625
1627
  else
1626
1628
  data
1627
1629
  end
@@ -1630,7 +1632,7 @@ module Squared
1630
1632
  when String
1631
1633
  "#{base} #{data.to_a.join(' ')}"
1632
1634
  when Hash
1633
- "#{append_hash(base).join(' ')} #{data.to_a.join(' ')}"
1635
+ "#{append_hash(base, target: []).join(' ')} #{data.to_a.join(' ')}"
1634
1636
  when Enumerable
1635
1637
  Set.new(base.to_a + data.to_a).to_a
1636
1638
  else
@@ -1686,8 +1688,8 @@ module Squared
1686
1688
  confirm "#{a}: #{b}#{c} #{sub_style(ver.rjust(col1 > 0 ? 10 : 0), styles: theme[:inline])} ", d
1687
1689
  end
1688
1690
 
1689
- def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil,
1690
- multiple: false, force: true, **kwargs)
1691
+ def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil, multiple: false,
1692
+ force: true, **kwargs)
1691
1693
  puts if !series && !printfirst?
1692
1694
  msg = "#{msg} (optional)" unless force
1693
1695
  unless (ret = choice(msg, list, multiple: multiple, force: force, **kwargs)) && !ret.empty?
@@ -1703,13 +1705,10 @@ module Squared
1703
1705
  if accept
1704
1706
  hint = Array(ret).map { |val| sub_style(val, styles: theme[:inline]) }.join(', ')
1705
1707
  accept = Array(accept).map { |val| Array(val) }
1706
- if accept.any? { |val| val[1] == true }
1707
- ret = [ret]
1708
- multiple = -1
1709
- end
1708
+ ret = Array(ret) if accept.any? { |val| val[1] == true }
1710
1709
  loop do
1711
1710
  item = accept.first
1712
- c = confirm("#{item[0]}#{a ? " [#{hint}]" : ''}", item[2] ? 'Y' : 'N', timeout: 60)
1711
+ c = confirm("#{item[0]}#{hint ? " [#{hint}]" : ''}", item[2] ? 'Y' : 'N', timeout: 60)
1713
1712
  if item[1] == true
1714
1713
  ret << c
1715
1714
  elsif !c
@@ -1722,8 +1721,8 @@ module Squared
1722
1721
  exit 1 unless accept.empty?
1723
1722
  end
1724
1723
  if values
1725
- ret = [ret] unless accept && multiple == -1
1726
- values.each do |val|
1724
+ ret = Array(ret)
1725
+ Array(values).each do |val|
1727
1726
  if val.is_a?(Array)
1728
1727
  val, force = val
1729
1728
  else
@@ -1756,21 +1755,17 @@ module Squared
1756
1755
  end
1757
1756
 
1758
1757
  def command(*args)
1759
- if workspace.powershell?
1760
- "powershell.exe -Command \"& {#{args.join(' ; ')}}\""
1761
- else
1762
- args.join(' && ')
1763
- end
1758
+ return args.join(' && ') unless workspace.powershell?
1759
+
1760
+ "powershell.exe -Command #{shell_quote("& {#{args.join(' ; ')}}", option: false, double: true)}"
1764
1761
  end
1765
1762
 
1766
1763
  def relativepath(*list, all: false)
1767
1764
  return [] if list.empty?
1768
1765
 
1769
1766
  list.flatten.map! { |val| Pathname.new(val) }.select { |val| projectpath?(val) }.map! do |val|
1770
- val = val.absolute? ? val.relative_path_from(path).to_s : val.to_s
1771
- val = val[2..-1] if val.start_with?('./')
1772
- val = "#{val}*" if all && val.end_with?('/')
1773
- val
1767
+ ret = (val.absolute? ? val.relative_path_from(path) : val.cleanpath).to_s
1768
+ all && val.to_s.end_with?('/') ? "#{ret}/*" : ret
1774
1769
  end
1775
1770
  end
1776
1771
 
@@ -1818,10 +1813,10 @@ module Squared
1818
1813
  return 1 if a.empty?
1819
1814
 
1820
1815
  a, b = [a.first, b.first].map! do |c|
1821
- begin
1822
- d = Integer(c[5]).to_s
1816
+ d = begin
1817
+ Integer(c[5]).to_s
1823
1818
  rescue StandardError
1824
- d = c[5] ? '-1' : '0'
1819
+ c[5] ? '-1' : '0'
1825
1820
  end
1826
1821
  [c[0], c[2], c[4] || '0', d]
1827
1822
  end
@@ -1854,8 +1849,7 @@ module Squared
1854
1849
  end
1855
1850
 
1856
1851
  def color(val)
1857
- ret = theme[val]
1858
- ret && !ret.empty? ? ret : [val]
1852
+ (ret = theme[val]) && !ret.empty? ? ret : [val]
1859
1853
  end
1860
1854
 
1861
1855
  def colormap(val)
@@ -2073,9 +2067,8 @@ module Squared
2073
2067
  end
2074
2068
 
2075
2069
  def projectpath?(val)
2076
- Pathname.new(val).cleanpath.yield_self do |file|
2077
- file.absolute? ? file.to_s.start_with?(File.join(path, '')) : !file.to_s.start_with?(File.join('..', ''))
2078
- end
2070
+ ret = Pathname.new(val).cleanpath
2071
+ ret.absolute? ? ret.to_s.start_with?(File.join(path, '')) : !ret.to_s.start_with?(File.join('..', ''))
2079
2072
  end
2080
2073
 
2081
2074
  def checkdir?(val)
@@ -2150,6 +2143,10 @@ module Squared
2150
2143
  !!verbose && !stdin?
2151
2144
  end
2152
2145
 
2146
+ def verbose?
2147
+ verbosetype > 1
2148
+ end
2149
+
2153
2150
  def warning?
2154
2151
  workspace.warning
2155
2152
  end
@@ -2181,6 +2178,10 @@ module Squared
2181
2178
  Workspace::Support.hashlist
2182
2179
  end
2183
2180
 
2181
+ def hashdup
2182
+ Workspace::Support.hashdup
2183
+ end
2184
+
2184
2185
  def borderstyle
2185
2186
  workspace.banner_get(*@ref, group: group)&.border || theme[:border]
2186
2187
  end
@@ -113,7 +113,7 @@ module Squared
113
113
  return unless dockerfile(file).exist?
114
114
 
115
115
  @context = context
116
- @tag = tag || tagname("#{@project}:#{@version || 'latest'}")
116
+ self.tag = tag || tagname("#{@project}:#{@version || 'latest'}")
117
117
  @mounts = mounts
118
118
  @secrets = secrets
119
119
  @registry = tagjoin registry, kwargs[:username]
@@ -206,7 +206,7 @@ module Squared
206
206
  end
207
207
  end
208
208
  else
209
- format_desc(action, flag, 'opts*,id/name', after: flag == :update ? '+' : '*')
209
+ format_desc action, flag, "opts*,id/name#{flag == :update ? '+' : '*'}"
210
210
  task flag do |_, args|
211
211
  container flag, args.to_a
212
212
  end
@@ -264,11 +264,11 @@ module Squared
264
264
  ret = docker_session
265
265
  if from == :run
266
266
  if bake?(n = filetype)
267
- ret << 'buildx' << 'bake'
267
+ ret << 'buildx bake'
268
268
  append_file n
269
269
  from = :bake
270
270
  elsif compose?(n)
271
- ret << 'compose' << 'build'
271
+ ret << 'compose build'
272
272
  append_file n
273
273
  from = :compose
274
274
  else
@@ -281,7 +281,7 @@ module Squared
281
281
  when String
282
282
  ret << opts
283
283
  when Hash
284
- ret.merge(append_hash(opts, build: true))
284
+ ret.merge(append_hash(opts, target: [], build: true))
285
285
  when Enumerable
286
286
  ret.merge(opts.to_a)
287
287
  end
@@ -533,7 +533,7 @@ module Squared
533
533
  list_image(flag, docker_output('image ls -a'), from: from) do |val|
534
534
  op << val
535
535
  if flag == :tag
536
- op << tagname("#{@project}:#{op.first}")
536
+ op << tagname("#{project}:#{op.first}")
537
537
  break
538
538
  end
539
539
  end
@@ -554,7 +554,7 @@ module Squared
554
554
  banner = false
555
555
  end
556
556
  ret = run(cmd, sync: sync, exception: exception, banner: banner, from: from)
557
- print_success if success?(ret) && (flag == :tag || flag == :save)
557
+ print_success if success?(ret, flag == :tag || flag == :save)
558
558
  end
559
559
 
560
560
  def network(flag, opts = [], target: nil)
@@ -648,7 +648,7 @@ module Squared
648
648
  target << list.join(' && ') unless list.empty?
649
649
  end
650
650
 
651
- def append_file(type, target: @session)
651
+ def append_file(type, target: @session, index: 2)
652
652
  return if !@file || (ENV['COMPOSE_FILE'] && compose?(type))
653
653
 
654
654
  unless @file.is_a?(Array)
@@ -656,15 +656,16 @@ module Squared
656
656
  when 2, 4
657
657
  return
658
658
  when 3
659
- return unless COMPOSEFILE.map { |val| basepath(val) }.select(&:exist?).size > 1
659
+ return unless COMPOSEFILE.select { |val| basepath(val).exist? }.size > 1
660
660
  end
661
661
  end
662
662
  files = Array(@file).map { |val| quote_option('file', basepath(val)) }
663
663
  if target.is_a?(Set)
664
- opts = target.to_a.insert(2, *files)
665
- target.clear.merge(opts)
664
+ opts = target.to_a.insert(index, *files)
665
+ target.clear
666
+ .merge(opts)
666
667
  else
667
- target.insert(2, *files)
668
+ target.insert(index, *files)
668
669
  end
669
670
  end
670
671
 
@@ -678,7 +679,7 @@ module Squared
678
679
  def append_tag(val, target: @session)
679
680
  case val
680
681
  when String
681
- val.split(',')
682
+ split_escape val
682
683
  when Array
683
684
  val
684
685
  else
@@ -820,7 +821,7 @@ module Squared
820
821
  end
821
822
  cmd.merge(Array(out).map! { |val| parse.call(val) })
822
823
  cmd << args
823
- print_success if success?(run(cmd)) && ctx.start_with?(/(?:network|tag|save)/)
824
+ print_success if success?(run(cmd), ctx.start_with?(/(?:network|tag|save)/))
824
825
  end
825
826
  end
826
827
 
@@ -408,10 +408,11 @@ module Squared
408
408
 
409
409
  case flag
410
410
  when :branch
411
- format_desc action, flag, 'path,opts*'
412
- task flag, [:path] do |_, args|
411
+ format_desc action, flag, 'path,name?'
412
+ task flag, [:path, :name] do |_, args|
413
413
  path = param_guard(action, flag, args: args, key: :path)
414
- submodule(flag, args.extras, path: path)
414
+ branch = args.name
415
+ submodule(flag, [branch ? 'b' : 'd'], branch: branch, path: path)
415
416
  end
416
417
  when :url
417
418
  format_desc action, flag, 'path,url,opts*'
@@ -498,7 +499,7 @@ module Squared
498
499
  remote = choice_remote
499
500
  end
500
501
  ret = tag(flag, refs: [name], message: message, commit: commit, remote: remote)
501
- print_success if success?(ret) && !remote
502
+ print_success if success?(ret, !remote)
502
503
  end
503
504
  end
504
505
  when 'stash'
@@ -607,7 +608,7 @@ module Squared
607
608
  detach = args.detach
608
609
  commit = commithead args.commit
609
610
  end
610
- param_guard(action, flag, args: { create: create }, key: :create, pat: /\Ab\z/i) if create
611
+ param_guard(action, flag, args: { create: create }, key: :create, pat: /\A[Bb]\z/) if create
611
612
  else
612
613
  branch = choice_refs 'Choose a branch to switch'
613
614
  end
@@ -640,7 +641,7 @@ module Squared
640
641
  task flag, [:commit] do |_, args|
641
642
  commit = commithead args.commit
642
643
  unless commit
643
- commit, merge = choice_commit(values: ['Merge? [y|N]'])
644
+ commit, merge = choice_commit(values: ['Merge? [y/N]'])
644
645
  merge = merge&.upcase == 'Y'
645
646
  end
646
647
  checkout(flag, commit: commit, merge: merge)
@@ -721,7 +722,7 @@ module Squared
721
722
  task flag, [:name, :commit] do |_, args|
722
723
  branch = param_guard(action, flag, args: args, key: :name)
723
724
  commit = commithead args.commit
724
- commit, track = choice_commit(values: ['Track? [Y|n]'], force: false) if commit == ':'
725
+ commit, track = choice_commit(values: ['Track? [Y/n]'], force: false) if commit == ':'
725
726
  switch(flag, branch: branch, commit: commit, track: track)
726
727
  end
727
728
  when :detach
@@ -824,7 +825,7 @@ module Squared
824
825
  []
825
826
  else
826
827
  commit = choice_refs 'Choose "onto" branch'
827
- target, opts = choice_commit(multiple: 2, values: ['Options'], reflog: false)
828
+ target, opts = choice_commit(reflog: false, multiple: 2, values: ['Options'])
828
829
  branch, upstream = target
829
830
  OptionPartition.strip(opts)
830
831
  end
@@ -1476,7 +1477,9 @@ module Squared
1476
1477
  source op
1477
1478
  cached = git_spawn 'diff --cached --name-only --no-color'
1478
1479
  if amend || !cached.empty? || dryrun?
1479
- puts cached if adding.empty? && !cached.empty? && banner?
1480
+ if adding.empty? && !cached.empty? && banner?
1481
+ puts(cached.lines.map! { |val| "cached #{shell_quote(val.chomp)}" })
1482
+ end
1480
1483
  source co
1481
1484
  source pu
1482
1485
  elsif banner?
@@ -1642,21 +1645,19 @@ module Squared
1642
1645
  source
1643
1646
  end
1644
1647
 
1645
- def submodule(flag, opts = [], path: nil, url: nil)
1648
+ def submodule(flag, opts = [], branch: nil, path: nil, url: nil)
1646
1649
  cmd, opts = git_session('submodule', opts: opts)
1647
1650
  op = OptionPartition.new(opts, OPT_GIT[:submodule].fetch(flag, []), cmd, project: self)
1648
1651
  case flag
1649
1652
  when :branch, :url
1650
1653
  op.adjoin("set-#{flag}")
1651
- op << '--'
1652
- op.add_path(path) if path
1653
- op.add_quote(url) if url
1654
+ op.add_quote(branch, '--', path, url)
1654
1655
  else
1655
1656
  op.adjoin(flag)
1656
1657
  op << '--recursive' if option('recursive', 'r')
1657
1658
  op.splice(path: true)
1658
1659
  end
1659
- source
1660
+ print_success if success?(source, flag == :branch)
1660
1661
  end
1661
1662
 
1662
1663
  def restore(flag, opts = [], commit: nil, files: nil)
@@ -132,13 +132,13 @@ module Squared
132
132
  task action, [:save] do |_, args|
133
133
  save = param_guard(action, 'save', args: args, key: :save)
134
134
  exact = true if save.delete_prefix!('=')
135
- case save
136
- when 'prod', 'dev', 'optional', 'peer'
137
- packages = args.extras
138
- else
139
- save = 'prod'
140
- packages = args.to_a
141
- end
135
+ packages = case save
136
+ when 'prod', 'dev', 'optional', 'peer'
137
+ args.extras
138
+ else
139
+ save = 'prod'
140
+ args.to_a
141
+ end
142
142
  param_guard(action, 'name', args: packages)
143
143
  depend(:add, packages: packages, save: save, exact: exact)
144
144
  end
@@ -374,7 +374,7 @@ module Squared
374
374
  target.mkpath
375
375
  subdir << target.to_s
376
376
  end
377
- FileUtils.cp(basepath(s), dest, verbose: verbose.is_a?(Numeric) && verbose > 0)
377
+ FileUtils.cp(basepath(s), dest, verbose: verbosetype > 0)
378
378
  rescue StandardError => e
379
379
  print_error e
380
380
  errors += 1
@@ -416,7 +416,7 @@ module Squared
416
416
  end
417
417
  target.each do |src, to|
418
418
  glob.each { |val| log.info "cp #{from + val} #{to}" }
419
- copy_dir(src, to, glob, create: create, link: link, force: force, pass: pass, verbose: verbose)
419
+ copy_dir(src, to, glob, create: create, link: link, force: force, pass: pass, verbose: verbosetype > 0)
420
420
  rescue StandardError => e
421
421
  on_error e, :copy
422
422
  end
@@ -487,7 +487,7 @@ module Squared
487
487
 
488
488
  def outdated(flag = nil, opts = [], sync: invoked_sync?('outdated', flag))
489
489
  dryrun = opts.include?('dry-run') || opts.include?('d')
490
- if pnpm? && read_packagemanager(version: '7.15', update: true)
490
+ if pnpm?
491
491
  cmd = session 'pnpm', 'outdated'
492
492
  dryrun ||= dryrun?('pnpm')
493
493
  else
@@ -532,7 +532,19 @@ module Squared
532
532
  next
533
533
  end
534
534
  current = val['current'] || file
535
- want = rev == :major && !latest[SEM_VER, 6] ? latest : val['wanted']
535
+ want = val['wanted']
536
+ unless latest[SEM_VER, 6]
537
+ case rev
538
+ when :major
539
+ want = latest
540
+ when :minor
541
+ want = latest if latest[SEM_VER, 1] == want[SEM_VER, 1]
542
+ when :patch
543
+ if (g = latest.match(SEM_VER)) && (h = want.match(SEM_VER)) && g[1] == h[1] && g[3] == h[3]
544
+ want = latest
545
+ end
546
+ end
547
+ end
536
548
  next unless (current != want || file != want) && (want.match?(SEM_VER) || !file.match?(SEM_VER))
537
549
 
538
550
  f = semscan file
@@ -849,7 +861,7 @@ module Squared
849
861
  when String
850
862
  target
851
863
  when Hash
852
- append_hash(target).join(' ')
864
+ append_hash(target, target: []).join(' ')
853
865
  when Enumerable
854
866
  target.to_a.join(' ')
855
867
  else
@@ -7,7 +7,7 @@ module Squared
7
7
  DEP_PYTHON = %w[poetry.lock setup.cfg pyproject.toml setup.py requirements.txt].freeze
8
8
  DIR_PYTHON = (DEP_PYTHON + %w[README.rst]).freeze
9
9
  OPT_PYTHON = {
10
- common: %w[b B d E h i I O OO P q s S u v x c=q m=b W=b X=q check-hash-based-pycs=b].freeze,
10
+ common: %w[b B d E h i I O P q s S u v x c=q m=b W=b X=q check-hash-based-pycs=b].freeze,
11
11
  build: %w[n|no-isolation s|sdist x|skip-dependency-check v|verbose w|wheel C|config-setting=q installer=b
12
12
  o|outdir=p].freeze,
13
13
  venv: %w[clear copies symlinks system-site-packages upgrade upgrade-deps without-scm-ignore-files without-pip
@@ -636,7 +636,7 @@ module Squared
636
636
  def python_session(*cmd, opts: nil)
637
637
  return session('python', *preopts(quiet: false), *cmd, path: venv.nil?) unless opts
638
638
 
639
- op = OptionPartition.new(opts, OPT_PYTHON[:common], project: self, single: /\Av+\z/)
639
+ op = OptionPartition.new(opts, OPT_PYTHON[:common], project: self, single: /\A(?:v+|OO)\z/)
640
640
  ret = session('python', *op.to_a, *cmd, path: venv.nil?)
641
641
  [ret, op.extras]
642
642
  end
@@ -895,7 +895,7 @@ module Squared
895
895
  op = OptionPartition.new(opts, OPT_PYTHON[:venv], cmd, project: self)
896
896
  status = op.append(dir, delim: true)
897
897
  .clear(pass: false)
898
- .arg?(/\A-v+\z/, 'verbose')
898
+ .arg?(/\A-v+\z/)
899
899
  run(op, env, exception: true, banner: banner)
900
900
  puts(dir.directory? ? "Success: #{dir}" : 'Failed') if banner && !status
901
901
  end
@@ -312,7 +312,7 @@ module Squared
312
312
  c = glob[i] || glob.first
313
313
  log.info "cp #{a + c} #{b}"
314
314
  begin
315
- copy_dir(a, b, c, pass: pass, verbose: verbose)
315
+ copy_dir(a, b, c, pass: pass, verbose: verbosetype > 0)
316
316
  rescue StandardError => e
317
317
  on_error e, :copy
318
318
  end
@@ -528,7 +528,9 @@ module Squared
528
528
  if @asdf
529
529
  cmd = 'asdf'
530
530
  ver = '.tool-versions'
531
- `asdf current #{@asdf.first}`[/\A\S+\s+\S+/, 0].sub(/\s+/, ' ')
531
+ opt = [@asdf.first]
532
+ opt.unshift('--no-header') unless @@asdf[1] == 15
533
+ `asdf current #{opt.join(' ')}`[/^\S+\s+\S+/, 0].sub(/\s+/, ' ')
532
534
  else
533
535
  ver = nil
534
536
  `ruby --version`
@@ -10,9 +10,10 @@ module Squared
10
10
  include Common::Shell
11
11
  extend Forwardable
12
12
 
13
+ OPT_NAME = /\A(?:(--)|-)((?(1)[A-Za-z\d]+|[A-Za-z\d]))\z/
13
14
  OPT_VALUE = /\A-{0,2}([^= ]+)(?: *= *| +)(.+)\z/
14
15
  OPT_SINGLE = /\A-([A-Za-z\d])(.+)\z/
15
- private_constant :OPT_VALUE, :OPT_SINGLE
16
+ private_constant :OPT_NAME, :OPT_VALUE, :OPT_SINGLE
16
17
 
17
18
  class << self
18
19
  include Common::Format
@@ -70,14 +71,14 @@ module Squared
70
71
  def strip(val)
71
72
  return [] unless val
72
73
 
73
- val = shell_split(val) if val.is_a?(String)
74
- val.map { |s| s.sub(OPT_SINGLE, '\1=\2').sub(OPT_VALUE, '\1=\2') }.reject(&:empty?)
74
+ val = shell_split val if val.is_a?(String)
75
+ val.map { |s| s.sub(OPT_SINGLE, '\1=\2').sub(OPT_VALUE, '\1=\2').sub(OPT_NAME, '\2') }.reject(&:empty?)
75
76
  end
76
77
 
77
78
  def select(list, bare: true, no: true, single: false, double: false)
78
79
  ret = bare ? list.grep_v(/=/) : list.grep(/=/).map! { |val| val.split('=', 2).first }
79
80
  ret.map! { |val| val.split('|', 2).last }
80
- ret = ret.grep_v(/^no-/) unless no
81
+ ret = ret.grep_v(/\Ano-/) unless no
81
82
  return ret if single == double
82
83
 
83
84
  ret.select { |val| single ? val.size == 1 : val.size > 1 }
@@ -247,9 +248,9 @@ module Squared
247
248
  add quote_option(key, val, double: qq.include?(key), merge: merge)
248
249
  elsif p.include?(key)
249
250
  if val.match?(/\A(["']).+\1\z/)
250
- add shell_option(key, val, escape: false, merge: merge, sep: sep)
251
+ add shell_option(key, val, escape: false, merge: merge)
251
252
  elsif path
252
- add quote_option(key, path + val, merge: merge, sep: sep)
253
+ add quote_option(key, path + val, merge: merge)
253
254
  else
254
255
  push opt
255
256
  end
@@ -414,7 +415,7 @@ module Squared
414
415
  end
415
416
 
416
417
  def add_quote(*args, **kwargs)
417
- merge(args.map! { |val| shell_quote(val, **kwargs) })
418
+ merge(args.compact.map! { |val| val == '--' ? val : shell_quote(val, **kwargs) })
418
419
  self
419
420
  end
420
421
 
@@ -132,19 +132,20 @@ module Squared
132
132
  task 'all' do |_, args|
133
133
  stage ||= 'all'
134
134
  ns['sync'].invoke(*args.to_a)
135
- next if env('REPO_STAGE', equals: '1')
135
+ next if (stage = env('REPO_STAGE')) == '1'
136
136
 
137
137
  @project.select do |_, proj|
138
138
  next unless proj.enabled?(proj.workspace.baseref)
139
139
 
140
140
  proj.depend(sync: true) if proj.depend?
141
- next if env('REPO_STAGE', equals: '2')
141
+ next if stage == '2'
142
142
 
143
143
  proj.build?
144
144
  end
145
145
  .each_value do |proj|
146
146
  proj.build(sync: true)
147
- next unless proj.dev? && proj.copy? && !env('REPO_STAGE', equals: '3')
147
+ next if stage == '3'
148
+ next unless proj.copy? && (proj.dev? || stage == '4')
148
149
 
149
150
  if (ws = proj.workspace).task_defined?(target = task_join(proj.name, 'copy'))
150
151
  task_invoke(target, **ws.invokeargs)
@@ -11,6 +11,26 @@ module Squared
11
11
  def hashlist
12
12
  Hash.new { |data, key| data[key] = [] }
13
13
  end
14
+
15
+ def hashdup(data, pass: [])
16
+ ret = {}
17
+ data.each do |key, val|
18
+ ret[key] = case val
19
+ when Hash
20
+ if pass.include?(val)
21
+ val
22
+ else
23
+ pass << val
24
+ hashdup(val, pass: pass)
25
+ end
26
+ when Proc, Method
27
+ val
28
+ else
29
+ val.dup
30
+ end
31
+ end
32
+ ret
33
+ end
14
34
  end
15
35
  end
16
36
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: squared
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.9
4
+ version: 0.5.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - An Pham
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2025-10-18 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: rake
@@ -110,6 +111,7 @@ metadata:
110
111
  homepage_uri: https://github.com/anpham6/squared-ruby
111
112
  source_code_uri: https://github.com/anpham6/squared-ruby
112
113
  documentation_uri: https://squared.readthedocs.io
114
+ post_install_message:
113
115
  rdoc_options: []
114
116
  require_paths:
115
117
  - lib
@@ -124,7 +126,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
126
  - !ruby/object:Gem::Version
125
127
  version: '0'
126
128
  requirements: []
127
- rubygems_version: 3.7.2
129
+ rubygems_version: 3.2.33
130
+ signing_key:
128
131
  specification_version: 4
129
132
  summary: Rake task generator for managing multi-language workspaces.
130
133
  test_files: []