@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
@@ -2,7 +2,7 @@ We need to build the pages from the attached images. Please create a plan and hi
2
2
 
3
3
  Embeddable ID: <EMBEDDABLE_ID>
4
4
 
5
- Important: Always read the Embeddables CLI context before starting (e.g. @.cursor/rules/embeddables-cli.md or @.claudefiles/embeddables-cli.md depending on your editor).
5
+ Important: Always read the Embeddables CLI context before starting (e.g. @.cursor/rules/embeddables-cli.md or @.claude/embeddables-cli.md depending on your editor).
6
6
 
7
7
  [KEEP / REMOVE]
8
8
  Use the designs from the images but use the content from the attached document instead, and build out the pages based on that content. Therefore, when using the designs from the images, for each page just find the most relevant design for the page's content and design it based on that. However, don't be confined to the provided designs - just find the closest design and modify it to fit the content required.
package/dist/cli.js CHANGED
@@ -1,8 +1,14 @@
1
+ import * as Sentry from '@sentry/node';
2
+ Sentry.init({
3
+ dsn: 'https://1660165be6d73c50814963e5c6bbd5dc@o4510046767742976.ingest.us.sentry.io/4510908525379584',
4
+ tracesSampleRate: 1.0,
5
+ enableLogs: true,
6
+ // debug: true,
7
+ });
1
8
  import { createRequire } from 'node:module';
2
9
  import path from 'node:path';
3
10
  import { fileURLToPath } from 'node:url';
4
11
  import { Command } from 'commander';
5
- import pc from 'picocolors';
6
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
13
  const require = createRequire(import.meta.url);
8
14
  const pkg = require(path.join(__dirname, '..', 'package.json'));
@@ -16,15 +22,6 @@ import { runLogout } from './commands/logout.js';
16
22
  import { runPull } from './commands/pull.js';
17
23
  import { runSave } from './commands/save.js';
18
24
  import { runUpgrade } from './commands/upgrade.js';
19
- // Make all console.warn output yellow
20
- const originalWarn = console.warn.bind(console);
21
- console.warn = (...args) => {
22
- originalWarn(...args.map((arg) => typeof arg === 'string' ? `${pc.bold(pc.bgYellow(' WARN '))} ${pc.yellow(arg)}` : arg));
23
- };
24
- const originalError = console.error.bind(console);
25
- console.error = (...args) => {
26
- originalError(...args.map((arg) => typeof arg === 'string' ? `${pc.bold(pc.bgRed(' ERROR '))} ${pc.red(arg)}` : arg));
27
- };
28
25
  const program = new Command();
29
26
  program
30
27
  .name('embeddables')
