Class: FastlaneFlutterFlavor::YamlSpecLoader

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

Constant Summary collapse

STANDARD_BUILD_TYPES =
%w[debug release profile].freeze

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.



13
14
15
16
17
18
19
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 13

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 = load_file
end

Instance Method Details

#check_removed_firebase_fields(spec) ⇒ Object

Raises UI.user_error! if firebase_app_id or build_target are present in any iOS firebase block. Both fields were removed — they are derived automatically now. The core's model has no fields for them at all, so this reads the raw YAML directly (a narrow, deliberate exception to delegation — it is a migration guard, not spec parsing/merging/resolution, per ADR-008).



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 48

def check_removed_firebase_fields(spec)
  full_path = File.expand_path(@file_path, @root_folder)
  raw = YAML.safe_load(File.read(full_path), aliases: true) || {}
  ios_app = raw.dig('app', 'ios')
  return unless ios_app.is_a?(Hash)

  sections = {}
  sections['ios.default'] = ios_app.dig('default') || {}

  flavors = ios_app.dig('flavor') || {}
  flavors.each do |flavor_key, flavor_data|
    sections["ios.flavor.#{flavor_key}"] = flavor_data || {}
  end

  sections.each do |section_path, section_data|
    build_types = section_data.dig('build_types') || {}
    build_types.each do |bt_key, bt_data|
      next unless bt_data.is_a?(Hash)
      firebase_block = bt_data.dig('firebase') || {}
      next unless firebase_block.is_a?(Hash)

      if firebase_block.key?('firebase_app_id')
        Fastlane::UI.user_error!(
          "[#{section_path}/#{bt_key}] `firebase_app_id` is no longer supported in annspec.yaml. " \
          "Remove it — GOOGLE_APP_ID is now read from the generated firebase_options dart file."
        )
      end

      if firebase_block.key?('build_target')
        Fastlane::UI.user_error!(
          "[#{section_path}/#{bt_key}] `build_target` is no longer supported in annspec.yaml. " \
          "Remove it — the Xcode build target is derived automatically as " \
          "'#{bt_key.to_s.capitalize}-#{section_path.split('.').last}'."
        )
      end
    end
  end
end

#empty_defaults_for(platform) ⇒ Object



142
143
144
145
146
147
148
149
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 142

def empty_defaults_for(platform)
  case platform.to_s
  when 'android' then AnnFlavorCore::AndroidDefault.defaults
  when 'ios'      then AnnFlavorCore::IosDefault.defaults
  when 'web'      then AnnFlavorCore::WebDefault.defaults
  when 'windows'  then AnnFlavorCore::WindowsDefault.defaults
  end
end

#empty_flavor_for(platform) ⇒ Object



133
134
135
136
137
138
139
140
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 133

def empty_flavor_for(platform)
  case platform.to_s
  when 'android' then AnnFlavorCore::AndroidFlavor.defaults
  when 'ios'      then AnnFlavorCore::IosFlavor.defaults
  when 'web'      then AnnFlavorCore::WebFlavor.defaults
  when 'windows'  then AnnFlavorCore::WindowsFlavor.defaults
  end
end

#enabled?Boolean

Returns:

  • (Boolean)


87
88
89
90
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 87

def enabled?
  return false unless @spec
  @spec.enabled != false
end

#get_api_key_path(platform, flavor_name) ⇒ String?

Returns The API key path (platform-level credential for uploading to the store).

Returns:

  • (String, nil)

    The API key path (platform-level credential for uploading to the store).



224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 224

def get_api_key_path(platform, flavor_name)
  return nil unless @spec

  platform_data = platform_for(@spec, platform)
  return nil unless platform_data

  case platform.to_s
  when 'android'
    platform_data.default&.credentials&.google_play&.api_key
  when 'ios'
    platform_data.default&.credentials&.app_store&.api_key
  end
end

#get_apple_id(platform, flavor_name) ⇒ String?

