appydave-tools 0.19.0 → 0.20.1
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 +4 -4
- data/CHANGELOG.md +15 -0
- data/bin/dam +122 -0
- data/docs/dam/dam-testing-plan.md +142 -45
- data/docs/dam/usage.md +138 -1
- data/docs/development/CODEX-recommendations.md +13 -9
- data/lib/appydave/tools/configuration/models/config_base.rb +1 -2
- data/lib/appydave/tools/dam/repo_push.rb +131 -0
- data/lib/appydave/tools/dam/repo_status.rb +140 -0
- data/lib/appydave/tools/dam/repo_sync.rb +122 -0
- data/lib/appydave/tools/dam/status.rb +278 -0
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools.rb +4 -0
- data/package.json +1 -1
- metadata +5 -1
|
@@ -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
|
data/lib/appydave/tools.rb
CHANGED
|
@@ -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
|
|