@ecrindigital/facetpack 0.1.10 → 0.2.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.
@@ -34,15 +34,268 @@ __export(exports_serializer, {
34
34
  });
35
35
  module.exports = __toCommonJS(exports_serializer);
36
36
  var import_facetpack_native = require("@ecrindigital/facetpack-native");
37
+
38
+ // src/stats.ts
39
+ var import_fs = require("fs");
40
+ var import_os = require("os");
41
+ var import_path = require("path");
42
+ var ANSI = {
43
+ green: "\x1B[32m",
44
+ yellow: "\x1B[33m",
45
+ cyan: "\x1B[36m",
46
+ white: "\x1B[37m",
47
+ gray: "\x1B[90m",
48
+ bold: "\x1B[1m",
49
+ dim: "\x1B[2m",
50
+ reset: "\x1B[0m"
51
+ };
52
+ var STATS_DIR = import_path.join(import_os.tmpdir(), "facetpack-stats");
53
+ var STATS_FILE_PREFIX = "stats-";
54
+ var LOCK_FILE = import_path.join(STATS_DIR, ".print-lock");
55
+ var PRINT_DELAY = 1000;
56
+ function getStatsFilePath() {
57
+ const workerId = process.env.METRO_WORKER_ID || process.pid.toString();
58
+ return import_path.join(STATS_DIR, `${STATS_FILE_PREFIX}${workerId}.json`);
59
+ }
60
+ function ensureStatsDir() {
61
+ if (!import_fs.existsSync(STATS_DIR)) {
62
+ import_fs.mkdirSync(STATS_DIR, { recursive: true });
63
+ }
64
+ }
65
+ function acquirePrintLock() {
66
+ try {
67
+ ensureStatsDir();
68
+ if (import_fs.existsSync(LOCK_FILE)) {
69
+ const lockTime = parseInt(import_fs.readFileSync(LOCK_FILE, "utf-8"), 10);
70
+ if (Date.now() - lockTime < 5000) {
71
+ return false;
72
+ }
73
+ }
74
+ import_fs.writeFileSync(LOCK_FILE, Date.now().toString());
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+ function releasePrintLock() {
81
+ try {
82
+ if (import_fs.existsSync(LOCK_FILE)) {
83
+ import_fs.unlinkSync(LOCK_FILE);
84
+ }
85
+ } catch {}
86
+ }
87
+
88
+ class GlobalStats {
89
+ stats = this.createEmptyStats();
90
+ exitHandlerRegistered = false;
91
+ hasPrinted = false;
92
+ printTimer = null;
93
+ createEmptyStats() {
94
+ return {
95
+ transformer: { oxc: 0, babel: 0 },
96
+ resolver: { facetpack: 0, metro: 0 },
97
+ minifier: { files: 0, originalSize: 0, minifiedSize: 0 },
98
+ treeShaking: { modulesAnalyzed: 0, modulesRemoved: 0, exportsRemoved: 0 },
99
+ startTime: Date.now()
100
+ };
101
+ }
102
+ recordTransform(engine) {
103
+ this.stats.transformer[engine]++;
104
+ this.persistStats();
105
+ this.schedulePrint();
106
+ }
107
+ adjustTransformFallback() {
108
+ this.stats.transformer.oxc--;
109
+ this.stats.transformer.babel++;
110
+ this.persistStats();
111
+ }
112
+ recordResolve(engine) {
113
+ this.stats.resolver[engine]++;
114
+ }
115
+ flush() {
116
+ this.persistStats();
117
+ }
118
+ schedulePrint() {
119
+ if (this.printTimer) {
120
+ clearTimeout(this.printTimer);
121
+ }
122
+ this.printTimer = setTimeout(() => {
123
+ if (acquirePrintLock()) {
124
+ this.print();
125
+ releasePrintLock();
126
+ }
127
+ }, PRINT_DELAY);
128
+ }
129
+ recordMinify(originalSize, minifiedSize) {
130
+ this.stats.minifier.files++;
131
+ this.stats.minifier.originalSize += originalSize;
132
+ this.stats.minifier.minifiedSize += minifiedSize;
133
+ }
134
+ recordTreeShaking(analyzed, removed, exports2) {
135
+ this.stats.treeShaking.modulesAnalyzed += analyzed;
136
+ this.stats.treeShaking.modulesRemoved += removed;
137
+ this.stats.treeShaking.exportsRemoved += exports2;
138
+ }
139
+ get() {
140
+ return JSON.parse(JSON.stringify(this.stats));
141
+ }
142
+ reset() {
143
+ this.stats = this.createEmptyStats();
144
+ this.hasPrinted = false;
145
+ this.cleanupStatsFiles();
146
+ }
147
+ persistStats() {
148
+ try {
149
+ ensureStatsDir();
150
+ import_fs.writeFileSync(getStatsFilePath(), JSON.stringify(this.stats));
151
+ } catch {}
152
+ }
153
+ cleanupStatsFiles() {
154
+ try {
155
+ if (import_fs.existsSync(STATS_DIR)) {
156
+ const { readdirSync } = require("fs");
157
+ const files = readdirSync(STATS_DIR);
158
+ for (const file of files) {
159
+ if (file.startsWith(STATS_FILE_PREFIX)) {
160
+ try {
161
+ import_fs.unlinkSync(import_path.join(STATS_DIR, file));
162
+ } catch {}
163
+ }
164
+ }
165
+ }
166
+ } catch {}
167
+ }
168
+ aggregateWorkerStats() {
169
+ const aggregated = this.createEmptyStats();
170
+ aggregated.startTime = this.stats.startTime;
171
+ try {
172
+ if (import_fs.existsSync(STATS_DIR)) {
173
+ const { readdirSync } = require("fs");
174
+ const files = readdirSync(STATS_DIR);
175
+ for (const file of files) {
176
+ if (file.startsWith(STATS_FILE_PREFIX)) {
177
+ try {
178
+ const content = import_fs.readFileSync(import_path.join(STATS_DIR, file), "utf-8");
179
+ const workerStats = JSON.parse(content);
180
+ aggregated.transformer.oxc += workerStats.transformer.oxc;
181
+ aggregated.transformer.babel += workerStats.transformer.babel;
182
+ aggregated.resolver.facetpack += workerStats.resolver.facetpack;
183
+ aggregated.resolver.metro += workerStats.resolver.metro;
184
+ if (workerStats.startTime < aggregated.startTime) {
185
+ aggregated.startTime = workerStats.startTime;
186
+ }
187
+ } catch {}
188
+ }
189
+ }
190
+ }
191
+ } catch {}
192
+ aggregated.minifier = this.stats.minifier;
193
+ aggregated.treeShaking = this.stats.treeShaking;
194
+ return aggregated;
195
+ }
196
+ formatPercent(value, total) {
197
+ if (total === 0)
198
+ return "0.0";
199
+ return (value / total * 100).toFixed(1);
200
+ }
201
+ formatSize(bytes) {
202
+ if (bytes < 1024)
203
+ return `${bytes} B`;
204
+ if (bytes < 1024 * 1024)
205
+ return `${(bytes / 1024).toFixed(1)} KB`;
206
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
207
+ }
208
+ formatDuration(ms) {
209
+ if (ms < 1000)
210
+ return `${ms}ms`;
211
+ return `${(ms / 1000).toFixed(2)}s`;
212
+ }
213
+ print() {
214
+ if (!process.env.FACETPACK_DEBUG || this.hasPrinted)
215
+ return;
216
+ const aggregated = this.aggregateWorkerStats();
217
+ const { transformer, resolver, minifier, treeShaking, startTime } = aggregated;
218
+ const duration = Date.now() - startTime;
219
+ const transformTotal = transformer.oxc + transformer.babel;
220
+ const resolveTotal = resolver.facetpack + resolver.metro;
221
+ if (transformTotal === 0 && resolveTotal === 0 && minifier.files === 0)
222
+ return;
223
+ this.hasPrinted = true;
224
+ const { cyan, green, yellow, white, gray, bold, dim, reset } = ANSI;
225
+ console.log(`
226
+ `);
227
+ console.log(`${bold}${cyan}╔════════════════════════════════════════════════════════════════════╗${reset}`);
228
+ console.log(`${bold}${cyan}║${reset} ${bold}FACETPACK BUNDLE STATS${reset} ${cyan}║${reset}`);
229
+ console.log(`${bold}${cyan}╠════════════════════════════════════════════════════════════════════╣${reset}`);
230
+ if (transformTotal > 0) {
231
+ const oxcPct = this.formatPercent(transformer.oxc, transformTotal);
232
+ const babelPct = this.formatPercent(transformer.babel, transformTotal);
233
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
234
+ console.log(`${cyan}║${reset} ${bold}TRANSFORMER${reset} ${cyan}║${reset}`);
235
+ console.log(`${cyan}║${reset} ${green}●${reset} OXC (native) ${bold}${green}${transformer.oxc.toString().padStart(6)}${reset} files ${green}${oxcPct.padStart(6)}%${reset} ${cyan}║${reset}`);
236
+ console.log(`${cyan}║${reset} ${yellow}●${reset} Babel ${bold}${yellow}${transformer.babel.toString().padStart(6)}${reset} files ${yellow}${babelPct.padStart(6)}%${reset} ${cyan}║${reset}`);
237
+ console.log(`${cyan}║${reset} ${dim}${white} Total ${transformTotal.toString().padStart(6)} files${reset} ${cyan}║${reset}`);
238
+ }
239
+ if (resolveTotal > 0) {
240
+ const fpPct = this.formatPercent(resolver.facetpack, resolveTotal);
241
+ const metroPct = this.formatPercent(resolver.metro, resolveTotal);
242
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
243
+ console.log(`${cyan}║${reset} ${bold}RESOLVER${reset} ${cyan}║${reset}`);
244
+ console.log(`${cyan}║${reset} ${green}●${reset} Facetpack ${bold}${green}${resolver.facetpack.toString().padStart(6)}${reset} hits ${green}${fpPct.padStart(6)}%${reset} ${cyan}║${reset}`);
245
+ console.log(`${cyan}║${reset} ${yellow}●${reset} Metro ${bold}${yellow}${resolver.metro.toString().padStart(6)}${reset} hits ${yellow}${metroPct.padStart(6)}%${reset} ${cyan}║${reset}`);
246
+ console.log(`${cyan}║${reset} ${dim}${white} Total ${resolveTotal.toString().padStart(6)} resolutions${reset} ${cyan}║${reset}`);
247
+ }
248
+ if (minifier.files > 0) {
249
+ const savings = minifier.originalSize - minifier.minifiedSize;
250
+ const savingsPct = this.formatPercent(savings, minifier.originalSize);
251
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
252
+ console.log(`${cyan}║${reset} ${bold}MINIFIER${reset} ${cyan}║${reset}`);
253
+ console.log(`${cyan}║${reset} ${green}●${reset} Files minified ${bold}${green}${minifier.files.toString().padStart(6)}${reset} ${cyan}║${reset}`);
254
+ console.log(`${cyan}║${reset} ${gray}●${reset} Original size ${this.formatSize(minifier.originalSize).padStart(12)} ${cyan}║${reset}`);
255
+ console.log(`${cyan}║${reset} ${green}●${reset} Minified size ${this.formatSize(minifier.minifiedSize).padStart(12)} ${green}-${savingsPct}%${reset} ${cyan}║${reset}`);
256
+ }
257
+ if (treeShaking.modulesAnalyzed > 0) {
258
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
259
+ console.log(`${cyan}║${reset} ${bold}TREE SHAKING${reset} ${cyan}║${reset}`);
260
+ console.log(`${cyan}║${reset} ${gray}●${reset} Modules analyzed ${bold}${treeShaking.modulesAnalyzed.toString().padStart(5)}${reset} ${cyan}║${reset}`);
261
+ console.log(`${cyan}║${reset} ${green}●${reset} Modules removed ${bold}${green}${treeShaking.modulesRemoved.toString().padStart(5)}${reset} ${cyan}║${reset}`);
262
+ console.log(`${cyan}║${reset} ${green}●${reset} Exports removed ${bold}${green}${treeShaking.exportsRemoved.toString().padStart(5)}${reset} ${cyan}║${reset}`);
263
+ }
264
+ console.log(`${cyan}║${reset} ${cyan}║${reset}`);
265
+ console.log(`${cyan}║${reset} ${dim}Duration: ${this.formatDuration(duration)}${reset} ${cyan}║${reset}`);
266
+ console.log(`${bold}${cyan}╚════════════════════════════════════════════════════════════════════╝${reset}`);
267
+ console.log(`
268
+ `);
269
+ this.cleanupStatsFiles();
270
+ }
271
+ registerExitHandler() {
272
+ if (this.exitHandlerRegistered)
273
+ return;
274
+ this.exitHandlerRegistered = true;
275
+ process.on("SIGINT", () => {
276
+ this.print();
277
+ process.exit(0);
278
+ });
279
+ process.on("beforeExit", () => this.print());
280
+ }
281
+ }
282
+ var globalStats = new GlobalStats;
283
+
284
+ // src/serializer.ts
37
285
  function createFacetpackSerializer(existingSerializer, config = {}) {
38
286
  return async (entryPoint, preModules, graph, options) => {
39
287
  if (options.dev || config.treeShake === false) {
288
+ let result2;
40
289
  if (existingSerializer) {
41
- return existingSerializer(entryPoint, preModules, graph, options);
290
+ result2 = await existingSerializer(entryPoint, preModules, graph, options);
291
+ } else {
292
+ result2 = defaultSerialize(entryPoint, preModules, graph, options);
42
293
  }
43
- return defaultSerialize(entryPoint, preModules, graph, options);
294
+ globalStats.print();
295
+ return result2;
44
296
  }
45
297
  const analyses = new Map;
298
+ let modulesAnalyzed = 0;
46
299
  for (const [path, module2] of graph.dependencies) {
47
300
  if (path.includes("node_modules")) {
48
301
  continue;
@@ -51,6 +304,7 @@ function createFacetpackSerializer(existingSerializer, config = {}) {
51
304
  const code = module2.output[0]?.data?.code ?? "";
52
305
  const analysis = import_facetpack_native.analyzeSync(path, code);
53
306
  analyses.set(path, analysis);
307
+ modulesAnalyzed++;
54
308
  } catch {
55
309
  analyses.set(path, {
56
310
  exports: [],
@@ -61,7 +315,8 @@ function createFacetpackSerializer(existingSerializer, config = {}) {
61
315
  }
62
316
  const usedExports = computeUsedExports(entryPoint, analyses, graph);
63
317
  const shakenModules = new Map;
64
- let totalRemoved = 0;
318
+ let modulesRemoved = 0;
319
+ let exportsRemoved = 0;
65
320
  for (const [path, module2] of graph.dependencies) {
66
321
  if (path.includes("node_modules")) {
67
322
  const code = module2.output[0]?.data?.code ?? "";
@@ -71,28 +326,30 @@ function createFacetpackSerializer(existingSerializer, config = {}) {
71
326
  const used = usedExports.get(path);
72
327
  const analysis = analyses.get(path);
73
328
  if ((!used || used.size === 0) && analysis && !analysis.hasSideEffects) {
74
- totalRemoved++;
329
+ modulesRemoved++;
75
330
  continue;
76
331
  }
77
332
  try {
78
333
  const code = module2.output[0]?.data?.code ?? "";
79
334
  const usedArray = used ? Array.from(used) : ["*"];
80
- const result = import_facetpack_native.shakeSync(path, code, usedArray);
81
- shakenModules.set(path, { code: result.code, map: result.map ?? undefined });
82
- totalRemoved += result.removedExports.length;
335
+ const result2 = import_facetpack_native.shakeSync(path, code, usedArray);
336
+ shakenModules.set(path, { code: result2.code, map: result2.map ?? undefined });
337
+ exportsRemoved += result2.removedExports.length;
83
338
  } catch {
84
339
  const code = module2.output[0]?.data?.code ?? "";
85
340
  shakenModules.set(path, { code });
86
341
  }
87
342
  }
88
- if (totalRemoved > 0) {
89
- console.log(`[facetpack] Tree-shaking removed ${totalRemoved} unused exports`);
90
- }
343
+ globalStats.recordTreeShaking(modulesAnalyzed, modulesRemoved, exportsRemoved);
344
+ let result;
91
345
  if (existingSerializer) {
92
346
  const shakenGraph = createShakenGraph(graph, shakenModules);
93
- return existingSerializer(entryPoint, preModules, shakenGraph, options);
347
+ result = await existingSerializer(entryPoint, preModules, shakenGraph, options);
348
+ } else {
349
+ result = defaultSerialize(entryPoint, preModules, graph, options, shakenModules);
94
350
  }
95
- return defaultSerialize(entryPoint, preModules, graph, options, shakenModules);
351
+ globalStats.print();
352
+ return result;
96
353
  };
97
354
  }
98
355
  function computeUsedExports(entryPoint, analyses, graph) {
@@ -38,22 +38,6 @@ export type CustomSerializer = (entryPoint: string, preModules: SerializerModule
38
38
  export interface FacetpackSerializerConfig {
39
39
  treeShake?: boolean;
40
40
  }
41
- /**
42
- * Creates a Facetpack serializer with tree-shaking support.
43
- * This serializer can be composed with existing Metro serializers.
44
- *
45
- * @example
46
- * ```js
47
- * // metro.config.js
48
- * const { createFacetpackSerializer } = require('@ecrindigital/facetpack')
49
- *
50
- * module.exports = {
51
- * serializer: {
52
- * customSerializer: createFacetpackSerializer(null, { treeShake: true }),
53
- * },
54
- * }
55
- * ```
56
- */
57
41
  export declare function createFacetpackSerializer(existingSerializer?: CustomSerializer | null, config?: FacetpackSerializerConfig): CustomSerializer;
58
42
  export default createFacetpackSerializer;
59
43
  //# sourceMappingURL=serializer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,GAAG,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAA;IACvD,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;IAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,OAAO,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAA;IAC3D,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACzC,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;IACpD,qBAAqB,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAA;CAC9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,gBAAgB,EAAE,CAAA;IAC9B,KAAK,EAAE,eAAe,CAAA;IACtB,OAAO,EAAE,iBAAiB,CAAA;CAC3B;AAED,MAAM,MAAM,gBAAgB,GAAG,CAC7B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,gBAAgB,EAAE,EAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,iBAAiB,KACvB,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE7F,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CACvC,kBAAkB,CAAC,EAAE,gBAAgB,GAAG,IAAI,EAC5C,MAAM,GAAE,yBAA8B,GACrC,gBAAgB,CA4ElB;AAkID,eAAe,yBAAyB,CAAA"}
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,GAAG,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAA;IACvD,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;IAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,OAAO,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAA;IAC3D,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACzC,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;IACpD,qBAAqB,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,OAAO,CAAA;CAC9D;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,gBAAgB,EAAE,CAAA;IAC9B,KAAK,EAAE,eAAe,CAAA;IACtB,OAAO,EAAE,iBAAiB,CAAA;CAC3B;AAED,MAAM,MAAM,gBAAgB,GAAG,CAC7B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,gBAAgB,EAAE,EAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,iBAAiB,KACvB,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE7F,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,wBAAgB,yBAAyB,CACvC,kBAAkB,CAAC,EAAE,gBAAgB,GAAG,IAAI,EAC5C,MAAM,GAAE,yBAA8B,GACrC,gBAAgB,CAsFlB;AAkID,eAAe,yBAAyB,CAAA"}