Returns The Apple ID (numeric) for the flavor from App Store stores config.

Returns:

  • (String, nil)

    The Apple ID (numeric) for the flavor from App Store stores config.



216
217
218
219
220
221
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 216

def get_apple_id(platform, flavor_name)
  pair = platform_flavor_and_defaults(platform, flavor_name)
  return nil unless pair
  flavor, = pair
  flavor&.stores&.app_store&.apple_id
end

#get_auth_client_id(platform, flavor_name, build_config) ⇒ Object

For Google Sign-In (annai_auth)



323
324
325
326
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 323

def get_auth_client_id(platform, flavor_name, build_config)
  resolved = resolve(platform, flavor_name, build_config)
  resolved&.effective_auth&.client_id
end

#get_auth_reversed_client_id(platform, flavor_name, build_config) ⇒ Object



328
329
330
331
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 328

def get_auth_reversed_client_id(platform, flavor_name, build_config)
  resolved = resolve(platform, flavor_name, build_config)
  resolved&.effective_auth&.reversed_client_id
end

#get_cloudflare_config(platform, flavor_name) ⇒ Hash

Returns the resolved Cloudflare config for a web flavor as a Hash with keys: project_name, account_id, branch, mode — cascaded flavor → default → hardcoded fallback.

Returns:

  • (Hash)

    Always returns a Hash; values may be nil if not configured.



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 306

def get_cloudflare_config(platform, flavor_name)
  return {} unless @spec
  return {} unless platform.to_s == 'web'

  resolved = resolve(platform, flavor_name, 'release')
  cf = resolved&.effective_cloudflare
  return {} unless cf

  {
    'project_name' => cf.project_name,
    'account_id'   => cf.,
    'branch'       => cf.branch,
    'mode'         => cf.mode,
  }
end

#get_dart_defines_flags(platform, flavor_name, build_type = 'release') ⇒ Object

Merges common + compile-scoped dart_defines (compile wins on collision, per REQ-DDEF-00015) into individual --dart-define=KEY=VALUE flags for flutter build. Returns [] when there's nothing to emit. Returned as separate array elements (not a single joined string) so callers can pass them straight through to Fastlane::Actions.sh's argv-array form without each flag being treated as one combined shell argument.

REQ-DDEF-00040's default-only scoping decision applies here too: resolve folds flavor-level overrides in via the core's 4-level cascade, so a flavor with its own dart_defines still resolves correctly per call — unlike the (now-removed) generated-Fastfile-lane version, which could only represent default-level dart_defines since it was a single static lane shared across every flavor.



346
347
348
349
350
351
352
353
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 346

def get_dart_defines_flags(platform, flavor_name, build_type = 'release')
  resolved = resolve(platform, flavor_name, build_type)
  defines = resolved&.effective_dart_defines
  return [] unless defines

  merged = (defines.common || {}).merge(defines.compile || {})
  merged.map { |k, v| "--dart-define=#{k}=#{v}" }
end

#get_display_name(platform, flavor_name) ⇒ String?

Returns The merged app name.

Returns:

  • (String, nil)

    The merged app name.



183
184
185
186
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 183

def get_display_name(platform, flavor_name)
  resolved = resolve(platform, flavor_name, 'release')
  resolved&.effective_name
end

#get_effective_id(platform, flavor_name, build_type) ⇒ Object

For AdMob (annai_ads) Returns the full app ID for a specific build type, applying build_types.id_suffix.



287
288
289
290
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 287

def get_effective_id(platform, flavor_name, build_type)
  resolved = resolve(platform, flavor_name, build_type)
  resolved&.effective_id
end

#get_effective_name(platform, flavor_name, build_type) ⇒ Object

Returns the full display name for a specific build type, applying build_types.name_suffix.



293
294
295
296
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 293

def get_effective_name(platform, flavor_name, build_type)
  resolved = resolve(platform, flavor_name, build_type)
  resolved&.effective_name
