ann-flavor-flutter 0.4.4 → 0.4.6
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/.gitignore +5 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +2 -0
- data/lib/ann_flavor_flutter/version.rb +1 -1
- data/lib/ann_flavor_flutter.rb +8 -0
- data/lib/fastlane/plugin/ann_flavor_flutter/helper/podspec_bridge.rb +1 -1
- data/lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb +161 -241
- data/lib/vendor/ann_flavor_core/lib/ann_flavor_core/ios_resolution.rb +37 -0
- data/lib/vendor/ann_flavor_core/lib/ann_flavor_core/model.rb +196 -0
- data/lib/vendor/ann_flavor_core/lib/ann_flavor_core/parser.rb +378 -0
- data/lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb +266 -0
- data/lib/vendor/ann_flavor_core/lib/ann_flavor_core/strict_mode_checker.rb +136 -0
- data/lib/vendor/ann_flavor_core/lib/ann_flavor_core/version.rb +3 -0
- data/lib/vendor/ann_flavor_core/lib/ann_flavor_core.rb +6 -0
- metadata +10 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2884061122825135d9f07bb18db889d23509649848aca1fca2c45b63b9b8c3bd
|
|
4
|
+
data.tar.gz: 5ed44c51bcf6c29195763aaa2615f4bb59b71a5b0908f26fdbc83677419273a4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8635f03da486e2aa4da6a7c9ed423d76260624ff5710a58c4a174951ecf2922bd874e4ff197edcb551db650ae7ef3fc76725d4f58935a076785f0c3f203e3a81
|
|
7
|
+
data.tar.gz: 0e5a13e1e4c03ddf24b31bb7b7c07fd330934213601b7a373408fdb051b8cfb2bb0bf298a9ad254df1ede039315b4871f6b2ff44a90818c7574a7361f2f197b6
|
data/.gitignore
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
/pkg/
|
|
2
2
|
Gemfile.lock
|
|
3
|
+
|
|
4
|
+
# Vendored ann_flavor_core source, generated transiently during publish (DEF-058).
|
|
5
|
+
# Never committed for real — CI/`make publish-fastlane` `git add -f` it briefly
|
|
6
|
+
# so `gem build`'s `git ls-files`-based file list includes it, then discard it.
|
|
7
|
+
/lib/vendor/
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.6
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Loading this gem (`require 'ann_flavor_flutter'` / `require 'fastlane/plugin/ann_flavor_flutter'`) failed with `cannot load such file -- ann_flavor_core` — the 0.4.5 core-delegation change added a hard `require 'ann_flavor_core'` in `utils_spec_loader.rb`, but `ann_flavor_core` is intentionally never published to RubyGems (ADR-008). The published gem now vendors `ann_flavor_core`'s source and resolves it automatically; local monorepo development is unaffected. ([#58](https://github.com/anntech-dev/ann-flavor-tooling/issues/58))
|
|
7
|
+
|
|
8
|
+
## 0.4.5
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `YamlSpecLoader` and `PodspecBridge` now delegate `annspec.yaml` parsing and resolution to the shared `ann-flavor-core-ruby` gem ([ADR-008](../../docs/02-decisions/008-core-delegation-mandate.md)).
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Fixed a `service_account` cascade bug where a flavor's or default's top-level Firebase `service_account` override was never consulted — only the two build-type-specific levels were checked.
|
|
15
|
+
- `PodspecBridge#bundle_id` no longer hardcodes the release build type's bundle ID regardless of the actual build type; debug/profile builds now resolve their own correct bundle ID.
|
|
16
|
+
|
|
3
17
|
## 0.4.4
|
|
4
18
|
|
|
5
19
|
### Fixed
|
data/Gemfile
CHANGED
data/lib/ann_flavor_flutter.rb
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# This file is loaded by require 'annai-flutter-flavor'
|
|
2
2
|
|
|
3
|
+
# The published gem vendors ann_flavor_core's source under lib/vendor/ (ADR-008:
|
|
4
|
+
# ann_flavor_core is never published to RubyGems, only path/git dependencies).
|
|
5
|
+
# In the monorepo this directory doesn't exist, and ann_flavor_core resolves via
|
|
6
|
+
# the real gem installed by the local Gemfile's `path:` dependency instead.
|
|
7
|
+
vendored_core_lib = File.expand_path('vendor/ann_flavor_core/lib', __dir__)
|
|
8
|
+
$LOAD_PATH.unshift(vendored_core_lib) if File.directory?(vendored_core_lib) &&
|
|
9
|
+
!$LOAD_PATH.include?(vendored_core_lib)
|
|
10
|
+
|
|
3
11
|
# Load the version (Good Practice)
|
|
4
12
|
require 'ann_flavor_flutter/version'
|
|
5
13
|
|
|
@@ -1,39 +1,37 @@
|
|
|
1
1
|
require 'yaml'
|
|
2
2
|
require 'fileutils'
|
|
3
3
|
require 'fastlane/action'
|
|
4
|
+
require 'ann_flavor_core'
|
|
4
5
|
|
|
5
6
|
module FastlaneFlutterFlavor
|
|
6
7
|
|
|
7
8
|
class YamlSpecLoader
|
|
8
9
|
|
|
10
|
+
STANDARD_BUILD_TYPES = %w[debug release profile].freeze
|
|
11
|
+
|
|
9
12
|
# Initialize the loader with the project root folder context and load the spec file.
|
|
10
13
|
def initialize(root_folder, file_path)
|
|
11
14
|
@root_folder = root_folder
|
|
12
15
|
@file_path = file_path # The file path, potentially overridden by ENV outside this class
|
|
13
16
|
|
|
14
17
|
# Load the file content immediately and store it
|
|
15
|
-
@
|
|
18
|
+
@spec = load_file
|
|
16
19
|
end
|
|
17
20
|
|
|
18
|
-
# Loads and parses the
|
|
19
|
-
#
|
|
21
|
+
# Loads and parses annspec.yaml via the shared core (ADR-008) — the single
|
|
22
|
+
# source of truth for parsing, cascade merging, and resolution.
|
|
23
|
+
# @return [AnnFlavorCore::AnnSpec, nil] The parsed spec, or nil on error.
|
|
20
24
|
def load_file
|
|
21
25
|
begin
|
|
22
26
|
# Ensure the file path is resolved relative to the project root
|
|
23
27
|
full_path = File.expand_path(@file_path, @root_folder)
|
|
24
28
|
Fastlane::UI.verbose("Reading Annai spec from: #{full_path}")
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
yaml_content = YAML.safe_load(File.read(full_path), aliases: true)
|
|
28
|
-
|
|
29
|
-
# Ensure the content is a Hash before returning
|
|
30
|
-
unless yaml_content.is_a?(Hash)
|
|
31
|
-
raise "YAML content must be a top-level Hash."
|
|
32
|
-
end
|
|
30
|
+
spec = AnnFlavorCore::Parser.parse_file(full_path)
|
|
33
31
|
|
|
34
|
-
check_removed_firebase_fields(
|
|
32
|
+
check_removed_firebase_fields(spec)
|
|
35
33
|
|
|
36
|
-
return
|
|
34
|
+
return spec
|
|
37
35
|
rescue Fastlane::Shell::ShellError => e
|
|
38
36
|
raise e
|
|
39
37
|
rescue => e
|
|
@@ -44,9 +42,13 @@ module FastlaneFlutterFlavor
|
|
|
44
42
|
end
|
|
45
43
|
|
|
46
44
|
# Raises UI.user_error! if firebase_app_id or build_target are present in any iOS firebase block.
|
|
47
|
-
# Both fields were removed — they are derived automatically now.
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
# Both fields were removed — they are derived automatically now. The core's model has no fields
|
|
46
|
+
# for them at all, so this reads the raw YAML directly (a narrow, deliberate exception to
|
|
47
|
+
# delegation — it is a migration guard, not spec parsing/merging/resolution, per ADR-008).
|
|
48
|
+
def check_removed_firebase_fields(spec)
|
|
49
|
+
full_path = File.expand_path(@file_path, @root_folder)
|
|
50
|
+
raw = YAML.safe_load(File.read(full_path), aliases: true) || {}
|
|
51
|
+
ios_app = raw.dig('app', 'ios')
|
|
50
52
|
return unless ios_app.is_a?(Hash)
|
|
51
53
|
|
|
52
54
|
sections = {}
|
|
@@ -82,263 +84,199 @@ module FastlaneFlutterFlavor
|
|
|
82
84
|
end
|
|
83
85
|
end
|
|
84
86
|
|
|
85
|
-
# Recursively merges defaults into a flavor's data.
|
|
86
|
-
# This function prioritizes 'current_data' (the flavor-specific value).
|
|
87
|
-
# If a key is present in 'default_data' but missing in 'current_data',
|
|
88
|
-
# the default value is used.
|
|
89
|
-
#
|
|
90
|
-
# @param current_data [Object] The flavor-specific data (Hash or scalar).
|
|
91
|
-
# @param default_data [Object] The default data to merge (Hash or scalar).
|
|
92
|
-
# @return [Object] The merged data.
|
|
93
|
-
def deep_merge_defaults(current_data, default_data)
|
|
94
|
-
|
|
95
|
-
# FIX: If current_data is a scalar, it means the flavor explicitly set this value.
|
|
96
|
-
# It overrides the default, so we return it immediately.
|
|
97
|
-
return current_data unless current_data.is_a?(Hash)
|
|
98
|
-
|
|
99
|
-
# Base case 2: If default_data is not a hash, there's nothing left to merge deeply.
|
|
100
|
-
return current_data unless default_data.is_a?(Hash)
|
|
101
|
-
|
|
102
|
-
# Recursive case: Both are hashes, perform the merge
|
|
103
|
-
default_data.each do |key, default_value|
|
|
104
|
-
current_value = current_data[key]
|
|
105
|
-
|
|
106
|
-
if current_data.key?(key)
|
|
107
|
-
# Key exists in flavor (current_data), recursively merge
|
|
108
|
-
current_data[key] = deep_merge_defaults(current_value, default_value)
|
|
109
|
-
else
|
|
110
|
-
# The key is missing in the current flavor data. Use the default value.
|
|
111
|
-
current_data[key] = default_value
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
return current_data
|
|
116
|
-
end
|
|
117
|
-
|
|
118
87
|
def enabled?
|
|
119
|
-
return false unless @
|
|
120
|
-
@
|
|
88
|
+
return false unless @spec
|
|
89
|
+
@spec.enabled != false
|
|
121
90
|
end
|
|
122
91
|
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
# @param flavor_name [String] The name of the flavor (e.g., 'dev', 'staging').
|
|
128
|
-
# @return [Hash, nil] The fully merged configuration for the flavor, or nil if missing.
|
|
129
|
-
def get_merged_flavor_config(platform, flavor_name)
|
|
130
|
-
return nil unless @spec_data && enabled?
|
|
131
|
-
|
|
132
|
-
platform_key = platform.to_s
|
|
133
|
-
flavor_key = flavor_name.to_s
|
|
134
|
-
|
|
135
|
-
# 1. Extract platform data from instance spec data
|
|
136
|
-
platform_data = @spec_data.dig('app', platform_key)
|
|
137
|
-
unless platform_data.is_a?(Hash)
|
|
138
|
-
Fastlane::UI.verbose("Annai spec missing 'app' or platform '#{platform_key}'.")
|
|
139
|
-
return nil
|
|
140
|
-
end
|
|
92
|
+
# Returns the [flavor, defaults] structs for a platform, or nil if the
|
|
93
|
+
# platform/flavor cannot be resolved.
|
|
94
|
+
def platform_flavor_and_defaults(platform, flavor_name)
|
|
95
|
+
return nil unless @spec && enabled?
|
|
141
96
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
default_data = platform_data.dig('default')
|
|
97
|
+
platform_data = platform_for(@spec, platform)
|
|
98
|
+
return nil if platform_data.nil?
|
|
145
99
|
|
|
146
|
-
|
|
147
|
-
if
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
100
|
+
flavor = platform_data.flavors[flavor_name.to_s]
|
|
101
|
+
return nil if flavor.nil? && platform_data.default.nil?
|
|
102
|
+
|
|
103
|
+
[flavor, platform_data.default]
|
|
104
|
+
end
|
|
151
105
|
|
|
152
|
-
|
|
153
|
-
|
|
106
|
+
def platform_for(spec, platform)
|
|
107
|
+
case platform.to_s
|
|
108
|
+
when 'android' then spec.app&.android
|
|
109
|
+
when 'ios' then spec.app&.ios
|
|
110
|
+
when 'web' then spec.app&.web
|
|
111
|
+
when 'windows' then spec.app&.windows
|
|
112
|
+
end
|
|
113
|
+
end
|
|
154
114
|
|
|
155
|
-
|
|
156
|
-
|
|
115
|
+
# Resolves a flavor against a build type via the core resolver. Returns nil
|
|
116
|
+
# when the platform/flavor cannot be resolved.
|
|
117
|
+
def resolve(platform, flavor_name, build_type)
|
|
118
|
+
pair = platform_flavor_and_defaults(platform, flavor_name)
|
|
119
|
+
return nil if pair.nil?
|
|
120
|
+
|
|
121
|
+
flavor, defaults = pair
|
|
122
|
+
flavor ||= empty_flavor_for(platform)
|
|
123
|
+
defaults ||= empty_defaults_for(platform)
|
|
124
|
+
|
|
125
|
+
case platform.to_s
|
|
126
|
+
when 'android' then AnnFlavorCore::Resolver.resolve_android_flavor(flavor, defaults, build_type.to_s)
|
|
127
|
+
when 'ios' then AnnFlavorCore::Resolver.resolve_ios_flavor(flavor, defaults, build_type.to_s)
|
|
128
|
+
when 'web' then AnnFlavorCore::Resolver.resolve_web_flavor(flavor, defaults, build_type.to_s)
|
|
129
|
+
when 'windows' then AnnFlavorCore::Resolver.resolve_windows_flavor(flavor, defaults, build_type.to_s)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
157
132
|
|
|
158
|
-
|
|
159
|
-
|
|
133
|
+
def empty_flavor_for(platform)
|
|
134
|
+
case platform.to_s
|
|
135
|
+
when 'android' then AnnFlavorCore::AndroidFlavor.defaults
|
|
136
|
+
when 'ios' then AnnFlavorCore::IosFlavor.defaults
|
|
137
|
+
when 'web' then AnnFlavorCore::WebFlavor.defaults
|
|
138
|
+
when 'windows' then AnnFlavorCore::WindowsFlavor.defaults
|
|
139
|
+
end
|
|
140
|
+
end
|
|
160
141
|
|
|
161
|
-
|
|
142
|
+
def empty_defaults_for(platform)
|
|
143
|
+
case platform.to_s
|
|
144
|
+
when 'android' then AnnFlavorCore::AndroidDefault.defaults
|
|
145
|
+
when 'ios' then AnnFlavorCore::IosDefault.defaults
|
|
146
|
+
when 'web' then AnnFlavorCore::WebDefault.defaults
|
|
147
|
+
when 'windows' then AnnFlavorCore::WindowsDefault.defaults
|
|
148
|
+
end
|
|
162
149
|
end
|
|
163
150
|
|
|
164
151
|
# --- GETTER WRAPPERS ---
|
|
165
152
|
|
|
166
153
|
# Retrieves the hash of all defined flavors for a given platform.
|
|
167
|
-
# Uses the instance spec data.
|
|
168
|
-
#
|
|
169
154
|
# @param platform [Symbol] The target platform (:ios or :android).
|
|
170
|
-
# @return [Hash]
|
|
155
|
+
# @return [Hash] flavor name => core flavor struct. Callers only rely on
|
|
156
|
+
# .empty? and .keys, so the struct-valued Hash is a compatible superset
|
|
157
|
+
# of the previous raw-Hash-valued return.
|
|
171
158
|
def get_platform_flavors(platform)
|
|
172
|
-
return {} unless @
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# Use dig to safely retrieve the flavors hash from @spec_data
|
|
176
|
-
flavors = @spec_data.dig('app', platform_key, 'flavor')
|
|
159
|
+
return {} unless @spec && enabled?
|
|
160
|
+
platform_data = platform_for(@spec, platform)
|
|
161
|
+
return {} if platform_data.nil?
|
|
177
162
|
|
|
178
|
-
|
|
179
|
-
return flavors.is_a?(Hash) ? flavors : {}
|
|
163
|
+
platform_data.flavors
|
|
180
164
|
end
|
|
181
165
|
|
|
182
166
|
# @return [String, nil] The flavor name (just for consistency with other getters).
|
|
183
167
|
def get_flavor_name(platform, flavor_name)
|
|
184
|
-
|
|
185
|
-
|
|
168
|
+
pair = platform_flavor_and_defaults(platform, flavor_name)
|
|
169
|
+
pair ? flavor_name.to_s : nil
|
|
186
170
|
end
|
|
187
171
|
|
|
188
|
-
# @return [String, nil] The merged package ID (bundle ID or application ID)
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return nil unless base_id
|
|
197
|
-
|
|
198
|
-
# Check for an optional suffix
|
|
199
|
-
suffix = config.dig('id_suffix')
|
|
200
|
-
|
|
201
|
-
# If a suffix is present, combine it directly with the base ID.
|
|
202
|
-
# This relies on the suffix string itself containing the correct separator (like ".")
|
|
203
|
-
# to correctly form the final package ID (e.g., "com.base" + ".dev" -> "com.base.dev").
|
|
204
|
-
if suffix && !suffix.empty?
|
|
205
|
-
return base_id + suffix
|
|
206
|
-
else
|
|
207
|
-
# Return the base ID if no suffix is present.
|
|
208
|
-
return base_id
|
|
209
|
-
end
|
|
172
|
+
# @return [String, nil] The merged package ID (bundle ID or application ID)
|
|
173
|
+
# for the given build type. Was previously hardcoded to 'release'
|
|
174
|
+
# regardless of the caller's actual build — corrected via plan-030
|
|
175
|
+
# Phase 6 / REQ-CDEL-00070 (PodspecBridge#bundle_id now threads its real
|
|
176
|
+
# @build_type through instead of relying on this default).
|
|
177
|
+
def get_package_id(platform, flavor_name, build_type = 'release')
|
|
178
|
+
resolved = resolve(platform, flavor_name, build_type)
|
|
179
|
+
resolved&.effective_id
|
|
210
180
|
end
|
|
211
181
|
|
|
212
182
|
# @return [String, nil] The merged app name.
|
|
213
|
-
# It combines 'name' and 'name_suffix' (if present) via direct concatenation.
|
|
214
|
-
# NOTE: It is assumed that the 'name_suffix' defined in the configuration
|
|
215
183
|
def get_display_name(platform, flavor_name)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
base_name = config.dig('name')
|
|
220
|
-
return nil unless base_name
|
|
221
|
-
|
|
222
|
-
# Check for an optional suffix
|
|
223
|
-
suffix = config.dig('name_suffix')
|
|
224
|
-
|
|
225
|
-
# If a suffix is present, combine it directly with the base Name.
|
|
226
|
-
# This relies on the suffix string itself containing the correct separator
|
|
227
|
-
# to correctly form the final Name (e.g., "Test" + " App" -> "Test App").
|
|
228
|
-
if suffix && !suffix.empty?
|
|
229
|
-
return base_name + suffix
|
|
230
|
-
else
|
|
231
|
-
# Return the base Name if no suffix is present.
|
|
232
|
-
return base_name
|
|
233
|
-
end
|
|
184
|
+
resolved = resolve(platform, flavor_name, 'release')
|
|
185
|
+
resolved&.effective_name
|
|
234
186
|
end
|
|
235
187
|
|
|
236
188
|
# @return [String, nil] The merged version name.
|
|
237
189
|
def get_version_name(platform, flavor_name)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
config.dig('version_name')
|
|
190
|
+
resolved = resolve(platform, flavor_name, 'release')
|
|
191
|
+
resolved&.effective_version_name
|
|
242
192
|
end
|
|
243
193
|
|
|
244
194
|
# @return [String, nil] The path to the version_code.
|
|
245
195
|
def get_version_code(platform, flavor_name)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return version_code_value.to_i(10).to_s
|
|
250
|
-
end
|
|
251
|
-
return nil
|
|
196
|
+
resolved = resolve(platform, flavor_name, 'release')
|
|
197
|
+
return nil unless resolved
|
|
198
|
+
resolved.effective_version_code.to_i.to_s
|
|
252
199
|
end
|
|
253
200
|
|
|
254
201
|
# @return [String, nil] The path to the main Dart file.
|
|
255
202
|
def get_main_file(platform, flavor_name)
|
|
256
|
-
|
|
257
|
-
|
|
203
|
+
resolved = resolve(platform, flavor_name, 'release')
|
|
204
|
+
resolved&.effective_main_file
|
|
258
205
|
end
|
|
259
206
|
|
|
260
207
|
# @return [String, nil] The Google Play update priority for the flavor.
|
|
261
208
|
def get_priority(platform, flavor_name)
|
|
262
|
-
|
|
263
|
-
|
|
209
|
+
pair = platform_flavor_and_defaults(platform, flavor_name)
|
|
210
|
+
return nil unless pair
|
|
211
|
+
flavor, = pair
|
|
212
|
+
flavor&.stores&.google_play&.priority
|
|
264
213
|
end
|
|
265
214
|
|
|
266
215
|
# @return [String, nil] The Apple ID (numeric) for the flavor from App Store stores config.
|
|
267
216
|
def get_apple_id(platform, flavor_name)
|
|
268
|
-
|
|
269
|
-
|
|
217
|
+
pair = platform_flavor_and_defaults(platform, flavor_name)
|
|
218
|
+
return nil unless pair
|
|
219
|
+
flavor, = pair
|
|
220
|
+
flavor&.stores&.app_store&.apple_id
|
|
270
221
|
end
|
|
271
222
|
|
|
272
223
|
# @return [String, nil] The API key path (platform-level credential for uploading to the store).
|
|
273
224
|
def get_api_key_path(platform, flavor_name)
|
|
274
|
-
return nil unless @
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
when
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
225
|
+
return nil unless @spec
|
|
226
|
+
|
|
227
|
+
platform_data = platform_for(@spec, platform)
|
|
228
|
+
return nil unless platform_data
|
|
229
|
+
|
|
230
|
+
case platform.to_s
|
|
231
|
+
when 'android'
|
|
232
|
+
platform_data.default&.credentials&.google_play&.api_key
|
|
233
|
+
when 'ios'
|
|
234
|
+
platform_data.default&.credentials&.app_store&.api_key
|
|
284
235
|
end
|
|
285
236
|
end
|
|
286
237
|
|
|
287
238
|
# @return [String, nil] The export options plist path (inside default.credentials.app_store).
|
|
288
239
|
def get_export_option_file(platform, flavor_name)
|
|
289
|
-
return nil unless @
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
@spec_data.dig('app', platform_key, 'default', 'credentials', 'app_store', 'export_options_plist')
|
|
295
|
-
else
|
|
296
|
-
nil
|
|
297
|
-
end
|
|
240
|
+
return nil unless @spec
|
|
241
|
+
return nil unless platform.to_s == 'ios'
|
|
242
|
+
|
|
243
|
+
ios = @spec.app&.ios
|
|
244
|
+
ios&.default&.credentials&.app_store&.export_options_plist
|
|
298
245
|
end
|
|
299
246
|
|
|
300
247
|
# @return [String, nil] The Apple Team ID for the flavor (flavor-level credentials override, falls back to default).
|
|
301
248
|
def get_team_id(platform, flavor_name)
|
|
302
|
-
return nil unless @
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
@spec_data.dig('app', platform_key, 'default', 'credentials', 'signing', 'team_id')
|
|
313
|
-
else
|
|
314
|
-
nil
|
|
315
|
-
end
|
|
249
|
+
return nil unless @spec
|
|
250
|
+
return nil unless platform.to_s == 'ios'
|
|
251
|
+
|
|
252
|
+
ios = @spec.app&.ios
|
|
253
|
+
return nil unless ios
|
|
254
|
+
|
|
255
|
+
flavor_team_id = ios.flavors[flavor_name.to_s]&.credentials&.signing&.team_id
|
|
256
|
+
return flavor_team_id if flavor_team_id && !flavor_team_id.empty?
|
|
257
|
+
|
|
258
|
+
ios.default&.credentials&.signing&.team_id
|
|
316
259
|
end
|
|
317
260
|
|
|
318
261
|
# @return [String, nil] The Firebase Project ID.
|
|
319
262
|
def get_firebase_project_id(platform, flavor_name, build_config)
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
config.dig('build_types', build_config, 'firebase', 'project_id') if config
|
|
263
|
+
resolved = resolve(platform, flavor_name, build_config)
|
|
264
|
+
resolved&.effective_firebase&.project_id
|
|
324
265
|
end
|
|
325
266
|
|
|
326
267
|
# Returns the resolved service_account path for the given platform/flavor/build_config,
|
|
327
|
-
# cascading
|
|
268
|
+
# cascading through the core's full 4-level resolution:
|
|
269
|
+
# flavor.build_types[bt].firebase.service_account (most specific)
|
|
270
|
+
# flavor.firebase.service_account
|
|
271
|
+
# default.build_types[bt].firebase.service_account
|
|
272
|
+
# default.firebase.service_account (least specific)
|
|
273
|
+
# Corrected via ADR-008/REQ-CDEL-00060 — the pre-delegation implementation only
|
|
274
|
+
# checked the first and third levels, silently skipping the flavor-top-level and
|
|
275
|
+
# default-top-level fallbacks.
|
|
328
276
|
# @return [String, nil]
|
|
329
277
|
def get_service_account(platform, flavor_name, build_config)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
platform_data = @spec_data.dig('app', platform)
|
|
333
|
-
return nil unless platform_data
|
|
334
|
-
|
|
335
|
-
flavor_data = platform_data.dig('flavor', flavor_name) || {}
|
|
336
|
-
default_data = platform_data['default'] || {}
|
|
337
|
-
|
|
338
|
-
flavor_bt_sa = flavor_data.dig('build_types', build_config, 'firebase', 'service_account')
|
|
339
|
-
default_bt_sa = default_data.dig('build_types', build_config, 'firebase', 'service_account')
|
|
340
|
-
|
|
341
|
-
path = flavor_bt_sa || default_bt_sa
|
|
278
|
+
resolved = resolve(platform, flavor_name, build_config)
|
|
279
|
+
path = resolved&.effective_firebase&.service_account
|
|
342
280
|
return nil if path.nil? || path.empty?
|
|
343
281
|
|
|
344
282
|
File.expand_path(path, @root_folder)
|
|
@@ -346,68 +284,50 @@ module FastlaneFlutterFlavor
|
|
|
346
284
|
|
|
347
285
|
# For AdMob (annai_ads)
|
|
348
286
|
# Returns the full app ID for a specific build type, applying build_types[build_type].id_suffix.
|
|
349
|
-
# Falls back to get_package_id if no build-type-specific suffix is defined.
|
|
350
287
|
def get_effective_id(platform, flavor_name, build_type)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
base_id = config.dig('id')
|
|
355
|
-
return nil unless base_id
|
|
356
|
-
|
|
357
|
-
id_suffix = config.dig('id_suffix') || ''
|
|
358
|
-
bt_id_suffix = config.dig('build_types', build_type.to_s, 'id_suffix') || ''
|
|
359
|
-
base_id + id_suffix + bt_id_suffix
|
|
288
|
+
resolved = resolve(platform, flavor_name, build_type)
|
|
289
|
+
resolved&.effective_id
|
|
360
290
|
end
|
|
361
291
|
|
|
362
292
|
# Returns the full display name for a specific build type, applying build_types[build_type].name_suffix.
|
|
363
293
|
def get_effective_name(platform, flavor_name, build_type)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
base_name = config.dig('name')
|
|
368
|
-
return nil unless base_name
|
|
369
|
-
|
|
370
|
-
name_suffix = config.dig('name_suffix') || ''
|
|
371
|
-
bt_name_suffix = config.dig('build_types', build_type.to_s, 'name_suffix') || ''
|
|
372
|
-
base_name + name_suffix + bt_name_suffix
|
|
294
|
+
resolved = resolve(platform, flavor_name, build_type)
|
|
295
|
+
resolved&.effective_name
|
|
373
296
|
end
|
|
374
297
|
|
|
375
298
|
def get_gms_ads_id(platform, flavor_name)
|
|
376
|
-
|
|
377
|
-
|
|
299
|
+
resolved = resolve(platform, flavor_name, 'release')
|
|
300
|
+
resolved&.effective_gms_ads_id
|
|
378
301
|
end
|
|
379
302
|
|
|
380
303
|
# Returns the resolved Cloudflare config for a web flavor as a Hash with keys:
|
|
381
304
|
# project_name, account_id, branch, mode — cascaded flavor → default → hardcoded fallback.
|
|
382
|
-
# project_name is flavor-only (never inherited from default).
|
|
383
305
|
# @return [Hash] Always returns a Hash; values may be nil if not configured.
|
|
384
306
|
def get_cloudflare_config(platform, flavor_name)
|
|
385
|
-
return {} unless @
|
|
386
|
-
|
|
387
|
-
platform_key = platform.to_s
|
|
388
|
-
platform_data = @spec_data.dig('app', platform_key)
|
|
389
|
-
return {} unless platform_data.is_a?(Hash)
|
|
307
|
+
return {} unless @spec
|
|
308
|
+
return {} unless platform.to_s == 'web'
|
|
390
309
|
|
|
391
|
-
|
|
392
|
-
|
|
310
|
+
resolved = resolve(platform, flavor_name, 'release')
|
|
311
|
+
cf = resolved&.effective_cloudflare
|
|
312
|
+
return {} unless cf
|
|
393
313
|
|
|
394
314
|
{
|
|
395
|
-
'project_name' =>
|
|
396
|
-
'account_id' =>
|
|
397
|
-
'branch' =>
|
|
398
|
-
'mode' =>
|
|
315
|
+
'project_name' => cf.project_name,
|
|
316
|
+
'account_id' => cf.account_id,
|
|
317
|
+
'branch' => cf.branch,
|
|
318
|
+
'mode' => cf.mode,
|
|
399
319
|
}
|
|
400
320
|
end
|
|
401
321
|
|
|
402
322
|
# For Google Sign-In (annai_auth)
|
|
403
323
|
def get_auth_client_id(platform, flavor_name, build_config)
|
|
404
|
-
|
|
405
|
-
|
|
324
|
+
resolved = resolve(platform, flavor_name, build_config)
|
|
325
|
+
resolved&.effective_auth&.client_id
|
|
406
326
|
end
|
|
407
327
|
|
|
408
328
|
def get_auth_reversed_client_id(platform, flavor_name, build_config)
|
|
409
|
-
|
|
410
|
-
|
|
329
|
+
resolved = resolve(platform, flavor_name, build_config)
|
|
330
|
+
resolved&.effective_auth&.reversed_client_id
|
|
411
331
|
end
|
|
412
332
|
end
|
|
413
|
-
end
|
|
333
|
+
end
|