appydave-tools 0.19.0 → 0.20.0

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: d1d9c19286530b337db5170902dce174c889b50a7c2425f6e209efec097a0a81
4
- data.tar.gz: 220392dd2eec9910e3c57e7c7de4a372303ab261d01a2db9d6c64769368bf71e
3
+ metadata.gz: fa0d28f2da43b19537700a122baaea2401ea5c7b545d687a1bff64150d1c7d88
4
+ data.tar.gz: 98b8575d8af3b09e0dbc025b076d9b221d0d5b28705ee714a2483f74022c73c6
5
5
  SHA512:
6
- metadata.gz: 33dd5ec557282ce787d457cfaa6e4b996fae6d82b2b4ac4aa5e531ac17f1c310f000fd905428ce3fb969b35635aea9122347a7760e73f0c6282425ffce6e65e9
7
- data.tar.gz: 5be8054544c85a36ef280ed6aa85096f386243df5e66c48f911728fe943350d66c69863b0005424df97e20697bc75126a37e1c4ce84cd8265728ca017e58a4e0
6
+ metadata.gz: 2f3c69c161ac3b1ca8642db8d1f6a15317510956ffe396635d6bf2f49e1be6bc6b22c4b5b9e9694b9b40ffbb9214da8487979078740a68a0b2dcec7c07a42ba1
7
+ data.tar.gz: de05f01bba2420b7c287cd28ac499ca56af5b7277c88522d867280e27be99d821baf2cb9d389beefe0c3e6573b6eee6095c0eafdd1a2806f1e49ec782f0de54f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [0.19.0](https://github.com/appydave/appydave-tools/compare/v0.18.5...v0.19.0) (2025-11-10)
2
+
3
+
4
+ ### Features
5
+
6
+ * Phase 1: add git_remote, S3 tracking, and hasStorylineJson to manifest ([4622271](https://github.com/appydave/appydave-tools/commit/4622271a9e1a01a7145981db4837ed9b69e8f721))
7
+
1
8
  ## [0.18.5](https://github.com/appydave/appydave-tools/compare/v0.18.4...v0.18.5) (2025-11-10)
2
9
 
3
10
 
data/bin/dam CHANGED
@@ -13,6 +13,7 @@ class VatCLI
13
13
  @commands = {
14
14
  'help' => method(:help_command),
15
15
  'list' => method(:list_command),
16
+ 'status' => method(:status_command),
16
17
  's3-up' => method(:s3_up_command),
17
18
  's3-down' => method(:s3_down_command),
18
19
  's3-status' => method(:s3_status_command),
@@ -21,6 +22,9 @@ class VatCLI
21
22
  'archive' => method(:archive_command),
22
23
  'manifest' => method(:manifest_command),
23
24
  'sync-ssd' => method(:sync_ssd_command),
25
+ 'repo-status' => method(:repo_status_command),
26
+ 'repo-sync' => method(:repo_sync_command),
27
+ 'repo-push' => method(:repo_push_command),
24
28
  # Deprecated aliases (for backward compatibility)
25
29
  's3-cleanup' => method(:s3_cleanup_remote_command),
26
30
  'cleanup-local' => method(:s3_cleanup_local_command)
@@ -262,6 +266,112 @@ class VatCLI
262
266
  exit 1
263
267
  end
264
268
 
269
+ # Show unified status (local/S3/SSD/git)
270
+ def status_command(args)
271
+ args = args.reject { |arg| arg.start_with?('--') }
272
+ brand_arg = args[0]
273
+ project_arg = args[1]
274
+
275
+ if brand_arg.nil?
276
+ # Auto-detect from PWD
277
+ brand, project_id = Appydave::Tools::Dam::ProjectResolver.detect_from_pwd
278
+ if brand.nil?
279
+ puts '❌ Could not auto-detect brand from current directory'
280
+ puts 'Usage: dam status <brand> [project]'
281
+ exit 1
282
+ end
283
+ brand_key = brand
284
+ else
285
+ brand_key = brand_arg
286
+ project_id = project_arg ? Appydave::Tools::Dam::ProjectResolver.resolve(brand_arg, project_arg) : nil
287
+ end
288
+
289
+ status = Appydave::Tools::Dam::Status.new(brand_key, project_id)
290
+ status.show
291
+ rescue StandardError => e
292
+ puts "❌ Error: #{e.message}"
293
+ exit 1
294
+ end
295
+
296
+ # Show git status for brand repositories
297
+ def repo_status_command(args)
298
+ all = args.include?('--all')
299
+ args = args.reject { |arg| arg.start_with?('--') }
300
+ brand_arg = args[0]
301
+
302
+ if all
303
+ # Show status for all brands
304
+ Appydave::Tools::Dam::RepoStatus.new.show_all
305
+ elsif brand_arg
306
+ # Show status for specific brand
307
+ Appydave::Tools::Dam::RepoStatus.new(brand_arg).show
308
+ else
309
+ puts 'Usage: dam repo-status <brand> [--all]'
310
+ puts ''
311
+ puts 'Check git status for brand repositories.'
312
+ puts ''
313
+ puts 'Examples:'
314
+ puts ' dam repo-status appydave # Show git status for AppyDave brand'
315
+ puts ' dam repo-status --all # Show git status for all brands'
316
+ exit 1
317
+ end
318
+ rescue StandardError => e
319
+ puts "❌ Error: #{e.message}"
320
+ exit 1
321
+ end
322
+
323
+ # Sync (pull) brand repositories
324
+ def repo_sync_command(args)
325
+ all = args.include?('--all')
326
+ args = args.reject { |arg| arg.start_with?('--') }
327
+ brand_arg = args[0]
328
+
329
+ if all
330
+ # Sync all brands
331
+ Appydave::Tools::Dam::RepoSync.new.sync_all
332
+ elsif brand_arg
333
+ # Sync specific brand
334
+ Appydave::Tools::Dam::RepoSync.new(brand_arg).sync
335
+ else
336
+ puts 'Usage: dam repo-sync <brand> [--all]'
337
+ puts ''
338
+ puts 'Pull updates for brand repositories.'
339
+ puts ''
340
+ puts 'Examples:'
341
+ puts ' dam repo-sync appydave # Pull updates for AppyDave brand'
342
+ puts ' dam repo-sync --all # Pull updates for all brands'
343
+ exit 1
344
+ end
345
+ rescue StandardError => e
346
+ puts "❌ Error: #{e.message}"
347
+ exit 1
348
+ end
349
+
350
+ # Push brand repository changes
351
+ def repo_push_command(args)
352
+ args = args.reject { |arg| arg.start_with?('--') }
353
+ brand_arg = args[0]
354
+ project_arg = args[1]
355
+
356
+ if brand_arg.nil?
357
+ puts 'Usage: dam repo-push <brand> [project]'
358
+ puts ''
359
+ puts 'Push changes for brand repository.'
360
+ puts ''
361
+ puts 'Examples:'
362
+ puts ' dam repo-push appydave # Push all changes for AppyDave brand'
363
+ puts ' dam repo-push appydave b65 # Validate project exists before push'
364
+ exit 1
365
+ end
366
+
367
+ project_id = project_arg ? Appydave::Tools::Dam::ProjectResolver.resolve(brand_arg, project_arg) : nil
368
+
369
+ Appydave::Tools::Dam::RepoPush.new(brand_arg, project_id).push
370
+ rescue StandardError => e
371
+ puts "❌ Error: #{e.message}"
372
+ exit 1
373
+ end
374
+
265
375
  # Parse S3 command arguments
266
376
  def parse_s3_args(args, command)
267
377
  dry_run = args.include?('--dry-run')
data/docs/dam/usage.md CHANGED
@@ -154,6 +154,143 @@ dam list appydave 'b6*'
154
154
  # Output: Lists b60, b61, b62...b69 projects
155
155
  ```
156
156
 
157
+ ### Status & Monitoring
158
+
159
+ #### `dam status [brand] [project]`
160
+ Show unified status for project or brand (local, S3, SSD, git).
161
+
162
+ **Brand status:**
163
+ ```bash
164
+ dam status appydave
165
+ ```
166
+
167
+ **Output:**
168
+ ```
169
+ 📊 Brand Status: v-appydave
170
+ 📡 Git Remote: git@github.com:klueless-io/v-appydave.git
171
+
172
+ 🌿 Branch: main
173
+ 📡 Remote: git@github.com:klueless-io/v-appydave.git
174
+ ✓ Working directory clean
175
+ ✓ Up to date with remote
176
+
177
+ 📋 Manifest Summary:
178
+ Total projects: 114
179
+ Local: 74
180
+ S3 staging: 12
181
+ SSD backup: 67
182
+
183
+ Storyline projects: 3
184
+ FliVideo projects: 111
185
+ ```
186
+
187
+ **Project status:**
188
+ ```bash
189
+ dam status appydave b65
190
+ ```
191
+
192
+ **Output:**
193
+ ```
194
+ 📊 Status: v-appydave/b65-guy-monroe-marketing-plan
195
+
196
+ Storage:
197
+ 📁 Local: ✓ exists (flat structure)
198
+ Heavy files: no
199
+ Light files: yes
200
+
201
+ ☁️ S3 Staging: ✓ exists
202
+ Local staging files: 3
203
+
204
+ 💾 SSD Backup: ✓ exists
205
+ Path: b65-guy-monroe-marketing-plan
206
+
207
+ Git:
208
+ 🌿 Branch: main
209
+ 📡 Remote: git@github.com:klueless-io/v-appydave.git
210
+ ↕️ Status: Clean working directory
211
+ 🔄 Sync: Up to date
212
+ ```
213
+
214
+ **Auto-detect from PWD:**
215
+ ```bash
216
+ cd ~/dev/video-projects/v-appydave/b65-project
217
+ dam status
218
+ ```
219
+
220
+ ### Git Repository Commands
221
+
222
+ #### `dam repo-status [brand] [--all]`
223
+ Check git status for brand repositories.
224
+
225
+ ```bash
226
+ # Single brand
227
+ dam repo-status appydave
228
+
229
+ # All brands
230
+ dam repo-status --all
231
+ ```
232
+
233
+ **Output:**
234
+ ```
235
+ 🔍 Git Status: v-appydave
236
+
237
+ 🌿 Branch: main
238
+ 📡 Remote: git@github.com:klueless-io/v-appydave.git
239
+ ✓ Working directory clean
240
+ ✓ Up to date with remote
241
+ ```
242
+
243
+ #### `dam repo-sync [brand] [--all]`
244
+ Pull updates for brand repositories.
245
+
246
+ ```bash
247
+ # Single brand
248
+ dam repo-sync appydave
249
+
250
+ # All brands
251
+ dam repo-sync --all
252
+ ```
253
+
254
+ **Output:**
255
+ ```
256
+ 🔄 Syncing: v-appydave
257
+
258
+ ✓ Already up to date
259
+ ```
260
+
261
+ **Safety features:**
262
+ - Skips brands with uncommitted changes (prevents conflicts)
263
+ - Shows summary when syncing multiple brands
264
+
265
+ #### `dam repo-push [brand] [project]`
266
+ Push changes for brand repository.
267
+
268
+ ```bash
269
+ # Push all changes
270
+ dam repo-push appydave
271
+
272
+ # Validate project exists before push
273
+ dam repo-push appydave b65
274
+ ```
275
+
276
+ **Output:**
277
+ ```
278
+ 📤 Pushing: v-appydave
279
+
280
+ ✓ Project validated: b65-guy-monroe-marketing-plan
281
+
282
+ 📤 Pushing 2 commit(s)...
283
+
284
+ ✓ Push successful
285
+
286
+ main -> main
287
+ ```
288
+
289
+ **Safety features:**
290
+ - Warns if uncommitted changes detected
291
+ - Optional project validation against manifest
292
+ - Shows push summary
293
+
157
294
  ### S3 Sync Commands
158
295
 
159
296
  #### `dam s3-up [brand] [project] [--dry-run]`
@@ -649,4 +786,4 @@ dam s3-up appydave b65
649
786
 
650
787
  ---
651
788
 
652
- **Last Updated:** 2025-11-08
789
+ **Last Updated:** 2025-11-10
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module Dam
6
+ # Push brand repository changes
7
+ class RepoPush
8
+ attr_reader :brand, :project_id, :brand_info, :brand_path
9
+
10
+ def initialize(brand, project_id = nil)
11
+ @brand_info = load_brand_info(brand)
12
+ @brand = @brand_info.key
13
+ @brand_path = Config.brand_path(@brand)
14
+ @project_id = project_id
15
+ end
16
+
17
+ # Push changes
18
+ def push
19
+ puts "📤 Pushing: v-#{brand}"
20
+ puts ''
21
+
22
+ unless git_repo?
23
+ puts "❌ Not a git repository: #{brand_path}"
24
+ return
25
+ end
26
+
27
+ # If project specified, validate it exists in manifest
28
+ validate_project if project_id
29
+
30
+ perform_push
31
+ end
32
+
33
+ private
34
+
35
+ def load_brand_info(brand)
36
+ Appydave::Tools::Configuration::Config.configure
37
+ Appydave::Tools::Configuration::Config.brands.get_brand(brand)
38
+ end
39
+
40
+ def git_repo?
41
+ git_dir = File.join(brand_path, '.git')
42
+ Dir.exist?(git_dir)
43
+ end
44
+
45
+ def validate_project
46
+ manifest = load_manifest
47
+ unless manifest
48
+ puts '⚠️ Warning: Manifest not found'
49
+ puts ' Continuing with push (manifest is optional for validation)'
50
+ return
51
+ end
52
+
53
+ # Resolve short name if needed (b65 -> b65-full-name)
54
+ resolved = ProjectResolver.new.resolve(brand, project_id)
55
+
56
+ project_entry = manifest[:projects].find { |p| p[:id] == resolved }
57
+ if project_entry
58
+ puts "✓ Project validated: #{resolved}"
59
+ else
60
+ puts "⚠️ Warning: Project '#{resolved}' not found in manifest"
61
+ puts ' Continuing with push (manifest may be outdated)'
62
+ end
63
+ puts ''
64
+ end
65
+
66
+ def load_manifest
67
+ manifest_path = File.join(brand_path, 'projects.json')
68
+ return nil unless File.exist?(manifest_path)
69
+
70
+ JSON.parse(File.read(manifest_path), symbolize_names: true)
71
+ rescue JSON::ParserError
72
+ nil
73
+ end
74
+
75
+ def perform_push
76
+ # Check for uncommitted changes
77
+ if uncommitted_changes?
78
+ puts '⚠️ Warning: Uncommitted changes detected'
79
+ puts ''
80
+ end
81
+
82
+ # Check if ahead of remote
83
+ ahead = commits_ahead
84
+ if ahead.zero?
85
+ puts '✓ Nothing to push (up to date with remote)'
86
+ return
87
+ end
88
+
89
+ puts "📤 Pushing #{ahead} commit(s)..."
90
+ puts ''
91
+
92
+ # Perform git push
93
+ output = `git -C "#{brand_path}" push 2>&1`
94
+ exit_code = $CHILD_STATUS.exitstatus
95
+
96
+ if exit_code.zero?
97
+ puts '✓ Push successful'
98
+ puts ''
99
+ show_push_summary(output)
100
+ else
101
+ puts '❌ Push failed:'
102
+ puts output
103
+ exit 1
104
+ end
105
+ end
106
+
107
+ def uncommitted_changes?
108
+ output = `git -C "#{brand_path}" status --porcelain 2>/dev/null`.strip
109
+ !output.empty?
110
+ rescue StandardError
111
+ false
112
+ end
113
+
114
+ def commits_ahead
115
+ `git -C "#{brand_path}" rev-list --count @{upstream}..HEAD 2>/dev/null`.strip.to_i
116
+ rescue StandardError
117
+ 0
118
+ end
119
+
120
+ def show_push_summary(output)
121
+ # Extract useful info from git push output
122
+ lines = output.lines.map(&:strip).reject(&:empty?)
123
+
124
+ lines.each do |line|
125
+ puts " #{line}" if line.include?('->') || line.include?('branch')
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module Dam
6
+ # Show git status for brand repositories
7
+ class RepoStatus
8
+ attr_reader :brand, :brand_info, :brand_path
9
+
10
+ def initialize(brand = nil)
11
+ return unless brand
12
+
13
+ @brand_info = load_brand_info(brand)
14
+ @brand = @brand_info.key
15
+ @brand_path = Config.brand_path(@brand)
16
+ end
17
+
18
+ # Show status for single brand
19
+ def show
20
+ puts "🔍 Git Status: v-#{brand}"
21
+ puts ''
22
+
23
+ unless git_repo?
24
+ puts "❌ Not a git repository: #{brand_path}"
25
+ return
26
+ end
27
+
28
+ show_git_info
29
+ end
30
+
31
+ # Show status for all brands
32
+ def show_all
33
+ puts '🔍 Git Status - All Brands'
34
+ puts ''
35
+
36
+ Appydave::Tools::Configuration::Config.configure
37
+ brands_config = Appydave::Tools::Configuration::Config.brands
38
+
39
+ brands_config.brands.each do |brand_info|
40
+ @brand_info = brand_info
41
+ @brand = brand_info.key
42
+ @brand_path = Config.brand_path(@brand)
43
+
44
+ next unless git_repo?
45
+
46
+ puts "📦 v-#{brand}"
47
+ show_git_info(indent: ' ')
48
+ puts ''
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def load_brand_info(brand)
55
+ Appydave::Tools::Configuration::Config.configure
56
+ Appydave::Tools::Configuration::Config.brands.get_brand(brand)
57
+ end
58
+
59
+ def git_repo?
60
+ git_dir = File.join(brand_path, '.git')
61
+ Dir.exist?(git_dir)
62
+ end
63
+
64
+ def show_git_info(indent: '')
65
+ status = git_status_info
66
+
67
+ puts "#{indent}🌿 Branch: #{status[:branch]}"
68
+ puts "#{indent}📡 Remote: #{status[:remote]}" if status[:remote]
69
+
70
+ if status[:modified_count].positive? || status[:untracked_count].positive?
71
+ puts "#{indent}↕️ Changes: #{status[:modified_count]} modified, #{status[:untracked_count]} untracked"
72
+ else
73
+ puts "#{indent}✓ Working directory clean"
74
+ end
75
+
76
+ if status[:ahead].positive? || status[:behind].positive?
77
+ puts "#{indent}🔄 Sync: #{sync_status_text(status[:ahead], status[:behind])}"
78
+ else
79
+ puts "#{indent}✓ Up to date with remote"
80
+ end
81
+ end
82
+
83
+ def sync_status_text(ahead, behind)
84
+ parts = []
85
+ parts << "#{ahead} ahead" if ahead.positive?
86
+ parts << "#{behind} behind" if behind.positive?
87
+ parts.join(', ')
88
+ end
89
+
90
+ def git_status_info
91
+ {
92
+ branch: current_branch,
93
+ remote: remote_url,
94
+ modified_count: modified_files_count,
95
+ untracked_count: untracked_files_count,
96
+ ahead: commits_ahead,
97
+ behind: commits_behind
98
+ }
99
+ end
100
+
101
+ def current_branch
102
+ `git -C "#{brand_path}" rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
103
+ rescue StandardError
104
+ 'unknown'
105
+ end
106
+
107
+ def remote_url
108
+ result = `git -C "#{brand_path}" remote get-url origin 2>/dev/null`.strip
109
+ result.empty? ? nil : result
110
+ rescue StandardError
111
+ nil
112
+ end
113
+
114
+ def modified_files_count
115
+ `git -C "#{brand_path}" status --porcelain 2>/dev/null | grep -E "^.M|^M" | wc -l`.strip.to_i
116
+ rescue StandardError
117
+ 0
118
+ end
119
+
120
+ def untracked_files_count
121
+ `git -C "#{brand_path}" status --porcelain 2>/dev/null | grep -E "^\\?\\?" | wc -l`.strip.to_i
122
+ rescue StandardError
123
+ 0
124
+ end
125
+
126
+ def commits_ahead
127
+ `git -C "#{brand_path}" rev-list --count @{upstream}..HEAD 2>/dev/null`.strip.to_i
128
+ rescue StandardError
129
+ 0
130
+ end
131
+
132
+ def commits_behind
133
+ `git -C "#{brand_path}" rev-list --count HEAD..@{upstream} 2>/dev/null`.strip.to_i
134
+ rescue StandardError
135
+ 0
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module Dam
6
+ # Sync (git pull) brand repositories
7
+ class RepoSync
8
+ attr_reader :brand, :brand_info, :brand_path
9
+
10
+ def initialize(brand = nil)
11
+ return unless brand
12
+
13
+ @brand_info = load_brand_info(brand)
14
+ @brand = @brand_info.key
15
+ @brand_path = Config.brand_path(@brand)
16
+ end
17
+
18
+ # Sync single brand
19
+ def sync
20
+ puts "🔄 Syncing: v-#{brand}"
21
+ puts ''
22
+
23
+ unless git_repo?
24
+ puts "❌ Not a git repository: #{brand_path}"
25
+ return
26
+ end
27
+
28
+ perform_sync
29
+ end
30
+
31
+ # Sync all brands
32
+ def sync_all
33
+ puts '🔄 Syncing All Brands'
34
+ puts ''
35
+
36
+ Appydave::Tools::Configuration::Config.configure
37
+ brands_config = Appydave::Tools::Configuration::Config.brands
38
+
39
+ results = []
40
+
41
+ brands_config.brands.each do |brand_info|
42
+ @brand_info = brand_info
43
+ @brand = brand_info.key
44
+ @brand_path = Config.brand_path(@brand)
45
+
46
+ next unless git_repo?
47
+
48
+ puts "📦 v-#{brand}"
49
+ result = perform_sync(indent: ' ')
50
+ results << result
51
+ puts ''
52
+ end
53
+
54
+ show_summary(results)
55
+ end
56
+
57
+ private
58
+
59
+ def load_brand_info(brand)
60
+ Appydave::Tools::Configuration::Config.configure
61
+ Appydave::Tools::Configuration::Config.brands.get_brand(brand)
62
+ end
63
+
64
+ def git_repo?
65
+ git_dir = File.join(brand_path, '.git')
66
+ Dir.exist?(git_dir)
67
+ end
68
+
69
+ def perform_sync(indent: '')
70
+ # Check for uncommitted changes
71
+ if uncommitted_changes?
72
+ puts "#{indent}⚠️ Uncommitted changes detected"
73
+ puts "#{indent} Skipping pull (would cause conflicts)"
74
+ return { brand: brand, status: 'skipped', reason: 'uncommitted changes' }
75
+ end
76
+
77
+ # Perform git pull
78
+ output = `git -C "#{brand_path}" pull 2>&1`
79
+ exit_code = $CHILD_STATUS.exitstatus
80
+
81
+ if exit_code.zero?
82
+ if output.include?('Already up to date')
83
+ puts "#{indent}✓ Already up to date"
84
+ { brand: brand, status: 'current' }
85
+ else
86
+ puts "#{indent}✓ Updated successfully"
87
+ puts "#{indent} #{output.lines.first.strip}" if output.lines.any?
88
+ { brand: brand, status: 'updated' }
89
+ end
90
+ else
91
+ puts "#{indent}❌ Pull failed: #{output.strip}"
92
+ { brand: brand, status: 'error', reason: output.strip }
93
+ end
94
+ end
95
+
96
+ def uncommitted_changes?
97
+ output = `git -C "#{brand_path}" status --porcelain 2>/dev/null`.strip
98
+ !output.empty?
99
+ rescue StandardError
100
+ false
101
+ end
102
+
103
+ def show_summary(results)
104
+ puts '=' * 60
105
+ puts '📊 Sync Summary:'
106
+ puts ''
107
+
108
+ updated = results.count { |r| r[:status] == 'updated' }
109
+ current = results.count { |r| r[:status] == 'current' }
110
+ skipped = results.count { |r| r[:status] == 'skipped' }
111
+ errors = results.count { |r| r[:status] == 'error' }
112
+
113
+ puts " Total repos checked: #{results.size}"
114
+ puts " Updated: #{updated}"
115
+ puts " Already current: #{current}"
116
+ puts " Skipped: #{skipped}" if skipped.positive?
117
+ puts " Errors: #{errors}" if errors.positive?
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,278 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Appydave
6
+ module Tools
7
+ module Dam
8
+ # Show unified status for video project (local, S3, SSD, git)
9
+ class Status
10
+ attr_reader :brand, :project_id, :brand_info, :brand_path, :project_path
11
+
12
+ def initialize(brand, project_id = nil)
13
+ @brand_info = load_brand_info(brand)
14
+ @brand = @brand_info.key
15
+ @brand_path = Config.brand_path(@brand)
16
+ @project_id = project_id
17
+ @project_path = project_id ? resolve_project_path(project_id) : nil
18
+ end
19
+
20
+ # Show status for project or brand
21
+ def show
22
+ if project_id
23
+ show_project_status
24
+ else
25
+ show_brand_status
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def load_brand_info(brand)
32
+ Appydave::Tools::Configuration::Config.configure
33
+ Appydave::Tools::Configuration::Config.brands.get_brand(brand)
34
+ end
35
+
36
+ def resolve_project_path(project_id)
37
+ # Resolve short name if needed (b65 -> b65-full-name)
38
+ resolved = ProjectResolver.new.resolve(brand, project_id)
39
+ File.join(brand_path, resolved)
40
+ end
41
+
42
+ def show_project_status
43
+ puts "📊 Status: v-#{brand}/#{File.basename(project_path)}"
44
+ puts ''
45
+
46
+ manifest = load_manifest
47
+ project_entry = find_project_in_manifest(manifest)
48
+
49
+ unless project_entry
50
+ puts '❌ Project not found in manifest'
51
+ puts " Run: dam manifest #{brand}"
52
+ return
53
+ end
54
+
55
+ show_storage_status(project_entry)
56
+ show_git_status if git_repo?
57
+ end
58
+
59
+ def show_brand_status
60
+ puts "📊 Brand Status: v-#{brand}"
61
+ puts ''
62
+
63
+ # Show git remote (with self-healing)
64
+ remote = Config.git_remote(brand)
65
+ if remote
66
+ puts "📡 Git Remote: #{remote}"
67
+ else
68
+ puts '📡 Git Remote: Not configured (not a git repository)'
69
+ end
70
+ puts ''
71
+
72
+ # Show git status
73
+ if git_repo?
74
+ show_brand_git_status
75
+ else
76
+ puts 'Git: Not a git repository'
77
+ end
78
+ puts ''
79
+
80
+ # Show manifest summary
81
+ manifest = load_manifest
82
+ if manifest
83
+ show_manifest_summary(manifest)
84
+ else
85
+ puts '❌ Manifest not found'
86
+ puts " Run: dam manifest #{brand}"
87
+ end
88
+ end
89
+
90
+ def show_storage_status(project_entry)
91
+ puts 'Storage:'
92
+
93
+ # Local storage
94
+ show_local_status(project_entry)
95
+
96
+ # S3 staging (inferred - only show if exists)
97
+ show_s3_status(project_entry) if project_entry[:storage][:s3][:exists]
98
+
99
+ # SSD backup (inferred - only show if configured and exists)
100
+ show_ssd_status(project_entry) if brand_info.locations.ssd_backup &&
101
+ brand_info.locations.ssd_backup != 'NOT-SET' &&
102
+ project_entry[:storage][:ssd][:exists]
103
+
104
+ puts ''
105
+ end
106
+
107
+ def show_local_status(project_entry)
108
+ local = project_entry[:storage][:local]
109
+
110
+ if local[:exists]
111
+ puts " 📁 Local: ✓ exists (#{local[:structure]} structure)"
112
+ puts " Heavy files: #{local[:has_heavy_files] ? 'yes' : 'no'}"
113
+ puts " Light files: #{local[:has_light_files] ? 'yes' : 'no'}"
114
+ else
115
+ puts ' 📁 Local: ✗ does not exist'
116
+ end
117
+ puts ''
118
+ end
119
+
120
+ def show_s3_status(_project_entry)
121
+ puts ' ☁️ S3 Staging: ✓ exists'
122
+
123
+ # TODO: Query S3 for detailed status (files needing sync)
124
+ # For now, just show that s3-staging folder exists locally
125
+ s3_staging_path = File.join(project_path, 's3-staging')
126
+ if Dir.exist?(s3_staging_path)
127
+ file_count = Dir.glob(File.join(s3_staging_path, '*')).count
128
+ puts " Local staging files: #{file_count}"
129
+ end
130
+ puts ''
131
+ end
132
+
133
+ def show_ssd_status(project_entry)
134
+ puts ' 💾 SSD Backup: ✓ exists'
135
+ puts " Path: #{project_entry[:storage][:ssd][:path]}"
136
+ puts ''
137
+ end
138
+
139
+ def show_git_status
140
+ puts 'Git:'
141
+
142
+ status = git_status_info
143
+
144
+ puts " 🌿 Branch: #{status[:branch]}"
145
+ puts " 📡 Remote: #{status[:remote]}" if status[:remote]
146
+
147
+ if status[:modified_count].positive? || status[:untracked_count].positive?
148
+ puts " ↕️ Status: #{status[:modified_count]} modified, #{status[:untracked_count]} untracked"
149
+ else
150
+ puts ' ↕️ Status: Clean working directory'
151
+ end
152
+
153
+ if status[:ahead].positive? || status[:behind].positive?
154
+ puts " 🔄 Sync: #{sync_status_text(status[:ahead], status[:behind])}"
155
+ else
156
+ puts ' 🔄 Sync: Up to date'
157
+ end
158
+
159
+ puts ''
160
+ end
161
+
162
+ def show_brand_git_status
163
+ status = git_status_info
164
+
165
+ puts "🌿 Branch: #{status[:branch]}"
166
+ puts "📡 Remote: #{status[:remote]}" if status[:remote]
167
+
168
+ if status[:modified_count].positive? || status[:untracked_count].positive?
169
+ puts "↕️ Changes: #{status[:modified_count]} modified, #{status[:untracked_count]} untracked"
170
+ else
171
+ puts '✓ Working directory clean'
172
+ end
173
+
174
+ if status[:ahead].positive? || status[:behind].positive?
175
+ puts "🔄 Sync: #{sync_status_text(status[:ahead], status[:behind])}"
176
+ else
177
+ puts '✓ Up to date with remote'
178
+ end
179
+ end
180
+
181
+ def show_manifest_summary(manifest)
182
+ puts '📋 Manifest Summary:'
183
+ puts " Total projects: #{manifest[:projects].size}"
184
+
185
+ local_count = manifest[:projects].count { |p| p[:storage][:local][:exists] }
186
+ s3_count = manifest[:projects].count { |p| p[:storage][:s3][:exists] }
187
+ ssd_count = manifest[:projects].count { |p| p[:storage][:ssd][:exists] }
188
+
189
+ puts " Local: #{local_count}"
190
+ puts " S3 staging: #{s3_count}"
191
+ puts " SSD backup: #{ssd_count}"
192
+
193
+ # Project types
194
+ storyline_count = manifest[:projects].count { |p| p[:hasStorylineJson] }
195
+ puts ''
196
+ puts " Storyline projects: #{storyline_count}"
197
+ puts " FliVideo projects: #{manifest[:projects].size - storyline_count}"
198
+ end
199
+
200
+ def sync_status_text(ahead, behind)
201
+ parts = []
202
+ parts << "#{ahead} ahead" if ahead.positive?
203
+ parts << "#{behind} behind" if behind.positive?
204
+ parts.join(', ')
205
+ end
206
+
207
+ def load_manifest
208
+ manifest_path = File.join(brand_path, 'projects.json')
209
+ return nil unless File.exist?(manifest_path)
210
+
211
+ JSON.parse(File.read(manifest_path), symbolize_names: true)
212
+ rescue JSON::ParserError
213
+ nil
214
+ end
215
+
216
+ def find_project_in_manifest(manifest)
217
+ return nil unless manifest
218
+
219
+ project_name = File.basename(project_path)
220
+ manifest[:projects].find { |p| p[:id] == project_name }
221
+ end
222
+
223
+ def git_repo?
224
+ git_dir = File.join(brand_path, '.git')
225
+ Dir.exist?(git_dir)
226
+ end
227
+
228
+ def git_status_info
229
+ {
230
+ branch: current_branch,
231
+ remote: remote_url,
232
+ modified_count: modified_files_count,
233
+ untracked_count: untracked_files_count,
234
+ ahead: commits_ahead,
235
+ behind: commits_behind
236
+ }
237
+ end
238
+
239
+ def current_branch
240
+ `git -C "#{brand_path}" rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
241
+ rescue StandardError
242
+ 'unknown'
243
+ end
244
+
245
+ def remote_url
246
+ result = `git -C "#{brand_path}" remote get-url origin 2>/dev/null`.strip
247
+ result.empty? ? nil : result
248
+ rescue StandardError
249
+ nil
250
+ end
251
+
252
+ def modified_files_count
253
+ `git -C "#{brand_path}" status --porcelain 2>/dev/null | grep -E "^.M|^M" | wc -l`.strip.to_i
254
+ rescue StandardError
255
+ 0
256
+ end
257
+
258
+ def untracked_files_count
259
+ `git -C "#{brand_path}" status --porcelain 2>/dev/null | grep -E "^\\?\\?" | wc -l`.strip.to_i
260
+ rescue StandardError
261
+ 0
262
+ end
263
+
264
+ def commits_ahead
265
+ `git -C "#{brand_path}" rev-list --count @{upstream}..HEAD 2>/dev/null`.strip.to_i
266
+ rescue StandardError
267
+ 0
268
+ end
269
+
270
+ def commits_behind
271
+ `git -C "#{brand_path}" rev-list --count HEAD..@{upstream} 2>/dev/null`.strip.to_i
272
+ rescue StandardError
273
+ 0
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.19.0'
5
+ VERSION = '0.20.0'
6
6
  end
7
7
  end
@@ -59,6 +59,10 @@ require 'appydave/tools/dam/s3_operations'
59
59
  require 'appydave/tools/dam/project_listing'
60
60
  require 'appydave/tools/dam/manifest_generator'
61
61
  require 'appydave/tools/dam/sync_from_ssd'
62
+ require 'appydave/tools/dam/status'
63
+ require 'appydave/tools/dam/repo_status'
64
+ require 'appydave/tools/dam/repo_sync'
65
+ require 'appydave/tools/dam/repo_push'
62
66
 
63
67
  require 'appydave/tools/youtube_automation/gpt_agent'
64
68
 
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "AppyDave YouTube Automation Tools",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appydave-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
@@ -292,7 +292,11 @@ files:
292
292
  - lib/appydave/tools/dam/manifest_generator.rb
293
293
  - lib/appydave/tools/dam/project_listing.rb
294
294
  - lib/appydave/tools/dam/project_resolver.rb
295
+ - lib/appydave/tools/dam/repo_push.rb
296
+ - lib/appydave/tools/dam/repo_status.rb
297
+ - lib/appydave/tools/dam/repo_sync.rb
295
298
  - lib/appydave/tools/dam/s3_operations.rb
299
+ - lib/appydave/tools/dam/status.rb
296
300
  - lib/appydave/tools/dam/sync_from_ssd.rb
297
301
  - lib/appydave/tools/debuggable.rb
298
302
  - lib/appydave/tools/gpt_context/_doc.md