end

#get_export_option_file(platform, flavor_name) ⇒ String?

Returns The export options plist path (inside default.credentials.app_store).

Returns:

  • (String, nil)

    The export options plist path (inside default.credentials.app_store).



239
240
241
242
243
244
245
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 239

def get_export_option_file(platform, flavor_name)
  return nil unless @spec
  return nil unless platform.to_s == 'ios'

  ios = @spec.app&.ios
  ios&.default&.credentials&.app_store&.export_options_plist
end

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

Returns The Firebase Project ID.

Returns:

  • (String, nil)

    The Firebase Project ID.



262
263
264
265
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 262

def get_firebase_project_id(platform, flavor_name, build_config)
  resolved = resolve(platform, flavor_name, build_config)
  resolved&.effective_firebase&.project_id
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).



167
168
169
170
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 167

def get_flavor_name(platform, flavor_name)
  pair = platform_flavor_and_defaults(platform, flavor_name)
  pair ? flavor_name.to_s : nil
end

#get_gms_ads_id(platform, flavor_name) ⇒ Object



298
299
300
301
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 298

def get_gms_ads_id(platform, flavor_name)
  resolved = resolve(platform, flavor_name, 'release')
  resolved&.effective_gms_ads_id
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.



202
203
204
205
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 202

def get_main_file(platform, flavor_name)
  resolved = resolve(platform, flavor_name, 'release')
  resolved&.effective_main_file
end

#get_package_id(platform, flavor_name, build_type = 'release') ⇒ String?

Returns The merged package ID (bundle ID or application ID) for the given build type. Was previously hardcoded to 'release' regardless of the caller's actual build — corrected via plan-030 Phase 6 / REQ-CDEL-00070 (PodspecBridge#bundle_id now threads its real @build_type through instead of relying on this default).

Returns:

  • (String, nil)

    The merged package ID (bundle ID or application ID) for the given build type. Was previously hardcoded to 'release' regardless of the caller's actual build — corrected via plan-030 Phase 6 / REQ-CDEL-00070 (PodspecBridge#bundle_id now threads its real @build_type through instead of relying on this default).



177
178
179
180
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 177

def get_package_id(platform, flavor_name, build_type = 'release')
  resolved = resolve(platform, flavor_name, build_type)
  resolved&.effective_id
end

#get_platform_flavors(platform) ⇒ Hash

Retrieves the hash of all defined flavors for a given platform.

Parameters:

  • platform (Symbol)

    The target platform (:ios or :android).

Returns:

  • (Hash)

    flavor name => core flavor struct. Callers only rely on .empty? and .keys, so the struct-valued Hash is a compatible superset of the previous raw-Hash-valued return.



158
159
160
161
162
163
164
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 158

def get_platform_flavors(platform)
  return {} unless @spec && enabled?
  platform_data = platform_for(@spec, platform)
  return {} if platform_data.nil?

  platform_data.flavors
end

#get_priority(platform, flavor_name) ⇒ String?

Returns The Google Play update priority for the flavor.

Returns:

  • (String, nil)

    The Google Play update priority for the flavor.



208
209
210
211
212
213
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 208

def get_priority(platform, flavor_name)
  pair = platform_flavor_and_defaults(platform, flavor_name)
  return nil unless pair
  flavor, = pair
  flavor&.stores&.google_play&.priority
end

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

Returns the resolved service_account path for the given platform/flavor/build_config, cascading through the core's full 4-level resolution:

flavor.build_types[bt].firebase.  (most specific)
flavor.firebase.
default.build_types[bt].firebase.
default.firebase.                 (least specific)

Corrected via ADR-008/REQ-CDEL-00060 — the pre-delegation implementation only checked the first and third levels, silently skipping the flavor-top-level and default-top-level fallbacks.

Returns:

  • (String, nil)


277
278
279
280
281
282
283
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 277

