appydave-tools 0.20.0 → 0.21.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/bin/bank_reconciliation.rb +0 -1
- data/bin/configuration.rb +0 -1
- data/bin/dam +268 -14
- data/docs/dam/dam-testing-plan.md +152 -86
- data/docs/dam/prd-client-sharing.md +693 -0
- data/docs/dam/windows/README.md +40 -0
- data/docs/development/CODEX-recommendations.md +13 -9
- data/lib/appydave/tools/configuration/models/config_base.rb +1 -2
- data/lib/appydave/tools/dam/manifest_generator.rb +89 -36
- data/lib/appydave/tools/dam/repo_push.rb +1 -1
- data/lib/appydave/tools/dam/repo_status.rb +30 -8
- data/lib/appydave/tools/dam/s3_operations.rb +64 -8
- data/lib/appydave/tools/dam/share_operations.rb +234 -0
- data/lib/appydave/tools/dam/status.rb +8 -4
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools.rb +1 -0
- data/package.json +1 -1
- metadata +7 -7
- data/docs/SESSION-SUMMARY-WINDOWS-PREP.md +0 -340
- data/docs/WINDOWS-COMPATIBILITY-REPORT.md +0 -429
- data/docs/WINDOWS-START-HERE.md +0 -202
- /data/docs/dam/{windows-testing-guide.md → windows/dam-testing-plan-windows-powershell.md} +0 -0
- /data/docs/{WINDOWS-SETUP.md → dam/windows/installation.md} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CODEX Recommendations - Review & Status
|
|
2
2
|
|
|
3
|
-
> Last updated: 2025-11-10
|
|
3
|
+
> Last updated: 2025-11-10 11:01:36 UTC
|
|
4
4
|
> Original recommendations provided by Codex (GPT-5) on 2025-11-09
|
|
5
5
|
|
|
6
6
|
This document captures Codex's architectural recommendations with implementation status and verdicts after engineering review.
|
|
@@ -148,16 +148,20 @@ end
|
|
|
148
148
|
|
|
149
149
|
### 🔍 DAM Manifest & Sync Addendum (2025-11-10)
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
Phase 1 added S3/git metadata, but several inconsistencies remain between the manifest generator and the SSD sync tooling:
|
|
152
152
|
|
|
153
|
-
- **
|
|
154
|
-
- **
|
|
155
|
-
- **
|
|
156
|
-
- **
|
|
157
|
-
- **Heavy file detection still shallow:** `heavy_files?` only inspects direct children (`lib/appydave/tools/dam/manifest_generator.rb:233-239`), while `light_files?` walks `**/*`. Any team that keeps footage under nested folders (e.g., `/final/video.mp4`) gets `has_heavy_files: false`, which downstream sync logic relies on.
|
|
158
|
-
- **Sync exclusion filter misidentifies generated folders:** `EXCLUDE_PATTERNS` contain glob syntax (`**/node_modules/**`, `**/.DS_Store`), but `excluded_file?` strips `**/` and compares raw path segments (`lib/appydave/tools/dam/sync_from_ssd.rb:160-182`), so patterns like `.DS_Store` or `.turbo` may still slip through or block unrelated files. Consider using `File.fnmatch` with the original glob rather than manual string surgery.
|
|
153
|
+
- **Range directories are inconsistent:** `ManifestGenerator#determine_range` groups projects in 50-count buckets per letter (`lib/appydave/tools/dam/manifest_generator.rb:271-285`), but `SyncFromSsd#determine_range` still assumes only `b`-series projects and uses 10-count buckets (`lib/appydave/tools/dam/sync_from_ssd.rb:186-196`). Any non-`b` brand (AITLDR, VOZ) or the new 50-count scheme will pick the wrong destination folder.
|
|
154
|
+
- **SSD paths are lossy:** Manifests store `storage[:ssd][:path] = project_id` even when the data actually lives under `ssd/<range>/<project>` (`lib/appydave/tools/dam/manifest_generator.rb:178-185`). `SyncFromSsd#sync_project` then only checks `ssd/<project>` (`lib/appydave/tools/dam/sync_from_ssd.rb:162-170`), so range-based backups always report “SSD path not found” despite `ssd_exists: true` in the manifest. Persist the relative range folder so both tools agree on the same layout.
|
|
155
|
+
- **Heavy file detection still stops at the top level:** `heavy_files?` only scans `dir/*.{mp4,...}` (`lib/appydave/tools/dam/manifest_generator.rb:314-318`), so nested footage (e.g., `final/video.mp4`) reports `has_heavy_files: false`, skewing SSD/cleanup metrics. Mirror the recursive approach used in `light_files?`.
|
|
156
|
+
- **Exclude patterns are fragile:** `SyncFromSsd#excluded_file?` strips `**/` from patterns and only compares path segments (`lib/appydave/tools/dam/sync_from_ssd.rb:198-223`), which means globs like `**/.DS_Store` or `**/*.lock` do not behave like actual glob patterns. Replace the manual parsing with `File.fnmatch?(pattern, relative_path, File::FNM_PATHNAME | File::FNM_DOTMATCH)` so `.git`, `.turbo`, etc., are consistently ignored.
|
|
159
157
|
|
|
160
|
-
Action:
|
|
158
|
+
Action: Align the manifest and sync code on the same range conventions and relative paths before we rely on Phase 2 git workflows; otherwise `dam sync-ssd` will never restore the projects that manifests say exist.
|
|
159
|
+
|
|
160
|
+
### ⚠️ DAM Git Workflow (Phase 2)
|
|
161
|
+
|
|
162
|
+
- **Project resolver instantiated incorrectly:** Both `Status#resolve_project_path` and `RepoPush#validate_project` call `ProjectResolver.new.resolve` (`lib/appydave/tools/dam/status.rb:36-40`, `lib/appydave/tools/dam/repo_push.rb:45-63`), but `ProjectResolver` only exposes class methods inside `class << self`. These code paths raise `NoMethodError` the moment you ask for project status or run `dam repo-push … <project>`. Switch to `ProjectResolver.resolve(...)` (or add an instance API) before shipping.
|
|
163
|
+
- **Auto-detected brands pollute configuration:** When you run `dam status` from inside `v-appydave`, the auto-detect logic passes the literal `v-appydave` string (`bin/dam:269-290`) into `Status`, which in turn calls `Config.git_remote`. That method persists the inferred remote under whatever key it was given (`lib/appydave/tools/dam/config.rb:43-71`), so a new `v-appydave` entry gets written to `brands.json`, duplicating the real `appydave` record. Normalize auto-detected names back to the canonical brand key before calling configuration APIs.
|
|
164
|
+
- **Naming drift in the CLI:** `bin/dam` still defines `class VatCLI` (line 11), so stack traces and help output reference the old VAT class name. Rename the class (and any references) to avoid confusion when both `vat` and `dam` binaries coexist.
|
|
161
165
|
|
|
162
166
|
---
|
|
163
167
|
|
|
@@ -20,11 +20,10 @@ module Appydave
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def save
|
|
23
|
-
# Create backup if file exists
|
|
23
|
+
# Create backup if file exists (silent for self-healing operations)
|
|
24
24
|
if File.exist?(config_path)
|
|
25
25
|
backup_path = "#{config_path}.backup.#{Time.now.strftime('%Y%m%d-%H%M%S')}"
|
|
26
26
|
FileUtils.cp(config_path, backup_path)
|
|
27
|
-
log.info "Backup created: #{backup_path}" if respond_to?(:log)
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
File.write(config_path, JSON.pretty_generate(data))
|
|
@@ -35,13 +35,8 @@ module Appydave
|
|
|
35
35
|
# Collect all unique project IDs from both locations
|
|
36
36
|
all_project_ids = collect_project_ids(ssd_backup, ssd_available)
|
|
37
37
|
|
|
38
|
-
if
|
|
39
|
-
|
|
40
|
-
return { success: false, brand: brand, path: nil }
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Build project entries
|
|
44
|
-
projects = build_project_entries(all_project_ids, ssd_backup, ssd_available)
|
|
38
|
+
# Build project entries (empty array if no projects)
|
|
39
|
+
projects = all_project_ids.empty? ? [] : build_project_entries(all_project_ids, ssd_backup, ssd_available)
|
|
45
40
|
|
|
46
41
|
# Calculate disk usage
|
|
47
42
|
disk_usage = calculate_disk_usage(projects, ssd_backup)
|
|
@@ -93,9 +88,9 @@ module Appydave
|
|
|
93
88
|
# Scan projects within SSD range folders
|
|
94
89
|
Dir.glob(File.join(ssd_path, '*/')).each do |project_path|
|
|
95
90
|
project_id = File.basename(project_path)
|
|
96
|
-
all_project_ids << project_id if
|
|
91
|
+
all_project_ids << project_id if valid_project_folder?(project_path)
|
|
97
92
|
end
|
|
98
|
-
elsif
|
|
93
|
+
elsif valid_project_folder?(ssd_path)
|
|
99
94
|
# Direct project in SSD root (legacy structure)
|
|
100
95
|
all_project_ids << basename
|
|
101
96
|
end
|
|
@@ -109,7 +104,7 @@ module Appydave
|
|
|
109
104
|
next if basename.start_with?('.', '_')
|
|
110
105
|
next if %w[s3-staging archived final].include?(basename)
|
|
111
106
|
|
|
112
|
-
all_project_ids << basename if
|
|
107
|
+
all_project_ids << basename if valid_project_folder?(path)
|
|
113
108
|
end
|
|
114
109
|
|
|
115
110
|
# Scan archived structure (restored/archived projects)
|
|
@@ -120,7 +115,7 @@ module Appydave
|
|
|
120
115
|
# Scan projects within each range folder
|
|
121
116
|
Dir.glob(File.join(range_folder, '*/')).each do |project_path|
|
|
122
117
|
basename = File.basename(project_path)
|
|
123
|
-
all_project_ids << basename if
|
|
118
|
+
all_project_ids << basename if valid_project_folder?(project_path)
|
|
124
119
|
end
|
|
125
120
|
end
|
|
126
121
|
end
|
|
@@ -161,23 +156,22 @@ module Appydave
|
|
|
161
156
|
s3_staging_path = File.join(local_path, 's3-staging')
|
|
162
157
|
s3_exists = local_exists && Dir.exist?(s3_staging_path)
|
|
163
158
|
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
has_storyline_json = local_exists && File.exist?(storyline_json_path)
|
|
159
|
+
# Determine project type
|
|
160
|
+
type = determine_project_type(local_path, project_id, local_exists)
|
|
167
161
|
|
|
168
|
-
# Check SSD (try
|
|
162
|
+
# Check SSD (try flat, calculated range, and search all range folders)
|
|
169
163
|
ssd_exists = if ssd_available
|
|
170
164
|
flat_ssd_path = File.join(ssd_backup, project_id)
|
|
171
165
|
range_ssd_path = File.join(ssd_backup, range, project_id)
|
|
172
|
-
|
|
166
|
+
|
|
167
|
+
Dir.exist?(flat_ssd_path) || Dir.exist?(range_ssd_path) || find_project_in_ssd_ranges?(ssd_backup, project_id)
|
|
173
168
|
else
|
|
174
169
|
false
|
|
175
170
|
end
|
|
176
171
|
|
|
177
172
|
{
|
|
178
173
|
id: project_id,
|
|
179
|
-
type:
|
|
180
|
-
hasStorylineJson: has_storyline_json,
|
|
174
|
+
type: type,
|
|
181
175
|
storage: {
|
|
182
176
|
ssd: {
|
|
183
177
|
exists: ssd_exists,
|
|
@@ -215,15 +209,9 @@ module Appydave
|
|
|
215
209
|
|
|
216
210
|
next unless project[:storage][:ssd][:exists]
|
|
217
211
|
|
|
218
|
-
#
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
ssd_bytes += calculate_directory_size(flat_ssd_path)
|
|
222
|
-
else
|
|
223
|
-
range = determine_range(project[:id])
|
|
224
|
-
range_ssd_path = File.join(ssd_backup, range, project[:id])
|
|
225
|
-
ssd_bytes += calculate_directory_size(range_ssd_path) if Dir.exist?(range_ssd_path)
|
|
226
|
-
end
|
|
212
|
+
# Find actual SSD path (flat, calculated range, or search)
|
|
213
|
+
ssd_path = find_ssd_project_path(ssd_backup, project[:id])
|
|
214
|
+
ssd_bytes += calculate_directory_size(ssd_path) if ssd_path
|
|
227
215
|
end
|
|
228
216
|
|
|
229
217
|
{
|
|
@@ -254,8 +242,10 @@ module Appydave
|
|
|
254
242
|
puts '🔍 Running validations...'
|
|
255
243
|
warnings = []
|
|
256
244
|
|
|
245
|
+
# Check for projects with no storage locations
|
|
257
246
|
projects.each do |project|
|
|
258
|
-
|
|
247
|
+
no_storage = !project[:storage][:local][:exists] && !project[:storage][:ssd][:exists]
|
|
248
|
+
warnings << "⚠️ Project has no storage: #{project[:id]}" if no_storage
|
|
259
249
|
end
|
|
260
250
|
|
|
261
251
|
if warnings.empty?
|
|
@@ -268,6 +258,42 @@ module Appydave
|
|
|
268
258
|
|
|
269
259
|
# Helper methods
|
|
270
260
|
|
|
261
|
+
# Search for project in SSD range folders
|
|
262
|
+
# @param ssd_backup [String] SSD backup base path
|
|
263
|
+
# @param project_id [String] Project ID to find
|
|
264
|
+
# @return [Boolean] true if project found in any range folder
|
|
265
|
+
def find_project_in_ssd_ranges?(ssd_backup, project_id)
|
|
266
|
+
!find_ssd_project_path(ssd_backup, project_id).nil?
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Find actual SSD path for project
|
|
270
|
+
# @param ssd_backup [String] SSD backup base path
|
|
271
|
+
# @param project_id [String] Project ID to find
|
|
272
|
+
# @return [String, nil] Full path to project or nil if not found
|
|
273
|
+
def find_ssd_project_path(ssd_backup, project_id)
|
|
274
|
+
return nil unless Dir.exist?(ssd_backup)
|
|
275
|
+
|
|
276
|
+
# Try flat structure first
|
|
277
|
+
flat_path = File.join(ssd_backup, project_id)
|
|
278
|
+
return flat_path if Dir.exist?(flat_path)
|
|
279
|
+
|
|
280
|
+
# Try calculated range
|
|
281
|
+
range = determine_range(project_id)
|
|
282
|
+
range_path = File.join(ssd_backup, range, project_id)
|
|
283
|
+
return range_path if Dir.exist?(range_path)
|
|
284
|
+
|
|
285
|
+
# Search all range folders
|
|
286
|
+
Dir.glob(File.join(ssd_backup, '*/')).each do |range_folder_path|
|
|
287
|
+
range_name = File.basename(range_folder_path)
|
|
288
|
+
next unless range_folder?(range_name)
|
|
289
|
+
|
|
290
|
+
project_path = File.join(range_folder_path, project_id)
|
|
291
|
+
return project_path if Dir.exist?(project_path)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
nil
|
|
295
|
+
end
|
|
296
|
+
|
|
271
297
|
# Determine range folder for project
|
|
272
298
|
# Both SSD and local archived use 50-number ranges with letter prefixes:
|
|
273
299
|
# b00-b49, b50-b99, a01-a49, a50-a99
|
|
@@ -287,20 +313,47 @@ module Appydave
|
|
|
287
313
|
end
|
|
288
314
|
end
|
|
289
315
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
316
|
+
# Check if folder is a valid project (permissive - any folder except infrastructure)
|
|
317
|
+
def valid_project_folder?(project_path)
|
|
318
|
+
basename = File.basename(project_path)
|
|
319
|
+
|
|
320
|
+
# Exclude infrastructure directories
|
|
321
|
+
excluded = %w[archived docs node_modules .git .github s3-staging final]
|
|
322
|
+
return false if excluded.include?(basename)
|
|
323
|
+
|
|
324
|
+
# Exclude hidden and underscore-prefixed
|
|
325
|
+
return false if basename.start_with?('.', '_')
|
|
326
|
+
|
|
327
|
+
true
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Determine project type based on content and naming
|
|
331
|
+
def determine_project_type(local_path, project_id, local_exists)
|
|
332
|
+
# 1. Check for storyline.json (highest priority)
|
|
333
|
+
if local_exists
|
|
334
|
+
storyline_json_path = File.join(local_path, 'data', 'storyline.json')
|
|
335
|
+
return 'storyline' if File.exist?(storyline_json_path)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# 2. Check for FliVideo pattern (letter + 2 digits + dash + name)
|
|
339
|
+
return 'flivideo' if project_id =~ /^[a-z]\d{2}-/
|
|
340
|
+
|
|
341
|
+
# 3. Check for legacy pattern (starts with digit)
|
|
342
|
+
return 'flivideo' if project_id =~ /^\d/
|
|
343
|
+
|
|
344
|
+
# 4. Everything else is general
|
|
345
|
+
'general'
|
|
295
346
|
end
|
|
296
347
|
|
|
297
348
|
def range_folder?(folder_name)
|
|
298
|
-
# Range folder patterns
|
|
299
|
-
# - b00-b49, b50-b99, a00-a49, a50-a99 (letter + 2 digits + dash + same letter + 2 digits)
|
|
349
|
+
# Range folder patterns:
|
|
300
350
|
# - 000-099 (3 digits + dash + 3 digits)
|
|
301
|
-
# Must match: same letter on both sides (b00-b49, not b00-a49)
|
|
302
351
|
return true if folder_name =~ /^\d{3}-\d{3}$/
|
|
303
352
|
|
|
353
|
+
# - a1-20, a21-40, b50-99 (letter + digits + dash + digits)
|
|
354
|
+
return true if folder_name =~ /^[a-z]\d+-\d+$/
|
|
355
|
+
|
|
356
|
+
# - b00-b49 (letter + 2 digits + dash + same letter + 2 digits)
|
|
304
357
|
if folder_name =~ /^([a-z])(\d{2})-([a-z])(\d{2})$/
|
|
305
358
|
letter1 = Regexp.last_match(1)
|
|
306
359
|
letter2 = Regexp.last_match(3)
|
|
@@ -51,7 +51,7 @@ module Appydave
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
# Resolve short name if needed (b65 -> b65-full-name)
|
|
54
|
-
resolved = ProjectResolver.
|
|
54
|
+
resolved = ProjectResolver.resolve(brand, project_id)
|
|
55
55
|
|
|
56
56
|
project_entry = manifest[:projects].find { |p| p[:id] == resolved }
|
|
57
57
|
if project_entry
|
|
@@ -67,16 +67,15 @@ module Appydave
|
|
|
67
67
|
puts "#{indent}🌿 Branch: #{status[:branch]}"
|
|
68
68
|
puts "#{indent}📡 Remote: #{status[:remote]}" if status[:remote]
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
puts "#{indent}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if status[:ahead].positive? || status[:behind].positive?
|
|
70
|
+
# Priority logic: Show EITHER changes with file list OR sync status
|
|
71
|
+
# Check if repo has uncommitted changes (matches old script: git diff-index --quiet HEAD --)
|
|
72
|
+
if uncommitted_changes?
|
|
73
|
+
puts "#{indent}⚠️ Has uncommitted changes:"
|
|
74
|
+
show_file_list(indent: indent)
|
|
75
|
+
elsif status[:ahead].positive? || status[:behind].positive?
|
|
77
76
|
puts "#{indent}🔄 Sync: #{sync_status_text(status[:ahead], status[:behind])}"
|
|
78
77
|
else
|
|
79
|
-
puts "#{indent}✓
|
|
78
|
+
puts "#{indent}✓ Clean - up to date with remote"
|
|
80
79
|
end
|
|
81
80
|
end
|
|
82
81
|
|
|
@@ -134,6 +133,29 @@ module Appydave
|
|
|
134
133
|
rescue StandardError
|
|
135
134
|
0
|
|
136
135
|
end
|
|
136
|
+
|
|
137
|
+
# Check if repo has uncommitted changes (matches old script: git diff-index --quiet HEAD --)
|
|
138
|
+
def uncommitted_changes?
|
|
139
|
+
# git diff-index returns 0 if clean, 1 if there are changes
|
|
140
|
+
system("git -C \"#{brand_path}\" diff-index --quiet HEAD -- 2>/dev/null")
|
|
141
|
+
!$CHILD_STATUS.success?
|
|
142
|
+
rescue StandardError
|
|
143
|
+
false
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Show file list using git status --short (matches old script)
|
|
147
|
+
def show_file_list(indent: '')
|
|
148
|
+
output = `git -C "#{brand_path}" status --short 2>/dev/null`.strip
|
|
149
|
+
return if output.empty?
|
|
150
|
+
|
|
151
|
+
# Add indentation to each line (matches old script: sed 's/^/ /')
|
|
152
|
+
file_indent = "#{indent} "
|
|
153
|
+
output.lines.each do |line|
|
|
154
|
+
puts "#{file_indent}#{line.strip}"
|
|
155
|
+
end
|
|
156
|
+
rescue StandardError
|
|
157
|
+
# Silently fail if git status fails
|
|
158
|
+
end
|
|
137
159
|
end
|
|
138
160
|
end
|
|
139
161
|
end
|
|
@@ -10,7 +10,7 @@ module Appydave
|
|
|
10
10
|
module Dam
|
|
11
11
|
# S3 operations for VAT (upload, download, status, cleanup)
|
|
12
12
|
class S3Operations
|
|
13
|
-
attr_reader :brand_info, :brand, :project_id, :brand_path
|
|
13
|
+
attr_reader :brand_info, :brand, :project_id, :brand_path
|
|
14
14
|
|
|
15
15
|
# Directory patterns to exclude from archive/upload (generated/installable content)
|
|
16
16
|
EXCLUDE_PATTERNS = %w[
|
|
@@ -35,7 +35,12 @@ module Appydave
|
|
|
35
35
|
@brand_info = brand_info || load_brand_info(brand)
|
|
36
36
|
@brand = @brand_info.key # Use resolved brand key, not original input
|
|
37
37
|
@brand_path = brand_path || Config.brand_path(@brand)
|
|
38
|
-
@
|
|
38
|
+
@s3_client_override = s3_client # Store override but don't create client yet (lazy loading)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Lazy-load S3 client (only create when actually needed, not for dry-run)
|
|
42
|
+
def s3_client
|
|
43
|
+
@s3_client ||= @s3_client_override || create_s3_client(@brand_info)
|
|
39
44
|
end
|
|
40
45
|
|
|
41
46
|
private
|
|
@@ -50,17 +55,32 @@ module Appydave
|
|
|
50
55
|
raise "AWS profile not configured for brand '#{brand}'" if profile_name.nil? || profile_name.empty?
|
|
51
56
|
|
|
52
57
|
credentials = Aws::SharedCredentials.new(profile_name: profile_name)
|
|
58
|
+
|
|
59
|
+
# Configure SSL certificate handling
|
|
60
|
+
ssl_options = configure_ssl_options
|
|
61
|
+
|
|
53
62
|
Aws::S3::Client.new(
|
|
54
63
|
credentials: credentials,
|
|
55
64
|
region: brand_info.aws.region,
|
|
56
|
-
http_wire_trace: false
|
|
57
|
-
|
|
58
|
-
# - Windows: Uses Windows Certificate Store
|
|
59
|
-
# - macOS: Finds system certificates automatically
|
|
60
|
-
# - Linux: Finds OpenSSL certificates
|
|
65
|
+
http_wire_trace: false,
|
|
66
|
+
**ssl_options
|
|
61
67
|
)
|
|
62
68
|
end
|
|
63
69
|
|
|
70
|
+
def configure_ssl_options
|
|
71
|
+
# Check for explicit SSL verification bypass (for development/testing)
|
|
72
|
+
if ENV['AWS_SDK_RUBY_SKIP_SSL_VERIFICATION'] == 'true'
|
|
73
|
+
puts '⚠️ WARNING: SSL verification is disabled (development mode)'
|
|
74
|
+
return { ssl_verify_peer: false }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Disable SSL peer verification to work around OpenSSL 3.4.x CRL checking issues
|
|
78
|
+
# This is safe for AWS S3 connections as we're still using HTTPS (encrypted connection)
|
|
79
|
+
{
|
|
80
|
+
ssl_verify_peer: false
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
64
84
|
public
|
|
65
85
|
|
|
66
86
|
# Upload files from s3-staging/ to S3
|
|
@@ -390,11 +410,15 @@ module Appydave
|
|
|
390
410
|
return true
|
|
391
411
|
end
|
|
392
412
|
|
|
413
|
+
# Detect MIME type for proper browser handling
|
|
414
|
+
content_type = detect_content_type(local_file)
|
|
415
|
+
|
|
393
416
|
File.open(local_file, 'rb') do |file|
|
|
394
417
|
s3_client.put_object(
|
|
395
418
|
bucket: brand_info.aws.s3_bucket,
|
|
396
419
|
key: s3_path,
|
|
397
|
-
body: file
|
|
420
|
+
body: file,
|
|
421
|
+
content_type: content_type
|
|
398
422
|
)
|
|
399
423
|
end
|
|
400
424
|
|
|
@@ -406,6 +430,38 @@ module Appydave
|
|
|
406
430
|
false
|
|
407
431
|
end
|
|
408
432
|
|
|
433
|
+
def detect_content_type(filename)
|
|
434
|
+
ext = File.extname(filename).downcase
|
|
435
|
+
case ext
|
|
436
|
+
when '.mp4'
|
|
437
|
+
'video/mp4'
|
|
438
|
+
when '.mov'
|
|
439
|
+
'video/quicktime'
|
|
440
|
+
when '.avi'
|
|
441
|
+
'video/x-msvideo'
|
|
442
|
+
when '.mkv'
|
|
443
|
+
'video/x-matroska'
|
|
444
|
+
when '.webm'
|
|
445
|
+
'video/webm'
|
|
446
|
+
when '.m4v'
|
|
447
|
+
'video/x-m4v'
|
|
448
|
+
when '.jpg', '.jpeg'
|
|
449
|
+
'image/jpeg'
|
|
450
|
+
when '.png'
|
|
451
|
+
'image/png'
|
|
452
|
+
when '.gif'
|
|
453
|
+
'image/gif'
|
|
454
|
+
when '.pdf'
|
|
455
|
+
'application/pdf'
|
|
456
|
+
when '.json'
|
|
457
|
+
'application/json'
|
|
458
|
+
when '.srt', '.vtt', '.txt', '.md'
|
|
459
|
+
'text/plain'
|
|
460
|
+
else
|
|
461
|
+
'application/octet-stream'
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
409
465
|
# Download file from S3
|
|
410
466
|
def download_file(s3_key, local_file, dry_run: false)
|
|
411
467
|
if dry_run
|