@geastack/cli 0.1.52 → 0.1.53
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/docs/SPEC.md +2 -2
- package/package.json +2 -3
- package/src/apps/app-index-writer.mjs +15 -0
- package/src/apps/apple-icons.mjs +147 -0
- package/src/apps/bundle-writer.mjs +156 -0
- package/src/apps/launcher-catalog.mjs +114 -0
- package/src/apps/openai-icons.mjs +356 -0
- package/src/apps/zip-writer.mjs +104 -0
- package/src/ble/ble-ota.swift +291 -0
- package/src/boards/config.mjs +32 -0
- package/src/boards/custom-target.mjs +309 -0
- package/src/boards/resolve.mjs +143 -0
- package/src/boards/targets.mjs +45 -0
- package/src/boards/usb.mjs +211 -0
- package/src/commands/apps.mjs +236 -0
- package/src/commands/board.mjs +402 -0
- package/src/commands/doctor.mjs +91 -0
- package/src/context.mjs +45 -52
- package/src/device/device.mjs +96 -0
- package/src/device/image.mjs +115 -0
- package/src/device/serial.mjs +430 -0
- package/src/device/wifi.mjs +190 -0
- package/src/esp32/build.mjs +321 -0
- package/src/esp32/capabilities.mjs +54 -0
- package/src/esp32/flash.mjs +181 -0
- package/src/esp32/idf-env.mjs +171 -0
- package/src/esp32/ota.mjs +80 -0
- package/src/esp32/partitions.mjs +59 -0
- package/src/esp32/sdkconfig.mjs +103 -0
- package/src/esp32/wifi-config.mjs +72 -0
- package/src/gea.mjs +86 -427
- package/src/geaos/adapter.mjs +119 -0
- package/src/heap-report.mjs +1276 -0
- package/src/manifest.mjs +135 -36
- package/src/rp2350/adapter.mjs +155 -0
- package/src/setup-wizard.mjs +6 -10
- package/src/taurus/adapter.mjs +52 -0
package/src/gea.mjs
CHANGED
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
1
|
import fs from 'node:fs'
|
|
3
2
|
|
|
4
3
|
import { flag, option, parseArgs } from './args.mjs'
|
|
5
|
-
import { runCreateGeastack } from './create-geastack.mjs'
|
|
6
4
|
import { runChips } from './chips.mjs'
|
|
7
|
-
import {
|
|
5
|
+
import { appsCommand, boardsCommand, heapReportCommand, targetsCommand } from './commands/apps.mjs'
|
|
6
|
+
import { buildCommand, cleanCommand, devctlCommand, flashCommand, geaosDeviceCommand, logsCommand, monitorCommand, otaCommand, screenshotCommand } from './commands/board.mjs'
|
|
7
|
+
import { doctorCommand } from './commands/doctor.mjs'
|
|
8
|
+
import { createContext } from './context.mjs'
|
|
9
|
+
import { runCreateGeastack } from './create-geastack.mjs'
|
|
8
10
|
import { ExitCode, fail } from './errors.mjs'
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
assertTargetEnabled,
|
|
12
|
-
boardConfigPath,
|
|
13
|
-
assertValidApp,
|
|
14
|
-
discoverApps,
|
|
15
|
-
loadBoardConfig,
|
|
16
|
-
loadTargetMetadata,
|
|
17
|
-
resolveRequestedApp,
|
|
18
|
-
validateApp
|
|
19
|
-
} from './manifest.mjs'
|
|
20
|
-
import { runExternal } from './run.mjs'
|
|
11
|
+
import { knownPlatforms } from './manifest.mjs'
|
|
21
12
|
import { runSetupWizard } from './setup-wizard.mjs'
|
|
22
|
-
import { commandVersion, nodeAtLeast } from './toolchain.mjs'
|
|
23
13
|
|
|
24
14
|
const version = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
|
|
25
15
|
|
|
16
|
+
const geaosDeviceActions = new Set(['bringup', 'probe', 'shell', 'push', 'record', 'tap', 'drag', 'down', 'move', 'up', 'flash-kernel'])
|
|
17
|
+
|
|
26
18
|
export async function runGea(argv, io = {}) {
|
|
27
19
|
const parsed = parseArgs(argv)
|
|
28
20
|
const stdout = io.stdout || console.log
|
|
@@ -50,438 +42,105 @@ export async function runGea(argv, io = {}) {
|
|
|
50
42
|
|
|
51
43
|
const ctx = createContext(parsed, env, cwd)
|
|
52
44
|
const rest = parsed.positionals.slice(1)
|
|
45
|
+
const options = { stdout, stderr, env, stdin, output, prompt }
|
|
46
|
+
|
|
47
|
+
// A platform name in --target (web, macos, ...) is not a board.
|
|
48
|
+
const target = option(parsed, 'target', '')
|
|
49
|
+
if (target && knownPlatforms.includes(target) && ['build', 'flash', 'run', 'monitor', 'ota'].includes(command)) {
|
|
50
|
+
if (target === 'esp32' || target === 'rp2350' || target === 'geaos') {
|
|
51
|
+
fail(`--target ${target} names a platform; pass --board <alias> (gea boards list) or --target <target id> (gea targets list).`, ExitCode.usage)
|
|
52
|
+
}
|
|
53
|
+
fail(`The ${target} build is not driven by gea; run its build script in the ${target} target project.`, ExitCode.usage)
|
|
54
|
+
}
|
|
53
55
|
|
|
54
56
|
switch (command) {
|
|
55
57
|
case 'doctor':
|
|
56
|
-
return
|
|
57
|
-
case 'dev':
|
|
58
|
-
return dev(ctx, parsed, rest, { stdout, env })
|
|
59
|
-
case 'build':
|
|
60
|
-
return build(ctx, parsed, rest, { stdout, env })
|
|
58
|
+
return doctorCommand(ctx, parsed, rest, options)
|
|
61
59
|
case 'setup':
|
|
62
|
-
return
|
|
60
|
+
return option(parsed, 'board') || option(parsed, 'target')
|
|
61
|
+
? buildCommand(ctx, { ...parsed, options: { ...parsed.options, 'configure-only': true } }, rest, options)
|
|
62
|
+
: runSetupWizard(ctx, parsed, options)
|
|
63
63
|
case 'chips':
|
|
64
|
-
return runChips(ctx, parsed, rest,
|
|
64
|
+
return runChips(ctx, parsed, rest, options)
|
|
65
|
+
case 'build':
|
|
66
|
+
return buildCommand(ctx, parsed, rest, options)
|
|
67
|
+
case 'clean':
|
|
68
|
+
return cleanCommand(ctx, parsed, rest, options)
|
|
65
69
|
case 'flash':
|
|
66
|
-
return
|
|
70
|
+
return flashCommand(ctx, parsed, rest, options, { monitor: flag(parsed, 'monitor') })
|
|
71
|
+
case 'run':
|
|
72
|
+
return flashCommand(ctx, parsed, rest, options, { monitor: true })
|
|
67
73
|
case 'monitor':
|
|
68
|
-
return
|
|
74
|
+
return monitorCommand(ctx, parsed, rest, options)
|
|
75
|
+
case 'logs':
|
|
76
|
+
case 'tail':
|
|
77
|
+
return logsCommand(ctx, parsed, rest, options)
|
|
69
78
|
case 'screenshot':
|
|
70
|
-
return
|
|
79
|
+
return screenshotCommand(ctx, parsed, rest, options)
|
|
71
80
|
case 'ota':
|
|
72
|
-
return
|
|
81
|
+
return otaCommand(ctx, parsed, rest, options)
|
|
82
|
+
case 'devctl':
|
|
83
|
+
return devctlCommand(ctx, parsed, rest, options)
|
|
84
|
+
case 'apps':
|
|
85
|
+
return appsCommand(ctx, parsed, rest, options)
|
|
86
|
+
case 'boards':
|
|
87
|
+
return boardsCommand(ctx, parsed, rest, options)
|
|
88
|
+
case 'targets':
|
|
89
|
+
return targetsCommand(ctx, parsed, rest, options)
|
|
90
|
+
case 'heap-report':
|
|
91
|
+
return heapReportCommand(ctx, parsed, rest, options)
|
|
73
92
|
case 'list':
|
|
74
|
-
return
|
|
93
|
+
return legacyList(ctx, parsed, rest, options)
|
|
75
94
|
case 'inspect':
|
|
76
|
-
return
|
|
95
|
+
return appsCommand(ctx, parsed, ['inspect', ...rest], options)
|
|
77
96
|
default:
|
|
97
|
+
if (geaosDeviceActions.has(command)) return geaosDeviceCommand(ctx, parsed, [command, ...rest], options)
|
|
78
98
|
stderr(`Unknown command: ${command}`)
|
|
79
99
|
stdout(usage())
|
|
80
100
|
return 1
|
|
81
101
|
}
|
|
82
102
|
}
|
|
83
103
|
|
|
84
|
-
function
|
|
85
|
-
const app = resolveRequestedApp(ctx, parsed, rest)
|
|
86
|
-
assertValidApp(app)
|
|
87
|
-
const target = option(parsed, 'target', 'web')
|
|
88
|
-
assertTargetEnabled(ctx, app, target)
|
|
89
|
-
|
|
90
|
-
if (target !== 'web') {
|
|
91
|
-
io.stdout(`No live dev loop is registered for target '${target}' yet. Use 'gea flash --app ${app.id} --board <alias> --monitor'.`)
|
|
92
|
-
return 0
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
requirePath(ctx.scripts.webDev, 'web dev script')
|
|
96
|
-
const args = [ctx.scripts.webDev, app.id]
|
|
97
|
-
const port = option(parsed, 'port')
|
|
98
|
-
if (port) args.push('--port', String(port))
|
|
99
|
-
return runExternal('node', args, {
|
|
100
|
-
cwd: ctx.simulatorRoot,
|
|
101
|
-
env: createChildEnv(ctx, io.env),
|
|
102
|
-
dryRun: flag(parsed, 'dry-run'),
|
|
103
|
-
failureCode: ExitCode.buildFailed,
|
|
104
|
-
stdout: io.stdout
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function build(ctx, parsed, rest, io) {
|
|
109
|
-
const app = resolveRequestedApp(ctx, parsed, rest)
|
|
110
|
-
assertValidApp(app)
|
|
111
|
-
const board = option(parsed, 'board', '')
|
|
112
|
-
const target = option(parsed, 'target', board ? '' : 'web')
|
|
113
|
-
assertTargetEnabled(ctx, app, board || target)
|
|
114
|
-
|
|
115
|
-
if (target === 'web' && !board) {
|
|
116
|
-
requirePath(ctx.scripts.webBuild, 'web build script')
|
|
117
|
-
return runExternal(ctx.scripts.webBuild, [app.id], {
|
|
118
|
-
cwd: ctx.simulatorRoot,
|
|
119
|
-
env: createChildEnv(ctx, io.env),
|
|
120
|
-
dryRun: flag(parsed, 'dry-run'),
|
|
121
|
-
failureCode: ExitCode.buildFailed,
|
|
122
|
-
stdout: io.stdout
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (target === 'macos') {
|
|
127
|
-
requirePath(ctx.scripts.macosBuild, 'macOS build script')
|
|
128
|
-
const outputTag = option(parsed, 'output-tag')
|
|
129
|
-
if (outputTag !== undefined && (typeof outputTag !== 'string' || outputTag.length === 0)) {
|
|
130
|
-
fail('--output-tag requires a non-empty value.', ExitCode.usage)
|
|
131
|
-
}
|
|
132
|
-
const args = [app.id]
|
|
133
|
-
if (outputTag !== undefined) args.push('--output-tag', outputTag)
|
|
134
|
-
return runExternal(ctx.scripts.macosBuild, args, {
|
|
135
|
-
cwd: ctx.appleRoot,
|
|
136
|
-
env: createChildEnv(ctx, io.env),
|
|
137
|
-
dryRun: flag(parsed, 'dry-run'),
|
|
138
|
-
failureCode: ExitCode.buildFailed,
|
|
139
|
-
stdout: io.stdout
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (target === 'ios') {
|
|
144
|
-
requirePath(ctx.scripts.iosBuild, 'iOS build script')
|
|
145
|
-
const mode = option(parsed, 'mode', 'simulator')
|
|
146
|
-
return runExternal(ctx.scripts.iosBuild, [app.id, mode], {
|
|
147
|
-
cwd: ctx.appleRoot,
|
|
148
|
-
env: createChildEnv(ctx, io.env),
|
|
149
|
-
dryRun: flag(parsed, 'dry-run'),
|
|
150
|
-
failureCode: ExitCode.buildFailed,
|
|
151
|
-
stdout: io.stdout
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (target === 'android') {
|
|
156
|
-
return runAndroid(ctx, parsed, {
|
|
157
|
-
appId: app.id,
|
|
158
|
-
mode: option(parsed, 'mode', 'debug'),
|
|
159
|
-
failureCode: ExitCode.buildFailed,
|
|
160
|
-
stdout: io.stdout,
|
|
161
|
-
env: io.env
|
|
162
|
-
})
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return runBoard(ctx, 'build', parsed, {
|
|
166
|
-
app,
|
|
167
|
-
appId: app.id,
|
|
168
|
-
board,
|
|
169
|
-
target,
|
|
170
|
-
failureCode: ExitCode.buildFailed,
|
|
171
|
-
stdout: io.stdout,
|
|
172
|
-
env: io.env
|
|
173
|
-
})
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function setup(ctx, parsed, io) {
|
|
177
|
-
const board = option(parsed, 'board', '')
|
|
178
|
-
const target = option(parsed, 'target', '')
|
|
179
|
-
if (!board && !target) {
|
|
180
|
-
return runSetupWizard(ctx, parsed, io)
|
|
181
|
-
}
|
|
182
|
-
return runBoard(ctx, 'setup', parsed, {
|
|
183
|
-
board,
|
|
184
|
-
target,
|
|
185
|
-
failureCode: ExitCode.buildFailed,
|
|
186
|
-
stdout: io.stdout,
|
|
187
|
-
env: io.env
|
|
188
|
-
})
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function flash(ctx, parsed, rest, io) {
|
|
192
|
-
const board = option(parsed, 'board', '')
|
|
193
|
-
const target = option(parsed, 'target', '')
|
|
194
|
-
if (!board && !target) fail('flash requires --board <alias> or --target <target>.', ExitCode.usage)
|
|
195
|
-
if (flag(parsed, 'bringup')) {
|
|
196
|
-
return runBoard(ctx, flag(parsed, 'monitor') ? 'flash-monitor' : 'flash', parsed, {
|
|
197
|
-
board,
|
|
198
|
-
target,
|
|
199
|
-
failureCode: ExitCode.deployFailed,
|
|
200
|
-
stdout: io.stdout,
|
|
201
|
-
env: io.env
|
|
202
|
-
})
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const app = resolveRequestedApp(ctx, parsed, rest)
|
|
206
|
-
assertValidApp(app)
|
|
207
|
-
assertTargetEnabled(ctx, app, board || target)
|
|
208
|
-
|
|
209
|
-
if (!board && target === 'android') {
|
|
210
|
-
return runAndroid(ctx, parsed, {
|
|
211
|
-
appId: app.id,
|
|
212
|
-
mode: 'device',
|
|
213
|
-
failureCode: ExitCode.deployFailed,
|
|
214
|
-
stdout: io.stdout,
|
|
215
|
-
env: io.env
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return runBoard(ctx, flag(parsed, 'monitor') ? 'flash-monitor' : 'flash', parsed, {
|
|
220
|
-
app,
|
|
221
|
-
appId: app.id,
|
|
222
|
-
board,
|
|
223
|
-
target,
|
|
224
|
-
failureCode: ExitCode.deployFailed,
|
|
225
|
-
stdout: io.stdout,
|
|
226
|
-
env: io.env
|
|
227
|
-
})
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function ota(ctx, parsed, rest, io) {
|
|
231
|
-
const board = option(parsed, 'board', '')
|
|
232
|
-
const target = option(parsed, 'target', '')
|
|
233
|
-
if (!board && !target) fail('ota requires --board <alias> or --target <target>.', ExitCode.usage)
|
|
234
|
-
|
|
235
|
-
const app = resolveRequestedApp(ctx, parsed, rest)
|
|
236
|
-
assertValidApp(app)
|
|
237
|
-
assertTargetEnabled(ctx, app, board || target)
|
|
238
|
-
|
|
239
|
-
const transport = option(parsed, 'transport', 'wifi')
|
|
240
|
-
if (transport !== 'wifi' && transport !== 'ble') {
|
|
241
|
-
fail("--transport must be 'wifi' or 'ble'.", ExitCode.usage)
|
|
242
|
-
}
|
|
243
|
-
return runBoard(ctx, transport === 'ble' ? 'ble-ota' : 'ota', parsed, {
|
|
244
|
-
app,
|
|
245
|
-
appId: app.id,
|
|
246
|
-
board,
|
|
247
|
-
target,
|
|
248
|
-
failureCode: ExitCode.deployFailed,
|
|
249
|
-
stdout: io.stdout,
|
|
250
|
-
env: io.env
|
|
251
|
-
})
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function monitor(ctx, parsed, rest, io) {
|
|
255
|
-
const board = option(parsed, 'board', '')
|
|
256
|
-
const target = option(parsed, 'target', '')
|
|
257
|
-
if (!board && !target) fail('monitor requires --board <alias> or --target <target>.', ExitCode.usage)
|
|
258
|
-
if (!board && target === 'android') {
|
|
259
|
-
return runAndroid(ctx, parsed, {
|
|
260
|
-
appId: 'css-3d-cube',
|
|
261
|
-
mode: 'monitor',
|
|
262
|
-
failureCode: ExitCode.deployFailed,
|
|
263
|
-
stdout: io.stdout,
|
|
264
|
-
env: io.env
|
|
265
|
-
})
|
|
266
|
-
}
|
|
267
|
-
return runBoard(ctx, 'monitor', parsed, {
|
|
268
|
-
board,
|
|
269
|
-
target,
|
|
270
|
-
failureCode: ExitCode.deployFailed,
|
|
271
|
-
stdout: io.stdout,
|
|
272
|
-
env: io.env
|
|
273
|
-
})
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function screenshot(ctx, parsed, rest, io) {
|
|
277
|
-
const board = option(parsed, 'board', '')
|
|
278
|
-
const target = option(parsed, 'target', '')
|
|
279
|
-
if (!board && !target) fail('screenshot requires --board <alias> or --target <target>.', ExitCode.usage)
|
|
280
|
-
|
|
281
|
-
const output = path.resolve(ctx.cwd, rest[0] || 'screenshot.png')
|
|
282
|
-
const extraArgs = [output]
|
|
283
|
-
const timeout = option(parsed, 'timeout')
|
|
284
|
-
if (timeout !== undefined) extraArgs.push(`--timeout=${timeout}`)
|
|
285
|
-
if (flag(parsed, 'legacy')) extraArgs.push('--legacy')
|
|
286
|
-
|
|
287
|
-
return runBoard(ctx, 'screenshot', parsed, {
|
|
288
|
-
board,
|
|
289
|
-
target,
|
|
290
|
-
extraArgs,
|
|
291
|
-
namedPort: true,
|
|
292
|
-
failureCode: ExitCode.deployFailed,
|
|
293
|
-
stdout: io.stdout,
|
|
294
|
-
env: io.env
|
|
295
|
-
})
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function runBoard(ctx, action, parsed, opts) {
|
|
299
|
-
requirePath(ctx.scripts.board, 'board script')
|
|
300
|
-
const args = [action]
|
|
301
|
-
if (opts.board) args.push(`--board=${opts.board}`)
|
|
302
|
-
if (opts.target) args.push(`--target=${opts.target}`)
|
|
303
|
-
if (opts.appId) args.push(`--app=${opts.appId}`)
|
|
304
|
-
const port = option(parsed, 'port')
|
|
305
|
-
if (port) args.push(opts.namedPort ? `--port=${port}` : String(port))
|
|
306
|
-
if (flag(parsed, 'manual-boot')) args.push('--manual-boot')
|
|
307
|
-
if (option(parsed, 'reset') === false) args.push('--no-reset')
|
|
308
|
-
const flashBaud = option(parsed, 'flash-baud')
|
|
309
|
-
if (flashBaud !== undefined) args.push(`--flash-baud=${flashBaud}`)
|
|
310
|
-
if (opts.extraArgs) args.push(...opts.extraArgs)
|
|
311
|
-
args.push(...parsed.passthrough)
|
|
312
|
-
const childEnv = createChildEnv(ctx, opts.env)
|
|
313
|
-
if (opts.app?.manifest?.ota?.ble === true) childEnv.GEA_EMBEDDED_BLE_OTA = '1'
|
|
314
|
-
return runExternal(ctx.scripts.board, args, {
|
|
315
|
-
cwd: ctx.targetsRoot,
|
|
316
|
-
env: childEnv,
|
|
317
|
-
dryRun: flag(parsed, 'dry-run'),
|
|
318
|
-
failureCode: opts.failureCode,
|
|
319
|
-
stdout: opts.stdout
|
|
320
|
-
})
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function runAndroid(ctx, parsed, opts) {
|
|
324
|
-
requirePath(ctx.scripts.androidBuild, 'Android build script')
|
|
325
|
-
return runExternal(ctx.scripts.androidBuild, [opts.appId, opts.mode, ...parsed.passthrough], {
|
|
326
|
-
cwd: ctx.androidRoot,
|
|
327
|
-
env: createChildEnv(ctx, opts.env),
|
|
328
|
-
dryRun: flag(parsed, 'dry-run'),
|
|
329
|
-
failureCode: opts.failureCode,
|
|
330
|
-
stdout: opts.stdout
|
|
331
|
-
})
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function list(ctx, parsed, rest, io) {
|
|
104
|
+
function legacyList(ctx, parsed, rest, options) {
|
|
335
105
|
const subject = rest[0] || 'apps'
|
|
336
|
-
if (subject === 'apps')
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
.filter((app) => !target || app.targets?.[target] === true)
|
|
340
|
-
.map((app) => ({ id: app.id, name: app.name, targets: app.targets, root: app.root }))
|
|
341
|
-
if (flag(parsed, 'json')) io.stdout(JSON.stringify(apps, null, 2))
|
|
342
|
-
else for (const app of apps) io.stdout(app.id)
|
|
343
|
-
return 0
|
|
344
|
-
}
|
|
345
|
-
if (subject === 'targets') {
|
|
346
|
-
const targets = loadTargetMetadata(ctx)
|
|
347
|
-
if (flag(parsed, 'json')) io.stdout(JSON.stringify(targets, null, 2))
|
|
348
|
-
else for (const id of Object.keys(targets).sort()) io.stdout(id)
|
|
349
|
-
return 0
|
|
350
|
-
}
|
|
351
|
-
if (subject === 'boards') {
|
|
352
|
-
const boards = loadBoardConfig(ctx)
|
|
353
|
-
if (flag(parsed, 'json')) io.stdout(JSON.stringify(boards, null, 2))
|
|
354
|
-
else for (const id of Object.keys(boards).sort()) io.stdout(id)
|
|
355
|
-
return 0
|
|
356
|
-
}
|
|
106
|
+
if (subject === 'apps') return appsCommand(ctx, parsed, ['list'], options)
|
|
107
|
+
if (subject === 'targets') return targetsCommand(ctx, parsed, ['list'], options)
|
|
108
|
+
if (subject === 'boards') return boardsCommand(ctx, parsed, ['list'], options)
|
|
357
109
|
fail(`Unknown list subject '${subject}'. Expected apps, targets, or boards.`, ExitCode.usage)
|
|
358
110
|
}
|
|
359
111
|
|
|
360
|
-
function inspect(ctx, parsed, rest, io) {
|
|
361
|
-
const app = resolveRequestedApp(ctx, parsed, rest)
|
|
362
|
-
assertValidApp(app)
|
|
363
|
-
const payload = {
|
|
364
|
-
id: app.id,
|
|
365
|
-
name: app.name,
|
|
366
|
-
root: app.root,
|
|
367
|
-
entry: app.entry,
|
|
368
|
-
runtime: app.runtime,
|
|
369
|
-
targets: app.targets,
|
|
370
|
-
icons: app.icons,
|
|
371
|
-
launcher: app.launcher
|
|
372
|
-
}
|
|
373
|
-
if (flag(parsed, 'json')) io.stdout(JSON.stringify(payload, null, 2))
|
|
374
|
-
else io.stdout(`${app.id}\t${app.name}\t${app.root}\t${app.entry}`)
|
|
375
|
-
return 0
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function doctor(ctx, parsed, io) {
|
|
379
|
-
const checks = []
|
|
380
|
-
addCheck(checks, '@geastack/targets', packageExists(ctx.targetsRoot), ctx.targetsRoot, true)
|
|
381
|
-
addCheck(checks, '@geastack/core', packageExists(ctx.corePackageDir), ctx.corePackageDir, true)
|
|
382
|
-
addCheck(checks, '@geastack/compiler', packageExists(ctx.compilerPackageDir), ctx.compilerPackageDir, true)
|
|
383
|
-
addCheck(checks, 'project root', exists(ctx.projectRoot), ctx.projectRoot, true)
|
|
384
|
-
addCheck(checks, 'web dev adapter', exists(ctx.scripts.webDev), ctx.scripts.webDev, false)
|
|
385
|
-
addCheck(checks, 'web build adapter', exists(ctx.scripts.webBuild), ctx.scripts.webBuild, false)
|
|
386
|
-
addCheck(checks, 'Android build adapter', exists(ctx.scripts.androidBuild), ctx.scripts.androidBuild, false)
|
|
387
|
-
addCheck(checks, 'board script', exists(ctx.scripts.board), ctx.scripts.board, true)
|
|
388
|
-
addCheck(checks, 'Node >= 20.19', nodeAtLeast(20, 19), process.version, true)
|
|
389
|
-
const npmVersion = commandVersion('npm', ['--version'], io.env)
|
|
390
|
-
const pythonCommand = commandVersion('python3', ['--version'], io.env) ? 'python3' : 'python'
|
|
391
|
-
const pythonVersion = commandVersion(pythonCommand, ['--version'], io.env)
|
|
392
|
-
const idfVersion = commandVersion('idf.py', ['--version'], io.env)
|
|
393
|
-
const emccVersion = commandVersion('emcc', ['--version'], io.env)
|
|
394
|
-
const xcodeVersion = commandVersion('xcodebuild', ['-version'], io.env)
|
|
395
|
-
const adbVersion = commandVersion('adb', ['version'], io.env)
|
|
396
|
-
const javacVersion = commandVersion('javac', ['--version'], io.env)
|
|
397
|
-
const androidSdk = io.env.ANDROID_HOME || io.env.ANDROID_SDK_ROOT || path.join(process.env.HOME || '', 'Library', 'Android', 'sdk')
|
|
398
|
-
addCheck(checks, 'npm', Boolean(npmVersion), npmVersion, false)
|
|
399
|
-
addCheck(checks, 'Python', Boolean(pythonVersion), pythonVersion, false)
|
|
400
|
-
addCheck(checks, 'ESP-IDF', Boolean(idfVersion) || Boolean(io.env.IDF_PATH), io.env.IDF_PATH || idfVersion, false)
|
|
401
|
-
addCheck(checks, 'Emscripten', Boolean(emccVersion), emccVersion.split('\n')[0], false)
|
|
402
|
-
addCheck(checks, 'Xcode', Boolean(xcodeVersion), xcodeVersion.split('\n')[0], false)
|
|
403
|
-
addCheck(checks, 'Android SDK', exists(androidSdk), androidSdk, false)
|
|
404
|
-
addCheck(checks, 'adb', Boolean(adbVersion), adbVersion.split('\n')[0], false)
|
|
405
|
-
addCheck(checks, 'javac', Boolean(javacVersion), javacVersion.split('\n')[0], false)
|
|
406
|
-
|
|
407
|
-
const boardFile = boardConfigPath(ctx)
|
|
408
|
-
try {
|
|
409
|
-
if (exists(boardFile)) readJson(boardFile)
|
|
410
|
-
addCheck(checks, 'boards.json', exists(boardFile), exists(boardFile) ? boardFile : 'not configured', false)
|
|
411
|
-
} catch (error) {
|
|
412
|
-
addCheck(checks, 'boards.json', false, error.message, true)
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const apps = discoverApps(ctx)
|
|
416
|
-
addCheck(checks, 'app catalog', apps.length > 0, `${apps.length} app(s)`, true)
|
|
417
|
-
|
|
418
|
-
const currentApp = (() => {
|
|
419
|
-
try {
|
|
420
|
-
return resolveRequestedApp(ctx, parsed, [])
|
|
421
|
-
} catch {
|
|
422
|
-
return null
|
|
423
|
-
}
|
|
424
|
-
})()
|
|
425
|
-
if (currentApp) {
|
|
426
|
-
const errors = validateApp(currentApp)
|
|
427
|
-
addCheck(checks, `app manifest (${currentApp.id})`, errors.length === 0, errors.length === 0 ? currentApp.root : errors.join('; '), true)
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const failedRequired = checks.filter((check) => check.required && !check.ok)
|
|
431
|
-
const failedOptional = checks.filter((check) => !check.required && !check.ok)
|
|
432
|
-
|
|
433
|
-
if (flag(parsed, 'json')) {
|
|
434
|
-
io.stdout(JSON.stringify({ ok: failedRequired.length === 0, checks }, null, 2))
|
|
435
|
-
} else {
|
|
436
|
-
for (const check of checks) {
|
|
437
|
-
const marker = check.ok ? '[ok]' : check.required ? '[fail]' : '[warn]'
|
|
438
|
-
io.stdout(`${marker} ${check.name}: ${check.detail}`)
|
|
439
|
-
}
|
|
440
|
-
if (failedRequired.length > 0 || failedOptional.length > 0) {
|
|
441
|
-
io.stdout('Setup guide: cli/docs/SETUP.md')
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (failedRequired.length > 0) return ExitCode.missingDependency
|
|
446
|
-
if (failedOptional.length > 0 && flag(parsed, 'strict')) return ExitCode.missingDependency
|
|
447
|
-
return 0
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function addCheck(checks, name, ok, detail, required) {
|
|
451
|
-
checks.push({ name, ok: Boolean(ok), detail: detail || (ok ? 'ok' : 'missing'), required: Boolean(required) })
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function packageExists(packageDir) {
|
|
455
|
-
return Boolean(packageDir) && exists(path.join(packageDir, 'package.json'))
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function requirePath(filePath, label) {
|
|
459
|
-
if (!exists(filePath)) fail(`Missing ${label}: ${filePath}`, ExitCode.missingDependency)
|
|
460
|
-
}
|
|
461
|
-
|
|
462
112
|
function usage() {
|
|
463
|
-
return `
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
gea
|
|
467
|
-
gea
|
|
468
|
-
gea
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
gea
|
|
472
|
-
gea
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
gea
|
|
477
|
-
gea
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
gea
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
--
|
|
485
|
-
--
|
|
486
|
-
|
|
113
|
+
return `gea ${version}
|
|
114
|
+
|
|
115
|
+
Usage:
|
|
116
|
+
gea create <name> scaffold a new Gea project
|
|
117
|
+
gea setup [--board <alias>] guided board setup, or configure a board's build
|
|
118
|
+
gea doctor [--json] [--strict] check packages and toolchains
|
|
119
|
+
|
|
120
|
+
Build and deploy (every device command takes --board <alias>, see gea boards list):
|
|
121
|
+
gea build --board <alias> [--app <id>] build firmware (ESP-IDF / Pico SDK / geaos)
|
|
122
|
+
gea flash --board <alias> [--app <id>] build + flash over USB [--monitor] [--manual-boot] [--no-reset] [--flash-baud N]
|
|
123
|
+
--slot ota_N [--image file] stage an app image into an OTA slot only
|
|
124
|
+
--slot-image ota_N=file ... provision several prebuilt images
|
|
125
|
+
--erase-slot ota_N | --restore-boot slot maintenance
|
|
126
|
+
gea run --board <alias> [--app <id>] build + flash + serial monitor
|
|
127
|
+
gea ota --board <alias> [--app <id>] build + WiFi OTA (transports.ota.host) [--monitor]
|
|
128
|
+
--transport ble [--device name] BLE OTA (macOS)
|
|
129
|
+
--slot ota_N [--boot] [--reboot] | --erase-slot ota_N
|
|
130
|
+
gea clean --board <alias> [--app <id>] remove build artifacts
|
|
131
|
+
|
|
132
|
+
Device access:
|
|
133
|
+
gea logs --board <alias> [--follow] log stream (WiFi when the board has an IP, else USB) [--transport auto|usb|wifi]
|
|
134
|
+
gea monitor --board <alias> raw USB serial monitor [--timestamps] [--log-file f]
|
|
135
|
+
gea screenshot [file.png] --board <alias> grab the screen [--transport auto|usb|wifi]
|
|
136
|
+
gea devctl <verb> ... --board <alias> GEADEV device control (gea devctl help)
|
|
137
|
+
|
|
138
|
+
Catalogs:
|
|
139
|
+
gea apps list|inspect|pack|index|launcher|icons|icon-sheet|apple-icons
|
|
140
|
+
gea boards list|show <alias>|add
|
|
141
|
+
gea targets list|show <id>
|
|
142
|
+
gea chips ... custom board composition from the chip catalog
|
|
143
|
+
gea heap-report [logs...] [--out file] [--map elf.map]
|
|
144
|
+
|
|
145
|
+
Global options: --project <dir> --boards-config <file> --dry-run --json`
|
|
487
146
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { resolveUsbSerialPort } from '../boards/usb.mjs'
|
|
6
|
+
import { CliError, ExitCode, fail } from '../errors.mjs'
|
|
7
|
+
import { formatCommand } from '../run.mjs'
|
|
8
|
+
|
|
9
|
+
// GeaOS phones and watches. The heavy lifting (docker cross-builds, boot
|
|
10
|
+
// image repacking, fastboot/BROM flashing) stays in the target project's
|
|
11
|
+
// scripts; the CLI resolves the board, the packages and the device, and
|
|
12
|
+
// hands them over as environment.
|
|
13
|
+
|
|
14
|
+
function geaosEnv(ctx, selection, env) {
|
|
15
|
+
const out = { ...env, GEA_BOARD_TARGET: selection.target, GEA_APPS_ROOT: ctx.projectRoot }
|
|
16
|
+
if (selection.telnetHost) {
|
|
17
|
+
out.GEA_BOARD_TELNET_HOST = selection.telnetHost
|
|
18
|
+
out.GEAOS_DEVICE_HOST = selection.telnetHost
|
|
19
|
+
}
|
|
20
|
+
if (selection.telnetPort) {
|
|
21
|
+
out.GEA_BOARD_TELNET_PORT = selection.telnetPort
|
|
22
|
+
out.GEAOS_DEVICE_PORT = selection.telnetPort
|
|
23
|
+
}
|
|
24
|
+
if (selection.fastbootSerial) {
|
|
25
|
+
out.GEA_BOARD_FASTBOOT_SERIAL = selection.fastbootSerial
|
|
26
|
+
out.GEAOS_FASTBOOT_SERIAL = selection.fastbootSerial
|
|
27
|
+
}
|
|
28
|
+
if (selection.usbSerial) out.GEA_BOARD_USB_SERIAL = selection.usbSerial
|
|
29
|
+
for (const [key, value] of Object.entries({
|
|
30
|
+
GEA_BOARD_MTK_WORKDIR: selection.mtkWorkdir,
|
|
31
|
+
GEA_BOARD_MTK_BOOT_SLOT: selection.mtkBootSlot,
|
|
32
|
+
GEA_BOARD_MTK_METHOD: selection.mtkMethod,
|
|
33
|
+
GEA_BOARD_MTK_MONITOR_GLOB: selection.mtkMonitorGlob
|
|
34
|
+
})) {
|
|
35
|
+
if (value) out[key] = value
|
|
36
|
+
}
|
|
37
|
+
// The device is addressed by its USB SERIAL, never by path order. A
|
|
38
|
+
// detached watch must still build, so an unresolved port stays empty and
|
|
39
|
+
// the flasher falls through to its own (BROM) path -- pinned to a glob only
|
|
40
|
+
// this serial can match, so another attached geaos device is never grabbed.
|
|
41
|
+
let port = selection.port
|
|
42
|
+
if (!port && selection.usbSerial) {
|
|
43
|
+
try {
|
|
44
|
+
port = resolveUsbSerialPort({ serial: selection.usbSerial })
|
|
45
|
+
} catch {
|
|
46
|
+
port = ''
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (port) {
|
|
50
|
+
out.GEAOS_SERIAL_PORT = port
|
|
51
|
+
out.GEAOS_MTK_GADGET_GLOB = port
|
|
52
|
+
out.GEA_BOARD_MTK_MONITOR_GLOB = port
|
|
53
|
+
} else if (selection.usbSerial) {
|
|
54
|
+
out.GEAOS_MTK_GADGET_GLOB = `/dev/cu.usbmodem*${selection.usbSerial}*`
|
|
55
|
+
out.GEA_BOARD_MTK_MONITOR_GLOB = `/dev/cu.usbmodem*${selection.usbSerial}*`
|
|
56
|
+
}
|
|
57
|
+
return out
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function run(command, args, { cwd, env, dryRun, stdout, failureCode }) {
|
|
61
|
+
if (dryRun) {
|
|
62
|
+
stdout(formatCommand([command, ...args]))
|
|
63
|
+
return 0
|
|
64
|
+
}
|
|
65
|
+
if (!existsSync(command)) fail(`${command} was not found. Is the geaos target project available at ${cwd}?`, ExitCode.missingDependency)
|
|
66
|
+
const result = spawnSync(command, args, { cwd, env, stdio: 'inherit' })
|
|
67
|
+
if (result.error) throw result.error
|
|
68
|
+
if (result.status !== 0) throw new CliError(`ERROR: Command failed (${result.status ?? 1}): ${formatCommand([command, ...args])}`, failureCode)
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function runGeaos({ ctx, selection, action, app = null, positionals = [], env, dryRun = false, stdout }) {
|
|
73
|
+
const childEnv = geaosEnv(ctx, selection, env)
|
|
74
|
+
const targetDir = selection.targetDir
|
|
75
|
+
const appId = app?.id || positionals[0] || ''
|
|
76
|
+
const needApp = () => {
|
|
77
|
+
if (!appId) fail('A geaos app id is required (--app <id>).', ExitCode.usage)
|
|
78
|
+
return appId
|
|
79
|
+
}
|
|
80
|
+
if (selection.adapter === 'geaos-arm64') {
|
|
81
|
+
const script = path.join(targetDir, 'flash-geaos-arm64.sh')
|
|
82
|
+
switch (action) {
|
|
83
|
+
case 'build':
|
|
84
|
+
case 'flash':
|
|
85
|
+
case 'flash-monitor':
|
|
86
|
+
return run(script, [needApp()], { cwd: targetDir, env: { ...childEnv, GEAOS_ARM64_ACTION: action }, dryRun, stdout, failureCode: action === 'build' ? ExitCode.buildFailed : ExitCode.deployFailed })
|
|
87
|
+
case 'monitor':
|
|
88
|
+
return run(script, [appId || 'tic-tac-toe'], { cwd: targetDir, env: { ...childEnv, GEAOS_ARM64_ACTION: 'monitor' }, dryRun, stdout, failureCode: ExitCode.deployFailed })
|
|
89
|
+
default:
|
|
90
|
+
fail(`'${action}' is not supported by geaos-arm64 boards (build|flash|run|monitor).`, ExitCode.usage)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const deviceArgs = []
|
|
94
|
+
if (selection.telnetHost) deviceArgs.push('--host', selection.telnetHost)
|
|
95
|
+
if (selection.telnetPort) deviceArgs.push('--port', selection.telnetPort)
|
|
96
|
+
switch (action) {
|
|
97
|
+
case 'build':
|
|
98
|
+
return run(path.join(targetDir, 'build-geaos.sh'), [needApp()], { cwd: targetDir, env: childEnv, dryRun, stdout, failureCode: ExitCode.buildFailed })
|
|
99
|
+
case 'flash':
|
|
100
|
+
case 'flash-monitor':
|
|
101
|
+
return run(path.join(targetDir, 'flash-geaos.sh'), [needApp()], { cwd: targetDir, env: childEnv, dryRun, stdout, failureCode: ExitCode.deployFailed })
|
|
102
|
+
case 'flash-kernel':
|
|
103
|
+
return run(path.join(targetDir, 'flash-kernel.sh'), positionals, { cwd: targetDir, env: childEnv, dryRun, stdout, failureCode: ExitCode.deployFailed })
|
|
104
|
+
case 'bringup':
|
|
105
|
+
case 'probe':
|
|
106
|
+
case 'shell':
|
|
107
|
+
case 'push':
|
|
108
|
+
case 'screenshot':
|
|
109
|
+
case 'record':
|
|
110
|
+
case 'tap':
|
|
111
|
+
case 'drag':
|
|
112
|
+
case 'down':
|
|
113
|
+
case 'move':
|
|
114
|
+
case 'up':
|
|
115
|
+
return run(path.join(targetDir, 'geaos-device.py'), [...deviceArgs, action, ...positionals], { cwd: targetDir, env: childEnv, dryRun, stdout, failureCode: ExitCode.deployFailed })
|
|
116
|
+
default:
|
|
117
|
+
fail(`'${action}' is not supported by geaos-linux boards.`, ExitCode.usage)
|
|
118
|
+
}
|
|
119
|
+
}
|