def (platform, flavor_name, build_config)
  resolved = resolve(platform, flavor_name, build_config)
  path = resolved&.effective_firebase&.
  return nil if path.nil? || path.empty?

  File.expand_path(path, @root_folder)
end

#get_team_id(platform, flavor_name) ⇒ String?

Returns The Apple Team ID for the flavor (flavor-level credentials override, falls back to default).

Returns:

  • (String, nil)

    The Apple Team ID for the flavor (flavor-level credentials override, falls back to default).



248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 248

def get_team_id(platform, flavor_name)
  return nil unless @spec
  return nil unless platform.to_s == 'ios'

  ios = @spec.app&.ios
  return nil unless ios

  flavor_team_id = ios.flavors[flavor_name.to_s]&.credentials&.signing&.team_id
  return flavor_team_id if flavor_team_id && !flavor_team_id.empty?

  ios.default&.credentials&.signing&.team_id
end

#get_version_code(platform, flavor_name) ⇒ String?

Returns The path to the version_code.

Returns:

  • (String, nil)

    The path to the version_code.



195
196
197
198
199
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 195

def get_version_code(platform, flavor_name)
  resolved = resolve(platform, flavor_name, 'release')
  return nil unless resolved
  resolved.effective_version_code.to_i.to_s
end

#get_version_name(platform, flavor_name) ⇒ String?

Returns The merged version name.

Returns:

  • (String, nil)

    The merged version name.



189
190
191
192
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 189

def get_version_name(platform, flavor_name)
  resolved = resolve(platform, flavor_name, 'release')
  resolved&.effective_version_name
end

#load_fileAnnFlavorCore::AnnSpec?

Loads and parses annspec.yaml via the shared core (ADR-008) — the single source of truth for parsing, cascade merging, and resolution.

Returns:



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 24

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}")

    spec = AnnFlavorCore::Parser.parse_file(full_path)

    check_removed_firebase_fields(spec)

    return spec
  rescue Fastlane::Shell::ShellError => e
    raise e
  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

#platform_flavor_and_defaults(platform, flavor_name) ⇒ Object

Returns the [flavor, defaults] structs for a platform, or nil if the platform/flavor cannot be resolved.



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 94

def platform_flavor_and_defaults(platform, flavor_name)
  return nil unless @spec && enabled?

  platform_data = platform_for(@spec, platform)
  return nil if platform_data.nil?

  flavor = platform_data.flavors[flavor_name.to_s]
  return nil if flavor.nil? && platform_data.default.nil?

  [flavor, platform_data.default]
end

#platform_for(spec, platform) ⇒ Object



106
107
108
109
110
111
112
113
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 106

def platform_for(spec, platform)
  case platform.to_s
  when 'android' then spec.app&.android
  when 'ios'      then spec.app&.ios
  when 'web'      then spec.app&.web
  when 'windows'  then spec.app&.windows
  end
end

#resolve(platform, flavor_name, build_type) ⇒ Object

Resolves a flavor against a build type via the core resolver. Returns nil when the platform/flavor cannot be resolved.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb', line 117

def resolve(platform, flavor_name, build_type)
  pair = platform_flavor_and_defaults(platform, flavor_name)
  return nil if pair.nil?

  flavor, defaults = pair
  flavor ||= empty_flavor_for(platform)
  defaults ||= empty_defaults_for(platform)

  case platform.to_s
  when 'android' then AnnFlavorCore::Resolver.resolve_android_flavor(flavor, defaults, build_type.to_s)
  when 'ios'      then AnnFlavorCore::Resolver.resolve_ios_flavor(flavor, defaults, build_type.to_s)
  when 'web'      then AnnFlavorCore::Resolver.resolve_web_flavor(flavor, defaults, build_type.to_s)
  when 'windows'  then AnnFlavorCore::Resolver.resolve_windows_flavor(flavor, defaults, build_type.to_s)
  end
end