squared 0.0.9 → 0.0.10

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.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
3
4
  require 'date'
4
5
  require 'logger'
5
6
 
@@ -12,9 +13,13 @@ module Squared
12
13
  include System
13
14
  include Shell
14
15
  include Task
16
+ include Utils
15
17
  include ::Rake::DSL
16
18
 
17
- SEM_VER = /(\d+)(?:(\.)(\d+))?(?:(\.)(\d+))?/.freeze
19
+ SEM_VER = /(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?/.freeze
20
+
21
+ VAR_SET = %i[parent global envname theme run script env depend doc test copy clean].freeze
22
+ private_constant :VAR_SET
18
23
 
19
24
  class << self
20
25
  def populate(*); end
@@ -44,9 +49,12 @@ module Squared
44
49
  @@print_order = 0
45
50
  @@tasks = {}
46
51
 
47
- attr_reader :name, :project, :workspace, :group, :path, :theme
52
+ attr_reader :name, :project, :workspace, :group, :path, :theme, :parent
53
+ attr_accessor :exception, :pipe, :verbose
48
54
 
49
- def initialize(name, path, workspace, *, group: nil, **kwargs)
55
+ def initialize(name, path, workspace, *,
56
+ group: nil, verbose: nil, pass: nil, exclude: nil,
57
+ common: Common::KEY[:COMMON], pipe: Common::KEY[:PIPE], **kwargs)
50
58
  @name = name.to_s
51
59
  @path = path
52
60
  @project = @path.basename.to_s
@@ -55,23 +63,82 @@ module Squared
55
63
  @depend = kwargs[:depend]
56
64
  @doc = kwargs[:doc]
57
65
  @test = kwargs[:test]
58
- @output = [kwargs[:run], nil]
59
66
  @copy = kwargs[:copy]
60
67
  @clean = kwargs[:clean]
61
- @theme = if !workspace.verbose
68
+ @exception = workspace.exception
69
+ @pipe = env_pipe(pipe, workspace.pipe)
70
+ @verbose = verbose.nil? ? workspace.verbose : verbose
71
+ @theme = if !@verbose
62
72
  {}
63
- elsif kwargs.fetch(:common, true)
73
+ elsif common
64
74
  workspace.theme
65
75
  else
66
76
  __get__(:theme)[:project][to_sym] ||= {}
67
77
  end
78
+ @output = []
68
79
  @ref = []
69
- @exclude = as_a(kwargs[:exclude], :to_sym).freeze
80
+ @children = []
81
+ @pass = (pass ? as_a(pass, :to_sym) : []).freeze
82
+ @exclude = (exclude ? as_a(exclude, :to_sym) : []).freeze
83
+ @envname = @name.gsub(/[^\w]+/, '_').upcase
84
+ @desc = @name.split(':').join(' => ')
85
+ @global = false
86
+ run_set kwargs[:run], kwargs[:env]
70
87
  initialize_ref(Base.ref)
88
+ end
89
+
90
+ def initialize_ref(ref)
91
+ @ref << ref unless @exclude.include?(ref)
92
+ end
93
+
94
+ def initialize_build(ref, **kwargs)
95
+ initialize_ref(ref)
96
+ if (@script = @workspace.script(group: @group, ref: ref))
97
+ if @script[:log] && !kwargs.key?(:log)
98
+ kwargs[:log] = @script[:log]
99
+ @log = nil
100
+ end
101
+ @depend = @script[:depend] if @depend.nil?
102
+ @doc = @script[:doc] if @doc.nil?
103
+ @test = @script[:test] if @test.nil?
104
+ @clean = @script[:clean] if @clean.nil?
105
+ @exclude = @script[:exclude] if @exclude.empty? && @script.key?(:exclude)
106
+ end
71
107
  initialize_logger(**kwargs)
108
+ return if @output[0] == false
109
+
110
+ data = @workspace.script(*@ref, @group)
111
+ if @output[0].nil?
112
+ if (scr = data[:script])
113
+ @global = true
114
+ script_set(scr, prod: kwargs[:prod])
115
+ elsif (run = data[:run])
116
+ @global = true
117
+ run_set run
118
+ end
119
+ unless data[:env]
120
+ if (scr = kwargs[:script])
121
+ @global = false
122
+ script_set scr
123
+ elsif @script && !data[:global]
124
+ if (scr = @script[:script])
125
+ @global = false
126
+ script_set scr
127
+ elsif (run = @script[:run])
128
+ @global = false
129
+ run_set run
130
+ end
131
+ end
132
+ end
133
+ elsif data[:env] && data[:run]
134
+ @global = true
135
+ run_set data[:run]
136
+ end
72
137
  end
73
138
 
74
139
  def initialize_logger(log: nil, **)
140
+ return if @log
141
+
75
142
  log = log.is_a?(::Hash) ? log.dup : { file: log }
76
143
  if (file = env('LOG_FILE')).nil? && (auto = env('LOG_AUTO'))
77
144
  file = case auto
@@ -89,49 +156,41 @@ module Squared
89
156
  begin
90
157
  file = file.realdirpath
91
158
  rescue StandardError => e
92
- raise if @workspace.exception
159
+ raise if @exception
93
160
 
94
161
  file = nil
95
- warn e if @workspace.warning
162
+ warn log_message(::Logger::WARN, e) if warning?
96
163
  end
97
164
  end
98
- log[:progname] = @name
99
- log[:level] = env('LOG_LEVEL', log[:level] || Logger::INFO, ignore: nil)
165
+ log[:progname] ||= @name
166
+ if (val = env('LOG_LEVEL', ignore: nil))
167
+ log[:level] = val
168
+ end
100
169
  log.delete(:file)
101
170
  @log = [file, log]
102
171
  end
103
172
 
104
- def initialize_build(ref, **kwargs)
105
- initialize_script(ref, **kwargs)
106
- if (val = env('BUILD', strict: true))
107
- @output[0] = val
108
- elsif @script && @output[0] != false
109
- @output[0] ||= @script[:run]
110
- end
111
- @dev = kwargs.delete(:dev)
112
- if env('BUILD', suffix: 'DEV', equals: '0')
113
- @dev = false
114
- elsif env('BUILD', suffix: 'DEV')
115
- @dev = true
116
- elsif @dev.nil?
117
- @dev = !group.nil? && workspace.dev?(group: group, global: true)
173
+ def initialize_env(dev: nil, prod: nil, **)
174
+ prefix = "BUILD_#{@envname}"
175
+ @dev = env_match("#{prefix}_DEV", dev)
176
+ @prod = env_match("#{prefix}_PROD", prod)
177
+ if (val = env('BUILD', suffix: 'ENV'))
178
+ begin
179
+ data = JSON.parse(val)
180
+ raise_error('invalid JSON object', val, hint: "#{prefix}_ENV") unless data.is_a?(::Hash)
181
+ @output[2] = data
182
+ rescue StandardError => e
183
+ log.warn e
184
+ end
118
185
  end
119
- end
186
+ return unless (val = env('BUILD', strict: true))
120
187
 
121
- def initialize_script(ref, **)
122
- initialize_ref(ref)
123
- return unless (script = workspace.script(group: group, ref: ref))
124
-
125
- @depend = script[:depend] if @depend.nil?
126
- @doc = script[:doc] if @doc.nil?
127
- @test = script[:test] if @test.nil?
128
- @clean = script[:clean] if @clean.nil?
129
- @exclude = script[:exclude] if @exclude.empty? && script.key?(:exclude)
130
- @script = script
131
- end
132
-
133
- def initialize_ref(ref)
134
- @ref << ref unless @exclude.include?(ref)
188
+ @global = false
189
+ if script?
190
+ script_set val
191
+ else
192
+ run_set(val, opts: false)
193
+ end
135
194
  end
136
195
 
137
196
  def ref
@@ -139,28 +198,51 @@ module Squared
139
198
  end
140
199
 
141
200
  def populate(*)
142
- valid = ref?(Base.ref)
143
- series = workspace.series
201
+ check = lambda do |proj, key|
202
+ workspace.series.include?(key) ? proj.has?(key, Base.ref) : workspace.task_extend?(proj, key)
203
+ end
144
204
 
145
205
  namespace name do
146
- series.each_key do |key|
147
- next unless series.include?(key) ? has?(key) && valid : workspace.task_extend?(self, key)
206
+ workspace.series.each_key do |key|
207
+ next unless check.(self, key)
148
208
 
149
- desc message(*name.split(':'), key)
150
- task key do
151
- __send__(key)
209
+ unless workspace.task_defined?("#{name}:#{key}")
210
+ desc message(@desc, key)
211
+ task key do
212
+ __send__(key)
213
+ end
152
214
  end
215
+ next if (items = @children.select { |item| check.(item, key) }).empty?
216
+
217
+ desc message(@desc, key, 'workspace')
218
+ task "#{key}:workspace" => items.map { |item| "#{item.name}:#{key}" }
153
219
  end
154
220
  end
155
221
  end
156
222
 
223
+ def add(path, name = nil, **kwargs, &blk)
224
+ return self unless source_path?(path = base_path(path))
225
+
226
+ kwargs[:group] = group unless kwargs.key?(:group)
227
+ kwargs[:ref] = ref unless kwargs.key?(:ref)
228
+ parent = self
229
+ proj = nil
230
+ workspace.add(path, name || path.basename.to_s, **kwargs) do
231
+ variable_set :parent, parent
232
+ proj = self
233
+ end
234
+ @children << proj
235
+ proj.instance_eval(&blk) if block_given?
236
+ self
237
+ end
238
+
157
239
  def build(*args, sync: true)
158
240
  if args.empty?
159
- cmd, opts = @output
160
- opts &&= shell_escape(opts)
241
+ cmd, opts, var = @output
242
+ opts &&= shell_split(opts, join: true)
161
243
  else
162
244
  cmd = args.shift
163
- opts = sanitize_args(*args)
245
+ opts = args.map { |val| shell_quote(val, force: false) }.join(' ')
164
246
  end
165
247
  if cmd
166
248
  if opts
@@ -168,24 +250,21 @@ module Squared
168
250
  elsif cmd.is_a?(::Array)
169
251
  cmd = cmd.join(' && ')
170
252
  end
171
- banner = verbose?
253
+ banner = verbose != false
172
254
  else
173
255
  return unless respond_to?(:compose)
174
256
 
175
257
  cmd = compose(opts)
176
- banner = env('REPO_BUILD') == 'verbose'
258
+ banner = verbose == 1
177
259
  end
178
- run(cmd, banner: banner, sync: sync)
260
+ run(cmd, var, banner: banner, sync: sync)
179
261
  end
180
262
 
181
263
  def refresh(*)
182
- build(sync: invoked_sync?('depend'))
183
- key = "#{name}:copy"
184
- if workspace.task_defined?(key)
185
- invoke(key, exception: workspace.exception, warning: workspace.warning)
186
- else
187
- copy
188
- end
264
+ build(sync: invoked_sync?('refresh') || invoked_sync?('build'))
265
+ return if run_task "#{name}:copy"
266
+
267
+ copy if copy?
189
268
  end
190
269
 
191
270
  def depend(*)
@@ -205,14 +284,12 @@ module Squared
205
284
  end
206
285
 
207
286
  def clean
208
- return unless @clean
209
-
210
- if @clean.is_a?(::String)
287
+ case @clean
288
+ when ::String
211
289
  run_s(@clean, sync: invoked_sync?('clean'))
212
- else
213
- @clean.each do |val|
214
- val = val.to_s
215
- if val =~ %r{[\\/]$}
290
+ when ::Enumerable
291
+ as_a(@clean).each do |val|
292
+ if (val = val.to_s) =~ %r{[\\/]$}
216
293
  dir = Pathname.new(val)
217
294
  dir = base_path(dir) unless dir.absolute?
218
295
  next unless dir.directory?
@@ -234,15 +311,43 @@ module Squared
234
311
  end
235
312
 
236
313
  def log
237
- return @log unless @log.is_a?(::Array)
238
-
239
- @log = Logger.new(enabled? ? @log[0] : nil, **@log[1])
314
+ if @log.is_a?(::Array)
315
+ @log = Logger.new(enabled? ? @log[0] : nil, **@log[1])
316
+ else
317
+ @log
318
+ end
240
319
  end
241
320
 
242
321
  def base_path(*args)
243
322
  path.join(*args)
244
323
  end
245
324
 
325
+ def color(val)
326
+ ret = theme[val]
327
+ ret && !ret.empty? ? ret : [val]
328
+ end
329
+
330
+ def variable_set(key, *val, **kwargs)
331
+ if variable_all.include?(key)
332
+ case key
333
+ when :run
334
+ run_set(*val, opts: false, **kwargs)
335
+ when :script
336
+ script_set(*val, **kwargs)
337
+ when :env
338
+ run_set(output[0], *val, opts: false, **kwargs)
339
+ else
340
+ instance_variable_set :"@#{key}", val.first
341
+ end
342
+ else
343
+ log.warn "variable_set: @#{key} (not defined)"
344
+ end
345
+ end
346
+
347
+ def variable_all
348
+ VAR_SET
349
+ end
350
+
246
351
  def inspect
247
352
  "#<#{self.class}: #{name} => #{self}>"
248
353
  end
@@ -255,12 +360,16 @@ module Squared
255
360
  name.to_sym
256
361
  end
257
362
 
258
- def enabled?
363
+ def enabled?(ref = nil)
364
+ return false if ref && !ref?(ref)
365
+
259
366
  path.directory? && !path.empty?
260
367
  end
261
368
 
262
- def has?(method)
263
- respond_to?(m = :"#{method}?") && __send__(m)
369
+ def has?(meth, ref = nil)
370
+ return false if ref && !ref?(ref)
371
+
372
+ respond_to?(meth = :"#{meth}?") && __send__(meth)
264
373
  end
265
374
 
266
375
  def ref?(val)
@@ -268,11 +377,15 @@ module Squared
268
377
  end
269
378
 
270
379
  def build?
271
- !!@output[0]
380
+ !!@output[0] || script?
381
+ end
382
+
383
+ def script?
384
+ @output[0].nil? && !!@output[1] && respond_to?(:compose)
272
385
  end
273
386
 
274
387
  def refresh?
275
- build? && (copy? || workspace.task_defined?("#{name}:copy"))
388
+ build? && copy?
276
389
  end
277
390
 
278
391
  def depend?
@@ -288,20 +401,24 @@ module Squared
288
401
  end
289
402
 
290
403
  def copy?
291
- @copy.is_a?(::String)
404
+ runnable?(@copy) || workspace.task_defined?("#{name}:copy")
292
405
  end
293
406
 
294
407
  def clean?
295
- !!@clean
408
+ runnable?(@clean) || workspace.task_defined?("#{name}:clean")
296
409
  end
297
410
 
298
411
  def dev?
299
- !!@dev
412
+ @dev != false && workspace.dev?(pat: @dev, **scriptargs)
413
+ end
414
+
415
+ def prod?
416
+ @prod != false && workspace.prod?(pat: @prod, **scriptargs)
300
417
  end
301
418
 
302
419
  private
303
420
 
304
- def run(cmd = @session, exception: workspace.exception, banner: true, sync: true, req: nil, **)
421
+ def run(cmd = @session, var = nil, exception: @exception, banner: true, sync: true, req: nil, **)
305
422
  if req && !base_path(req).exist?
306
423
  log.warn "#{req} (not found)"
307
424
  return
@@ -311,10 +428,12 @@ module Squared
311
428
  begin
312
429
  if cmd =~ /^\S+:(\S+:?)+$/ && workspace.task_defined?(cmd)
313
430
  print_item if sync
314
- invoke(cmd, exception: exception, warning: workspace.warning)
431
+ log.warn "ENV was discarded: #{var}" if var
432
+ invoke(cmd, exception: exception, warning: warning?)
315
433
  else
316
434
  print_item format_banner(cmd, banner: banner) if sync
317
- shell(cmd, chdir: path, exception: exception)
435
+ args = var.is_a?(::Hash) ? [var, cmd] : [cmd]
436
+ shell(*args, chdir: path, exception: exception)
318
437
  end
319
438
  rescue StandardError => e
320
439
  log.error e
@@ -323,15 +442,21 @@ module Squared
323
442
  end
324
443
 
325
444
  def run_s(cmd, **kwargs)
326
- run(cmd, banner: verbose?, **kwargs) if cmd.is_a?(::String)
445
+ as_a(cmd).each { |val| run(val, banner: !!verbose, **kwargs) if val.is_a?(::String) }
446
+ end
447
+
448
+ def run_task(key)
449
+ return false unless workspace.task_defined?(key)
450
+
451
+ invoke(key, exception: exception, warning: warning?)
452
+ true
327
453
  end
328
454
 
329
455
  def env(key, default = nil, equals: nil, ignore: ['0'].freeze, suffix: nil, strict: false)
330
- @env ||= name.gsub(/[^\w]+/, '_').upcase
331
- a = "#{key}_#{@env}"
456
+ a = "#{key}_#{@envname}"
332
457
  b = ''
333
458
  if suffix
334
- a = [a, suffix].flatten.join('_')
459
+ a = "#{a}_#{suffix}"
335
460
  elsif !strict
336
461
  b = ENV.fetch(key, '')
337
462
  end
@@ -341,8 +466,12 @@ module Squared
341
466
  ret.empty? || as_a(ignore).any? { |val| ret == val.to_s } ? default : ret
342
467
  end
343
468
 
344
- def session(*cmd, options: nil)
345
- if (val = ENV["#{(options || cmd.first).upcase}_OPTIONS"])
469
+ def puts(*args)
470
+ pipe == 2 ? $stderr.puts(*args) : $stdout.puts(*args)
471
+ end
472
+
473
+ def session(*cmd, prefix: nil)
474
+ if (val = ENV["#{(prefix || cmd.first).upcase}_OPTIONS"])
346
475
  split_escape(val).each { |opt| cmd << fill_option(opt) }
347
476
  end
348
477
  @session = JoinSet.new(cmd)
@@ -351,13 +480,13 @@ module Squared
351
480
  def close_session(cmd)
352
481
  return cmd unless cmd.respond_to?(:done)
353
482
 
354
- raise_error('none were provided', hint: name) if cmd.empty?
483
+ raise_error('no args were added', hint: cmd.first || name) unless cmd.size > 1
355
484
  @session = nil if cmd == @session
356
485
  cmd.done
357
486
  end
358
487
 
359
488
  def print_item(*val)
360
- puts unless @@print_order == 0 || pipe?
489
+ puts unless @@print_order == 0 || stdin?
361
490
  @@print_order += 1
362
491
  puts val unless val.empty? || (val.size == 1 && val.first.nil?)
363
492
  end
@@ -407,7 +536,7 @@ module Squared
407
536
  action = ''
408
537
  end
409
538
  req = opts ? "#{req}," : "[#{req}]" unless req.to_s.empty?
410
- message(*name.split(':'), action, opts ? "#{flag}[#{req}#{opts}]" : flag.to_s + req)
539
+ message(@desc, action, opts ? "#{flag}[#{req}#{opts}]" : flag.to_s + req)
411
540
  end
412
541
 
413
542
  def format_banner(cmd, banner: true, multiple: false)
@@ -418,7 +547,7 @@ module Squared
418
547
  else
419
548
  data = { command: true, order: %i[path], styles: theme[:banner], border: theme[:border] }
420
549
  end
421
- if verbose?
550
+ if verbose
422
551
  out = []
423
552
  out << cmd.sub(/^\S+/, &:upcase) if data[:command]
424
553
  data[:order].each { |val| out << val.to_s if (val = __send__(val)) }
@@ -432,8 +561,16 @@ module Squared
432
561
  "#{msg}#{!always && (!obj || obj == 0 || obj.to_s.empty?) ? '' : message(hint: message(title, obj.to_s))}"
433
562
  end
434
563
 
564
+ def append_repeat(flag, opts)
565
+ opts.each { |val| @session << "--#{flag}=#{shell_escape(val, quote: true)}" }
566
+ end
567
+
568
+ def append_value(opts)
569
+ opts.each { |val| @session << val }
570
+ end
571
+
435
572
  def append_nocolor
436
- @session << (!ENV.fetch('NO_COLOR', '').empty? || pipe? ? '--no-color' : '')
573
+ @session << (!ENV.fetch('NO_COLOR', '').empty? || stdin? ? '--no-color' : '')
437
574
  end
438
575
 
439
576
  def guard_params(action, flag, args: nil, key: nil, pat: nil)
@@ -450,12 +587,26 @@ module Squared
450
587
  end
451
588
  end
452
589
 
453
- def store_pwd(done = nil)
454
- if @pwd
590
+ def pwd_set(done = nil, &blk)
591
+ pwd = Pathname.pwd
592
+ if block_given?
593
+ if path == pwd
594
+ instance_eval(&blk)
595
+ else
596
+ Dir.chdir(path)
597
+ instance_eval(&blk)
598
+ Dir.chdir(pwd)
599
+ end
600
+ elsif @pwd == pwd
601
+ @pwd = nil
602
+ pwd unless done
603
+ elsif @pwd
604
+ return unless path == pwd
605
+
455
606
  Dir.chdir(@pwd)
456
607
  @pwd = nil
457
- elsif !done && Dir.pwd != path.to_s
458
- @pwd = Dir.pwd
608
+ elsif !done && path != pwd
609
+ @pwd = pwd
459
610
  Dir.chdir(path)
460
611
  @pwd
461
612
  end
@@ -474,37 +625,72 @@ module Squared
474
625
  end
475
626
 
476
627
  def semmajor(cur, want)
477
- cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]
628
+ (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
478
629
  end
479
630
 
480
- def verbose?
481
- workspace.verbose
631
+ def scriptargs
632
+ { target: script? ? @output[1] : @output[0], ref: ref, group: group, global: @global }
482
633
  end
483
634
 
484
- def pipe?
485
- workspace.pipe
635
+ def run_set(cmd, val = nil, *, opts: true, **)
636
+ @output[0] = cmd
637
+ @output[1] = if cmd && opts
638
+ env('BUILD', opts.is_a?(::String) ? opts : nil, suffix: 'OPTS')
639
+ end
640
+ if val.is_a?(::Hash)
641
+ @output[2] = val
642
+ elsif val == false
643
+ @output[2] = nil
644
+ end
486
645
  end
487
646
 
488
- def invoked_sync?(action, flag = nil)
489
- action = workspace.task_name(action)
490
- return true if !flag.nil? || workspace.series.sync?("#{action}:sync")
647
+ def script_set(cmd, *, prod: nil, **)
648
+ @output[0] = nil
649
+ @output[1] = if @global && cmd.is_a?(::Array)
650
+ cmd[prod == true ? 1 : 0]
651
+ else
652
+ cmd
653
+ end
654
+ end
491
655
 
492
- check = lambda do |val|
493
- if invoked?(val)
494
- !workspace.task_defined?("#{val}:sync")
495
- elsif workspace.series.sync?("#{val}:sync")
496
- true
497
- end
498
- end
499
- if group
500
- ret = check.("#{action}:#{group}")
501
- return ret unless ret.nil?
656
+ def source_path?(val)
657
+ Pathname.new(val).absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.match?(%r{^\.\.[/\\]})
658
+ end
659
+
660
+ def warning?
661
+ workspace.warning
662
+ end
663
+
664
+ def stdin?
665
+ pipe == 0
666
+ end
667
+
668
+ def runnable?(val)
669
+ case val
670
+ when ::String
671
+ true
672
+ when ::Enumerable
673
+ !val.is_a?(::Hash)
674
+ else
675
+ false
502
676
  end
503
- if (base = workspace.find_base(self))
504
- ret = check.("#{action}:#{base.ref}")
505
- return ret unless ret.nil?
677
+ end
678
+
679
+ def from_sync?(val)
680
+ if invoked?(val)
681
+ !workspace.task_defined?("#{val}:sync")
682
+ elsif workspace.series.sync?("#{val}:sync")
683
+ true
506
684
  end
507
- invoked?("#{name}:#{action}") && (!invoked?(action) || !workspace.task_defined?("#{action}:sync"))
685
+ end
686
+
687
+ def invoked_sync?(action, val = nil)
688
+ return true if !val.nil? || from_sync?(ac = workspace.task_name(action))
689
+
690
+ return val if group && !(val = from_sync?("#{ac}:#{group}")).nil?
691
+ return val if (base = workspace.find_base(self)) && !(val = from_sync?("#{ac}:#{base.ref}")).nil?
692
+
693
+ invoked?("#{name}:#{ac}") && (!invoked?(ac) || !workspace.task_defined?("#{ac}:sync"))
508
694
  end
509
695
  end
510
696
  end