@geastack/cli 0.1.5 → 0.1.7

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.7",
4
4
  "type": "module",
5
5
  "description": "Command-line front door for GeaStack apps, targets, and local toolchains.",
6
6
  "publishConfig": {
@@ -22,9 +22,10 @@ export async function runCreateGeastack(argv, io = {}) {
22
22
  const stdout = io.stdout || console.log
23
23
  const env = io.env || process.env
24
24
  const cwd = io.cwd || process.cwd()
25
+ const commandName = io.commandName || 'create-geastack'
25
26
 
26
27
  if (flag(parsed, 'help') || parsed.positionals[0] === 'help') {
27
- stdout(usage())
28
+ stdout(usage(commandName))
28
29
  return 0
29
30
  }
30
31
 
@@ -42,10 +43,7 @@ export async function runCreateGeastack(argv, io = {}) {
42
43
  const coreDependency = option(parsed, 'core-dependency') || '^0.1.4'
43
44
  const cliDependency = option(parsed, 'cli-dependency') || `^${cliVersion}`
44
45
  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
- }
46
+ const entry = projectRelativePath(starter.starter?.entry || starter.example?.entry || 'index.tsx', 'entry')
49
47
 
50
48
  fs.mkdirSync(targetDir, { recursive: true })
51
49
  if (starter.kind === 'empty') {
@@ -70,26 +68,28 @@ export async function runCreateGeastack(argv, io = {}) {
70
68
 
71
69
  const sourcePackage = readPackageJson(targetDir)
72
70
  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'))
71
+ const requestedTargets = optionList(parsed, 'targets')
72
+ const targets = requestedTargets.length > 0
73
+ ? targetManifest(requestedTargets)
74
+ : starter.kind === 'empty'
75
+ ? targetManifest(['web'])
76
+ : targetManifestFromObject(sourceManifest.targets)
76
77
  writeJson(path.join(targetDir, 'package.json'), packageJson({
77
78
  appId,
78
79
  displayName,
79
80
  targets,
80
81
  entry,
81
- bleOta: flag(parsed, 'ble-ota'),
82
82
  coreDependency,
83
83
  cliDependency,
84
84
  sourcePackage,
85
85
  sourceManifest
86
86
  }))
87
- ensureFile(path.join(targetDir, 'index.html'), () => indexHtml(displayName, entry))
87
+ if (targets.web) ensureFile(path.join(targetDir, 'index.html'), () => indexHtml(displayName, entry))
88
88
  fs.mkdirSync(path.join(targetDir, '.gea'), { recursive: true })
89
89
  writeJson(path.join(targetDir, '.gea', 'boards.json'), {})
90
90
  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 }))
91
+ if (targets.web) ensureFile(path.join(targetDir, 'vite.config.ts'), viteConfigTs)
92
+ fs.writeFileSync(path.join(targetDir, 'README.md'), readme({ appId, displayName, starter, targets }))
93
93
 
94
94
  const installDependencies = shouldInstallDependencies(parsed, io)
95
95
  if (installDependencies) {
@@ -157,11 +157,23 @@ async function resolveStarter(ctx, parsed, io) {
157
157
  async function chooseStarterMode({ interactive, prompt, bundled, examples }) {
158
158
  if (!interactive) return bundled.length > 0 ? 'counter' : 'empty'
159
159
  return choose(prompt, {
160
- message: 'Starter app',
160
+ message: 'What do you want to build?',
161
161
  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' }] : [])
162
+ ...(bundled.length > 0 ? [{
163
+ value: 'counter',
164
+ label: 'Embedded component counter',
165
+ description: 'Touchscreen +/− counter with local state and BLE updates, ready for ESP32.'
166
+ }] : []),
167
+ {
168
+ value: 'empty',
169
+ label: 'Blank application',
170
+ description: 'A minimal screen for building your own Gea application.'
171
+ },
172
+ ...(examples.length > 0 ? [{
173
+ value: 'example',
174
+ label: 'Example application',
175
+ description: 'Choose a complete application from the GeaStack example gallery.'
176
+ }] : [])
165
177
  ],
166
178
  defaultValue: bundled.length > 0 ? 'counter' : 'empty'
167
179
  })
@@ -213,7 +225,7 @@ function targetManifestFromObject(targets = {}) {
213
225
  }
214
226
  }
215
227
 
216
- function packageJson({ appId, displayName, targets, entry, bleOta, coreDependency, cliDependency, sourcePackage = {}, sourceManifest = {} }) {
228
+ function packageJson({ appId, displayName, targets, entry, coreDependency, cliDependency, sourcePackage = {}, sourceManifest = {} }) {
217
229
  const geaManifest = {
218
230
  ...sourceManifest,
219
231
  id: appId,
@@ -221,7 +233,6 @@ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependenc
221
233
  entry,
222
234
  targets
223
235
  }
224
- if (bleOta) geaManifest.ota = { ...(geaManifest.ota || {}), ble: true }
225
236
  if (!geaManifest.runtime || geaManifest.runtime === 'gea') delete geaManifest.runtime
226
237
 
227
238
  return {
@@ -231,8 +242,7 @@ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependenc
231
242
  type: 'module',
232
243
  scripts: {
233
244
  ...(sourcePackage.scripts || {}),
234
- dev: 'gea dev',
235
- build: 'gea build --target web',
245
+ ...(targets.web ? { dev: 'gea dev', build: 'gea build --target web' } : {}),
236
246
  check: 'tsc --noEmit'
237
247
  },
238
248
  dependencies: {
@@ -244,7 +254,7 @@ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependenc
244
254
  ...(sourcePackage.devDependencies || {}),
245
255
  '@geastack/cli': cliDependency,
246
256
  typescript: sourcePackage.devDependencies?.typescript || 'latest',
247
- vite: sourcePackage.devDependencies?.vite || 'latest'
257
+ ...(targets.web ? { vite: sourcePackage.devDependencies?.vite || 'latest' } : {})
248
258
  },
249
259
  gea: geaManifest,
250
260
  license: 'MIT'
@@ -358,12 +368,22 @@ export default defineConfig({
358
368
  `
359
369
  }
360
370
 
361
- function readme({ appId, displayName, starter }) {
371
+ function readme({ appId, displayName, starter, targets }) {
362
372
  const starterLine = starter.kind === 'example'
363
373
  ? `Started from GitHub example: \`${starter.example.name}\` (\`${starter.example.id}\`).`
364
374
  : starter.kind === 'bundled'
365
375
  ? `Started from bundled starter: \`${starter.starter.name}\` (\`${starter.starter.id}\`).`
366
- : 'Started from an empty app.'
376
+ : 'Started from a blank app.'
377
+ const commands = targets.web
378
+ ? `npx gea setup
379
+ npx gea dev
380
+ npx gea build --target web`
381
+ : targets.esp32
382
+ ? `npx gea setup
383
+ npx gea build --board <alias>
384
+ npx gea flash --board <alias> --monitor`
385
+ : `npx gea setup
386
+ npx gea build`
367
387
  return `# ${displayName}
368
388
 
369
389
  GeaStack app id: \`${appId}\`.
@@ -371,9 +391,7 @@ GeaStack app id: \`${appId}\`.
371
391
  ${starterLine}
372
392
 
373
393
  \`\`\`sh
374
- npx gea setup
375
- npx gea dev
376
- npx gea build --target web
394
+ ${commands}
377
395
  \`\`\`
378
396
  `
379
397
  }
@@ -431,21 +449,28 @@ function ensureJson(filePath, create) {
431
449
  if (!exists(filePath)) writeJson(filePath, create())
432
450
  }
433
451
 
434
- function usage() {
452
+ function usage(commandName = 'create-geastack') {
435
453
  return `Usage:
436
- create-geastack <name> [--dir <path>] [--id <app-id>] [--name <display-name>]
454
+ ${commandName} <name>
455
+
456
+ Run without options for guided setup. The CLI asks what you want to build and
457
+ derives the source layout, targets, and runtime configuration from your choice.
437
458
 
438
459
  Options:
439
- --starter counter|empty|example
460
+ --starter counter|blank|example
440
461
  --example <example-id>
462
+ --no-install
463
+
464
+ Automation options:
465
+ --dir <path>
466
+ --id <app-id>
467
+ --name <display-name>
441
468
  --examples-repo <git-url-or-local-path>
442
469
  --examples-ref <git-ref>
443
470
  --targets web,esp32,rp2350,geaos,macos,ios,android
444
- --entry <relative-path>
445
- --ble-ota
446
471
  --core-dependency <specifier>
447
472
  --cli-dependency <specifier>
448
- --install / --no-install
473
+ --install
449
474
  --dry-run
450
475
  --yes
451
476
  --force
package/src/gea.mjs CHANGED
@@ -40,7 +40,7 @@ export async function runGea(argv, io = {}) {
40
40
  if (command === 'create') {
41
41
  const createArgs = argv.slice(1)
42
42
  if (option(parsed, 'install') === undefined) createArgs.push('--install')
43
- return runCreateGeastack(createArgs, io)
43
+ return runCreateGeastack(createArgs, { ...io, commandName: 'gea create' })
44
44
  }
45
45
  if (!command || command === 'help' || flag(parsed, 'help')) {
46
46
  stdout(usage())
@@ -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
- }