xgem-cli 2.0.0-alpha.1 → 2.0.0-alpha.10
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 +64 -8
- package/bin/xgem +19 -4
- package/bin/xgem.js +220 -0
- package/lib/create.sh +257 -0
- package/lib/doctor.sh +13 -2
- package/lib/flutter.sh +161 -18
- package/lib/git.sh +106 -8
- package/lib/ios.sh +126 -47
- package/lib/scaffold.sh +4 -4
- package/lib/version.sh +1 -1
- package/lib-win/doctor.js +44 -0
- package/lib-win/flutter.js +101 -0
- package/lib-win/git.js +241 -0
- package/lib-win/logger.js +35 -0
- package/lib-win/scaffold.js +80 -0
- package/lib-win/utils.js +67 -0
- package/package.json +8 -7
- package/templates-win/docker/build-up.mjs.tmpl +3 -0
- package/templates-win/docker/hard-clean.mjs.tmpl +4 -0
- package/templates-win/flutter/build-runner.mjs.tmpl +4 -0
- package/templates-win/flutter/build.mjs.tmpl +4 -0
- package/templates-win/flutter/hard-clean.mjs.tmpl +6 -0
- package/templates-win/go/build.mjs.tmpl +3 -0
- package/templates-win/go/hard-clean.mjs.tmpl +4 -0
- package/templates-win/node/build.mjs.tmpl +4 -0
- package/templates-win/node/hard-clean.mjs.tmpl +9 -0
- package/templates-win/node/start.mjs.tmpl +3 -0
- package/templates-win/python/hard-clean.mjs.tmpl +8 -0
- package/templates-win/python/install.mjs.tmpl +12 -0
- package/templates-win/rust/build.mjs.tmpl +3 -0
- package/templates-win/rust/hard-clean.mjs.tmpl +3 -0
- package/templates-win/webframework/build.mjs.tmpl +3 -0
- package/templates-win/webframework/dev.mjs.tmpl +3 -0
- package/templates-win/webframework/hard-clean.mjs.tmpl +9 -0
- package/scripts/check-platform.js +0 -17
package/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# xgem
|
|
2
2
|
|
|
3
|
-
A framework-aware automation CLI. `xgem init`
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
A framework-aware automation CLI. `xgem init` can create a brand-new project
|
|
4
|
+
(real `npm create vite`/`create-next-app`/`create-vue`/`ng new`/`flutter create`
|
|
5
|
+
scaffolding, framework-specific questions, dependency install, and launching
|
|
6
|
+
the dev server in your browser or the app on a picked device/simulator) or
|
|
7
|
+
just scaffold clean/build/dev scripts into a project you already have
|
|
8
|
+
(Flutter, Node, Python, React/Vue/Angular/Next, Go, Rust, Docker, Swift).
|
|
9
|
+
`xgem run <framework> <script>` runs those scripts, `xgem doctor` reports on
|
|
10
|
+
your environment, and `xgem git` wraps a common stage → commit → rebase-pull
|
|
11
|
+
→ push workflow.
|
|
8
12
|
|
|
9
13
|
Flutter's iOS builds go through a dedicated engine that detects whether your
|
|
10
14
|
project uses CocoaPods or Swift Package Manager (SPM), figures out the actual
|
|
@@ -27,7 +31,8 @@ xgem isn't in Homebrew's central `homebrew-core` (that requires a formal submiss
|
|
|
27
31
|
npm install -g xgem-cli
|
|
28
32
|
```
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
Works natively on Windows too (no WSL needed) — npm's install runs a small Node.js
|
|
35
|
+
engine there instead of the bash one; see [Windows support](#windows-support).
|
|
31
36
|
|
|
32
37
|
### curl
|
|
33
38
|
|
|
@@ -48,6 +53,7 @@ xgem doctor ios [path] Report on iOS build readiness: CocoaPods vs SPM, d
|
|
|
48
53
|
targets per config, and whether they're reconciled
|
|
49
54
|
xgem git cmt "message" Auto-stage, commit, rebase-pull, and push
|
|
50
55
|
xgem git init Setup local repo, attach remote tracker shortcuts
|
|
56
|
+
xgem git branch Pick, create, or switch branches; remembers your choice
|
|
51
57
|
xgem git rm-remote Drop a configured remote
|
|
52
58
|
xgem git rm-branch Safely drop local and/or remote branch
|
|
53
59
|
xgem --version Print xgem's version
|
|
@@ -57,7 +63,36 @@ Flags (any command): --yes (skip confirmations), --dry-run (show, don't apply),
|
|
|
57
63
|
|
|
58
64
|
## Supported frameworks
|
|
59
65
|
|
|
60
|
-
flutter, node, python, react, vue, angular, go, rust, docker, swift
|
|
66
|
+
flutter, node, python, react, vue, angular, next, go, rust, docker, swift
|
|
67
|
+
|
|
68
|
+
## Creating a new project
|
|
69
|
+
|
|
70
|
+
`xgem init` asks, per framework, whether to create a brand-new project or use
|
|
71
|
+
what's already in the directory:
|
|
72
|
+
|
|
73
|
+
- **react**: Vite or Create React App ("the normal version"), then TypeScript
|
|
74
|
+
or JavaScript, then current directory or a new folder.
|
|
75
|
+
- **vue** / **next**: runs the official `create-vue`/`create-next-app` wizard
|
|
76
|
+
directly, so you get their own real prompts (router, Tailwind, App Router,
|
|
77
|
+
etc.) rather than a re-implementation of them.
|
|
78
|
+
- **angular**: `ng new` (via a local Angular CLI if you have one, `npx
|
|
79
|
+
@angular/cli` otherwise).
|
|
80
|
+
- **flutter**: `flutter create`, then lists available devices/simulators
|
|
81
|
+
(falling back to available-but-not-booted emulators you can launch) and
|
|
82
|
+
runs on whichever you pick.
|
|
83
|
+
- **node/python/go/rust/docker/swift**: the ecosystem's own minimal init
|
|
84
|
+
command (`npm init`, a `.venv`, `cargo new`, `go mod init`, a starter
|
|
85
|
+
Dockerfile, `swift package init`) — no sub-wizard, since these don't have
|
|
86
|
+
an equivalent "dev server" to launch.
|
|
87
|
+
|
|
88
|
+
For the frameworks with a dev server (react/vue/angular/next), after
|
|
89
|
+
dependencies install xgem offers to start it and open your default browser —
|
|
90
|
+
it tails the server's own log for the first `http://localhost:PORT` it
|
|
91
|
+
prints rather than guessing a framework's default port, so it works even if
|
|
92
|
+
that port's already taken and a different one gets picked.
|
|
93
|
+
|
|
94
|
+
Answering "existing" (the default) skips all of this and behaves exactly
|
|
95
|
+
like before — just scaffolds `.xgem-automate/` into the current directory.
|
|
61
96
|
|
|
62
97
|
## Architecture
|
|
63
98
|
|
|
@@ -69,8 +104,9 @@ lib/
|
|
|
69
104
|
doctor.sh environment + feature detection ("xgem doctor")
|
|
70
105
|
ios.sh the SwiftPM-aware iOS build engine
|
|
71
106
|
flutter.sh flutter command group (build/hard-clean/build-runner), delegates iOS builds to ios.sh
|
|
72
|
-
git.sh git cmt/init/rm-remote/rm-branch
|
|
107
|
+
git.sh git cmt/init/branch/rm-remote/rm-branch
|
|
73
108
|
scaffold.sh generic clean/build/dev handling for the other, simpler frameworks
|
|
109
|
+
create.sh project creation wizards (react/vue/angular/next/simple frameworks)
|
|
74
110
|
templates/ the actual clean/build/dev script content xgem scaffolds into your project's .xgem-automate/
|
|
75
111
|
```
|
|
76
112
|
|
|
@@ -106,6 +142,24 @@ force Flutter to regenerate the generated package.
|
|
|
106
142
|
|
|
107
143
|
Run `xgem doctor ios` any time for a standalone readiness report without doing a build.
|
|
108
144
|
|
|
145
|
+
## Windows support
|
|
146
|
+
|
|
147
|
+
Homebrew and the curl installer are macOS/Linux only, same as any bash tool — that's
|
|
148
|
+
not going to change. npm is different: it's published with a small native Windows
|
|
149
|
+
engine (`lib-win/`, driven by `bin/xgem.js`) so `npm install -g xgem-cli` actually works
|
|
150
|
+
in `cmd.exe`/PowerShell, no WSL required.
|
|
151
|
+
|
|
152
|
+
What works on Windows: `init`/`add`/`run`/`terminate`, `doctor`, the git workflow
|
|
153
|
+
commands, and Flutter builds for **APK, App Bundle, and Windows desktop** targets. What doesn't:
|
|
154
|
+
Flutter **iOS/macOS** builds — Xcode has no Windows equivalent, so `xgem doctor ios`
|
|
155
|
+
explains that plainly instead of pretending. `swift` isn't offered as a framework
|
|
156
|
+
choice on Windows for the same reason. If you're inside WSL, none of this applies —
|
|
157
|
+
WSL reports itself as Linux, so you get the full bash engine automatically.
|
|
158
|
+
|
|
159
|
+
On macOS/Linux, npm installs run the exact same bash engine as Homebrew/curl (`bin/xgem.js`
|
|
160
|
+
is a thin passthrough that execs the real `bin/xgem` script) — there's only one
|
|
161
|
+
implementation to trust on POSIX; Windows is the only platform with a second one.
|
|
162
|
+
|
|
109
163
|
## Roadmap
|
|
110
164
|
|
|
111
165
|
Deferred to a follow-up pass, not yet in this release:
|
|
@@ -114,6 +168,8 @@ Deferred to a follow-up pass, not yet in this release:
|
|
|
114
168
|
- Shell completions, `--json` output, per-project config caching (stop re-prompting
|
|
115
169
|
build name/number/platform every run)
|
|
116
170
|
- Deeper Android SDK/Java detection
|
|
171
|
+
- Project creation wizards on the Windows engine (`lib-win/`) — react/vue/angular/next
|
|
172
|
+
scaffolding is pure Node.js tooling and would work fine there too, just not built yet
|
|
117
173
|
|
|
118
174
|
## License
|
|
119
175
|
|
package/bin/xgem
CHANGED
|
@@ -33,6 +33,8 @@ source "$XGEM_HOME/lib/ios.sh"
|
|
|
33
33
|
source "$XGEM_HOME/lib/flutter.sh"
|
|
34
34
|
# shellcheck source=lib/scaffold.sh
|
|
35
35
|
source "$XGEM_HOME/lib/scaffold.sh"
|
|
36
|
+
# shellcheck source=lib/create.sh
|
|
37
|
+
source "$XGEM_HOME/lib/create.sh"
|
|
36
38
|
|
|
37
39
|
CONFIG_DIR=".xgem-automate"
|
|
38
40
|
|
|
@@ -70,6 +72,7 @@ print_usage() {
|
|
|
70
72
|
echo " xgem doctor [ios] - Report on your environment / iOS SwiftPM build readiness"
|
|
71
73
|
echo " xgem git cmt \"message\" - Auto-stage, commit, rebase-pull, and push"
|
|
72
74
|
echo " xgem git init - Setup local repo, attach remote tracker shortcuts"
|
|
75
|
+
echo " xgem git branch - Pick, create, or switch branches; remembers your choice"
|
|
73
76
|
echo " xgem git rm-remote - Drop specified target remote tracing rules"
|
|
74
77
|
echo " xgem git rm-branch - Safely drop local and remote workspace branch states"
|
|
75
78
|
echo " xgem --version - Print xgem's version"
|
|
@@ -84,12 +87,12 @@ case "${1:-}" in
|
|
|
84
87
|
log_warn "Workspace configuration layer already exists! Use 'xgem add' to append frameworks."
|
|
85
88
|
exit 0
|
|
86
89
|
fi
|
|
87
|
-
mkdir -p "$CONFIG_DIR"
|
|
88
|
-
|
|
89
90
|
echo -e "\033[1;34mSelect your starting framework architecture:\033[0m"
|
|
90
91
|
PS3="Enter target selection number: "
|
|
91
92
|
select fw in "${ALL_FRAMEWORKS[@]}"; do
|
|
92
93
|
if [ -n "$fw" ]; then
|
|
94
|
+
create_project_wizard "$fw"
|
|
95
|
+
mkdir -p "$CONFIG_DIR"
|
|
93
96
|
scaffold_inject_templates "$fw" "$CONFIG_DIR"
|
|
94
97
|
log_success "Successfully appended standard scripts for: $CONFIG_DIR/$fw"
|
|
95
98
|
break
|
|
@@ -107,6 +110,18 @@ case "${1:-}" in
|
|
|
107
110
|
echo "$CONFIG_DIR/" > .gitignore
|
|
108
111
|
log_success "Created .gitignore and hidden tracking layer folder references."
|
|
109
112
|
fi
|
|
113
|
+
|
|
114
|
+
_ask_launch_dev_server
|
|
115
|
+
|
|
116
|
+
# A child process (this script) can never change the parent
|
|
117
|
+
# shell's cwd — xgem's own process correctly cd'd into the new
|
|
118
|
+
# project folder for everything above, but your actual terminal
|
|
119
|
+
# is still wherever it started. Say so explicitly instead of
|
|
120
|
+
# leaving you to discover it the confusing way.
|
|
121
|
+
if [ -n "$XGEM_CREATED_NEW_FOLDER" ]; then
|
|
122
|
+
echo ""
|
|
123
|
+
log_info "Your project is in ./$XGEM_CREATED_NEW_FOLDER — your terminal is still in the original directory. Run: cd $XGEM_CREATED_NEW_FOLDER"
|
|
124
|
+
fi
|
|
110
125
|
exit 0
|
|
111
126
|
;;
|
|
112
127
|
|
|
@@ -182,7 +197,7 @@ case "${1:-}" in
|
|
|
182
197
|
;;
|
|
183
198
|
|
|
184
199
|
git)
|
|
185
|
-
[ -n "${2:-}" ] || die "Usage: xgem git <cmt|init|rm-remote|rm-branch>"
|
|
200
|
+
[ -n "${2:-}" ] || die "Usage: xgem git <cmt|init|branch|rm-remote|rm-branch>"
|
|
186
201
|
cmd_git "$2" "${3:-}"
|
|
187
202
|
exit 0
|
|
188
203
|
;;
|
|
@@ -197,7 +212,7 @@ case "${1:-}" in
|
|
|
197
212
|
exit 0
|
|
198
213
|
;;
|
|
199
214
|
|
|
200
|
-
flutter|node|python|react|vue|angular|go|rust|docker|swift)
|
|
215
|
+
flutter|node|python|react|vue|angular|next|go|rust|docker|swift)
|
|
201
216
|
TARGET_FW=$1
|
|
202
217
|
TARGET_FW_UPPER=$(echo "$TARGET_FW" | tr '[:lower:]' '[:upper:]')
|
|
203
218
|
|
package/bin/xgem.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// xgem npm entrypoint. On macOS/Linux this is a thin passthrough to the
|
|
3
|
+
// real, already-tested bash bin/xgem sitting next to it — zero logic
|
|
4
|
+
// duplication, zero behavioral change for POSIX users. On Windows it runs
|
|
5
|
+
// the native lib-win/*.js engine, since bash doesn't exist there.
|
|
6
|
+
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const { spawnSync } = require('node:child_process');
|
|
9
|
+
|
|
10
|
+
if (process.platform !== 'win32') {
|
|
11
|
+
const bashScript = path.join(__dirname, 'xgem');
|
|
12
|
+
const result = spawnSync(bashScript, process.argv.slice(2), { stdio: 'inherit' });
|
|
13
|
+
process.exit(result.status ?? 0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ---- Windows-native engine ----
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const { logInfo, logSuccess, logWarn, logError, die, paint } = require('../lib-win/logger');
|
|
19
|
+
const { prompt } = require('../lib-win/utils');
|
|
20
|
+
const { cmdDoctor } = require('../lib-win/doctor');
|
|
21
|
+
const { cmdGit } = require('../lib-win/git');
|
|
22
|
+
const { cmdFlutter } = require('../lib-win/flutter');
|
|
23
|
+
const scaffold = require('../lib-win/scaffold');
|
|
24
|
+
|
|
25
|
+
const CONFIG_DIR = '.xgem-automate';
|
|
26
|
+
|
|
27
|
+
function printBanner() {
|
|
28
|
+
console.log(paint('yellow', ''));
|
|
29
|
+
console.log('██╗ ██╗ ██████╗ ███████╗███╗ ███╗██╗███╗ ██╗██╗');
|
|
30
|
+
console.log('╚██╗██╔╝ ██╔════╝ ██╔════╝████╗ ████║██║████╗ ██║██║');
|
|
31
|
+
console.log(' ╚███╔╝ █████╗██║ ███╗█████╗ ██╔████╔██║██║██╔██╗ ██║██║');
|
|
32
|
+
console.log(' ██╔██╗ ╚════╝██║ ██║██╔══╝ ██║╚██╔╝██║██║██║╚██╗██║██║');
|
|
33
|
+
console.log('██╔╝ ██╗ ╚██████╔╝███████╗██║ ╚═╝ ██║██║██║ ╚████║██║');
|
|
34
|
+
console.log('╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝');
|
|
35
|
+
console.log(paint('cyan', ' ....Windows engine....\n'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function printUsage() {
|
|
39
|
+
console.log('Usage:');
|
|
40
|
+
console.log(' xgem init - Initialize tracking system and add first framework');
|
|
41
|
+
console.log(' xgem add - Interactive state-aware addition of remaining frameworks');
|
|
42
|
+
console.log(' xgem run <framework> <script> - Run a workspace automation command script');
|
|
43
|
+
console.log(' xgem terminate - Purge all generated automated layouts completely');
|
|
44
|
+
console.log(' xgem <framework> [help] - View tailored instructions for a specific script layout');
|
|
45
|
+
console.log(' xgem doctor [ios] - Report on your environment / iOS build availability');
|
|
46
|
+
console.log(' xgem git cmt "message" - Auto-stage, commit, rebase-pull, and push');
|
|
47
|
+
console.log(' xgem git init - Setup local repo, attach remote tracker shortcuts');
|
|
48
|
+
console.log(' xgem git branch - Pick, create, or switch branches; remembers your choice');
|
|
49
|
+
console.log(' xgem git rm-remote - Drop specified target remote tracing rules');
|
|
50
|
+
console.log(' xgem git rm-branch - Safely drop local and remote workspace branch states');
|
|
51
|
+
console.log(' xgem --version - Print xgem\'s version');
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log('Flags (any command): --yes (skip confirmations), --dry-run (show, don\'t apply), --verbose');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function selectFramework(options) {
|
|
57
|
+
options.forEach((fw, i) => console.log(`${i + 1}) ${fw}`));
|
|
58
|
+
const choice = await prompt('Enter target selection number');
|
|
59
|
+
const idx = parseInt(choice, 10) - 1;
|
|
60
|
+
if (Number.isNaN(idx) || idx < 0 || idx >= options.length) return null;
|
|
61
|
+
return options[idx];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function updateGitignore() {
|
|
65
|
+
const gitignorePath = '.gitignore';
|
|
66
|
+
if (fs.existsSync(gitignorePath)) {
|
|
67
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
68
|
+
if (!content.includes(`${CONFIG_DIR}/`)) {
|
|
69
|
+
fs.appendFileSync(gitignorePath, `\n${CONFIG_DIR}/\n`);
|
|
70
|
+
logSuccess('Added automation tracking to .gitignore');
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
fs.writeFileSync(gitignorePath, `${CONFIG_DIR}/\n`);
|
|
74
|
+
logSuccess('Created .gitignore and hidden tracking layer folder references.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function cmdInit() {
|
|
79
|
+
printBanner();
|
|
80
|
+
if (fs.existsSync(CONFIG_DIR)) {
|
|
81
|
+
logWarn("Workspace configuration layer already exists! Use 'xgem add' to append frameworks.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
85
|
+
|
|
86
|
+
console.log('Select your starting framework architecture:');
|
|
87
|
+
const fw = await selectFramework(scaffold.ALL_FRAMEWORKS);
|
|
88
|
+
if (!fw) die('Invalid selection.');
|
|
89
|
+
scaffold.injectTemplates(fw, CONFIG_DIR);
|
|
90
|
+
logSuccess(`Successfully appended standard scripts for: ${CONFIG_DIR}/${fw}`);
|
|
91
|
+
|
|
92
|
+
updateGitignore();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function cmdAdd() {
|
|
96
|
+
if (!fs.existsSync(CONFIG_DIR)) die("System tracking layer not found. Run 'xgem init' first.");
|
|
97
|
+
|
|
98
|
+
const remaining = scaffold.getRemainingFrameworks(CONFIG_DIR);
|
|
99
|
+
if (remaining.length === 0) {
|
|
100
|
+
logWarn('All available framework blocks are already generated inside the workspace!');
|
|
101
|
+
const restore = (await prompt('Do you want to restore/regenerate all framework templates to defaults? (y/n)')).toLowerCase();
|
|
102
|
+
if (restore === 'y') {
|
|
103
|
+
for (const fw of scaffold.ALL_FRAMEWORKS) scaffold.injectTemplates(fw, CONFIG_DIR);
|
|
104
|
+
logSuccess('All framework automation layouts cleanly re-generated!');
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log('Select a framework script block to add:');
|
|
110
|
+
const fw = await selectFramework(remaining);
|
|
111
|
+
if (!fw) { console.log('Operation canceled.'); return; }
|
|
112
|
+
scaffold.injectTemplates(fw, CONFIG_DIR);
|
|
113
|
+
logSuccess(`Successfully loaded and updated files for: ${CONFIG_DIR}/${fw}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function cmdRun(fw, script) {
|
|
117
|
+
if (!fw || !script) die('Usage: xgem run <framework> <script>');
|
|
118
|
+
if (fw === 'flutter' && ['hard-clean', 'build', 'build-runner'].includes(script)) {
|
|
119
|
+
await cmdFlutter(script);
|
|
120
|
+
} else {
|
|
121
|
+
scaffold.runScript(fw, script, CONFIG_DIR);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function cmdTerminate() {
|
|
126
|
+
if (!fs.existsSync(CONFIG_DIR)) die(`Tracking layer ${CONFIG_DIR} not found. Nothing to terminate.`);
|
|
127
|
+
|
|
128
|
+
logWarn(`Initiating core termination sequence for ${CONFIG_DIR}...`);
|
|
129
|
+
const folders = fs.readdirSync(CONFIG_DIR, { withFileTypes: true })
|
|
130
|
+
.filter((d) => d.isDirectory())
|
|
131
|
+
.map((d) => d.name);
|
|
132
|
+
|
|
133
|
+
const { confirm } = require('../lib-win/utils');
|
|
134
|
+
const ok = await confirm(`This deletes ${CONFIG_DIR} and everything in it. Continue?`);
|
|
135
|
+
if (!ok) { logInfo('Cancelled.'); return; }
|
|
136
|
+
|
|
137
|
+
for (const fw of folders) {
|
|
138
|
+
console.log(` [ x ] Removing ${fw}...`);
|
|
139
|
+
fs.rmSync(path.join(CONFIG_DIR, fw), { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
fs.rmSync(CONFIG_DIR, { recursive: true, force: true });
|
|
142
|
+
|
|
143
|
+
if (fs.existsSync('.gitignore')) {
|
|
144
|
+
const lines = fs.readFileSync('.gitignore', 'utf8').split(/\r?\n/).filter((l) => l !== `${CONFIG_DIR}/`);
|
|
145
|
+
fs.writeFileSync('.gitignore', lines.join('\n'));
|
|
146
|
+
logSuccess('Cleaned up .gitignore tracking records.');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
logSuccess(`Termination complete. Total blocks destroyed: ${folders.length} (${folders.join(', ') || 'none'})`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function printFrameworkHelp(fw) {
|
|
153
|
+
console.log(paint('cyan', `=== ${fw.toUpperCase()} Automation Blueprint (Windows) ===`));
|
|
154
|
+
if (!fs.existsSync(path.join(CONFIG_DIR, fw))) {
|
|
155
|
+
logWarn('Status: Not currently injected in this workspace.');
|
|
156
|
+
console.log("Run 'xgem add' to instantly append its structural scripts.\n");
|
|
157
|
+
} else {
|
|
158
|
+
logSuccess(`Status: Active & Mounted inside ${CONFIG_DIR}/${fw}/`);
|
|
159
|
+
console.log('');
|
|
160
|
+
}
|
|
161
|
+
console.log(`To execute scripts, use: xgem run ${fw} <script>\n`);
|
|
162
|
+
console.log('Configured Commands:');
|
|
163
|
+
for (const script of scaffold.frameworkScripts(fw) || []) {
|
|
164
|
+
console.log(` ${script}`);
|
|
165
|
+
}
|
|
166
|
+
if (fw === 'flutter') {
|
|
167
|
+
console.log('');
|
|
168
|
+
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.");
|
|
169
|
+
}
|
|
170
|
+
console.log('');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function main() {
|
|
174
|
+
const rawArgs = process.argv.slice(2);
|
|
175
|
+
const args = [];
|
|
176
|
+
for (const arg of rawArgs) {
|
|
177
|
+
if (arg === '--yes') process.env.XGEM_YES = '1';
|
|
178
|
+
else if (arg === '--dry-run') process.env.XGEM_DRY_RUN = '1';
|
|
179
|
+
else if (arg === '--verbose') process.env.XGEM_VERBOSE = '1';
|
|
180
|
+
else args.push(arg);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const [cmd, a2, a3] = args;
|
|
184
|
+
|
|
185
|
+
switch (cmd) {
|
|
186
|
+
case 'init': return cmdInit();
|
|
187
|
+
case 'add': return cmdAdd();
|
|
188
|
+
case 'run': return cmdRun(a2, a3);
|
|
189
|
+
case 'terminate': return cmdTerminate();
|
|
190
|
+
case 'git':
|
|
191
|
+
if (!a2) die('Usage: xgem git <cmt|init|branch|rm-remote|rm-branch>');
|
|
192
|
+
return cmdGit(a2, a3);
|
|
193
|
+
case 'doctor': return cmdDoctor(a2);
|
|
194
|
+
case '--version':
|
|
195
|
+
case '-V':
|
|
196
|
+
case 'version': {
|
|
197
|
+
const pkg = require('../package.json');
|
|
198
|
+
console.log(`xgem ${pkg.version} (Windows engine)`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
case 'flutter':
|
|
202
|
+
case 'node':
|
|
203
|
+
case 'python':
|
|
204
|
+
case 'react':
|
|
205
|
+
case 'vue':
|
|
206
|
+
case 'angular':
|
|
207
|
+
case 'next':
|
|
208
|
+
case 'go':
|
|
209
|
+
case 'rust':
|
|
210
|
+
case 'docker':
|
|
211
|
+
return printFrameworkHelp(cmd);
|
|
212
|
+
default:
|
|
213
|
+
return printUsage();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
main().catch((err) => {
|
|
218
|
+
logError(err.message || String(err));
|
|
219
|
+
process.exit(1);
|
|
220
|
+
});
|
package/lib/create.sh
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# xgem project creation wizards — runs from `xgem init`, before
|
|
3
|
+
# scaffold_inject_templates, for frameworks with a real "ask sub-choice ->
|
|
4
|
+
# scaffold -> install -> launch" story worth building (react/vue/angular/
|
|
5
|
+
# next/flutter). The rest (node/python/go/rust/docker/swift) get a lighter
|
|
6
|
+
# "create with the ecosystem's own standard init command" step.
|
|
7
|
+
#
|
|
8
|
+
# Depends on lib/logger.sh, lib/utils.sh. Uses `cd` directly (no
|
|
9
|
+
# subshells) so a newly created project directory persists as the cwd for
|
|
10
|
+
# the rest of `xgem init` (template injection, .gitignore) without the
|
|
11
|
+
# caller needing to track/re-cd into a returned path.
|
|
12
|
+
#
|
|
13
|
+
# Dev-server launch is deliberately deferred: creators only set
|
|
14
|
+
# XGEM_POST_INIT_LAUNCH_CMD (an array) rather than launching immediately,
|
|
15
|
+
# so `xgem init` finishes template injection and .gitignore setup *before*
|
|
16
|
+
# handing the terminal over to a long-running dev server.
|
|
17
|
+
XGEM_POST_INIT_LAUNCH_CMD=()
|
|
18
|
+
|
|
19
|
+
# Set by creators when a NEW folder was created (not "current directory").
|
|
20
|
+
# A child process can never change its parent shell's cwd, so once `xgem
|
|
21
|
+
# init` exits, the user's actual terminal is back wherever it started —
|
|
22
|
+
# xgem's own process is correctly cd'd into the new folder for the rest of
|
|
23
|
+
# ITS run (template injection, dev server), but that never propagates back.
|
|
24
|
+
# bin/xgem prints a "cd <name>" reminder using this at the very end.
|
|
25
|
+
XGEM_CREATED_NEW_FOLDER=""
|
|
26
|
+
|
|
27
|
+
# _prompt_project_location <human label> -> echoes "." or a new folder name.
|
|
28
|
+
# Pure prompt-and-echo, no other stdout output, so it's safe to capture via
|
|
29
|
+
# command substitution.
|
|
30
|
+
_prompt_project_location() {
|
|
31
|
+
local label=$1
|
|
32
|
+
local choice
|
|
33
|
+
read -r -p "Initialize $label in the current directory, or create a new folder? [current/new] (default: new): " choice
|
|
34
|
+
choice=${choice:-new}
|
|
35
|
+
if [[ "$choice" == c* || "$choice" == C* ]]; then
|
|
36
|
+
echo "."
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
local name
|
|
40
|
+
read -r -p "Project name: " name
|
|
41
|
+
[ -n "$name" ] || die "Project name cannot be empty."
|
|
42
|
+
echo "$name"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_open_url() {
|
|
46
|
+
local url=$1
|
|
47
|
+
case "$(detect_os)" in
|
|
48
|
+
darwin) open "$url" 2>/dev/null ;;
|
|
49
|
+
linux) xdg-open "$url" 2>/dev/null ;;
|
|
50
|
+
*) log_debug "Don't know how to open a browser on this OS — visit $url manually." ;;
|
|
51
|
+
esac
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# _launch_dev_server_and_open_browser <cmd...>
|
|
55
|
+
# Starts the dev server in the background, tails its log for the first
|
|
56
|
+
# http://localhost URL it prints — this works across Vite/CRA/Next/Angular
|
|
57
|
+
# without hardcoding any one framework's default port (which can differ or
|
|
58
|
+
# be taken already) — opens it, then waits so Ctrl-C stops the server
|
|
59
|
+
# normally instead of leaving it orphaned in the background.
|
|
60
|
+
_launch_dev_server_and_open_browser() {
|
|
61
|
+
local log_file
|
|
62
|
+
log_file=$(mktemp)
|
|
63
|
+
log_info "Starting dev server: $*"
|
|
64
|
+
"$@" > "$log_file" 2>&1 &
|
|
65
|
+
local server_pid=$!
|
|
66
|
+
|
|
67
|
+
local url="" waited=0
|
|
68
|
+
while [ -z "$url" ] && [ "$waited" -lt 30 ] && kill -0 "$server_pid" 2>/dev/null; do
|
|
69
|
+
url=$(grep -oE 'https?://(localhost|127\.0\.0\.1)[:0-9]*[^[:space:]]*' "$log_file" 2>/dev/null | head -1)
|
|
70
|
+
if [ -z "$url" ]; then
|
|
71
|
+
sleep 1
|
|
72
|
+
waited=$((waited + 1))
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
if [ -n "$url" ]; then
|
|
77
|
+
log_success "Dev server up at $url"
|
|
78
|
+
_open_url "$url"
|
|
79
|
+
elif kill -0 "$server_pid" 2>/dev/null; then
|
|
80
|
+
log_warn "Dev server is running (pid $server_pid) but its URL wasn't detected automatically after ${waited}s — check the output below."
|
|
81
|
+
else
|
|
82
|
+
log_error "Dev server exited early."
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
cat "$log_file" &
|
|
86
|
+
wait "$server_pid" 2>/dev/null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_ask_launch_dev_server() {
|
|
90
|
+
[ ${#XGEM_POST_INIT_LAUNCH_CMD[@]} -gt 0 ] || return 0
|
|
91
|
+
|
|
92
|
+
# `flutter run` isn't a web dev server — it has no localhost URL to
|
|
93
|
+
# detect/open, and it depends on a fully-inherited interactive stdin
|
|
94
|
+
# for its own hot-reload keybindings (r/R/q/etc.), which the
|
|
95
|
+
# background+log-tail approach below would break. Run it directly.
|
|
96
|
+
# (Whether to run at all was already confirmed in
|
|
97
|
+
# _flutter_offer_run_on_device before this command was ever set, so
|
|
98
|
+
# this doesn't ask again.)
|
|
99
|
+
if [ "${XGEM_POST_INIT_LAUNCH_CMD[0]}" = "flutter" ]; then
|
|
100
|
+
"${XGEM_POST_INIT_LAUNCH_CMD[@]}"
|
|
101
|
+
return 0
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
local answer
|
|
105
|
+
read -r -p "Launch the dev server now and open it in your browser? (Y/n): " answer
|
|
106
|
+
answer=${answer:-y}
|
|
107
|
+
[[ "$answer" == "y" || "$answer" == "Y" ]] && _launch_dev_server_and_open_browser "${XGEM_POST_INIT_LAUNCH_CMD[@]}"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_create_react() {
|
|
111
|
+
local variant lang project_dir
|
|
112
|
+
read -r -p "Use Vite or Create React App (the 'normal' version)? [vite/cra] (default: vite): " variant
|
|
113
|
+
variant=${variant:-vite}
|
|
114
|
+
read -r -p "TypeScript or JavaScript? [ts/js] (default: ts): " lang
|
|
115
|
+
lang=${lang:-ts}
|
|
116
|
+
project_dir=$(_prompt_project_location "React")
|
|
117
|
+
[ "$project_dir" != "." ] && XGEM_CREATED_NEW_FOLDER="$project_dir"
|
|
118
|
+
|
|
119
|
+
if [[ "$variant" == cra* || "$variant" == CRA* ]]; then
|
|
120
|
+
require_cmd npx "Install Node.js (npm ships with it): https://nodejs.org"
|
|
121
|
+
local -a args=(create-react-app)
|
|
122
|
+
[ "$project_dir" != "." ] && args+=("$project_dir") || args+=(.)
|
|
123
|
+
[ "$lang" = "ts" ] && args+=(--template typescript)
|
|
124
|
+
npx "${args[@]}" || die "create-react-app failed."
|
|
125
|
+
else
|
|
126
|
+
require_cmd npm "Install Node.js (npm ships with it): https://nodejs.org"
|
|
127
|
+
local template="react"
|
|
128
|
+
[ "$lang" = "ts" ] && template="react-ts"
|
|
129
|
+
# --no-immediate: create-vite's own "install deps and start dev
|
|
130
|
+
# server" prompt would otherwise block here and, if Ctrl-C'd, kill
|
|
131
|
+
# this whole xgem process before it ever reaches template
|
|
132
|
+
# injection / .gitignore setup below. xgem handles install/launch
|
|
133
|
+
# itself instead, after everything else is done.
|
|
134
|
+
npm create vite@latest "$project_dir" -- --template "$template" --no-immediate || die "npm create vite failed."
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
[ "$project_dir" != "." ] && { cd "$project_dir" || die "Could not enter $project_dir"; }
|
|
138
|
+
log_info "Installing dependencies..."
|
|
139
|
+
npm install
|
|
140
|
+
XGEM_POST_INIT_LAUNCH_CMD=(npm run dev)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_create_vue() {
|
|
144
|
+
require_cmd npm "Install Node.js (npm ships with it): https://nodejs.org"
|
|
145
|
+
local project_dir
|
|
146
|
+
project_dir=$(_prompt_project_location "Vue")
|
|
147
|
+
[ "$project_dir" != "." ] && XGEM_CREATED_NEW_FOLDER="$project_dir"
|
|
148
|
+
npm create vue@latest "$project_dir" || die "npm create vue failed."
|
|
149
|
+
[ "$project_dir" != "." ] && { cd "$project_dir" || die "Could not enter $project_dir"; }
|
|
150
|
+
log_info "Installing dependencies..."
|
|
151
|
+
npm install
|
|
152
|
+
XGEM_POST_INIT_LAUNCH_CMD=(npm run dev)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_create_angular() {
|
|
156
|
+
local project_dir
|
|
157
|
+
project_dir=$(_prompt_project_location "Angular")
|
|
158
|
+
[ "$project_dir" != "." ] && XGEM_CREATED_NEW_FOLDER="$project_dir"
|
|
159
|
+
local -a ng_cmd=(ng)
|
|
160
|
+
has_cmd ng || ng_cmd=(npx @angular/cli@latest)
|
|
161
|
+
|
|
162
|
+
if [ "$project_dir" = "." ]; then
|
|
163
|
+
"${ng_cmd[@]}" new "$(basename "$PWD")" --directory=. || die "ng new failed."
|
|
164
|
+
else
|
|
165
|
+
"${ng_cmd[@]}" new "$project_dir" || die "ng new failed."
|
|
166
|
+
cd "$project_dir" || die "Could not enter $project_dir"
|
|
167
|
+
fi
|
|
168
|
+
XGEM_POST_INIT_LAUNCH_CMD=(npm run start)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
_create_next() {
|
|
172
|
+
require_cmd npx "Install Node.js (npm ships with it): https://nodejs.org"
|
|
173
|
+
local project_dir
|
|
174
|
+
project_dir=$(_prompt_project_location "Next.js")
|
|
175
|
+
[ "$project_dir" != "." ] && XGEM_CREATED_NEW_FOLDER="$project_dir"
|
|
176
|
+
npx create-next-app@latest "$project_dir" || die "create-next-app failed."
|
|
177
|
+
[ "$project_dir" != "." ] && { cd "$project_dir" || die "Could not enter $project_dir"; }
|
|
178
|
+
XGEM_POST_INIT_LAUNCH_CMD=(npm run dev)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# Lighter path for frameworks without a dev-server/browser story — just the
|
|
182
|
+
# ecosystem's own standard init command, no sub-wizard.
|
|
183
|
+
_create_simple() {
|
|
184
|
+
local fw=$1
|
|
185
|
+
local project_dir
|
|
186
|
+
project_dir=$(_prompt_project_location "$fw")
|
|
187
|
+
[ "$project_dir" != "." ] && XGEM_CREATED_NEW_FOLDER="$project_dir"
|
|
188
|
+
[ "$project_dir" != "." ] && { mkdir -p "$project_dir" && cd "$project_dir" || die "Could not enter $project_dir"; }
|
|
189
|
+
|
|
190
|
+
case "$fw" in
|
|
191
|
+
node)
|
|
192
|
+
require_cmd npm "Install Node.js (npm ships with it): https://nodejs.org"
|
|
193
|
+
npm init -y
|
|
194
|
+
;;
|
|
195
|
+
python)
|
|
196
|
+
require_cmd python3 "Install Python 3: https://www.python.org/downloads/"
|
|
197
|
+
python3 -m venv .venv
|
|
198
|
+
log_success "Created .venv — activate with 'source .venv/bin/activate'."
|
|
199
|
+
;;
|
|
200
|
+
go)
|
|
201
|
+
require_cmd go "Install Go: https://go.dev/doc/install"
|
|
202
|
+
local module_name
|
|
203
|
+
read -r -p "Module name (e.g. github.com/you/project): " module_name
|
|
204
|
+
[ -n "$module_name" ] || module_name=$(basename "$PWD")
|
|
205
|
+
go mod init "$module_name"
|
|
206
|
+
;;
|
|
207
|
+
rust)
|
|
208
|
+
require_cmd cargo "Install Rust: https://www.rust-lang.org/tools/install"
|
|
209
|
+
if [ "$project_dir" != "." ]; then
|
|
210
|
+
cd .. || return 1
|
|
211
|
+
cargo new "$project_dir" || die "cargo new failed."
|
|
212
|
+
cd "$project_dir" || die "Could not enter $project_dir"
|
|
213
|
+
else
|
|
214
|
+
cargo init || die "cargo init failed."
|
|
215
|
+
fi
|
|
216
|
+
;;
|
|
217
|
+
swift)
|
|
218
|
+
require_cmd swift "Install Swift: https://www.swift.org/install/"
|
|
219
|
+
swift package init --type executable
|
|
220
|
+
;;
|
|
221
|
+
docker)
|
|
222
|
+
if [ ! -f Dockerfile ]; then
|
|
223
|
+
cat > Dockerfile << 'EOF'
|
|
224
|
+
FROM alpine:latest
|
|
225
|
+
WORKDIR /app
|
|
226
|
+
COPY . .
|
|
227
|
+
CMD ["sh"]
|
|
228
|
+
EOF
|
|
229
|
+
log_success "Created a starter Dockerfile — edit it for your actual base image/entrypoint."
|
|
230
|
+
else
|
|
231
|
+
log_warn "Dockerfile already exists, leaving it as-is."
|
|
232
|
+
fi
|
|
233
|
+
;;
|
|
234
|
+
esac
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# create_project_wizard <framework>
|
|
238
|
+
# Asks whether to create a new project or use what's already here; if
|
|
239
|
+
# creating, dispatches to the right per-framework creator. Leaves the
|
|
240
|
+
# process cwd inside the (possibly new) project directory either way.
|
|
241
|
+
create_project_wizard() {
|
|
242
|
+
local fw=$1
|
|
243
|
+
local create_choice
|
|
244
|
+
read -r -p "Create a brand-new $fw project, or use what's already in this directory? [new/existing] (default: existing): " create_choice
|
|
245
|
+
create_choice=${create_choice:-existing}
|
|
246
|
+
[[ "$create_choice" == n* || "$create_choice" == N* ]] || return 0
|
|
247
|
+
|
|
248
|
+
case "$fw" in
|
|
249
|
+
react) _create_react ;;
|
|
250
|
+
vue) _create_vue ;;
|
|
251
|
+
angular) _create_angular ;;
|
|
252
|
+
next) _create_next ;;
|
|
253
|
+
flutter) create_flutter_project ;;
|
|
254
|
+
node|python|go|rust|docker|swift) _create_simple "$fw" ;;
|
|
255
|
+
*) log_debug "No creation wizard for '$fw'; using current directory as-is." ;;
|
|
256
|
+
esac
|
|
257
|
+
}
|