squared 0.4.9 → 0.4.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: 81e1fa9c773a59c8cb351dec47eb27b4c858c6266c83bd14be02c621cf68ad57
4
- data.tar.gz: 23ccad44a3f5fbc1ade8d5950692fd401566f862b0131debebdd9d5d1a4d6dab
3
+ metadata.gz: fa16edc572cc39bb0bef0b2b5ba04419f2994e824923b354182f9e60b6aee1b2
4
+ data.tar.gz: 75a6b805d236d45f09b331bf62cbfcc5f9b043632d194474511763c7fc7fbf64
5
5
  SHA512:
6
- metadata.gz: '00945ddcf48ae0482bdb40f143117ae464a03491fadf6c33b2e34c9f50b4d628c4b72b43c1a388e2c8c175067f62970c7ac60721330c1340a48103c4b972b560'
7
- data.tar.gz: d3846c85547ee3acb6d040703d21b7a61630f502c17da71f8d0ddf9032c67930819b933b3c3896a0e2b23c2664bfc2ed5454d95313849a9885c429d0cfacb5f1
6
+ metadata.gz: 510b7807e4e2be74dfe8ba5202115e3b5cefbd27d94cb14af169007bcdedde7f8c8516679c1bc614cb633eb1a904a6309403346f7c0c8490ad13fa208f2bd1f0
7
+ data.tar.gz: 3a124f06b78467a4eda6a1f34c2a80f6bb9f78867c5daefbcb0f628d9eb6a4bfbf0193e0d5509f38000e7e6ce6c8c3f4d112f0c0146260d30664692cda495186
data/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.11] - 2025-05-09
4
+
5
+ ### Added
6
+
7
+ - Docker command build action bake is interactive.
8
+
9
+ ### Fixed
10
+
11
+ - Docker build command was not functional.
12
+
13
+ ## [0.4.10] - 2025-05-08
14
+
15
+ ### Added
16
+
17
+ - Git command pull and fetch action remote is interactive.
18
+ - Ruby command script uses multiline input when available.
19
+ - Ruby command file is interactive.
20
+ - Node command publish can accept access option levels.
21
+ - Docker command image action rm when called empty is interactive.
22
+
23
+ ### Fixed
24
+
25
+ - Disabled batch and alias tasks were not hidden.
26
+ - Docker tags from ENV were appended twice.
27
+ - Git events were not always registered.
28
+ - Git revbuild did not write lock data file with parallel tasks.
29
+
30
+ ### Changed
31
+
32
+ - Choice selection default was set to no timeout.
33
+ - Git command add can accept a file search pattern.
34
+
3
35
  ## [0.4.9] - 2025-04-27
4
36
 
5
37
  ### Added
@@ -633,6 +665,8 @@
633
665
 
634
666
  - Changelog was created.
635
667
 
668
+ [0.4.11]: https://github.com/anpham6/squared/releases/tag/v0.4.11-ruby
669
+ [0.4.10]: https://github.com/anpham6/squared/releases/tag/v0.4.10-ruby
636
670
  [0.4.9]: https://github.com/anpham6/squared/releases/tag/v0.4.9-ruby
637
671
  [0.4.8]: https://github.com/anpham6/squared/releases/tag/v0.4.8-ruby
638
672
  [0.4.7]: https://github.com/anpham6/squared/releases/tag/v0.4.7-ruby
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # squared 5.4
1
+ # squared 5.5
2
2
 
3
3
  ## Documentation
4
4
 
