@geastack/cli 0.1.5 → 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
@@ -29,12 +29,12 @@ flowchart TD
29
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"]
@@ -38,13 +38,16 @@ cd my-app
38
38
  When the wizard asks what to create, choose:
39
39
 
40
40
  ```text
41
- ? What do you want to create?
42
- Counter starter - bundled minimal JSX app
43
- Empty app - minimal blank Gea app
44
- 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.
45
48
  ```
46
49
 
47
- For the fastest first flash, choose `Counter starter`.
50
+ For the fastest first flash, choose `Embedded component counter`.
48
51
 
49
52
  That creates a tiny JSX app and installs all of its project dependencies.
50
53
 
@@ -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
@@ -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.5",
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": {
@@ -42,10 +42,7 @@ export async function runCreateGeastack(argv, io = {}) {
42
42
  const coreDependency = option(parsed, 'core-dependency') || '^0.1.4'
43
43
  const cliDependency = option(parsed, 'cli-dependency') || `^${cliVersion}`
44
44
  const starter = await resolveStarter(ctx, parsed, io)
45
- const entry = projectRelativePath(option(parsed, 'entry') || starter.starter?.entry || starter.example?.entry || 'index.tsx', 'entry')
46
- if (option(parsed, 'entry') && starter.kind !== 'empty') {
47
- fail('--entry can only be used with --starter empty.', ExitCode.usage)
48
- }
45
+ const entry = projectRelativePath(starter.starter?.entry || starter.example?.entry || 'index.tsx', 'entry')
49
46
 
50
47
  fs.mkdirSync(targetDir, { recursive: true })
51
48
  if (starter.kind === 'empty') {
@@ -70,26 +67,28 @@ export async function runCreateGeastack(argv, io = {}) {
70
67
 
71
68
  const sourcePackage = readPackageJson(targetDir)
72
69
  const sourceManifest = sourcePackage.gea || starter.starter?.manifest || starter.example?.manifest || {}
73
- const targets = optionList(parsed, 'targets').length === 0
74
- ? targetManifestFromObject(sourceManifest.targets)
75
- : 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)
76
76
  writeJson(path.join(targetDir, 'package.json'), packageJson({
77
77
  appId,
78
78
  displayName,
79
79
  targets,
80
80
  entry,
81
- bleOta: flag(parsed, 'ble-ota'),
82
81
  coreDependency,
83
82
  cliDependency,
84
83
  sourcePackage,
85
84
  sourceManifest
86
85
  }))
87
- ensureFile(path.join(targetDir, 'index.html'), () => indexHtml(displayName, entry))
86
+ if (targets.web) ensureFile(path.join(targetDir, 'index.html'), () => indexHtml(displayName, entry))
88
87
  fs.mkdirSync(path.join(targetDir, '.gea'), { recursive: true })
89
88
  writeJson(path.join(targetDir, '.gea', 'boards.json'), {})
90
89
  ensureJson(path.join(targetDir, 'tsconfig.json'), tsconfigJson)
91
- ensureFile(path.join(targetDir, 'vite.config.ts'), viteConfigTs)
92
- 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 }))
93
92
 
94
93
  const installDependencies = shouldInstallDependencies(parsed, io)
95
94
  if (installDependencies) {
@@ -157,11 +156,23 @@ async function resolveStarter(ctx, parsed, io) {
157
156
  async function chooseStarterMode({ interactive, prompt, bundled, examples }) {
158
157
  if (!interactive) return bundled.length > 0 ? 'counter' : 'empty'
159
158
  return choose(prompt, {
160
- message: 'Starter app',
159
+ message: 'What do you want to build?',
161
160
  choices: [
162
- ...(bundled.length > 0 ? [{ value: 'counter', label: 'Counter starter - bundled minimal JSX app' }] : []),
163
- { value: 'empty', label: 'Empty app - minimal blank Gea app' },
164
- ...(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
+ }] : [])
165
176
  ],
166
177
  defaultValue: bundled.length > 0 ? 'counter' : 'empty'
167
178
  })
@@ -213,7 +224,7 @@ function targetManifestFromObject(targets = {}) {
213
224
  }
214
225
  }
215
226
 
216
- function packageJson({ appId, displayName, targets, entry, bleOta, coreDependency, cliDependency, sourcePackage = {}, sourceManifest = {} }) {
227
+ function packageJson({ appId, displayName, targets, entry, coreDependency, cliDependency, sourcePackage = {}, sourceManifest = {} }) {
217
228
  const geaManifest = {
218
229
  ...sourceManifest,
219
230
  id: appId,
@@ -221,7 +232,6 @@ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependenc
221
232
  entry,
222
233
  targets
223
234
  }
224
- if (bleOta) geaManifest.ota = { ...(geaManifest.ota || {}), ble: true }
225
235
  if (!geaManifest.runtime || geaManifest.runtime === 'gea') delete geaManifest.runtime
226
236
 
227
237
  return {
@@ -231,8 +241,7 @@ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependenc
231
241
  type: 'module',
232
242
  scripts: {
233
243
  ...(sourcePackage.scripts || {}),
234
- dev: 'gea dev',
235
- build: 'gea build --target web',
244
+ ...(targets.web ? { dev: 'gea dev', build: 'gea build --target web' } : {}),
236
245
  check: 'tsc --noEmit'
237
246
  },
238
247
  dependencies: {
@@ -244,7 +253,7 @@ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependenc
244
253
  ...(sourcePackage.devDependencies || {}),
245
254
  '@geastack/cli': cliDependency,
246
255
  typescript: sourcePackage.devDependencies?.typescript || 'latest',
247
- vite: sourcePackage.devDependencies?.vite || 'latest'
256
+ ...(targets.web ? { vite: sourcePackage.devDependencies?.vite || 'latest' } : {})
248
257
  },
249
258
  gea: geaManifest,
250
259
  license: 'MIT'
@@ -358,12 +367,22 @@ export default defineConfig({
358
367
  `
359
368
  }
360
369
 
361
- function readme({ appId, displayName, starter }) {
370
+ function readme({ appId, displayName, starter, targets }) {
362
371
  const starterLine = starter.kind === 'example'
363
372
  ? `Started from GitHub example: \`${starter.example.name}\` (\`${starter.example.id}\`).`
364
373
  : starter.kind === 'bundled'
365
374
  ? `Started from bundled starter: \`${starter.starter.name}\` (\`${starter.starter.id}\`).`
366
- : '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`
367
386
  return `# ${displayName}
368
387
 
369
388
  GeaStack app id: \`${appId}\`.
@@ -371,9 +390,7 @@ GeaStack app id: \`${appId}\`.
371
390
  ${starterLine}
372
391
 
373
392
  \`\`\`sh
374
- npx gea setup
375
- npx gea dev
376
- npx gea build --target web
393
+ ${commands}
377
394
  \`\`\`
378
395
  `
379
396
  }
@@ -441,8 +458,6 @@ Options:
441
458
  --examples-repo <git-url-or-local-path>
442
459
  --examples-ref <git-ref>
443
460
  --targets web,esp32,rp2350,geaos,macos,ios,android
444
- --entry <relative-path>
445
- --ble-ota
446
461
  --core-dependency <specifier>
447
462
  --cli-dependency <specifier>
448
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
- }