Class: FastlaneFlutterFlavor::YamlSpecLoader

Inherits:
Object
  • Object
show all
Defined in:
lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb

Instance Method Summary collapse

Constructor Details

#initialize(root_folder, file_path) ⇒ YamlSpecLoader

Initialize the loader with the project root folder context and load the spec file.



10
11
12
13
14
15
16
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 10

def initialize(root_folder, file_path)
  @root_folder = root_folder
  @file_path = file_path # The file path, potentially overridden by ENV outside this class

  # Load the file content immediately and store it
  @spec_data = load_file
end

Instance Method Details

#deep_merge_defaults(current_data, default_data) ⇒ Object

Recursively merges defaults into a flavor’s data. This function prioritizes ‘current_data’ (the flavor-specific value). If a key is present in ‘default_data’ but missing in ‘current_data’, the default value is used.

Parameters:

  • current_data (Object)

    The flavor-specific data (Hash or scalar).

  • default_data (Object)

    The default data to merge (Hash or scalar).

Returns:

  • (Object)

    The merged data.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 49

def deep_merge_defaults(current_data, default_data)

  # FIX: If current_data is a scalar, it means the flavor explicitly set this value.
  # It overrides the default, so we return it immediately.
  return current_data unless current_data.is_a?(Hash)

  # Base case 2: If default_data is not a hash, there's nothing left to merge deeply.
  return current_data unless default_data.is_a?(Hash)

  # Recursive case: Both are hashes, perform the merge
  default_data.each do |key, default_value|
    current_value = current_data[key]

    if current_data.key?(key)
      # Key exists in flavor (current_data), recursively merge
      current_data[key] = deep_merge_defaults(current_value, default_value)
    else
      # The key is missing in the current flavor data. Use the default value.
      current_data[key] = default_value
    end
  end

  return current_data
end

#get_api_key_path(platform, flavor_name) ⇒ String?

Returns The API key path (e.g., path to Google Service Account JSON or App Store Connect API Key JSON).

Returns:

  • (String, nil)

    The API key path (e.g., path to Google Service Account JSON or App Store Connect API Key JSON).



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 234

def get_api_key_path(platform, flavor_name)
  # 1. Get the merged configuration for the specific flavor
  config = get_merged_flavor_config(platform, flavor_name)

  # Return nil early if configuration is missing
  return nil unless config

  # 2. Extract the key based on the platform
  case platform
  when :android
    # For Android, we look for 'google_api_key'
    config.dig('fastlane', 'google_api_key')
  when :ios
    # For iOS, we look for 'apple_api_key'
    config.dig('fastlane', 'apple_api_key')
  else
    # Return nil for other platforms
    nil
  end
end

#get_auth_client_id(platform, flavor_name, build_config) ⇒ Object

For Google Sign-In (annai_auth)



353
354
355
356
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 353

def get_auth_client_id(platform, flavor_name, build_config)
  config = get_merged_flavor_config(platform, flavor_name)
  config.dig('auth', build_config, 'clientId') if config
end

#get_auth_reversed_client_id(platform, flavor_name, build_config) ⇒ Object



358
359
360
361
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 358

def get_auth_reversed_client_id(platform, flavor_name, build_config)
  config = get_merged_flavor_config(platform, flavor_name)
  config.dig('auth', build_config, 'reversedClientId') if config
end

#get_display_name(platform, flavor_name) ⇒ String?

It combines ‘name’ and ‘name_suffix’ (if present) via direct concatenation. NOTE: It is assumed that the ‘name_suffix’ defined in the configuration

Returns:

  • (String, nil)

    The merged app name.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 166

def get_display_name(platform, flavor_name)
  config = get_merged_flavor_config(platform, flavor_name)
  return nil unless config

  base_name = config.dig('name')
  return nil unless base_name

  # Check for an optional suffix
  suffix = config.dig('name_suffix')

  # If a suffix is present, combine it directly with the base Name.
  # This relies on the suffix string itself containing the correct separator
  # to correctly form the final Name (e.g., "Test" + " App" -> "Test App").
  if suffix && !suffix.empty?
    return base_name + suffix
  else
    # Return the base Name if no suffix is present.
    return base_name
  end
end

#get_export_option_file(platform, flavor_name) ⇒ String?

Returns The export_option_file.

Returns:

  • (String, nil)

    The export_option_file.



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 256

def get_export_option_file(platform, flavor_name)
  # 1. Get the merged configuration for the specific flavor
  config = get_merged_flavor_config(platform, flavor_name)

  # Return nil early if configuration is missing
  return nil unless config

  # 2. Extract the key based on the platform
  case platform
  when :ios
    # For iOS, we look for 'export_options_plist'
    config.dig('fastlane', 'export_options_plist')
  else
    # Return nil for other platforms
    nil
  end
end

#get_firebase_project_id(platform, flavor_name, build_config) ⇒ String?