@@ -1 +1 @@
1
- {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/commands/branch.ts"],"names":[],"mappings":"AAMA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,iBAwDpD"}
1
+ {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/commands/branch.ts"],"names":[],"mappings":"AAQA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,iBA4DpD"}
@@ -2,13 +2,17 @@ import pc from 'picocolors';
2
2
  import { isLoggedIn } from '../auth/index.js';
3
3
  import { runPull } from './pull.js';
4
4
  import { promptForLocalEmbeddable, fetchBranches, promptForBranch } from '../prompts/index.js';
5
+ import { createLogger, exit } from '../logger.js';
6
+ import * as stdout from '../stdout.js';
5
7
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
6
8
  export async function runBranch(opts) {
9
+ const logger = createLogger('runBranch');
7
10
  // Check login status
8
11
  if (!isLoggedIn()) {
9
- console.error(pc.red('Not logged in.'));
10
- console.log(pc.gray('Run "embeddables login" first.'));
11
- process.exit(1);
12
+ stdout.error(pc.red('Not logged in.'));
13
+ stdout.print(pc.gray('Run "embeddables login" first.'));
14
+ logger.error('not logged in');
15
+ await exit(1);
12
16
  }
13
17
  // Get embeddable ID
14
18
  const inferred = inferEmbeddableFromCwd();
@@ -19,33 +23,34 @@ export async function runBranch(opts) {
19
23
  if (!embeddableId) {
20
24
  const selected = await promptForLocalEmbeddable();
21
25
  if (!selected) {
22
- process.exit(1);
26
+ await exit(1);
27
+ return;
23
28
  }
24
29
  embeddableId = selected;
25
30
  }
26
- console.log('');
27
- console.log(pc.cyan('Fetching branches...'));
31
+ stdout.print('');
32
+ stdout.print(pc.cyan('Fetching branches...'));
28
33
  // Fetch branches for this embeddable
29
34
  const branches = await fetchBranches(embeddableId);
30
35
  if (branches.length === 0) {
31
- console.log(pc.yellow('No branches found for this embeddable.'));
32
- console.log(pc.gray('Branches are created in the Embeddables Builder.'));
36
+ stdout.print(pc.yellow('No branches found for this embeddable.'));
37
+ stdout.print(pc.gray('Branches are created in the Embeddables Builder.'));
33
38
  return;
34
39
  }
35
- console.log('');
40
+ stdout.print('');
36
41
  // Prompt for branch selection
37
42
  const selectedBranch = await promptForBranch(branches);
38
- console.log('');
43
+ stdout.print('');
39
44
  // Pull the selected branch
40
45
  if (selectedBranch === null) {
41
- // User selected "main" - pull main and clear saved branch
42
- console.log(pc.cyan('Switching to main (latest published version)...'));
43
- console.log('');
46
+ // User selected "main" - pull without branch
47
+ stdout.print(pc.cyan('Switching to main (latest published version)...'));
48
+ stdout.print('');
44
49
  await runPull({ id: embeddableId, useMain: true });
45
50
  }
46
51
  else {
47
- console.log(pc.cyan(`Switching to branch: ${selectedBranch.name}...`));
48
- console.log('');
52
+ stdout.print(pc.cyan(`Switching to branch: ${selectedBranch.name}...`));
53
+ stdout.print('');
49
54
  await runPull({
50
55
  id: embeddableId,
51
56
  branch: selectedBranch.id,
@@ -1 +1 @@
1
- {"version":3,"file":"build-workbench.d.ts","sourceRoot":"","sources":["../../src/commands/build-workbench.ts"],"names":[],"mappings":"AAQA,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,iBA2H/E"}
1
+ {"version":3,"file":"build-workbench.d.ts","sourceRoot":"","sources":["../../src/commands/build-workbench.ts"],"names":[],"mappings":"AAUA,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,iBA+H/E"}
@@ -5,29 +5,34 @@ import postcss from 'postcss';
5
5
  import tailwindcss from '@tailwindcss/postcss';
6
6
  import autoprefixer from 'autoprefixer';
7
7
  import pc from 'picocolors';
8
+ import { createLogger, exit } from '../logger.js';
9
+ import * as stdout from '../stdout.js';
8
10
  export async function runBuildWorkbench(opts) {
11
+ const logger = createLogger('runBuildWorkbench');
9
12
  const outDir = opts.out || 'dist/workbench';
10
13
  const shouldMinify = opts.minify ?? true;
11
14
  // Check if wrangler config exists in outDir (native Cloudflare Git integration mode)
12
15
  const hasWranglerConfig = fs.existsSync(path.join(outDir, 'wrangler.jsonc')) ||
13
16
  fs.existsSync(path.join(outDir, 'wrangler.toml'));
14
- console.log(pc.cyan('Building Workbench for CDN deployment...'));
15
- console.log();
17
+ stdout.print(pc.cyan('Building Workbench for CDN deployment...'));
18
+ stdout.print();
16
19
  // Ensure output directory exists
17
20
  fs.mkdirSync(outDir, { recursive: true });
18
21
  const workbenchEntry = path.join(process.cwd(), 'src', 'workbench', 'index.tsx');
19
22
  const workbenchCssEntry = path.join(process.cwd(), 'src', 'workbench', 'workbench.css');
20
23
  // Check that source files exist
21
24
  if (!fs.existsSync(workbenchEntry)) {
22
- console.error(`Workbench entry not found: ${workbenchEntry}`);
23
- process.exit(1);
25
+ stdout.error(`Workbench entry not found: ${workbenchEntry}`);
26
+ logger.error('workbench entry not found', { path: workbenchEntry });
27
+ await exit(1);
24
28
  }
25
29
  if (!fs.existsSync(workbenchCssEntry)) {
26
- console.error(`Workbench CSS not found: ${workbenchCssEntry}`);
27
- process.exit(1);
30
+ stdout.error(`Workbench CSS not found: ${workbenchCssEntry}`);
31
+ logger.error('workbench css not found', { path: workbenchCssEntry });
32
+ await exit(1);
28
33
  }
29
34
  // Build JavaScript bundle
30
- console.log(pc.dim(' Building JavaScript bundle...'));
35
+ stdout.print(pc.dim(' Building JavaScript bundle...'));
31
36
  const jsResult = await esbuild.build({
32
37
  entryPoints: [workbenchEntry],
33
38
  bundle: true,
@@ -41,11 +46,12 @@ export async function runBuildWorkbench(opts) {
41
46
  });
42
47
  const jsOutput = jsResult.outputFiles?.[0];
43
48
  if (!jsOutput) {
44
- console.error('Failed to build JavaScript bundle');
45
- process.exit(1);
49
+ stdout.error('Failed to build JavaScript bundle');
50
+ logger.error('workbench js bundle build failed');
51
+ await exit(1);
46
52
  }
47
53
  // Build CSS
48
- console.log(pc.dim(' Building CSS...'));
54
+ stdout.print(pc.dim(' Building CSS...'));
49
55
  const rawCss = fs.readFileSync(workbenchCssEntry, 'utf8');
50
56
  const cssResult = await postcss([tailwindcss(), autoprefixer]).process(rawCss, {
51
57
  from: workbenchCssEntry,
@@ -63,50 +69,50 @@ export async function runBuildWorkbench(opts) {
63
69
  fs.mkdirSync(publicDir, { recursive: true });
64
70
  fs.writeFileSync(path.join(publicDir, 'workbench.js'), jsOutput.contents);
65
71
  const jsSize = (jsOutput.contents.length / 1024).toFixed(1);
66
- console.log(pc.green(` ✓ ${publicDir}/workbench.js (${jsSize} KB)`));
72
+ stdout.print(pc.green(` ✓ ${publicDir}/workbench.js (${jsSize} KB)`));
67
73
  fs.writeFileSync(path.join(publicDir, 'workbench.css'), cssResult.css);
68
74
  const cssSize = (cssResult.css.length / 1024).toFixed(1);
69
- console.log(pc.green(` ✓ ${publicDir}/workbench.css (${cssSize} KB)`));
75
+ stdout.print(pc.green(` ✓ ${publicDir}/workbench.css (${cssSize} KB)`));
70
76
  fs.writeFileSync(path.join(publicDir, '_headers'), headersContent);
71
- console.log(pc.green(` ✓ ${publicDir}/_headers`));
72
- console.log();
73
- console.log(pc.bold(pc.green('Build complete!')));
74
- console.log();
75
- console.log(pc.dim('Using existing wrangler.toml for Cloudflare Git integration'));
77
+ stdout.print(pc.green(` ✓ ${publicDir}/_headers`));
78
+ stdout.print();
79
+ stdout.print(pc.bold(pc.green('Build complete!')));
80
+ stdout.print();
81
+ stdout.print(pc.dim('Using existing wrangler.toml for Cloudflare Git integration'));
76
82
  }
77
83
  else {
78
84
  // Standalone mode: output to outDir with nested cloudflare-worker folder
79
85
  const jsPath = path.join(outDir, 'workbench.js');
80
86
  fs.writeFileSync(jsPath, jsOutput.contents);
81
87
  const jsSize = (jsOutput.contents.length / 1024).toFixed(1);
82
- console.log(pc.green(` ✓ ${jsPath} (${jsSize} KB)`));
88
+ stdout.print(pc.green(` ✓ ${jsPath} (${jsSize} KB)`));
83
89
  const cssPath = path.join(outDir, 'workbench.css');
84
90
  fs.writeFileSync(cssPath, cssResult.css);
85
91
  const cssSize = (cssResult.css.length / 1024).toFixed(1);
86
- console.log(pc.green(` ✓ ${cssPath} (${cssSize} KB)`));
92
+ stdout.print(pc.green(` ✓ ${cssPath} (${cssSize} KB)`));
87
93
  // Generate Cloudflare static site files
88
- console.log(pc.dim(' Generating Cloudflare deployment files...'));
94
+ stdout.print(pc.dim(' Generating Cloudflare deployment files...'));
89
95
  const workerDir = path.join(outDir, 'cloudflare');
90
96
  fs.mkdirSync(workerDir, { recursive: true });
91
97
  const wranglerConfig = generateWranglerConfig();
92
98
  fs.writeFileSync(path.join(workerDir, 'wrangler.jsonc'), wranglerConfig);
93
- console.log(pc.green(` ✓ ${workerDir}/wrangler.jsonc`));
99
+ stdout.print(pc.green(` ✓ ${workerDir}/wrangler.jsonc`));
94
100
  const publicDir = path.join(workerDir, 'public');
95
101
  fs.mkdirSync(publicDir, { recursive: true });
96
102
  fs.copyFileSync(jsPath, path.join(publicDir, 'workbench.js'));
97
103
  fs.copyFileSync(cssPath, path.join(publicDir, 'workbench.css'));
98
104
  fs.writeFileSync(path.join(publicDir, '_headers'), headersContent);
99
- console.log(pc.green(` ✓ ${publicDir}/ (assets + _headers)`));
100
- console.log();
101
- console.log(pc.bold(pc.green('Build complete!')));
102
- console.log();
103
- console.log(pc.cyan('Deploy to Cloudflare:'));
104
- console.log(pc.dim(` cd ${workerDir} && npx wrangler deploy`));
105
+ stdout.print(pc.green(` ✓ ${publicDir}/ (assets + _headers)`));
106
+ stdout.print();
107
+ stdout.print(pc.bold(pc.green('Build complete!')));
108
+ stdout.print();
109
+ stdout.print(pc.cyan('Deploy to Cloudflare:'));
110
+ stdout.print(pc.dim(` cd ${workerDir} && npx wrangler deploy`));
105
111
  }
106
- console.log();
107
- console.log(pc.cyan('Usage:'));
108
- console.log(pc.dim(' Add ?workbench=true to any engine preview URL'));
109
- console.log();
112
+ stdout.print();
113
+ stdout.print(pc.cyan('Usage:'));
114
+ stdout.print(pc.dim(' Add ?workbench=true to any engine preview URL'));
115
+ stdout.print();
110
116
  }
111
117
  function generateWranglerConfig() {
112
118
  return `{
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAMA,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBAiCA"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAQA,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBA4CA"}
@@ -2,8 +2,11 @@ import path from 'node:path';
2
2
  import { compileAllPages } from '../compiler/index.js';
3
3
  import { formatError } from '../compiler/errors.js';
4
4
  import { promptForLocalEmbeddable } from '../prompts/index.js';
5
+ import { captureException, createLogger, exit } from '../logger.js';
6
+ import * as stdout from '../stdout.js';
5
7
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
6
8
  export async function runBuild(opts) {
9
+ const logger = createLogger('runBuild');
7
10
  const inferred = inferEmbeddableFromCwd();
8
11
  let embeddableId = opts.id ?? inferred?.embeddableId;
9
12
  if (inferred && !opts.id && embeddableId) {
@@ -14,10 +17,13 @@ export async function runBuild(opts) {
14
17
  message: 'Select an embeddable to build:',
15
18
  });
16
19
  if (!selected) {
17
- process.exit(1);
20
+ logger.error('no embeddable selected');
21
+ await exit(1);
22
+ return;
18
23
  }
19
24
  embeddableId = selected;
20
25
  }
26
+ logger.info('build started', { embeddableId });
21
27
  const pagesGlob = opts.pages || `embeddables/${embeddableId}/pages/**/*.page.tsx`;
22
28
  const outPath = opts.out || path.join('embeddables', embeddableId, '.generated', 'embeddable.json');
23
29
  const stylesDir = path.join('embeddables', embeddableId, 'styles');
@@ -32,7 +38,10 @@ export async function runBuild(opts) {
32
38
  });
33
39
  }
34
40
  catch (e) {
35
- console.error(formatError(e));
36
- process.exit(1);
41
+ captureException(e);
42
+ stdout.error(formatError(e));
43
+ logger.error('build failed', { embeddableId });
44
+ await exit(1);
37
45
  }
46
+ logger.info('build complete', { embeddableId });
38
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAoHA,wBAAsB,MAAM,CAAC,IAAI,EAAE;IACjC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBAkHA"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAsHA,wBAAsB,MAAM,CAAC,IAAI,EAAE;IACjC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,iBAiIA"}
@@ -7,6 +7,8 @@ import prompts from 'prompts';
7
7
  import { compileAllPages } from '../compiler/index.js';
8
8
  import { startProxyServer } from '../proxy/server.js';
9
9
  import { formatError } from '../compiler/errors.js';
10
+ import { captureException, createLogger, exit } from '../logger.js';
11
+ import * as stdout from '../stdout.js';
10
12
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
11
13
  /**
12
14
  * Check whether a port is available by attempting to listen on it.
@@ -72,8 +74,8 @@ async function discoverEmbeddables() {
72
74
  async function promptForEmbeddable() {
73
75
  const embeddables = await discoverEmbeddables();
74
76
  if (embeddables.length === 0) {
75
- console.error('No embeddables found in the embeddables/ directory.');
76
- console.error('Run `embeddables pull --id <id>` to pull an embeddable first.');
77
+ stdout.error('No embeddables found in the embeddables/ directory.');
78
+ stdout.error('Run `embeddables pull --id <id>` to pull an embeddable first.');
77
79
  return null;
78
80
  }
79
81
  const choices = embeddables.map((e) => ({
@@ -93,6 +95,7 @@ async function promptForEmbeddable() {
93
95
  return response.id || null;
94
96
  }
95
97
  export async function runDev(opts) {
98
+ const logger = createLogger('runDev');
96
99
  const inferred = inferEmbeddableFromCwd();
97
100
  let embeddableId = opts.id ?? inferred?.embeddableId;
98
101
  if (inferred && !opts.id && embeddableId) {
@@ -101,10 +104,13 @@ export async function runDev(opts) {
101
104
  if (!embeddableId) {
102
105
  const selected = await promptForEmbeddable();
103
106
  if (!selected) {
104
- process.exit(1);
107
+ logger.error('no embeddable selected');
108
+ await exit(1);
109
+ return;
105
110
  }
106
111
  embeddableId = selected;
107
112
  }
113
+ logger.info('dev started', { embeddableId, engine: opts.engine, port: opts.port });
108
114
  const pagesGlob = opts.pages || `embeddables/${embeddableId}/pages/**/*.page.tsx`;
109
115
  const outPath = opts.out || path.join('embeddables', embeddableId, '.generated', 'embeddable.json');
110
116
  const stylesDir = path.join('embeddables', embeddableId, 'styles');
@@ -127,8 +133,10 @@ export async function runDev(opts) {
127
133
  });
128
134
  }
129
135
  catch (e) {
130
- console.error(formatError(e));
131
- process.exit(1);
136
+ captureException(e);
137
+ stdout.error(formatError(e));
138
+ logger.error('initial build failed', { embeddableId });
139
+ await exit(1);
132
140
  }
133
141
  // Start proxy — find an available port if the requested one is taken
134
142
  const requestedPort = Number(opts.port);
@@ -137,12 +145,15 @@ export async function runDev(opts) {
137
145
  port = await getAvailablePort(requestedPort);
138
146
  }
139
147
  catch (e) {
140
- console.error(pc.red(e.message));
141
- process.exit(1);
148
+ captureException(e);
149
+ stdout.error(pc.red(e.message));
150
+ logger.error('no available port', { requestedPort });
151
+ await exit(1);
152
+ return;
142
153
  }
143
154
  if (port !== requestedPort) {
144
- console.warn(`Port ${requestedPort} is in use, using ${port} instead.`);
145
- console.log('');
155
+ stdout.warn(`Port ${requestedPort} is in use, using ${port} instead.`);
156
+ stdout.print('');
146
157
  }
147
158
  const proxy = await startProxyServer({
148
159
  port,
@@ -152,6 +163,7 @@ export async function runDev(opts) {
152
163
  embeddableId,
153
164
  watchWorkbench: true,
154
165
  });
166
+ logger.info('dev server running', { embeddableId, port });
155
167
  // Watch all source files: pages, styles, config, global components, computed fields, and actions
156
168
  const watcher = chokidar.watch([pagesGlob, stylesGlob, configPath, globalComponentsGlob, computedFieldsGlob, actionsGlob], {
157
169
  ignoreInitial: true,
@@ -172,7 +184,8 @@ export async function runDev(opts) {
172
184
  catch (e) {
173
185
  // Keep dev server running; compiler errors show in console.
174
186
  // (Proxy will still serve the last good JSON.)
175
- console.error(formatError(e));
187
+ captureException(e);
188
+ stdout.error(formatError(e));
176
189
  }
177
190
  };
178
191
  let t;
@@ -180,21 +193,21 @@ export async function runDev(opts) {
180
193
  clearTimeout(t);
181
194
  t = setTimeout(rebuild, 80);
182
195
  });
183
- console.log(`Watching embeddables/${embeddableId}/ for changes...`);
184
- console.log('');
196
+ stdout.print(`Watching embeddables/${embeddableId}/ for changes...`);
197
+ stdout.print('');
185
198
  const terminalWidth = process.stdout.columns || 80;
186
199
  const separator = '━'.repeat(terminalWidth);
187
- console.log(`${pc.bold(pc.cyan(separator))}`);
188
- console.log('');
200
+ stdout.print(`${pc.bold(pc.cyan(separator))}`);
201
+ stdout.print('');
189
202
  if (opts.local) {
190
- console.log(`${pc.bold(pc.blue('Mode:'))} ${pc.blue('Local')} (using ${opts.engine})`);
203
+ stdout.print(`${pc.bold(pc.blue('Mode:'))} ${pc.blue('Local')} (using ${opts.engine})`);
191
204
  }
192
205
  else {
193
- console.log(`${pc.bold(pc.yellow('Mode:'))} ${pc.yellow('Remote')} (using ${opts.engine})`);
206
+ stdout.print(`${pc.bold(pc.yellow('Mode:'))} ${pc.yellow('Remote')} (using ${opts.engine})`);
194
207
  }
195
- console.log('');
196
- console.log(`${pc.bold(pc.green('Preview URL:'))} ${pc.underline(pc.cyan(`http://localhost:${port}?id=${embeddableId}&version=latest`))}`);
197
- console.log('');
198
- console.log(`${pc.bold(pc.cyan(separator))}`);
199
- console.log('');
208
+ stdout.print('');
209
+ stdout.print(`${pc.bold(pc.green('Preview URL:'))} ${pc.underline(pc.cyan(`http://localhost:${port}?id=${embeddableId}&version=latest`))}`);
210
+ stdout.print('');
211
+ stdout.print(`${pc.bold(pc.cyan(separator))}`);
212
+ stdout.print('');
200
213
  }
@@ -1 +1 @@
1
- {"version":3,"file":"experiments-connect.d.ts","sourceRoot":"","sources":["../../src/commands/experiments-connect.ts"],"names":[],"mappings":"AAaA,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,iBAcA"}
1
+ {"version":3,"file":"experiments-connect.d.ts","sourceRoot":"","sources":["../../src/commands/experiments-connect.ts"],"names":[],"mappings":"AAeA,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,iBAeA"}
@@ -4,38 +4,44 @@ import pc from 'picocolors';
4
4
  import { isLoggedIn } from '../auth/index.js';
5
5
  import { getProjectId, writeProjectConfig } from '../config/index.js';
6
6
  import { promptForProject, promptForLocalEmbeddable, promptForExperiment, } from '../prompts/index.js';
7
+ import { createLogger, exit } from '../logger.js';
8
+ import * as stdout from '../stdout.js';
7
9
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
8
10
  export async function runExperimentsConnect(opts) {
11
+ const logger = createLogger('runExperimentsConnect');
9
12
  try {
10
13
  await runExperimentsConnectInner(opts);
11
14
  }
12
15
  catch (error) {
13
16
  if (error instanceof Error) {
14
- console.error(pc.red(`Connect failed: ${error.message}`));
17
+ stdout.error(pc.red(`Connect failed: ${error.message}`));
15
18
  }
16
19
  else {
17
- console.error(pc.red('Connect failed with an unexpected error.'));
18
- }
19
- process.exit(1);
20
- if (error instanceof Error && error.message === 'exit') {
21
- throw error;
20
+ stdout.error(pc.red('Connect failed with an unexpected error.'));
22
21
  }
22
+ logger.error('experiments connect failed', {
23
+ message: error instanceof Error ? error.message : 'unexpected error',
24
+ });
25
+ await exit(1);
23
26
  }
24
27
  }
25
28
  async function runExperimentsConnectInner(opts) {
29
+ const logger = createLogger('runExperimentsConnect');
26
30
  // 1. Check login (needed for fetching experiments from Supabase)
27
31
  if (!isLoggedIn()) {
28
- console.error(pc.red('Not logged in.'));
29
- console.log(pc.gray('Run "embeddables login" first.'));
30
- process.exit(1);
32
+ stdout.error(pc.red('Not logged in.'));
33
+ stdout.print(pc.gray('Run "embeddables login" first.'));
34
+ logger.error('not logged in');
35
+ await exit(1);
31
36
  }
32
37
  // 2. Get project ID (needed for experiment list)
33
38
  let projectId = getProjectId();
34
39
  if (!projectId) {
35
- console.log(pc.cyan('No project configured. Fetching projects...'));
40
+ stdout.print(pc.cyan('No project configured. Fetching projects...'));
36
41
  const selectedProject = await promptForProject();
37
42
  if (!selectedProject) {
38
- process.exit(1);
43
+ await exit(1);
44
+ return;
39
45
  }
40
46
  projectId = selectedProject.id;
41
47
  writeProjectConfig({
@@ -44,8 +50,8 @@ async function runExperimentsConnectInner(opts) {
44
50
  project_id: projectId,
45
51
  project_name: selectedProject.title || undefined,
46
52
  });
47
- console.log(pc.green('✓ Saved project to embeddables.json'));
48
- console.log('');
53
+ stdout.print(pc.green('✓ Saved project to embeddables.json'));
54
+ stdout.print('');
49
55
  }
50
56
  // 4. Get embeddable ID (from option, cwd inference, or interactive prompt)
51
57
  const inferred = inferEmbeddableFromCwd();
@@ -58,41 +64,46 @@ async function runExperimentsConnectInner(opts) {
58
64
  message: 'Select an embeddable to connect the experiment to:',
59
65
  });
60
66
  if (!selected) {
61
- process.exit(1);
67
+ await exit(1);
68
+ return;
62
69
  }
63
70
  embeddableId = selected;
64
- console.log('');
71
+ stdout.print('');
65
72
  }
66
73
  // 5. Get experiment_id and experiment_key (from opts or interactive prompt)
67
74
  let experimentId = opts.experimentId;
68
75
  let experimentKey = opts.experimentKey;
69
76
  if (experimentId && !experimentKey) {
70
- console.error(pc.red('When using --experiment-id, --experiment-key is also required.'));
71
- process.exit(1);
77
+ stdout.error(pc.red('When using --experiment-id, --experiment-key is also required.'));
78
+ logger.error('missing experiment-key flag');
79
+ await exit(1);
72
80
  }
73
81
  if (experimentKey && !experimentId) {
74
- console.error(pc.red('When using --experiment-key, --experiment-id is also required.'));
75
- process.exit(1);
82
+ stdout.error(pc.red('When using --experiment-key, --experiment-id is also required.'));
83
+ logger.error('missing experiment-id flag');
84
+ await exit(1);
76
85
  }
77
86
  if (!experimentId || !experimentKey) {
78
- console.log(pc.cyan('Fetching experiments from project...'));
87
+ stdout.print(pc.cyan('Fetching experiments from project...'));
79
88
  const selected = await promptForExperiment(projectId, {
80
89
  message: 'Select an experiment to connect:',
81
90
  excludeConnectedTo: embeddableId,
82
91
  });
83
92
  if (!selected) {
84
- process.exit(1);
93
+ await exit(1);
94
+ return;
85
95
  }
86
96
  experimentId = selected.experiment_id;
87
97
  experimentKey = selected.experiment_key;
88
- console.log('');
98
+ stdout.print('');
89
99
  }
90
100
  // 6. Read config.json
91
101
  const configPath = path.join('embeddables', embeddableId, 'config.json');
92
102
  if (!fs.existsSync(configPath)) {
93
- console.error(pc.red(`No config.json found at ${configPath}`));
94
- console.log(pc.gray('Run "embeddables pull" or "embeddables init" first.'));
95
- process.exit(1);
103
+ stdout.error(pc.red(`No config.json found at ${configPath}`));
104
+ stdout.print(pc.gray('Run "embeddables pull" or "embeddables init" first.'));
105
+ logger.error('config not found', { configPath });
106
+ await exit(1);
96
107
  }
97
108
  let config;
98
109
  try {
@@ -100,20 +111,22 @@ async function runExperimentsConnectInner(opts) {
100
111
  config = JSON.parse(content);
101
112
  }
102
113
  catch {
103
- console.error(pc.red('Failed to parse config.json.'));
104
- process.exit(1);
114
+ stdout.error(pc.red('Failed to parse config.json.'));
115
+ logger.error('failed to parse config');
116
+ await exit(1);
117
+ return;
105
118
  }
106
119
  // 7. Append to connected_experiments
107
120
  const connectedExperiments = Array.isArray(config.connected_experiments) ? [...config.connected_experiments] : [];
108
121
  const alreadyConnected = connectedExperiments.some((e) => e.experiment_id === experimentId && e.experiment_key === experimentKey);
109
122
  if (alreadyConnected) {
110
- console.log(pc.yellow(`Experiment "${experimentKey}" is already connected to this embeddable.`));
123
+ stdout.print(pc.yellow(`Experiment "${experimentKey}" is already connected to this embeddable.`));
111
124
  return;
112
125
  }
113
126
  connectedExperiments.push({ experiment_id: experimentId, experiment_key: experimentKey });
114
127
  config.connected_experiments = connectedExperiments;
115
128
  // 8. Write the modified config back
116
129
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
117
- console.log(pc.green(`✓ Connected experiment "${experimentKey}" to embeddable (updated config.json)`));
118
- console.log(pc.gray('Run "embeddables save" to persist to the cloud.'));
130
+ stdout.print(pc.green(`✓ Connected experiment "${experimentKey}" to embeddable (updated config.json)`));
131
+ stdout.print(pc.gray('Run "embeddables save" to persist to the cloud.'));
119
132
  }
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAuHA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBAyOxE"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAwHA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,iBAyOxE"}