@geastack/cli 0.1.3 → 0.1.5

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
@@ -9,23 +9,24 @@ keeping target-specific build logic behind stable backend contracts.
9
9
  ## Commands
10
10
 
11
11
  ```sh
12
- npx @geastack/create-geastack my-panel
12
+ npm install --global @geastack/cli
13
+ gea create my-panel
13
14
  cd my-panel
14
- npx gea doctor
15
- npx gea setup
16
- npx gea dev
17
- npx gea build --target web
18
- npx gea build --target ios
19
- npx gea flash --board amoled --monitor
20
- npx gea monitor --board amoled
21
- npx gea inspect --json
15
+ gea doctor
16
+ gea setup
17
+ gea dev
18
+ gea build --target web
19
+ gea build --target ios
20
+ gea flash --board amoled --monitor
21
+ gea monitor --board amoled
22
+ gea inspect --json
22
23
  ```
23
24
 
24
25
  ## Interactive Menu Map
25
26
 
26
27
  ```mermaid
27
28
  flowchart TD
28
- 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"]
29
30
  identity --> starter{"Starter app?"}
30
31
 
31
32
  starter -->|"Counter starter"| counter["Copy bundled minimal JSX counter"]
@@ -30,7 +30,8 @@ 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
 
@@ -45,12 +46,12 @@ When the wizard asks what to create, choose:
45
46
 
46
47
  For the fastest first flash, choose `Counter starter`.
47
48
 
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`.
49
+ That creates a tiny JSX app and installs all of its project dependencies.
49
50
 
50
51
  ## 3. Set Up The Board
51
52
 
52
53
  ```sh
53
- npx gea setup
54
+ gea setup
54
55
  ```
55
56
 
56
57
  Choose:
@@ -82,7 +83,7 @@ If ESP-IDF was installed, the CLI prints this for future shells:
82
83
  . "$HOME/esp/esp-idf/export.sh"
83
84
  ```
84
85
 
85
- You do not need to run that before every command unless `npx gea doctor` says ESP-IDF is not active.
86
+ You do not need to run that before every command unless `gea doctor` says ESP-IDF is not active.
86
87
 
87
88
  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
89
 
@@ -93,13 +94,13 @@ The wizard writes the board config into your app at `.gea/boards.json` and initi
93
94
  Flash and open the serial monitor:
94
95
 
95
96
  ```sh
96
- npx gea flash --board amoled --monitor
97
+ gea flash --board amoled --monitor
97
98
  ```
98
99
 
99
100
  If you need to specify the serial port manually:
100
101
 
101
102
  ```sh
102
- npx gea flash --board amoled --port /dev/cu.usbmodemXXXX --monitor
103
+ gea flash --board amoled --port /dev/cu.usbmodemXXXX --monitor
103
104
  ```
104
105
 
105
106
  ## Rich Examples
@@ -109,10 +110,10 @@ The bundled starter is intentionally tiny. Rich examples live in the GeaStack ex
109
110
  For example:
110
111
 
111
112
  ```sh
112
- npx @geastack/create-geastack watch-demo --starter example --example watch
113
+ gea create watch-demo --starter example --example watch
113
114
  cd watch-demo
114
- npx gea setup
115
- npx gea flash --board amoled --monitor
115
+ gea setup
116
+ gea flash --board amoled --monitor
116
117
  ```
117
118
 
118
119
  The CLI owns the example catalog, so the user does not need to clone the examples repo.
@@ -122,7 +123,7 @@ The CLI owns the example catalog, so the user does not need to clone the example
122
123
  Run:
123
124
 
124
125
  ```sh
