@geastack/cli 0.1.8 → 0.1.9

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 CHANGED
@@ -31,8 +31,10 @@ flowchart TD
31
31
 
32
32
  starter -->|"Embedded component counter"| counter["Copy the touchscreen counter used in the embedded tutorial"]
33
33
 
34
- starter -->|"Blank application"| empty["Generate a minimal browser application"]
35
- empty --> emptyFiles["index.tsx, styles.css, index.html,<br/>tsconfig.json, vite.config.ts"]
34
+ starter -->|"Blank application"| emptyTarget{"Where should it run?"}
35
+ emptyTarget -->|"Web browser"| emptyWeb["Generate browser entry, HTML, and Vite config"]
36
+ emptyTarget -->|"ESP32 board"| emptyEsp["Generate native entry<br/>Ask whether to enable Bluetooth updates"]
37
+ emptyTarget -->|"Other native target"| emptyNative["Generate native entry for the selected platform"]
36
38
 
37
39
  starter -->|"Example application"| pickExample["Pick from the example gallery<br/>web, ESP32, GeaOS, iOS, macOS, Android"]
38
40
  pickExample --> fetchExample["Fetch selected app from GitHub"]
@@ -41,7 +43,9 @@ flowchart TD
41
43
 
42
44
  counter --> projectWiring["Add @geastack/core and @geastack/cli"]
43
45
  rewriteExample --> projectWiring["Add @geastack/core and @geastack/cli"]
44
- emptyFiles --> projectWiring
46
+ emptyWeb --> projectWiring
47
+ emptyEsp --> projectWiring
48
+ emptyNative --> projectWiring
45
49
  projectWiring --> boardConfig["Create .gea/boards.json"]
46
50
  boardConfig --> install["Install npm dependencies<br/>(interactive default)"]
47
51
  install --> setup["Next: npx gea setup"]
@@ -53,9 +53,10 @@ What do you want to build?
53
53
  `Embedded component counter` is the bundled tutorial starter. It uses local
54
54
  component state, targets ESP32, and enables BLE updates.
55
55
 
56
- `Blank application` creates the smallest browser starter: `index.tsx`, `styles.css`,
57
- `index.html`, `tsconfig.json`, `vite.config.ts`, `package.json`, and
58
- `.gea/boards.json`.
56
+ `Blank application` asks where the app should run. It creates the smallest
57
+ project for that target and, for ESP32, asks whether to enable wireless
58
+ firmware updates over Bluetooth. Browser-only files such as `index.html` and
59
+ `vite.config.ts` are added only when the web target is selected.
59
60
 
60
61
  `Example application` opens an example picker populated from the hard-coded
61
62
  `examples/catalog.json` shipped inside `@geastack/cli`. The catalog contains web,
package/docs/SPEC.md CHANGED
@@ -132,7 +132,8 @@ Interactive starter choices must explain the tradeoff in-line:
132
132
 
133
133
  - `Embedded component counter`: create a touchscreen counter with local
134
134
  component state, an ESP32 target, and BLE updates enabled.
135
- - `Blank application`: generate a minimal browser application as a clean slate.
135
+ - `Blank application`: ask where the app should run, generate the matching
136
+ minimal project, and ask whether to enable Bluetooth updates for ESP32.
136
137
  - `Example application`: fetch a selected app from `geastack/examples`, then rewrite
137
138
  package name, app id, dependencies, and board config for the new project.
