xgem-cli 2.0.0-alpha.10 → 2.0.0-alpha.12
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 +29 -63
- package/bin/xgem +5 -13
- package/bin/xgem.js +17 -2
- package/lib/create.sh +141 -64
- package/lib/flutter.sh +10 -8
- package/lib/git.sh +51 -15
- package/lib/scaffold.sh +21 -2
- package/lib/version.sh +1 -1
- package/lib-win/git.js +55 -17
- package/lib-win/scaffold.js +23 -1
- package/package.json +1 -1
- package/templates/node/build.sh.tmpl +9 -1
- package/templates/node/hard-clean.sh.tmpl +34 -2
- package/templates/node/lint.sh.tmpl +10 -0
- package/templates/node/start.sh.tmpl +9 -1
- package/templates/node/test.sh.tmpl +10 -0
- package/templates/webframework/build.sh.tmpl +10 -1
- package/templates/webframework/dev.sh.tmpl +9 -1
- package/templates/webframework/hard-clean.sh.tmpl +35 -2
- package/templates/webframework/lint.sh.tmpl +10 -0
- package/templates/webframework/test.sh.tmpl +10 -0
- package/templates-win/node/build.mjs.tmpl +8 -2
- package/templates-win/node/hard-clean.mjs.tmpl +24 -5
- package/templates-win/node/lint.mjs.tmpl +10 -0
- package/templates-win/node/start.mjs.tmpl +8 -1
- package/templates-win/node/test.mjs.tmpl +10 -0
- package/templates-win/webframework/build.mjs.tmpl +8 -1
- package/templates-win/webframework/dev.mjs.tmpl +8 -1
- package/templates-win/webframework/hard-clean.mjs.tmpl +28 -5
- package/templates-win/webframework/lint.mjs.tmpl +10 -0
- package/templates-win/webframework/test.mjs.tmpl +10 -0
package/README.md
CHANGED
|
@@ -13,7 +13,13 @@ your environment, and `xgem git` wraps a common stage → commit → rebase-pull
|
|
|
13
13
|
Flutter's iOS builds go through a dedicated engine that detects whether your
|
|
14
14
|
project uses CocoaPods or Swift Package Manager (SPM), figures out the actual
|
|
15
15
|
deployment target your resolved SPM plugins require (instead of guessing),
|
|
16
|
-
and reconciles it automatically
|
|
16
|
+
and reconciles it automatically.
|
|
17
|
+
|
|
18
|
+
**Full documentation: [docs/](https://github.com/Code4Hacker/x-gem-cli/blob/xgem/docs/README.md)** — getting started, every command in
|
|
19
|
+
depth, exactly what each framework's project-creation wizard does, the iOS build
|
|
20
|
+
engine explained, Windows support, and troubleshooting. This README is a summary.
|
|
21
|
+
(Absolute link on purpose — `docs/` isn't shipped inside the npm package itself,
|
|
22
|
+
so a relative link would 404 on npmjs.com's rendered README.)
|
|
17
23
|
|
|
18
24
|
## Install
|
|
19
25
|
|
|
@@ -68,31 +74,13 @@ flutter, node, python, react, vue, angular, next, go, rust, docker, swift
|
|
|
68
74
|
## Creating a new project
|
|
69
75
|
|
|
70
76
|
`xgem init` asks, per framework, whether to create a brand-new project or use
|
|
71
|
-
what's already in the directory
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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.
|
|
77
|
+
what's already in the directory — real `create-vite`/`create-vue`/`ng new`/
|
|
78
|
+
`create-next-app`/`flutter create` scaffolding, with framework-specific
|
|
79
|
+
questions and a working dev-server/device launch at the end. Package manager
|
|
80
|
+
(npm/yarn/pnpm/bun) is auto-detected everywhere, never assumed. Answering
|
|
81
|
+
"existing" (the default) skips all of this and just scaffolds
|
|
82
|
+
`.xgem-automate/` into the current directory. Full detail, including exactly
|
|
83
|
+
what runs for each framework: **[docs/frameworks.md](https://github.com/Code4Hacker/x-gem-cli/blob/xgem/docs/frameworks.md)**.
|
|
96
84
|
|
|
97
85
|
## Architecture
|
|
98
86
|
|
|
@@ -118,47 +106,25 @@ of logic that can drift out of sync with two copies.
|
|
|
118
106
|
## Why the iOS build engine exists
|
|
119
107
|
|
|
120
108
|
Flutter auto-generates a wrapper Swift package, `FlutterGeneratedPluginSwiftPackage`,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
upstream Flutter bug
|
|
109
|
+
whose declared minimum iOS platform can desync from your app's own
|
|
110
|
+
`IPHONEOS_DEPLOYMENT_TARGET` — independently, even when your app's own target is
|
|
111
|
+
already high enough. This is a confirmed, currently-open upstream Flutter bug
|
|
112
|
+
([flutter/flutter#186804](https://github.com/flutter/flutter/issues/186804),
|
|
124
113
|
[#189422](https://github.com/flutter/flutter/issues/189422),
|
|
125
|
-
[#162072](https://github.com/flutter/flutter/issues/162072)).
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
1. Detects whether the project uses CocoaPods, SPM, or both.
|
|
133
|
-
2. Computes the deployment target actually required by your *resolved* SPM plugins
|
|
134
|
-
(by reading their `ios/Package.swift` manifests via `.dart_tool/package_config.json`),
|
|
135
|
-
instead of assuming a fixed value.
|
|
136
|
-
3. Shows a plan and asks for confirmation (unless `--yes`/`--dry-run`) before patching
|
|
137
|
-
*every* `IPHONEOS_DEPLOYMENT_TARGET` occurrence in `project.pbxproj`.
|
|
138
|
-
4. Forces a clean regeneration (`flutter pub get` after clearing `ios/Flutter/ephemeral`)
|
|
139
|
-
and verifies the regenerated package matches.
|
|
140
|
-
5. If it still doesn't (the upstream bug above), patches the generated package directly
|
|
141
|
-
as a documented, loudly-logged last resort — never silently.
|
|
142
|
-
|
|
143
|
-
Run `xgem doctor ios` any time for a standalone readiness report without doing a build.
|
|
114
|
+
[#162072](https://github.com/flutter/flutter/issues/162072)). `xgem run flutter build`
|
|
115
|
+
(iOS target) detects the actual requirement from your resolved SPM plugins, patches
|
|
116
|
+
every deployment-target occurrence, forces a clean regeneration, verifies it, and
|
|
117
|
+
falls back to a documented direct patch if the upstream bug is still biting — full
|
|
118
|
+
detail: **[docs/ios-build-engine.md](https://github.com/Code4Hacker/x-gem-cli/blob/xgem/docs/ios-build-engine.md)**. Run `xgem doctor ios`
|
|
119
|
+
any time for a standalone readiness report without doing a build.
|
|
144
120
|
|
|
145
121
|
## Windows support
|
|
146
122
|
|
|
147
|
-
Homebrew and the curl installer are macOS/Linux only, same as any bash tool
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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.
|
|
123
|
+
Homebrew and the curl installer are macOS/Linux only, same as any bash tool. npm is
|
|
124
|
+
different: it ships a small native Windows engine (`lib-win/`) so `npm install -g
|
|
125
|
+
xgem-cli` actually works in `cmd.exe`/PowerShell, no WSL required — everything except
|
|
126
|
+
Flutter iOS/macOS builds (Xcode has no Windows equivalent) and the project-creation
|
|
127
|
+
wizards (not ported yet). Full detail: **[docs/windows.md](https://github.com/Code4Hacker/x-gem-cli/blob/xgem/docs/windows.md)**.
|
|
162
128
|
|
|
163
129
|
## Roadmap
|
|
164
130
|
|
package/bin/xgem
CHANGED
|
@@ -91,26 +91,18 @@ case "${1:-}" in
|
|
|
91
91
|
PS3="Enter target selection number: "
|
|
92
92
|
select fw in "${ALL_FRAMEWORKS[@]}"; do
|
|
93
93
|
if [ -n "$fw" ]; then
|
|
94
|
+
# create_project_wizard handles ALL of xgem's own
|
|
95
|
+
# bookkeeping (.xgem-automate + .gitignore) internally now,
|
|
96
|
+
# at the earliest safe point for each framework — never
|
|
97
|
+
# after a slow/interruptible step, so a Ctrl-C during (say)
|
|
98
|
+
# `ng new`'s install can no longer lose it. See lib/create.sh.
|
|
94
99
|
create_project_wizard "$fw"
|
|
95
|
-
mkdir -p "$CONFIG_DIR"
|
|
96
|
-
scaffold_inject_templates "$fw" "$CONFIG_DIR"
|
|
97
|
-
log_success "Successfully appended standard scripts for: $CONFIG_DIR/$fw"
|
|
98
100
|
break
|
|
99
101
|
else
|
|
100
102
|
echo "Invalid selection."
|
|
101
103
|
fi
|
|
102
104
|
done
|
|
103
105
|
|
|
104
|
-
if [ -f .gitignore ]; then
|
|
105
|
-
if ! grep -q "$CONFIG_DIR/" .gitignore; then
|
|
106
|
-
echo -e "\n$CONFIG_DIR/" >> .gitignore
|
|
107
|
-
log_success "Added automation tracking to .gitignore"
|
|
108
|
-
fi
|
|
109
|
-
else
|
|
110
|
-
echo "$CONFIG_DIR/" > .gitignore
|
|
111
|
-
log_success "Created .gitignore and hidden tracking layer folder references."
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
106
|
_ask_launch_dev_server
|
|
115
107
|
|
|
116
108
|
# A child process (this script) can never change the parent
|
package/bin/xgem.js
CHANGED
|
@@ -61,8 +61,23 @@ async function selectFramework(options) {
|
|
|
61
61
|
return options[idx];
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
// Some teams want the generated scripts checked in so collaborators get the
|
|
65
|
+
// same automation; others want them private/local-only. Ask instead of
|
|
66
|
+
// always gitignoring.
|
|
67
|
+
async function updateGitignore() {
|
|
65
68
|
const gitignorePath = '.gitignore';
|
|
69
|
+
const ignoreChoice = ((await prompt(`Should ${CONFIG_DIR}/ be ignored by git (private to you), or tracked so collaborators get the same scripts? [ignore/track]`, 'ignore'))).toLowerCase();
|
|
70
|
+
|
|
71
|
+
if (ignoreChoice.startsWith('t')) {
|
|
72
|
+
if (fs.existsSync(gitignorePath)) {
|
|
73
|
+
const lines = fs.readFileSync(gitignorePath, 'utf8').split(/\r?\n/).filter((l) => l !== `${CONFIG_DIR}/`);
|
|
74
|
+
fs.writeFileSync(gitignorePath, lines.join('\n'));
|
|
75
|
+
logInfo(`Removed existing ${CONFIG_DIR}/ entry from .gitignore since you chose to track it.`);
|
|
76
|
+
}
|
|
77
|
+
logSuccess(`${CONFIG_DIR}/ will be tracked in git.`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
if (fs.existsSync(gitignorePath)) {
|
|
67
82
|
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
68
83
|
if (!content.includes(`${CONFIG_DIR}/`)) {
|
|
@@ -89,7 +104,7 @@ async function cmdInit() {
|
|
|
89
104
|
scaffold.injectTemplates(fw, CONFIG_DIR);
|
|
90
105
|
logSuccess(`Successfully appended standard scripts for: ${CONFIG_DIR}/${fw}`);
|
|
91
106
|
|
|
92
|
-
updateGitignore();
|
|
107
|
+
await updateGitignore();
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
async function cmdAdd() {
|
package/lib/create.sh
CHANGED
|
@@ -1,29 +1,76 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# xgem project creation wizards — runs from `xgem init`,
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
2
|
+
# xgem project creation wizards — runs from `xgem init`, for frameworks
|
|
3
|
+
# with a real "ask sub-choice -> scaffold -> install -> launch" story
|
|
4
|
+
# worth building (react/vue/angular/next/flutter). The rest (node/python/
|
|
5
|
+
# go/rust/docker/swift) get a lighter "create with the ecosystem's own
|
|
6
|
+
# standard init command" step.
|
|
7
7
|
#
|
|
8
|
-
# Depends on lib/logger.sh, lib/utils.sh. Uses `cd`
|
|
9
|
-
# subshells) so a newly created project directory persists as
|
|
10
|
-
# the rest of `xgem init
|
|
11
|
-
# caller needing to track/re-cd into a returned path.
|
|
8
|
+
# Depends on lib/logger.sh, lib/utils.sh, lib/scaffold.sh. Uses `cd`
|
|
9
|
+
# directly (no subshells) so a newly created project directory persists as
|
|
10
|
+
# the cwd for the rest of `xgem init`.
|
|
12
11
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
12
|
+
# Ordering principle (this file's main correctness property): xgem's own
|
|
13
|
+
# bookkeeping (.xgem-automate + .gitignore, via _xgem_bookkeeping) always
|
|
14
|
+
# runs BEFORE the slow, interruptible step of each framework (an install,
|
|
15
|
+
# or an atomic scaffold+install like `ng new`) — never after. A real user
|
|
16
|
+
# hit this: Ctrl-C during `ng new`'s multi-minute install killed the whole
|
|
17
|
+
# xgem process before .xgem-automate was ever created, since it used to run
|
|
18
|
+
# only at the very end. Where a tool supports decoupling scaffold-from-
|
|
19
|
+
# install (--no-immediate, --skip-install, --no-pub), xgem scaffolds first
|
|
20
|
+
# (fast), does its own bookkeeping, then runs the install itself. Where a
|
|
21
|
+
# tool has no such flag (create-react-app), bookkeeping runs right after
|
|
22
|
+
# that one atomic call returns — a small residual risk, documented at the
|
|
23
|
+
# call site, not silently left as-was.
|
|
24
|
+
#
|
|
25
|
+
# Dev-server / device-run launch is deliberately deferred: creators only
|
|
26
|
+
# set XGEM_POST_INIT_LAUNCH_CMD (an array) rather than launching
|
|
27
|
+
# immediately, so `xgem init` finishes well before handing the terminal
|
|
28
|
+
# over to a long-running dev server.
|
|
17
29
|
XGEM_POST_INIT_LAUNCH_CMD=()
|
|
18
30
|
|
|
19
31
|
# Set by creators when a NEW folder was created (not "current directory").
|
|
20
32
|
# A child process can never change its parent shell's cwd, so once `xgem
|
|
21
33
|
# init` exits, the user's actual terminal is back wherever it started —
|
|
22
34
|
# xgem's own process is correctly cd'd into the new folder for the rest of
|
|
23
|
-
# ITS run
|
|
24
|
-
#
|
|
35
|
+
# ITS run, but that never propagates back. bin/xgem prints a "cd <name>"
|
|
36
|
+
# reminder using this at the very end.
|
|
25
37
|
XGEM_CREATED_NEW_FOLDER=""
|
|
26
38
|
|
|
39
|
+
# _xgem_bookkeeping <framework>
|
|
40
|
+
# Creates .xgem-automate + updates .gitignore. Called as early as the
|
|
41
|
+
# target directory allows, always before any slow/interruptible step.
|
|
42
|
+
_xgem_bookkeeping() {
|
|
43
|
+
local fw=$1
|
|
44
|
+
mkdir -p "$CONFIG_DIR"
|
|
45
|
+
scaffold_inject_templates "$fw" "$CONFIG_DIR"
|
|
46
|
+
log_success "Successfully appended standard scripts for: $CONFIG_DIR/$fw"
|
|
47
|
+
|
|
48
|
+
# Some teams want the generated scripts checked in so collaborators get
|
|
49
|
+
# the same automation; others want them private/local-only. Ask instead
|
|
50
|
+
# of always gitignoring.
|
|
51
|
+
local ignore_choice
|
|
52
|
+
read -r -p "Should $CONFIG_DIR/ be ignored by git (private to you), or tracked so collaborators get the same scripts? [ignore/track] (default: ignore): " ignore_choice
|
|
53
|
+
ignore_choice=${ignore_choice:-ignore}
|
|
54
|
+
|
|
55
|
+
if [[ "$ignore_choice" == t* || "$ignore_choice" == T* ]]; then
|
|
56
|
+
if [ -f .gitignore ] && grep -qx "$CONFIG_DIR/" .gitignore; then
|
|
57
|
+
local tmp
|
|
58
|
+
tmp=$(mktemp)
|
|
59
|
+
grep -vx "$CONFIG_DIR/" .gitignore > "$tmp" && mv "$tmp" .gitignore
|
|
60
|
+
log_info "Removed existing $CONFIG_DIR/ entry from .gitignore since you chose to track it."
|
|
61
|
+
fi
|
|
62
|
+
log_success "$CONFIG_DIR/ will be tracked in git."
|
|
63
|
+
elif [ -f .gitignore ]; then
|
|
64
|
+
if ! grep -qx "$CONFIG_DIR/" .gitignore; then
|
|
65
|
+
echo -e "\n$CONFIG_DIR/" >> .gitignore
|
|
66
|
+
log_success "Added automation tracking to .gitignore"
|
|
67
|
+
fi
|
|
68
|
+
else
|
|
69
|
+
echo "$CONFIG_DIR/" > .gitignore
|
|
70
|
+
log_success "Created .gitignore and hidden tracking layer folder references."
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
27
74
|
# _prompt_project_location <human label> -> echoes "." or a new folder name.
|
|
28
75
|
# Pure prompt-and-echo, no other stdout output, so it's safe to capture via
|
|
29
76
|
# command substitution.
|
|
@@ -42,6 +89,15 @@ _prompt_project_location() {
|
|
|
42
89
|
echo "$name"
|
|
43
90
|
}
|
|
44
91
|
|
|
92
|
+
# _enter_project_dir <project_dir> — mkdir+cd if a new folder was chosen,
|
|
93
|
+
# and records XGEM_CREATED_NEW_FOLDER. No-op for "." (current directory).
|
|
94
|
+
_enter_project_dir() {
|
|
95
|
+
local project_dir=$1
|
|
96
|
+
[ "$project_dir" = "." ] && return 0
|
|
97
|
+
XGEM_CREATED_NEW_FOLDER="$project_dir"
|
|
98
|
+
mkdir -p "$project_dir" && cd "$project_dir" || die "Could not enter $project_dir"
|
|
99
|
+
}
|
|
100
|
+
|
|
45
101
|
_open_url() {
|
|
46
102
|
local url=$1
|
|
47
103
|
case "$(detect_os)" in
|
|
@@ -89,13 +145,11 @@ _launch_dev_server_and_open_browser() {
|
|
|
89
145
|
_ask_launch_dev_server() {
|
|
90
146
|
[ ${#XGEM_POST_INIT_LAUNCH_CMD[@]} -gt 0 ] || return 0
|
|
91
147
|
|
|
92
|
-
# `flutter run` isn't a web dev server —
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
# _flutter_offer_run_on_device before this command was ever set, so
|
|
98
|
-
# this doesn't ask again.)
|
|
148
|
+
# `flutter run` isn't a web dev server — no localhost URL to detect/
|
|
149
|
+
# open, and it depends on fully-inherited interactive stdin for its
|
|
150
|
+
# own hot-reload keybindings, which the background+log-tail approach
|
|
151
|
+
# would break. Run it directly. (Whether to run at all was already
|
|
152
|
+
# confirmed in _flutter_offer_run_on_device, so this doesn't ask again.)
|
|
99
153
|
if [ "${XGEM_POST_INIT_LAUNCH_CMD[0]}" = "flutter" ]; then
|
|
100
154
|
"${XGEM_POST_INIT_LAUNCH_CMD[@]}"
|
|
101
155
|
return 0
|
|
@@ -114,27 +168,33 @@ _create_react() {
|
|
|
114
168
|
read -r -p "TypeScript or JavaScript? [ts/js] (default: ts): " lang
|
|
115
169
|
lang=${lang:-ts}
|
|
116
170
|
project_dir=$(_prompt_project_location "React")
|
|
117
|
-
|
|
171
|
+
_enter_project_dir "$project_dir"
|
|
118
172
|
|
|
119
173
|
if [[ "$variant" == cra* || "$variant" == CRA* ]]; then
|
|
120
174
|
require_cmd npx "Install Node.js (npm ships with it): https://nodejs.org"
|
|
121
|
-
|
|
122
|
-
|
|
175
|
+
# create-react-app has no flag to decouple scaffold-from-install
|
|
176
|
+
# (confirmed: nothing in --help), so this one atomic call does
|
|
177
|
+
# both — a Ctrl-C during its own install (it's known to be slow)
|
|
178
|
+
# would still lose xgem's bookkeeping below. Accepted residual
|
|
179
|
+
# risk for this specific, non-default, legacy-leaning option.
|
|
180
|
+
log_info "Create React App scaffolds and installs in one step and can take a few minutes — best to let it finish without interrupting."
|
|
181
|
+
local -a args=(create-react-app .)
|
|
123
182
|
[ "$lang" = "ts" ] && args+=(--template typescript)
|
|
124
183
|
npx "${args[@]}" || die "create-react-app failed."
|
|
125
|
-
|
|
126
|
-
|
|
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."
|
|
184
|
+
_xgem_bookkeeping react
|
|
185
|
+
return 0
|
|
135
186
|
fi
|
|
136
187
|
|
|
137
|
-
|
|
188
|
+
require_cmd npm "Install Node.js (npm ships with it): https://nodejs.org"
|
|
189
|
+
local template="react"
|
|
190
|
+
[ "$lang" = "ts" ] && template="react-ts"
|
|
191
|
+
# --no-immediate: create-vite's own "install deps and start dev server"
|
|
192
|
+
# prompt would otherwise block here (and if Ctrl-C'd, kill this whole
|
|
193
|
+
# xgem process). --overwrite: harmless here since the only files that
|
|
194
|
+
# could exist at this point are ones xgem itself hasn't created yet
|
|
195
|
+
# (bookkeeping happens after this call, deliberately).
|
|
196
|
+
npm create vite@latest . -- --template "$template" --no-immediate --overwrite || die "npm create vite failed."
|
|
197
|
+
_xgem_bookkeeping react
|
|
138
198
|
log_info "Installing dependencies..."
|
|
139
199
|
npm install
|
|
140
200
|
XGEM_POST_INIT_LAUNCH_CMD=(npm run dev)
|
|
@@ -144,9 +204,13 @@ _create_vue() {
|
|
|
144
204
|
require_cmd npm "Install Node.js (npm ships with it): https://nodejs.org"
|
|
145
205
|
local project_dir
|
|
146
206
|
project_dir=$(_prompt_project_location "Vue")
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
207
|
+
_enter_project_dir "$project_dir"
|
|
208
|
+
# create-vue doesn't install automatically (confirmed: no node_modules
|
|
209
|
+
# after it runs), so there's already a natural gap here to bookkeep in
|
|
210
|
+
# before the slow step. --force since this dir may be genuinely empty
|
|
211
|
+
# or (for "current directory") may already contain unrelated files.
|
|
212
|
+
npm create vue@latest . --force || die "npm create vue failed."
|
|
213
|
+
_xgem_bookkeeping vue
|
|
150
214
|
log_info "Installing dependencies..."
|
|
151
215
|
npm install
|
|
152
216
|
XGEM_POST_INIT_LAUNCH_CMD=(npm run dev)
|
|
@@ -155,16 +219,20 @@ _create_vue() {
|
|
|
155
219
|
_create_angular() {
|
|
156
220
|
local project_dir
|
|
157
221
|
project_dir=$(_prompt_project_location "Angular")
|
|
158
|
-
|
|
222
|
+
_enter_project_dir "$project_dir"
|
|
159
223
|
local -a ng_cmd=(ng)
|
|
160
224
|
has_cmd ng || ng_cmd=(npx @angular/cli@latest)
|
|
161
225
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
226
|
+
# --skip-install: ng new's own install is a multi-minute step (Angular
|
|
227
|
+
# dependency trees are large) — decoupling it is exactly what let a
|
|
228
|
+
# real user's Ctrl-C lose xgem's bookkeeping before this fix.
|
|
229
|
+
# --skip-git: xgem has its own `xgem git init`; ng new's own auto-
|
|
230
|
+
# commit would happen before .xgem-automate even exists.
|
|
231
|
+
"${ng_cmd[@]}" new "$(basename "$PWD")" --directory=. --skip-install --skip-git \
|
|
232
|
+
|| die "ng new failed."
|
|
233
|
+
_xgem_bookkeeping angular
|
|
234
|
+
log_info "Installing dependencies..."
|
|
235
|
+
npm install
|
|
168
236
|
XGEM_POST_INIT_LAUNCH_CMD=(npm run start)
|
|
169
237
|
}
|
|
170
238
|
|
|
@@ -172,20 +240,26 @@ _create_next() {
|
|
|
172
240
|
require_cmd npx "Install Node.js (npm ships with it): https://nodejs.org"
|
|
173
241
|
local project_dir
|
|
174
242
|
project_dir=$(_prompt_project_location "Next.js")
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
243
|
+
_enter_project_dir "$project_dir"
|
|
244
|
+
# --skip-install: decouple from create-next-app's own install, same
|
|
245
|
+
# reasoning as Angular above.
|
|
246
|
+
npx create-next-app@latest . --skip-install || die "create-next-app failed."
|
|
247
|
+
_xgem_bookkeeping next
|
|
248
|
+
log_info "Installing dependencies..."
|
|
249
|
+
npm install
|
|
178
250
|
XGEM_POST_INIT_LAUNCH_CMD=(npm run dev)
|
|
179
251
|
}
|
|
180
252
|
|
|
181
253
|
# Lighter path for frameworks without a dev-server/browser story — just the
|
|
182
|
-
# ecosystem's own standard init command, no sub-wizard.
|
|
254
|
+
# ecosystem's own standard init command, no sub-wizard. These are all fast
|
|
255
|
+
# (no multi-minute install step), so bookkeeping-then-init is low-risk
|
|
256
|
+
# regardless of ordering, but it's kept bookkeeping-first for consistency.
|
|
183
257
|
_create_simple() {
|
|
184
258
|
local fw=$1
|
|
185
259
|
local project_dir
|
|
186
260
|
project_dir=$(_prompt_project_location "$fw")
|
|
187
|
-
|
|
188
|
-
|
|
261
|
+
_enter_project_dir "$project_dir"
|
|
262
|
+
_xgem_bookkeeping "$fw"
|
|
189
263
|
|
|
190
264
|
case "$fw" in
|
|
191
265
|
node)
|
|
@@ -206,13 +280,7 @@ _create_simple() {
|
|
|
206
280
|
;;
|
|
207
281
|
rust)
|
|
208
282
|
require_cmd cargo "Install Rust: https://www.rust-lang.org/tools/install"
|
|
209
|
-
|
|
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
|
|
283
|
+
cargo init || die "cargo init failed."
|
|
216
284
|
;;
|
|
217
285
|
swift)
|
|
218
286
|
require_cmd swift "Install Swift: https://www.swift.org/install/"
|
|
@@ -235,15 +303,21 @@ EOF
|
|
|
235
303
|
}
|
|
236
304
|
|
|
237
305
|
# create_project_wizard <framework>
|
|
238
|
-
# Asks whether to create a new project or use what's already here
|
|
239
|
-
#
|
|
240
|
-
#
|
|
306
|
+
# Asks whether to create a new project or use what's already here. Either
|
|
307
|
+
# way, xgem's own bookkeeping (.xgem-automate + .gitignore) is guaranteed
|
|
308
|
+
# to happen — for "existing", immediately (nothing to interrupt); for
|
|
309
|
+
# "new", each creator handles it internally at the earliest safe point,
|
|
310
|
+
# per the ordering principle documented at the top of this file.
|
|
241
311
|
create_project_wizard() {
|
|
242
312
|
local fw=$1
|
|
243
313
|
local create_choice
|
|
244
314
|
read -r -p "Create a brand-new $fw project, or use what's already in this directory? [new/existing] (default: existing): " create_choice
|
|
245
315
|
create_choice=${create_choice:-existing}
|
|
246
|
-
|
|
316
|
+
|
|
317
|
+
if [[ "$create_choice" != n* && "$create_choice" != N* ]]; then
|
|
318
|
+
_xgem_bookkeeping "$fw"
|
|
319
|
+
return 0
|
|
320
|
+
fi
|
|
247
321
|
|
|
248
322
|
case "$fw" in
|
|
249
323
|
react) _create_react ;;
|
|
@@ -252,6 +326,9 @@ create_project_wizard() {
|
|
|
252
326
|
next) _create_next ;;
|
|
253
327
|
flutter) create_flutter_project ;;
|
|
254
328
|
node|python|go|rust|docker|swift) _create_simple "$fw" ;;
|
|
255
|
-
*)
|
|
329
|
+
*)
|
|
330
|
+
log_debug "No creation wizard for '$fw'; using current directory as-is."
|
|
331
|
+
_xgem_bookkeeping "$fw"
|
|
332
|
+
;;
|
|
256
333
|
esac
|
|
257
334
|
}
|
package/lib/flutter.sh
CHANGED
|
@@ -239,18 +239,20 @@ flutter_run_native_command() {
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
# create_flutter_project — used by lib/create.sh's create_project_wizard.
|
|
242
|
-
#
|
|
242
|
+
# --no-pub decouples scaffolding from the (slower, network-dependent)
|
|
243
|
+
# `flutter pub get` step, so xgem's own bookkeeping (_xgem_bookkeeping) can
|
|
244
|
+
# happen in between — same ordering principle as the other creators in
|
|
245
|
+
# lib/create.sh: bookkeeping before any slow/interruptible step, never after.
|
|
243
246
|
create_flutter_project() {
|
|
244
247
|
require_cmd flutter "Install Flutter: https://docs.flutter.dev/get-started/install"
|
|
245
248
|
local project_dir
|
|
246
249
|
project_dir=$(_prompt_project_location "Flutter")
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
fi
|
|
250
|
+
_enter_project_dir "$project_dir"
|
|
251
|
+
|
|
252
|
+
flutter create --no-pub . || die "flutter create failed."
|
|
253
|
+
_xgem_bookkeeping flutter
|
|
254
|
+
log_info "Resolving packages..."
|
|
255
|
+
flutter pub get
|
|
254
256
|
_flutter_offer_run_on_device
|
|
255
257
|
}
|
|
256
258
|
|
package/lib/git.sh
CHANGED
|
@@ -102,31 +102,67 @@ cmd_git_cmt() {
|
|
|
102
102
|
fi
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
_cmd_git_init_manual_remote() {
|
|
106
|
+
local remote_name=$1
|
|
107
|
+
local remote_url user_remote_name
|
|
108
|
+
read -r -p "Enter remote repository URL (or leave blank to skip): " remote_url
|
|
109
|
+
[ -n "$remote_url" ] || return 0
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if [ -n "$remote_url" ]; then
|
|
112
|
-
read -r -p "Enter remote name (default: origin): " user_remote_name
|
|
113
|
-
[ -n "$user_remote_name" ] && remote_name="$user_remote_name"
|
|
111
|
+
read -r -p "Enter remote name (default: $remote_name): " user_remote_name
|
|
112
|
+
[ -n "$user_remote_name" ] && remote_name="$user_remote_name"
|
|
114
113
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
fi
|
|
114
|
+
if git remote | grep -q "^$remote_name$"; then
|
|
115
|
+
git remote set-url "$remote_name" "$remote_url"
|
|
116
|
+
log_success "Remote '$remote_name' already existed. URL updated."
|
|
117
|
+
else
|
|
118
|
+
git remote add "$remote_name" "$remote_url"
|
|
119
|
+
log_success "Remote '$remote_name' successfully added."
|
|
122
120
|
fi
|
|
121
|
+
}
|
|
123
122
|
|
|
123
|
+
cmd_git_init() {
|
|
124
|
+
log_info "Initializing local Git repository..."
|
|
125
|
+
git init
|
|
126
|
+
|
|
127
|
+
local branch_name
|
|
124
128
|
read -r -p "Enter branch name (default: main): " branch_name
|
|
125
129
|
branch_name=${branch_name:-main}
|
|
126
130
|
git branch -M "$branch_name"
|
|
127
131
|
|
|
128
132
|
git add .
|
|
129
133
|
git commit -m "initial changes" 2>/dev/null
|
|
134
|
+
|
|
135
|
+
# Real GitHub repo creation, not just wiring a remote to a URL you
|
|
136
|
+
# already had to go create by hand — this is the whole point of
|
|
137
|
+
# `xgem git init` over plain `git init`. Falls back to the manual-URL
|
|
138
|
+
# flow if `gh` isn't installed/authenticated.
|
|
139
|
+
if has_cmd gh && gh auth status >/dev/null 2>&1; then
|
|
140
|
+
local create_choice
|
|
141
|
+
read -r -p "Create a new GitHub repository for this project right now? (Y/n): " create_choice
|
|
142
|
+
create_choice=${create_choice:-y}
|
|
143
|
+
if [[ "$create_choice" == "y" || "$create_choice" == "Y" ]]; then
|
|
144
|
+
local repo_name visibility vis_flag
|
|
145
|
+
read -r -p "Repository name (default: $(basename "$PWD")): " repo_name
|
|
146
|
+
repo_name=${repo_name:-$(basename "$PWD")}
|
|
147
|
+
read -r -p "Public or private? [public/private] (default: private): " visibility
|
|
148
|
+
visibility=${visibility:-private}
|
|
149
|
+
vis_flag="--private"
|
|
150
|
+
[[ "$visibility" == pub* || "$visibility" == Pub* ]] && vis_flag="--public"
|
|
151
|
+
|
|
152
|
+
if gh repo create "$repo_name" "$vis_flag" --source=. --remote=origin --push; then
|
|
153
|
+
log_success "Created GitHub repo '$repo_name' and pushed '$branch_name' to it."
|
|
154
|
+
else
|
|
155
|
+
log_error "gh repo create failed — falling back to manual remote setup."
|
|
156
|
+
_cmd_git_init_manual_remote origin
|
|
157
|
+
fi
|
|
158
|
+
log_success "Local baseline configuration setup completed."
|
|
159
|
+
return 0
|
|
160
|
+
fi
|
|
161
|
+
elif has_cmd gh; then
|
|
162
|
+
log_warn "GitHub CLI (gh) is installed but not authenticated — run 'gh auth login' to enable one-step repo creation next time."
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
_cmd_git_init_manual_remote origin
|
|
130
166
|
log_success "Local baseline configuration setup completed."
|
|
131
167
|
}
|
|
132
168
|
|
package/lib/scaffold.sh
CHANGED
|
@@ -13,12 +13,31 @@
|
|
|
13
13
|
|
|
14
14
|
ALL_FRAMEWORKS=(flutter node python react vue angular next go rust docker swift)
|
|
15
15
|
|
|
16
|
+
# _package_json_has_script <script> — checks the CURRENT directory's
|
|
17
|
+
# package.json, not a guess. Used to only offer lint/test automation when
|
|
18
|
+
# the project actually has those scripts, instead of always generating
|
|
19
|
+
# scripts that fail with "Missing script" on projects that don't.
|
|
20
|
+
_package_json_has_script() {
|
|
21
|
+
[ -f package.json ] || return 1
|
|
22
|
+
grep -qE "\"$1\"[[:space:]]*:" package.json 2>/dev/null
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
framework_scripts() {
|
|
17
26
|
case "$1" in
|
|
18
27
|
flutter) echo "hard-clean build build-runner" ;;
|
|
19
|
-
node)
|
|
28
|
+
node)
|
|
29
|
+
local scripts="hard-clean build start"
|
|
30
|
+
_package_json_has_script lint && scripts="$scripts lint"
|
|
31
|
+
_package_json_has_script test && scripts="$scripts test"
|
|
32
|
+
echo "$scripts"
|
|
33
|
+
;;
|
|
20
34
|
python) echo "hard-clean install" ;;
|
|
21
|
-
react|vue|angular|next)
|
|
35
|
+
react|vue|angular|next)
|
|
36
|
+
local scripts="hard-clean build dev"
|
|
37
|
+
_package_json_has_script lint && scripts="$scripts lint"
|
|
38
|
+
_package_json_has_script test && scripts="$scripts test"
|
|
39
|
+
echo "$scripts"
|
|
40
|
+
;;
|
|
22
41
|
go) echo "hard-clean build" ;;
|
|
23
42
|
rust) echo "hard-clean build" ;;
|
|
24
43
|
docker) echo "hard-clean build-up" ;;
|
package/lib/version.sh
CHANGED
package/lib-win/git.js
CHANGED
|
@@ -3,14 +3,23 @@
|
|
|
3
3
|
// commit-exit-code check and origin-preference fix already in lib/git.sh
|
|
4
4
|
// carry over here too.
|
|
5
5
|
|
|
6
|
+
const path = require('node:path');
|
|
6
7
|
const { spawnSync } = require('node:child_process');
|
|
7
8
|
const { logInfo, logSuccess, logWarn, logError, die } = require('./logger');
|
|
8
|
-
const { prompt } = require('./utils');
|
|
9
|
+
const { prompt, hasCmd } = require('./utils');
|
|
9
10
|
|
|
10
11
|
function git(args, opts = {}) {
|
|
11
12
|
return spawnSync('git', args, { stdio: 'inherit', ...opts });
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
function gh(args, opts = {}) {
|
|
16
|
+
return spawnSync('gh', args, { stdio: 'inherit', ...opts });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ghAuthenticated() {
|
|
20
|
+
return spawnSync('gh', ['auth', 'status'], { stdio: 'ignore' }).status === 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
function gitCapture(args) {
|
|
15
24
|
const result = spawnSync('git', args, { encoding: 'utf8' });
|
|
16
25
|
return (result.stdout || '').trim();
|
|
@@ -90,7 +99,6 @@ async function cmdCmt(commitMsg) {
|
|
|
90
99
|
const pullResult = git(['pull', '--rebase', remoteName, currentBranch]);
|
|
91
100
|
if (pullResult.status !== 0) {
|
|
92
101
|
const gitDir = gitCapture(['rev-parse', '--git-dir']);
|
|
93
|
-
const path = require('node:path');
|
|
94
102
|
const fs = require('node:fs');
|
|
95
103
|
const inConflict = fs.existsSync(path.join(gitDir, 'rebase-merge')) || fs.existsSync(path.join(gitDir, 'rebase-apply'));
|
|
96
104
|
if (inConflict) {
|
|
@@ -112,30 +120,60 @@ async function cmdCmt(commitMsg) {
|
|
|
112
120
|
}
|
|
113
121
|
}
|
|
114
122
|
|
|
115
|
-
async function
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
async function manualRemoteSetup(defaultRemoteName) {
|
|
124
|
+
let remoteName = defaultRemoteName;
|
|
125
|
+
const remoteUrl = await prompt('Enter remote repository URL (or leave blank to skip)');
|
|
126
|
+
if (!remoteUrl) return;
|
|
118
127
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (remoteUrl) {
|
|
122
|
-
const userRemoteName = await prompt('Enter remote name (default: origin)');
|
|
123
|
-
if (userRemoteName) remoteName = userRemoteName;
|
|
128
|
+
const userRemoteName = await prompt(`Enter remote name (default: ${remoteName})`);
|
|
129
|
+
if (userRemoteName) remoteName = userRemoteName;
|
|
124
130
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
131
|
+
if (remoteExists(remoteName)) {
|
|
132
|
+
git(['remote', 'set-url', remoteName, remoteUrl]);
|
|
133
|
+
logSuccess(`Remote '${remoteName}' already existed. URL updated.`);
|
|
134
|
+
} else {
|
|
135
|
+
git(['remote', 'add', remoteName, remoteUrl]);
|
|
136
|
+
logSuccess(`Remote '${remoteName}' successfully added.`);
|
|
132
137
|
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function cmdInit() {
|
|
141
|
+
logInfo('Initializing local Git repository...');
|
|
142
|
+
git(['init']);
|
|
133
143
|
|
|
134
144
|
const branchName = (await prompt('Enter branch name (default: main)')) || 'main';
|
|
135
145
|
git(['branch', '-M', branchName]);
|
|
136
146
|
|
|
137
147
|
git(['add', '.']);
|
|
138
148
|
git(['commit', '-m', 'initial changes']);
|
|
149
|
+
|
|
150
|
+
// Real GitHub repo creation, not just wiring a remote to a URL you
|
|
151
|
+
// already had to go create by hand — the whole point of `xgem git
|
|
152
|
+
// init` over plain `git init`. Falls back to the manual-URL flow if
|
|
153
|
+
// `gh` isn't installed/authenticated.
|
|
154
|
+
if (hasCmd('gh') && ghAuthenticated()) {
|
|
155
|
+
const createChoice = (await prompt('Create a new GitHub repository for this project right now? (Y/n)')) || 'y';
|
|
156
|
+
if (createChoice.toLowerCase() === 'y') {
|
|
157
|
+
const cwdName = path.basename(process.cwd());
|
|
158
|
+
const repoName = (await prompt(`Repository name (default: ${cwdName})`)) || cwdName;
|
|
159
|
+
const visibility = ((await prompt('Public or private? [public/private] (default: private)')) || 'private').toLowerCase();
|
|
160
|
+
const visFlag = visibility.startsWith('pub') ? '--public' : '--private';
|
|
161
|
+
|
|
162
|
+
const result = gh(['repo', 'create', repoName, visFlag, '--source=.', '--remote=origin', '--push']);
|
|
163
|
+
if (result.status === 0) {
|
|
164
|
+
logSuccess(`Created GitHub repo '${repoName}' and pushed '${branchName}' to it.`);
|
|
165
|
+
} else {
|
|
166
|
+
logError('gh repo create failed — falling back to manual remote setup.');
|
|
167
|
+
await manualRemoteSetup('origin');
|
|
168
|
+
}
|
|
169
|
+
logSuccess('Local baseline configuration setup completed.');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
} else if (hasCmd('gh')) {
|
|
173
|
+
logWarn("GitHub CLI (gh) is installed but not authenticated — run 'gh auth login' to enable one-step repo creation next time.");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
await manualRemoteSetup('origin');
|
|
139
177
|
logSuccess('Local baseline configuration setup completed.');
|
|
140
178
|
}
|
|
141
179
|
|
package/lib-win/scaffold.js
CHANGED
|
@@ -22,8 +22,30 @@ const FRAMEWORK_SCRIPTS = {
|
|
|
22
22
|
docker: ['hard-clean', 'build-up'],
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
const WEB_FRAMEWORKS = ['node', 'react', 'vue', 'angular', 'next'];
|
|
26
|
+
|
|
27
|
+
// Only offer lint/test automation when the current directory's
|
|
28
|
+
// package.json actually declares those scripts, instead of always
|
|
29
|
+
// generating scripts that fail with "Missing script" on projects that
|
|
30
|
+
// don't have them.
|
|
31
|
+
function packageJsonHasScript(scriptName) {
|
|
32
|
+
try {
|
|
33
|
+
const pkg = JSON.parse(require('node:fs').readFileSync('package.json', 'utf8'));
|
|
34
|
+
return Boolean(pkg.scripts && pkg.scripts[scriptName]);
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
function frameworkScripts(fw) {
|
|
26
|
-
|
|
41
|
+
const base = FRAMEWORK_SCRIPTS[fw];
|
|
42
|
+
if (!base) return null;
|
|
43
|
+
if (!WEB_FRAMEWORKS.includes(fw)) return base;
|
|
44
|
+
|
|
45
|
+
const scripts = [...base];
|
|
46
|
+
if (packageJsonHasScript('lint')) scripts.push('lint');
|
|
47
|
+
if (packageJsonHasScript('test')) scripts.push('test');
|
|
48
|
+
return scripts;
|
|
27
49
|
}
|
|
28
50
|
|
|
29
51
|
function templateDir(fw) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xgem-cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.12",
|
|
4
4
|
"description": "Framework-aware automation CLI: scaffolds and runs clean/build/dev scripts per project type, an environment doctor, a SwiftPM-aware iOS build engine, and a git workflow helper. Runs natively on macOS, Linux, and Windows.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xgem": "bin/xgem.js"
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
echo -e "\033[1;34mBuilding Node project...\033[0m"
|
|
3
|
-
|
|
3
|
+
if [ -f pnpm-lock.yaml ]; then
|
|
4
|
+
pnpm run build
|
|
5
|
+
elif [ -f yarn.lock ]; then
|
|
6
|
+
yarn build
|
|
7
|
+
elif [ -f bun.lockb ] || [ -f bun.lock ]; then
|
|
8
|
+
bun run build
|
|
9
|
+
else
|
|
10
|
+
npm run build
|
|
11
|
+
fi
|
|
@@ -1,3 +1,35 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
# Detect the package manager actually in use instead of assuming npm —
|
|
3
|
+
# blindly deleting yarn.lock/pnpm-lock.yaml on a project that uses them
|
|
4
|
+
# and running `npm install` instead corrupts the project by introducing a
|
|
5
|
+
# conflicting lockfile.
|
|
6
|
+
PKG_MANAGER=npm
|
|
7
|
+
if [ -f pnpm-lock.yaml ]; then
|
|
8
|
+
PKG_MANAGER=pnpm
|
|
9
|
+
elif [ -f yarn.lock ]; then
|
|
10
|
+
PKG_MANAGER=yarn
|
|
11
|
+
elif [ -f bun.lockb ] || [ -f bun.lock ]; then
|
|
12
|
+
PKG_MANAGER=bun
|
|
13
|
+
elif [ -f package.json ]; then
|
|
14
|
+
declared=$(grep -o '"packageManager"[[:space:]]*:[[:space:]]*"[a-z]*' package.json 2>/dev/null | grep -oE '[a-z]+$')
|
|
15
|
+
case "$declared" in npm|yarn|pnpm|bun) PKG_MANAGER=$declared ;; esac
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [ -f .nvmrc ] && command -v node >/dev/null 2>&1; then
|
|
19
|
+
wanted=$(tr -d 'v[:space:]' < .nvmrc)
|
|
20
|
+
have=$(node -v | tr -d 'v')
|
|
21
|
+
case "$have" in
|
|
22
|
+
"$wanted"*) ;;
|
|
23
|
+
*) echo -e "\033[33mWarning: .nvmrc wants Node $wanted, but 'node -v' reports $have.\033[0m" ;;
|
|
24
|
+
esac
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo -e "\033[1;33mNuking node_modules and resetting package locks (package manager: $PKG_MANAGER)...\033[0m"
|
|
28
|
+
rm -rf node_modules
|
|
29
|
+
|
|
30
|
+
case "$PKG_MANAGER" in
|
|
31
|
+
yarn) rm -f yarn.lock; yarn install ;;
|
|
32
|
+
pnpm) rm -f pnpm-lock.yaml; pnpm install ;;
|
|
33
|
+
bun) rm -f bun.lockb bun.lock; bun install ;;
|
|
34
|
+
*) rm -f package-lock.json; npm install ;;
|
|
35
|
+
esac
|
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
|
|
2
|
+
# Use whatever package manager this project actually uses.
|
|
3
|
+
if [ -f pnpm-lock.yaml ]; then
|
|
4
|
+
pnpm run build
|
|
5
|
+
elif [ -f yarn.lock ]; then
|
|
6
|
+
yarn build
|
|
7
|
+
elif [ -f bun.lockb ] || [ -f bun.lock ]; then
|
|
8
|
+
bun run build
|
|
9
|
+
else
|
|
10
|
+
npm run build
|
|
11
|
+
fi
|
|
@@ -1,3 +1,36 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
# Detect the package manager actually in use instead of assuming npm —
|
|
3
|
+
# blindly deleting yarn.lock/pnpm-lock.yaml on a project that uses them
|
|
4
|
+
# and running `npm install` instead corrupts the project by introducing a
|
|
5
|
+
# conflicting lockfile.
|
|
6
|
+
PKG_MANAGER=npm
|
|
7
|
+
if [ -f pnpm-lock.yaml ]; then
|
|
8
|
+
PKG_MANAGER=pnpm
|
|
9
|
+
elif [ -f yarn.lock ]; then
|
|
10
|
+
PKG_MANAGER=yarn
|
|
11
|
+
elif [ -f bun.lockb ] || [ -f bun.lock ]; then
|
|
12
|
+
PKG_MANAGER=bun
|
|
13
|
+
elif [ -f package.json ]; then
|
|
14
|
+
declared=$(grep -o '"packageManager"[[:space:]]*:[[:space:]]*"[a-z]*' package.json 2>/dev/null | grep -oE '[a-z]+$')
|
|
15
|
+
case "$declared" in npm|yarn|pnpm|bun) PKG_MANAGER=$declared ;; esac
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Warn (don't block) on a Node version mismatch against .nvmrc, if present.
|
|
19
|
+
if [ -f .nvmrc ] && command -v node >/dev/null 2>&1; then
|
|
20
|
+
wanted=$(tr -d 'v[:space:]' < .nvmrc)
|
|
21
|
+
have=$(node -v | tr -d 'v')
|
|
22
|
+
case "$have" in
|
|
23
|
+
"$wanted"*) ;;
|
|
24
|
+
*) echo -e "\033[33mWarning: .nvmrc wants Node $wanted, but 'node -v' reports $have.\033[0m" ;;
|
|
25
|
+
esac
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
echo -e "\033[1;33mResetting __FRAMEWORK__ dependencies (package manager: $PKG_MANAGER)...\033[0m"
|
|
29
|
+
rm -rf node_modules
|
|
30
|
+
|
|
31
|
+
case "$PKG_MANAGER" in
|
|
32
|
+
yarn) rm -f yarn.lock; yarn install ;;
|
|
33
|
+
pnpm) rm -f pnpm-lock.yaml; pnpm install ;;
|
|
34
|
+
bun) rm -f bun.lockb bun.lock; bun install ;;
|
|
35
|
+
*) rm -f package-lock.json; npm install ;;
|
|
36
|
+
esac
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
let cmd = 'npm run build';
|
|
6
|
+
if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run build';
|
|
7
|
+
else if (existsSync('yarn.lock')) cmd = 'yarn build';
|
|
8
|
+
else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run build';
|
|
9
|
+
|
|
10
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
import { rmSync } from 'node:fs';
|
|
3
|
+
import { existsSync, rmSync, readFileSync } from 'node:fs';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
function detectPkgManager() {
|
|
6
|
+
if (existsSync('pnpm-lock.yaml')) return 'pnpm';
|
|
7
|
+
if (existsSync('yarn.lock')) return 'yarn';
|
|
8
|
+
if (existsSync('bun.lockb') || existsSync('bun.lock')) return 'bun';
|
|
9
|
+
if (existsSync('package.json')) {
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
|
|
12
|
+
if (['npm', 'yarn', 'pnpm', 'bun'].includes(pkg.packageManager?.split('@')[0])) {
|
|
13
|
+
return pkg.packageManager.split('@')[0];
|
|
14
|
+
}
|
|
15
|
+
} catch { /* ignore malformed package.json */ }
|
|
16
|
+
}
|
|
17
|
+
return 'npm';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const pm = detectPkgManager();
|
|
21
|
+
console.log(`Nuking node_modules and resetting package locks (package manager: ${pm})...`);
|
|
6
22
|
rmSync('node_modules', { recursive: true, force: true });
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
23
|
+
|
|
24
|
+
const lockfiles = { npm: ['package-lock.json'], yarn: ['yarn.lock'], pnpm: ['pnpm-lock.yaml'], bun: ['bun.lockb', 'bun.lock'] };
|
|
25
|
+
for (const f of lockfiles[pm]) rmSync(f, { force: true });
|
|
26
|
+
|
|
27
|
+
const installCmd = { npm: 'npm install', yarn: 'yarn', pnpm: 'pnpm install', bun: 'bun install' };
|
|
28
|
+
execSync(installCmd[pm], { stdio: 'inherit' });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
let cmd = 'npm run lint';
|
|
6
|
+
if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run lint';
|
|
7
|
+
else if (existsSync('yarn.lock')) cmd = 'yarn lint';
|
|
8
|
+
else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run lint';
|
|
9
|
+
|
|
10
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
let cmd = 'npm run start';
|
|
6
|
+
if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run start';
|
|
7
|
+
else if (existsSync('yarn.lock')) cmd = 'yarn start';
|
|
8
|
+
else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run start';
|
|
9
|
+
|
|
10
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
let cmd = 'npm run test';
|
|
6
|
+
if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run test';
|
|
7
|
+
else if (existsSync('yarn.lock')) cmd = 'yarn test';
|
|
8
|
+
else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run test';
|
|
9
|
+
|
|
10
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
let cmd = 'npm run build';
|
|
6
|
+
if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run build';
|
|
7
|
+
else if (existsSync('yarn.lock')) cmd = 'yarn build';
|
|
8
|
+
else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run build';
|
|
9
|
+
|
|
10
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
let cmd = 'npm run dev';
|
|
6
|
+
if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run dev';
|
|
7
|
+
else if (existsSync('yarn.lock')) cmd = 'yarn dev';
|
|
8
|
+
else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run dev';
|
|
9
|
+
|
|
10
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
import { rmSync } from 'node:fs';
|
|
3
|
+
import { existsSync, rmSync, readFileSync } from 'node:fs';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Detect the package manager actually in use instead of assuming npm —
|
|
6
|
+
// blindly deleting yarn.lock/pnpm-lock.yaml on a project that uses them
|
|
7
|
+
// and running `npm install` instead corrupts the project by introducing a
|
|
8
|
+
// conflicting lockfile.
|
|
9
|
+
function detectPkgManager() {
|
|
10
|
+
if (existsSync('pnpm-lock.yaml')) return 'pnpm';
|
|
11
|
+
if (existsSync('yarn.lock')) return 'yarn';
|
|
12
|
+
if (existsSync('bun.lockb') || existsSync('bun.lock')) return 'bun';
|
|
13
|
+
if (existsSync('package.json')) {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
|
|
16
|
+
if (['npm', 'yarn', 'pnpm', 'bun'].includes(pkg.packageManager?.split('@')[0])) {
|
|
17
|
+
return pkg.packageManager.split('@')[0];
|
|
18
|
+
}
|
|
19
|
+
} catch { /* ignore malformed package.json */ }
|
|
20
|
+
}
|
|
21
|
+
return 'npm';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const pm = detectPkgManager();
|
|
25
|
+
console.log(`Resetting __FRAMEWORK__ dependencies (package manager: ${pm})...`);
|
|
6
26
|
rmSync('node_modules', { recursive: true, force: true });
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
27
|
+
|
|
28
|
+
const lockfiles = { npm: ['package-lock.json'], yarn: ['yarn.lock'], pnpm: ['pnpm-lock.yaml'], bun: ['bun.lockb', 'bun.lock'] };
|
|
29
|
+
for (const f of lockfiles[pm]) rmSync(f, { force: true });
|
|
30
|
+
|
|
31
|
+
const installCmd = { npm: 'npm install', yarn: 'yarn', pnpm: 'pnpm install', bun: 'bun install' };
|
|
32
|
+
execSync(installCmd[pm], { stdio: 'inherit' });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
let cmd = 'npm run lint';
|
|
6
|
+
if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run lint';
|
|
7
|
+
else if (existsSync('yarn.lock')) cmd = 'yarn lint';
|
|
8
|
+
else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run lint';
|
|
9
|
+
|
|
10
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
let cmd = 'npm run test';
|
|
6
|
+
if (existsSync('pnpm-lock.yaml')) cmd = 'pnpm run test';
|
|
7
|
+
else if (existsSync('yarn.lock')) cmd = 'yarn test';
|
|
8
|
+
else if (existsSync('bun.lockb') || existsSync('bun.lock')) cmd = 'bun run test';
|
|
9
|
+
|
|
10
|
+
execSync(cmd, { stdio: 'inherit' });
|