xgem-cli 2.0.0-alpha.10 → 2.0.0-alpha.14

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.
Files changed (43) hide show
  1. package/README.md +37 -63
  2. package/bin/xgem +46 -15
  3. package/bin/xgem.js +22 -20
  4. package/lib/bootstrap.sh +165 -0
  5. package/lib/ci.sh +44 -0
  6. package/lib/create.sh +140 -71
  7. package/lib/flutter.sh +10 -8
  8. package/lib/git.sh +291 -29
  9. package/lib/registry.sh +45 -0
  10. package/lib/release.sh +185 -0
  11. package/lib/scaffold.sh +21 -2
  12. package/lib/status.sh +42 -0
  13. package/lib/utils.sh +11 -0
  14. package/lib/version.sh +1 -1
  15. package/lib-win/bootstrap.js +189 -0
  16. package/lib-win/ci.js +43 -0
  17. package/lib-win/git.js +275 -21
  18. package/lib-win/registry.js +43 -0
  19. package/lib-win/release.js +143 -0
  20. package/lib-win/scaffold.js +64 -2
  21. package/lib-win/status.js +53 -0
  22. package/lib-win/utils.js +6 -1
  23. package/package.json +1 -1
  24. package/templates/node/build.sh.tmpl +9 -1
  25. package/templates/node/hard-clean.sh.tmpl +34 -2
  26. package/templates/node/lint.sh.tmpl +10 -0
  27. package/templates/node/start.sh.tmpl +9 -1
  28. package/templates/node/test.sh.tmpl +10 -0
  29. package/templates/webframework/build.sh.tmpl +10 -1
  30. package/templates/webframework/dev.sh.tmpl +9 -1
  31. package/templates/webframework/hard-clean.sh.tmpl +35 -2
  32. package/templates/webframework/lint.sh.tmpl +10 -0
  33. package/templates/webframework/test.sh.tmpl +10 -0
  34. package/templates-win/node/build.mjs.tmpl +8 -2
  35. package/templates-win/node/hard-clean.mjs.tmpl +24 -5
  36. package/templates-win/node/lint.mjs.tmpl +10 -0
  37. package/templates-win/node/start.mjs.tmpl +8 -1
  38. package/templates-win/node/test.mjs.tmpl +10 -0
  39. package/templates-win/webframework/build.mjs.tmpl +8 -1
  40. package/templates-win/webframework/dev.mjs.tmpl +8 -1
  41. package/templates-win/webframework/hard-clean.mjs.tmpl +28 -5
  42. package/templates-win/webframework/lint.mjs.tmpl +10 -0
  43. package/templates-win/webframework/test.mjs.tmpl +10 -0
