@geastack/cli 0.1.4 → 0.1.6

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
@@ -26,15 +26,15 @@ gea inspect --json
26
26
 
27
27
  ```mermaid
28
28
  flowchart TD
29
- create["npx @geastack/create-geastack my-app"] --> identity["Resolve app identity<br/>argument, optional --id, optional --name"]
29
+ create["gea create my-app"] --> identity["Resolve app identity<br/>argument, optional --id, optional --name"]
30
30
  identity --> starter{"Starter app?"}
31
31
 
32
- starter -->|"Counter starter"| counter["Copy bundled minimal JSX counter"]
32
+ starter -->|"Embedded component counter"| counter["Copy the touchscreen counter used in the embedded tutorial"]
33
33
 
34
- starter -->|"Empty app"| empty["Generate minimal app files"]
34
+ starter -->|"Blank application"| empty["Generate a minimal browser application"]
35
35
  empty --> emptyFiles["index.tsx, styles.css, index.html,<br/>tsconfig.json, vite.config.ts"]
36
36
 
37
- starter -->|"Rich example"| pickExample["Pick from hard-coded example list<br/>web, ESP32, GeaOS, iOS, macOS, Android"]
37
+ starter -->|"Example application"| pickExample["Pick from the example gallery<br/>web, ESP32, GeaOS, iOS, macOS, Android"]
38
38
  pickExample --> fetchExample["Fetch selected app from GitHub"]
39
39
  fetchExample --> copyExample["Copy fetched example files"]
40
40
  copyExample --> rewriteExample["Rewrite package name and gea manifest"]
@@ -30,27 +30,31 @@ You do not clone GeaStack.
30
30
 
31
31
  ```sh
32
32
  npm login
33
- npx @geastack/create-geastack my-app
33
+ npm install --global @geastack/cli
34
+ gea create my-app
34
35
  cd my-app
35
36
  ```
36
37
 
37
38
  When the wizard asks what to create, choose:
38
39
 
39
40
  ```text
40
- ? What do you want to create?
41
- Counter starter - bundled minimal JSX app
42
- Empty app - minimal blank Gea app
43
- Rich example - fetch from GitHub examples repo
41
+ ? What do you want to build?
42
+ Embedded component counter
43
+ Touchscreen +/− counter with local state and BLE updates, ready for ESP32.
44
+ Blank application
45
+ A minimal screen for building your own Gea application.
46
+ Example application
47
+ Choose a complete application from the GeaStack example gallery.
44
48
  ```
45
49
 
46
- For the fastest first flash, choose `Counter starter`.
50
+ For the fastest first flash, choose `Embedded component counter`.
47
51
 
48
- That creates a tiny JSX app with one store and installs the project-local Gea CLI, so inside the app you can run `npx gea`.
52
+ That creates a tiny JSX app and installs all of its project dependencies.
49
53
 
50
54
  ## 3. Set Up The Board
51
55
 
52
56
  ```sh
53
- npx gea setup
57
+ gea setup
54
58
  ```
55
59
 
56
60
  Choose:
@@ -82,7 +86,7 @@ If ESP-IDF was installed, the CLI prints this for future shells:
82
86
  . "$HOME/esp/esp-idf/export.sh"
83
87
  ```
84
88
 
85
- You do not need to run that before every command unless `npx gea doctor` says ESP-IDF is not active.
89
+ You do not need to run that before every command unless `gea doctor` says ESP-IDF is not active.
86
90
 
87
91
  If the wizard finds a connected serial device, pick it. If no board is plugged in yet, that is fine; you can provide the port later.
88
92
 
@@ -93,13 +97,13 @@ The wizard writes the board config into your app at `.gea/boards.json` and initi
93
97
  Flash and open the serial monitor:
94
98
 
95
99
  ```sh
96
- npx gea flash --board amoled --monitor
100
+ gea flash --board amoled --monitor
97
101
  ```
98
102
 
