ann-flavor-flutter 0.4.5 → 0.4.7
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 +10 -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/lanes_annai.rb +16 -0
- data/lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb +22 -0
- 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: fd7cce396ef86ea1e15205e7440d3755a79707eeaa55126a84bce553d0785add
|
|
4
|
+
data.tar.gz: 10d3037745026703bcd2ae17d93ea6467fb8fe3b6d0962f228b23f2d25e4e7cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3312b0e1231f50e7c99bb0ea43580245bd5bae4814dfd63d790e29fa41f9bc197f5492dcb3e0f9b120ac95e8cbbd4c27666874ed0abc18caf8b0183d0013a26
|
|
7
|
+
data.tar.gz: 174e5a6e6a016a6772ad6d764056b4576e09279f03130a0e91e36e323d4f0bb3f9b6dc4db825d3405a3feeefc034bfe1f42c8dbf7b1cdd5217f0a13bc1eb8308
|
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,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.7
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- `dart_defines` (`annspec.yaml`'s `android.default.dart_defines` / per-flavor overrides) was silently never applied to `flutter build` when compilation happened as part of `upload_to_play_store`, `upload_to_app_store`, or `upload_to_cloudflare` — only a manual `bundle exec fastlane android compile_build` invocation picked it up, since the flag was injected only into the generated Fastfile's standalone `compile_build` lane, which those upload/publish helpers never call. `compile_build` (the shared helper in `lanes_annai.rb`) now resolves `dart_defines` itself at runtime, so every caller gets it automatically — this also means a flavor-level `dart_defines` override is now honored, which the old generation-time injection could never represent (it could only see default-level values). ([#59](https://github.com/anntech-dev/ann-flavor-tooling/issues/59))
|
|
7
|
+
|
|
8
|
+
## 0.4.6
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- 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))
|
|
12
|
+
|
|
3
13
|
## 0.4.5
|
|
4
14
|
|
|
5
15
|
### 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
|
|
|
@@ -113,6 +113,22 @@ module FastlaneFlutterFlavor
|
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
# dart_defines (REQ-DDEF-00040): resolved here — not only in the
|
|
117
|
+
# standalone Fastfile `compile_build` lane — so every caller that
|
|
118
|
+
# compiles via this helper gets it, including upload_to_play_store,
|
|
119
|
+
# upload_to_app_store, and upload_to_cloudflare, which call this
|
|
120
|
+
# method directly and previously never applied dart_defines at all
|
|
121
|
+
# (DEF-059, #59). A caller-supplied additional_parameter is preserved
|
|
122
|
+
# and the resolved flags are appended alongside it as separate argv
|
|
123
|
+
# elements (Fastlane::Actions.sh below is called with a splatted
|
|
124
|
+
# array, so each flag must be its own element, not one combined
|
|
125
|
+
# string). additional_parameter may already be an Array here (the web
|
|
126
|
+
# branch above turns it into one) or a String.
|
|
127
|
+
dart_defines_flags = @specLoader.get_dart_defines_flags(@platform, flavor, build_config)
|
|
128
|
+
unless dart_defines_flags.empty?
|
|
129
|
+
additional_parameter = Array(additional_parameter).flatten.reject { |p| p.to_s.empty? } + dart_defines_flags
|
|
130
|
+
end
|
|
131
|
+
|
|
116
132
|
# Base command parts
|
|
117
133
|
command_parts = [
|
|
118
134
|
"flutter", "build",
|
|
@@ -329,5 +329,27 @@ module FastlaneFlutterFlavor
|
|
|
329
329
|
resolved = resolve(platform, flavor_name, build_config)
|
|
330
330
|
resolved&.effective_auth&.reversed_client_id
|
|
331
331
|
end
|
|
332
|
+
|
|
333
|
+
# Merges common + compile-scoped dart_defines (compile wins on collision,
|
|
334
|
+
# per REQ-DDEF-00015) into individual --dart-define=KEY=VALUE flags for
|
|
335
|
+
# `flutter build`. Returns [] when there's nothing to emit. Returned as
|
|
336
|
+
# separate array elements (not a single joined string) so callers can pass
|
|
337
|
+
# them straight through to Fastlane::Actions.sh's argv-array form without
|
|
338
|
+
# each flag being treated as one combined shell argument.
|
|
339
|
+
#
|
|
340
|
+
# REQ-DDEF-00040's default-only scoping decision applies here too:
|
|
341
|
+
# `resolve` folds flavor-level overrides in via the core's 4-level cascade,
|
|
342
|
+
# so a flavor with its own dart_defines still resolves correctly per call —
|
|
343
|
+
# unlike the (now-removed) generated-Fastfile-lane version, which could
|
|
344
|
+
# only represent default-level dart_defines since it was a single static
|
|
345
|
+
# lane shared across every flavor.
|
|
346
|
+
def get_dart_defines_flags(platform, flavor_name, build_type = 'release')
|
|
347
|
+
resolved = resolve(platform, flavor_name, build_type)
|
|
348
|
+
defines = resolved&.effective_dart_defines
|
|
349
|
+
return [] unless defines
|
|
350
|
+
|
|
351
|
+
merged = (defines.common || {}).merge(defines.compile || {})
|
|
352
|
+
merged.map { |k, v| "--dart-define=#{k}=#{v}" }
|
|
353
|
+
end
|
|
332
354
|
end
|
|
333
355
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require_relative 'resolver'
|
|
2
|
+
|
|
3
|
+
module AnnFlavorCore
|
|
4
|
+
# Shared iOS bundle-ID / Firebase-path resolution — used by both
|
|
5
|
+
# ann-flavor-cocoapods and ann-flavor-fastlane so the two plugins compute
|
|
6
|
+
# identical effective values instead of independently re-deriving them
|
|
7
|
+
# (plan-030 Phase 6 / REQ-CDEL-00070).
|
|
8
|
+
module IosResolution
|
|
9
|
+
# Effective bundle ID for a flavor at a specific build type. Always applies
|
|
10
|
+
# the full cascade (default → flavor → build_type suffix) — there is no
|
|
11
|
+
# build-type-less variant; callers must pass the actual build type they are
|
|
12
|
+
# assembling for.
|
|
13
|
+
#
|
|
14
|
+
# An unknown flavor key falls back to the unsuffixed default id (matches
|
|
15
|
+
# pre-consolidation behavior — never raises for a missing flavor).
|
|
16
|
+
def self.bundle_id(ios_platform, flavor_key, build_type)
|
|
17
|
+
flavor = ios_platform.flavors[flavor_key.to_s] || AnnFlavorCore::IosFlavor.defaults
|
|
18
|
+
Resolver.resolve_ios_flavor(flavor, ios_platform.default, build_type.to_s).effective_id
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Effective Firebase config file path for a flavor at a specific build
|
|
22
|
+
# type. Full 4-level cascade via the core resolver (config_file,
|
|
23
|
+
# project_id, service_account, target). When only project_id is set (no
|
|
24
|
+
# config_file), synthesizes the stable generated-plist path convention:
|
|
25
|
+
# #{generated_dir}/GoogleService-Info-#{flavor_key}-#{build_type}.plist
|
|
26
|
+
#
|
|
27
|
+
# Returns nil when neither config_file nor project_id is set.
|
|
28
|
+
def self.firebase_config_path(ios_platform, flavor_key, build_type, generated_dir: 'lib/generated/firebase')
|
|
29
|
+
flavor = ios_platform.flavors[flavor_key.to_s] || AnnFlavorCore::IosFlavor.defaults
|
|
30
|
+
resolved = Resolver.resolve_ios_flavor(flavor, ios_platform.default, build_type.to_s)
|
|
31
|
+
fb = resolved.effective_firebase
|
|
32
|
+
return nil if fb.nil?
|
|
33
|
+
|
|
34
|
+
fb.config_file || (fb.project_id && "#{generated_dir}/GoogleService-Info-#{flavor_key}-#{build_type}.plist")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
module AnnFlavorCore
|
|
2
|
+
# ── dart_defines ────────────────────────────────────────────────────────────
|
|
3
|
+
#
|
|
4
|
+
# compile/run/deploy are reserved action-scope sub-keys (REQ-DDEF-00015).
|
|
5
|
+
# +common+ holds keys declared directly under dart_defines — they apply to every
|
|
6
|
+
# action. Each action Hash is resolved as an addition on top of +common+, never a
|
|
7
|
+
# replacement.
|
|
8
|
+
DartDefines = Struct.new(:common, :compile, :run, :deploy, keyword_init: true) do
|
|
9
|
+
def self.defaults = new(common: {}, compile: {}, run: {}, deploy: {})
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# ── Top-level ───────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
AnnSpec = Struct.new(:app, :enabled, :debug_config, :tooling, keyword_init: true) do
|
|
15
|
+
def self.defaults = new(app: AnnApp.new, enabled: true, debug_config: DebugConfig.new)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
AnnApp = Struct.new(:android, :ios, :web, :windows, :general, :integrations, keyword_init: true)
|
|
19
|
+
|
|
20
|
+
# ── Tooling (REQ-TOOL-00010 — top-level, non-cascading) ──────────────────────
|
|
21
|
+
|
|
22
|
+
ToolingConfig = Struct.new(:gradle_plugin, :cocoapods_plugin, :fastlane_plugin, keyword_init: true)
|
|
23
|
+
|
|
24
|
+
# ── Integrations ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
AnnIntegrations = Struct.new(:fastlane, :melos, :firebase, keyword_init: true) do
|
|
27
|
+
def self.defaults = new(fastlane: false, melos: false, firebase: false)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# ── Android ─────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
AndroidPlatform = Struct.new(:default, :flavors, keyword_init: true) do
|
|
33
|
+
def self.defaults = new(default: AndroidDefault.new, flavors: {})
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
AndroidDefault = Struct.new(
|
|
37
|
+
:id, :name, :version_name, :version_code, :main_file,
|
|
38
|
+
:admob, :icon, :sdk, :credentials, :firebase, :build_types, :custom, :dart_defines,
|
|
39
|
+
keyword_init: true
|
|
40
|
+
) do
|
|
41
|
+
def self.defaults = new(build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
AndroidFlavor = Struct.new(
|
|
45
|
+
:id, :id_suffix, :name, :name_suffix, :version_name, :version_code,
|
|
46
|
+
:main_file, :admob, :icon, :firebase, :stores, :credentials, :build_types, :custom, :dart_defines,
|
|
47
|
+
keyword_init: true
|
|
48
|
+
) do
|
|
49
|
+
def self.defaults = new(id_suffix: '', name_suffix: '', build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
AndroidSdk = Struct.new(:min_sdk, :compile_sdk, :target_sdk, keyword_init: true)
|
|
53
|
+
|
|
54
|
+
AndroidCredentials = Struct.new(:signing, :google_play, :samsung_galaxy, :amazon, keyword_init: true)
|
|
55
|
+
AndroidSigning = Struct.new(:key_file, keyword_init: true)
|
|
56
|
+
GooglePlayCredentials = Struct.new(:api_key, keyword_init: true)
|
|
57
|
+
SamsungGalaxyCredentials = Struct.new(:seller_id, :api_key, keyword_init: true)
|
|
58
|
+
AmazonCredentials = Struct.new(:client_id, :client_secret, keyword_init: true)
|
|
59
|
+
|
|
60
|
+
# ── iOS ─────────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
IosPlatform = Struct.new(:default, :flavors, keyword_init: true) do
|
|
63
|
+
def self.defaults = new(default: IosDefault.new, flavors: {})
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
IosDefault = Struct.new(
|
|
67
|
+
:id, :name, :version_name, :version_code, :main_file,
|
|
68
|
+
:admob, :icon, :credentials, :firebase, :build_types, :custom, :dart_defines,
|
|
69
|
+
keyword_init: true
|
|
70
|
+
) do
|
|
71
|
+
def self.defaults = new(build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
IosFlavor = Struct.new(
|
|
75
|
+
:id, :id_suffix, :name, :name_suffix, :version_name, :version_code,
|
|
76
|
+
:main_file, :admob, :icon, :firebase, :stores, :credentials, :build_types, :custom, :dart_defines,
|
|
77
|
+
keyword_init: true
|
|
78
|
+
) do
|
|
79
|
+
def self.defaults = new(id_suffix: '', name_suffix: '', build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
IosCredentials = Struct.new(:signing, :app_store, keyword_init: true)
|
|
83
|
+
IosSigning = Struct.new(:team_id, keyword_init: true)
|
|
84
|
+
AppStoreCredentials = Struct.new(
|
|
85
|
+
:api_key, :export_options_plist, :export_options_team_id,
|
|
86
|
+
:export_options_signing_certificate,
|
|
87
|
+
keyword_init: true
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# ── Web ─────────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
WebPlatform = Struct.new(:default, :flavors, keyword_init: true) do
|
|
93
|
+
def self.defaults = new(default: WebDefault.new, flavors: {})
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
CloudflareConfig = Struct.new(:project_name, :account_id, :branch, :mode, keyword_init: true)
|
|
97
|
+
|
|
98
|
+
WebDefault = Struct.new(
|
|
99
|
+
:id, :name, :version_name, :version_code, :main_file, :icon, :cloudflare, :firebase, :build_types, :custom, :dart_defines,
|
|
100
|
+
keyword_init: true
|
|
101
|
+
) do
|
|
102
|
+
def self.defaults = new(build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
WebFlavor = Struct.new(
|
|
106
|
+
:id, :id_suffix, :name, :name_suffix, :version_name, :version_code,
|
|
107
|
+
:main_file, :icon, :cloudflare, :firebase, :build_types, :custom, :dart_defines,
|
|
108
|
+
keyword_init: true
|
|
109
|
+
) do
|
|
110
|
+
def self.defaults = new(id_suffix: '', name_suffix: '', build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# ── Windows ─────────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
WindowsPlatform = Struct.new(:default, :flavors, keyword_init: true) do
|
|
116
|
+
def self.defaults = new(default: WindowsDefault.new, flavors: {})
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
WindowsDefault = Struct.new(
|
|
120
|
+
:id, :name, :version_name, :version_code, :main_file, :firebase, :build_types, :custom, :dart_defines,
|
|
121
|
+
keyword_init: true
|
|
122
|
+
) do
|
|
123
|
+
def self.defaults = new(build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
WindowsFlavor = Struct.new(
|
|
127
|
+
:id, :id_suffix, :name, :name_suffix, :version_name, :version_code,
|
|
128
|
+
:main_file, :firebase, :build_types, :custom, :dart_defines,
|
|
129
|
+
keyword_init: true
|
|
130
|
+
) do
|
|
131
|
+
def self.defaults = new(id_suffix: '', name_suffix: '', build_types: {}, custom: {}, dart_defines: DartDefines.defaults)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# ── Shared types ─────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
AdmobConfig = Struct.new(:gms_ads_id, keyword_init: true)
|
|
137
|
+
|
|
138
|
+
BuildTypeConfig = Struct.new(
|
|
139
|
+
:id_suffix, :name_suffix,
|
|
140
|
+
:firebase, :auth, :admob,
|
|
141
|
+
:minify_enabled, :shrink_resources, :lint_check_release_builds,
|
|
142
|
+
:ndk_version, :ndk_debug_symbol_level, :ndk_abi_filters, :custom, :dart_defines,
|
|
143
|
+
keyword_init: true
|
|
144
|
+
) do
|
|
145
|
+
def self.defaults = new(id_suffix: '', name_suffix: '', ndk_abi_filters: [], custom: {}, dart_defines: DartDefines.defaults)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
FirebaseConfig = Struct.new(:project_id, :config_file, :service_account, :target, keyword_init: true)
|
|
149
|
+
|
|
150
|
+
AuthConfig = Struct.new(:client_id, :reversed_client_id, :others, keyword_init: true) do
|
|
151
|
+
def self.defaults = new(others: {})
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
FlavorStores = Struct.new(:google_play, :samsung_galaxy, :amazon, :app_store, keyword_init: true)
|
|
155
|
+
GooglePlayFlavorStore = Struct.new(:priority, keyword_init: true)
|
|
156
|
+
SamsungGalaxyFlavorStore = Struct.new(:app_id, keyword_init: true)
|
|
157
|
+
AmazonFlavorStore = Struct.new(:app_id, keyword_init: true)
|
|
158
|
+
AppStoreFlavorStore = Struct.new(:apple_id, :team_id, keyword_init: true)
|
|
159
|
+
|
|
160
|
+
class GeneralConfig
|
|
161
|
+
def self.defaults = new
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
DebugConfig = Struct.new(
|
|
165
|
+
:print_debug, :print_build_and_flavor_info, :print_sdk_versions, :print_release_build_type_info,
|
|
166
|
+
keyword_init: true
|
|
167
|
+
) do
|
|
168
|
+
def self.defaults
|
|
169
|
+
new(
|
|
170
|
+
print_debug: false,
|
|
171
|
+
print_build_and_flavor_info: true,
|
|
172
|
+
print_sdk_versions: true,
|
|
173
|
+
print_release_build_type_info: false
|
|
174
|
+
)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# ── Resolved output types ────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
ResolvedSpec = Struct.new(:android, :ios, :web, :windows, keyword_init: true)
|
|
181
|
+
ResolvedFlavor = Struct.new(:flavor_name, :by_build_type, keyword_init: true)
|
|
182
|
+
|
|
183
|
+
ResolvedBuildOutput = Struct.new(
|
|
184
|
+
:flavor_name, :build_type,
|
|
185
|
+
:effective_id, :effective_name,
|
|
186
|
+
:effective_version_name, :effective_version_code,
|
|
187
|
+
:effective_main_file,
|
|
188
|
+
:effective_firebase, :effective_auth,
|
|
189
|
+
:effective_gms_ads_id,
|
|
190
|
+
:effective_icon,
|
|
191
|
+
:effective_cloudflare,
|
|
192
|
+
:effective_custom,
|
|
193
|
+
:effective_dart_defines,
|
|
194
|
+
keyword_init: true
|
|
195
|
+
)
|
|
196
|
+
end
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require_relative 'model'
|
|
3
|
+
|
|
4
|
+
module AnnFlavorCore
|
|
5
|
+
#
|
|
6
|
+
# Single source of truth for parsing annspec.yaml.
|
|
7
|
+
#
|
|
8
|
+
# Two entry points:
|
|
9
|
+
# AnnFlavorCore::Parser.parse(yaml_string) — from raw YAML text
|
|
10
|
+
# AnnFlavorCore::Parser.parse_file(path) — from a file path
|
|
11
|
+
# AnnFlavorCore::Parser.from_hash(hash) — from a pre-parsed Hash
|
|
12
|
+
# (e.g. in-memory UI data)
|
|
13
|
+
#
|
|
14
|
+
# After parsing, pass the AnnSpec to AnnFlavorCore::Resolver.resolve_all
|
|
15
|
+
# to obtain all resolved (effective) values per flavor × build-type.
|
|
16
|
+
#
|
|
17
|
+
module Parser
|
|
18
|
+
def self.parse(yaml_string)
|
|
19
|
+
raw = YAML.safe_load(yaml_string, aliases: true) || {}
|
|
20
|
+
from_hash(raw)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.parse_file(path)
|
|
24
|
+
parse(File.read(path))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.from_hash(raw)
|
|
28
|
+
app_hash = h(raw, 'app')
|
|
29
|
+
tooling_hash = h(raw, 'tooling')
|
|
30
|
+
AnnSpec.new(
|
|
31
|
+
app: parse_ann_app(app_hash),
|
|
32
|
+
enabled: raw.fetch('enabled', true),
|
|
33
|
+
debug_config: parse_debug_config(h(raw, 'debug')),
|
|
34
|
+
tooling: (parse_tooling(tooling_hash) unless tooling_hash.empty?),
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.parse_custom_config(m)
|
|
39
|
+
return {} unless m.is_a?(Hash)
|
|
40
|
+
m.transform_values do |group_val|
|
|
41
|
+
next {} unless group_val.is_a?(Hash)
|
|
42
|
+
group_val.transform_values { |v| normalize_custom_value(v) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.normalize_custom_value(v)
|
|
47
|
+
return v.map { |e| e.nil? ? '' : e.to_s } if v.is_a?(Array)
|
|
48
|
+
v
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.parse_tooling(m)
|
|
52
|
+
ToolingConfig.new(
|
|
53
|
+
gradle_plugin: m['gradle_plugin'],
|
|
54
|
+
cocoapods_plugin: m['cocoapods_plugin'],
|
|
55
|
+
fastlane_plugin: m['fastlane_plugin'],
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# ── AnnApp ────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
def self.parse_ann_app(m)
|
|
62
|
+
AnnApp.new(
|
|
63
|
+
android: (parse_android(h(m, 'android')) unless h(m, 'android').empty?),
|
|
64
|
+
ios: (parse_ios(h(m, 'ios')) unless h(m, 'ios').empty?),
|
|
65
|
+
web: (parse_web(h(m, 'web')) unless h(m, 'web').empty?),
|
|
66
|
+
windows: (parse_windows(h(m, 'windows')) unless h(m, 'windows').empty?),
|
|
67
|
+
general: (parse_general(h(m, 'general')) unless h(m, 'general').empty?),
|
|
68
|
+
integrations: (parse_integrations(h(m, 'integrations')) unless h(m, 'integrations').empty?),
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.parse_integrations(m)
|
|
73
|
+
AnnIntegrations.new(
|
|
74
|
+
fastlane: m.fetch('fastlane', false),
|
|
75
|
+
melos: m.fetch('melos', false),
|
|
76
|
+
firebase: m.fetch('firebase', false),
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# ── Android ───────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
def self.parse_android(m)
|
|
83
|
+
AndroidPlatform.new(
|
|
84
|
+
default: parse_android_default(h(m, 'default')),
|
|
85
|
+
flavors: parse_flavors(h(m, 'flavor')) { |v| parse_android_flavor(v) },
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.parse_android_default(m)
|
|
90
|
+
AndroidDefault.new(
|
|
91
|
+
id: m['id'],
|
|
92
|
+
name: m['name'],
|
|
93
|
+
version_name: m['version_name']&.to_s,
|
|
94
|
+
version_code: m['version_code']&.to_i,
|
|
95
|
+
main_file: m['main_file'],
|
|
96
|
+
admob: (AdmobConfig.new(gms_ads_id: h(m, 'admob')['gms_ads_id'] || m['gms_ads_id']) unless (h(m, 'admob')['gms_ads_id'] || m['gms_ads_id']).nil?),
|
|
97
|
+
icon: m['icon'],
|
|
98
|
+
sdk: (parse_android_sdk(h(m, 'sdk')) unless h(m, 'sdk').empty?),
|
|
99
|
+
credentials: (parse_android_credentials(h(m, 'credentials')) unless h(m, 'credentials').empty?),
|
|
100
|
+
firebase: (parse_firebase_config(h(m, 'firebase')) unless h(m, 'firebase').empty?),
|
|
101
|
+
build_types: parse_build_type_configs(h(m, 'build_types')),
|
|
102
|
+
custom: parse_custom_config(h(m, 'custom')),
|
|
103
|
+
dart_defines: parse_dart_defines(h(m, 'dart_defines')),
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.parse_android_flavor(m)
|
|
108
|
+
AndroidFlavor.new(
|
|
109
|
+
id: m['id'],
|
|
110
|
+
id_suffix: m.fetch('id_suffix', '').to_s,
|
|
111
|
+
name: m['name'],
|
|
112
|
+
name_suffix: m.fetch('name_suffix', '').to_s,
|
|
113
|
+
version_name: m['version_name']&.to_s,
|
|
114
|
+
version_code: m['version_code']&.to_i,
|
|
115
|
+
main_file: m['main_file'],
|
|
116
|
+
admob: (AdmobConfig.new(gms_ads_id: h(m, 'admob')['gms_ads_id'] || m['gms_ads_id']) unless (h(m, 'admob')['gms_ads_id'] || m['gms_ads_id']).nil?),
|
|
117
|
+
icon: m['icon'],
|
|
118
|
+
firebase: (parse_firebase_config(h(m, 'firebase')) unless h(m, 'firebase').empty?),
|
|
119
|
+
stores: (parse_flavor_stores(h(m, 'stores')) unless h(m, 'stores').empty?),
|
|
120
|
+
credentials: (parse_android_credentials(h(m, 'credentials')) unless h(m, 'credentials').empty?),
|
|
121
|
+
build_types: parse_build_type_configs(h(m, 'build_types')),
|
|
122
|
+
custom: parse_custom_config(h(m, 'custom')),
|
|
123
|
+
dart_defines: parse_dart_defines(h(m, 'dart_defines')),
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.parse_android_sdk(m)
|
|
128
|
+
AndroidSdk.new(min_sdk: m['minSdk'], compile_sdk: m['compileSdk'], target_sdk: m['targetSdk'])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.parse_android_credentials(m)
|
|
132
|
+
AndroidCredentials.new(
|
|
133
|
+
signing: (AndroidSigning.new(key_file: h(m, 'signing')['key_file']) unless h(m, 'signing').empty?),
|
|
134
|
+
google_play: (GooglePlayCredentials.new(api_key: h(m, 'google_play')['api_key']) unless h(m, 'google_play').empty?),
|
|
135
|
+
samsung_galaxy: (SamsungGalaxyCredentials.new(seller_id: h(m, 'samsung_galaxy')['seller_id'],
|
|
136
|
+
api_key: h(m, 'samsung_galaxy')['api_key']) unless h(m, 'samsung_galaxy').empty?),
|
|
137
|
+
amazon: (AmazonCredentials.new(client_id: h(m, 'amazon')['client_id'],
|
|
138
|
+
client_secret: h(m, 'amazon')['client_secret']) unless h(m, 'amazon').empty?),
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# ── iOS ───────────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
def self.parse_ios(m)
|
|
145
|
+
IosPlatform.new(
|
|
146
|
+
default: parse_ios_default(h(m, 'default')),
|
|
147
|
+
flavors: parse_flavors(h(m, 'flavor')) { |v| parse_ios_flavor(v) },
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def self.parse_ios_default(m)
|
|
152
|
+
IosDefault.new(
|
|
153
|
+
id: m['id'],
|
|
154
|
+
name: m['name'],
|
|
155
|
+
version_name: m['version_name']&.to_s,
|
|
156
|
+
version_code: m['version_code']&.to_i,
|
|
157
|
+
main_file: m['main_file'],
|
|
158
|
+
admob: (AdmobConfig.new(gms_ads_id: h(m, 'admob')['gms_ads_id'] || m['gms_ads_id']) unless (h(m, 'admob')['gms_ads_id'] || m['gms_ads_id']).nil?),
|
|
159
|
+
icon: m['icon'],
|
|
160
|
+
credentials: (parse_ios_credentials(h(m, 'credentials')) unless h(m, 'credentials').empty?),
|
|
161
|
+
firebase: (parse_firebase_config(h(m, 'firebase')) unless h(m, 'firebase').empty?),
|
|
162
|
+
build_types: parse_build_type_configs(h(m, 'build_types')),
|
|
163
|
+
custom: parse_custom_config(h(m, 'custom')),
|
|
164
|
+
dart_defines: parse_dart_defines(h(m, 'dart_defines')),
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def self.parse_ios_flavor(m)
|
|
169
|
+
IosFlavor.new(
|
|
170
|
+
id: m['id'],
|
|
171
|
+
id_suffix: m.fetch('id_suffix', '').to_s,
|
|
172
|
+
name: m['name'],
|
|
173
|
+
name_suffix: m.fetch('name_suffix', '').to_s,
|
|
174
|
+
version_name: m['version_name']&.to_s,
|
|
175
|
+
version_code: m['version_code']&.to_i,
|
|
176
|
+
main_file: m['main_file'],
|
|
177
|
+
admob: (AdmobConfig.new(gms_ads_id: h(m, 'admob')['gms_ads_id'] || m['gms_ads_id']) unless (h(m, 'admob')['gms_ads_id'] || m['gms_ads_id']).nil?),
|
|
178
|
+
icon: m['icon'],
|
|
179
|
+
firebase: (parse_firebase_config(h(m, 'firebase')) unless h(m, 'firebase').empty?),
|
|
180
|
+
stores: (parse_flavor_stores(h(m, 'stores')) unless h(m, 'stores').empty?),
|
|
181
|
+
credentials: (parse_ios_credentials(h(m, 'credentials')) unless h(m, 'credentials').empty?),
|
|
182
|
+
build_types: parse_build_type_configs(h(m, 'build_types')),
|
|
183
|
+
custom: parse_custom_config(h(m, 'custom')),
|
|
184
|
+
dart_defines: parse_dart_defines(h(m, 'dart_defines')),
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def self.parse_ios_credentials(m)
|
|
189
|
+
IosCredentials.new(
|
|
190
|
+
signing: (IosSigning.new(team_id: h(m, 'signing')['team_id']) unless h(m, 'signing').empty?),
|
|
191
|
+
app_store: (AppStoreCredentials.new(
|
|
192
|
+
api_key: h(m, 'app_store')['api_key'],
|
|
193
|
+
export_options_plist: h(m, 'app_store')['export_options_plist'],
|
|
194
|
+
export_options_team_id: h(m, 'app_store')['export_options_team_id'],
|
|
195
|
+
export_options_signing_certificate: h(m, 'app_store')['export_options_signing_certificate'],
|
|
196
|
+
) unless h(m, 'app_store').empty?),
|
|
197
|
+
)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# ── Web ───────────────────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
def self.parse_web(m)
|
|
203
|
+
WebPlatform.new(
|
|
204
|
+
default: parse_web_default(h(m, 'default')),
|
|
205
|
+
flavors: parse_flavors(h(m, 'flavor')) { |v| parse_web_flavor(v) },
|
|
206
|
+
)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def self.parse_web_default(m)
|
|
210
|
+
WebDefault.new(
|
|
211
|
+
id: m['id'], name: m['name'],
|
|
212
|
+
version_name: m['version_name']&.to_s, version_code: m['version_code']&.to_i,
|
|
213
|
+
main_file: m['main_file'], icon: m['icon'],
|
|
214
|
+
cloudflare: parse_cloudflare(h(m, 'cloudflare')),
|
|
215
|
+
firebase: (parse_firebase_config(h(m, 'firebase')) unless h(m, 'firebase').empty?),
|
|
216
|
+
build_types: parse_build_type_configs(h(m, 'build_types')),
|
|
217
|
+
custom: parse_custom_config(h(m, 'custom')),
|
|
218
|
+
dart_defines: parse_dart_defines(h(m, 'dart_defines')),
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def self.parse_web_flavor(m)
|
|
223
|
+
WebFlavor.new(
|
|
224
|
+
id: m['id'], id_suffix: m.fetch('id_suffix', '').to_s,
|
|
225
|
+
name: m['name'], name_suffix: m.fetch('name_suffix', '').to_s,
|
|
226
|
+
version_name: m['version_name']&.to_s, version_code: m['version_code']&.to_i,
|
|
227
|
+
main_file: m['main_file'], icon: m['icon'],
|
|
228
|
+
cloudflare: parse_cloudflare(h(m, 'cloudflare')),
|
|
229
|
+
firebase: (parse_firebase_config(h(m, 'firebase')) unless h(m, 'firebase').empty?),
|
|
230
|
+
build_types: parse_build_type_configs(h(m, 'build_types')),
|
|
231
|
+
custom: parse_custom_config(h(m, 'custom')),
|
|
232
|
+
dart_defines: parse_dart_defines(h(m, 'dart_defines')),
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def self.parse_cloudflare(m)
|
|
237
|
+
return nil if m.empty?
|
|
238
|
+
CloudflareConfig.new(
|
|
239
|
+
project_name: m['project_name'],
|
|
240
|
+
account_id: m['account_id'],
|
|
241
|
+
branch: m['branch'],
|
|
242
|
+
mode: m['mode'],
|
|
243
|
+
)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# ── Windows ───────────────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
def self.parse_windows(m)
|
|
249
|
+
WindowsPlatform.new(
|
|
250
|
+
default: parse_windows_default(h(m, 'default')),
|
|
251
|
+
flavors: parse_flavors(h(m, 'flavor')) { |v| parse_windows_flavor(v) },
|
|
252
|
+
)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def self.parse_windows_default(m)
|
|
256
|
+
WindowsDefault.new(
|
|
257
|
+
id: m['id'], name: m['name'],
|
|
258
|
+
version_name: m['version_name']&.to_s, version_code: m['version_code']&.to_i,
|
|
259
|
+
main_file: m['main_file'],
|
|
260
|
+
firebase: (parse_firebase_config(h(m, 'firebase')) unless h(m, 'firebase').empty?),
|
|
261
|
+
build_types: parse_build_type_configs(h(m, 'build_types')),
|
|
262
|
+
custom: parse_custom_config(h(m, 'custom')),
|
|
263
|
+
dart_defines: parse_dart_defines(h(m, 'dart_defines')),
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def self.parse_windows_flavor(m)
|
|
268
|
+
WindowsFlavor.new(
|
|
269
|
+
id: m['id'], id_suffix: m.fetch('id_suffix', '').to_s,
|
|
270
|
+
name: m['name'], name_suffix: m.fetch('name_suffix', '').to_s,
|
|
271
|
+
version_name: m['version_name']&.to_s, version_code: m['version_code']&.to_i,
|
|
272
|
+
main_file: m['main_file'],
|
|
273
|
+
firebase: (parse_firebase_config(h(m, 'firebase')) unless h(m, 'firebase').empty?),
|
|
274
|
+
build_types: parse_build_type_configs(h(m, 'build_types')),
|
|
275
|
+
custom: parse_custom_config(h(m, 'custom')),
|
|
276
|
+
dart_defines: parse_dart_defines(h(m, 'dart_defines')),
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# ── Shared parsers ────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
def self.parse_firebase_config(m)
|
|
283
|
+
FirebaseConfig.new(
|
|
284
|
+
project_id: m['project_id'],
|
|
285
|
+
config_file: m['config_file'],
|
|
286
|
+
service_account: m['service_account'],
|
|
287
|
+
target: m['target'],
|
|
288
|
+
)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def self.parse_auth_config(m)
|
|
292
|
+
AuthConfig.new(
|
|
293
|
+
client_id: m['clientId'],
|
|
294
|
+
reversed_client_id: m['reversedClientId'],
|
|
295
|
+
others: h(m, 'others'),
|
|
296
|
+
)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def self.parse_build_type_configs(m)
|
|
300
|
+
m.transform_values do |v|
|
|
301
|
+
v = v.is_a?(Hash) ? v : {}
|
|
302
|
+
BuildTypeConfig.new(
|
|
303
|
+
id_suffix: v.fetch('id_suffix', '').to_s,
|
|
304
|
+
name_suffix: v.fetch('name_suffix', '').to_s,
|
|
305
|
+
firebase: (parse_firebase_config(h(v, 'firebase')) unless h(v, 'firebase').empty?),
|
|
306
|
+
auth: (parse_auth_config(h(v, 'auth')) unless h(v, 'auth').empty?),
|
|
307
|
+
admob: (AdmobConfig.new(gms_ads_id: h(v, 'admob')['gms_ads_id']) unless h(v, 'admob').empty?),
|
|
308
|
+
minify_enabled: v['minifyEnabled'],
|
|
309
|
+
shrink_resources: v['shrinkResources'],
|
|
310
|
+
lint_check_release_builds: v['lintCheckReleaseBuilds'],
|
|
311
|
+
ndk_version: v['ndkVersion'],
|
|
312
|
+
ndk_debug_symbol_level: v['ndkDebugSymbolLevel'],
|
|
313
|
+
ndk_abi_filters: (v['ndkAbiFilters'].is_a?(Array) ? v['ndkAbiFilters'] : []),
|
|
314
|
+
custom: parse_custom_config(h(v, 'custom')),
|
|
315
|
+
dart_defines: parse_dart_defines(h(v, 'dart_defines')),
|
|
316
|
+
)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def self.parse_flavor_stores(m)
|
|
321
|
+
FlavorStores.new(
|
|
322
|
+
google_play: (GooglePlayFlavorStore.new(priority: h(m, 'google_play')['priority']) unless h(m, 'google_play').empty?),
|
|
323
|
+
samsung_galaxy: (SamsungGalaxyFlavorStore.new(app_id: h(m, 'samsung_galaxy')['app_id']) unless h(m, 'samsung_galaxy').empty?),
|
|
324
|
+
amazon: (AmazonFlavorStore.new(app_id: h(m, 'amazon')['app_id']) unless h(m, 'amazon').empty?),
|
|
325
|
+
app_store: (AppStoreFlavorStore.new(apple_id: h(m, 'app_store')['apple_id'],
|
|
326
|
+
team_id: h(m, 'app_store')['team_id']) unless h(m, 'app_store').empty?),
|
|
327
|
+
)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def self.parse_general(_m)
|
|
331
|
+
GeneralConfig.new
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def self.parse_debug_config(m)
|
|
335
|
+
DebugConfig.new(
|
|
336
|
+
print_debug: m.fetch('printDebug', false),
|
|
337
|
+
print_build_and_flavor_info: m.fetch('printBuildAndFlavorInfo', true),
|
|
338
|
+
print_sdk_versions: m.fetch('printSdkVersions', true),
|
|
339
|
+
print_release_build_type_info: m.fetch('printReleaseBuildTypeInfo', false),
|
|
340
|
+
)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def self.parse_flavors(flavor_map, &parse_fn)
|
|
344
|
+
return {} unless flavor_map.is_a?(Hash)
|
|
345
|
+
flavor_map.transform_values { |v| parse_fn.call(v.is_a?(Hash) ? v : {}) }
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Safe Hash accessor — returns {} when key is missing or value is not a Hash
|
|
349
|
+
def self.h(map, key)
|
|
350
|
+
val = map.is_a?(Hash) ? map[key] : nil
|
|
351
|
+
val.is_a?(Hash) ? val : {}
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
DART_DEFINES_ACTION_KEYS = %w[compile run deploy].freeze
|
|
355
|
+
|
|
356
|
+
# Parses dart_defines: — plain string entries are the common map; the three
|
|
357
|
+
# reserved sub-keys compile/run/deploy (each a nested map) become the
|
|
358
|
+
# corresponding action scope. Values are coerced to strings.
|
|
359
|
+
def self.parse_dart_defines(m)
|
|
360
|
+
return DartDefines.defaults unless m.is_a?(Hash)
|
|
361
|
+
|
|
362
|
+
string_map = ->(raw) {
|
|
363
|
+
return {} unless raw.is_a?(Hash)
|
|
364
|
+
raw.transform_values { |v| v.nil? ? '' : v.to_s }
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
common = m.reject { |k, _| DART_DEFINES_ACTION_KEYS.include?(k) }
|
|
368
|
+
.transform_values { |v| v.nil? ? '' : v.to_s }
|
|
369
|
+
|
|
370
|
+
DartDefines.new(
|
|
371
|
+
common: common,
|
|
372
|
+
compile: string_map.call(m['compile']),
|
|
373
|
+
run: string_map.call(m['run']),
|
|
374
|
+
deploy: string_map.call(m['deploy']),
|
|
375
|
+
)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
require_relative 'model'
|
|
2
|
+
|
|
3
|
+
module AnnFlavorCore
|
|
4
|
+
module Resolver
|
|
5
|
+
STANDARD_BUILD_TYPES = %w[debug release profile].freeze
|
|
6
|
+
|
|
7
|
+
# ── Low-level cascade helpers (used by all plugins) ───────────────────────
|
|
8
|
+
|
|
9
|
+
def self.effective_build_type_suffix(flavor_suffix, default_suffix)
|
|
10
|
+
flavor_suffix.to_s.empty? ? default_suffix.to_s : flavor_suffix.to_s
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.resolve_id(base_id, id_suffix, build_type_id_suffix = '')
|
|
14
|
+
base_id.to_s + id_suffix.to_s + build_type_id_suffix.to_s
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.resolve_name(base_name, name_suffix, build_type_name_suffix = '')
|
|
18
|
+
base_name.to_s + name_suffix.to_s + build_type_name_suffix.to_s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# ── High-level resolver ───────────────────────────────────────────────────
|
|
22
|
+
#
|
|
23
|
+
# Usage patterns:
|
|
24
|
+
#
|
|
25
|
+
# # From YAML (batch — all platforms, all build types)
|
|
26
|
+
# spec = AnnFlavorCore::Parser.parse_file('annspec.yaml')
|
|
27
|
+
# resolved = AnnFlavorCore::Resolver.resolve_all(spec)
|
|
28
|
+
#
|
|
29
|
+
# # From in-memory model (e.g. live UI data before saving to YAML)
|
|
30
|
+
# output = AnnFlavorCore::Resolver.resolve_android_flavor(flavor, defaults, 'debug')
|
|
31
|
+
|
|
32
|
+
def self.resolve_all(spec, build_types: STANDARD_BUILD_TYPES)
|
|
33
|
+
ResolvedSpec.new(
|
|
34
|
+
android: spec.app&.android ? resolve_android(spec.app.android, build_types: build_types) : {},
|
|
35
|
+
ios: spec.app&.ios ? resolve_ios(spec.app.ios, build_types: build_types) : {},
|
|
36
|
+
web: spec.app&.web ? resolve_web(spec.app.web, build_types: build_types) : {},
|
|
37
|
+
windows: spec.app&.windows ? resolve_windows(spec.app.windows, build_types: build_types) : {},
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.resolve_android(platform, build_types: STANDARD_BUILD_TYPES)
|
|
42
|
+
platform.flavors.each_with_object({}) do |(name, flavor), result|
|
|
43
|
+
result[name] = ResolvedFlavor.new(
|
|
44
|
+
flavor_name: name,
|
|
45
|
+
by_build_type: build_types.each_with_object({}) { |bt, h| h[bt] = resolve_android_flavor(flavor, platform.default, bt) },
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.resolve_ios(platform, build_types: STANDARD_BUILD_TYPES)
|
|
51
|
+
platform.flavors.each_with_object({}) do |(name, flavor), result|
|
|
52
|
+
result[name] = ResolvedFlavor.new(
|
|
53
|
+
flavor_name: name,
|
|
54
|
+
by_build_type: build_types.each_with_object({}) { |bt, h| h[bt] = resolve_ios_flavor(flavor, platform.default, bt) },
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.resolve_web(platform, build_types: STANDARD_BUILD_TYPES)
|
|
60
|
+
platform.flavors.each_with_object({}) do |(name, flavor), result|
|
|
61
|
+
result[name] = ResolvedFlavor.new(
|
|
62
|
+
flavor_name: name,
|
|
63
|
+
by_build_type: build_types.each_with_object({}) { |bt, h| h[bt] = resolve_web_flavor(flavor, platform.default, bt) },
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.resolve_windows(platform, build_types: STANDARD_BUILD_TYPES)
|
|
69
|
+
platform.flavors.each_with_object({}) do |(name, flavor), result|
|
|
70
|
+
result[name] = ResolvedFlavor.new(
|
|
71
|
+
flavor_name: name,
|
|
72
|
+
by_build_type: build_types.each_with_object({}) { |bt, h| h[bt] = resolve_windows_flavor(flavor, platform.default, bt) },
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# ── Per-flavor resolvers (callable directly from UI / in-memory data) ─────
|
|
78
|
+
|
|
79
|
+
def self.resolve_android_flavor(flavor, defaults, build_type)
|
|
80
|
+
bt = effective_bt_config(flavor.build_types, defaults.build_types, build_type)
|
|
81
|
+
ResolvedBuildOutput.new(
|
|
82
|
+
flavor_name: nil,
|
|
83
|
+
build_type: build_type,
|
|
84
|
+
effective_id: resolve_id((flavor.id || defaults.id).to_s, flavor.id_suffix.to_s, bt.id_suffix),
|
|
85
|
+
effective_name: resolve_name((flavor.name || defaults.name).to_s, flavor.name_suffix.to_s, bt.name_suffix),
|
|
86
|
+
effective_version_name: (flavor.version_name || defaults.version_name).to_s,
|
|
87
|
+
effective_version_code: flavor.version_code || defaults.version_code,
|
|
88
|
+
effective_main_file: flavor.main_file || defaults.main_file,
|
|
89
|
+
effective_firebase: resolve_firebase(flavor.build_types[build_type]&.firebase, flavor.firebase, defaults.build_types[build_type]&.firebase, defaults.firebase),
|
|
90
|
+
effective_auth: bt.auth,
|
|
91
|
+
effective_gms_ads_id: flavor.admob&.gms_ads_id || defaults.admob&.gms_ads_id,
|
|
92
|
+
effective_icon: flavor.icon || defaults.icon,
|
|
93
|
+
effective_custom: resolve_custom(defaults.custom, defaults.build_types[build_type]&.custom, flavor.custom, flavor.build_types[build_type]&.custom),
|
|
94
|
+
effective_dart_defines: resolve_dart_defines(defaults.dart_defines, defaults.build_types[build_type]&.dart_defines, flavor.dart_defines, flavor.build_types[build_type]&.dart_defines),
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.resolve_ios_flavor(flavor, defaults, build_type)
|
|
99
|
+
bt = effective_bt_config(flavor.build_types, defaults.build_types, build_type)
|
|
100
|
+
ResolvedBuildOutput.new(
|
|
101
|
+
flavor_name: nil,
|
|
102
|
+
build_type: build_type,
|
|
103
|
+
effective_id: resolve_id((flavor.id || defaults.id).to_s, flavor.id_suffix.to_s, bt.id_suffix),
|
|
104
|
+
effective_name: resolve_name((flavor.name || defaults.name).to_s, flavor.name_suffix.to_s, bt.name_suffix),
|
|
105
|
+
effective_version_name: (flavor.version_name || defaults.version_name).to_s,
|
|
106
|
+
effective_version_code: flavor.version_code || defaults.version_code,
|
|
107
|
+
effective_main_file: flavor.main_file || defaults.main_file,
|
|
108
|
+
effective_firebase: resolve_firebase(flavor.build_types[build_type]&.firebase, flavor.firebase, defaults.build_types[build_type]&.firebase, defaults.firebase),
|
|
109
|
+
effective_auth: bt.auth,
|
|
110
|
+
effective_gms_ads_id: flavor.admob&.gms_ads_id || defaults.admob&.gms_ads_id,
|
|
111
|
+
effective_icon: flavor.icon || defaults.icon,
|
|
112
|
+
effective_custom: resolve_custom(defaults.custom, defaults.build_types[build_type]&.custom, flavor.custom, flavor.build_types[build_type]&.custom),
|
|
113
|
+
effective_dart_defines: resolve_dart_defines(defaults.dart_defines, defaults.build_types[build_type]&.dart_defines, flavor.dart_defines, flavor.build_types[build_type]&.dart_defines),
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.resolve_web_flavor(flavor, defaults, build_type)
|
|
118
|
+
bt = effective_bt_config(flavor.build_types, defaults.build_types, build_type)
|
|
119
|
+
ResolvedBuildOutput.new(
|
|
120
|
+
flavor_name: nil,
|
|
121
|
+
build_type: build_type,
|
|
122
|
+
effective_id: resolve_id((flavor.id || defaults.id).to_s, flavor.id_suffix.to_s, bt.id_suffix),
|
|
123
|
+
effective_name: resolve_name((flavor.name || defaults.name).to_s, flavor.name_suffix.to_s, bt.name_suffix),
|
|
124
|
+
effective_version_name: (flavor.version_name || defaults.version_name).to_s,
|
|
125
|
+
effective_version_code: flavor.version_code || defaults.version_code,
|
|
126
|
+
effective_main_file: flavor.main_file || defaults.main_file,
|
|
127
|
+
effective_firebase: resolve_firebase(flavor.build_types[build_type]&.firebase, flavor.firebase, defaults.build_types[build_type]&.firebase, defaults.firebase),
|
|
128
|
+
effective_auth: bt.auth,
|
|
129
|
+
effective_gms_ads_id: nil,
|
|
130
|
+
effective_icon: flavor.icon || defaults.icon,
|
|
131
|
+
effective_cloudflare: resolve_cloudflare(flavor.cloudflare, defaults.cloudflare),
|
|
132
|
+
effective_custom: resolve_custom(defaults.custom, defaults.build_types[build_type]&.custom, flavor.custom, flavor.build_types[build_type]&.custom),
|
|
133
|
+
effective_dart_defines: resolve_dart_defines(defaults.dart_defines, defaults.build_types[build_type]&.dart_defines, flavor.dart_defines, flavor.build_types[build_type]&.dart_defines),
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.resolve_cloudflare(flavor_cf, default_cf)
|
|
138
|
+
CloudflareConfig.new(
|
|
139
|
+
project_name: flavor_cf&.project_name,
|
|
140
|
+
account_id: flavor_cf&.account_id || default_cf&.account_id,
|
|
141
|
+
branch: flavor_cf&.branch || default_cf&.branch || 'main',
|
|
142
|
+
mode: flavor_cf&.mode || default_cf&.mode || 'pages',
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.resolve_windows_flavor(flavor, defaults, build_type)
|
|
147
|
+
bt = effective_bt_config(flavor.build_types, defaults.build_types, build_type)
|
|
148
|
+
ResolvedBuildOutput.new(
|
|
149
|
+
flavor_name: nil,
|
|
150
|
+
build_type: build_type,
|
|
151
|
+
effective_id: resolve_id((flavor.id || defaults.id).to_s, flavor.id_suffix.to_s, bt.id_suffix),
|
|
152
|
+
effective_name: resolve_name((flavor.name || defaults.name).to_s, flavor.name_suffix.to_s, bt.name_suffix),
|
|
153
|
+
effective_version_name: (flavor.version_name || defaults.version_name).to_s,
|
|
154
|
+
effective_version_code: flavor.version_code || defaults.version_code,
|
|
155
|
+
effective_main_file: flavor.main_file || defaults.main_file,
|
|
156
|
+
effective_firebase: resolve_firebase(flavor.build_types[build_type]&.firebase, flavor.firebase, defaults.build_types[build_type]&.firebase, defaults.firebase),
|
|
157
|
+
effective_auth: bt.auth,
|
|
158
|
+
effective_gms_ads_id: nil,
|
|
159
|
+
effective_icon: nil,
|
|
160
|
+
effective_custom: resolve_custom(defaults.custom, defaults.build_types[build_type]&.custom, flavor.custom, flavor.build_types[build_type]&.custom),
|
|
161
|
+
effective_dart_defines: resolve_dart_defines(defaults.dart_defines, defaults.build_types[build_type]&.dart_defines, flavor.dart_defines, flavor.build_types[build_type]&.dart_defines),
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# ── Internal helpers ──────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
# Deep-merges custom config — lowest priority first, key-by-key within each group.
|
|
168
|
+
def self.resolve_custom(default_custom, default_bt_custom, flavor_custom, flavor_bt_custom)
|
|
169
|
+
merge = lambda do |base, over|
|
|
170
|
+
next base if over.nil? || over.empty?
|
|
171
|
+
result = base.dup
|
|
172
|
+
over.each { |group, group_map| result[group] = (result[group] || {}).merge(group_map) }
|
|
173
|
+
result
|
|
174
|
+
end
|
|
175
|
+
merge.call(merge.call(merge.call(default_custom || {}, default_bt_custom), flavor_custom), flavor_bt_custom)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Shallow key-union across all four cascade levels — most specific key wins.
|
|
179
|
+
def self.merge_defines_map(default_map, default_bt_map, flavor_map, flavor_bt_map)
|
|
180
|
+
(default_map || {})
|
|
181
|
+
.merge(default_bt_map || {})
|
|
182
|
+
.merge(flavor_map || {})
|
|
183
|
+
.merge(flavor_bt_map || {})
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Resolves all four dart_defines sub-maps (common + three action scopes)
|
|
187
|
+
# independently.
|
|
188
|
+
def self.resolve_dart_defines(default_defines, default_bt_defines, flavor_defines, flavor_bt_defines)
|
|
189
|
+
d = default_defines || DartDefines.defaults
|
|
190
|
+
db = default_bt_defines || DartDefines.defaults
|
|
191
|
+
f = flavor_defines || DartDefines.defaults
|
|
192
|
+
fb = flavor_bt_defines || DartDefines.defaults
|
|
193
|
+
DartDefines.new(
|
|
194
|
+
common: merge_defines_map(d.common, db.common, f.common, fb.common),
|
|
195
|
+
compile: merge_defines_map(d.compile, db.compile, f.compile, fb.compile),
|
|
196
|
+
run: merge_defines_map(d.run, db.run, f.run, fb.run),
|
|
197
|
+
deploy: merge_defines_map(d.deploy, db.deploy, f.deploy, fb.deploy),
|
|
198
|
+
)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def self.effective_bt_config(flavor_bts, default_bts, build_type)
|
|
202
|
+
f = (flavor_bts || {})[build_type]
|
|
203
|
+
d = (default_bts || {})[build_type]
|
|
204
|
+
BuildTypeConfig.new(
|
|
205
|
+
id_suffix: effective_build_type_suffix(f&.id_suffix, d&.id_suffix),
|
|
206
|
+
name_suffix: effective_build_type_suffix(f&.name_suffix, d&.name_suffix),
|
|
207
|
+
firebase: merge_firebase_config(f&.firebase, d&.firebase),
|
|
208
|
+
auth: merge_auth_config(f&.auth, d&.auth),
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Full 4-level cascade for every firebase field (most specific wins):
|
|
213
|
+
# L1: flavor_bt — flavor.build_types[bt].firebase
|
|
214
|
+
# L2: flavor_top — flavor.firebase
|
|
215
|
+
# L3: default_bt — default.build_types[bt].firebase
|
|
216
|
+
# L4: default_top — defaults.firebase
|
|
217
|
+
def self.resolve_firebase(flavor_bt, flavor_top, default_bt, default_top)
|
|
218
|
+
project_id = flavor_bt&.project_id ||
|
|
219
|
+
flavor_top&.project_id ||
|
|
220
|
+
default_bt&.project_id ||
|
|
221
|
+
default_top&.project_id
|
|
222
|
+
config_file = flavor_bt&.config_file ||
|
|
223
|
+
flavor_top&.config_file ||
|
|
224
|
+
default_bt&.config_file ||
|
|
225
|
+
default_top&.config_file
|
|
226
|
+
sa = flavor_bt&.service_account ||
|
|
227
|
+
flavor_top&.service_account ||
|
|
228
|
+
default_bt&.service_account ||
|
|
229
|
+
default_top&.service_account
|
|
230
|
+
target = flavor_bt&.target ||
|
|
231
|
+
flavor_top&.target ||
|
|
232
|
+
default_bt&.target ||
|
|
233
|
+
default_top&.target
|
|
234
|
+
return nil if project_id.nil? && config_file.nil? && sa.nil? && target.nil?
|
|
235
|
+
FirebaseConfig.new(
|
|
236
|
+
project_id: project_id,
|
|
237
|
+
config_file: config_file,
|
|
238
|
+
service_account: sa,
|
|
239
|
+
target: target,
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def self.merge_firebase_config(flavor, default)
|
|
244
|
+
return default if flavor.nil?
|
|
245
|
+
return flavor if default.nil?
|
|
246
|
+
# service_account cascades independently — a flavor can override project_id without having to
|
|
247
|
+
# repeat the service_account key, and vice versa.
|
|
248
|
+
FirebaseConfig.new(
|
|
249
|
+
project_id: flavor.project_id || default.project_id,
|
|
250
|
+
config_file: flavor.config_file || default.config_file,
|
|
251
|
+
service_account: flavor.service_account || default.service_account,
|
|
252
|
+
target: flavor.target || default.target,
|
|
253
|
+
)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def self.merge_auth_config(flavor, default)
|
|
257
|
+
return default if flavor.nil?
|
|
258
|
+
return flavor if default.nil?
|
|
259
|
+
AuthConfig.new(
|
|
260
|
+
client_id: flavor.client_id || default.client_id,
|
|
261
|
+
reversed_client_id: flavor.reversed_client_id || default.reversed_client_id,
|
|
262
|
+
others: (default.others || {}).merge(flavor.others || {}),
|
|
263
|
+
)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require_relative 'model'
|
|
2
|
+
|
|
3
|
+
module AnnFlavorCore
|
|
4
|
+
#
|
|
5
|
+
# Opt-in unknown-key detection for annspec.yaml.
|
|
6
|
+
#
|
|
7
|
+
# Per-level VALID KEYS are derived automatically from each Struct's +members+ (Ruby's
|
|
8
|
+
# built-in reflection for Struct.new(...)-defined classes) -- this part can never
|
|
9
|
+
# drift out of sync with the model, the same way Kotlin's reflection-based checker
|
|
10
|
+
# can't. Ruby has no static typing, so which nested Struct class a field recurses
|
|
11
|
+
# into cannot be derived by reflection alone; RECURSION_TARGETS is the one small,
|
|
12
|
+
# explicit map needed for that -- it is a target-class lookup, not a key list, so it
|
|
13
|
+
# stays small (~20 entries) and stable even as fields are added to existing structs.
|
|
14
|
+
#
|
|
15
|
+
# Does NOT modify AnnFlavorCore::Parser in any way -- a fully separate, additive pass
|
|
16
|
+
# over the same raw Hash the parser already receives.
|
|
17
|
+
#
|
|
18
|
+
# Usage: AnnFlavorCore::StrictModeChecker.check(raw_hash, AnnFlavorCore::AnnSpec)
|
|
19
|
+
#
|
|
20
|
+
module StrictModeChecker
|
|
21
|
+
# Fields whose value is a free-form Hash (arbitrary user-declared keys are data,
|
|
22
|
+
# not schema) -- exempted from unknown-key checking entirely, at any level.
|
|
23
|
+
FREE_FORM_FIELDS = %i[custom dart_defines].freeze
|
|
24
|
+
|
|
25
|
+
# (containing_struct, field_name) => target_struct — only the "does this field
|
|
26
|
+
# recurse, and into what" relationship; the valid-keys-within-that-struct question
|
|
27
|
+
# is still fully derived from Struct#members.
|
|
28
|
+
RECURSION_TARGETS = {
|
|
29
|
+
[AnnSpec, :app] => AnnApp,
|
|
30
|
+
[AnnSpec, :tooling] => ToolingConfig,
|
|
31
|
+
[AnnApp, :android] => AndroidPlatform,
|
|
32
|
+
[AnnApp, :ios] => IosPlatform,
|
|
33
|
+
[AnnApp, :web] => WebPlatform,
|
|
34
|
+
[AnnApp, :windows] => WindowsPlatform,
|
|
35
|
+
[AnnApp, :integrations] => AnnIntegrations,
|
|
36
|
+
|
|
37
|
+
[AndroidPlatform, :default] => AndroidDefault,
|
|
38
|
+
[AndroidPlatform, :flavors] => AndroidFlavor, # via `flavor:` YAML key, map values
|
|
39
|
+
[IosPlatform, :default] => IosDefault,
|
|
40
|
+
[IosPlatform, :flavors] => IosFlavor,
|
|
41
|
+
[WebPlatform, :default] => WebDefault,
|
|
42
|
+
[WebPlatform, :flavors] => WebFlavor,
|
|
43
|
+
[WindowsPlatform, :default] => WindowsDefault,
|
|
44
|
+
[WindowsPlatform, :flavors] => WindowsFlavor,
|
|
45
|
+
|
|
46
|
+
[AndroidDefault, :admob] => AdmobConfig,
|
|
47
|
+
[AndroidDefault, :sdk] => AndroidSdk,
|
|
48
|
+
[AndroidDefault, :credentials] => AndroidCredentials,
|
|
49
|
+
[AndroidDefault, :firebase] => FirebaseConfig,
|
|
50
|
+
[AndroidDefault, :build_types] => BuildTypeConfig, # map values
|
|
51
|
+
[AndroidFlavor, :admob] => AdmobConfig,
|
|
52
|
+
[AndroidFlavor, :credentials] => AndroidCredentials,
|
|
53
|
+
[AndroidFlavor, :firebase] => FirebaseConfig,
|
|
54
|
+
[AndroidFlavor, :stores] => FlavorStores,
|
|
55
|
+
[AndroidFlavor, :build_types] => BuildTypeConfig,
|
|
56
|
+
|
|
57
|
+
[IosDefault, :admob] => AdmobConfig,
|
|
58
|
+
[IosDefault, :credentials] => IosCredentials,
|
|
59
|
+
[IosDefault, :firebase] => FirebaseConfig,
|
|
60
|
+
[IosDefault, :build_types] => BuildTypeConfig,
|
|
61
|
+
[IosFlavor, :admob] => AdmobConfig,
|
|
62
|
+
[IosFlavor, :credentials] => IosCredentials,
|
|
63
|
+
[IosFlavor, :firebase] => FirebaseConfig,
|
|
64
|
+
[IosFlavor, :stores] => FlavorStores,
|
|
65
|
+
[IosFlavor, :build_types] => BuildTypeConfig,
|
|
66
|
+
|
|
67
|
+
[WebDefault, :cloudflare] => CloudflareConfig,
|
|
68
|
+
[WebDefault, :build_types] => BuildTypeConfig,
|
|
69
|
+
[WebFlavor, :cloudflare] => CloudflareConfig,
|
|
70
|
+
[WebFlavor, :build_types] => BuildTypeConfig,
|
|
71
|
+
|
|
72
|
+
[WindowsDefault, :build_types] => BuildTypeConfig,
|
|
73
|
+
[WindowsFlavor, :build_types] => BuildTypeConfig,
|
|
74
|
+
|
|
75
|
+
[BuildTypeConfig, :admob] => AdmobConfig,
|
|
76
|
+
[BuildTypeConfig, :firebase] => FirebaseConfig,
|
|
77
|
+
[BuildTypeConfig, :auth] => AuthConfig,
|
|
78
|
+
|
|
79
|
+
[AndroidCredentials, :signing] => AndroidSigning,
|
|
80
|
+
[AndroidCredentials, :google_play] => GooglePlayCredentials,
|
|
81
|
+
[AndroidCredentials, :samsung_galaxy] => SamsungGalaxyCredentials,
|
|
82
|
+
[AndroidCredentials, :amazon] => AmazonCredentials,
|
|
83
|
+
[IosCredentials, :signing] => IosSigning,
|
|
84
|
+
[IosCredentials, :app_store] => AppStoreCredentials,
|
|
85
|
+
|
|
86
|
+
[FlavorStores, :google_play] => GooglePlayFlavorStore,
|
|
87
|
+
[FlavorStores, :samsung_galaxy] => SamsungGalaxyFlavorStore,
|
|
88
|
+
[FlavorStores, :amazon] => AmazonFlavorStore,
|
|
89
|
+
[FlavorStores, :app_store] => AppStoreFlavorStore,
|
|
90
|
+
}.freeze
|
|
91
|
+
|
|
92
|
+
# Fields whose target struct's instances live inside a Hash keyed by an arbitrary
|
|
93
|
+
# user-chosen name (flavor name, build-type name), not a single nested Hash.
|
|
94
|
+
MAP_VALUED_FIELDS = %i[flavors build_types].freeze
|
|
95
|
+
|
|
96
|
+
def self.check(raw, root_struct)
|
|
97
|
+
issues = []
|
|
98
|
+
walk(raw, root_struct, '', issues)
|
|
99
|
+
issues
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def self.walk(raw, struct_class, path, issues)
|
|
103
|
+
return unless raw.is_a?(Hash)
|
|
104
|
+
|
|
105
|
+
valid_keys = struct_class.members.map(&:to_s)
|
|
106
|
+
# `flavors` (Struct member) reads YAML key `flavor` (singular) -- the one
|
|
107
|
+
# deliberate field-name <-> YAML-key exception in the schema.
|
|
108
|
+
valid_keys = valid_keys.map { |k| k == 'flavors' ? 'flavor' : k }
|
|
109
|
+
|
|
110
|
+
raw.each do |raw_key, raw_value|
|
|
111
|
+
key = raw_key.to_s
|
|
112
|
+
field_path = path.empty? ? key : "#{path}.#{key}"
|
|
113
|
+
|
|
114
|
+
unless valid_keys.include?(key)
|
|
115
|
+
context = path.empty? ? 'top-level annspec.yaml' : "#{path} block"
|
|
116
|
+
issues << "Unknown field \"#{key}\" in #{context}. Check for typos or remove the field."
|
|
117
|
+
next
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
field_sym = (key == 'flavor' ? 'flavors' : key).to_sym
|
|
121
|
+
next if FREE_FORM_FIELDS.include?(field_sym)
|
|
122
|
+
|
|
123
|
+
target_struct = RECURSION_TARGETS[[struct_class, field_sym]]
|
|
124
|
+
next unless target_struct
|
|
125
|
+
|
|
126
|
+
if MAP_VALUED_FIELDS.include?(field_sym)
|
|
127
|
+
raw_value.is_a?(Hash) && raw_value.each_value do |entry_val|
|
|
128
|
+
walk(entry_val, target_struct, field_path, issues)
|
|
129
|
+
end
|
|
130
|
+
else
|
|
131
|
+
walk(raw_value, target_struct, field_path, issues)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
require_relative 'ann_flavor_core/version'
|
|
2
|
+
require_relative 'ann_flavor_core/model'
|
|
3
|
+
require_relative 'ann_flavor_core/parser'
|
|
4
|
+
require_relative 'ann_flavor_core/resolver'
|
|
5
|
+
require_relative 'ann_flavor_core/ios_resolution'
|
|
6
|
+
require_relative 'ann_flavor_core/strict_mode_checker'
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ann-flavor-flutter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ANN Solutions
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: fastlane
|
|
@@ -66,7 +65,6 @@ dependencies:
|
|
|
66
65
|
- - "~>"
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
67
|
version: '0.6'
|
|
69
|
-
description:
|
|
70
68
|
email:
|
|
71
69
|
- support@annaibrands.com
|
|
72
70
|
executables: []
|
|
@@ -102,12 +100,18 @@ files:
|
|
|
102
100
|
- lib/fastlane/plugin/ann_flavor_flutter/helper/utils_project_config.rb
|
|
103
101
|
- lib/fastlane/plugin/ann_flavor_flutter/helper/utils_spec_loader.rb
|
|
104
102
|
- lib/fastlane/plugin/ann_flavor_flutter/helper/utils_status.rb
|
|
103
|
+
- lib/vendor/ann_flavor_core/lib/ann_flavor_core.rb
|
|
104
|
+
- lib/vendor/ann_flavor_core/lib/ann_flavor_core/ios_resolution.rb
|
|
105
|
+
- lib/vendor/ann_flavor_core/lib/ann_flavor_core/model.rb
|
|
106
|
+
- lib/vendor/ann_flavor_core/lib/ann_flavor_core/parser.rb
|
|
107
|
+
- lib/vendor/ann_flavor_core/lib/ann_flavor_core/resolver.rb
|
|
108
|
+
- lib/vendor/ann_flavor_core/lib/ann_flavor_core/strict_mode_checker.rb
|
|
109
|
+
- lib/vendor/ann_flavor_core/lib/ann_flavor_core/version.rb
|
|
105
110
|
- plugin_manager.sh
|
|
106
111
|
homepage: https://github.com/anntech-dev
|
|
107
112
|
licenses:
|
|
108
113
|
- MIT
|
|
109
114
|
metadata: {}
|
|
110
|
-
post_install_message:
|
|
111
115
|
rdoc_options: []
|
|
112
116
|
require_paths:
|
|
113
117
|
- lib
|
|
@@ -122,8 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
122
126
|
- !ruby/object:Gem::Version
|
|
123
127
|
version: '0'
|
|
124
128
|
requirements: []
|
|
125
|
-
rubygems_version: 3.
|
|
126
|
-
signing_key:
|
|
129
|
+
rubygems_version: 3.6.9
|
|
127
130
|
specification_version: 4
|
|
128
131
|
summary: A fastlane plugin to manage Flutter build flavors and shared setup.
|
|
129
132
|
test_files: []
|