Returns The Firebase Project ID.

Returns:

  • (String, nil)

    The Firebase Project ID.



294
295
296
297
298
299
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 294

def get_firebase_project_id(platform, flavor_name, build_config)
  # 1. Get the merged configuration for the specific flavor
  config = get_merged_flavor_config(platform, flavor_name)

  config.dig('firebase', build_config, 'project_id') if config
end

#get_firebase_token_from_propertiesString?

Retrieves the firebase token from a external .properties file as defined in annai_app -> general -> firebase_token_file

Returns:

  • (String, nil)

    The token value, or nil if not found/error.



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 304

def get_firebase_token_from_properties
  return nil unless @spec_data

  # 1. Get the properties file path from the YAML spec
  # Path: annai_app -> general -> firebase_token_file
  rel_props_path = @spec_data.dig('annai_app', 'general', 'firebase_token_file')

  if rel_props_path.nil? || rel_props_path.empty?
    Fastlane::UI.verbose("No 'firebase_token_file' defined in annai_app -> general.")
    return nil
  end

  # 2. Resolve the full path relative to project root
  full_props_path = File.expand_path(rel_props_path, @root_folder)

  unless File.exist?(full_props_path)
    Fastlane::UI.error("Firebase token file not found at: #{full_props_path}")
    return nil
  end

  # 3. Parse the .properties file for FIREBASE_TOKEN
  begin
    File.foreach(full_props_path) do |line|
      # Remove whitespace and skip comments/empty lines
      line = line.strip
      next if line.empty? || line.start_with?('#', '!')

      # Match KEY=VALUE (handles both = and : as separators)
      if line =~ /^\s*FIREBASE_TOKEN\s*[=:]\s*(.*)$/
        token = $1.strip
        # Remove optional surrounding quotes if present
        return token.gsub(/^['"]|['"]$/, '')
      end
    end
  rescue => e
    Fastlane::UI.error("Error reading properties file at #{rel_props_path}: #{e.message}")
  end

  Fastlane::UI.verbose("FIREBASE_TOKEN key not found in #{rel_props_path}")
  return nil
end

#get_flavor_name(platform, flavor_name) ⇒ String?

Returns The flavor name (just for consistency with other getters).

Returns:

  • (String, nil)

    The flavor name (just for consistency with other getters).



134
135
136
137
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 134

def get_flavor_name(platform, flavor_name)
  config = get_merged_flavor_config(platform, flavor_name)
  config.dig('flavor') if config
end

#get_gms_ads_id(platform, flavor_name) ⇒ Object

For AdMob (annai_ads)



347
348
349
350
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 347

def get_gms_ads_id(platform, flavor_name)
  config = get_merged_flavor_config(platform, flavor_name)
  config.dig('gms_ads_id') if config
end

#get_main_file(platform, flavor_name) ⇒ String?

Returns The path to the main Dart file.

Returns:

  • (String, nil)

    The path to the main Dart file.



222
223
224
225
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 222

def get_main_file(platform, flavor_name)
  config = get_merged_flavor_config(platform, flavor_name)
  config.dig('main_file') if config
end

#get_merged_flavor_config(platform, flavor_name) ⇒ Hash?

Retrieves the configuration for a specific flavor, merged with the platform defaults. This function uses the spec data loaded into the instance variable @spec_data.

Parameters:

  • platform (Symbol)

    The target platform (:ios or :android).

  • flavor_name (String)

    The name of the flavor (e.g., ‘dev’, ‘staging’).

Returns:

  • (Hash, nil)

    The fully merged configuration for the flavor, or nil if missing.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 80

def get_merged_flavor_config(platform, flavor_name)
  return nil unless @spec_data # Check if file was loaded successfully

  platform_key = platform.to_s
  flavor_key = flavor_name.to_s

  # 1. Extract platform data from instance spec data
  platform_data = @spec_data.dig('annai_app', platform_key)
  unless platform_data.is_a?(Hash)
    Fastlane::UI.verbose("Annai spec missing 'annai_app' or platform '#{platform_key}'.")
    return nil
  end

  # 2. Extract specific flavor data and defaults
  flavor_data = platform_data.dig('flavor', flavor_key)
  default_data = platform_data.dig('default')

  # If flavor data is missing entirely, and we can't merge, return nil
  if flavor_data.nil? && default_data.nil?
    Fastlane::UI.verbose("Flavor '#{flavor_key}' and platform default config are missing for platform '#{platform_key}'.")
    return nil
  end

  # 3. Deep merge the flavor data (current) over the default data (base)
  current_config = flavor_data || {}

  # Perform the merge operation. We clone current_config to avoid modifying the original spec hash.
  merged_config = deep_merge_defaults(current_config.dup, default_data || {})

  # Add the flavor name itself for consistency in the returned config hash
  merged_config['flavor'] = flavor_key

  return merged_config
end

#get_package_id(platform, flavor_name) ⇒ String?

It combines ‘id’ and ‘id_suffix’ (if present) via direct concatenation. NOTE: It is assumed that the ‘id_suffix’ defined in the configuration

Returns:

  • (String, nil)

    The merged package ID (bundle ID or application ID).



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 142

def get_package_id(platform, flavor_name)
  config = get_merged_flavor_config(platform, flavor_name)
  return nil unless config

  base_id = config.dig('id')
  return nil unless base_id

  # Check for an optional suffix
  suffix = config.dig('id_suffix')

  # If a suffix is present, combine it directly with the base ID.
  # This relies on the suffix string itself containing the correct separator (like ".")
  # to correctly form the final package ID (e.g., "com.base" + ".dev" -> "com.base.dev").
  if suffix && !suffix.empty?
    return base_id + suffix
  else
    # Return the base ID if no suffix is present.
    return base_id
  end
end

#get_platform_flavors(platform) ⇒ Hash

Retrieves the hash of all defined flavors for a given platform. Uses the instance spec data.

Parameters:

  • platform (Symbol)

    The target platform (:ios or :android).

Returns:

  • (Hash)

    A hash where keys are flavor names (String) and values are their raw configs (Hash).



122
123
124
125
126
127
128
129
130
131
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 122

def get_platform_flavors(platform)
  return {} unless @spec_data
  platform_key = platform.to_s

  # Use dig to safely retrieve the flavors hash from @spec_data
  flavors = @spec_data.dig('annai_app', platform_key, 'flavor')

  # Return the flavors hash, or an empty hash if no flavors are defined
  return flavors.is_a?(Hash) ? flavors : {}
end

#get_priority(platform, flavor_name) ⇒ String?

Returns The priority level.

Returns:

  • (String, nil)

    The priority level.



228
229
230
231
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 228

def get_priority(platform, flavor_name)
  config = get_merged_flavor_config(platform, flavor_name)
  config.dig('priority') if config
end

#get_team_id(platform, flavor_name) ⇒ String?

Returns The App store Team ID.

Returns:

  • (String, nil)

    The App store Team ID.



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 275

def get_team_id(platform, flavor_name)
  # 1. Get the merged configuration for the specific flavor
  config = get_merged_flavor_config(platform, flavor_name)

  # Return nil early if configuration is missing
  return nil unless config

  # 2. Extract the key based on the platform
  case platform
  when :ios
    # For iOS, we look for 'export_option_file'
    config.dig('team_id')
  else
    # Return nil for other platforms
    nil
  end
end

#get_version_code(platform, flavor_name) ⇒ String?

Returns The path to the version_code.

Returns:

  • (String, nil)

    The path to the version_code.



212
213
214
215
216
217
218
219
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 212

def get_version_code(platform, flavor_name)
  config = get_merged_flavor_config(platform, flavor_name)
  if config
    version_code_value = config.dig('version_code').to_s.strip
    return version_code_value.to_i(10).to_s
  end
  return nil
end

#get_version_name(platform, flavor_name) ⇒ String?

It combines ‘version_name’ and ‘version_name_suffix’ (if present) via direct concatenation. NOTE: It is assumed that the ‘version_name_suffix’ defined in the configuration

Returns:

  • (String, nil)

    The merged version name.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 190

def get_version_name(platform, flavor_name)
  config = get_merged_flavor_config(platform, flavor_name)
  return nil unless config

  base_version_name = config.dig('version_name')
  return nil unless base_version_name

  # Check for an optional suffix
  suffix = config.dig('version_name_suffix')

  # If a suffix is present, combine it directly with the base version name.
  # This relies on the suffix string itself containing the correct separator
  # to correctly form the final version name (e.g., "V" + "1.0.1" -> "V1.0.1").
  if suffix && !suffix.empty?
    return base_version_name + suffix
  else
    # Return the base version name if no suffix is present.
    return base_version_name
  end
end

#load_fileHash?

Loads and parses the raw YAML spec file using the instance file path.

Returns:

  • (Hash, nil)

    The parsed YAML data, or nil on error.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/fastlane/plugin/ann_flutter_flavor/helper/utils_spec_loader.rb', line 20

def load_file
  begin
    # Ensure the file path is resolved relative to the project root
    full_path = File.expand_path(@file_path, @root_folder)
    Fastlane::UI.verbose("Reading Annai spec from: #{full_path}")

    # Use safe_load to prevent arbitrary code execution from YAML files
    yaml_content = YAML.safe_load(File.read(full_path), aliases: true)

    # Ensure the content is a Hash before returning
    unless yaml_content.is_a?(Hash)
      raise "YAML content must be a top-level Hash."
    end
    return yaml_content
  rescue => e
    # Use @file_path for reporting the error path
    Fastlane::UI.error("Failed to load or parse annai spec YAML file at #{@file_path}: #{e.message}")
    return nil
  end
end