@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,427 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { createRequire } from 'node:module'
4
+
5
+ const require = createRequire(new URL('../vendor/gea/package.json', import.meta.url))
6
+ const parser = require('@babel/parser')
7
+ const traverse = require('@babel/traverse').default
8
+ const generate = require('@babel/generator').default
9
+ const t = require('@babel/types')
10
+
11
+ const ignoredKeys = new Set([
12
+ 'loc',
13
+ 'start',
14
+ 'end',
15
+ 'leadingComments',
16
+ 'innerComments',
17
+ 'trailingComments',
18
+ 'extra',
19
+ ])
20
+
21
+ function hasJsx(node) {
22
+ if (!node || typeof node !== 'object') return false
23
+ if (Array.isArray(node)) return node.some(hasJsx)
24
+ if (typeof node.type === 'string' && node.type.startsWith('JSX')) return true
25
+ for (const key of Object.keys(node)) {
26
+ if (ignoredKeys.has(key)) continue
27
+ if (hasJsx(node[key])) return true
28
+ }
29
+ return false
30
+ }
31
+
32
+ function isComponentName(name) {
33
+ return /^[A-Z]/.test(name)
34
+ }
35
+
36
+ function isFunctionComponent(fn, name) {
37
+ return !!fn && isComponentName(name) && hasJsx(fn.body)
38
+ }
39
+
40
+ function functionTemplateBody(fn) {
41
+ if (t.isBlockStatement(fn.body)) {
42
+ return fn.body.body.map((stmt) => t.cloneNode(stmt, true))
43
+ }
44
+ return [t.returnStatement(t.cloneNode(fn.body, true))]
45
+ }
46
+
47
+ function componentClassFromFunction(name, fn) {
48
+ const params = fn.params.map((param) => t.cloneNode(param, true))
49
+ const method = t.classMethod(
50
+ 'method',
51
+ t.identifier('template'),
52
+ params,
53
+ t.blockStatement(functionTemplateBody(fn)),
54
+ )
55
+ if (fn.returnType) method.returnType = t.cloneNode(fn.returnType, true)
56
+ const declaration = t.classDeclaration(t.identifier(name), t.identifier('Component'), t.classBody([method]))
57
+ // A function component's props are its first parameter's type, and the class
58
+ // this becomes has to say so: `Component`'s second type parameter is where a
59
+ // component states them, and it defaults to `void`. Leaving it defaulted made
60
+ // the generated `template({ label, value }: { ... })` an override whose
61
+ // parameter is neither assignable to `void` nor assignable from it -- a
62
+ // checker error on every function component that takes props, which is what
63
+ // the settings/hid-clicker/app-launcher pipelines hit. The type is COPIED
64
+ // from the parameter rather than inferred, so the two spellings cannot drift.
65
+ const propsType = params[0]?.typeAnnotation
66
+ if (propsType && t.isTSTypeAnnotation(propsType)) {
67
+ declaration.superTypeParameters = t.tsTypeParameterInstantiation([
68
+ t.tsTypeReference(t.identifier('GeaElement')),
69
+ t.cloneNode(propsType.typeAnnotation, true),
70
+ ])
71
+ }
72
+ return declaration
73
+ }
74
+
75
+ function componentClassFromVariableDeclarator(declarator) {
76
+ if (!t.isIdentifier(declarator.id)) return null
77
+ const name = declarator.id.name
78
+ const init = declarator.init
79
+ if (!isFunctionComponent(init, name)) return null
80
+ return componentClassFromFunction(name, init)
81
+ }
82
+
83
+ // `GeaElement` is an interface, so it must be imported `type`-only: the staged
84
+ // tree is bundled as well as checked, and a value import of a name the runtime
85
+ // module does not export at runtime is an unresolved export, not a no-op.
86
+ function runtimeImportSpecifier(name) {
87
+ const specifier = t.importSpecifier(t.identifier(name), t.identifier(name))
88
+ if (name === 'GeaElement') specifier.importKind = 'type'
89
+ return specifier
90
+ }
91
+
92
+ // The names a generated component class spells: `Component` always, and
93
+ // `GeaElement` only when one of them states props -- it is the first type
94
+ // argument you have to pass in order to reach the second. `runtime.ts`
95
+ // re-exports it (via `runtime-surface.ts`) for exactly this kind of reason, so
96
+ // it resolves from the same module `Component` does.
97
+ function ensureComponentImport(ast, alsoGeaElement) {
98
+ const wanted = alsoGeaElement ? ['Component', 'GeaElement'] : ['Component']
99
+ for (const node of ast.program.body) {
100
+ if (!t.isImportDeclaration(node)) continue
101
+ if (node.source.value !== 'gea-embedded' && node.source.value !== '@geajs/core') continue
102
+ const present = new Set(
103
+ node.specifiers.filter((spec) => t.isImportSpecifier(spec)).map((spec) => spec.imported.name),
104
+ )
105
+ for (const name of wanted) {
106
+ if (!present.has(name)) node.specifiers.push(runtimeImportSpecifier(name))
107
+ }
108
+ return
109
+ }
110
+
111
+ ast.program.body.unshift(
112
+ t.importDeclaration(wanted.map(runtimeImportSpecifier), t.stringLiteral('gea-embedded')),
113
+ )
114
+ }
115
+
116
+ function runtimeCoreSpecifierName(spec) {
117
+ if (!t.isImportSpecifier(spec)) return null
118
+ const imported = spec.imported
119
+ const name = t.isIdentifier(imported) ? imported.name : imported.value
120
+ return name === 'Component' || name === 'Store' ? name : null
121
+ }
122
+
123
+ function importHasSpecifier(node, importedName, localName) {
124
+ return node.specifiers.some((spec) => {
125
+ if (!t.isImportSpecifier(spec)) return false
126
+ const imported = spec.imported
127
+ const name = t.isIdentifier(imported) ? imported.name : imported.value
128
+ return name === importedName && spec.local.name === localName
129
+ })
130
+ }
131
+
132
+ function ensureGeaEmbeddedRuntimeImport(ast, specs) {
133
+ let target = ast.program.body.find(
134
+ (node) => t.isImportDeclaration(node) && node.source.value === 'gea-embedded',
135
+ )
136
+ if (!target) {
137
+ target = t.importDeclaration([], t.stringLiteral('gea-embedded'))
138
+ ast.program.body.unshift(target)
139
+ }
140
+ for (const spec of specs) {
141
+ const importedName = runtimeCoreSpecifierName(spec)
142
+ if (!importedName) continue
143
+ if (importHasSpecifier(target, importedName, spec.local.name)) continue
144
+ target.specifiers.push(t.cloneNode(spec, true))
145
+ }
146
+ }
147
+
148
+ function normalizeRuntimeCoreImports(ast) {
149
+ let changed = false
150
+ const nextBody = []
151
+ const movedSpecs = []
152
+
153
+ for (const node of ast.program.body) {
154
+ if (!t.isImportDeclaration(node) || node.source.value !== '@geastack/core') {
155
+ nextBody.push(node)
156
+ continue
157
+ }
158
+
159
+ const keep = []
160
+ for (const spec of node.specifiers) {
161
+ if (runtimeCoreSpecifierName(spec)) {
162
+ movedSpecs.push(spec)
163
+ changed = true
164
+ } else {
165
+ keep.push(spec)
166
+ }
167
+ }
168
+
169
+ if (keep.length > 0) {
170
+ node.specifiers = keep
171
+ nextBody.push(node)
172
+ } else {
173
+ changed = true
174
+ }
175
+ }
176
+
177
+ if (!changed) return false
178
+ ast.program.body = nextBody
179
+ ensureGeaEmbeddedRuntimeImport(ast, movedSpecs)
180
+ return true
181
+ }
182
+
183
+ function transformTopLevelStatement(stmt) {
184
+ if (t.isFunctionDeclaration(stmt) && stmt.id && isFunctionComponent(stmt, stmt.id.name)) {
185
+ return { changed: true, nodes: [componentClassFromFunction(stmt.id.name, stmt)] }
186
+ }
187
+
188
+ if (t.isExportNamedDeclaration(stmt) && t.isFunctionDeclaration(stmt.declaration)) {
189
+ const decl = stmt.declaration
190
+ if (decl.id && isFunctionComponent(decl, decl.id.name)) {
191
+ return {
192
+ changed: true,
193
+ nodes: [t.exportNamedDeclaration(componentClassFromFunction(decl.id.name, decl), [])],
194
+ }
195
+ }
196
+ }
197
+
198
+ if (t.isVariableDeclaration(stmt) && stmt.declarations.length === 1) {
199
+ const classDecl = componentClassFromVariableDeclarator(stmt.declarations[0])
200
+ if (classDecl) return { changed: true, nodes: [classDecl] }
201
+ }
202
+
203
+ if (
204
+ t.isExportNamedDeclaration(stmt) &&
205
+ t.isVariableDeclaration(stmt.declaration) &&
206
+ stmt.declaration.declarations.length === 1
207
+ ) {
208
+ const classDecl = componentClassFromVariableDeclarator(stmt.declaration.declarations[0])
209
+ if (classDecl) return { changed: true, nodes: [t.exportNamedDeclaration(classDecl, [])] }
210
+ }
211
+
212
+ return { changed: false, nodes: [stmt] }
213
+ }
214
+
215
+ export function transformGeaEmbeddedCompatSource(code, filename) {
216
+ if ((!code.includes('<') || !code.includes('>')) && !code.includes('@geastack/core')) return code
217
+
218
+ const ast = parser.parse(code, {
219
+ sourceType: 'module',
220
+ plugins: ['jsx', 'typescript', 'classProperties', 'classPrivateProperties'],
221
+ sourceFilename: filename,
222
+ })
223
+
224
+ let changed = false
225
+ let transformedComponents = false
226
+ const nextBody = []
227
+ for (const stmt of ast.program.body) {
228
+ const transformed = transformTopLevelStatement(stmt)
229
+ if (transformed.changed) {
230
+ changed = true
231
+ transformedComponents = true
232
+ }
233
+ nextBody.push(...transformed.nodes)
234
+ }
235
+ ast.program.body = nextBody
236
+ changed = normalizeRuntimeCoreImports(ast) || changed
237
+ if (!changed) return code
238
+
239
+ if (transformedComponents) {
240
+ // `GeaElement` is needed only if one of the classes we just made states
241
+ // props -- that is the only place the generated code spells it.
242
+ const statesProps = ast.program.body.some((node) => {
243
+ const declaration = t.isExportNamedDeclaration(node) ? node.declaration : node
244
+ return t.isClassDeclaration(declaration) && declaration.superTypeParameters != null
245
+ })
246
+ ensureComponentImport(ast, statesProps)
247
+ }
248
+
249
+ return generate(ast, { retainLines: true }, code).code
250
+ }
251
+
252
+ // ─── JSON-import constant folding ────────────────────────────────────────────
253
+ //
254
+ // `import data from './x.json'` followed by `data.notes` / `data.selectedId`
255
+ // reads. The geatsc C++ pipeline has no native lowering for a JSON module's
256
+ // default-export object: vite inlines it as a module-level object literal,
257
+ // which geatsc's global collector defers and BOXES into a `gea_cpp_value`
258
+ // global — every read goes through dynamic `record_get_literal`, and a store
259
+ // field seeded from it loses its array-of-objects shape (the vite-plugin-gea
260
+ // IR extractor sees a member expression, not a literal), so keyed lists
261
+ // silently render nothing.
262
+ //
263
+ // JSON modules are static data, so fold them at the source level instead:
264
+ // every member-access read of the imported binding is replaced with the
265
+ // actual JSON value as an inline literal, and the import is dropped once all
266
+ // references fold. Downstream (IR shape extraction, typed store-array
267
+ // storage, geatsc record lowering) then sees exactly what it would have seen
268
+ // had the data been written inline — the fully native path.
269
+ //
270
+ // Folding duplicates the value per reference site, which would split JS
271
+ // object identity if the same subtree were read through two aliases and
272
+ // mutated through one. To stay semantics-preserving we fold only when it is
273
+ // provably safe and otherwise leave the file untouched (boxed but correct):
274
+ // - every reference to the binding must be a member-access read chain
275
+ // (no bare uses, calls, exports, assignments, ++/--, delete, spread);
276
+ // - per top-level JSON key, either all folded reads are scalars
277
+ // (copies carry no identity) or there is exactly one read.
278
+
279
+ function jsonValueToAst(value) {
280
+ if (value === null) return t.nullLiteral()
281
+ switch (typeof value) {
282
+ case 'string':
283
+ return t.stringLiteral(value)
284
+ case 'boolean':
285
+ return t.booleanLiteral(value)
286
+ case 'number':
287
+ if (!Number.isFinite(value)) return null
288
+ return value < 0 ? t.unaryExpression('-', t.numericLiteral(-value)) : t.numericLiteral(value)
289
+ case 'object': {
290
+ if (Array.isArray(value)) {
291
+ const elements = value.map(jsonValueToAst)
292
+ if (elements.some((element) => element === null)) return null
293
+ return t.arrayExpression(elements)
294
+ }
295
+ const properties = []
296
+ for (const [key, child] of Object.entries(value)) {
297
+ const childAst = jsonValueToAst(child)
298
+ if (childAst === null) return null
299
+ const keyNode = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? t.identifier(key) : t.stringLiteral(key)
300
+ properties.push(t.objectProperty(keyNode, childAst))
301
+ }
302
+ return t.objectExpression(properties)
303
+ }
304
+ default:
305
+ return null
306
+ }
307
+ }
308
+
309
+ // Static member-access key for `obj.key` / `obj["key"]` / `obj[0]`, else null.
310
+ function memberAccessKey(member) {
311
+ if (!member.computed && t.isIdentifier(member.property)) return member.property.name
312
+ if (member.computed && t.isStringLiteral(member.property)) return member.property.value
313
+ if (member.computed && t.isNumericLiteral(member.property)) return member.property.value
314
+ return null
315
+ }
316
+
317
+ // Walk up the longest static member-access chain rooted at `refPath` and
318
+ // return { topPath, keys } — the outermost member-expression path plus the
319
+ // JSON key path it selects. Returns null when the chain breaks (computed
320
+ // non-literal key) or the chain is used as a write target.
321
+ function memberChainFromReference(refPath) {
322
+ let top = refPath
323
+ const keys = []
324
+ while (t.isMemberExpression(top.parent) && top.parent.object === top.node) {
325
+ const key = memberAccessKey(top.parent)
326
+ if (key === null) return null
327
+ keys.push(key)
328
+ top = top.parentPath
329
+ }
330
+ if (keys.length === 0) return null
331
+ const parent = top.parent
332
+ if (t.isAssignmentExpression(parent) && parent.left === top.node) return null
333
+ if (t.isUpdateExpression(parent)) return null
334
+ if (t.isUnaryExpression(parent) && parent.operator === 'delete') return null
335
+ return { topPath: top, keys }
336
+ }
337
+
338
+ function jsonValueAtPath(root, keys) {
339
+ let current = root
340
+ for (const key of keys) {
341
+ if (current === null || typeof current !== 'object') return undefined
342
+ current = current[key]
343
+ }
344
+ return current
345
+ }
346
+
347
+ const isScalarJsonValue = (value) => value === null || typeof value !== 'object'
348
+
349
+ export function inlineJsonImports(code, filename) {
350
+ if (!code.includes('.json')) return code
351
+
352
+ let ast
353
+ try {
354
+ ast = parser.parse(code, {
355
+ sourceType: 'module',
356
+ plugins: ['jsx', 'typescript', 'classProperties', 'classPrivateProperties'],
357
+ sourceFilename: filename,
358
+ })
359
+ } catch {
360
+ return code
361
+ }
362
+
363
+ let changed = false
364
+ traverse(ast, {
365
+ ImportDeclaration(importPath) {
366
+ const source = importPath.node.source.value
367
+ if (!source.endsWith('.json') || !source.startsWith('.')) return
368
+ // Only the default-import form (TS `resolveJsonModule`'s canonical
369
+ // shape). Named/namespace specifiers on a JSON import are left alone.
370
+ if (importPath.node.specifiers.length !== 1 || !t.isImportDefaultSpecifier(importPath.node.specifiers[0])) return
371
+ const localName = importPath.node.specifiers[0].local.name
372
+ const jsonFile = path.resolve(path.dirname(filename), source)
373
+ let data
374
+ try {
375
+ data = JSON.parse(fs.readFileSync(jsonFile, 'utf8'))
376
+ } catch {
377
+ return
378
+ }
379
+ const binding = importPath.scope.getBinding(localName)
380
+ if (!binding || binding.constantViolations.length > 0) return
381
+
382
+ const folds = []
383
+ const readsByTopLevelKey = new Map()
384
+ for (const refPath of binding.referencePaths) {
385
+ const chain = memberChainFromReference(refPath)
386
+ if (!chain) {
387
+ warnSkippedJsonFold(filename, source, localName, 'it is used outside a static property read')
388
+ return
389
+ }
390
+ const value = jsonValueAtPath(data, chain.keys)
391
+ if (value === undefined) {
392
+ warnSkippedJsonFold(filename, source, localName, `'${chain.keys.join('.')}' does not exist in the JSON`)
393
+ return
394
+ }
395
+ const literal = jsonValueToAst(value)
396
+ if (literal === null) {
397
+ warnSkippedJsonFold(filename, source, localName, `'${chain.keys.join('.')}' has a non-JSON value`)
398
+ return
399
+ }
400
+ const group = readsByTopLevelKey.get(chain.keys[0]) ?? []
401
+ group.push(value)
402
+ readsByTopLevelKey.set(chain.keys[0], group)
403
+ folds.push({ topPath: chain.topPath, literal })
404
+ }
405
+ for (const [key, reads] of readsByTopLevelKey) {
406
+ if (reads.length > 1 && reads.some((value) => !isScalarJsonValue(value))) {
407
+ warnSkippedJsonFold(filename, source, localName, `'${key}' is read more than once and holds an object/array (folding would split its identity)`)
408
+ return
409
+ }
410
+ }
411
+
412
+ for (const fold of folds) fold.topPath.replaceWith(fold.literal)
413
+ importPath.remove()
414
+ changed = true
415
+ },
416
+ })
417
+ if (!changed) return code
418
+
419
+ return generate(ast, { retainLines: true }, code).code
420
+ }
421
+
422
+ function warnSkippedJsonFold(filename, source, localName, reason) {
423
+ process.stderr.write(
424
+ `${filename}: warning: JSON import '${localName}' from '${source}' was not inlined because ${reason}. ` +
425
+ `On the embedded target the JSON object stays a dynamic boxed value (slow reads, and store list fields seeded from it do not render).\n`,
426
+ )
427
+ }