99
103
  If you need to specify the serial port manually:
100
104
 
101
105
  ```sh
102
- npx gea flash --board amoled --port /dev/cu.usbmodemXXXX --monitor
106
+ gea flash --board amoled --port /dev/cu.usbmodemXXXX --monitor
103
107
  ```
104
108
 
105
109
  ## Rich Examples
@@ -109,10 +113,10 @@ The bundled starter is intentionally tiny. Rich examples live in the GeaStack ex
109
113
  For example:
110
114
 
111
115
  ```sh
112
- npx @geastack/create-geastack watch-demo --starter example --example watch
116
+ gea create watch-demo --starter example --example watch
113
117
  cd watch-demo
114
- npx gea setup
115
- npx gea flash --board amoled --monitor
118
+ gea setup
119
+ gea flash --board amoled --monitor
116
120
  ```
117
121
 
118
122
  The CLI owns the example catalog, so the user does not need to clone the examples repo.
@@ -122,7 +126,7 @@ The CLI owns the example catalog, so the user does not need to clone the example
122
126
  Run:
123
127
 
124
128
  ```sh
125
- npx gea doctor
129
+ gea doctor
126
130
  ```
127
131
 
128
132
  Most first-run issues are one of:
@@ -44,20 +44,20 @@ npx @geastack/create-geastack my-app
44
44
  In an interactive terminal, `create-geastack` asks:
45
45
 
46
46
  ```text
47
- Starter app
48
- 1. Counter starter - bundled minimal JSX app
49
- 2. Empty app - minimal blank Gea app
50
- 3. Rich example - fetch from GitHub examples repo
47
+ What do you want to build?
48
+ 1. Embedded component counter
49
+ 2. Blank application
50
+ 3. Example application
51
51
  ```
52
52
 
53
- `Counter starter` is the only bundled source starter. It is intentionally tiny:
54
- JSX, one `store.ts`, and enough styling to run.
53
+ `Embedded component counter` is the bundled tutorial starter. It uses local
54
+ component state, targets ESP32, and enables BLE updates.
55
55
 
56
- `Empty app` creates the smallest starter: `index.tsx`, `styles.css`,
56
+ `Blank application` creates the smallest browser starter: `index.tsx`, `styles.css`,
57
57
  `index.html`, `tsconfig.json`, `vite.config.ts`, `package.json`, and
58
58
  `.gea/boards.json`.
59
59
 
60
- `Rich example` opens an example picker populated from the hard-coded
60
+ `Example application` opens an example picker populated from the hard-coded
61
61
  `examples/catalog.json` shipped inside `@geastack/cli`. The catalog contains web,
62
62
  ESP32, GeaOS, iOS, macOS, and Android examples with names, descriptions, target
63
63
  flags, and GitHub paths. When the user selects an example, the CLI fetches that app from
package/docs/SPEC.md CHANGED
@@ -115,7 +115,7 @@ Minimum checks:
115
115
  Human-readable output should point to [SETUP.md](SETUP.md) whenever a required
116
116
  or optional dependency is missing.
117
117
 
118
- ### `create-geastack`
118
+ ### `gea create`
119
119
 
120
120
  Scaffolds a new app folder with:
121
121
 
@@ -130,9 +130,10 @@ Scaffolds a new app folder with:
130
130
 
131
131
  Interactive starter choices must explain the tradeoff in-line:
132
132
 
133
- - `Counter starter`: copy the bundled minimal JSX counter with one tiny store.
134
- - `Empty app`: generate the smallest blank app with no example-specific code.
135
- - `Rich example`: fetch a selected app from `geastack/examples`, then rewrite
133
+ - `Embedded component counter`: create a touchscreen counter with local
134
+ component state, an ESP32 target, and BLE updates enabled.
135
+ - `Blank application`: generate a minimal browser application as a clean slate.
136
+ - `Example application`: fetch a selected app from `geastack/examples`, then rewrite
136
137
  package name, app id, dependencies, and board config for the new project.
