@embeddables/cli 0.6.11 → 0.7.1

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 (40) hide show
  1. package/.prompts/custom/build-funnel.md +1 -1
  2. package/dist/cli.js +7 -10
  3. package/dist/commands/branch.d.ts.map +1 -1
  4. package/dist/commands/branch.js +20 -15
  5. package/dist/commands/build-workbench.d.ts.map +1 -1
  6. package/dist/commands/build-workbench.js +37 -31
  7. package/dist/commands/build.d.ts.map +1 -1
  8. package/dist/commands/build.js +12 -3
  9. package/dist/commands/dev.d.ts.map +1 -1
  10. package/dist/commands/dev.js +34 -21
  11. package/dist/commands/experiments-connect.d.ts.map +1 -1
  12. package/dist/commands/experiments-connect.js +43 -30
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +49 -48
  15. package/dist/commands/login.d.ts.map +1 -1
  16. package/dist/commands/login.js +35 -25
  17. package/dist/commands/logout.d.ts.map +1 -1
  18. package/dist/commands/logout.js +10 -6
  19. package/dist/commands/pull.d.ts.map +1 -1
  20. package/dist/commands/pull.js +49 -36
  21. package/dist/commands/save.d.ts.map +1 -1
  22. package/dist/commands/save.js +79 -53
  23. package/dist/compiler/index.d.ts.map +1 -1
  24. package/dist/compiler/index.js +10 -9
  25. package/dist/compiler/reverse.d.ts.map +1 -1
  26. package/dist/compiler/reverse.js +18 -17
  27. package/dist/logger.d.ts +1 -0
  28. package/dist/logger.d.ts.map +1 -1
  29. package/dist/logger.js +4 -0
  30. package/dist/prompts/branches.d.ts.map +1 -1
  31. package/dist/prompts/branches.js +3 -2
  32. package/dist/prompts/embeddables.d.ts.map +1 -1
  33. package/dist/prompts/embeddables.js +8 -7
  34. package/dist/prompts/experiments.d.ts.map +1 -1
  35. package/dist/prompts/experiments.js +5 -4
  36. package/dist/prompts/projects.d.ts.map +1 -1
  37. package/dist/prompts/projects.js +4 -3
  38. package/dist/proxy/server.d.ts.map +1 -1
  39. package/dist/proxy/server.js +33 -32
  40. package/package.json +2 -1
@@ -8,6 +8,8 @@ import { compileAllPages } from '../compiler/index.js';
8
8
  import { formatError } from '../compiler/errors.js';
9
9
  import { promptForLocalEmbeddable, promptForProject } from '../prompts/index.js';
10
10
  import { WEB_APP_BASE_URL } from '../constants.js';
11
+ import { captureException, createLogger, exit } from '../logger.js';
12
+ import * as stdout from '../stdout.js';
11
13
  import { translateJsonDiffToEditCommands } from '../helpers/json.js';
12
14
  import { generateId, inferEmbeddableFromCwd } from '../helpers/utils.js';
13
15
  /** Error with optional gray detail line (hint/next step) for the user. */
@@ -188,42 +190,48 @@ async function fetchOtherUsersDrafts(supabase, flowId, versionNumber, branchId,
188
190
  }
189
191
  }
190
192
  export async function runSave(opts) {
193
+ const logger = createLogger('runSave');
191
194
  try {
192
195
  await runSaveInner(opts);
193
196
  }
194
197
  catch (error) {
198
+ captureException(error);
195
199
  if (error instanceof SaveError) {
196
- console.error(pc.red(`Save failed: ${error.message}`));
200
+ stdout.error(pc.red(`Save failed: ${error.message}`));
197
201
  if (error.detail) {
198
- console.log(pc.gray(error.detail));
202
+ stdout.print(pc.gray(error.detail));
199
203
  }
200
204
  }
201
205
  else if (error instanceof Error) {
202
- console.error(pc.red(`Save failed: ${error.message}`));
206
+ stdout.error(pc.red(`Save failed: ${error.message}`));
203
207
  }
204
208
  else {
205
- console.error(pc.red('Save failed with an unexpected error.'));
206
- }
207
- process.exit(1);
208
- // Rethrow only when exit was mocked to throw (e.g. in tests expecting rejection)
209
- if (error instanceof Error && error.message === 'exit') {
210
- throw error;
209
+ stdout.error(pc.red('Save failed with an unexpected error.'));
211
210
  }
211
+ logger.error('save failed', {
212
+ message: error instanceof Error ? error.message : 'unexpected error',
213
+ });
214
+ await exit(1);
212
215
  }
213
216
  }
