@geastack/cli 0.1.7 → 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,13 +43,15 @@ 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"]
48
52
 
49
53
  auto["Automation flags"] -.-> starter
50
- auto --> autoEmpty["--starter empty --yes"]
54
+ auto --> autoEmpty["--starter blank --yes"]
51
55
  auto --> autoExample["--starter example --example watch"]
52
56
  ```
53
57
 
@@ -157,6 +161,6 @@ First implementation is in place:
157
161
  - npm-resolved embedded board builds for ESP32 and RP2350;
158
162
  - `flash`, `monitor`, WiFi OTA, and BLE OTA through `@geastack/targets`;
159
163
  - `list` and `inspect` helpers;
160
- - `create-geastack` with a bundled counter starter, an empty starter, and a
164
+ - `create-geastack` with a bundled counter starter, a blank application, and a
161
165
  GitHub-backed rich example flow for web, embedded, GeaOS, iOS, macOS, and Android apps,
162
166
  all with `.gea/boards.json`.
@@ -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,
@@ -69,7 +70,7 @@ Automation can skip prompts:
69
70
 
70
71
  ```sh
71
72
  npx @geastack/create-geastack my-app --starter counter
72
- npx @geastack/create-geastack my-app --starter empty --yes
73
+ npx @geastack/create-geastack my-app --starter blank --yes
73
74
  npx @geastack/create-geastack my-app --starter example --example watch
74
75
  npx @geastack/create-geastack my-ios-app --starter example --example ios-native-showcase
75
76
  npx gea build --target ios --mode simulator
package/docs/SPEC.md CHANGED
@@ -120,7 +120,7 @@ or optional dependency is missing.
120
120
  Scaffolds a new app folder with:
121
121
 
122
122
  - `package.json` containing a `gea` manifest;
123
- - a bundled counter starter, an empty starter, or a selected GitHub example;
123
+ - a bundled counter starter, a blank application, or a selected GitHub example;
124
124
  - `index.tsx` or `index.ts`;
125
125
  - `tsconfig.json`;
126
126
  - `vite.config.ts`;
@@ -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.7",
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,
@@ -106,7 +106,7 @@ export async function runCreateGeastack(argv, io = {}) {
106
106
  stdout(`Created ${displayName} at ${targetDir}`)
107
107
  if (starter.kind === 'bundled') stdout(`Starter: ${starter.starter.name}`)
108
108
  else if (starter.kind === 'example') stdout(`Example: fetched ${starter.example.name}`)
109
- else stdout('Starter: empty app')
109
+ else stdout('Starter: blank application')
110
110
  if (installDependencies) stdout(`Next: cd ${targetDir} && npx gea setup`)
111
111
  else stdout(`Next: cd ${targetDir} && npm install && npx gea setup`)
112
112
  return 0
@@ -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
@@ -134,10 +150,10 @@ async function resolveStarter(ctx, parsed, io) {
134
150
  return { kind: 'bundled', starter }
135
151
  }
136
152
  if (mode !== 'example') {
137
- fail(`Unknown starter '${mode}'. Expected counter, empty, or example.`, ExitCode.usage)
153
+ fail(`Unknown starter '${mode}'. Expected counter, blank, or example.`, ExitCode.usage)
138
154
  }
139
155
  if (examples.length === 0) {
140
- fail('No GitHub examples are available. Use --starter counter or --starter empty.', ExitCode.usage)
156
+ fail('No GitHub examples are available. Use --starter counter or --starter blank.', ExitCode.usage)
141
157
  }
142
158
 
143
159
  const requestedExample = option(parsed, 'example') || option(parsed, 'from-example') || ''
@@ -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')