138
139
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geastack/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "description": "Command-line front door for GeaStack apps, targets, and local toolchains.",
6
6
  "publishConfig": {
@@ -5,7 +5,7 @@ import { flag, option, optionList, parseArgs } from './args.mjs'
5
5
  import { createContext } from './context.mjs'
6
6
  import { ExitCode, fail } from './errors.mjs'
7
7
  import { exists, readJson, writeJson } from './fs-utils.mjs'
8
- import { canPrompt, choose, createPrompt } from './prompts.mjs'
8
+ import { canPrompt, choose, confirm, createPrompt } from './prompts.mjs'
9
9
  import { runExternal } from './run.mjs'
10
10
  import {
11
11
  copyStarterFiles,
@@ -67,12 +67,12 @@ export async function runCreateGeastack(argv, io = {}) {
67
67
  }
68
68
 
69
69
  const sourcePackage = readPackageJson(targetDir)
70
- const sourceManifest = sourcePackage.gea || starter.starter?.manifest || starter.example?.manifest || {}
70
+ const sourceManifest = sourcePackage.gea || starter.manifest || starter.starter?.manifest || starter.example?.manifest || {}
71
71
  const requestedTargets = optionList(parsed, 'targets')
72
72
  const targets = requestedTargets.length > 0
73
73
  ? targetManifest(requestedTargets)
74
74
  : starter.kind === 'empty'
75
- ? targetManifest(['web'])
75
+ ? targetManifest(starter.targets || ['web'])
76
76
  : targetManifestFromObject(sourceManifest.targets)
77
77
  writeJson(path.join(targetDir, 'package.json'), packageJson({
78
78
  appId,
@@ -121,7 +121,23 @@ async function resolveStarter(ctx, parsed, io) {
121
121
  try {
122
122
  const mode = requested || await chooseStarterMode({ interactive, prompt, bundled, examples })
123
123
 
124
- if (mode === 'empty') return { kind: 'empty' }
124
+ if (mode === 'empty') {
125
+ const requestedTargets = optionList(parsed, 'targets')
126
+ if (!interactive || requestedTargets.length > 0) return { kind: 'empty' }
127
+
128
+ const target = await chooseBlankTarget(prompt)
129
+ const enableBleOta = target === 'esp32'
130
+ ? await confirm(prompt, {
131
+ message: 'Enable wireless firmware updates over Bluetooth?',
132
+ defaultValue: true
133
+ })
134
+ : false
135
+ return {
136
+ kind: 'empty',
137
+ targets: [target],
138
+ manifest: enableBleOta ? { ota: { ble: true } } : {}
139
+ }
140
+ }
125
141
  if (mode === 'bundled' || mode === 'counter') {
126
142
  const requestedBundled = mode === 'counter' ? 'counter' : option(parsed, 'bundled') || option(parsed, 'starter-id') || ''
127
143
  const starter = requestedBundled
@@ -191,6 +207,50 @@ async function chooseExample({ interactive, prompt, examples }) {
191
207
  return examples.find((example) => example.id === id) || null
192
208
  }
193
209
 
210
+ async function chooseBlankTarget(prompt) {
211
+ return choose(prompt, {
212
+ message: 'Where should this application run?',
213
+ choices: [
214
+ {
215
+ value: 'web',
216
+ label: 'Web browser',
217
+ description: 'A browser app with a local development server.'
218
+ },
219
+ {
220
+ value: 'esp32',
221
+ label: 'ESP32 board',
222
+ description: 'Native firmware for a supported ESP32 device.'
223
+ },
224
+ {
225
+ value: 'rp2350',
226
+ label: 'RP2350 board',
227
+ description: 'Native firmware for a supported Raspberry Pi RP2350 device.'
228
+ },
229
+ {
230
+ value: 'geaos',
231
+ label: 'GeaOS device',
232
+ description: 'A native application for GeaOS.'
233
+ },
234
+ {
235
+ value: 'macos',
236
+ label: 'macOS',
237
+ description: 'A native Mac application.'
238
+ },
239
+ {
240
+ value: 'ios',
241
+ label: 'iPhone or iPad',
242
+ description: 'A native iOS application.'
243
+ },
244
+ {
245
+ value: 'android',
246
+ label: 'Android',
247
+ description: 'A native Android application.'
248
+ }
249
+ ],
250
+ defaultValue: 'web'
251
+ })
252
+ }
253
+
194
254
  function ensureWritableTarget(targetDir, force) {
195
255
  if (!exists(targetDir)) return
196
256
  const entries = fs.readdirSync(targetDir).filter((entry) => entry !== '.DS_Store')