214
217
  async function runSaveInner(opts) {
218
+ const logger = createLogger('runSave');
215
219
  // 1. Check login
216
220
  if (!isLoggedIn()) {
217
- console.error(pc.red('Not logged in.'));
218
- console.log(pc.gray('Run "embeddables login" first.'));
219
- process.exit(1);
221
+ stdout.error(pc.red('Not logged in.'));
222
+ stdout.print(pc.gray('Run "embeddables login" first.'));
223
+ logger.error('not logged in');
224
+ await exit(1);
225
+ return;
220
226
  }
221
227
  // 2. Get access token
222
228
  const accessToken = getAccessToken();
223
229
  if (!accessToken) {
224
- console.error(pc.red('Could not retrieve access token.'));
225
- console.log(pc.gray('Run "embeddables login" to re-authenticate.'));
226
- process.exit(1);
230
+ stdout.error(pc.red('Could not retrieve access token.'));
231
+ stdout.print(pc.gray('Run "embeddables login" to re-authenticate.'));
232
+ logger.error('no access token');
233
+ await exit(1);
234
+ return;
227
235
  }
228
236
  // 3. Get embeddable ID (from option, cwd inference, or interactive prompt)
229
237
  const inferred = inferEmbeddableFromCwd();
@@ -236,20 +244,23 @@ async function runSaveInner(opts) {
236
244
  message: 'Select an embeddable to save:',
237
245
  });
238
246
  if (!selected) {
239
- process.exit(1);
247
+ await exit(1);
248
+ return;
240
249
  }
241
250
  embeddableId = selected;
242
- console.log('');
251
+ stdout.print('');
243
252
  }
244
253
  // Resolve branch: explicit -b flag wins, otherwise use current branch from config (set by `embeddables branch`)
245
254
  const effectiveBranch = opts.branch ?? getBranchFromConfig(embeddableId) ?? undefined;
255
+ logger.info('save started', { embeddableId, branch: effectiveBranch, fromVersion: opts.fromVersion });
246
256
  // 4. Get project ID (from config or interactive prompt)
247
257
  let projectId = getProjectId();
