xgem-cli 2.0.0-alpha.5 → 2.0.0-alpha.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.
package/README.md CHANGED
@@ -115,7 +115,7 @@ engine (`lib-win/`, driven by `bin/xgem.js`) so `npm install -g xgem-cli` actual
115
115
  in `cmd.exe`/PowerShell, no WSL required.
116
116
 
117
117
  What works on Windows: `init`/`add`/`run`/`terminate`, `doctor`, the git workflow
118
- commands, and Flutter builds for **APK and Windows desktop** targets. What doesn't:
118
+ commands, and Flutter builds for **APK, App Bundle, and Windows desktop** targets. What doesn't:
119
119
  Flutter **iOS/macOS** builds — Xcode has no Windows equivalent, so `xgem doctor ios`
120
120
  explains that plainly instead of pretending. `swift` isn't offered as a framework
121
121
  choice on Windows for the same reason. If you're inside WSL, none of this applies —
package/bin/xgem.js CHANGED
@@ -164,7 +164,7 @@ function printFrameworkHelp(fw) {
164
164
  }
165
165
  if (fw === 'flutter') {
166
166
  console.log('');
167
- logInfo("Flutter builds on Windows support APK and Windows desktop targets. Run 'xgem doctor ios' for why iOS/macOS aren't available here.");
167
+ logInfo("Flutter builds on Windows support APK, App Bundle, and Windows desktop targets. Run 'xgem doctor ios' for why iOS/macOS aren't available here.");
168
168
  }
169
169
  console.log('');
170
170
  }
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
 
@@ -164,20 +189,21 @@ cmd_flutter_build() {
164
189
  echo ""
165
190
  echo "Select Target Platform:"
166
191
  echo "1) APK (Android)"
167
- echo "2) iOS"
168
- echo "3) macOS"
169
- echo "4) Windows"
170
- echo "5) Linux"
192
+ echo "2) App Bundle (Android, .aab — required for Play Store uploads)"
193
+ echo "3) iOS"
194
+ echo "4) macOS"
195
+ echo "5) Windows"
196
+ echo "6) Linux"
171
197
  local platform_choice
172
- read -r -p "Choose [1-5]: " platform_choice
173
- if ! [[ "$platform_choice" =~ ^[1-5]$ ]]; then
198
+ read -r -p "Choose [1-6]: " platform_choice
199
+ if ! [[ "$platform_choice" =~ ^[1-6]$ ]]; then
174
200
  die "Invalid platform choice '$platform_choice'."
175
201
  fi
176
202
 
177
203
  local mode="--release"
178
204
  [[ "$is_release" == "n" || "$is_release" == "N" ]] && mode="--debug"
179
205
 
180
- if [ "$platform_choice" -eq 2 ]; then
206
+ if [ "$platform_choice" -eq 3 ]; then
181
207
  _flutter_build_ios "$build_name" "$build_number" "$mode"
182
208
  return
183
209
  fi
@@ -185,9 +211,10 @@ cmd_flutter_build() {
185
211
  log_info "Initializing Flutter Build ($mode) for version $build_name+$build_number..."
186
212
  case $platform_choice in
187
213
  1) flutter build apk "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
188
- 3) flutter build macos "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
189
- 4) flutter build windows "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
190
- 5) flutter build linux "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
214
+ 2) flutter build appbundle "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
215
+ 4) flutter build macos "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
216
+ 5) flutter build windows "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
217
+ 6) flutter build linux "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
191
218
  esac
192
219
  }
193
220
 
package/lib/ios.sh CHANGED
@@ -103,9 +103,11 @@ ios_required_spm_deployment_target() {
103
103
  local packages_dir="$project_dir/ios/Flutter/ephemeral/Packages/.packages"
104
104
 
105
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 '_' '.' \
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 '_' '.' \
109
111
  | sort -g | tail -1
110
112
  return 0
111
113
  fi
@@ -142,9 +144,12 @@ for pkg in data.get("packages", []):
142
144
  except OSError:
143
145
  continue
144
146
 
145
- 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)
146
151
  try:
147
- fv = float(m.group(1).replace("_", "."))
152
+ fv = float(raw.replace("_", "."))
148
153
  except ValueError:
149
154
  continue
150
155
  max_target = max(max_target, fv)
@@ -188,21 +193,40 @@ ios_generated_package_swift_path() {
188
193
  echo "$guess"
189
194
  return 0
190
195
  fi
191
- 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" \
192
200
  -path "*FlutterGeneratedPluginSwiftPackage*" 2>/dev/null | head -1
193
201
  }
194
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
+
195
216
  ios_generated_package_target() {
196
217
  local project_dir=${1:-.}
197
218
  local pkg
198
219
  pkg=$(ios_generated_package_swift_path "$project_dir")
199
220
  [ -n "$pkg" ] && [ -f "$pkg" ] || return 1
200
- grep -oE '\.iOS\(\.v[0-9_]+\)' "$pkg" | head -1 | grep -oE '[0-9_]+' | tr '_' '.'
221
+ _ios_extract_platform_version < "$pkg"
201
222
  }
202
223
 
203
224
  # Last-resort workaround for the still-open upstream desync bug: directly
204
225
  # patch the generated package's platform declaration. Always logged loudly
205
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.
206
230
  ios_patch_generated_package_target() {
207
231
  local new_target=$1
208
232
  local project_dir=${2:-.}
@@ -214,10 +238,9 @@ ios_patch_generated_package_target() {
214
238
  fi
215
239
 
216
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."
217
- local token=${new_target//./_}
218
241
  local tmp
219
242
  tmp=$(mktemp)
220
- 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"
221
244
  mv "$tmp" "$pkg"
222
245
  log_success "Patched generated package platform to iOS $new_target."
223
246
  }
@@ -229,6 +252,21 @@ ios_regenerate_generated_package() {
229
252
  ( cd "$project_dir" && flutter pub get )
230
253
  }
231
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
+
232
270
  ios_known_issue_check() {
233
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."
234
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.5"
4
+ XGEM_VERSION="2.0.0-alpha.7"
5
5
 
6
6
  print_version() {
7
7
  echo "xgem $XGEM_VERSION"
@@ -67,9 +67,10 @@ async function cmdBuild() {
67
67
 
68
68
  console.log('\nSelect Target Platform:');
69
69
  console.log('1) APK (Android)');
70
- console.log('2) Windows');
70
+ console.log('2) App Bundle (Android, .aab — required for Play Store uploads)');
71
+ console.log('3) Windows');
71
72
  console.log('(iOS/macOS require a Mac; Linux desktop can\'t be cross-built from Windows.)');
72
- const platformChoice = await prompt('Choose [1-2]');
73
+ const platformChoice = await prompt('Choose [1-3]');
73
74
 
74
75
  const mode = (isRelease === 'n') ? '--debug' : '--release';
75
76
  const buildArgs = [`--build-name=${buildName}`, `--build-number=${buildNumber}`];
@@ -78,6 +79,9 @@ async function cmdBuild() {
78
79
  logInfo(`Building APK (${mode})...`);
79
80
  flutter(['build', 'apk', mode, ...buildArgs]);
80
81
  } else if (platformChoice === '2') {
82
+ logInfo(`Building App Bundle (${mode})...`);
83
+ flutter(['build', 'appbundle', mode, ...buildArgs]);
84
+ } else if (platformChoice === '3') {
81
85
  logInfo(`Building Windows desktop app (${mode})...`);
82
86
  flutter(['build', 'windows', mode, ...buildArgs]);
83
87
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xgem-cli",
3
- "version": "2.0.0-alpha.5",
3
+ "version": "2.0.0-alpha.7",
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"