xgem-cli 2.0.0-alpha.3 → 2.0.0-alpha.5

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
@@ -86,7 +89,7 @@ doctor_print_ios() {
86
89
  echo "Deployment target [$cfg]: ${t:-unknown}"
87
90
  done
88
91
 
89
- local required
92
+ local required generated
90
93
  required=$(ios_required_spm_deployment_target "$project_dir" 2>/dev/null)
91
94
  if [ -n "$required" ]; then
92
95
  echo "Required by resolved SwiftPM plugins: $required"
@@ -94,6 +97,14 @@ doctor_print_ios() {
94
97
  echo "Required by resolved SwiftPM plugins: unable to determine (run 'flutter pub get' first, or no SPM plugins declare an explicit floor)"
95
98
  fi
96
99
 
100
+ if [ "$uses_spm" = "yes" ]; then
101
+ generated=$(ios_generated_package_target "$project_dir" 2>/dev/null)
102
+ echo "FlutterGeneratedPluginSwiftPackage declares: ${generated:-unknown}"
103
+ if [ -n "$required" ] && [ -n "$generated" ] && _ios_ver_lt "$generated" "$required"; then
104
+ log_warn "This is stale — it declares iOS $generated but plugins need $required. This can happen even when your app's own deployment target is already high enough; 'xgem run flutter build' will detect and fix this."
105
+ fi
106
+ fi
107
+
97
108
  if [ "$uses_spm" = "yes" ]; then
98
109
  ios_known_issue_check
99
110
  fi
package/lib/flutter.sh CHANGED
@@ -66,14 +66,19 @@ _flutter_build_ios() {
66
66
 
67
67
  require_cmd xcodebuild "iOS builds require Xcode's command-line tools."
68
68
 
69
+ # Must run pub get BEFORE reconciling: the required-deployment-target
70
+ # check reads .dart_tool/package_config.json, and that file only
71
+ # reflects the plugin versions actually in use after a fresh pub get.
72
+ # Reconciling against a stale package_config.json can silently pass a
73
+ # check that the real, just-resolved dependency graph would fail.
74
+ log_info "Regenerating dependencies..."
75
+ flutter pub get
76
+
69
77
  log_info "Reconciling iOS deployment target against resolved SwiftPM plugins..."
70
78
  if ! ios_reconcile_deployment_target "."; then
71
79
  die "iOS deployment target could not be reconciled; aborting before build."
72
80
  fi
73
81
 
74
- log_info "Regenerating dependencies..."
75
- flutter pub get
76
-
77
82
  if ios_project_uses_pods "."; then
78
83
  log_info "CocoaPods detected - installing pods..."
79
84
  ( cd ios || exit 1; pod install )
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,34 @@ 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
+ find "$packages_dir" -maxdepth 4 -name "Package.swift" -print0 2>/dev/null \
107
+ | xargs -0 grep -ohE '\.iOS\(\.v[0-9_]+\)' 2>/dev/null \
108
+ | grep -oE '[0-9]+(_[0-9]+)?' | tr '_' '.' \
109
+ | sort -g | tail -1
110
+ return 0
111
+ fi
91
112
 
113
+ log_debug "No resolved SwiftPM packages directory at $packages_dir yet; falling back to package_config.json."
114
+
115
+ local pkg_config="$project_dir/.dart_tool/package_config.json"
92
116
  [ -f "$pkg_config" ] || { log_debug "No .dart_tool/package_config.json — run flutter pub get first."; return 1; }
93
117
  has_cmd python3 || { log_debug "python3 not found; cannot parse package_config.json."; return 1; }
94
118
 
@@ -206,13 +230,23 @@ ios_regenerate_generated_package() {
206
230
  }
207
231
 
208
232
  ios_known_issue_check() {
209
- log_warn "Known upstream issue (flutter/flutter#186804, #189422, #162072): FlutterGeneratedPluginSwiftPackage's deployment target can desync from your app's IPHONEOS_DEPLOYMENT_TARGET. Currently open — xgem detects and works around it automatically below."
233
+ 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."
234
+ }
235
+
236
+ # _ios_ver_lt a b -> true if a < b, numeric comparison (not string equality,
237
+ # since "15" and "15.0" are the same version but different strings).
238
+ _ios_ver_lt() {
239
+ awk -v a="$1" -v b="$2" 'BEGIN{exit !(a<b)}'
210
240
  }
211
241
 
212
242
  # ios_reconcile_deployment_target [project_dir]
213
- # The core "self-healing" step: detect a required-vs-current mismatch, plan
214
- # the fix, confirm (unless --yes/--dry-run), apply, regenerate, verify, and
215
- # fall back to the documented workaround if the upstream bug is still biting.
243
+ # The core "self-healing" step. Checks TWO independent things against the
244
+ # required version the app's own pbxproj target, AND
245
+ # FlutterGeneratedPluginSwiftPackage's own declared platform because the
246
+ # upstream bug is exactly that these two can desync from each other. A
247
+ # project whose pbxproj is already at (say) 15.6 can still have a generated
248
+ # package stuck at 13.0; checking only the pbxproj target (as an earlier
249
+ # version of this function did) misses that case entirely.
216
250
  # Returns 0 if no action was needed or the fix succeeded; 1 if it couldn't
217
251
  # reconcile and the caller should stop before attempting a build.
218
252
  ios_reconcile_deployment_target() {
@@ -220,46 +254,53 @@ ios_reconcile_deployment_target() {
220
254
 
221
255
  ios_project_uses_spm "$project_dir" || { log_debug "Project does not use SwiftPM; skipping deployment-target reconciliation."; return 0; }
222
256
 
223
- local required current
257
+ local required
224
258
  required=$(ios_required_spm_deployment_target "$project_dir")
225
- current=$(ios_deployment_target_for_config "Release" "$project_dir")
226
-
227
259
  if [ -z "$required" ]; then
228
260
  log_debug "Could not determine a required deployment target from resolved SPM plugins; skipping reconciliation."
229
261
  return 0
230
262
  fi
231
- if [ -z "$current" ]; then
232
- log_warn "Could not determine the project's current deployment target; proceeding without reconciliation."
263
+
264
+ ios_known_issue_check
265
+
266
+ local current generated
267
+ current=$(ios_deployment_target_for_config "Release" "$project_dir")
268
+ generated=$(ios_generated_package_target "$project_dir")
269
+
270
+ local pbxproj_needs_bump=0 generated_needs_fix=0
271
+ [ -n "$current" ] && _ios_ver_lt "$current" "$required" && pbxproj_needs_bump=1
272
+ if [ -z "$generated" ] || _ios_ver_lt "$generated" "$required"; then
273
+ generated_needs_fix=1
274
+ fi
275
+
276
+ if [ "$pbxproj_needs_bump" = 0 ] && [ "$generated_needs_fix" = 0 ]; then
277
+ log_debug "Deployment target ($current) and generated package ($generated) already satisfy SwiftPM requirement ($required)."
233
278
  return 0
234
279
  fi
235
280
 
236
- ios_known_issue_check
281
+ log_warn "Resolved SwiftPM plugins require iOS $required."
282
+ [ "$pbxproj_needs_bump" = 1 ] && echo " - project.pbxproj currently targets iOS $current"
283
+ [ "$generated_needs_fix" = 1 ] && echo " - FlutterGeneratedPluginSwiftPackage currently declares iOS ${generated:-unknown}"
284
+ echo "Plan:"
285
+ [ "$pbxproj_needs_bump" = 1 ] && echo " 1. Set IPHONEOS_DEPLOYMENT_TARGET = $required across every build configuration in project.pbxproj"
286
+ echo " 2. Clear ios/Flutter/ephemeral and re-run 'flutter pub get' to regenerate FlutterGeneratedPluginSwiftPackage"
287
+ echo " 3. Verify the regenerated package declares iOS $required; if it still doesn't (known upstream bug), patch it directly"
288
+
289
+ if ! confirm "Apply this fix?"; then
290
+ log_warn "Skipped. The build will likely fail SwiftPM resolution until this is reconciled manually."
291
+ return 1
292
+ fi
293
+
294
+ [ "$pbxproj_needs_bump" = 1 ] && ios_pbxproj_patch_deployment_target "$required" "$project_dir"
295
+ ios_regenerate_generated_package "$project_dir"
237
296
 
238
- if awk -v a="$required" -v b="$current" 'BEGIN{exit !(a>b)}'; then
239
- log_warn "Resolved SwiftPM plugins require iOS $required, but the project targets iOS $current."
240
- echo "Plan:"
241
- echo " 1. Set IPHONEOS_DEPLOYMENT_TARGET = $required across every build configuration in project.pbxproj"
242
- echo " 2. Clear ios/Flutter/ephemeral and re-run 'flutter pub get' to regenerate FlutterGeneratedPluginSwiftPackage"
243
- echo " 3. Verify the regenerated package declares iOS $required; if it still doesn't (known upstream bug), patch it directly"
244
-
245
- if ! confirm "Apply this fix?"; then
246
- log_warn "Skipped. The build will likely fail SwiftPM resolution until the deployment target is reconciled manually."
247
- return 1
248
- fi
249
-
250
- ios_pbxproj_patch_deployment_target "$required" "$project_dir"
251
- ios_regenerate_generated_package "$project_dir"
252
-
253
- local regenerated
254
- regenerated=$(ios_generated_package_target "$project_dir")
255
- if [ "$regenerated" = "$required" ]; then
256
- log_success "Generated package now correctly declares iOS $regenerated."
257
- else
258
- log_warn "Generated package declares '${regenerated:-unknown}' after regeneration, not $required."
259
- ios_patch_generated_package_target "$required" "$project_dir"
260
- fi
297
+ local regenerated
298
+ regenerated=$(ios_generated_package_target "$project_dir")
299
+ if [ -n "$regenerated" ] && ! _ios_ver_lt "$regenerated" "$required"; then
300
+ log_success "Generated package now correctly declares iOS $regenerated."
261
301
  else
262
- log_debug "Deployment target ($current) already satisfies SwiftPM requirement ($required)."
302
+ log_warn "Generated package declares '${regenerated:-unknown}' after regeneration, still below $required."
303
+ ios_patch_generated_package_target "$required" "$project_dir"
263
304
  fi
264
305
 
265
306
  return 0
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
+ XGEM_VERSION="2.0.0-alpha.5"
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.3",
3
+ "version": "2.0.0-alpha.5",
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"