@gjsify/node-gi 0.13.0 → 0.20.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/globals.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // @gjsify/node-gi/globals — types for the GJS ambient-globals shim.
3
3
 
4
+ import type { SystemModule } from './system.js';
5
+ import type { GettextModule } from './gettext.js';
6
+
4
7
  /** The legacy GJS `imports` object (a minimal Node-backed subset). */
5
8
  export interface GjsImports {
6
9
  /** `imports.gi.<Ns>` resolves a namespace; `imports.gi.versions.<Ns>` pins a version. */
@@ -8,39 +11,10 @@ export interface GjsImports {
8
11
  versions: Record<string, string | undefined>;
9
12
  [namespace: string]: unknown;
10
13
  };
11
- /** `imports.system` — process identity + lifecycle (Node-backed subset). */
12
- system: {
13
- exit(code?: number): void;
14
- gc(): void;
15
- readonly programInvocationName: string;
16
- readonly programPath: string | null;
17
- version: number;
18
- addressOf(): string;
19
- refcount(): number;
20
- breakpoint(): void;
21
- dumpHeap(): void;
22
- dumpMemoryInfo(): void;
23
- };
24
- /** `imports.gettext` — a no-translation passthrough. */
25
- gettext: {
26
- gettext(s: string): string;
27
- dgettext(domain: string, s: string): string;
28
- dcgettext(domain: string, s: string): string;
29
- ngettext(s: string, p: string, n: number): string;
30
- dngettext(domain: string, s: string, p: string, n: number): string;
31
- pgettext(ctx: string, s: string): string;
32
- dpgettext(domain: string, ctx: string, s: string): string;
33
- domain(domain: string): {
34
- gettext(s: string): string;
35
- ngettext(s: string, p: string, n: number): string;
36
- pgettext(ctx: string, s: string): string;
37
- };
38
- setlocale(): null;
39
- bindtextdomain(): null;
40
- textdomain(): null;
41
- bindtextdomainCodeset(): null;
42
- LocaleCategory: Record<string, number>;
43
- };
14
+ /** `imports.system` — the `@gjsify/node-gi/system` module (process identity + lifecycle). */
15
+ system: SystemModule;
16
+ /** `imports.gettext` — the `@gjsify/node-gi/gettext` module (no-translation passthrough). */
17
+ gettext: GettextModule;
44
18
  versions: Record<string, unknown>;
45
19
  }
46
20
 
package/globals.js CHANGED
@@ -12,6 +12,17 @@
12
12
  // Reference: GJS's global definitions (gjs/modules/{print,system,gettext}). The
13
13
  // legacy `imports.*` namespace mirrors gjs/modules/esm/gi.js's require shape.
14
14
  import { requireGi } from './gi.js';
15
+ // `imports.system` / `imports.gettext` / `imports.cairo` reuse the standalone
16
+ // module implementations — ONE source of truth, also reachable directly as
17
+ // `@gjsify/node-gi/{system,gettext,cairo}` (and the bare `system` / `gettext` /
18
+ // `cairo` specifiers on the `--app node` build).
19
+ import system from './system.js';
20
+ import gettext from './gettext.js';
21
+ import cairo from './cairo.js';
22
+ // Legacy `imports.signals` (the pure-JS Signals mixin) + `imports.mainloop` (the
23
+ // GLib.MainLoop convenience layer) — the GJS script modules many older sources use.
24
+ import * as signalsModule from './overrides/_signals.js';
25
+ import { createMainloop } from './overrides/mainloop.js';
15
26
 
16
27
  // GJS stringifies each argument with String() and joins with a space (no
17
28
  // util.inspect object formatting) — match that for fidelity.
@@ -80,58 +91,36 @@ function makeImports() {
80
91
  },
81
92
  );
82
93
 