248
258
  if (!projectId) {
249
- console.log(pc.cyan('No project configured. Fetching projects...'));
259
+ stdout.print(pc.cyan('No project configured. Fetching projects...'));
250
260
  const selectedProject = await promptForProject();
251
261
  if (!selectedProject) {
252
- process.exit(1);
262
+ await exit(1);
263
+ return;
253
264
  }
254
265
  projectId = selectedProject.id;
255
266
  writeProjectConfig({
@@ -258,8 +269,8 @@ async function runSaveInner(opts) {
258
269
  project_id: projectId,
259
270
  project_name: selectedProject.title || undefined,
260
271
  });
261
- console.log(pc.green('✓ Saved project to embeddables.json'));
262
- console.log('');
272
+ stdout.print(pc.green('✓ Saved project to embeddables.json'));
273
+ stdout.print('');
263
274
  }
264
275
  // 5. Build (compile TSX → JSON) unless --skip-build is set
265
276
  const generatedDir = path.join('embeddables', embeddableId, '.generated');
@@ -268,7 +279,7 @@ async function runSaveInner(opts) {
268
279
  const pagesGlob = `embeddables/${embeddableId}/pages/**/*.page.tsx`;
269
280
  const stylesDir = path.join('embeddables', embeddableId, 'styles');
270
281
  const configPath = path.join('embeddables', embeddableId, 'config.json');
271
- console.log(pc.cyan('Building embeddable...'));
282
+ stdout.print(pc.cyan('Building embeddable...'));
272
283
  try {
273
284
  await compileAllPages({
274
285
  pagesGlob,
@@ -280,17 +291,22 @@ async function runSaveInner(opts) {
280
291
  });
281
292
  }
282
293
  catch (e) {
283
- console.error(formatError(e));
284
- process.exit(1);
294
+ captureException(e);
295
+ stdout.error(formatError(e));
296
+ logger.error('build failed', { embeddableId });
297
+ await exit(1);
298
+ return;
285
299
  }
286
- console.log(pc.green('✓ Build successful'));
287
- console.log('');
300
+ stdout.print(pc.green('✓ Build successful'));
301
+ stdout.print('');
288
302
  }
289
303
  // 6. Read the compiled JSON
290
304
  if (!fs.existsSync(outPath)) {
291
- console.error(pc.red(`No compiled embeddable found at ${outPath}`));
292
- console.log(pc.gray('Run "embeddables build" or "embeddables pull" first.'));
293
- process.exit(1);
305
+ stdout.error(pc.red(`No compiled embeddable found at ${outPath}`));
306
+ stdout.print(pc.gray('Run "embeddables build" or "embeddables pull" first.'));
307
+ logger.error('compiled json not found', { outPath });
308
+ await exit(1);
309
+ return;
294
310
  }
295
311
  const jsonContent = fs.readFileSync(outPath, 'utf8');
296
312
  let embeddableJson;
@@ -298,32 +314,40 @@ async function runSaveInner(opts) {
298
314
  embeddableJson = JSON.parse(jsonContent);
299
315
  }
300
316
  catch {
301
- console.error(pc.red('Failed to parse embeddable JSON.'));
302
- process.exit(1);
317
+ stdout.error(pc.red('Failed to parse embeddable JSON.'));
318
+ logger.error('failed to parse compiled json');
319
+ await exit(1);
320
+ return;
303
321
  }
304
322
  // Validate that pages array exists and is non-empty
305
323
  if (!embeddableJson.pages ||
306
324
  !Array.isArray(embeddableJson.pages) ||
307
325
  embeddableJson.pages.length === 0) {
308
- console.error(pc.red('Embeddable JSON must contain a non-empty pages array.'));
309
- process.exit(1);
326
+ stdout.error(pc.red('Embeddable JSON must contain a non-empty pages array.'));
327
+ logger.error('empty pages array');
328
+ await exit(1);
329
+ return;
310
330
  }
311
331
  // 7. Determine fromVersionNumber
312
332
  let fromVersionNumber;
313
333
  if (opts.fromVersion) {
314
334
  fromVersionNumber = parseInt(opts.fromVersion, 10);
315
335
  if (isNaN(fromVersionNumber)) {
316
- console.error(pc.red(`Invalid --from-version value: ${opts.fromVersion}`));
317
- process.exit(1);
336
+ stdout.error(pc.red(`Invalid --from-version value: ${opts.fromVersion}`));
337
+ logger.error('invalid from-version', { fromVersion: opts.fromVersion });
338
+ await exit(1);
339
+ return;
318
340
  }
319
341
  }
320
342
  else {
321
343
  // Primary: read _version from config.json; fallback: scan .generated/ for versioned files
322
344
  const detectedVersion = getVersionFromConfig(embeddableId) ?? getLatestVersionFromFiles(generatedDir);
323
345
  if (detectedVersion === null) {
324
- console.error(pc.red('Could not determine the current version number.'));
325
- console.log(pc.gray('Make sure you have pulled the embeddable first (embeddables pull), or specify --from-version <number>.'));
326
- process.exit(1);
346
+ stdout.error(pc.red('Could not determine the current version number.'));
347
+ stdout.print(pc.gray('Make sure you have pulled the embeddable first (embeddables pull), or specify --from-version <number>.'));
348
+ logger.error('could not determine version');
349
+ await exit(1);
350
+ return;
327
351
  }
328
352
  fromVersionNumber = detectedVersion;
329
353
  }
@@ -343,8 +367,8 @@ async function runSaveInner(opts) {
343
367
  const namesText = names.length === 1
344
368
  ? names[0]
345
369
  : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
346
- console.log('');
347
- console.warn(pc.yellow(`⚠ ${namesText} ${names.length === 1 ? 'has' : 'have'} unsaved edits on version ${fromVersionNumber}. Saving may cause conflicts.`));
370
+ stdout.print('');
371
+ stdout.warn(pc.yellow(`⚠ ${namesText} ${names.length === 1 ? 'has' : 'have'} unsaved edits on version ${fromVersionNumber}. Saving may cause conflicts.`));
348
372
  const { proceed } = await prompts({
349
373
  type: 'confirm',
350
374
  name: 'proceed',
@@ -356,15 +380,15 @@ async function runSaveInner(opts) {
356
380
  },
357
381
  });
358
382
  if (!proceed) {
359
- console.log(pc.gray('Save cancelled.'));
360
- process.exit(0);
383
+ stdout.print(pc.gray('Save cancelled.'));
384
+ await exit(0);
361
385
  }
362
- console.log('');
386
+ stdout.print('');
363
387
  }
364
388
  }
365
389
  }
366
390
  // 8. POST to save-version API
367
- console.log(pc.cyan(`Saving embeddable (based on v${fromVersionNumber})...`));
391
+ stdout.print(pc.cyan(`Saving embeddable (based on v${fromVersionNumber})...`));
368
392
  // Resolve current user id (required by API)
