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 +21 -0
- package/README.md +121 -0
- package/bin/xgem +230 -0
- package/lib/doctor.sh +109 -0
- package/lib/flutter.sh +207 -0
- package/lib/git.sh +156 -0
- package/lib/ios.sh +266 -0
- package/lib/logger.sh +28 -0
- package/lib/scaffold.sh +88 -0
- package/lib/utils.sh +61 -0
- package/lib/version.sh +8 -0
- package/package.json +33 -0
- package/templates/docker/build-up.sh.tmpl +2 -0
- package/templates/docker/hard-clean.sh.tmpl +3 -0
- package/templates/flutter/build-runner.sh.tmpl +4 -0
- package/templates/flutter/build.sh.tmpl +5 -0
- package/templates/flutter/hard-clean.sh.tmpl +5 -0
- package/templates/go/build.sh.tmpl +2 -0
- package/templates/go/hard-clean.sh.tmpl +3 -0
- package/templates/node/build.sh.tmpl +3 -0
- package/templates/node/hard-clean.sh.tmpl +3 -0
- package/templates/node/start.sh.tmpl +2 -0
- package/templates/python/hard-clean.sh.tmpl +4 -0
- package/templates/python/install.sh.tmpl +6 -0
- package/templates/rust/build.sh.tmpl +2 -0
- package/templates/rust/hard-clean.sh.tmpl +2 -0
- package/templates/swift/build.sh.tmpl +2 -0
- package/templates/swift/hard-clean.sh.tmpl +2 -0
- package/templates/webframework/build.sh.tmpl +2 -0
- package/templates/webframework/dev.sh.tmpl +2 -0
- package/templates/webframework/hard-clean.sh.tmpl +3 -0
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
|
+
}
|