125
- npx gea doctor
126
+ gea doctor
126
127
  ```
127
128
 
128
129
  Most first-run issues are one of:
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geastack/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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,13 +40,19 @@ 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.3'
43
+ const cliDependency = option(parsed, 'cli-dependency') || `^${cliVersion}`
42
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
+ }
43
49
 
44
50
  fs.mkdirSync(targetDir, { recursive: true })
45
51
  if (starter.kind === 'empty') {
46
- fs.writeFileSync(path.join(targetDir, 'index.tsx'), indexTsx(displayName))
47
- fs.writeFileSync(path.join(targetDir, 'styles.css'), stylesCss())
52
+ const entryPath = path.join(targetDir, entry)
53
+ fs.mkdirSync(path.dirname(entryPath), { recursive: true })
54
+ fs.writeFileSync(entryPath, indexTsx(displayName))
55
+ fs.writeFileSync(path.join(path.dirname(entryPath), 'styles.css'), stylesCss())
48
56
  } else if (starter.kind === 'bundled') {
49
57
  copyStarterFiles(starter.starter.root, targetDir)
50
58
  } else if (starter.kind === 'example') {
@@ -69,12 +77,14 @@ export async function runCreateGeastack(argv, io = {}) {
69
77
  appId,
70
78
  displayName,
71
79
  targets,
80
+ entry,
81
+ bleOta: flag(parsed, 'ble-ota'),
72
82
  coreDependency,
73
83
  cliDependency,
74
84
  sourcePackage,
75
85
  sourceManifest
76
86
  }))
77
- ensureFile(path.join(targetDir, 'index.html'), () => indexHtml(displayName))
87
+ ensureFile(path.join(targetDir, 'index.html'), () => indexHtml(displayName, entry))
78
88
  fs.mkdirSync(path.join(targetDir, '.gea'), { recursive: true })
79
89
  writeJson(path.join(targetDir, '.gea', 'boards.json'), {})
80
90
  ensureJson(path.join(targetDir, 'tsconfig.json'), tsconfigJson)
@@ -203,14 +213,15 @@ function targetManifestFromObject(targets = {}) {
203
213
  }
204
214
  }
205
215
 
206
- function packageJson({ appId, displayName, targets, coreDependency, cliDependency, sourcePackage = {}, sourceManifest = {} }) {
216
+ function packageJson({ appId, displayName, targets, entry, bleOta, coreDependency, cliDependency, sourcePackage = {}, sourceManifest = {} }) {
207
217
  const geaManifest = {
208
218
  ...sourceManifest,
209
219
  id: appId,
210
220
  name: displayName,
211
- entry: sourceManifest.entry || 'index.tsx',
221
+ entry,
212
222
  targets
213
223
  }
224
+ if (bleOta) geaManifest.ota = { ...(geaManifest.ota || {}), ble: true }
214
225
  if (!geaManifest.runtime || geaManifest.runtime === 'gea') delete geaManifest.runtime
215
226
 
216
227
  return {
@@ -299,7 +310,7 @@ h1 {
299
310
  `
300
311
  }
301
312
 
302
- function indexHtml(displayName) {
313
+ function indexHtml(displayName, entry = 'index.tsx') {
303
314
  return `<!doctype html>
304
315
  <html lang="en">
305
316
  <head>
@@ -309,7 +320,7 @@ function indexHtml(displayName) {
309
320
  </head>
310
321
  <body>
311
322
  <div id="app"></div>
312
- <script type="module" src="/index.tsx"></script>
323
+ <script type="module" src="/${entry}"></script>
313
324
  </body>
314
325
  </html>
315
326
  `
@@ -398,6 +409,14 @@ function normalizeStarter(value) {
398
409
  return normalized
399
410
  }
400
411
 
412
+ function projectRelativePath(value, label) {
413
+ const normalized = path.posix.normalize(String(value || '').replaceAll('\\', '/')).replace(/^\.\//, '')
414
+ if (!normalized || normalized === '.' || normalized === '..' || normalized.startsWith('../') || path.posix.isAbsolute(normalized)) {
415
+ fail(`--${label} must be a path inside the project.`, ExitCode.usage)
416
+ }
417
+ return normalized
418
+ }
419
+
401
420
  function readPackageJson(targetDir) {
402
421
  const packagePath = path.join(targetDir, 'package.json')
403
422
  if (!exists(packagePath)) return {}
@@ -422,6 +441,8 @@ Options:
422
441
  --examples-repo <git-url-or-local-path>
423
442
  --examples-ref <git-ref>
424
443
  --targets web,esp32,rp2350,geaos,macos,ios,android
444
+ --entry <relative-path>
445
+ --ble-ota
425
446
  --core-dependency <specifier>
426
447
  --cli-dependency <specifier>
427
448
  --install / --no-install
package/src/gea.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import path from 'node:path'
2
+ import fs from 'node:fs'
2
3
 
3
4
  import { flag, option, parseArgs } from './args.mjs'
5
+ import { runCreateGeastack } from './create-geastack.mjs'
4
6
  import { createChildEnv, createContext } from './context.mjs'
5
7
  import { ExitCode, fail } from './errors.mjs'
6
8
  import { exists, readJson } from './fs-utils.mjs'
@@ -18,7 +20,7 @@ import { runExternal } from './run.mjs'
18
20
  import { runSetupWizard } from './setup-wizard.mjs'
19
21
  import { commandVersion, nodeAtLeast } from './toolchain.mjs'
20
22
 
21
- const version = '0.1.1'
23
+ const version = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
22
24
 
23
25
  export async function runGea(argv, io = {}) {
24
26
  const parsed = parseArgs(argv)
@@ -35,6 +37,11 @@ export async function runGea(argv, io = {}) {
35
37
  stdout(version)
36
38
  return 0
37
39
  }
40
+ if (command === 'create') {
41
+ const createArgs = argv.slice(1)
42
+ if (option(parsed, 'install') === undefined) createArgs.push('--install')
43
+ return runCreateGeastack(createArgs, io)
44
+ }
38
45
  if (!command || command === 'help' || flag(parsed, 'help')) {
39
46
  stdout(usage())
40
47
  return 0
@@ -422,6 +429,7 @@ function requirePath(filePath, label) {
422
429
 
423
430
  function usage() {
424
431
  return `Usage:
432
+ gea create <name> [--starter counter|empty|example] [--targets <list>]
425
433
  gea doctor [--strict] [--json]
426
434
  gea setup --board <alias>
427
435
  gea dev [app] [--target web] [--port 5181]