@geastack/core 0.1.23

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.
Files changed (73) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +36 -0
  3. package/app_frame_perf.cpp +77 -0
  4. package/css.d.ts +1 -0
  5. package/events.cpp +67 -0
  6. package/gea_app_entry.cpp +317 -0
  7. package/gea_sources.mjs +126 -0
  8. package/gea_sources.sh +36 -0
  9. package/include/app.h +60 -0
  10. package/include/apps.h +46 -0
  11. package/include/audio.h +90 -0
  12. package/include/ble.h +212 -0
  13. package/include/bluetooth/hid_profile.h +92 -0
  14. package/include/camera.h +107 -0
  15. package/include/canvas.h +290 -0
  16. package/include/display.h +390 -0
  17. package/include/display_present.h +869 -0
  18. package/include/event.h +32 -0
  19. package/include/events.h +116 -0
  20. package/include/gea/embedded.h +13 -0
  21. package/include/gea/reactive_runtime.h +32 -0
  22. package/include/gea.h +14 -0
  23. package/include/gea_perf_config.h +96 -0
  24. package/include/geaos/abi.h +165 -0
  25. package/include/geolocation.h +126 -0
  26. package/include/gpio.h +61 -0
  27. package/include/graphics/font.h +122 -0
  28. package/include/i2c.h +20 -0
  29. package/include/image.h +156 -0
  30. package/include/imu.h +23 -0
  31. package/include/input.h +33 -0
  32. package/include/memory.h +52 -0
  33. package/include/pixel.h +907 -0
  34. package/include/platform/file_cache.h +18 -0
  35. package/include/power.h +11 -0
  36. package/include/runtime.h +83 -0
  37. package/include/services/app_runner.h +10 -0
  38. package/include/services/app_state.h +14 -0
  39. package/include/services/battery_service.h +12 -0
  40. package/include/services/bluetooth_service.h +13 -0
  41. package/include/services/diagnostics.h +76 -0
  42. package/include/services/diagnostics_platform.h +54 -0
  43. package/include/services/frame_scheduler.h +71 -0
  44. package/include/services/mirror.h +49 -0
  45. package/include/services/network_services.h +25 -0
  46. package/include/services/ota.h +32 -0
  47. package/include/services/runtime_log.h +11 -0
  48. package/include/services/storage_service.h +26 -0
  49. package/include/touch.h +38 -0
  50. package/include/ui/signal.h +149 -0
  51. package/include/wifi.h +67 -0
  52. package/index.d.ts +1940 -0
  53. package/jsx.d.ts +1 -0
  54. package/package.json +70 -0
  55. package/runtime/ble.ts +49 -0
  56. package/runtime/color.ts +21 -0
  57. package/runtime/compiler.ts +43 -0
  58. package/runtime/host.ts +1083 -0
  59. package/runtime/images.ts +129 -0
  60. package/runtime/primitives.ts +49 -0
  61. package/runtime-surface.ts +180 -0
  62. package/runtime.cpp +431 -0
  63. package/runtime.ts +34 -0
  64. package/scripts/build-gea-vite-geatsc.mjs +4514 -0
  65. package/scripts/dotenv-defines.mjs +98 -0
  66. package/scripts/gea-bundle-type-hints.mjs +546 -0
  67. package/scripts/gea-embedded-compat-staging.mjs +17 -0
  68. package/scripts/gea-embedded-compat-transform.mjs +427 -0
  69. package/scripts/gea-vite-module-graph-plugin.mjs +578 -0
  70. package/scripts/generate-gea-embedded-assets.mjs +296 -0
  71. package/scripts/generate-gea-embedded-fonts.mjs +1252 -0
  72. package/scripts/generate-installed-app-plan.mjs +55 -0
  73. package/scripts/native-object-cache.sh +244 -0
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env node
2
+ // Scans an app directory for binary assets (images/fonts) and emits a C++ translation
3
+ // unit that embeds each one as a named, externally-visible `.rodata` symbol.
4
+ //
5
+ // This is the build half of "store images as files, read them as files": the
6
+ // PNG stays a real file in the app folder, and instead of a hand-written byte
7
+ // array in the JS source, the bytes are linked into the app binary (the ESP32
8
+ // app partition; the macOS executable; the WASM module — the web build runs
9
+ // with NO_FILESYSTEM=1, so compiled-in bytes are the only option).
10
+ //
11
+ // Each asset is exported as `gea_asset_<sanitized-path>` + a matching `_len`,
12
+ // with the SAME public-path normalization and sanitization the codegen uses (see
13
+ // canonicalAssetPath() / assetSymbol() here and in
14
+ // packages/geatsc-plugin-gea/src/cpp-mounted-lowering.ts). An `<img src="x.png">`
15
+ // is lowered to a direct reference to that symbol. That reference is what pulls
16
+ // this object into the link: ESP-IDF links framework code from static archives,
17
+ // and an archive member with no referenced symbol (e.g. a static-init-only TU)
18
+ // is DISCARDED — so registration via a global constructor silently never runs
19
+ // on-device. A referenced data symbol is kept on every toolchain.
20
+ //
21
+ // Symbols are `weak` so several apps' asset TUs can be linked together (the
22
+ // shared binary) without duplicate-definition errors when they share
23
+ // an asset filename; the linker keeps one copy.
24
+
25
+ import fs from 'node:fs'
26
+ import path from 'node:path'
27
+ import process from 'node:process'
28
+ import { execFileSync } from 'node:child_process'
29
+ import { createHash } from 'node:crypto'
30
+ import { fileURLToPath } from 'node:url'
31
+
32
+ const args = process.argv.slice(2)
33
+
34
+ function readOption(name) {
35
+ const index = args.indexOf(name)
36
+ return index >= 0 ? args[index + 1] : undefined
37
+ }
38
+
39
+ function fail(message) {
40
+ process.stderr.write(`${message}\n`)
41
+ process.exit(1)
42
+ }
43
+
44
+ const appDir = path.resolve(readOption('--app-dir') ?? fail('missing --app-dir <dir>'))
45
+ const outCpp = path.resolve(readOption('--out-cpp') ?? fail('missing --out-cpp <file>'))
46
+
47
+ const ASSET_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.ttf', '.otf'])
48
+ // Launcher/package icons are metadata, not runtime `<Image>` assets. If an app
49
+ // wants to render one, keep a copy under `public/` so the reference is explicit.
50
+ const PRUNE_DIRS = new Set(['node_modules', 'dist', 'build'])
51
+ // Hidden directories are never an app's assets. They are where tools keep their
52
+ // own state -- .git, .vite, .gea, an editor's cache, a browser automation run's
53
+ // screenshots -- and an app cannot reference what is inside one anyway, because
54
+ // nothing writes an <Image src=".something/...">. Scanning them once put three
55
+ // megabytes of a test tool's PNGs into a firmware image, where they cost flash
56
+ // and, because flash rodata and PSRAM share one address window on the ESP32-S3,
57
+ // three megabytes of addressable PSRAM with it.
58
+
59
+ // Keep this identical to canonicalAssetPath() / assetSymbol() in
60
+ // packages/geatsc-plugin-gea/src/cpp-mounted-lowering.ts.
61
+ function canonicalAssetPath(relPath) {
62
+ const normalized = relPath.startsWith('/') ? relPath.slice(1) : relPath
63
+ return normalized.startsWith('public/') ? normalized.slice('public/'.length) : normalized
64
+ }
65
+
66
+ function assetSymbol(relPath) {
67
+ return `gea_asset_${canonicalAssetPath(relPath).replace(/[^A-Za-z0-9]/g, '_')}`
68
+ }
69
+
70
+ function collectAssets(dir) {
71
+ const found = []
72
+ const walk = (current) => {
73
+ let entries
74
+ try {
75
+ entries = fs.readdirSync(current, { withFileTypes: true })
76
+ } catch {
77
+ return
78
+ }
79
+ for (const entry of entries) {
80
+ const full = path.join(current, entry.name)
81
+ if (entry.isDirectory()) {
82
+ const rel = path.relative(appDir, full).split(path.sep).join('/')
83
+ if (!PRUNE_DIRS.has(entry.name) && !entry.name.startsWith('.') && rel !== 'icons') walk(full)
84
+ } else if (entry.isFile() && ASSET_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
85
+ found.push(full)
86
+ }
87
+ }
88
+ }
89
+ walk(dir)
90
+ found.sort()
91
+ return found
92
+ }
93
+
94
+ function relPath(full) {
95
+ return path.relative(appDir, full).split(path.sep).join('/')
96
+ }
97
+
98
+ function byteArrayBody(buffer) {
99
+ const lines = []
100
+ const perLine = 16
101
+ for (let i = 0; i < buffer.length; i += perLine) {
102
+ const chunk = []
103
+ for (let j = i; j < Math.min(i + perLine, buffer.length); j++) chunk.push(buffer[j])
104
+ lines.push(' ' + chunk.join(', ') + (i + perLine < buffer.length ? ',' : ''))
105
+ }
106
+ return lines.join('\n')
107
+ }
108
+
109
+ // These TUs are megabytes of generated byte arrays that no human reads, and
110
+ // clang-format is quadratic enough on them to cost minutes of CPU on a first
111
+ // build (15 CPU-minutes on a 2.8 MB asset TU, on every fresh clone and every
112
+ // CI runner, because the stamp below only helps a machine that already paid
113
+ // it once). Formatting is therefore opt-in: set GEATSC_FORMAT_CPP=1 when the
114
+ // generated output is being read by a person.
115
+ const CPP_FORMAT_EXTENSIONS = /\.(?:c|cc|cpp|cxx|m|mm|h|hh|hpp|hxx)$/i
116
+ const CPP_FORMAT_STYLE = '{BasedOnStyle: LLVM, SortIncludes: false, ColumnLimit: 160}'
117
+ let clangFormatUnavailable = false
118
+
119
+ function formatCppSourceIfNeeded(filePath, contents) {
120
+ if (!CPP_FORMAT_EXTENSIONS.test(filePath)) return contents
121
+ if (process.env.GEATSC_FORMAT_CPP !== '1') return contents
122
+ if (clangFormatUnavailable) return contents
123
+ const clangFormat = process.env.CLANG_FORMAT || 'clang-format'
124
+ try {
125
+ return execFileSync(clangFormat, [`--style=${CPP_FORMAT_STYLE}`, `--assume-filename=${filePath}`], {
126
+ input: contents,
127
+ encoding: 'utf8',
128
+ maxBuffer: 512 * 1024 * 1024
129
+ })
130
+ } catch (error) {
131
+ if (error?.code === 'ENOENT') {
132
+ clangFormatUnavailable = true
133
+ process.stderr.write('generate-gea-embedded-assets: clang-format not found; generated C++ will be written unformatted\n')
134
+ return contents
135
+ }
136
+ throw error
137
+ }
138
+ }
139
+
140
+ // Formatting an embedded-asset TU (megabytes of byte arrays) and then throwing
141
+ // the result away because nothing changed is pure waste, so prove the formatter
142
+ // cannot produce anything new instead of running it. A sidecar stamp records the
143
+ // hash of the UNFORMATTED input, the hash of the formatted bytes it produced and
144
+ // the formatter identity; when all three still hold, the file on disk already IS
145
+ // the formatter's output. See the identical guard in
146
+ // generate-gea-embedded-fonts.mjs.
147
+ const FORMAT_STAMP_VERSION = 1
148
+ let clangFormatIdentity
149
+
150
+ function sha256(value) {
151
+ return createHash('sha256').update(value, typeof value === 'string' ? 'utf8' : undefined).digest('hex')
152
+ }
153
+
154
+ function formatterIdentity() {
155
+ if (clangFormatIdentity !== undefined) return clangFormatIdentity
156
+ const clangFormat = process.env.CLANG_FORMAT || 'clang-format'
157
+ try {
158
+ const version = execFileSync(clangFormat, ['--version'], { encoding: 'utf8' }).trim()
159
+ clangFormatIdentity = `${clangFormat} ${version} ${CPP_FORMAT_STYLE}`
160
+ } catch {
161
+ clangFormatIdentity = ''
162
+ }
163
+ return clangFormatIdentity
164
+ }
165
+
166
+ function formatStampPath(filePath) {
167
+ return path.join(path.dirname(filePath), `.${path.basename(filePath)}.fmtstamp`)
168
+ }
169
+
170
+ function writeFileIfChanged(filePath, contents) {
171
+ const formats = CPP_FORMAT_EXTENSIONS.test(filePath) && process.env.GEATSC_FORMAT_CPP === '1' && !clangFormatUnavailable
172
+ if (!formats) {
173
+ if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8') === contents) return false
174
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
175
+ fs.writeFileSync(filePath, contents)
176
+ return true
177
+ }
178
+
179
+ const stampPath = formatStampPath(filePath)
180
+ const rawHash = sha256(contents)
181
+ const identity = formatterIdentity()
182
+ if (identity) {
183
+ let stamp
184
+ try {
185
+ stamp = JSON.parse(fs.readFileSync(stampPath, 'utf8'))
186
+ } catch {
187
+ stamp = undefined
188
+ }
189
+ if (stamp?.version === FORMAT_STAMP_VERSION && stamp.raw === rawHash && stamp.identity === identity) {
190
+ try {
191
+ if (sha256(fs.readFileSync(filePath)) === stamp.formatted) return false
192
+ } catch {
193
+ // fall through and format
194
+ }
195
+ }
196
+ }
197
+
198
+ const formattedContents = formatCppSourceIfNeeded(filePath, contents)
199
+ const changed = !fs.existsSync(filePath) || fs.readFileSync(filePath, 'utf8') !== formattedContents
200
+ if (changed) {
201
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
202
+ fs.writeFileSync(filePath, formattedContents)
203
+ }
204
+ if (identity) {
205
+ try {
206
+ fs.writeFileSync(
207
+ stampPath,
208
+ JSON.stringify({ version: FORMAT_STAMP_VERSION, raw: rawHash, formatted: sha256(formattedContents), identity })
209
+ )
210
+ } catch {
211
+ // a stamp we cannot write only costs the next build the formatting pass
212
+ }
213
+ }
214
+ return changed
215
+ }
216
+
217
+ const assets = collectAssets(appDir)
218
+
219
+ const parts = []
220
+ parts.push('// Generated by scripts/generate-gea-embedded-assets.mjs. Do not edit by hand.')
221
+ parts.push(`// app: ${relPath(appDir) || path.basename(appDir)}`)
222
+ parts.push('')
223
+ parts.push('#include <cstring>')
224
+ parts.push('')
225
+
226
+ if (assets.length === 0) {
227
+ parts.push('// No embedded assets for this app.')
228
+ // Still define gea_embedded_asset_lookup (always false): host/image.cpp declares
229
+ // it __attribute__((weak)), which is a weak *reference* on GCC/Xtensa (resolves to
230
+ // null when undefined) but NOT on Apple's ld — there an undefined weak symbol is a
231
+ // hard link error. Defining it unconditionally keeps no-asset apps linking on macOS/iOS.
232
+ parts.push('extern "C" __attribute__((weak)) bool gea_embedded_asset_lookup(const char *, const unsigned char **, unsigned long *) {')
233
+ parts.push(' return false;')
234
+ parts.push('}')
235
+ parts.push('')
236
+ } else {
237
+ // Symbols are defined with C linkage at global scope. The codegen references
238
+ // them from inside `namespace gea_ir`, but when a program uses isolated symbols
239
+ // inside a bundle (e.g. the launcher) geatsc wraps that whole program — and
240
+ // its `gea_ir` — in an anonymous namespace. A C++ `extern` would then resolve
241
+ // to `(anonymous namespace)::gea_ir::gea_asset_<path>` (internal linkage),
242
+ // which no separate TU can define. `extern "C"` gives each asset one flat,
243
+ // unmangled symbol that the reference resolves to identically whether its
244
+ // enclosing `gea_ir` is global or anonymous-namespace-nested. Keep this in
245
+ // sync with imageSrcAssetLines() / the forward decls in
246
+ // packages/geatsc-plugin-gea/src/cpp-mounted-lowering.ts and cpp-ir.ts.
247
+ //
248
+ // `extern "C"` + initializer gives the arrays external linkage (a plain
249
+ // `const` at namespace scope would be internal and invisible to the
250
+ // reference). `weak` lets duplicate asset filenames across bundled apps (the
251
+ // shared binary) link without a duplicate-definition error.
252
+ const seen = new Set()
253
+ for (const file of assets) {
254
+ const rel = relPath(file)
255
+ const sym = assetSymbol(rel)
256
+ if (seen.has(sym)) {
257
+ process.stderr.write(`generate-gea-embedded-assets: WARNING duplicate asset symbol ${sym} for ${rel}; skipping\n`)
258
+ continue
259
+ }
260
+ seen.add(sym)
261
+ const buffer = fs.readFileSync(file)
262
+ parts.push(`// ${rel} (${buffer.length} bytes)`)
263
+ // `extern "C"` on the definition: a namespace-scope `const` defaults to
264
+ // INTERNAL linkage, and a `weak` symbol must be public — GCC (Xtensa)
265
+ // rejects `weak` on an internal const. `extern "C"` + initializer = a weak
266
+ // definition with external, unmangled (namespace-independent) linkage,
267
+ // which both GCC and Clang accept.
268
+ parts.push(`extern "C" __attribute__((weak)) const unsigned char ${sym}[] = {`)
269
+ parts.push(byteArrayBody(buffer))
270
+ parts.push('};')
271
+ parts.push(`extern "C" __attribute__((weak)) const unsigned long ${sym}_len = ${buffer.length}UL;`)
272
+ parts.push('')
273
+ }
274
+ parts.push('extern "C" __attribute__((weak)) bool gea_embedded_asset_lookup(const char *path, const unsigned char **data, unsigned long *length) {')
275
+ parts.push(' if (!path || !data || !length) return false;')
276
+ for (const file of assets) {
277
+ const rel = relPath(file)
278
+ const sym = assetSymbol(rel)
279
+ const aliases = [rel]
280
+ if (rel.startsWith('public/')) aliases.push(rel.slice('public/'.length))
281
+ const condition = aliases
282
+ .map((alias) => `(std::strcmp(path, ${JSON.stringify(alias)}) == 0 || (path[0] == '/' && std::strcmp(path + 1, ${JSON.stringify(alias)}) == 0))`)
283
+ .join(' || ')
284
+ parts.push(` if (${condition}) {`)
285
+ parts.push(` *data = ${sym};`)
286
+ parts.push(` *length = ${sym}_len;`)
287
+ parts.push(' return true;')
288
+ parts.push(' }')
289
+ }
290
+ parts.push(' return false;')
291
+ parts.push('}')
292
+ parts.push('')
293
+ }
294
+
295
+ writeFileIfChanged(outCpp, parts.join('\n'))
296
+ process.stderr.write(`generate-gea-embedded-assets: ${assets.length} asset(s) -> ${path.relative(process.cwd(), outCpp)}\n`)