xgem-cli 2.0.0-alpha.4 → 2.0.0-alpha.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.
package/lib/doctor.sh CHANGED
@@ -26,7 +26,10 @@ flutter_supports_flag() {
26
26
  flutter_config_spm_enabled() {
27
27
  has_cmd flutter || { echo "unknown (flutter not found)"; return; }
28
28
  local line
29
- line=$(flutter config 2>/dev/null | grep -i "swift-package-manager")
29
+ # Anchored to "enable-swift-package-manager:" (the current-value line)
30
+ # so this doesn't instead match the "--[no-]enable-swift-package-manager"
31
+ # flag-syntax help line that `flutter config` also prints.
32
+ line=$(flutter config 2>/dev/null | grep -im1 -E '^[[:space:]]*enable-swift-package-manager:')
30
33
  if [ -z "$line" ]; then
31
34
  echo "unknown (not reported by this Flutter version)"
32
35
  else
package/lib/flutter.sh CHANGED
@@ -105,7 +105,7 @@ _flutter_build_ios() {
105
105
  mkdir -p "$archive_dir"
106
106
  archive_path="$archive_dir/Runner $archive_time.xcarchive"
107
107
 
108
- if ! xcodebuild -workspace ios/Runner.xcworkspace \
108
+ local -a archive_cmd=(xcodebuild -workspace ios/Runner.xcworkspace \
109
109
  -scheme Runner \
110
110
  -sdk iphoneos \
111
111
  -configuration Release \
@@ -113,10 +113,35 @@ _flutter_build_ios() {
113
113
  -archivePath "$archive_path" \
114
114
  "BUILD_NUMBER=$build_number" \
115
115
  "MARKETING_VERSION=$build_name" \
116
- -allowProvisioningUpdates > "$log_file" 2>&1; then
117
- log_error "Archive FAILED"
118
- cat "$log_file"
119
- die "xcodebuild archive failed" "$?"
116
+ -allowProvisioningUpdates)
117
+
118
+ if ! "${archive_cmd[@]}" > "$log_file" 2>&1; then
119
+ # Static pre-checks can miss cases the real SwiftPM resolution
120
+ # catches (different plugin manifest layouts, symlinked package
121
+ # dirs, etc.) — Xcode's own error is authoritative, so parse it
122
+ # directly and retry once before giving up.
123
+ local retry_required
124
+ retry_required=$(ios_parse_required_from_build_log "$log_file")
125
+ if [ -n "$retry_required" ]; then
126
+ log_warn "Archive failed on a SwiftPM platform-version mismatch Xcode reports directly (requires iOS $retry_required). Retrying with that applied..."
127
+ ios_pbxproj_patch_deployment_target "$retry_required" "."
128
+ ios_regenerate_generated_package "."
129
+ local regenerated
130
+ regenerated=$(ios_generated_package_target ".")
131
+ if [ -z "$regenerated" ] || _ios_ver_lt "$regenerated" "$retry_required"; then
132
+ ios_patch_generated_package_target "$retry_required" "."
133
+ fi
134
+
135
+ if ! "${archive_cmd[@]}" > "$log_file" 2>&1; then
136
+ log_error "Archive FAILED again after retry"
137
+ cat "$log_file"
138
+ die "xcodebuild archive failed" "$?"
139
+ fi
140
+ else
141
+ log_error "Archive FAILED"
142
+ cat "$log_file"
143
+ die "xcodebuild archive failed" "$?"
144
+ fi
120
145
  fi
121
146
  log_success "Archive created at: $archive_path"
122
147
 
package/lib/ios.sh CHANGED
@@ -60,8 +60,13 @@ ios_deployment_target_for_config() {
60
60
 
61
61
  if has_cmd xcodebuild; then
62
62
  local value
63
+ # Anchored: newer Xcode also emits DEPLOYMENT_TARGET_SETTING_NAME =
64
+ # IPHONEOS_DEPLOYMENT_TARGET (a meta-setting whose *value* is that
65
+ # string), which an unanchored grep matches before the real
66
+ # "IPHONEOS_DEPLOYMENT_TARGET = 15.6" line — anchoring to the key
67
+ # position avoids grabbing that decoy.
63
68
  value=$(xcodebuild -showBuildSettings "${xcb_args[@]}" -configuration "$config" 2>/dev/null \
64
- | grep -m1 'IPHONEOS_DEPLOYMENT_TARGET' | awk '{print $NF}')
69
+ | grep -m1 -E '^[[:space:]]*IPHONEOS_DEPLOYMENT_TARGET[[:space:]]*=' | awk '{print $NF}')
65
70
  [ -n "$value" ] && { echo "$value"; return 0; }
66
71
  fi
67
72
 
@@ -80,15 +85,36 @@ ios_pbxproj_current_max_target() {
80
85
  }
81
86
 
82
87
  # ios_required_spm_deployment_target [project_dir]
83
- # Reads .dart_tool/package_config.json (written by `flutter pub get`) to
84
- # find every resolved package's own ios/Package.swift, and returns the
85
- # highest .iOS(.vNN) platform floor declared across them. This replaces
86
- # hardcoding a guessed value (e.g. "15.0") with an actual measurement of
87
- # what the resolved dependency graph requires.
88
+ # Returns the highest .iOS(.vNN) platform floor declared across every
89
+ # resolved SwiftPM plugin, instead of hardcoding a guessed value.
90
+ #
91
+ # Primary source: ios/Flutter/ephemeral/Packages/.packages/<name>-<version>/,
92
+ # which `flutter pub get` itself populates with each SPM-enabled plugin as
93
+ # part of dependency resolution — this is the actual, already-resolved
94
+ # location Xcode/SPM uses, not a path we have to reconstruct. Scanning it
95
+ # directly sidesteps having to guess each plugin's on-disk manifest layout
96
+ # (which varies — not every plugin puts Package.swift at <root>/ios/).
97
+ #
98
+ # Falls back to parsing .dart_tool/package_config.json's rootUri entries
99
+ # (assuming the common <root>/ios/Package.swift convention) only if that
100
+ # ephemeral directory doesn't exist yet — e.g. before any pub get has run.
88
101
  ios_required_spm_deployment_target() {
89
102
  local project_dir=${1:-.}
90
- local pkg_config="$project_dir/.dart_tool/package_config.json"
103
+ local packages_dir="$project_dir/ios/Flutter/ephemeral/Packages/.packages"
104
+
105
+ if [ -d "$packages_dir" ]; then
106
+ # -L: per-plugin dirs here are frequently symlinks into pub-cache;
107
+ # without -L, find silently skips descending into them.
108
+ find -L "$packages_dir" -maxdepth 4 -name "Package.swift" -print0 2>/dev/null \
109
+ | xargs -0 grep -ohE '\.iOS\(\.v[0-9_]+\)|\.iOS\("[0-9.]+"\)' 2>/dev/null \
110
+ | grep -oE '[0-9]+(\.[0-9]+)?(_[0-9]+)?' | tr '_' '.' \
111
+ | sort -g | tail -1
112
+ return 0
113
+ fi
114
+
115
+ log_debug "No resolved SwiftPM packages directory at $packages_dir yet; falling back to package_config.json."
91
116
 
117
+ local pkg_config="$project_dir/.dart_tool/package_config.json"
92
118
  [ -f "$pkg_config" ] || { log_debug "No .dart_tool/package_config.json — run flutter pub get first."; return 1; }
93
119
  has_cmd python3 || { log_debug "python3 not found; cannot parse package_config.json."; return 1; }
94
120
 
@@ -118,9 +144,12 @@ for pkg in data.get("packages", []):
118
144
  except OSError:
119
145
  continue
120
146
 
121
- for m in re.finditer(r"\.iOS\(\.v(\d+(?:_\d+)?)\)", content):
147
+ # Both SPM syntaxes: enum .iOS(.v15) and string .iOS("15.0") (the form
148
+ # Flutter's own generator uses).
149
+ for m in re.finditer(r'\.iOS\(\.v(\d+(?:_\d+)?)\)|\.iOS\("(\d+(?:\.\d+)?)"\)', content):
150
+ raw = m.group(1) or m.group(2)
122
151
  try:
123
- fv = float(m.group(1).replace("_", "."))
152
+ fv = float(raw.replace("_", "."))
124
153
  except ValueError:
125
154
  continue
126
155
  max_target = max(max_target, fv)
@@ -164,21 +193,40 @@ ios_generated_package_swift_path() {
164
193
  echo "$guess"
165
194
  return 0
166
195
  fi
167
- find "$project_dir/ios" -maxdepth 6 -type f -name "Package.swift" \
196
+ # -L: plugin package directories under ephemeral/Packages/.packages are
197
+ # frequently symlinks into the pub-cache; a plain find silently skips
198
+ # symlinked directories instead of descending into them.
199
+ find -L "$project_dir/ios" -maxdepth 6 -type f -name "Package.swift" \
168
200
  -path "*FlutterGeneratedPluginSwiftPackage*" 2>/dev/null | head -1
169
201
  }
170
202
 
203
+ # SPM's SupportedPlatform.iOS accepts two syntaxes: the enum form .iOS(.v15)
204
+ # for the fixed known-version cases, and a string form .iOS("15.0") for
205
+ # arbitrary versions — Flutter's own code generator uses the *string* form
206
+ # for FlutterGeneratedPluginSwiftPackage, not the enum form. Matching only
207
+ # the enum form (as an earlier version of this function did) means the
208
+ # patch step silently never matches anything on a real generated package:
209
+ # sed finds no match, writes back an identical file, and "succeeds" while
210
+ # doing nothing.
211
+ _ios_extract_platform_version() {
212
+ grep -oE '\.iOS\(\.v[0-9_]+\)|\.iOS\("[0-9.]+"\)' | head -1 \
213
+ | grep -oE '[0-9]+(\.[0-9]+)?(_[0-9]+)?' | tr '_' '.'
214
+ }
215
+
171
216
  ios_generated_package_target() {
172
217
  local project_dir=${1:-.}
173
218
  local pkg
174
219
  pkg=$(ios_generated_package_swift_path "$project_dir")
175
220
  [ -n "$pkg" ] && [ -f "$pkg" ] || return 1
176
- grep -oE '\.iOS\(\.v[0-9_]+\)' "$pkg" | head -1 | grep -oE '[0-9_]+' | tr '_' '.'
221
+ _ios_extract_platform_version < "$pkg"
177
222
  }
178
223
 
179
224
  # Last-resort workaround for the still-open upstream desync bug: directly
180
225
  # patch the generated package's platform declaration. Always logged loudly
181
226
  # — this is a documented workaround for a known bug, never a silent hack.
227
+ # Writes the string form (.iOS("15.0")) since that's what Flutter's own
228
+ # generator uses in this file — matching its existing convention rather
229
+ # than introducing a different syntax.
182
230
  ios_patch_generated_package_target() {
183
231
  local new_target=$1
184
232
  local project_dir=${2:-.}
@@ -190,10 +238,9 @@ ios_patch_generated_package_target() {
190
238
  fi
191
239
 
192
240
  log_warn "Working around known Flutter SPM bug (flutter/flutter#186804): the regenerated package still doesn't match your deployment target, so patching it directly."
193
- local token=${new_target//./_}
194
241
  local tmp
195
242
  tmp=$(mktemp)
196
- sed -E "s/\.iOS\(\.v[0-9_]+\)/.iOS(.v${token})/g" "$pkg" > "$tmp"
243
+ sed -E "s/\.iOS\(\.v[0-9_]+\)/.iOS(\"${new_target}\")/g; s/\.iOS\(\"[0-9.]+\"\)/.iOS(\"${new_target}\")/g" "$pkg" > "$tmp"
197
244
  mv "$tmp" "$pkg"
198
245
  log_success "Patched generated package platform to iOS $new_target."
199
246
  }
@@ -205,6 +252,21 @@ ios_regenerate_generated_package() {
205
252
  ( cd "$project_dir" && flutter pub get )
206
253
  }
207
254
 
255
+ # ios_parse_required_from_build_log <log_file>
256
+ # Statically scanning plugin manifests to predict the required deployment
257
+ # target has proven unreliable in practice (plugin authors structure SPM
258
+ # manifests differently, package dirs can be symlinks, etc.). Xcode itself
259
+ # always computes this correctly and states it plainly in its own error:
260
+ # "requires minimum platform version X for the iOS platform". Parsing that
261
+ # directly is the authoritative fallback when the static pre-check misses
262
+ # something.
263
+ ios_parse_required_from_build_log() {
264
+ local log_file=$1
265
+ grep -oE "requires minimum platform version [0-9]+(\.[0-9]+)?" "$log_file" 2>/dev/null \
266
+ | grep -oE '[0-9]+(\.[0-9]+)?' \
267
+ | sort -g | tail -1
268
+ }
269
+
208
270
  ios_known_issue_check() {
209
271
  log_warn "Known upstream issue (flutter/flutter#186804, #189422, #162072): FlutterGeneratedPluginSwiftPackage's deployment target can desync from your app's IPHONEOS_DEPLOYMENT_TARGET — independently, even when the app's own target is already high enough. Currently open — xgem detects and works around it automatically below."
210
272
  }
package/lib/version.sh CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  # xgem version info.
3
3
 
4
- XGEM_VERSION="2.0.0-alpha.4"
4
+ XGEM_VERSION="2.0.0-alpha.6"
5
5
 
6
6
  print_version() {
7
7
  echo "xgem $XGEM_VERSION"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xgem-cli",
3
- "version": "2.0.0-alpha.4",
3
+ "version": "2.0.0-alpha.6",
4
4
  "description": "Framework-aware automation CLI: scaffolds and runs clean/build/dev scripts per project type, an environment doctor, a SwiftPM-aware iOS build engine, and a git workflow helper. Runs natively on macOS, Linux, and Windows.",
5
5
  "bin": {
6
6
  "xgem": "bin/xgem.js"