xgem-cli 2.0.0-alpha

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Paulo Michael
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # xgem
2
+
3
+ A framework-aware automation CLI. `xgem init` scaffolds clean/build/dev scripts
4
+ tailored to your project type (Flutter, Node, Python, React/Vue/Angular, Go,
5
+ Rust, Docker, Swift), `xgem run <framework> <script>` runs them, `xgem doctor`
6
+ reports on your environment, and `xgem git` wraps a common stage → commit →
7
+ rebase-pull → push workflow.
8
+
9
+ Flutter's iOS builds go through a dedicated engine that detects whether your
10
+ project uses CocoaPods or Swift Package Manager (SPM), figures out the actual
11
+ deployment target your resolved SPM plugins require (instead of guessing),
12
+ and reconciles it automatically — see [Why the iOS build engine exists](#why-the-ios-build-engine-exists).
13
+
14
+ ## Install
15
+
16
+ ### Homebrew (macOS/Linux)
17
+
18
+ ```sh
19
+ brew tap Code4Hacker/xgem
20
+ brew install xgem
21
+ ```
22
+
23
+ *(requires a separate `homebrew-xgem` tap repo and a tagged release — not set up yet)*
24
+
25
+ ### npm
26
+
27
+ ```sh
28
+ npm install -g xgem-cli
29
+ ```
30
+
31
+ *(not yet published to the npm registry)*
32
+
33
+ ### curl
34
+
35
+ ```sh
36
+ curl -fsSL https://raw.githubusercontent.com/Code4Hacker/x-gem-cli/xgem/install.sh | bash
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```
42
+ xgem init Initialize tracking system and add first framework
43
+ xgem add Interactive state-aware addition of remaining frameworks
44
+ xgem run <framework> <script> Run a workspace automation command script
45
+ xgem terminate Purge all generated automated layouts completely
46
+ xgem <framework> [help] View tailored instructions for a specific script layout
47
+ xgem doctor Report on your environment (OS, arch, toolchains found)
48
+ xgem doctor ios [path] Report on iOS build readiness: CocoaPods vs SPM, deployment
49
+ targets per config, and whether they're reconciled
50
+ xgem git cmt "message" Auto-stage, commit, rebase-pull, and push
51
+ xgem git init Setup local repo, attach remote tracker shortcuts
52
+ xgem git rm-remote Drop a configured remote
53
+ xgem git rm-branch Safely drop local and/or remote branch
54
+ xgem --version Print xgem's version
55
+
56
+ Flags (any command): --yes (skip confirmations), --dry-run (show, don't apply), --verbose
57
+ ```
58
+
59
+ ## Supported frameworks
60
+
61
+ flutter, node, python, react, vue, angular, go, rust, docker, swift
62
+
63
+ ## Architecture
64
+
65
+ ```
66
+ bin/xgem thin dispatcher — no business logic, just resolves paths and routes commands
67
+ lib/
68
+ logger.sh log levels + verbose mode
69
+ utils.sh os/arch detection, confirm(), require_cmd()
70
+ doctor.sh environment + feature detection ("xgem doctor")
71
+ ios.sh the SwiftPM-aware iOS build engine
72
+ flutter.sh flutter command group (build/hard-clean/build-runner), delegates iOS builds to ios.sh
73
+ git.sh git cmt/init/rm-remote/rm-branch
74
+ scaffold.sh generic clean/build/dev handling for the other, simpler frameworks
75
+ templates/ the actual clean/build/dev script content xgem scaffolds into your project's .xgem-automate/
76
+ ```
77
+
78
+ `xgem run flutter <script>` always goes through the native engine in `lib/flutter.sh`;
79
+ the `.xgem-automate/flutter/*.sh` files that get scaffolded into your project are thin
80
+ delegators to `xgem run flutter <script>`, so there's a single source of truth instead
81
+ of logic that can drift out of sync with two copies.
82
+
83
+ ## Why the iOS build engine exists
84
+
85
+ Flutter auto-generates a wrapper Swift package, `FlutterGeneratedPluginSwiftPackage`,
86
+ that aggregates your plugins' SPM dependencies. Its declared minimum iOS platform can
87
+ desync from your app's own `IPHONEOS_DEPLOYMENT_TARGET` — a confirmed, currently-open
88
+ upstream Flutter bug ([flutter/flutter#186804](https://github.com/flutter/flutter/issues/186804),
89
+ [#189422](https://github.com/flutter/flutter/issues/189422),
90
+ [#162072](https://github.com/flutter/flutter/issues/162072)). A single `sed` patch to
91
+ `project.pbxproj` doesn't reliably fix this: there are multiple deployment-target entries
92
+ across Debug/Release/Profile × Runner/RunnerTests, and even a fully correct patch doesn't
93
+ force Flutter to regenerate the generated package.
94
+
95
+ `xgem run flutter build` (for the iOS target) instead:
96
+
97
+ 1. Detects whether the project uses CocoaPods, SPM, or both.
98
+ 2. Computes the deployment target actually required by your *resolved* SPM plugins
99
+ (by reading their `ios/Package.swift` manifests via `.dart_tool/package_config.json`),
100
+ instead of assuming a fixed value.
101
+ 3. Shows a plan and asks for confirmation (unless `--yes`/`--dry-run`) before patching
102
+ *every* `IPHONEOS_DEPLOYMENT_TARGET` occurrence in `project.pbxproj`.
103
+ 4. Forces a clean regeneration (`flutter pub get` after clearing `ios/Flutter/ephemeral`)
104
+ and verifies the regenerated package matches.
105
+ 5. If it still doesn't (the upstream bug above), patches the generated package directly
106
+ as a documented, loudly-logged last resort — never silently.
107
+
108
+ Run `xgem doctor ios` any time for a standalone readiness report without doing a build.
109
+
110
+ ## Roadmap
111
+
112
+ Deferred to a follow-up pass, not yet in this release:
113
+ - Full self-healing/doctor treatment for the non-Flutter frameworks
114
+ - A plugin system (`xgem plugin install <name>`), starting with docker + firebase
115
+ - Shell completions, `--json` output, per-project config caching (stop re-prompting
116
+ build name/number/platform every run)
117
+ - Deeper Android SDK/Java detection
118
+
119
+ ## License
120
+
121
+ MIT
package/bin/xgem ADDED
@@ -0,0 +1,230 @@
1
+ #!/bin/bash
2
+ # xgem — framework-aware automation CLI. Entry point only: this dispatches
3
+ # to lib/*.sh modules and holds no business logic of its own.
4
+
5
+ _resolve_self_dir() {
6
+ local src="${BASH_SOURCE[0]}"
7
+ while [ -h "$src" ]; do
8
+ local dir
9
+ dir="$(cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd)"
10
+ src="$(readlink "$src")"
11
+ [[ $src != /* ]] && src="$dir/$src"
12
+ done
13
+ cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd
14
+ }
15
+
16
+ BIN_DIR="$(_resolve_self_dir)"
17
+ XGEM_HOME="$(cd -P "$BIN_DIR/.." >/dev/null 2>&1 && pwd)"
18
+ export XGEM_HOME
19
+
20
+ # shellcheck source=lib/logger.sh
21
+ source "$XGEM_HOME/lib/logger.sh"
22
+ # shellcheck source=lib/utils.sh
23
+ source "$XGEM_HOME/lib/utils.sh"
24
+ # shellcheck source=lib/version.sh
25
+ source "$XGEM_HOME/lib/version.sh"
26
+ # shellcheck source=lib/doctor.sh
27
+ source "$XGEM_HOME/lib/doctor.sh"
28
+ # shellcheck source=lib/git.sh
29
+ source "$XGEM_HOME/lib/git.sh"
30
+ # shellcheck source=lib/ios.sh
31
+ source "$XGEM_HOME/lib/ios.sh"
32
+ # shellcheck source=lib/flutter.sh
33
+ source "$XGEM_HOME/lib/flutter.sh"
34
+ # shellcheck source=lib/scaffold.sh
35
+ source "$XGEM_HOME/lib/scaffold.sh"
36
+
37
+ CONFIG_DIR=".xgem-automate"
38
+
39
+ # ---- Global flags: --yes, --dry-run, --verbose — recognized anywhere in
40
+ # the argument list and stripped before dispatch. ----
41
+ ARGS=()
42
+ for arg in "$@"; do
43
+ case "$arg" in
44
+ --yes) XGEM_YES=1 ;;
45
+ --dry-run) XGEM_DRY_RUN=1 ;;
46
+ --verbose) XGEM_VERBOSE=1 ;;
47
+ *) ARGS+=("$arg") ;;
48
+ esac
49
+ done
50
+ set -- "${ARGS[@]}"
51
+
52
+ print_banner() {
53
+ echo -e "\033[38;5;208m"
54
+ echo -e "██╗ ██╗ ██████╗ ███████╗███╗ ███╗██╗███╗ ██╗██╗"
55
+ echo -e "╚██╗██╔╝ ██╔════╝ ██╔════╝████╗ ████║██║████╗ ██║██║"
56
+ echo -e " ╚███╔╝ █████╗██║ ███╗█████╗ ██╔████╔██║██║██╔██╗ ██║██║"
57
+ echo -e " ██╔██╗ ╚════╝██║ ██║██╔══╝ ██║╚██╔╝██║██║██║╚██╗██║██║"
58
+ echo -e "██╔╝ ██╗ ╚██████╔╝███████╗██║ ╚═╝ ██║██║██║ ╚████║██║"
59
+ echo -e "╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝\033[0m"
60
+ echo -e "\033[1;36m ....Authored by Paulo Michael....\033[0m\n"
61
+ }
62
+
63
+ print_usage() {
64
+ echo "Usage:"
65
+ echo " xgem init - Initialize tracking system and add first framework"
66
+ echo " xgem add - Interactive state-aware addition of remaining frameworks"
67
+ echo " xgem run <framework> <script> - Run a workspace automation command script"
68
+ echo " xgem terminate - Purge all generated automated layouts completely"
69
+ echo " xgem <framework> [help] - View tailored instructions for a specific script layout"
70
+ echo " xgem doctor [ios] - Report on your environment / iOS SwiftPM build readiness"
71
+ echo " xgem git cmt \"message\" - Auto-stage, commit, rebase-pull, and push"
72
+ echo " xgem git init - Setup local repo, attach remote tracker shortcuts"
73
+ echo " xgem git rm-remote - Drop specified target remote tracing rules"
74
+ echo " xgem git rm-branch - Safely drop local and remote workspace branch states"
75
+ echo " xgem --version - Print xgem's version"
76
+ echo ""
77
+ echo "Flags (any command): --yes (skip confirmations), --dry-run (show, don't apply), --verbose"
78
+ }
79
+
80
+ case "${1:-}" in
81
+ init)
82
+ print_banner
83
+ if [ -d "$CONFIG_DIR" ]; then
84
+ log_warn "Workspace configuration layer already exists! Use 'xgem add' to append frameworks."
85
+ exit 0
86
+ fi
87
+ mkdir -p "$CONFIG_DIR"
88
+
89
+ echo -e "\033[1;34mSelect your starting framework architecture:\033[0m"
90
+ PS3="Enter target selection number: "
91
+ select fw in "${ALL_FRAMEWORKS[@]}"; do
92
+ if [ -n "$fw" ]; then
93
+ scaffold_inject_templates "$fw" "$CONFIG_DIR"
94
+ log_success "Successfully appended standard scripts for: $CONFIG_DIR/$fw"
95
+ break
96
+ else
97
+ echo "Invalid selection."
98
+ fi
99
+ done
100
+
101
+ if [ -f .gitignore ]; then
102
+ if ! grep -q "$CONFIG_DIR/" .gitignore; then
103
+ echo -e "\n$CONFIG_DIR/" >> .gitignore
104
+ log_success "Added automation tracking to .gitignore"
105
+ fi
106
+ else
107
+ echo "$CONFIG_DIR/" > .gitignore
108
+ log_success "Created .gitignore and hidden tracking layer folder references."
109
+ fi
110
+ exit 0
111
+ ;;
112
+
113
+ add)
114
+ [ -d "$CONFIG_DIR" ] || die "System tracking layer not found. Run 'xgem init' first."
115
+
116
+ scaffold_get_remaining_frameworks "$CONFIG_DIR"
117
+
118
+ if [ ${#REMAINING_FWS[@]} -eq 0 ]; then
119
+ log_warn "All available framework blocks are already generated inside the workspace!"
120
+ read -r -p "Do you want to restore/regenerate all framework templates to defaults? (y/n): " restore_choice
121
+ if [[ "$restore_choice" == "y" || "$restore_choice" == "Y" ]]; then
122
+ for fw in "${ALL_FRAMEWORKS[@]}"; do
123
+ scaffold_inject_templates "$fw" "$CONFIG_DIR"
124
+ done
125
+ log_success "All framework automation layouts cleanly re-generated!"
126
+ fi
127
+ exit 0
128
+ fi
129
+
130
+ echo -e "\033[1;34mSelect a framework script block to add:\033[0m"
131
+ PS3="Choose an unconfigured system tool: "
132
+ select choice in "${REMAINING_FWS[@]}" "Cancel"; do
133
+ if [ "$choice" == "Cancel" ]; then
134
+ echo "Operation canceled."
135
+ exit 0
136
+ elif [ -n "$choice" ]; then
137
+ scaffold_inject_templates "$choice" "$CONFIG_DIR"
138
+ log_success "Successfully loaded and updated files for: $CONFIG_DIR/$choice"
139
+ break
140
+ else
141
+ echo "Invalid option."
142
+ fi
143
+ done
144
+ exit 0
145
+ ;;
146
+
147
+ run)
148
+ FRAMEWORK=${2:-}
149
+ SCRIPT_NAME=${3:-}
150
+ [ -n "$FRAMEWORK" ] && [ -n "$SCRIPT_NAME" ] || die "Usage: xgem run <framework> <script>"
151
+
152
+ if [ "$FRAMEWORK" = "flutter" ] && flutter_native_command_exists "$SCRIPT_NAME"; then
153
+ flutter_run_native_command "$SCRIPT_NAME"
154
+ else
155
+ scaffold_run_script "$FRAMEWORK" "$SCRIPT_NAME" "$CONFIG_DIR"
156
+ fi
157
+ exit 0
158
+ ;;
159
+
160
+ terminate)
161
+ [ -d "$CONFIG_DIR" ] || die "Tracking layer $CONFIG_DIR not found. Nothing to terminate."
162
+
163
+ log_warn "Initiating core termination sequence for $CONFIG_DIR..."
164
+ FOLDERS=($(find "$CONFIG_DIR" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;))
165
+ COUNT=${#FOLDERS[@]}
166
+
167
+ confirm "This deletes $CONFIG_DIR and everything in it. Continue?" || { log_info "Cancelled."; exit 0; }
168
+
169
+ for fw in "${FOLDERS[@]}"; do
170
+ echo -e " \033[31m[ x ]\033[0m Removing $fw..."
171
+ rm -rf "${CONFIG_DIR:?}/$fw"
172
+ done
173
+ rm -rf "$CONFIG_DIR"
174
+
175
+ if [ -f .gitignore ]; then
176
+ grep -v "^$CONFIG_DIR/" .gitignore > tmp_gitignore && mv tmp_gitignore .gitignore
177
+ log_success "Cleaned up .gitignore tracking records."
178
+ fi
179
+
180
+ log_success "Termination complete. Total blocks destroyed: $COUNT (${FOLDERS[*]:-none})"
181
+ exit 0
182
+ ;;
183
+
184
+ git)
185
+ [ -n "${2:-}" ] || die "Usage: xgem git <cmt|init|rm-remote|rm-branch>"
186
+ cmd_git "$2" "${3:-}"
187
+ exit 0
188
+ ;;
189
+
190
+ doctor)
191
+ cmd_doctor "${2:-}" "${3:-.}"
192
+ exit 0
193
+ ;;
194
+
195
+ --version|-V|version)
196
+ print_version
197
+ exit 0
198
+ ;;
199
+
200
+ flutter|node|python|react|vue|angular|go|rust|docker|swift)
201
+ TARGET_FW=$1
202
+ TARGET_FW_UPPER=$(echo "$TARGET_FW" | tr '[:lower:]' '[:upper:]')
203
+
204
+ echo -e "\033[1;36m=== $TARGET_FW_UPPER Automation Blueprint ===\033[0m"
205
+ if [ ! -d "$CONFIG_DIR/$TARGET_FW" ]; then
206
+ log_warn "Status: Not currently injected in this workspace."
207
+ echo -e "Run \033[1;32mxgem add\033[0m to instantly append its structural scripts.\n"
208
+ else
209
+ log_success "Status: Active & Mounted inside $CONFIG_DIR/$TARGET_FW/"
210
+ echo ""
211
+ fi
212
+
213
+ echo -e "To execute scripts, use: \033[1;34mxgem run $TARGET_FW <script>\033[0m\n"
214
+ echo "Configured Commands:"
215
+ for script in $(framework_scripts "$TARGET_FW"); do
216
+ echo -e " \033[1;32m$script\033[0m"
217
+ done
218
+ if [ "$TARGET_FW" = "flutter" ]; then
219
+ echo ""
220
+ log_info "Flutter builds now use xgem's SwiftPM-aware iOS engine automatically. Run 'xgem doctor ios' for a build-readiness report."
221
+ fi
222
+ echo ""
223
+ exit 0
224
+ ;;
225
+
226
+ *)
227
+ print_usage
228
+ exit 0
229
+ ;;
230
+ esac
package/lib/doctor.sh ADDED
@@ -0,0 +1,109 @@
1
+ #!/bin/bash
2
+ # xgem doctor — environment + feature detection.
3
+ # Never assumes; always probes for the actual capability/version/config present.
4
+ # Depends on lib/logger.sh, lib/utils.sh (and, for `doctor ios`, lib/ios.sh).
5
+
6
+ doctor_tool_version() {
7
+ local name=$1
8
+ local version_flag=${2:---version}
9
+ if has_cmd "$name"; then
10
+ "$name" $version_flag 2>&1 | head -1
11
+ else
12
+ echo "not found"
13
+ fi
14
+ }
15
+
16
+ # flutter_supports_flag <command> <flag>
17
+ # Feature detection instead of version-gating: probe --help output for the
18
+ # flag rather than assuming it exists as of some Flutter version.
19
+ flutter_supports_flag() {
20
+ local subcmd=$1
21
+ local flag=$2
22
+ has_cmd flutter || return 1
23
+ flutter "$subcmd" --help 2>/dev/null | grep -q -- "$flag"
24
+ }
25
+
26
+ flutter_config_spm_enabled() {
27
+ has_cmd flutter || { echo "unknown (flutter not found)"; return; }
28
+ local line
29
+ line=$(flutter config 2>/dev/null | grep -i "swift-package-manager")
30
+ if [ -z "$line" ]; then
31
+ echo "unknown (not reported by this Flutter version)"
32
+ else
33
+ echo "$line" | sed -E 's/^[[:space:]]*//'
34
+ fi
35
+ }
36
+
37
+ doctor_print_general() {
38
+ echo -e "\033[1;36m=== xgem doctor ===\033[0m"
39
+ echo "OS: $(detect_os)"
40
+ echo "Arch: $(detect_arch)$( is_apple_silicon && echo ' (Apple Silicon)' )"
41
+ echo "git: $(doctor_tool_version git)"
42
+ echo "flutter: $(doctor_tool_version flutter)"
43
+ echo "dart: $(doctor_tool_version dart)"
44
+ echo "node: $(doctor_tool_version node)"
45
+ echo "python3: $(doctor_tool_version python3)"
46
+ echo "go: $(doctor_tool_version go version)"
47
+ echo "cargo: $(doctor_tool_version cargo)"
48
+ echo "docker: $(doctor_tool_version docker)"
49
+ if [ "$(detect_os)" = "darwin" ]; then
50
+ echo "xcodebuild: $(doctor_tool_version xcodebuild -version)"
51
+ echo "pod (CocoaPods): $(doctor_tool_version pod)"
52
+ fi
53
+ }
54
+
55
+ # doctor_print_ios [project_dir]
56
+ doctor_print_ios() {
57
+ local project_dir=${1:-.}
58
+ echo -e "\033[1;36m=== xgem doctor ios ===\033[0m"
59
+
60
+ if [ "$(detect_os)" != "darwin" ]; then
61
+ log_warn "iOS builds require macOS. Detected OS: $(detect_os)."
62
+ fi
63
+
64
+ echo "flutter: $(doctor_tool_version flutter)"
65
+ echo "xcodebuild: $(doctor_tool_version xcodebuild -version)"
66
+ echo "CocoaPods: $(doctor_tool_version pod)"
67
+ echo "SPM enabled (global cfg): $(flutter_config_spm_enabled)"
68
+
69
+ if [ ! -d "$project_dir/ios" ]; then
70
+ log_warn "No ios/ directory found under '$project_dir' — not a Flutter project with an iOS target, or run this from the project root."
71
+ return 0
72
+ fi
73
+
74
+ local uses_pods=no uses_spm=no
75
+ ios_project_uses_pods "$project_dir" && uses_pods=yes
76
+ ios_project_uses_spm "$project_dir" && uses_spm=yes
77
+ echo "Project uses CocoaPods: $uses_pods"
78
+ echo "Project uses SwiftPM: $uses_spm"
79
+ if [ "$uses_pods" = "yes" ] && [ "$uses_spm" = "yes" ]; then
80
+ log_warn "Mixed CocoaPods + SwiftPM detected. Flutter's own docs note this can cause complex dependency cycles and build errors — prefer migrating fully to SPM where all plugins support it."
81
+ fi
82
+
83
+ for cfg in Debug Release Profile; do
84
+ local t
85
+ t=$(ios_deployment_target_for_config "$cfg" "$project_dir" 2>/dev/null)
86
+ echo "Deployment target [$cfg]: ${t:-unknown}"
87
+ done
88
+
89
+ local required
90
+ required=$(ios_required_spm_deployment_target "$project_dir" 2>/dev/null)
91
+ if [ -n "$required" ]; then
92
+ echo "Required by resolved SwiftPM plugins: $required"
93
+ else
94
+ echo "Required by resolved SwiftPM plugins: unable to determine (run 'flutter pub get' first, or no SPM plugins declare an explicit floor)"
95
+ fi
96
+
97
+ if [ "$uses_spm" = "yes" ]; then
98
+ ios_known_issue_check
99
+ fi
100
+ }
101
+
102
+ # xgem doctor [ios]
103
+ cmd_doctor() {
104
+ case "${1:-}" in
105
+ ios) doctor_print_ios "${2:-.}" ;;
106
+ "") doctor_print_general ;;
107
+ *) die "Unknown doctor target '$1'. Usage: xgem doctor [ios]" ;;
108
+ esac
109
+ }
package/lib/flutter.sh ADDED
@@ -0,0 +1,207 @@
1
+ #!/bin/bash
2
+ # xgem flutter command group. iOS builds delegate to lib/ios.sh for
3
+ # SwiftPM-aware deployment-target reconciliation before ever calling
4
+ # `flutter build ios`.
5
+ #
6
+ # Depends on lib/logger.sh, lib/utils.sh, lib/ios.sh.
7
+
8
+ cmd_flutter_hard_clean() {
9
+ log_info "Cleaning Flutter project..."
10
+ rm -f pubspec.lock
11
+ flutter clean
12
+
13
+ if [ -d ios ]; then
14
+ log_info "Removing iOS ephemeral files..."
15
+ rm -rf ios/Flutter/ephemeral
16
+ (
17
+ cd ios || exit 1
18
+ if [ -f Podfile ]; then
19
+ log_info "CocoaPods detected - removing lock and Pods..."
20
+ rm -f Podfile.lock
21
+ rm -rf Pods
22
+ else
23
+ log_info "Swift Package Manager detected - resolving packages..."
24
+ xcodebuild -resolvePackageDependencies -workspace Runner.xcworkspace -scheme Runner
25
+ fi
26
+ )
27
+ else
28
+ log_info "No iOS target found, skipping iOS-specific cleanup."
29
+ fi
30
+
31
+ log_info "Getting Flutter packages..."
32
+ flutter pub get
33
+
34
+ if [ -f ios/Podfile ]; then
35
+ log_info "Installing iOS Pods..."
36
+ ( cd ios || exit 1; pod install )
37
+ fi
38
+
39
+ log_info "Running Flutter..."
40
+ flutter run -v
41
+ }
42
+
43
+ cmd_flutter_build_runner() {
44
+ log_info "Running Flutter Build Runner..."
45
+ local verbose_choice
46
+ read -r -p "Run in verbose mode? (Y/n): " verbose_choice
47
+ verbose_choice=${verbose_choice:-Y}
48
+
49
+ local -a cmd=(flutter pub run build_runner build --delete-conflicting-outputs)
50
+ if [[ "$verbose_choice" == "Y" || "$verbose_choice" == "y" ]]; then
51
+ cmd+=(-v)
52
+ fi
53
+ log_info "Executing: ${cmd[*]}"
54
+ "${cmd[@]}"
55
+ }
56
+
57
+ _flutter_pubspec_version() {
58
+ [ -f pubspec.yaml ] || return 1
59
+ grep '^version:' pubspec.yaml | head -n 1 | awk '{print $2}'
60
+ }
61
+
62
+ _flutter_build_ios() {
63
+ local build_name=$1
64
+ local build_number=$2
65
+ local mode=$3
66
+
67
+ require_cmd xcodebuild "iOS builds require Xcode's command-line tools."
68
+
69
+ log_info "Reconciling iOS deployment target against resolved SwiftPM plugins..."
70
+ if ! ios_reconcile_deployment_target "."; then
71
+ die "iOS deployment target could not be reconciled; aborting before build."
72
+ fi
73
+
74
+ log_info "Regenerating dependencies..."
75
+ flutter pub get
76
+
77
+ if ios_project_uses_pods "."; then
78
+ log_info "CocoaPods detected - installing pods..."
79
+ ( cd ios || exit 1; pod install )
80
+ elif ios_project_uses_spm "."; then
81
+ log_info "Swift Package Manager detected - resolving packages..."
82
+ xcodebuild -resolvePackageDependencies -workspace ios/Runner.xcworkspace -scheme Runner
83
+ fi
84
+
85
+ log_info "Initializing Flutter iOS Build ($mode)..."
86
+ local log_file
87
+ log_file=$(mktemp)
88
+ if ! flutter build ios "$mode" "--build-name=$build_name" "--build-number=$build_number" > "$log_file" 2>&1; then
89
+ log_error "Build FAILED"
90
+ cat "$log_file"
91
+ die "flutter build ios failed" "$?"
92
+ fi
93
+ log_success "Build complete."
94
+
95
+ log_info "Archiving for Xcode Organizer..."
96
+ local archive_date archive_time archive_dir archive_path
97
+ archive_date=$(date +%Y-%m-%d)
98
+ archive_time=$(date +%H.%M.%S)
99
+ archive_dir="$HOME/Library/Developer/Xcode/Archives/$archive_date"
100
+ mkdir -p "$archive_dir"
101
+ archive_path="$archive_dir/Runner $archive_time.xcarchive"
102
+
103
+ if ! xcodebuild -workspace ios/Runner.xcworkspace \
104
+ -scheme Runner \
105
+ -sdk iphoneos \
106
+ -configuration Release \
107
+ archive \
108
+ -archivePath "$archive_path" \
109
+ "BUILD_NUMBER=$build_number" \
110
+ "MARKETING_VERSION=$build_name" \
111
+ -allowProvisioningUpdates > "$log_file" 2>&1; then
112
+ log_error "Archive FAILED"
113
+ cat "$log_file"
114
+ die "xcodebuild archive failed" "$?"
115
+ fi
116
+ log_success "Archive created at: $archive_path"
117
+
118
+ if has_cmd osascript; then
119
+ osascript -e '
120
+ tell application "Xcode" to activate
121
+ delay 1
122
+ tell application "System Events" to tell process "Xcode"
123
+ click menu item "Organizer" of menu "Window" of menu bar 1
124
+ end tell
125
+ ' 2>/dev/null
126
+ fi
127
+ open ios/Runner.xcworkspace 2>/dev/null
128
+ }
129
+
130
+ cmd_flutter_build() {
131
+ echo -e "\033[1;34m--- Flutter Build Orchestrator ---\033[0m"
132
+
133
+ local current_name="" current_num="" current_full
134
+ if current_full=$(_flutter_pubspec_version) && [ -n "$current_full" ]; then
135
+ current_name=${current_full%%+*}
136
+ current_num=${current_full#*+}
137
+ [ "$current_name" = "$current_num" ] && current_num="1"
138
+ log_info "Current pubspec.yaml version: $current_full"
139
+ fi
140
+
141
+ local build_name build_number is_release
142
+ read -r -p "Enter build version name [${current_name:-1.0.0}]: " build_name
143
+ build_name=${build_name:-${current_name:-1.0.0}}
144
+
145
+ read -r -p "Enter build number [${current_num:-1}]: " build_number
146
+ build_number=${build_number:-${current_num:-1}}
147
+
148
+ read -r -p "Is this a release build? (Y/n) [default: y]: " is_release
149
+ is_release=${is_release:-y}
150
+
151
+ if [ -f pubspec.yaml ]; then
152
+ local new_version="${build_name}+${build_number}"
153
+ log_success "Updating pubspec.yaml to version: $new_version"
154
+ local tmp
155
+ tmp=$(mktemp)
156
+ sed -e "s/^version:.*/version: $new_version/" pubspec.yaml > "$tmp" && mv "$tmp" pubspec.yaml
157
+ fi
158
+
159
+ echo ""
160
+ echo "Select Target Platform:"
161
+ echo "1) APK (Android)"
162
+ echo "2) iOS"
163
+ echo "3) macOS"
164
+ echo "4) Windows"
165
+ echo "5) Linux"
166
+ local platform_choice
167
+ read -r -p "Choose [1-5]: " platform_choice
168
+ if ! [[ "$platform_choice" =~ ^[1-5]$ ]]; then
169
+ die "Invalid platform choice '$platform_choice'."
170
+ fi
171
+
172
+ local mode="--release"
173
+ [[ "$is_release" == "n" || "$is_release" == "N" ]] && mode="--debug"
174
+
175
+ if [ "$platform_choice" -eq 2 ]; then
176
+ _flutter_build_ios "$build_name" "$build_number" "$mode"
177
+ return
178
+ fi
179
+
180
+ log_info "Initializing Flutter Build ($mode) for version $build_name+$build_number..."
181
+ case $platform_choice in
182
+ 1) flutter build apk "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
183
+ 3) flutter build macos "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
184
+ 4) flutter build windows "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
185
+ 5) flutter build linux "$mode" "--build-name=$build_name" "--build-number=$build_number" ;;
186
+ esac
187
+ }
188
+
189
+ # xgem run flutter <script> — native dispatch, called before falling back to
190
+ # any generated .xgem-automate/flutter/<script>.sh template.
191
+ flutter_native_command_exists() {
192
+ local script=$1
193
+ case "$script" in
194
+ hard-clean|build|build-runner) return 0 ;;
195
+ *) return 1 ;;
196
+ esac
197
+ }
198
+
199
+ flutter_run_native_command() {
200
+ local script=$1
201
+ case "$script" in
202
+ hard-clean) cmd_flutter_hard_clean ;;
203
+ build) cmd_flutter_build ;;
204
+ build-runner) cmd_flutter_build_runner ;;
205
+ *) die "No native flutter command for '$script'." ;;
206
+ esac
207
+ }