package/lib/create.sh CHANGED
@@ -1,29 +1,77 @@
1
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.
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` 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.
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
- # 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.
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 (template injection, dev server), but that never propagates back.
24
- # bin/xgem prints a "cd <name>" reminder using this at the very end.
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
+ registry_add "$(pwd)"
46
+ scaffold_inject_templates "$fw" "$CONFIG_DIR"
47
+ log_success "Successfully appended standard scripts for: $CONFIG_DIR/$fw"
48
+
49
+ # Some teams want the generated scripts checked in so collaborators get
50
+ # the same automation; others want them private/local-only. Ask instead
51
+ # of always gitignoring.
52
+ local ignore_choice
53
+ 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
54
+ ignore_choice=${ignore_choice:-ignore}
55
+
56
+ if [[ "$ignore_choice" == t* || "$ignore_choice" == T* ]]; then
57
+ if [ -f .gitignore ] && grep -qx "$CONFIG_DIR/" .gitignore; then
58
+ local tmp
59
+ tmp=$(mktemp)
60
+ grep -vx "$CONFIG_DIR/" .gitignore > "$tmp" && mv "$tmp" .gitignore
61
+ log_info "Removed existing $CONFIG_DIR/ entry from .gitignore since you chose to track it."
62
+ fi
63
+ log_success "$CONFIG_DIR/ will be tracked in git."
64
+ elif [ -f .gitignore ]; then
65
+ if ! grep -qx "$CONFIG_DIR/" .gitignore; then
66
+ echo -e "\n$CONFIG_DIR/" >> .gitignore
67
+ log_success "Added automation tracking to .gitignore"
68
+ fi
69
+ else
70
+ echo "$CONFIG_DIR/" > .gitignore
71
+ log_success "Created .gitignore and hidden tracking layer folder references."
72
+ fi
73
+ }
74
+
27
75
  # _prompt_project_location <human label> -> echoes "." or a new folder name.
28
76
  # Pure prompt-and-echo, no other stdout output, so it's safe to capture via
29
77
  # command substitution.
@@ -42,13 +90,13 @@ _prompt_project_location() {
42
90
  echo "$name"
43
91
  }
44
92
 
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
93
+ # _enter_project_dir <project_dir> — mkdir+cd if a new folder was chosen,
94
+ # and records XGEM_CREATED_NEW_FOLDER. No-op for "." (current directory).
95
+ _enter_project_dir() {
96
+ local project_dir=$1
97
+ [ "$project_dir" = "." ] && return 0
98
+ XGEM_CREATED_NEW_FOLDER="$project_dir"
99
+ mkdir -p "$project_dir" && cd "$project_dir" || die "Could not enter $project_dir"
52
100
  }
53
101
 
54
102
  # _launch_dev_server_and_open_browser <cmd...>
@@ -89,13 +137,11 @@ _launch_dev_server_and_open_browser() {
89
137
  _ask_launch_dev_server() {
90
138
  [ ${#XGEM_POST_INIT_LAUNCH_CMD[@]} -gt 0 ] || return 0
91
139
 
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.)
140
+ # `flutter run` isn't a web dev server — no localhost URL to detect/
141
+ # open, and it depends on fully-inherited interactive stdin for its
142
+ # own hot-reload keybindings, which the background+log-tail approach
143
+ # would break. Run it directly. (Whether to run at all was already
144
+ # confirmed in _flutter_offer_run_on_device, so this doesn't ask again.)
99
145
  if [ "${XGEM_POST_INIT_LAUNCH_CMD[0]}" = "flutter" ]; then
100
146
  "${XGEM_POST_INIT_LAUNCH_CMD[@]}"
101
147
  return 0
@@ -114,27 +160,33 @@ _create_react() {
114
160
  read -r -p "TypeScript or JavaScript? [ts/js] (default: ts): " lang
115
161
  lang=${lang:-ts}
116
162
  project_dir=$(_prompt_project_location "React")
117
- [ "$project_dir" != "." ] && XGEM_CREATED_NEW_FOLDER="$project_dir"
163
+ _enter_project_dir "$project_dir"
118
164
 
119
165
  if [[ "$variant" == cra* || "$variant" == CRA* ]]; then
120
166
  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+=(.)
167
+ # create-react-app has no flag to decouple scaffold-from-install
168
+ # (confirmed: nothing in --help), so this one atomic call does
169
+ # both — a Ctrl-C during its own install (it's known to be slow)
170
+ # would still lose xgem's bookkeeping below. Accepted residual
171
+ # risk for this specific, non-default, legacy-leaning option.
172
+ log_info "Create React App scaffolds and installs in one step and can take a few minutes — best to let it finish without interrupting."
173
+ local -a args=(create-react-app .)
123
174
  [ "$lang" = "ts" ] && args+=(--template typescript)
124
175
  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."
176
+ _xgem_bookkeeping react
177
+ return 0
135
178
  fi
136
179
 
137
- [ "$project_dir" != "." ] && { cd "$project_dir" || die "Could not enter $project_dir"; }
180
+ require_cmd npm "Install Node.js (npm ships with it): https://nodejs.org"
181
+ local template="react"
182
+ [ "$lang" = "ts" ] && template="react-ts"
183
+ # --no-immediate: create-vite's own "install deps and start dev server"
184
+ # prompt would otherwise block here (and if Ctrl-C'd, kill this whole
185
+ # xgem process). --overwrite: harmless here since the only files that
186
+ # could exist at this point are ones xgem itself hasn't created yet
187
+ # (bookkeeping happens after this call, deliberately).
188
+ npm create vite@latest . -- --template "$template" --no-immediate --overwrite || die "npm create vite failed."
189
+ _xgem_bookkeeping react
138
190
  log_info "Installing dependencies..."
139
191
  npm install
140
192
  XGEM_POST_INIT_LAUNCH_CMD=(npm run dev)
@@ -144,9 +196,13 @@ _create_vue() {
144
196
  require_cmd npm "Install Node.js (npm ships with it): https://nodejs.org"
145
197
  local project_dir
146
198
  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"; }
199
+ _enter_project_dir "$project_dir"
200
+ # create-vue doesn't install automatically (confirmed: no node_modules
201
+ # after it runs), so there's already a natural gap here to bookkeep in
202
+ # before the slow step. --force since this dir may be genuinely empty
203
+ # or (for "current directory") may already contain unrelated files.
204
+ npm create vue@latest . --force || die "npm create vue failed."
205
+ _xgem_bookkeeping vue
150
206
  log_info "Installing dependencies..."
151
207
  npm install
152
208
  XGEM_POST_INIT_LAUNCH_CMD=(npm run dev)
@@ -155,16 +211,20 @@ _create_vue() {
155
211
  _create_angular() {
156
212
  local project_dir
157
213
  project_dir=$(_prompt_project_location "Angular")
158
- [ "$project_dir" != "." ] && XGEM_CREATED_NEW_FOLDER="$project_dir"
214
+ _enter_project_dir "$project_dir"
159
215
  local -a ng_cmd=(ng)
160
216
  has_cmd ng || ng_cmd=(npx @angular/cli@latest)
161
217
 
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
218
+ # --skip-install: ng new's own install is a multi-minute step (Angular
219
+ # dependency trees are large) decoupling it is exactly what let a
220
+ # real user's Ctrl-C lose xgem's bookkeeping before this fix.
221
+ # --skip-git: xgem has its own `xgem git init`; ng new's own auto-
222
+ # commit would happen before .xgem-automate even exists.
223
+ "${ng_cmd[@]}" new "$(basename "$PWD")" --directory=. --skip-install --skip-git \
224
+ || die "ng new failed."
225
+ _xgem_bookkeeping angular
226
+ log_info "Installing dependencies..."
227
+ npm install
168
228
  XGEM_POST_INIT_LAUNCH_CMD=(npm run start)
169
229
  }
170
230
 
@@ -172,20 +232,26 @@ _create_next() {
172
232
  require_cmd npx "Install Node.js (npm ships with it): https://nodejs.org"
173
233
  local project_dir
174
234
  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"; }
235
+ _enter_project_dir "$project_dir"
236
+ # --skip-install: decouple from create-next-app's own install, same
237
+ # reasoning as Angular above.
238
+ npx create-next-app@latest . --skip-install || die "create-next-app failed."
239
+ _xgem_bookkeeping next
240
+ log_info "Installing dependencies..."
241
+ npm install
178
242
  XGEM_POST_INIT_LAUNCH_CMD=(npm run dev)
179
243
  }
180
244
 
181
245
  # Lighter path for frameworks without a dev-server/browser story — just the
182
- # ecosystem's own standard init command, no sub-wizard.
246
+ # ecosystem's own standard init command, no sub-wizard. These are all fast
247
+ # (no multi-minute install step), so bookkeeping-then-init is low-risk
248
+ # regardless of ordering, but it's kept bookkeeping-first for consistency.
183
249
  _create_simple() {
184
250
  local fw=$1
185
251
  local project_dir
186
252
  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"; }
253
+ _enter_project_dir "$project_dir"
254
+ _xgem_bookkeeping "$fw"
189
255
 
190
256
  case "$fw" in
191
257
  node)
@@ -206,13 +272,7 @@ _create_simple() {
206
272
  ;;
207
273
  rust)
208
274
  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
275
+ cargo init || die "cargo init failed."
216
276
  ;;
217
277
  swift)
218
278
  require_cmd swift "Install Swift: https://www.swift.org/install/"
@@ -235,15 +295,21 @@ EOF
235
295
  }
236
296
 
237
297
  # 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.
298
+ # Asks whether to create a new project or use what's already here. Either
299
+ # way, xgem's own bookkeeping (.xgem-automate + .gitignore) is guaranteed
300
+ # to happen for "existing", immediately (nothing to interrupt); for
301
+ # "new", each creator handles it internally at the earliest safe point,
302
+ # per the ordering principle documented at the top of this file.
241
303
  create_project_wizard() {
242
304
  local fw=$1
243
305
  local create_choice
244
306
  read -r -p "Create a brand-new $fw project, or use what's already in this directory? [new/existing] (default: existing): " create_choice
245
307
  create_choice=${create_choice:-existing}
246
- [[ "$create_choice" == n* || "$create_choice" == N* ]] || return 0
308
+
309
+ if [[ "$create_choice" != n* && "$create_choice" != N* ]]; then
310
+ _xgem_bookkeeping "$fw"
311
+ return 0
312
+ fi
247
313
 
248
314
  case "$fw" in
249
315
  react) _create_react ;;
@@ -252,6 +318,9 @@ create_project_wizard() {
252
318
  next) _create_next ;;
253
319
  flutter) create_flutter_project ;;
254
320
  node|python|go|rust|docker|swift) _create_simple "$fw" ;;
255
- *) log_debug "No creation wizard for '$fw'; using current directory as-is." ;;
321
+ *)
322
+ log_debug "No creation wizard for '$fw'; using current directory as-is."
323
+ _xgem_bookkeeping "$fw"
324
+ ;;
256
325
  esac
257
326
  }
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
- # `flutter create` needs a target dir; "." scaffolds into the current one.
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
- [ "$project_dir" != "." ] && XGEM_CREATED_NEW_FOLDER="$project_dir"
248
- if [ "$project_dir" = "." ]; then
249
- flutter create . || die "flutter create failed."
250
- else
251
- flutter create "$project_dir" || die "flutter create failed."
252
- cd "$project_dir" || die "Could not enter $project_dir"
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