83
- // Minimal `imports.system` the most-used members (exit, gc, the program
84
- // identity). Backed by Node's process where there is an equivalent.
85
- const system = {
86
- exit(code) {
87
- if (typeof process !== 'undefined') process.exit(code ?? 0);
88
- },
89
- gc() {
90
- if (typeof globalThis.gc === 'function') globalThis.gc();
91
- },
92
- get programInvocationName() {
93
- return (typeof process !== 'undefined' && process.argv[1]) || '';
94
- },
95
- get programPath() {
96
- return (typeof process !== 'undefined' && process.argv[1]) || null;
97
- },
98
- version: 0,
99
- addressOf() {
100
- return '0x0';
101
- },
102
- refcount() {
103
- return 0;
104
- },
105
- breakpoint() {},
106
- dumpHeap() {},
107
- dumpMemoryInfo() {},
108
- };
109
-
110
- // Minimal `imports.gettext` — a no-translation passthrough (the strings are
111
- // returned untranslated, which is the correct fallback when no catalog is
112
- // bound). Mirrors the surface GJS apps call at module load.
113
- const identity = (s) => s;
114
- const gettext = {
115
- gettext: identity,
116
- dgettext: (_domain, s) => s,
117
- dcgettext: (_domain, s) => s,
118
- ngettext: (s, p, n) => (n === 1 ? s : p),
119
- dngettext: (_domain, s, p, n) => (n === 1 ? s : p),
120
- pgettext: (_ctx, s) => s,
121
- dpgettext: (_domain, _ctx, s) => s,
122
- domain: (_domain) => ({
123
- gettext: identity,
124
- ngettext: (s, p, n) => (n === 1 ? s : p),
125
- pgettext: (_ctx, s) => s,
126
- }),
127
- setlocale: () => null,
128
- bindtextdomain: () => null,
129
- textdomain: () => null,
130
- bindtextdomainCodeset: () => null,
131
- LocaleCategory: { ALL: 6, COLLATE: 3, CTYPE: 0, MESSAGES: 5, MONETARY: 4, NUMERIC: 1, TIME: 2 },
94
+ // `imports.system` + `imports.gettext` + `imports.cairo` are the standalone
95
+ // module objects (`./system.js` / `./gettext.js` / `./cairo.js`) the single
96
+ // source of truth for those surfaces, shared with the bare `system` / `gettext` /
97
+ // `cairo` specifiers on Node.
98
+ //
99
+ // `imports.signals` is the pure-JS Signals mixin (`addSignalMethods` + the
100
+ // `_connect`/`_disconnect`/`_emit`/… primitives), the SAME module the DBus proxy
101
+ // surface uses. `imports.mainloop` (a lazy getter — it needs GLib, and a source
102
+ // that never touches the main loop should not pull GLib in) is the thin
103
+ // GLib.MainLoop convenience layer.
104
+ const { addSignalMethods, _connect, _connectAfter, _disconnect, _emit, _signalHandlerIsConnected, _disconnectAll } =
105
+ signalsModule;
106
+ const imports = {
107
+ gi,
108
+ system,
109
+ gettext,
110
+ cairo,
111
+ signals: { addSignalMethods, _connect, _connectAfter, _disconnect, _emit, _signalHandlerIsConnected, _disconnectAll },
112
+ versions: Object.create(null),
132
113
  };
133
-
134
- return { gi, system, gettext, versions: Object.create(null) };
114
+ let mainloop;
115
+ Object.defineProperty(imports, 'mainloop', {
116
+ get() {
117
+ if (mainloop === undefined) mainloop = createMainloop(requireGi('GLib', '2.0'));
118
+ return mainloop;
119
+ },
120
+ enumerable: true,
121
+ configurable: true,
122
+ });
123
+ return imports;
135
124
  }
136
125
 
137
126
  // Side effect on import: seed the globals.