369
393
  if (!currentUserId && supabase) {
370
394
  const { data: { user }, } = await supabase.auth.getUser();
@@ -448,8 +472,8 @@ async function runSaveInner(opts) {
448
472
  if (!conflictResult || !isSaveConflictResponse(conflictResult)) {
449
473
  throw new SaveError(`Version conflict but invalid response (HTTP ${response.status}).`, 'Try saving again; if it persists, the server may be misconfigured.');
450
474
  }
451
- console.log('');
452
- console.warn(pc.yellow(`⚠ Version conflict: the server has version ${conflictResult.latestVersionNumber}, but you are saving from version ${conflictResult.yourVersionNumber}.`));
475
+ stdout.print('');
476
+ stdout.warn(pc.yellow(`⚠ Version conflict: the server has version ${conflictResult.latestVersionNumber}, but you are saving from version ${conflictResult.yourVersionNumber}.`));
453
477
  const { forceSave } = await prompts({
454
478
  type: 'confirm',
455
479
  name: 'forceSave',
@@ -461,11 +485,11 @@ async function runSaveInner(opts) {
461
485
  },
462
486
  });
463
487
  if (!forceSave) {
464
- console.log(pc.gray('Save cancelled.'));
465
- process.exit(0);
488
+ stdout.print(pc.gray('Save cancelled.'));
489
+ await exit(0);
466
490
  }
467
491
  // Retry with force flag
468
- console.log(pc.cyan('Retrying save with force...'));
492
+ stdout.print(pc.cyan('Retrying save with force...'));
469
493
  let forceResponse;
470
494
  try {
471
495
  forceResponse = await fetch(url, {
@@ -492,13 +516,14 @@ async function runSaveInner(opts) {
492
516
  throw new SaveError(`Invalid response from server (HTTP ${forceResponse.status}).`, 'The server returned an unexpected format. Try again or contact support if it persists.');
493
517
  }
494
518
  const { newVersionNumber } = forceResult.data;
495
- console.log(pc.green(`✓ Saved as version ${newVersionNumber}`));
519
+ stdout.print(pc.green(`✓ Saved as version ${newVersionNumber}`));
520
+ logger.info('save complete', { embeddableId, newVersionNumber, forced: true });
496
521
  setVersionInConfig(embeddableId, newVersionNumber);
497
522
  const branchSlug = getBranchSlugFromConfig(embeddableId);
498
523
  const versionedPath = path.join(generatedDir, `embeddable-${branchSlug}@${newVersionNumber}.json`);
499
524
  fs.mkdirSync(generatedDir, { recursive: true });
500
525
  fs.writeFileSync(versionedPath, jsonContent, 'utf8');
501
- console.log(pc.cyan(`✓ Saved version file to ${versionedPath}`));
526
+ stdout.print(pc.cyan(`✓ Saved version file to ${versionedPath}`));
502
527
  return;
503
528
  }
504
529
  const result = await safeParseJson(response);
@@ -510,7 +535,8 @@ async function runSaveInner(opts) {
510
535
  throw new SaveError(`Invalid response from server (HTTP ${response.status}).`, 'The server returned an unexpected format. Try again or contact support if it persists.');
511
536
  }
512
537
  const { newVersionNumber } = result.data;
513
- console.log(pc.green(`✓ Saved as version ${newVersionNumber}`));
538
+ stdout.print(pc.green(`✓ Saved as version ${newVersionNumber}`));
539
+ logger.info('save complete', { embeddableId, newVersionNumber });
514
540
  // Update _version in config.json so future saves know the base version
515
541
  setVersionInConfig(embeddableId, newVersionNumber);
516
542
  // Also save the versioned file to .generated/ as a snapshot (embeddable-{branch}@{version}.json)
@@ -518,7 +544,7 @@ async function runSaveInner(opts) {
518
544
  const versionedPath = path.join(generatedDir, `embeddable-${branchSlug}@${newVersionNumber}.json`);
519
545
  fs.mkdirSync(generatedDir, { recursive: true });
520
546
  fs.writeFileSync(versionedPath, jsonContent, 'utf8');
521
- console.log(pc.cyan(`✓ Saved version file to ${versionedPath}`));
547
+ stdout.print(pc.cyan(`✓ Saved version file to ${versionedPath}`));
522
548
  }
523
549
  function setMultipleFlowUpdates({ commands, metadata, }) {
524
550
  const type = 'set_multiple_embeddable_updates';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compiler/index.ts"],"names":[],"mappings":"AAmDA;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CA2DlF;AA6BD,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mHAAmH;IACnH,OAAO,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAA;CAC1B,iBAoeA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compiler/index.ts"],"names":[],"mappings":"AAoDA;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CA2DlF;AA6BD,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mHAAmH;IACnH,OAAO,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAA;CAC1B,iBAoeA"}
@@ -1,6 +1,7 @@
1
1
  import fg from 'fast-glob';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ import * as stdout from '../stdout.js';
4
5
  import { parsePageFromFile } from './parsePage.js';
5
6
  import { CompileError } from './errors.js';
6
7
  import CSSJSON from 'cssjson';
@@ -263,23 +264,23 @@ export async function compileAllPages(opts) {
263
264
  if (totalLintIssues > 0) {
264
265
  // Report all issues
265
266
  if (idFixes.size > 0) {
266
- console.warn(`Found ${idFixes.size} duplicate ID(s). Keys and IDs must be unique across the embeddable.`);
267
+ stdout.warn(`Found ${idFixes.size} duplicate ID(s). Keys and IDs must be unique across the embeddable.`);
267
268
  for (const [fixKey, newId] of idFixes) {
268
269
  const parts = fixKey.split(':');
269
270
  const file = parts[0];
270
271
  const compIdx = parts[1];
271
272
  const btnIdx = parts[2];
272
273
  const loc = btnIdx !== undefined ? `component ${compIdx}, button ${btnIdx}` : `component ${compIdx}`;
273
- console.warn(` → ${file}: ${loc} → "${newId}"`);
274
+ stdout.warn(` → ${file}: ${loc} → "${newId}"`);
274
275
  }
275
276
  }
276
277
  if (numericLeadingFixes.length > 0) {
277
- console.warn(`Found ${numericLeadingFixes.length} key(s)/ID(s) that start with a number. Use a prefix (e.g. range_1_to_2_weeks).`);
278
+ stdout.warn(`Found ${numericLeadingFixes.length} key(s)/ID(s) that start with a number. Use a prefix (e.g. range_1_to_2_weeks).`);
278
279
  for (const f of numericLeadingFixes) {
279
280
  const loc = f.buttonIndex !== undefined
280
281
  ? `component ${f.componentIndex}, button ${f.buttonIndex} (${f.kind})`
281
282
  : `component ${f.componentIndex} (${f.kind})`;
282
- console.warn(` → ${f.file}: "${f.oldValue}" → "${f.newValue}" (${loc})`);
283
+ stdout.warn(` → ${f.file}: "${f.oldValue}" → "${f.newValue}" (${loc})`);
283
284
  }
284
285
  }
285
286
  if (opts.fixLint === true) {
@@ -311,7 +312,7 @@ export async function compileAllPages(opts) {
311
312
  code = fixNumericLeadingInSourceFile(code, file, numericForFile);
312
313
  }
313
314
  fs.writeFileSync(file, code, 'utf8');
314
- console.log(` ✓ Updated ${file}`);
315
+ stdout.print(` ✓ Updated ${file}`);
315
316
  }
316
317
  }
317
318
  // Re-parse after fixes
@@ -348,7 +349,7 @@ export async function compileAllPages(opts) {
348
349
  config = JSON.parse(configContent);
349
350
  }
350
351
  catch (error) {
351
- console.warn(`Failed to parse config.json: ${error}`);
352
+ stdout.warn(`Failed to parse config.json: ${error}`);
352
353
  }
353
354
  }
354
355
  else if (opts.embeddableId) {
@@ -360,7 +361,7 @@ export async function compileAllPages(opts) {
360
361
  config = JSON.parse(configContent);
361
362
  }
362
363
  catch (error) {
363
- console.warn(`Failed to parse config.json: ${error}`);
364
+ stdout.warn(`Failed to parse config.json: ${error}`);
364
365
  }
365
366
  }
366
367
  }
@@ -548,7 +549,7 @@ export async function compileAllPages(opts) {
548
549
  }
549
550
  }
550
551
  writeAtomic(opts.outPath, JSON.stringify(toWrite, null, 2));
551
- console.log(`Wrote ${opts.outPath} (${orderedPages.length} pages, ${Object.keys(styles).length > 0 ? 'with styles' : 'no styles'})`);
552
+ stdout.print(`Wrote ${opts.outPath} (${orderedPages.length} pages, ${Object.keys(styles).length > 0 ? 'with styles' : 'no styles'})`);
552
553
  }
553
554
  function derivePageKey(file, mode) {
554
555
  if (mode === 'filename') {
@@ -1191,7 +1192,7 @@ async function loadGlobalComponents(embeddableId, _config = null // config.compo
1191
1192
  // Extract location from filename (e.g., "before_page.location.tsx" -> "before_page")
1192
1193
  const locationMatch = tsxFile.match(/^(.+)\.location\.tsx$/);
1193
1194
  if (!locationMatch) {
1194
- console.warn(`Global component file "${tsxFile}" doesn't match expected pattern <location>.location.tsx`);
1195
+ stdout.warn(`Global component file "${tsxFile}" doesn't match expected pattern <location>.location.tsx`);
1195
1196
  continue;
1196
1197
  }
1197
1198
  const fileLocation = locationMatch[1];
@@ -1 +1 @@
1
- {"version":3,"file":"reverse.d.ts","sourceRoot":"","sources":["../../src/compiler/reverse.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,YAAY,CAAA;AAqlBzD,wBAAsB,cAAc,CAClC,UAAU,EAAE;IACV,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,cAAc,CAAC,EAAE,GAAG,EAAE,CAAA;IACtB,WAAW,CAAC,EAAE,GAAG,EAAE,CAAA;IACnB,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB,EACD,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,YAAY,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5E,iBAoFF;AAgiDD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKpD"}
1
+ {"version":3,"file":"reverse.d.ts","sourceRoot":"","sources":["../../src/compiler/reverse.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,YAAY,CAAA;AAqlBzD,wBAAsB,cAAc,CAClC,UAAU,EAAE;IACV,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,QAAQ,EAAE,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,cAAc,CAAC,EAAE,GAAG,EAAE,CAAA;IACtB,WAAW,CAAC,EAAE,GAAG,EAAE,CAAA;IACnB,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB,EACD,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE;IACL,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,YAAY,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5E,iBAoFF;AAgiDD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKpD"}
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import CSSJSON from 'cssjson';
4
4
  import pc from 'picocolors';
5
+ import * as stdout from '../stdout.js';
5
6
  import { TYPE_MAP } from './registry.js';
6
7
  import { generateRandomIdByType } from './helpers/duplicateIds.js';
7
8
  /**
@@ -262,7 +263,7 @@ function hasRequiredProps(component, fix) {
262
263
  }
263
264
  if (missingProps.length > 0) {
264
265
  if (fix) {
265
- console.warn(`Removed ${component.type} component (id: ${component.id}, key: ${component.key}) missing required props: ${missingProps.join(', ')}.`);
266
+ stdout.warn(`Removed ${component.type} component (id: ${component.id}, key: ${component.key}) missing required props: ${missingProps.join(', ')}.`);
266
267
  return false;
267
268
  }
268
269
  throw new Error(`${component.type} component (id: ${component.id}, key: ${component.key}) missing required props: ${missingProps.join(', ')}.`);
@@ -400,13 +401,13 @@ function checkAndFixDuplicateIds(pages, fix) {
400
401
  // This ID is duplicated
401
402
  const isButton = occurrences[0].buttonIndex !== undefined;
402
403
  const suffix = isButton ? ' (OptionSelector button)' : '';
403
- console.warn(`Found duplicate ID "${id}"${suffix} used ${occurrences.length} times:`);
404
+ stdout.warn(`Found duplicate ID "${id}"${suffix} used ${occurrences.length} times:`);
404
405
  for (let i = 0; i < occurrences.length; i++) {
405
406
  const occ = occurrences[i];
406
407
  const location = occ.buttonIndex !== undefined
407
408
  ? `button at index ${occ.buttonIndex}`
408
409
  : `component at index ${occ.componentIndex}`;
409
- console.log(` - Page "${occ.pageKey}": ${occ.type} (${location}) with ID "${occ.id}"`);
410
+ stdout.print(` - Page "${occ.pageKey}": ${occ.type} (${location}) with ID "${occ.id}"`);
410
411
  }
411
412
  // Create unique IDs for all but the first occurrence (type-based: plaintext_xxx, button_xxx, option_xxx)
412
413
  for (let i = 1; i < occurrences.length; i++) {
@@ -419,7 +420,7 @@ function checkAndFixDuplicateIds(pages, fix) {
419
420
  const location = occ.buttonIndex !== undefined
420
421
  ? `button at index ${occ.buttonIndex}`
421
422
  : `component at index ${occ.componentIndex}`;
422
- console.log(` → Renamed duplicate ID "${occ.id}" to "${newId}" in page "${occ.pageKey}" (${location})`);
423
+ stdout.print(` → Renamed duplicate ID "${occ.id}" to "${newId}" in page "${occ.pageKey}" (${location})`);
423
424
  }
424
425
  }
425
426
  }
@@ -454,14 +455,14 @@ function fixParentKeyDeprecation(components, fix) {
454
455
  const resolvedId = keyToId.get(parentKey);
455
456
  if (resolvedId) {
456
457
  comp.parent_id = resolvedId;
457
- console.warn(`Fixed parent_key on component (id: ${comp.id}, key: ${comp.key}) – resolved parent_key "${parentKey}" to parent_id.`);
458
+ stdout.warn(`Fixed parent_key on component (id: ${comp.id}, key: ${comp.key}) – resolved parent_key "${parentKey}" to parent_id.`);
458
459
  }
459
460
  else {
460
461
  toRemove.set(comp.id, parentKey);
461
462
  }
462
463
  }
463
464
  else {
464
- console.warn(`Fixed parent_key on component (id: ${comp.id}, key: ${comp.key}) – removed deprecated parent_key (already has parent_id).`);
465
+ stdout.warn(`Fixed parent_key on component (id: ${comp.id}, key: ${comp.key}) – removed deprecated parent_key (already has parent_id).`);
465
466
  }
466
467
  delete comp.parent_key;
467
468
  }
@@ -471,7 +472,7 @@ function fixParentKeyDeprecation(components, fix) {
471
472
  while (i < components.length) {
472
473
  const comp = components[i];
473
474
  if (toRemove.has(comp.id)) {
474
- console.warn(`Removed component (id: ${comp.id}, key: ${comp.key}) – parent_key "${toRemove.get(comp.id)}" not found, no parent_id.`);
475
+ stdout.warn(`Removed component (id: ${comp.id}, key: ${comp.key}) – parent_key "${toRemove.get(comp.id)}" not found, no parent_id.`);
475
476
  components.splice(i, 1);
476
477
  }
477
478
  else {
@@ -579,7 +580,7 @@ export async function reverseCompile(embeddable, embeddableId, opts) {
579
580
  embeddable.components.length > 0) {
580
581
  await extractGlobalComponents(embeddable.components, embeddableId, fix);
581
582
  }
582
- console.log(`${pc.cyan(`✓ Generated ${embeddable.pages.length} page(s) and ${embeddable.styles && Object.keys(embeddable.styles).length > 0 ? 'styles' : 'no styles'}`)}`);
583
+ stdout.print(`${pc.cyan(`✓ Generated ${embeddable.pages.length} page(s) and ${embeddable.styles && Object.keys(embeddable.styles).length > 0 ? 'styles' : 'no styles'}`)}`);
583
584
  }
584
585
  async function generatePageFile(page, embeddableId, fix) {
585
586
  const pageKey = page.key;
@@ -592,7 +593,7 @@ async function generatePageFile(page, embeddableId, fix) {
592
593
  // Write to embeddables/<id>/pages/<pageKey>.page.tsx
593
594
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
594
595
  fs.writeFileSync(filePath, tsx, 'utf8');
595
- console.log(`${pc.gray(`Generated ${filePath}`)}`);
596
+ stdout.print(`${pc.gray(`Generated ${filePath}`)}`);
596
597
  }
597
598
  catch (error) {
598
599
  if (error instanceof Error) {
@@ -606,7 +607,7 @@ function buildTree(components, pageKey, fix) {
606
607
  let filteredComponents = components.filter((comp) => {
607
608
  if (!comp.type) {
608
609
  if (fix) {
609
- console.warn(`Removed component (id: ${comp.id}, key: ${comp.key}) – missing type property.`);
610
+ stdout.warn(`Removed component (id: ${comp.id}, key: ${comp.key}) – missing type property.`);
610
611
  return false;
611
612
  }
612
613
  throw new Error(`Component (id: ${comp.id}, key: ${comp.key}) is missing required type property.`);
@@ -1066,7 +1067,7 @@ async function generateStylesFile(styles, embeddableId) {
1066
1067
  const filePath = path.join('embeddables', embeddableId, 'styles', 'index.css');
1067
1068
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
1068
1069
  fs.writeFileSync(filePath, css, 'utf8');
1069
- console.log(`${pc.gray(`Generated ${filePath}`)}`);
1070
+ stdout.print(`${pc.gray(`Generated ${filePath}`)}`);
1070
1071
  }
1071
1072
  catch (error) {
1072
1073
  if (error instanceof Error) {
@@ -1698,7 +1699,7 @@ async function generateConfigFile(embeddable, embeddableId, opts) {
1698
1699
  }
1699
1700
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
1700
1701
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
1701
- console.log(`${pc.gray(`Generated ${configPath}`)}`);
1702
+ stdout.print(`${pc.gray(`Generated ${configPath}`)}`);
1702
1703
  }
1703
1704
  catch (error) {
1704
1705
  if (error instanceof Error) {
@@ -1727,7 +1728,7 @@ async function extractComputedFields(computedFields, embeddableId) {
1727
1728
  const filePath = path.join(computedFieldsDir, fileName);
1728
1729
  // Write the code as-is (it should be valid JavaScript)
1729
1730
  fs.writeFileSync(filePath, field.code, 'utf8');
1730
- console.log(`${pc.gray(`Generated ${filePath}`)}`);
1731
+ stdout.print(`${pc.gray(`Generated ${filePath}`)}`);
1731
1732
  }
1732
1733
  }
1733
1734
  /**
@@ -1750,7 +1751,7 @@ async function extractDataOutputs(dataOutputs, embeddableId) {
1750
1751
  const filePath = path.join(actionsDir, fileName);
1751
1752
  // Write the code as-is (it should be valid JavaScript)
1752
1753
  fs.writeFileSync(filePath, action.code, 'utf8');
1753
- console.log(`${pc.gray(`Generated ${filePath}`)}`);
1754
+ stdout.print(`${pc.gray(`Generated ${filePath}`)}`);
1754
1755
  }
1755
1756
  }
1756
1757
  /**
@@ -1796,7 +1797,7 @@ async function extractGlobalComponents(components, embeddableId, fix) {
1796
1797
  // Components without parent_id must have _location
1797
1798
  if (!component.parent_id && !location) {
1798
1799
  if (fix) {
1799
- console.warn(`Removed global component "${component.id}" (key: "${component.key}") – missing _location (no parent_id).`);
1800
+ stdout.warn(`Removed global component "${component.id}" (key: "${component.key}") – missing _location (no parent_id).`);
1800
1801
  continue;
1801
1802
  }
1802
1803
  throw new Error(`Global component "${component.id}" (key: "${component.key}") must have _location since it has no parent_id.`);
@@ -1818,7 +1819,7 @@ async function extractGlobalComponents(components, embeddableId, fix) {
1818
1819
  const tsx = generateGlobalComponentTSX(locationComponents, tree);
1819
1820
  // Write to file
1820
1821
  fs.writeFileSync(filePath, tsx, 'utf8');
1821
- console.log(`${pc.gray(`Generated ${filePath}`)}`);
1822
+ stdout.print(`${pc.gray(`Generated ${filePath}`)}`);
1822
1823
  }
1823
1824
  }
1824
1825
  /**
@@ -1829,7 +1830,7 @@ function buildTreeForGlobalComponents(components, fix) {
1829
1830
  let filteredComponents = components.filter((comp) => {
1830
1831
  if (!comp.type) {
1831
1832
  if (fix) {
1832
- console.warn(`Removed global component (id: ${comp.id}, key: ${comp.key}) – missing type property.`);
1833
+ stdout.warn(`Removed global component (id: ${comp.id}, key: ${comp.key}) – missing type property.`);
1833
1834
  return false;
1834
1835
  }
1835
1836
  throw new Error(`Global component (id: ${comp.id}, key: ${comp.key}) is missing required type property.`);
package/dist/logger.d.ts CHANGED
@@ -6,5 +6,6 @@ export interface Logger {
6
6
  }
7
7
  export declare function createLogger(fn: string): Logger;
8
8
  export declare function captureException(error: unknown): void;
9
+ export declare function exit(code: number): Promise<never>;
9
10
  export {};
10
11
  //# sourceMappingURL=logger.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;AAE9E,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC7C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;CAC/C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAY/C;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAErD"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;AAE9E,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC7C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;CAC/C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAY/C;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAErD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAGvD"}
package/dist/logger.js CHANGED
@@ -15,3 +15,7 @@ export function createLogger(fn) {
15
15
  export function captureException(error) {
16
16
  Sentry.captureException(error);
17
17
  }
18
+ export async function exit(code) {
19
+ await Sentry.flush(2000);
20
+ process.exit(code);
21
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"branches.d.ts","sourceRoot":"","sources":["../../src/prompts/branches.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAG9C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,YAAY,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CA8BzE;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA8DxF"}
1
+ {"version":3,"file":"branches.d.ts","sourceRoot":"","sources":["../../src/prompts/branches.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAG9C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,YAAY,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CA8BzE;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA8DxF"}
@@ -1,6 +1,7 @@
1
1
  import pc from 'picocolors';
2
2
  import prompts from 'prompts';
3
3
  import { getAuthenticatedSupabaseClient } from '../auth/index.js';
4
+ import * as stdout from '../stdout.js';
4
5
  import { BranchStatus } from '../constants.js';
5
6
  import { formatDate } from '../helpers/dates.js';
6
7
  /**
@@ -18,7 +19,7 @@ export async function fetchBranches(flowId) {
18
19
  .eq('flow_id', flowId)
19
20
  .order('created_at', { ascending: false });
20
21
  if (error) {
21
- console.warn(pc.yellow(`Could not fetch branches: ${error.message}`));
22
+ stdout.warn(pc.yellow(`Could not fetch branches: ${error.message}`));
22
23
  return [];
23
24
  }
24
25
  return (data || []).map((row) => ({
@@ -31,7 +32,7 @@ export async function fetchBranches(flowId) {
31
32
  }));
32
33
  }
33
34
  catch (err) {
34
- console.warn(pc.yellow(`Could not fetch branches: ${err}`));
35
+ stdout.warn(pc.yellow(`Could not fetch branches: ${err}`));
35
36
  return [];
36
37
  }
37
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"embeddables.d.ts","sourceRoot":"","sources":["../../src/prompts/embeddables.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CA0B1F;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA2BpC;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuCxB;AAED;;GAEG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAqC3E;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuCxB"}
1
+ {"version":3,"file":"embeddables.d.ts","sourceRoot":"","sources":["../../src/prompts/embeddables.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,0BAA0B;IACzC,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CA0B1F;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA2BpC;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuCxB;AAED;;GAEG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAqC3E;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuCxB"}