@@ -15,10 +15,11 @@
15
15
  * [squared-express](https://github.com/anpham6/squared-express#readme)
16
16
  * [E-mc](https://github.com/anpham6/e-mc#readme)
17
17
  * [Pi-r](https://github.com/anpham6/pi-r#readme)
18
+ * [Pi-r2](https://github.com/anpham6/pi-r2#readme)
18
19
 
19
20
  ## Installation
20
21
 
21
- * NodeJS 16 LTS
22
+ * NodeJS 18
22
23
 
23
24
  ### NPX
24
25
 
@@ -131,26 +132,22 @@ rake clone # node + docs
131
132
 
132
133
  ```sh
133
134
  # NODE_TAG=latest
134
- # RUBY_VERSION=2.4.0-3.3.0
135
- # MANIFEST=nightly,staging,prod,android
135
+ # RUBY_VERSION=2.4.0-3.4.0
136
+ # MANIFEST=nightly,prod,latest,android
136
137
  # BUILD={dev,prod}
137
138
  # DEV={0,1,local}
138
139
  # DOCS=any
139
140
  # PIPE_FAIL={0,1}
140
141
  # PORT=3000
141
- docker build -t squared --build-arg MANIFEST=prod --build-arg SQUARED=prod .
142
- docker build -t node --build-arg NODE_TAG=20 --build-arg NODE_INSTALL=pnpm -f Dockerfile.slim . # no docs
143
- docker buildx bake node
142
+ docker build -t squared --build-arg MANIFEST=prod --build-arg NODE_ENV=production .
143
+ docker build -t node --build-arg NODE_TAG=22 --build-arg NODE_INSTALL=pnpm -f Dockerfile.slim .
144
+ NODE=22 docker buildx bake node
144
145
  # OR
145
- # RUBY_TAG=latest
146
- # NODE_VERSION=22.x
147
- docker build -t ruby --build-arg RUBY_TAG=3.0.0 --build-arg NODE_VERSION=20.x --build-arg PIPE_FAIL=0 -f Dockerfile.ruby .
148
- docker buildx bake ruby
146
+ docker build -t ruby --build-arg RUBY_TAG=3.4.0 --build-arg NODE_VERSION=22 --build-arg PIPE_FAIL=0 -f Dockerfile.ruby .
147
+ RUBY=3.4.0 docker buildx bake ruby
149
148
  # OR
150
- # NGINX_VERSION=1.27
151
- # NGINX_VARIANT=bookworm
152
- docker build -t nginx --build-arg NGINX_VERSION=1.27 --build-arg PORT=3000 --build-arg NODE_VERSION=18.x -f Dockerfile.nginx .
153
- docker buildx bake nginx
149
+ docker build -t nginx --build-arg NGINX_VERSION=1.27 --build-arg PORT=3000 --build-arg NODE_VERSION=20 -f Dockerfile.nginx .
150
+ NGINX=1.27 docker buildx bake nginx
154
151
 
155
152
  # Express
156
153
  docker run -it --name express --rm -p 3000:3000 \
@@ -521,6 +518,7 @@ squared.settings = {
521
518
  ],
522
519
  compressImages: false, // TinyPNG API Key <https://tinypng.com/developers>
523
520
  compressImages: "****************", // API key
521
+ compressImages: [{ plugin: "imagemin-pngquant", format: "png", options: { quality: [0.6, 0.8] } }], // v5.5
524
522
  convertImages: "", // png | jpeg | webp | gif | bmp
525
523
  showAttributes: true,
526
524
  showAttributes: {
@@ -541,7 +539,7 @@ squared.settings = {
541
539
  showErrorMessages: false,
542
540
  convertPixels: "dp", // "sp" | "pt" | "in" | "mm"
543
541
  convertLineHeight: "sp", // "dp" | "pt" | "in" | "mm"
544
- convertEntities: ["numeric"], // v5.4
542
+ convertEntities: ["numeric"],
545
543
  convertEntities: ["codepoints", {/* JSON (last) */}], // https://html.spec.whatwg.org/entities.json
546
544
  insertSpaces: 4,
547
545
  outputDocumentHandler: "android",
@@ -1361,4 +1359,4 @@ NOTE: Defining an element "**id**" will prevent it from being removed during the
1361
1359
 
1362
1360
  ## LICENSE
1363
1361
 
1364
- BSD 3-Clause
1362
+ BSD 3-Clause
data/README.ruby.md CHANGED
@@ -98,6 +98,9 @@ Workspace::Application
98
98
  with(exclude: [:base]) { add("publish/*", "packages") } # rake packages:sqd-serve:build
99
99
  # OR
100
100
  add(["publish/sqd-cli", "publish/sqd-serve"], true, exclude: [:base]) # rake squared:sqd-serve:build
101
+
102
+ # Git
103
+ revbuild(include: %w[src/ framework/ types/]) # Synchronous is recommended
101
104
  end
102
105
  .add("squared/sqd", exclude: :git, pass: [:node, "checkout", "bump"]) do # Skip initialize(:node) + squared:checkout:* + squared:bump:*
103
106
  variable_set :script, "build:sqd" # Override detection
@@ -116,9 +119,9 @@ Workspace::Application
116
119
  end
117
120
  end
118
121
  end
119
- .pass("pull", group: "default") { test? || doc? } # pathname:pull | optparse:pull
122
+ .pass("pull", group: "default") { test? || doc? } # rake pathname:pull | rake optparse:pull
120
123
  .style("banner", 255.255) # 256 colors (fg | fg.bg | -0.bg)
121
- .build(default: "build", parallel: ["pull", "fetch", "rebase", "archive", "copy", "clean", /^outdated:/], pass: ["publish"]) do |workspace|
124
+ .build(default: "build", parallel: ["pull", "fetch", "rebase", "archive", "clean", /^outdated:/], pass: ["publish"]) do |workspace|
122
125
  workspace
123
126
  .enable_aixterm
124
127
  .style({
@@ -194,9 +197,9 @@ Workspace::Application
194
197
  "pir": { # rake pir:clone
195
198
  uri: "https://github.com/anpham6/pi-r", #
196
199
  options: { #
197
- "origin": "github", # --origin='github'
200
+ "origin": "github", # --origin=github
198
201
  "recurse-submodules": false, # --no-recurse-submodules
199
- "shallow-exclude": ["v0.0.1", "v0.0.2"] # --shallow-exclude='v0.0.1' --shallow-exclude='v0.0.2'
202
+ "shallow-exclude": ["v0.0.1", "v0.0.2"] # --shallow-exclude=v0.0.1 --shallow-exclude=v0.0.2
200
203
  }
201
204
  }
202
205
  )
@@ -238,7 +241,7 @@ Workspace::Application
238
241
  .new(main: "squared")
239
242
  .graph(["depend"], ref: :git) # Optional
240
243
  .with(:python) do
241
- doc(windows? ? '.\make.bat html' : 'make html')
244
+ doc(windows? ? ".\make.bat html" : "make html") # rake android-docs:doc | rake doc:python
242
245
  add("android-docs", "android", venv: "/home/user/.venv") # rake android-docs:depend
243
246
  add("chrome-docs", "chrome", graph: "android", venv: ".venv") do # /workspaces/chrome-docs/.venv
244
247
  variable_set :dependindex, 2 # Use Poetry for dependencies (optional)
@@ -490,7 +493,7 @@ rake squared:log:view[H1,HEAD^5,all,lib,./H12345] # git log --all @~1 @^5 -- 'l
490
493
  All project binary programs can have their executable path set to a non-global alias.
491
494
 
492
495
  ```ruby
493
- Common::PATH.merge!({
496
+ Common::PATH.update({
494
497
  GIT: "/usr/bin/git", # PATH_GIT=/usr/bin/git
495
498
  TAR: "/opt/archivers/tar", # PATH_TAR=/opt/archivers/tar
496
499
  UNZIP: "/opt/archivers/unzip",
@@ -593,6 +596,7 @@ GIT_AUTOSTASH_${NAME}=0 # rebase (project only)
593
596
  | log | * | PATHSPEC=s |
594
597
  | pull | rebase | AUTOSTASH |
595
598
  | pull | remote | REFSPEC=s |
599
+ | pull | -remote | ALL |
596
600
  | pull | * | REBASE=0,1 FORCE RECURSE_SUBMODULES=0,1,s |
597
601
  | rebase | branch | HEAD=s |
598
602
  | rebase | onto | INTERACTIVE I HEAD=s |
@@ -621,6 +625,7 @@ DOCKER_OPTIONS_${NAME}=v,no-cache=false # project only (override)
621
625
  DOCKER_TAG=latest # all
622
626
  DOCKER_TAG_${NAME}=v0.1.0 # project only (override)
623
627
  DOCKER_ALL=1 # list every image/container
628
+ DOCKER_Y=1 # confirm all
624
629
  ```
625
630
 
626
631
  | Command | Flag | ENV |
@@ -629,6 +634,7 @@ DOCKER_ALL=1 # list every image/container
629
634
  | buildx | bake | SERVICE=s |
630
635
  | compose | build | TARGET=s |
631
636
  | container | commit | REGISTRY=s PLATFORM=s DISABLE_CONTENT_TRUST=0,1 |
637
+ | image | rm | Y=0,1 |
632
638
  | image | push | TAG=s REGISTRY=s |
633
639
 
634
640
  ### Repo
@@ -8,8 +8,8 @@ module Squared
8
8
  def confirm(msg, default = nil, agree: 'Y', cancel: 'N', attempts: 5, timeout: 30)
9
9
  require 'readline'
10
10
  require 'timeout'
11
- agree = /^#{agree}$/i if agree.is_a?(::String)
12
- cancel = /^#{cancel}$/i if cancel.is_a?(::String)
11
+ agree = /^#{Regexp.escape(agree)}$/i if agree.is_a?(::String)
12
+ cancel = /^#{Regexp.escape(cancel)}$/i if cancel.is_a?(::String)
13
13
  Timeout.timeout(timeout) do
14
14
  begin
15
15
  while (ch = Readline.readline(msg))
@@ -32,24 +32,28 @@ module Squared
32
32
  end
33
33
  end
34
34
 
35
- def choice(msg, list = nil, min: 1, max: 1, multiple: false, auto: true, force: true, attempts: 5, timeout: 60)
35
+ def choice(msg, list = nil, min: 1, max: 1, multiple: false, force: true, grep: nil, auto: true,
36
+ attempts: 5, timeout: 0)
36
37
  require 'readline'
37
38
  require 'timeout'
38
39
  if list
40
+ grep &&= (grep.is_a?(::Enumerable) ? grep : [grep]).map { |val| Regexp.new(val) }
39
41
  items = []
40
- list.each_with_index do |val, index|
41
- puts "#{index.succ.to_s.rjust(2)}. #{val}"
42
+ list.each do |val|
43
+ next if grep&.none? { |pat| pat.match?(line) }
44
+
42
45
  items << val.chomp
46
+ puts "#{items.size.to_s.rjust(2)}. #{val}"
43
47
  end
44
48
  max = items.size
49
+ raise_error 'empty selection list' if max == 0
50
+ min = [min, max].min
45
51
  if auto
46
52
  msg = "#{msg}: [1-#{max}#{if multiple
47
53
  "|,#{multiple.is_a?(Numeric) ? "{#{multiple}}" : ''}"
48
54
  end}] "
49
55
  end
50
56
  end
51
- return unless max >= min
52
-
53
57
  valid = ->(s) { s.match?(/^-?\d+$/) && s.to_i.between?(min, max) }
54
58
  Timeout.timeout(timeout) do
55
59
  begin
@@ -82,16 +86,33 @@ module Squared
82
86
  end
83
87
  end
84
88
 
85
- def readline(msg, history = false, force: nil)
89
+ def readline(msg, history = false, force: nil, multiline: nil, &blk)
86
90
  require 'readline'
91
+ multiline = if multiline && Readline.respond_to?(:readmultiline)
92
+ multiline.is_a?(::Enumerable) || block_given? ? multiline : [multiline.to_s]
93
+ end
94
+ prompt = lambda do
95
+ if !multiline
96
+ Readline.readline(msg, history)
97
+ elsif block_given?
98
+ Readline.readmultiline(msg, history, &blk)
99
+ else
100
+ Readline.readmultiline(msg, history) { |line| multiline.any? { |val| line.split.last.end_with?(val) } }
101
+ end
102
+ end
87
103
  case force
88
104
  when ::TrueClass, ::FalseClass
89
- msg = "#{msg} (#{force ? 'required' : 'optional'}): "
90
- ret = Readline.readline(msg, history)&.strip || ''
105
+ msg = "#{msg} %s " % if multiline
106
+ multiline.is_a?(::Enumerable) ? "{#{multiline.join('|')}}" : multiline
107
+ else
108
+ "(#{force ? 'required' : 'optional'}):"
109
+ end
110
+ ret = (prompt.call || '').strip
111
+ multiline.each { |val| break if ret.delete_suffix!(val) } if multiline.is_a?(::Enumerable)
91
112
  raise_error 'user cancelled' if force && ret.empty?
92
113
  ret
93
114
  else
94
- Readline.readline(msg, history)
115
+ prompt.call
95
116
  end
96
117
  end
97
118
  end
@@ -29,10 +29,10 @@ module Squared
29
29
  dest.mkpath if create
30
30
  if pass
31
31
  exclude = []
32
- pass = [pass] unless pass.is_a?(::Array)
32
+ pass = [pass] unless pass.is_a?(::Enumerable)
33
33
  pass.each { |val| exclude.concat(Dir.glob(src + val)) }
34
34
  end
35
- (glob.is_a?(::Array) ? glob : [glob]).each do |val|
35
+ (glob.is_a?(::Enumerable) ? glob : [glob]).each do |val|
36
36
  Dir.glob(src + val) do |path|
37
37
  next if exclude&.include?(path) || (path = Pathname.new(path)).directory?
38
38
 
@@ -79,7 +79,7 @@ module Squared
79
79
  def copy_guard(src, dest, link: nil, force: false, verbose: true)
80
80
  unless force
81
81
  if (path = Pathname.new(dest)).directory?
82
- src = [src] unless src.is_a?(::Array)
82
+ src = [src] unless src.is_a?(::Enumerable)
83
83
  src = src.reject { |val| path.join(File.basename(val)).exist? }
84
84
  return if src.empty?
85
85
  elsif path.exist?
@@ -89,7 +89,7 @@ module Squared
89
89
  case link
90
90
  when 'hard', 1
91
91
  FileUtils.ln(src, dest, force: force, verbose: verbose)
92
- when TrueClass, 'soft', 0
92
+ when ::TrueClass, 'soft', 0
93
93
  FileUtils.ln_s(src, dest, force: force, verbose: verbose)
94
94
  else
95
95
  FileUtils.cp(src, dest, verbose: verbose)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Squared
4
- VERSION = '0.4.9'
4
+ VERSION = '0.4.11'
5
5
  end
@@ -463,7 +463,7 @@ module Squared
463
463
  tasks << key if obj.has?(key, baseref)
464
464
  elsif (batch = series.batch_get(key))
465
465
  obj.allref.each do |ref|
466
- next unless (data = batch[ref])
466
+ next unless obj.has?(key, ref) && (data = batch[ref])
467
467
 
468
468
  data.each do |val|
469
469
  if (items = task_resolve(obj, val)).empty?
@@ -482,7 +482,7 @@ module Squared
482
482
  return [] if (base && !obj.ref?(baseref)) || !(data = series.alias_get(key))
483
483
 
484
484
  obj.allref.each do |ref|
485
- next unless (alt = data[ref])
485
+ next unless obj.has?(key, ref) && (alt = data[ref])
486
486
 
487
487
  ret = task_resolve(obj, alt)
488
488
  break unless ret.empty?
@@ -602,6 +602,7 @@ module Squared
602
602
  end
603
603
 
604
604
  def powershell?
605
+ return true if ENV['SHELL']&.end_with?(File.join('', 'pwsh'))
605
606
  return false unless windows?
606
607
 
607
608
  case ENV['TERM_PROGRAM']
@@ -1505,11 +1505,11 @@ module Squared
1505
1505
  confirm("Upgrade to #{a}? #{b + e} [#{c}] ", d)
1506
1506
  end
1507
1507
 
1508
- def choice_index(msg, list, values: nil, multiple: false, accept: nil, series: false, trim: nil, column: nil,
1509
- attempts: 5, force: true)
1508
+ def choice_index(msg, list, values: nil, accept: nil, series: false, trim: nil, column: nil,
1509
+ multiple: false, force: true, **kwargs)
1510
1510
  puts if !series && @@print_order > 0
1511
1511
  msg = "#{msg} (optional)" unless force
1512
- unless (ret = choice(msg, list, multiple: multiple, force: force, attempts: attempts)) || !force
1512
+ unless (ret = choice(msg, list, multiple: multiple, force: force, **kwargs)) || !force
1513
1513
  raise_error 'user cancelled'
1514
1514
  end
1515
1515
  if ret.nil? || ret.empty?
@@ -1855,14 +1855,14 @@ module Squared
1855
1855
  end
1856
1856
 
1857
1857
  def has_value?(data, val)
1858
- return data.value?(val) if data.is_a?(::Hash)
1859
- return data.is_a?(::Enumerable) && data.to_a.include?(val) if !val.is_a?(::Enumerable) || val.is_a?(::Hash)
1858
+ return data.value?(val) if data.is_a?(Hash)
1859
+ return data.is_a?(Enumerable) && data.to_a.include?(val) if !val.is_a?(Enumerable) || val.is_a?(Hash)
1860
1860
 
1861
1861
  val.to_a.any? do |obj|
1862
1862
  case data
1863
- when ::Hash
1863
+ when Hash
1864
1864
  data.value?(obj)
1865
- when ::Enumerable
1865
+ when Enumerable
1866
1866
  data.to_a.include?(obj)
1867
1867
  end
1868
1868
  end
@@ -138,10 +138,14 @@ module Squared
138
138
  buildx(:build, args.extras, "#{flag}": param)
139
139
  end
140
140
  when :bake
141
- format_desc action, flag, 'opts*,target*,context?'
141
+ format_desc action, flag, ':?,opts*,target*,context?'
142
142
  task flag do |_, args|
143
- args = param_guard(action, flag, args: args.to_a)
144
- buildx flag, args
143
+ args = args.to_a
144
+ if args.first == ':'
145
+ choice_command :bake
146
+ else
147
+ buildx flag, args
148
+ end
145
149
  end
146
150
  end
147
151
  when 'compose'
@@ -196,7 +200,12 @@ module Squared
196
200
  when :list, :rm
197
201
  format_desc(action, flag, flag == :rm ? 'id*,opts*' : 'opts*,args*')
198
202
  task flag do |_, args|
199
- image flag, args.to_a
203
+ args = args.to_a
204
+ if flag == :rm && args.empty?
205
+ choice_command :rm
206
+ else
207
+ image flag, args
208
+ end
200
209
  end
201
210
  end
202
211
  when 'network'
@@ -221,11 +230,11 @@ module Squared
221
230
  image(:rm, sync: sync)
222
231
  end
223
232
 
224
- def compose(opts, flags = nil, script: false, args: nil, from: :build, **)
233
+ def compose(opts, flags = nil, script: false, args: nil, from: :run, **)
225
234
  return opts if script == false
226
235
 
227
236
  ret = docker_session
228
- if from == :build
237
+ if from == :run
229
238
  case (n = filetype)
230
239
  when 1, 2
231
240
  ret << 'buildx' << 'bake'
@@ -249,14 +258,16 @@ module Squared
249
258
  when Enumerable
250
259
  ret.merge(opts.to_a)
251
260
  end
252
- [args, flags].each_with_index do |target, index|
253
- next unless target
254
261
 
255
- target = append_any(target, target: []) unless target.is_a?(Array)
256
- ret.merge(target.map { |arg| index == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
262
+ [args, flags].each_with_index do |target, index|
263
+ if target.is_a?(String)
264
+ ret << target
265
+ elsif (target = append_any(target, target: []))
266
+ ret.merge(target.map { |arg| index == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
267
+ end
257
268
  end
258
269
  case from
259
- when :build
270
+ when :run
260
271
  case @secrets
261
272
  when String
262
273
  ret << quote_option('secret', @secrets, double: true)
@@ -271,7 +282,6 @@ module Squared
271
282
  end
272
283
  if (val = option('tag', ignore: false))
273
284
  append_tag val
274
- ret << basic_option('tag', tagname(val))
275
285
  elsif !session_arg?('t', 'tag')
276
286
  append_tag tag
277
287
  end
@@ -331,7 +341,7 @@ module Squared
331
341
  def container(flag, opts = [], id: nil)
332
342
  cmd, opts = docker_session('container', flag, opts: opts)
333
343
  list = OPT_DOCKER[:container].fetch(flag, [])
334
- list.concat(OPT_DOCKER[:container][:update]) if flag == :run
344
+ list += OPT_DOCKER[:container][:update] if flag == :run
335
345
  op = OptionPartition.new(opts, list, cmd, project: self, args: flag == :run || flag == :exec)
336
346
  from = :"container:#{flag}"
337
347
  case flag
@@ -433,6 +443,8 @@ module Squared
433
443
  def image(flag, opts = [], sync: true, id: nil, registry: nil)
434
444
  cmd, opts = docker_session('image', flag, opts: opts)
435
445
  op = OptionPartition.new(opts, OPT_DOCKER[:image][flag], cmd, project: self)
446
+ exception = @exception
447
+ banner = true
436
448
  from = :"image:#{flag}"
437
449
  case flag
438
450
  when :list
@@ -455,6 +467,10 @@ module Squared
455
467
  when :rm
456
468
  if id
457
469
  op << id
470
+ if option('y')
471
+ exception = false
472
+ banner = false
473
+ end
458
474
  else
459
475
  if op.empty?
460
476
  list_image(flag, docker_output('image ls -a'), from: from) do |val|
@@ -476,9 +492,12 @@ module Squared
476
492
  img = docker_output 'image', 'tag', id, uri
477
493
  return unless confirm_command(img.to_s, cmd.to_s, target: id, as: registry, title: from)
478
494
 
479
- run(img, exception: true, sync: false, banner: false)
495
+ cmd = img
496
+ sync = false
497
+ exception = true
498
+ banner = false
480
499
  end
481
- run(sync: sync, from: from)
500
+ run(cmd, sync: sync, exception: exception, banner: banner, from: from)
482
501
  end
483
502
 
484
503
  def network(flag, opts = [], target: nil)
@@ -535,8 +554,12 @@ module Squared
535
554
  case flag
536
555
  when :run
537
556
  unless session_arg?('name', target: target)
538
- require 'random/formatter'
539
- target << basic_option('name', dnsname("#{name}_#{Random.new.alphanumeric(6)}"))
557
+ target << basic_option('name', dnsname("#{name}_%s" % if RUBY_VERSION >= '3.1'
558
+ require 'random/formatter'
559
+ Random.new.alphanumeric(6)
560
+ else
561
+ (0...6).map { rand(97..122).chr }.join
562
+ end))
540
563
  end
541
564
  when :exec
542
565
  raise_error('no command args', hint: from) if list.empty?
@@ -583,6 +606,8 @@ module Squared
583
606
  pwd_set do
584
607
  found = false
585
608
  index = 0
609
+ all = option('all', prefix: 'docker')
610
+ y = from == :'image:rm' && option('y', prefix: 'docker')
586
611
  pat = /^(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})(?:[_.,:-]|$)/
587
612
  IO.popen(session_done(cmd << '--format=json')).each do |line|
588
613
  data = JSON.parse(line)
@@ -596,41 +621,43 @@ module Squared
596
621
  id
597
622
  end)
598
623
  ee = data['Image'] || rt || aa
599
- next unless option('all') || ee.match?(pat) || aa.match?(pat)
600
-
601
- bb = index.succ.to_s
602
- cc = bb.size + 1
603
- a = sub_style(ee, styles: theme[:inline])
604
- b = "Execute #{sub_style(flag, styles: theme[:active])} on #{a}#{ee == id ? '' : " (#{id})"}"
605
- c, d = no ? ['y/N', 'N'] : ['Y/n', 'Y']
606
- e = time_format(time_since(data['CreatedAt']), pass: ['ms'])
607
- f = sub_style(ARG[:BORDER][0], styles: theme[:inline])
608
- g = ' ' * (cc + 1)
609
- h = "#{sub_style(bb.rjust(cc), styles: theme[:current])} #{f} "
610
- puts unless index == 0
611
- puts "#{h + sub_style(aa, styles: theme[:subject])} (created #{e} ago)"
612
- cols = %w[Tag Status Ports]
613
- cols << case flag
614
- when :connect, :disconnect
615
- 'Networks'
616
- else
617
- 'Size'
618
- end
619
- cols.each do |key|
620
- next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
624
+ next unless all || ee.match?(pat) || aa.match?(pat)
625
+
626
+ unless y
627
+ bb = index.succ.to_s
628
+ cc = bb.size + 1
629
+ a = sub_style(ee, styles: theme[:inline])
630
+ b = "Execute #{sub_style(flag, styles: theme[:active])} on #{a}#{ee == id ? '' : " (#{id})"}"
631
+ c, d = no ? ['y/N', 'N'] : ['Y/n', 'Y']
632
+ e = time_format(time_since(data['CreatedAt']), pass: ['ms'])
633
+ f = sub_style(ARG[:BORDER][0], styles: theme[:inline])
634
+ g = ' ' * (cc + 1)
635
+ h = "#{sub_style(bb.rjust(cc), styles: theme[:current])} #{f} "
636
+ puts unless index == 0
637
+ puts "#{h + sub_style(aa, styles: theme[:subject])} (created #{e} ago)"
638
+ cols = %w[Tag Status Ports]
639
+ cols << case flag
640
+ when :connect, :disconnect
641
+ 'Networks'
642
+ else
643
+ 'Size'
644
+ end
645
+ cols.each do |key|
646
+ next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
621
647
 
622
- puts "#{g + f} #{key}: #{as_a(data[key]).join(', ')}" unless data[key].to_s.empty?
623
- end
624
- w = 9 + flag.to_s.size + 4 + ee.size
625
- puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
626
- found = true
627
- index += 1
628
- next unless confirm("#{h + b}? [#{c}] ", d, timeout: 60)
648
+ puts "#{g + f} #{key}: #{as_a(data[key]).join(', ')}" unless data[key].to_s.empty?
649
+ end
650
+ w = 9 + flag.to_s.size + 4 + ee.size
651
+ puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
652
+ found = true
653
+ index += 1
654
+ next unless confirm("#{h + b}? [#{c}] ", d, timeout: 60)
629
655
 
630
- puts if @@print_order == 0
656
+ puts if @@print_order == 0
657
+ end
631
658
  yield id
632
659
  end
633
- puts log_message(Logger::INFO, 'none detected', subject: "#{name}:#{from}", hint: hint) unless found
660
+ puts log_message(Logger::INFO, 'none detected', subject: "#{name}:#{from}", hint: hint) unless found || y
634
661
  end
635
662
  rescue StandardError => e
636
663
  log.error e
@@ -658,10 +685,12 @@ module Squared
658
685
 
659
686
  def choice_command(flag)
660
687
  msg, cmd, index = case flag
661
- when :run
688
+ when :run, :rm
662
689
  ['Choose an image', 'images -a', 2]
663
690
  when :exec
664
691
  ['Choose a container', 'ps -a', 0]
692
+ when :bake
693
+ ['Choose a target', 'buildx bake --list=type=targets', 0]
665
694
  else
666
695
  ['Choose a network', 'network ls', 0]
667
696
  end
@@ -671,16 +700,26 @@ module Squared
671
700
  puts log_message(Logger::INFO, 'none found', subject: name, hint: "docker #{cmd}")
672
701
  else
673
702
  puts " # #{header}"
703
+ multiple = false
704
+ parse = ->(val) { val.split(/\s+/)[index] }
674
705
  case flag
675
706
  when :run, :exec
676
707
  values = [['Options', flag == :run], ['Arguments', flag == :exec]]
677
708
  cmd = flag.to_s
709
+ when :rm, :bake
710
+ values = ['Options']
711
+ multiple = true
712
+ cmd = flag == :rm ? 'image rm' : "buildx bake -f #{shell_quote(dockerfile)}"
678
713
  else
679
- values = [['Options', false], ['Container', true]]
714
+ values = ['Options', ['Container', true]]
680
715
  cmd = "network #{flag}"
681
716
  end
682
- out, opts, args = choice_index(msg, lines, values: values)
683
- ret = run docker_output(cmd, opts, '--', out.split(/\s+/)[index], args)
717
+ out, opts, args = choice_index(msg, lines, multiple: multiple, values: values)
718
+ ret = run docker_output(cmd, opts, '--', *(if out.is_a?(Array)
719
+ out.map! { |val| parse.call(val) }
720
+ else
721
+ [parse.call(out)]
722
+ end), args)
684
723
  print_success if success?(ret && cmd.start_with?('network'))
685
724
  end
686
725
  end
@@ -111,21 +111,30 @@ module Squared
111
111
  time_format(epoch, clock: clock)
112
112
  end
113
113
 
114
- def rev_clear(name)
114
+ def rev_clear(name, sync: true)
115
115
  if Dir.exist?(name) && (proj = find(name))
116
116
  name = proj.name
117
117
  end
118
- rev_write if rev_entry(name, 'revision', val: '', create: false)
118
+ rev_write(sync: sync) if rev_entry(name, 'revision', val: '', create: false)
119
119
  end
120
120
 
121
- def rev_write(name = nil, data = nil, utc: nil)
121
+ def rev_write(name = nil, data = nil, sync: true, utc: nil)
122
122
  return unless @revfile
123
123
 
124
124
  if name
125
125
  data&.each { |key, val| rev_entry(name, key, val: val) }
126
126
  rev_timeutc(name, utc) if utc
127
127
  end
128
- File.write(@revfile, JSON.pretty_generate(@revdoc))
128
+ sleep 0 while !sync && @revlock
129
+ begin
130
+ @revlock = true
131
+ File.write(@revfile, JSON.pretty_generate(@revdoc))
132
+ rescue StandardError => e
133
+ log&.debug e
134
+ warn log_message(Logger::WARN, e, pass: true) if warning?
135
+ ensure
136
+ @revlock = false
137
+ end
129
138
  end
130
139
 
131
140
  def git_clone?(path, name = nil)
@@ -279,17 +288,20 @@ module Squared
279
288
  namespace(name = ws.task_name('git')) do
280
289
  all = ws.task_join(name, 'all')
281
290
 
282
- ws.format_desc(all, %w[stash|rebase depend])
291
+ ws.format_desc(all, 'stash|rebase|autostash?,depend?')
283
292
  task 'all' do |_, args|
284
- opts = args.to_a
285
- cmd = if opts.include?('stash')
286
- [ws.task_sync('stash'), ws.task_sync('pull')]
287
- elsif opts.include?('rebase')
288
- [ws.task_sync('rebase')]
293
+ args = args.to_a
294
+ cmd = if args.include?('stash')
295
+ ['stash', 'pull']
296
+ elsif args.include?('rebase')
297
+ ['rebase']
298
+ elsif args.include?('autostash')
299
+ ['autostash']
289
300
  else
290
- [ws.task_sync('pull')]
301
+ ['pull']
291
302
  end
292
- cmd << ws.task_sync('depend') if opts.include?('depend') && !ws.series[:depend].empty?
303
+ cmd.map! { |val| ws.task_sync(val) }
304
+ cmd << ws.task_sync('depend') if args.include?('depend') && !ws.series.exclude?(:depend, true)
293
305
  cmd << ws.task_sync('build')
294
306
  Common::Utils.task_invoke(*cmd, **ws.invokeargs)
295
307
  end
@@ -355,8 +367,13 @@ module Squared
355
367
  if flag == :remote
356
368
  format_desc action, flag, 'remote,opts*'
357
369
  task flag, [:remote] do |_, args|
358
- remote = param_guard(action, flag, args: args, key: :remote)
359
- __send__(action, flag, args.extras, remote: remote)
370
+ if (remote = args.remote)
371
+ args = args.extras
372
+ else
373
+ remote = choice_remote
374
+ args = args.to_a.drop(1)
375
+ end
376
+ __send__(action, flag, args, remote: remote)
360
377
  end
361
378
  else
362
379
  format_desc action, flag, 'opts*'
@@ -845,12 +862,10 @@ module Squared
845
862
  args = args.to_a
846
863
  if args.empty? || args.last == ':'
847
864
  files = []
848
- git_spawn('status -s --porcelain', stdout: false).each do |line|
849
- next unless line =~ /^(.)(.)\s+(.+)$/
850
-
851
- case (flag == :staged ? $1 : $2)
852
- when 'A', 'M'
853
- files << $3
865
+ status_data.each do |line|
866
+ case (flag == :staged ? line[2] : line[1])
867
+ when /[AMDRTC]/
868
+ files << line[0]
854
869
  end
855
870
  end
856
871
  unless files.empty?
@@ -867,12 +882,11 @@ module Squared
867
882
  end
868
883
  when 'git'
869
884
  before = case flag
870
- when :rm then 'source+,destination'
885
+ when :mv then 'source+,destination'
871
886
  when :revert then 'commit+' end
872
887
  format_desc(action, flag, 'opts*', before: before, after: case flag
873
- when :add, :clean, :mv
874
- 'pathspec*'
875
- end)
888
+ when :add then 'pathspec*|:pattern:*'
889
+ when :clean, :rm then 'pathspec*' end)
876
890
  task flag do |_, args|
877
891
  git flag, args.to_a
878
892
  end
@@ -888,13 +902,13 @@ module Squared
888
902
  super
889
903
  end
890
904
 
891
- def depend(*, **)
892
- workspace.rev_clear(name)
905
+ def depend(*, sync: invoked_sync?('depend'), **)
906
+ workspace.rev_clear(name, sync: sync)
893
907
  super
894
908
  end
895
909
 
896
- def clean(*, **)
897
- workspace.rev_clear(name)
910
+ def clean(*, sync: invoked_sync?('clean'), **)
911
+ workspace.rev_clear(name, sync: sync)
898
912
  super
899
913
  end
900
914
 
@@ -915,7 +929,7 @@ module Squared
915
929
  end
916
930
  end
917
931
  append_pull(opts, OPT_GIT[:pull] + OPT_GIT[:fetch][:pull],
918
- no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull], remote: remote, flag: flag)
932
+ no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull], remote: remote, flag: flag, from: :pull)
919
933
  source(sync: sync, sub: if verbose
920
934
  [
921
935
  { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: color(:red), index: 4 },
@@ -957,11 +971,9 @@ module Squared
957
971
  end
958
972
 
959
973
  def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
960
- cmd, opts = git_session('fetch', opts: opts)
961
- append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]), remote: remote,
962
- flag: flag)
963
- cmd << '--all' if !remote && !session_arg?('multiple') && option('all')
964
- cmd << '--verbose' if verbose && !session_arg?('quiet')
974
+ opts = git_session('fetch', opts: opts).last
975
+ append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]),
976
+ remote: remote, flag: flag, from: :fetch)
965
977
  source(sync: sync, **threadargs)
966
978
  end
967
979
 
@@ -980,7 +992,7 @@ module Squared
980
992
  opts[:origin] = val if (val = option('origin', ignore: false))
981
993
  opts[:branch] = val if (val = option('branch', strict: true))
982
994
  opts[:local] = val != '0' if (val = option('local', strict: true))
983
- opts.delete(:'recurse-submodules') || opts.delete(:'no-recurse-submodules') if append_submodules(:clone)
995
+ opts.delete(:'recurse-submodules') || opts.delete(:'no-recurse-submodules') if append_submodules(from: :clone)
984
996
  append_hash opts
985
997
  cmd << '--quiet' unless verbose
986
998
  append_value(data[0], path, delim: true)
@@ -1112,7 +1124,8 @@ module Squared
1112
1124
  msg = sub_style('completed', styles: theme[:active])
1113
1125
  puts log_message(Logger::INFO, name, msg, subject: 'revbuild', hint: time_format(epochtime - start))
1114
1126
  end
1115
- workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) }, utc: 'build')
1127
+ workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1128
+ sync: sync, utc: 'build')
1116
1129
  end
1117
1130
 
1118
1131
  def reset(flag, opts = [], refs: nil, ref: nil, mode: nil, commit: nil)
@@ -1600,17 +1613,25 @@ module Squared
1600
1613
  append_commit(*op.extras)
1601
1614
  end
1602
1615
  when :add, :clean
1603
- if flag == :add && op.empty? && !op.arg?('pathspec-from-file')
1604
- files = []
1605
- red = color(:red)
1606
- git_spawn('status -s --porcelain -uall', stdout: false).each do |line|
1607
- next unless line =~ /^.(.) (.+)$/
1608
-
1609
- files << "#{sub_style($1, styles: red)} #{$2}"
1616
+ if flag == :add && !op.arg?('pathspec-from-file')
1617
+ grep, list = op.extras.partition { |val| val.start_with?(':') && val.end_with?(':') }
1618
+ if list.empty? || !grep.empty?
1619
+ red = color(:red)
1620
+ grep.map! { |val| Regexp.new(val[1..-2]) }
1621
+ files = status_data.map! do |a, b|
1622
+ next unless grep.empty? || grep.any? { |pat| pat.match?(a) }
1623
+
1624
+ "#{sub_style(b, styles: red)} #{a}"
1625
+ end
1626
+ .compact
1627
+ unless files.empty?
1628
+ files = choice_index('Select files', files, multiple: true, force: true, trim: /^\S+\s/,
1629
+ accept: 'Add?')
1630
+ end
1631
+ op.swap(list + files)
1610
1632
  end
1611
- files = choice_index('Select files', files, multiple: true, force: true, trim: /^\S+\s/, accept: 'Add?')
1612
1633
  end
1613
- append_pathspec(files || op.extras)
1634
+ append_pathspec(op.extras)
1614
1635
  verbose = flag == :add && !op.arg?('verbose')
1615
1636
  print_success if success?(source) && verbose
1616
1637
  return
@@ -1619,7 +1640,7 @@ module Squared
1619
1640
  raise_error 'no source/destination' unless refs.size > 1
1620
1641
  op.merge(refs)
1621
1642
  when :rm
1622
- append_pathspec(op.extras, expect: !op.arg?('pathspec-from-file'))
1643
+ append_pathspec(op.extras, expect: true)
1623
1644
  end
1624
1645
  source(sync: false, stderr: true)
1625
1646
  end
@@ -1639,13 +1660,13 @@ module Squared
1639
1660
  private
1640
1661
 
1641
1662
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
1642
- multiple: false, sub: nil)
1663
+ multiple: false, **kwargs)
1643
1664
  cmd = cmd.target if cmd.is_a?(OptionPartition)
1644
1665
  banner = nil if multiple && banner
1645
1666
  if cmd.respond_to?(:done)
1646
1667
  if io && banner == false
1647
1668
  from = nil
1648
- elsif !from && (from = cmd.drop(1).find { |val| val.match?(/\A[a-z][a-z\-]{2,}\z/) })
1669
+ elsif !from && (from = cmd.drop(1).find { |val| val.match?(/\A[a-z]{1,2}[a-z\-]*\z/) })
1649
1670
  from = :"git:#{from}"
1650
1671
  end
1651
1672
  banner &&= cmd.temp { |val| val.start_with?('--work-tree') || val.start_with?('--git-dir') }
@@ -1677,7 +1698,7 @@ module Squared
1677
1698
  require 'open3'
1678
1699
  if stderr
1679
1700
  Open3.popen3(cmd) do |_, out, err|
1680
- n = write_lines(out, banner: banner, sub: sub, pass: true)
1701
+ n = write_lines(out, banner: banner, pass: true, **kwargs)
1681
1702
  if n == 0
1682
1703
  n = write_lines(err, banner: banner)
1683
1704
  print_success if success?(n == 0 && !banner.nil?)
@@ -1686,7 +1707,7 @@ module Squared
1686
1707
  end
1687
1708
  end
1688
1709
  else
1689
- Open3.popen2e(cmd) { |_, out| write_lines(out, banner: banner) }
1710
+ Open3.popen2e(cmd) { |_, out| write_lines(out, banner: banner, **kwargs) }
1690
1711
  end
1691
1712
  end
1692
1713
  rescue StandardError => e
@@ -1703,13 +1724,8 @@ module Squared
1703
1724
  end
1704
1725
 
1705
1726
  def write_lines(data, banner: nil, loglevel: nil, grep: nil, sub: nil, pass: false, first: false)
1706
- grep = as_a(grep).map do |val|
1707
- next val if val.is_a?(Regexp)
1708
-
1709
- Regexp.new(val == '*' ? '.+' : val.to_s)
1710
- end
1711
- grep = nil if grep.empty?
1712
- sub = nil if stdin?
1727
+ grep &&= as_a(grep).yield_self { |a| a.empty? || a.include?('*') ? nil : a.map { |val| Regexp.new(val) } }
1728
+ sub &&= stdin? ? nil : as_a(sub)
1713
1729
  ret = 0
1714
1730
  out = []
1715
1731
  data.each do |line|
@@ -1739,7 +1755,7 @@ module Squared
1739
1755
  styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
1740
1756
  styles << :bold if styles.size <= 1
1741
1757
  puts print_footer("#{size} #{size == 1 ? type.sub(/(?:(?<!l)e)?s\z/, '') : type}",
1742
- sub: { pat: /\A(\d+)(.+)\z/m, styles: styles })
1758
+ sub: { pat: /^(\d+)(.+)$/, styles: styles })
1743
1759
  else
1744
1760
  puts empty_status("No #{type} were #{action}", 'grep', grep.is_a?(Array) ? case grep.size
1745
1761
  when 0
@@ -1786,8 +1802,8 @@ module Squared
1786
1802
  glob = kwargs.fetch(:include, [])
1787
1803
  pass = kwargs.fetch(:exclude, [])
1788
1804
  ret = {}
1789
- git_spawn('status -s --porcelain', *args, stdout: false).each do |line|
1790
- next unless (file = line[/^[A-Z ?!]{3}"?(.+?)"?$/, 1])
1805
+ status_data(*args).each do |line|
1806
+ file = line.first
1791
1807
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1792
1808
  next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1793
1809
 
@@ -1796,9 +1812,19 @@ module Squared
1796
1812
  ret
1797
1813
  end
1798
1814
 
1799
- def append_pull(opts, list, target: @session, flag: nil, no: nil, remote: nil)
1815
+ def status_data(*args)
1816
+ ret = []
1817
+ git_spawn('status -z -uall', *args).split("\x0").each do |line|
1818
+ next unless line =~ /^(.)(.) (.+)$/
1819
+
1820
+ ret << [$3, $2, $1]
1821
+ end
1822
+ ret
1823
+ end
1824
+
1825
+ def append_pull(opts, list, target: @session, flag: nil, no: nil, remote: nil, from: nil)
1800
1826
  target << '--force' if option('force', target: target)
1801
- append_submodules(target: target)
1827
+ append_submodules(target: target, from: from)
1802
1828
  return if !remote && opts.empty?
1803
1829
 
1804
1830
  refspec = []
@@ -1823,6 +1849,7 @@ module Squared
1823
1849
  op.errors << opt
1824
1850
  end
1825
1851
  end
1852
+ op << '--verbose' if (flag || from == :fetch) && verbose && !op.arg?('quiet')
1826
1853
  if remote
1827
1854
  op.append(remote, delim: true)
1828
1855
  if (val = option('refspec', target: target, strict: true))
@@ -1834,6 +1861,8 @@ module Squared
1834
1861
  elsif op.arg?('--multiple')
1835
1862
  op.swap.merge(op.map! { |opt| shell_escape(opt, quote: true) })
1836
1863
  return
1864
+ elsif option('all')
1865
+ cmd << '--all'
1837
1866
  end
1838
1867
  op.clear(errors: true, subject: flag.to_s) if flag
1839
1868
  end
@@ -1874,7 +1903,7 @@ module Squared
1874
1903
  append_first('head', 'tree-ish', 'object', target: target, flag: false, ignore: false)
1875
1904
  end
1876
1905
 
1877
- def append_submodules(from = nil, target: @session)
1906
+ def append_submodules(target: @session, from: nil)
1878
1907
  return unless (val = option('recurse-submodules', target: target, ignore: false))
1879
1908
 
1880
1909
  if from == :clone
@@ -1928,7 +1957,7 @@ module Squared
1928
1957
  target.include?('--dry-run') || !option('dry-run', target: target).nil?
1929
1958
  end
1930
1959
 
1931
- def quiet?(target: @session)
1960
+ def quiet?(*, target: @session, **)
1932
1961
  return false unless target
1933
1962
 
1934
1963
  target.include?('--quiet') || (target.include?('-q') && stripext(target.first) == 'git')
@@ -191,22 +191,21 @@ module Squared
191
191
  end
192
192
  end
193
193
  when 'publish'
194
- format_desc(action, flag, 'otp?,dry-run?=true', before: flag == :tag ? 'tag' : nil)
194
+ format_desc(action, flag, 'otp?,dry-run?,public|restricted?', before: flag == :tag ? 'tag' : nil)
195
195
  task flag do |_, args|
196
- if flag == :latest
197
- otp, dryrun = args.to_a
198
- else
199
- args = param_guard(action, flag, args: args.to_a)
200
- tag, otp, dryrun = args
196
+ args = args.to_a
197
+ dryrun = true if args.delete('dry-run') || args.delete('true') || args.delete('d')
198
+ if args.delete('public') || args.delete('p')
199
+ access = 'public'
200
+ elsif args.delete('restricted') || args.delete('r')
201
+ access = 'restricted'
201
202
  end
202
- check = ->(val) { val == 'dry-run' || val == 'true' }
203
- if check.call(otp)
204
- dryrun = true
205
- otp = nil
203
+ if flag == :latest
204
+ otp = args.first
206
205
  else
207
- dryrun &&= check.call(dryrun)
206
+ tag, otp = param_guard(action, flag, args: args)
208
207
  end
209
- publish(flag, otp: otp, tag: tag, dryrun: dryrun)
208
+ publish(flag, otp: otp, tag: tag, dryrun: dryrun, access: access)
210
209
  end
211
210
  end
212
211
  end
@@ -217,7 +216,7 @@ module Squared
217
216
  end
218
217
 
219
218
  def copy(from: 'build', into: 'node_modules', scope: nil, also: nil, create: nil, workspace: false,
220
- link: false, force: false, override: false, **kwargs)
219
+ link: false, force: false, override: false, sync: invoked_sync?('copy'), **kwargs)
221
220
  glob = kwargs[:include]
222
221
  pass = kwargs[:exclude]
223
222
  if @copy && !override
@@ -239,7 +238,7 @@ module Squared
239
238
  items = []
240
239
  if build? && path != @workspace.home && @workspace.home?
241
240
  items << @workspace.home
242
- @workspace.rev_clear(@workspace.find(@workspace.home).name)
241
+ @workspace.rev_clear(@workspace.find(@workspace.home).name, sync: sync)
243
242
  end
244
243
  items.concat(as_a(also)) if also
245
244
  return if items.empty?
@@ -250,13 +249,13 @@ module Squared
250
249
  case dir
251
250
  when Pathname
252
251
  dest = dir
253
- @workspace.rev_clear(dest)
252
+ @workspace.rev_clear(dest, sync: sync)
254
253
  when String
255
254
  dest = @workspace.root + dir
256
- @workspace.rev_clear(dest)
255
+ @workspace.rev_clear(dest, sync: sync)
257
256
  when Symbol
258
257
  if (proj = @workspace.find(name: dir))
259
- @workspace.rev_clear(proj.name)
258
+ @workspace.rev_clear(proj.name, sync: sync)
260
259
  dest = proj.path
261
260
  else
262
261
  log.warn message("copy project :#{dir}", hint: 'not found')
@@ -274,10 +273,10 @@ module Squared
274
273
  glob = dir[:include]
275
274
  pass = dir[:exclude]
276
275
  dest = items.first unless dest && dest != true
277
- @workspace.rev_clear(dest) unless dest == true
276
+ @workspace.rev_clear(dest, sync: sync) unless dest == true
278
277
  when Project::Base
279
278
  dest = dir.path
280
- @workspace.rev_clear(dir.name)
279
+ @workspace.rev_clear(dir.name, sync: sync)
281
280
  else
282
281
  raise_error "copy: given #{dir}"
283
282
  end
@@ -329,7 +328,7 @@ module Squared
329
328
  if @depend && !flag
330
329
  super
331
330
  elsif outdated?
332
- workspace.rev_clear(name)
331
+ workspace.rev_clear(name, sync: sync)
333
332
  return update if !flag && env('NODE_UPDATE')
334
333
 
335
334
  if (yarn = dependtype(:yarn)) > 0
@@ -548,7 +547,7 @@ module Squared
548
547
  package 'update'
549
548
  end
550
549
 
551
- def publish(flag = nil, *, sync: invoked_sync?('publish', flag), otp: nil, tag: nil, dryrun: nil, **)
550
+ def publish(flag = nil, *, sync: invoked_sync?('publish', flag), otp: nil, tag: nil, dryrun: nil, access: nil)
552
551
  if read_packagemanager(:private)
553
552
  if warning?
554
553
  warn log_message(Logger::WARN, 'invalid task "publish"', subject: name, hint: 'private', pass: true)
@@ -558,11 +557,10 @@ module Squared
558
557
  return unless version
559
558
 
560
559
  cmd = session 'npm', 'publish'
561
- otp = option('otp') if otp.nil?
562
- tag = option('tag') if tag.nil?
563
560
  dryrun = dryrun?('npm') if dryrun.nil?
564
- cmd << basic_option('otp', otp) if otp
565
- cmd << shell_option('tag', tag) if tag
561
+ cmd << basic_option('otp', otp) if otp ||= option('otp')
562
+ cmd << basic_option('tag', tag) if tag ||= option('tag')
563
+ cmd << basic_option('access', access) if access ||= option('access')
566
564
  if verbose
567
565
  if dryrun
568
566
  cmd << '--dry-run'
@@ -760,6 +758,10 @@ module Squared
760
758
  outdated?
761
759
  end
762
760
 
761
+ def refresh?
762
+ !Node.prod?
763
+ end
764
+
763
765
  def yarn?
764
766
  (@pm[:yarn] ||= if rootpath('yarn.lock', ascend: dependext).exist?
765
767
  if (rc = rootpath('.yarnrc.yml', ascend: dependext)).exist?
@@ -184,12 +184,12 @@ module Squared
184
184
  task flag, [:strategy] do |_, args|
185
185
  case (strategy = args.strategy)
186
186
  when 'eager', 'only-if-needed'
187
- opts = args.extras
187
+ args = args.extras
188
188
  else
189
- opts = args.to_a
189
+ args = args.to_a
190
190
  strategy = nil
191
191
  end
192
- install(flag, opts, strategy: strategy)
192
+ install(flag, args, strategy: strategy)
193
193
  end
194
194
  when :target
195
195
  task flag, [:dir] do |_, args|
@@ -229,7 +229,7 @@ module Squared
229
229
  super
230
230
  elsif outdated?
231
231
  venv_init
232
- workspace.rev_clear(name)
232
+ workspace.rev_clear(name, sync: sync)
233
233
  if !flag && dependtype == 1
234
234
  cmd = poetry_session 'install', '-n'
235
235
  cmd << '--no-root' if option('no-root')
@@ -219,13 +219,20 @@ module Squared
219
219
  when :file
220
220
  format_desc action, flag, 'path,opts*,args*'
221
221
  task flag, [:rb] do |_, args|
222
- file = param_guard(action, flag, args: args, key: :rb)
223
- ruby(flag, args.to_a.drop(1), file: file)
222
+ if (file = args.rb)
223
+ args = args.to_a.drop(1)
224
+ else
225
+ file, opts, extra = choice_index('Select a file', Dir.glob('*.rb', base: path),
226
+ values: %w[Options Arguments], force: true, series: true)
227
+ args = OptionPartition.strip(opts)
228
+ ENV['RUBY_ARGS'] = extra if extra
229
+ end
230
+ ruby(flag, args, file: file)
224
231
  end
225
232
  when :script
226
- format_desc action, flag, 'RUBY_E?,opts*,args*'
233
+ format_desc action, flag, 'opts*,args*'
227
234
  task flag do |_, args|
228
- command = ENV['RUBY_E'] || readline('Enter script', force: true)
235
+ command = ENV['RUBY_E'] || readline('Enter script', force: true, multiline: ['##', ';'])
229
236
  ruby(flag, args.to_a, command: command)
230
237
  end
231
238
  when :version
@@ -246,7 +253,7 @@ module Squared
246
253
  if @depend
247
254
  super
248
255
  elsif outdated?
249
- workspace.rev_clear(name)
256
+ workspace.rev_clear(name, sync: sync)
250
257
  cmd = bundle_session 'install'
251
258
  cmd << '--without=development' if prod?
252
259
  if (n = option('jobs')).to_i > 0
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: squared
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.9
4
+ version: 0.4.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - An Pham
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  requirements: []
126
- rubygems_version: 3.6.2
126
+ rubygems_version: 3.6.7
127
127
  specification_version: 4
128
128
  summary: Rake task generator for managing multi-language workspaces.
129
129
  test_files: []