@@ -0,0 +1,17 @@
1
+ // SPDX-License-Identifier: MIT
2
+ /** A resolved GTK runtime bundle: native-code dir (dylibs on darwin, DLLs on win32) + typelib dir. */
3
+ export interface GtkRuntimeBundle {
4
+ dir: string;
5
+ libDir: string;
6
+ typelibDir: string;
7
+ }
8
+ /** Resolve the GTK runtime bundle directory for this platform, or null. */
9
+ export function resolveGtkRuntimeBundle(): GtkRuntimeBundle | null;
10
+ /** macOS: re-exec once with DYLD_FALLBACK_LIBRARY_PATH set to the bundle (no-op off darwin). */
11
+ export function maybeReexecForGtkRuntime(): void;
12
+ /** Windows: prepend the bundle's gtk/bin DLL dir to process.env.PATH (no-op off win32). */
13
+ export function maybePrependGtkRuntimeDllPath(): void;
14
+ /** Windows: wire the env for a full-windowing bundle's runtime data — GSettings schemas, gdk-pixbuf loaders, icon themes, fontconfig (no-op off win32 / for a display-free bundle). */
15
+ export function maybeWireGtkWindowingEnv(): void;
16
+ /** Activate the bundled GTK runtime for the native engine, if one is present. */
17
+ export function activateBundledGtkRuntime(native: { prependSearchPath: (p: string) => void }): GtkRuntimeBundle | null;
package/gtk-runtime.js ADDED
@@ -0,0 +1,245 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // @gjsify/node-gi/gtk-runtime — locate + activate a batteries-included, relocated
3
+ // GTK/GObject-Introspection runtime bundle so gi:// namespaces load with NO system
4
+ // or Homebrew GTK (Phase 2 of cross-platform node-gi). Today only darwin-arm64
5
+ // ships such a bundle (@gjsify/gtk-runtime-darwin-arm64); this is a no-op on every
6
+ // other platform, and harmless when no bundle is present (the addon then uses the
7
+ // host's GTK exactly as before).
8
+ import { createRequire } from 'node:module';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { dirname, join } from 'node:path';
11
+ import { existsSync } from 'node:fs';
12
+
13
+ // NB: node:child_process is intentionally NOT imported at module top level. It is
14
+ // pulled in lazily (via `require` below) ONLY on the darwin re-exec path — importing
15
+ // it eagerly put it in every runtime's module graph, and under Deno that tripped a
16
+ // teardown regression on an unrelated conformance file (all its tests passed but the
17
+ // process exited non-zero). This keeps non-darwin loads free of that side effect.
18
+ const require = createRequire(import.meta.url);
19
+ const here = dirname(fileURLToPath(import.meta.url)); // package root
20
+
21
+ // Sentinel: set on the re-exec so a bundle-activated child never re-execs again.
22
+ const REEXEC_SENTINEL = 'GJSIFY_GTK_REEXEC';
23
+
24
+ /**
25
+ * Resolve the GTK runtime bundle directory for this platform, or `null`. A valid
26
+ * bundle dir contains the native-code dir (`lib/` relocated dylibs on darwin,
27
+ * `bin/` DLLs on win32) + `girepository-1.0/` (typelibs).
28
+ * Search order (first hit wins):
29
+ * 1. GJSIFY_GTK_RUNTIME env (an explicit bundle dir),
30
+ * 2. node-gi's own prebuilds/<platform>-<arch>/gtk/ (CI/dev staging path),
31
+ * 3. the sibling monorepo dir ../gtk-runtime-<platform>-<arch>/gtk,
32
+ * 4. the published optional dep @gjsify/gtk-runtime-<platform>-<arch>.
33
+ * @returns {{ dir: string, libDir: string, typelibDir: string } | null}
34
+ */
35
+ export function resolveGtkRuntimeBundle() {
36
+ const tag = `${process.platform}-${process.arch}`;
37
+ const candidates = [];
38
+
39
+ if (process.env.GJSIFY_GTK_RUNTIME) candidates.push(process.env.GJSIFY_GTK_RUNTIME);
40
+ candidates.push(join(here, 'prebuilds', tag, 'gtk'));
41
+ candidates.push(join(here, '..', `gtk-runtime-${tag}`, 'gtk'));
42
+ try {
43
+ // The package's index.js sits at its root; the bundle is ./gtk beside it.
44
+ const pkgIndex = require.resolve(`@gjsify/gtk-runtime-${tag}`);
45
+ candidates.push(join(dirname(pkgIndex), 'gtk'));
46
+ } catch {
47
+ // Not installed — fine.
48
+ }
49
+
50
+ // The loadable native code lives in `lib` (dylibs) on macOS and `bin` (DLLs) on
51
+ // Windows. `libDir` is the dir node-gi adds to the OS library search path
52
+ // (DYLD_FALLBACK on darwin, PATH on win32) — its NAME differs, its ROLE does not.
53
+ const nativeSubdir = process.platform === 'win32' ? 'bin' : 'lib';
54
+ for (const dir of candidates) {
55
+ if (!dir) continue;
56
+ const libDir = join(dir, nativeSubdir);
57
+ const typelibDir = join(dir, 'girepository-1.0');
58
+ if (existsSync(libDir) && existsSync(typelibDir)) return { dir, libDir, typelibDir };
59
+ }
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Make the bundled GTK runtime genuinely env-free by RE-EXECING this process once
65
+ * with `DYLD_FALLBACK_LIBRARY_PATH` (+ `GI_TYPELIB_PATH`) pointed at the bundle.
66
+ *
67
+ * Why a re-exec and not just `process.env` mutation: on macOS, dyld captures
68
+ * `DYLD_FALLBACK_LIBRARY_PATH` at PROCESS LAUNCH, so setting it from JS never
69
+ * affects the running dyld. It is load-bearing beyond the addon's own link
70
+ * closure: GObject-Introspection resolves a type's `get_type()` (e.g. for
71
+ * `registerClass` subclassing) — and the Pango/Gdk/Graphene backers — via
72
+ * `g_module_open(<leaf soname>)`, and dyld will NOT satisfy a bare-leaf dlopen
73
+ * from an `@loader_path`-loaded image. Re-launching with the fallback set (exactly
74
+ * what the Homebrew-based CI leg does) resolves every such leaf lookup from the
75
+ * bundle. Guarded by a sentinel env var so it fires AT MOST ONCE; a no-op unless
76
+ * darwin + a bundle is present + the fallback does not already cover it.
77
+ *
78
+ * MUST be called at module top-level BEFORE the native addon (and thus its GTK
79
+ * dependency closure) is dlopen'd — the re-exec then loads everything correctly.
80
+ * On re-exec this calls `process.exit()` and never returns to the caller.
81
+ * @returns {void}
82
+ */
83
+ export function maybeReexecForGtkRuntime() {
84
+ if (process.platform !== 'darwin') return; // strict no-op on every non-darwin runtime
85
+ // Node only: the argv/execArgv reconstruction below is Node-shaped, and Node is
86
+ // the runtime the conformance proves. Bun/Deno set globals of their own — skip the
87
+ // re-exec there (they still get env-free TYPELIBS via activateBundledGtkRuntime;
88
+ // their DYLD path for non-addon-linked backers is a documented follow-up), so the
89
+ // darwin path can never spawn a malformed re-exec under a non-Node runtime.
90
+ if (typeof globalThis.Bun !== 'undefined' || typeof globalThis.Deno !== 'undefined') return;
91
+ if (process.env[REEXEC_SENTINEL]) return; // already re-exec'd — the child path
92
+ const bundle = resolveGtkRuntimeBundle();
93
+ if (!bundle) return; // strict no-op when no bundle is present
94
+
95
+ const curDyld = process.env.DYLD_FALLBACK_LIBRARY_PATH ?? '';
96
+ if (curDyld.split(':').filter(Boolean).includes(bundle.libDir)) return; // already covered
97
+
98
+ // Lazily load node:child_process ONLY here (darwin + bundle present + not yet
99
+ // covered) — see the top-of-file note; keeps it out of non-darwin module graphs.
100
+ const { spawnSync } = require('node:child_process');
101
+ const curTypelib = process.env.GI_TYPELIB_PATH ?? '';
102
+ const env = {
103
+ ...process.env,
104
+ [REEXEC_SENTINEL]: '1',
105
+ DYLD_FALLBACK_LIBRARY_PATH: curDyld ? `${bundle.libDir}:${curDyld}` : `${bundle.libDir}:/usr/lib`,
106
+ GI_TYPELIB_PATH: curTypelib ? `${bundle.typelibDir}:${curTypelib}` : bundle.typelibDir,
107
+ };
108
+ // Reproduce this exact invocation (node flags + argv) with the augmented env.
109
+ const res = spawnSync(process.execPath, [...process.execArgv, ...process.argv.slice(1)], {
110
+ stdio: 'inherit',
111
+ env,
112
+ });
113
+ if (res.error) throw res.error; // spawn failed outright — surface it, don't silently continue mis-linked
114
+ process.exit(res.status === null ? 1 : res.status);
115
+ }
116
+
117
+ /**
118
+ * Prepend the bundled GTK runtime's DLL dir (`gtk/bin`) to `process.env.PATH` so a
119
+ * later `LoadLibrary` resolves against the bundle with NO gvsbuild/system GTK — both
120
+ * the native addon's OWN static imports (glib/gobject/gio/girepository/cairo/ffi/…)
121
+ * AND every runtime `g_module_open(<soname>)` a typelib does for its backers
122
+ * (Pango/Gdk/Graphene). This is the Windows analog of the darwin re-exec, but
123
+ * SIMPLER: Windows re-reads the DLL search path at EACH `LoadLibrary` (unlike dyld,
124
+ * which captures `DYLD_FALLBACK_LIBRARY_PATH` only at process launch), so an
125
+ * in-process `process.env.PATH` mutation is sufficient — no re-exec, and no native
126
+ * `AddDllDirectory` (which would be chicken-and-egg: the addon must already be loaded
127
+ * to expose it). MUST be called at module top-level BEFORE the addon is `require()`'d
128
+ * (index.js does exactly that, before loadNative()). Strict no-op off win32 / without
129
+ * a bundle / when the dir is already on PATH. Idempotent.
130
+ * @returns {void}
131
+ */
132
+ export function maybePrependGtkRuntimeDllPath() {
133
+ if (process.platform !== 'win32') return; // strict no-op on every non-win32 runtime
134
+ const bundle = resolveGtkRuntimeBundle();
135
+ if (!bundle) return; // strict no-op when no bundle is present
136
+ const binDir = bundle.libDir; // on win32 the native-code dir is gtk/bin (the DLLs)
137
+ const cur = process.env.PATH ?? '';
138
+ if (cur.split(';').filter(Boolean).includes(binDir)) return; // already on PATH
139
+ process.env.PATH = cur ? `${binDir};${cur}` : binDir;
140
+ }
141
+
142
+ /**
143
+ * Wire the env a bundled FULL-WINDOWING GTK runtime needs so a REAL GTK WINDOW
144
+ * finds its runtime DATA on Windows: compiled GSettings schemas
145
+ * (`GSETTINGS_SCHEMA_DIR`), the gdk-pixbuf image loaders (`GDK_PIXBUF_MODULEDIR` +
146
+ * `GDK_PIXBUF_MODULE_FILE`), the icon themes (`XDG_DATA_DIRS` → `<bundle>/share`),
147
+ * and — when bundled — Fontconfig (`FONTCONFIG_PATH`/`FONTCONFIG_FILE`). These live
148
+ * beside the DLLs the display-free path already wires (maybePrependGtkRuntimeDllPath).
149
+ *
150
+ * STRICT no-op off win32, without a bundle, or when the bundle carries NO windowing
151
+ * data — keyed on the `share/glib-2.0/schemas/gschemas.compiled` marker the
152
+ * --windowing build produces, so the DEFAULT display-free bundle is byte-unchanged.
153
+ * Windows re-reads these vars at first use (schema/loader/icon-theme init runs AFTER
154
+ * the addon loads), so an in-process mutation here suffices — no re-exec, the DLL-
155
+ * search analog. Each var is set only when currently unset (a host override wins).
156
+ * Idempotent; MUST run at module top-level with maybePrependGtkRuntimeDllPath, before
157
+ * the app initializes GTK. GLib uses `;` as the win32 search-path separator.
158
+ * @returns {void}
159
+ */
160
+ export function maybeWireGtkWindowingEnv() {
161
+ if (process.platform !== 'win32') return; // strict no-op on every non-win32 runtime
162
+ const bundle = resolveGtkRuntimeBundle();
163
+ if (!bundle) return; // strict no-op when no bundle is present
164
+
165
+ const schemaDir = join(bundle.dir, 'share', 'glib-2.0', 'schemas');
166
+ // gschemas.compiled = the windowing-data marker; absent → display-free bundle.
167
+ if (!existsSync(join(schemaDir, 'gschemas.compiled'))) return;
168
+
169
+ const setIfUnset = (name, value) => {
170
+ if (!process.env[name]) process.env[name] = value;
171
+ };
172
+ setIfUnset('GSETTINGS_SCHEMA_DIR', schemaDir);
173
+
174
+ const loaderCache = join(bundle.dir, 'lib', 'gdk-pixbuf-2.0', '2.10.0', 'loaders.cache');
175
+ if (existsSync(loaderCache)) {
176
+ setIfUnset('GDK_PIXBUF_MODULEDIR', join(bundle.dir, 'lib', 'gdk-pixbuf-2.0', '2.10.0', 'loaders'));
177
+ setIfUnset('GDK_PIXBUF_MODULE_FILE', loaderCache);
178
+ }
179
+
180
+ const shareDir = join(bundle.dir, 'share');
181
+ if (existsSync(join(shareDir, 'icons'))) {
182
+ const cur = process.env.XDG_DATA_DIRS ?? '';
183
+ if (!cur.split(';').filter(Boolean).includes(shareDir)) {
184
+ process.env.XDG_DATA_DIRS = cur ? `${shareDir};${cur}` : shareDir;
185
+ }
186
+ }
187
+
188
+ const fontsConf = join(bundle.dir, 'etc', 'fonts', 'fonts.conf');
189
+ if (existsSync(fontsConf)) {
190
+ setIfUnset('FONTCONFIG_PATH', join(bundle.dir, 'etc', 'fonts'));
191
+ setIfUnset('FONTCONFIG_FILE', fontsConf);
192
+ }
193
+ }
194
+
195
+ let activated = null; // memoize: the activation result (idempotent)
196
+
197
+ /**
198
+ * Activate the bundled GTK runtime for the native engine, if one is present.
199
+ * • typelibs — prepend the bundle's girepository-1.0 dir to the GIRepository
200
+ * search path (env-free, via the native prependSearchPath); this alone lets
201
+ * node-gi FIND the bundled typelibs without GI_TYPELIB_PATH.
202
+ * • native code — darwin: prepend the bundle's lib dir to
203
+ * process.env.DYLD_FALLBACK_LIBRARY_PATH. NB dyld captures that variable at
204
+ * LAUNCH, so setting it here only helps child processes / a re-exec; a bundle
205
+ * whose dylibs a namespace loads by leaf name still needs the value present in
206
+ * the launching environment (see the package README's env-free caveat).
207
+ * Namespaces whose dylib is already in-process via the addon's link closure
208
+ * (GLib/GObject/Gio/cairo) resolve regardless. win32: prepend the bundle's bin
209
+ * dir to process.env.PATH — already done by maybePrependGtkRuntimeDllPath before
210
+ * the addon loaded; re-asserted here for child processes + the runtime
211
+ * g_module_open of typelib backers. Windows re-reads PATH per LoadLibrary, so no
212
+ * launch-time capture applies (see the package README).
213
+ * Scoped to darwin + win32 (the platforms shipping a batteries-included bundle).
214
+ * @param {{ prependSearchPath: (p: string) => void }} native the loaded addon
215
+ * @returns {{ dir: string, libDir: string, typelibDir: string } | null}
216
+ */
217
+ export function activateBundledGtkRuntime(native) {
218
+ if (activated !== null) return activated || null;
219
+ activated = false;
220
+ if (process.platform !== 'darwin' && process.platform !== 'win32') return null;
221
+
222
+ const bundle = resolveGtkRuntimeBundle();
223
+ if (!bundle) return null;
224
+
225
+ try {
226
+ native.prependSearchPath(bundle.typelibDir);
227
+ } catch {
228
+ // A stubbed/old addon without prependSearchPath — non-fatal.
229
+ }
230
+
231
+ if (process.platform === 'win32') {
232
+ const existing = process.env.PATH ?? '';
233
+ if (!existing.split(';').filter(Boolean).includes(bundle.libDir)) {
234
+ process.env.PATH = existing ? `${bundle.libDir};${existing}` : bundle.libDir;
235
+ }
236
+ } else {
237
+ const existing = process.env.DYLD_FALLBACK_LIBRARY_PATH;
238
+ if (!existing || !existing.split(':').includes(bundle.libDir)) {
239
+ process.env.DYLD_FALLBACK_LIBRARY_PATH = existing ? `${bundle.libDir}:${existing}` : `${bundle.libDir}:/usr/lib`;
240
+ }
241
+ }
242
+
243
+ activated = bundle;
244
+ return bundle;
245
+ }