137
138
 
138
139
  Rich example choices come from the hard-coded `examples/catalog.json`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geastack/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "Command-line front door for GeaStack apps, targets, and local toolchains.",
6
6
  "publishConfig": {
@@ -15,6 +15,8 @@ import {
15
15
  formatStarterChoice
16
16
  } from './starter-catalog.mjs'
17
17
 
18
+ const cliVersion = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
19
+
18
20
  export async function runCreateGeastack(argv, io = {}) {
19
21
  const parsed = parseArgs(argv)
20
22
  const stdout = io.stdout || console.log
@@ -38,12 +40,9 @@ export async function runCreateGeastack(argv, io = {}) {
38
40
  const ctx = createContext(parsed, env, cwd)
39
41
  const displayName = option(parsed, 'name', titleFromId(appId))
40
42
  const coreDependency = option(parsed, 'core-dependency') || '^0.1.4'
41
- const cliDependency = option(parsed, 'cli-dependency') || '^0.1.4'
43
+ const cliDependency = option(parsed, 'cli-dependency') || `^${cliVersion}`
42
44
  const starter = await resolveStarter(ctx, parsed, io)
43
- const entry = projectRelativePath(option(parsed, 'entry') || starter.starter?.entry || starter.example?.entry || 'index.tsx', 'entry')
44
- if (option(parsed, 'entry') && starter.kind !== 'empty') {
45
- fail('--entry can only be used with --starter empty.', ExitCode.usage)
46
- }
45
+ const entry = projectRelativePath(starter.starter?.entry || starter.example?.entry || 'index.tsx', 'entry')
47
46
 
48
47
  fs.mkdirSync(targetDir, { recursive: true })
49
48
  if (starter.kind === 'empty') {
@@ -68,26 +67,28 @@ export async function runCreateGeastack(argv, io = {}) {
68
67
 
69
68
  const sourcePackage = readPackageJson(targetDir)
70
69
  const sourceManifest = sourcePackage.gea || starter.starter?.manifest || starter.example?.manifest || {}
71
- const targets = optionList(parsed, 'targets').length === 0
72
- ? targetManifestFromObject(sourceManifest.targets)
73
- : targetManifest(optionList(parsed, 'targets'))
70
+ const requestedTargets = optionList(parsed, 'targets')
71
+ const targets = requestedTargets.length > 0
72
+ ? targetManifest(requestedTargets)
73
+ : starter.kind === 'empty'
74
+ ? targetManifest(['web'])
75
+ : targetManifestFromObject(sourceManifest.targets)
74
76
  writeJson(path.join(targetDir, 'package.json'), packageJson({
75
77
  appId,
76
78
  displayName,
77
79
  targets,
78
80
  entry,
79
- bleOta: flag(parsed, 'ble-ota'),
80
81
  coreDependency,
81
82
  cliDependency,
82
83
  sourcePackage,
83
84
  sourceManifest
84
85
  }))
85
- ensureFile(path.join(targetDir, 'index.html'), () => indexHtml(displayName, entry))
86
+ if (targets.web) ensureFile(path.join(targetDir, 'index.html'), () => indexHtml(displayName, entry))
86
87
  fs.mkdirSync(path.join(targetDir, '.gea'), { recursive: true })
87
88
  writeJson(path.join(targetDir, '.gea', 'boards.json'), {})
88
89
  ensureJson(path.join(targetDir, 'tsconfig.json'), tsconfigJson)
89
- ensureFile(path.join(targetDir, 'vite.config.ts'), viteConfigTs)
90
- fs.writeFileSync(path.join(targetDir, 'README.md'), readme({ appId, displayName, starter }))
90
+ if (targets.web) ensureFile(path.join(targetDir, 'vite.config.ts'), viteConfigTs)
91
+ fs.writeFileSync(path.join(targetDir, 'README.md'), readme({ appId, displayName, starter, targets }))
91
92
 
92
93
  const installDependencies = shouldInstallDependencies(parsed, io)
93
94
  if (installDependencies) {
@@ -155,11 +156,23 @@ async function resolveStarter(ctx, parsed, io) {
155
156
  async function chooseStarterMode({ interactive, prompt, bundled, examples }) {
156
157
  if (!interactive) return bundled.length > 0 ? 'counter' : 'empty'
157
158
  return choose(prompt, {
158
- message: 'Starter app',
159
+ message: 'What do you want to build?',
159
160
  choices: [
160
- ...(bundled.length > 0 ? [{ value: 'counter', label: 'Counter starter - bundled minimal JSX app' }] : []),
161
- { value: 'empty', label: 'Empty app - minimal blank Gea app' },
162
- ...(examples.length > 0 ? [{ value: 'example', label: 'Rich example - fetch from GitHub examples repo' }] : [])
161
+ ...(bundled.length > 0 ? [{
162
+ value: 'counter',
163
+ label: 'Embedded component counter',
164
+ description: 'Touchscreen +/− counter with local state and BLE updates, ready for ESP32.'
165
+ }] : []),
166
+ {
167
+ value: 'empty',
168
+ label: 'Blank application',
169
+ description: 'A minimal screen for building your own Gea application.'
170
+ },
171
+ ...(examples.length > 0 ? [{
172
+ value: 'example',
173
+ label: 'Example application',
174
+ description: 'Choose a complete application from the GeaStack example gallery.'
175
+ }] : [])
163
176
  ],
164
177
  defaultValue: bundled.length > 0 ? 'counter' : 'empty'
165
178
  })
@@ -211,7 +224,7 @@ function targetManifestFromObject(targets = {}) {
211
224
  }
212
225
  }
213
226
 
214
- function packageJson({ appId, displayName, targets, entry, bleOta, coreDependency, cliDependency, sourcePackage = {}, sourceManifest = {} }) {
227
+ function packageJson({ appId, displayName, targets, entry, coreDependency, cliDependency, sourcePackage = {}, sourceManifest = {} }) {
215
228
  const geaManifest = {
216
229
  ...sourceManifest,
217
230
  id: appId,
@@ -219,7 +232,6 @@ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependenc
219
232
  entry,
220
233
  targets
221
234
  }
222
- if (bleOta) geaManifest.ota = { ...(geaManifest.ota || {}), ble: true }
223
235
  if (!geaManifest.runtime || geaManifest.runtime === 'gea') delete geaManifest.runtime
224
236
 
225
237
  return {
@@ -229,8 +241,7 @@ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependenc
229
241
  type: 'module',
230
242
  scripts: {
231
243
  ...(sourcePackage.scripts || {}),
232
- dev: 'gea dev',
233
- build: 'gea build --target web',
244
+ ...(targets.web ? { dev: 'gea dev', build: 'gea build --target web' } : {}),
234
245
  check: 'tsc --noEmit'
235
246
  },
236
247
  dependencies: {
@@ -242,7 +253,7 @@ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependenc
242
253
  ...(sourcePackage.devDependencies || {}),
243
254
  '@geastack/cli': cliDependency,
244
255
  typescript: sourcePackage.devDependencies?.typescript || 'latest',
245
- vite: sourcePackage.devDependencies?.vite || 'latest'
256
+ ...(targets.web ? { vite: sourcePackage.devDependencies?.vite || 'latest' } : {})
246
257
  },
247
258
  gea: geaManifest,
248
259
  license: 'MIT'
@@ -356,12 +367,22 @@ export default defineConfig({
356
367
  `
357
368
  }
358
369
 
359
- function readme({ appId, displayName, starter }) {
370
+ function readme({ appId, displayName, starter, targets }) {
360
371
  const starterLine = starter.kind === 'example'
361
372
  ? `Started from GitHub example: \`${starter.example.name}\` (\`${starter.example.id}\`).`
362
373
  : starter.kind === 'bundled'
363
374
  ? `Started from bundled starter: \`${starter.starter.name}\` (\`${starter.starter.id}\`).`
364
- : 'Started from an empty app.'
375
+ : 'Started from a blank app.'
376
+ const commands = targets.web
377
+ ? `npx gea setup
378
+ npx gea dev
379
+ npx gea build --target web`
380
+ : targets.esp32
381
+ ? `npx gea setup
382
+ npx gea build --board <alias>
383
+ npx gea flash --board <alias> --monitor`
384
+ : `npx gea setup
385
+ npx gea build`
365
386
  return `# ${displayName}
366
387
 
367
388
  GeaStack app id: \`${appId}\`.
@@ -369,9 +390,7 @@ GeaStack app id: \`${appId}\`.
369
390
  ${starterLine}
370
391
 
371
392
  \`\`\`sh
372
- npx gea setup
373
- npx gea dev
374
- npx gea build --target web
393
+ ${commands}
375
394
  \`\`\`
376
395
  `
377
396
  }
@@ -439,8 +458,6 @@ Options:
439
458
  --examples-repo <git-url-or-local-path>
440
459
  --examples-ref <git-ref>
441
460
  --targets web,esp32,rp2350,geaos,macos,ios,android
442
- --entry <relative-path>
443
- --ble-ota
444
461
  --core-dependency <specifier>
445
462
  --cli-dependency <specifier>
446
463
  --install / --no-install
@@ -3,30 +3,31 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "type": "module",
6
- "description": "Minimal JSX counter with one tiny store.",
6
+ "description": "Touchscreen counter with component-local reactive state.",
7
7
  "scripts": {
8
- "build": "vite build",
9
8
  "check": "tsc --noEmit"
10
9
  },
11
10
  "dependencies": {
12
- "@geajs/core": "^1.3.0"
11
+ "@geajs/core": "latest"
13
12
  },
14
13
  "devDependencies": {
15
- "typescript": "latest",
16
- "vite": "latest"
14
+ "typescript": "latest"
17
15
  },
18
16
  "gea": {
19
17
  "id": "counter",
20
- "name": "Counter",
21
- "description": "Minimal JSX counter with one tiny store.",
22
- "entry": "index.tsx",
18
+ "name": "Component Counter",
19
+ "description": "Touchscreen counter with component-local reactive state.",
20
+ "entry": "src/index.tsx",
23
21
  "targets": {
24
- "web": true,
22
+ "web": false,
25
23
  "esp32": true,
26
- "rp2350": true,
27
- "geaos": true,
24
+ "rp2350": false,
25
+ "geaos": false,
28
26
  "macos": false,
29
27
  "ios": false
28
+ },
29
+ "ota": {
30
+ "ble": true
30
31
  }
31
32
  }
32
33
  }
@@ -0,0 +1,28 @@
1
+ import { ReactiveComponent } from '@geastack/core'
2
+ import './styles.css'
3
+
4
+ export class App extends ReactiveComponent {
5
+ count = 0
6
+
7
+ decrement() {
8
+ this.count = this.count - 1
9
+ }
10
+
11
+ increment() {
12
+ this.count = this.count + 1
13
+ }
14
+
15
+ template() {
16
+ return (
17
+ <div class="screen">
18
+ <span class="eyebrow">GEASTACK</span>
19
+ <span class="title">Component state</span>
20
+ <span class="count">{this.count}</span>
21
+ <div class="controls">
22
+ <button class="button secondary" onClick={() => this.decrement()}>−</button>
23
+ <button class="button primary" onClick={() => this.increment()}>+</button>
24
+ </div>
25
+ </div>
26
+ )
27
+ }
28
+ }
@@ -0,0 +1,4 @@
1
+ import { mount } from '@geastack/core'
2
+ import { App } from './App'
3
+
4
+ mount(App)
@@ -0,0 +1,20 @@
1
+ .screen {
2
+ display: flex;
3
+ flex-direction: column;
4
+ width: 100vw;
5
+ height: 100vh;
6
+ padding: 28px;
7
+ gap: 16px;
8
+ align-items: center;
9
+ justify-content: center;
10
+ background-color: #080b12;
11
+ color: #f8fafc;
12
+ }
13
+
14
+ .eyebrow { color: #2dd4bf; font-size: 13px; }
15
+ .title { color: #cbd5e1; font-size: 22px; }
16
+ .count { color: #ffffff; font-size: 76px; line-height: 1; }
17
+ .controls { display: flex; flex-direction: row; gap: 16px; }
18
+ .button { width: 92px; height: 58px; border-radius: 14px; border-width: 2px; font-size: 30px; }
19
+ .secondary { color: #f8fafc; background-color: #1e293b; border-color: #475569; }
20
+ .primary { color: #042f2e; background-color: #2dd4bf; border-color: #5eead4; }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "skipLibCheck": true,
10
+ "jsx": "preserve",
11
+ "types": []
12
+ },
13
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"]
14
+ }
@@ -3,15 +3,15 @@
3
3
  "starters": [
4
4
  {
5
5
  "id": "counter",
6
- "name": "Counter",
7
- "description": "Minimal JSX counter with one tiny store.",
6
+ "name": "Component Counter",
7
+ "description": "Touchscreen counter with component-local reactive state and BLE updates.",
8
8
  "path": "bundled/counter",
9
- "entry": "index.tsx",
9
+ "entry": "src/index.tsx",
10
10
  "targets": {
11
- "web": true,
11
+ "web": false,
12
12
  "esp32": true,
13
- "rp2350": true,
14
- "geaos": true,
13
+ "rp2350": false,
14
+ "geaos": false,
15
15
  "macos": false,
16
16
  "ios": false,
17
17
  "android": false
@@ -1,25 +0,0 @@
1
- import { mount } from '@geastack/core'
2
- import { counter } from './store'
3
- import './styles.css'
4
-
5
- function App() {
6
- function increment() {
7
- const value = counter.increment()
8
- const count = document.querySelector('[data-count]')
9
- if (count) count.textContent = String(value)
10
- }
11
-
12
- return (
13
- <body class="app">
14
- <main class="panel">
15
- <p class="eyebrow">GeaStack</p>
16
- <h1>Counter</h1>
17
- <button class="button" onClick={increment}>
18
- Count <span data-count>0</span>
19
- </button>
20
- </main>
21
- </body>
22
- )
23
- }
24
-
25
- mount(App)
@@ -1,7 +0,0 @@
1
- export const counter = {
2
- value: 0,
3
- increment() {
4
- this.value += 1
5
- return this.value
6
- }
7
- }
@@ -1,39 +0,0 @@
1
- .app {
2
- width: 100vw;
3
- height: 100vh;
4
- margin: 0;
5
- display: flex;
6
- align-items: center;
7
- justify-content: center;
8
- background: #101418;
9
- color: #f8fafc;
10
- font-family: Inter, system-ui, sans-serif;
11
- }
12
-
13
- .panel {
14
- width: min(320px, calc(100vw - 32px));
15
- padding: 24px;
16
- border: 1px solid #2dd4bf;
17
- background: #182026;
18
- }
19
-
20
- .eyebrow {
21
- margin: 0 0 8px;
22
- color: #2dd4bf;
23
- font-size: 12px;
24
- text-transform: uppercase;
25
- }
26
-
27
- h1 {
28
- margin: 0 0 16px;
29
- font-size: 28px;
30
- }
31
-
32
- .button {
33
- min-width: 120px;
34
- min-height: 40px;
35
- border: 0;
36
- background: #2dd4bf;
37
- color: #081018;
38
- font: inherit;
39
- }