@geastack/cli 0.1.0

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/src/args.mjs ADDED
@@ -0,0 +1,78 @@
1
+ export function parseArgs(argv) {
2
+ const options = {}
3
+ const positionals = []
4
+ const passthrough = []
5
+ let pass = false
6
+
7
+ for (let i = 0; i < argv.length; i += 1) {
8
+ const arg = argv[i]
9
+ if (pass) {
10
+ passthrough.push(arg)
11
+ continue
12
+ }
13
+ if (arg === '--') {
14
+ pass = true
15
+ continue
16
+ }
17
+ if (arg === '-h') {
18
+ setOption(options, 'help', true)
19
+ continue
20
+ }
21
+ if (arg === '-v') {
22
+ setOption(options, 'version', true)
23
+ continue
24
+ }
25
+ if (arg.startsWith('--')) {
26
+ const raw = arg.slice(2)
27
+ if (raw.startsWith('no-') && !raw.includes('=')) {
28
+ setOption(options, raw.slice(3), false)
29
+ continue
30
+ }
31
+ const eq = raw.indexOf('=')
32
+ if (eq !== -1) {
33
+ setOption(options, raw.slice(0, eq), raw.slice(eq + 1))
34
+ continue
35
+ }
36
+ const next = argv[i + 1]
37
+ if (next && !next.startsWith('-')) {
38
+ setOption(options, raw, next)
39
+ i += 1
40
+ } else {
41
+ setOption(options, raw, true)
42
+ }
43
+ continue
44
+ }
45
+ positionals.push(arg)
46
+ }
47
+
48
+ return { options, positionals, passthrough }
49
+ }
50
+
51
+ export function option(parsed, name, fallback = undefined) {
52
+ const value = parsed.options[name]
53
+ if (value === undefined) return fallback
54
+ return Array.isArray(value) ? value[value.length - 1] : value
55
+ }
56
+
57
+ export function optionList(parsed, name) {
58
+ const value = parsed.options[name]
59
+ if (value === undefined) return []
60
+ return (Array.isArray(value) ? value : [value])
61
+ .flatMap((entry) => String(entry).split(','))
62
+ .map((entry) => entry.trim())
63
+ .filter(Boolean)
64
+ }
65
+
66
+ export function flag(parsed, name) {
67
+ return option(parsed, name, false) !== false && option(parsed, name, false) !== undefined
68
+ }
69
+
70
+ function setOption(options, name, value) {
71
+ if (options[name] === undefined) {
72
+ options[name] = value
73
+ } else if (Array.isArray(options[name])) {
74
+ options[name].push(value)
75
+ } else {
76
+ options[name] = [options[name], value]
77
+ }
78
+ }
@@ -0,0 +1,224 @@
1
+ export const knownBoards = Object.freeze([
2
+ {
3
+ id: 'waveshare-amoled-206',
4
+ label: 'Waveshare ESP32-S3 Touch AMOLED 2.06',
5
+ alias: 'amoled',
6
+ target: 'esp32-s3-touch-amoled-2.06',
7
+ adapter: 'esp32-idf',
8
+ mcu: 'ESP32-S3',
9
+ capabilities: {
10
+ display: '2.06 inch AMOLED touch panel',
11
+ touch: 'board integrated touch',
12
+ wireless: ['WiFi', 'BLE'],
13
+ storage: ['flash', 'PSRAM'],
14
+ audio: [],
15
+ gps: false
16
+ }
17
+ },
18
+ {
19
+ id: 'waveshare-amoled-18',
20
+ label: 'Waveshare ESP32-S3 Touch AMOLED 1.8',
21
+ alias: 'amoled-18',
22
+ target: 'esp32-s3-touch-amoled-1.8',
23
+ adapter: 'esp32-idf',
24
+ mcu: 'ESP32-S3',
25
+ capabilities: {
26
+ display: '1.8 inch AMOLED touch panel',
27
+ touch: 'board integrated touch',
28
+ wireless: ['WiFi', 'BLE'],
29
+ storage: ['flash', 'PSRAM'],
30
+ audio: [],
31
+ gps: false
32
+ }
33
+ },
34
+ {
35
+ id: 'lilygo-t-display-s3-long',
36
+ label: 'LILYGO T-Display-S3 Long',
37
+ alias: 't-display-long',
38
+ target: 'esp32-s3-lilygo-t-display-s3-long',
39
+ adapter: 'esp32-idf',
40
+ mcu: 'ESP32-S3R8',
41
+ capabilities: {
42
+ display: '3.4 inch 180x640 AMOLED (AXS15231B)',
43
+ touch: 'capacitive touch hardware (target driver pending)',
44
+ wireless: ['WiFi', 'BLE'],
45
+ storage: ['16 MB flash', '8 MB PSRAM'],
46
+ audio: [],
47
+ gps: false
48
+ }
49
+ },
50
+ {
51
+ id: 'waveshare-amoled-175',
52
+ label: 'Waveshare ESP32-S3 Touch AMOLED 1.75',
53
+ alias: 'amoled-175',
54
+ target: 'esp32-s3-touch-amoled-1.75',
55
+ adapter: 'esp32-idf',
56
+ mcu: 'ESP32-S3',
57
+ capabilities: {
58
+ display: '1.75 inch AMOLED touch panel',
59
+ touch: 'board integrated touch',
60
+ wireless: ['WiFi', 'BLE'],
61
+ storage: ['flash', 'PSRAM'],
62
+ audio: [],
63
+ gps: false
64
+ }
65
+ },
66
+ {
67
+ id: 'esp32-s3-epaper-154',
68
+ label: 'ESP32-S3 e-paper 1.54',
69
+ alias: 'epaper',
70
+ target: 'esp32-s3-epaper-1.54',
71
+ adapter: 'esp32-idf',
72
+ mcu: 'ESP32-S3',
73
+ capabilities: {
74
+ display: '1.54 inch e-paper',
75
+ touch: 'none',
76
+ wireless: ['WiFi', 'BLE'],
77
+ storage: ['flash'],
78
+ audio: [],
79
+ gps: false
80
+ }
81
+ },
82
+ {
83
+ id: 'elecrow-rotary-21',
84
+ label: 'Elecrow ESP32-S3 Rotary 2.1',
85
+ alias: 'rotary',
86
+ target: 'esp32-s3-elecrow-rotary-2.1',
87
+ adapter: 'esp32-idf',
88
+ mcu: 'ESP32-S3',
89
+ capabilities: {
90
+ display: '2.1 inch round display',
91
+ touch: 'rotary/input board controls',
92
+ wireless: ['WiFi', 'BLE'],
93
+ storage: ['flash', 'PSRAM'],
94
+ audio: [],
95
+ gps: false
96
+ }
97
+ },
98
+ {
99
+ id: 'waveshare-p4-lcd-7',
100
+ label: 'Waveshare ESP32-P4 Touch LCD 7',
101
+ alias: 'waveshare-p4-7',
102
+ target: 'esp32-p4-waveshare-touch-lcd-7',
103
+ adapter: 'esp32-idf',
104
+ mcu: 'ESP32-P4',
105
+ capabilities: {
106
+ display: '7 inch RGB touch LCD',
107
+ touch: 'board integrated touch',
108
+ wireless: [],
109
+ storage: ['flash', 'PSRAM'],
110
+ audio: [],
111
+ gps: false
112
+ }
113
+ },
114
+ {
115
+ id: 'm5stack-tab5',
116
+ label: 'M5Stack Tab5',
117
+ alias: 'm5tab',
118
+ target: 'esp32-p4-m5stack-tab5',
119
+ adapter: 'esp32-idf',
120
+ mcu: 'ESP32-P4 + ESP32-C6',
121
+ capabilities: {
122
+ display: '5 inch 1280x720 IPS touch panel (ST7123/ST7121 current, ILI9881C legacy)',
123
+ touch: 'GT911 or ST7123/ST7121 integrated touch',
124
+ wireless: ['ESP32-C6 WiFi 6', 'BLE/Thread/Zigbee capable'],
125
+ storage: ['16 MB flash', '32 MB PSRAM', 'microSD'],
126
+ audio: ['ES8388 codec', 'ES7210 dual-mic AEC', 'NS4150B 1W speaker', '3.5mm headphone'],
127
+ gps: false,
128
+ sensors: ['BMI270 IMU', 'RX8130CE RTC', 'INA226 power monitor', 'IP2326 charger', 'PI4IOE5V6408 IO expanders', 'SC2356 camera', 'SIT3088 RS485']
129
+ }
130
+ },
131
+ {
132
+ id: 'waveshare-p4-lcd-35',
133
+ label: 'Waveshare ESP32-P4 Touch LCD 3.5',
134
+ alias: 'waveshare-p4-3.5',
135
+ target: 'esp32-p4-waveshare-touch-lcd-3.5',
136
+ adapter: 'esp32-idf',
137
+ mcu: 'ESP32-P4',
138
+ capabilities: {
139
+ display: '3.5 inch RGB touch LCD',
140
+ touch: 'board integrated touch',
141
+ wireless: [],
142
+ storage: ['flash', 'PSRAM'],
143
+ audio: [],
144
+ gps: false
145
+ }
146
+ },
147
+ {
148
+ id: 'waveshare-rp2350-amoled-241',
149
+ label: 'Waveshare RP2350 Touch AMOLED 2.41',
150
+ alias: 'waveshare-rp2350-amoled-2.41',
151
+ target: 'rp2350-waveshare-touch-amoled-2.41',
152
+ adapter: 'rp2350-pico',
153
+ mcu: 'RP2350',
154
+ capabilities: {
155
+ display: '2.41 inch 450x600 AMOLED touch panel (RM690B0)',
156
+ touch: 'FT6336 capacitive touch',
157
+ wireless: [],
158
+ storage: ['16 MB flash', '2 MB PSRAM'],
159
+ audio: [],
160
+ gps: false,
161
+ sensors: ['QMI8658 IMU', 'PCF85063 RTC', 'ETA6098 power']
162
+ }
163
+ },
164
+ {
165
+ id: 'pimoroni-tufty-2350',
166
+ label: 'Pimoroni Tufty 2350',
167
+ alias: 'tufty-2350',
168
+ target: 'rp2350-tufty-2350',
169
+ adapter: 'rp2350-pico',
170
+ mcu: 'RP2350',
171
+ capabilities: {
172
+ display: '2.8 inch 320x240 IPS LCD (ST7789 parallel)',
173
+ touch: 'none',
174
+ wireless: ['WiFi', 'BLE'],
175
+ storage: ['16 MB flash', '8 MB PSRAM'],
176
+ audio: [],
177
+ gps: false,
178
+ sensors: ['PCF85063 RTC', 'buttons', 'battery sense', 'rear lighting']
179
+ }
180
+ }
181
+ ])
182
+
183
+ export const customBoardDefaults = Object.freeze({
184
+ targetFamily: 'esp32',
185
+ adapter: 'esp32-idf',
186
+ baseTarget: '',
187
+ mcu: '',
188
+ display: {
189
+ kind: '',
190
+ controller: '',
191
+ interface: '',
192
+ resolution: ''
193
+ },
194
+ touch: {
195
+ controller: '',
196
+ interface: ''
197
+ },
198
+ wireless: {
199
+ wifi: '',
200
+ ble: ''
201
+ },
202
+ gps: {
203
+ module: '',
204
+ interface: ''
205
+ },
206
+ audio: {
207
+ codec: '',
208
+ output: '',
209
+ input: ''
210
+ },
211
+ storage: [],
212
+ sensors: [],
213
+ power: '',
214
+ transports: {},
215
+ notes: ''
216
+ })
217
+
218
+ export function boardById(id) {
219
+ return knownBoards.find((board) => board.id === id) || null
220
+ }
221
+
222
+ export function boardByTarget(target) {
223
+ return knownBoards.find((board) => board.target === target) || null
224
+ }
@@ -0,0 +1,110 @@
1
+ import path from 'node:path'
2
+ import { createRequire } from 'node:module'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ import { option } from './args.mjs'
6
+ import { exists, findUp } from './fs-utils.mjs'
7
+
8
+ const srcDir = path.dirname(fileURLToPath(import.meta.url))
9
+ export const cliPackageRoot = path.resolve(srcDir, '..')
10
+ const requireFromCli = createRequire(import.meta.url)
11
+
12
+ export function createContext(parsed, _env = process.env, cwd = process.cwd()) {
13
+ const absoluteCwd = path.resolve(cwd)
14
+ const projectRoot = findNodeProjectRoot(absoluteCwd) || absoluteCwd
15
+ const initialAnchors = [projectRoot, cliPackageRoot]
16
+ const targetsRoot = resolveInstalledPackageDir('@geastack/targets', initialAnchors)
17
+ const corePackageDir = resolveInstalledPackageDir('@geastack/core', initialAnchors)
18
+ const packageAnchors = [projectRoot, cliPackageRoot, targetsRoot, corePackageDir].filter(Boolean)
19
+ const compilerPackageDir = resolveInstalledPackageDir('@geastack/compiler', packageAnchors)
20
+ const projectBoardsConfig = path.join(projectRoot, '.gea', 'boards.json')
21
+ const explicitBoardsConfig = option(parsed, 'boards-config') || ''
22
+ const boardsConfig = explicitBoardsConfig || (exists(projectBoardsConfig) ? projectBoardsConfig : '')
23
+
24
+ const ctx = {
25
+ cwd: absoluteCwd,
26
+ projectRoot,
27
+ projectBoardsConfig,
28
+ cliPackageRoot,
29
+ compilerPackageDir,
30
+ corePackageDir,
31
+ chipsPackageDir: resolveInstalledPackageDir('@geastack/chips', packageAnchors),
32
+ elementsPackageDir: resolveInstalledPackageDir('@geastack/elements', packageAnchors),
33
+ enginePackageDir: resolveInstalledPackageDir('@geastack/engine', packageAnchors),
34
+ geaosPackageDir: resolveInstalledPackageDir('@geastack/geaos', packageAnchors),
35
+ hostPackageDir: resolveInstalledPackageDir('@geastack/host', packageAnchors),
36
+ pluginPackageDir: resolveInstalledPackageDir('@geastack/geatsc-plugin-gea', packageAnchors),
37
+ boardsConfig: boardsConfig ? path.resolve(absoluteCwd, boardsConfig) : '',
38
+ examplesRoot: projectRoot,
39
+ simulatorRoot: '',
40
+ androidRoot: '',
41
+ appleRoot: '',
42
+ targetsRoot
43
+ }
44
+ ctx.scripts = {
45
+ board: packageFile(ctx.targetsRoot, 'scripts', 'board'),
46
+ webBuild: '',
47
+ webDev: '',
48
+ androidBuild: '',
49
+ macosBuild: '',
50
+ iosBuild: '',
51
+ geaEmbedded: packageFile(ctx.corePackageDir, 'bin', 'gea-embedded.mjs')
52
+ }
53
+ return ctx
54
+ }
55
+
56
+ export function createChildEnv(ctx, env = process.env) {
57
+ const out = {
58
+ ...env,
59
+ GEA_APPS_ROOT: ctx.projectRoot,
60
+ GEA_CLI_BIN: path.join(ctx.cliPackageRoot, 'bin', 'gea.mjs'),
61
+ GEA_CHIPS_DIR: ctx.chipsPackageDir,
62
+ GEA_CORE_PACKAGE: ctx.corePackageDir,
63
+ GEA_CORE_DIR: ctx.corePackageDir,
64
+ GEA_COMPILER_DIR: ctx.compilerPackageDir,
65
+ GEA_ELEMENTS_DIR: ctx.elementsPackageDir,
66
+ GEA_ENGINE_DIR: ctx.enginePackageDir,
67
+ GEA_EXTRA_APP_DIRS: appendPathList(env.GEA_EXTRA_APP_DIRS, ctx.projectRoot),
68
+ GEA_GEAOS_PACKAGE_DIR: ctx.geaosPackageDir,
69
+ GEA_HOST_DIR: ctx.hostPackageDir,
70
+ GEA_PLUGIN_DIR: ctx.pluginPackageDir,
71
+ GEA_TARGETS_ROOT: ctx.targetsRoot
72
+ }
73
+ if (ctx.boardsConfig) out.GEA_BOARDS_CONFIG = ctx.boardsConfig
74
+ for (const [name, value] of Object.entries(out)) {
75
+ if (name.startsWith('GEA_') && value === '') delete out[name]
76
+ }
77
+ return out
78
+ }
79
+
80
+ function resolveInstalledPackageDir(packageName, anchors) {
81
+ for (const anchor of anchors) {
82
+ let current = path.resolve(anchor)
83
+ while (true) {
84
+ const candidate = path.join(current, 'node_modules', ...packageName.split('/'))
85
+ if (exists(path.join(candidate, 'package.json'))) return candidate
86
+ const parent = path.dirname(current)
87
+ if (parent === current) break
88
+ current = parent
89
+ }
90
+ }
91
+ try {
92
+ return path.dirname(requireFromCli.resolve(`${packageName}/package.json`))
93
+ } catch {
94
+ return ''
95
+ }
96
+ }
97
+
98
+ function packageFile(packageRoot, ...segments) {
99
+ return packageRoot ? path.join(packageRoot, ...segments) : ''
100
+ }
101
+
102
+ function appendPathList(current, value) {
103
+ const entries = String(current || '').split(path.delimiter).filter(Boolean)
104
+ if (value && !entries.includes(value)) entries.push(value)
105
+ return entries.join(path.delimiter)
106
+ }
107
+
108
+ function findNodeProjectRoot(start) {
109
+ return findUp(start, (dir) => exists(path.join(dir, 'package.json')))
110
+ }