@embeddables/cli 0.7.7 → 0.7.8

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/dist/cli.js CHANGED
@@ -13,7 +13,9 @@ import pc from 'picocolors';
13
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
14
  const require = createRequire(import.meta.url);
15
15
  const pkg = require(path.join(__dirname, '..', 'package.json'));
16
+ Sentry.getCurrentScope().setAttributes({ cliVersion: pkg.version });
16
17
  import { isLoggedIn } from './auth/index.js';
18
+ import { getSentryContextFromProjectConfig, setSentryContext, setSentryUserFromAuth, } from './sentry-context.js';
17
19
  import { runBranch } from './commands/branch.js';
18
20
  import { runBuild } from './commands/build.js';
19
21
  import { runDev } from './commands/dev.js';
@@ -38,6 +40,11 @@ async function requireLogin(commandName) {
38
40
  stdout.print(pc.gray('Run "embeddables login" first.'));
39
41
  await exit(1);
40
42
  }
43
+ /** Set Sentry context from config and auth for the current command. */
44
+ async function setSentryContextForCommand() {
45
+ setSentryContext(getSentryContextFromProjectConfig());
46
+ await setSentryUserFromAuth();
47
+ }
41
48
  const program = new Command();
42
49
  program
43
50
  .name('embeddables')
@@ -50,6 +57,7 @@ program
50
57
  .option('-y, --yes', 'Skip prompts and use defaults')
51
58
  .action(async (opts) => {
52
59
  await requireLogin('init');
60
+ await setSentryContextForCommand();
53
61
  await runInit({ projectId: opts.projectId, yes: opts.yes });
54
62
  });
55
63
  program
@@ -61,6 +69,7 @@ program
61
69
  .option('--pageKeyFrom <mode>', 'filename|export', 'filename')
62
70
  .action(async (opts) => {
63
71
  await requireLogin('build');
72
+ await setSentryContextForCommand();
64
73
  await runBuild(opts);
65
74
  });
66
75
  program
@@ -76,6 +85,7 @@ program
76
85
  .option('--pageKeyFrom <mode>', 'filename|export', 'filename')
77
86
  .action(async (opts) => {
78
87
  await requireLogin('dev');
88
+ await setSentryContextForCommand();
79
89
  // --local flag overrides --engine to use local engine
80
90
  if (opts.local) {
81
91
  opts.engine = 'http://localhost:8787';
@@ -104,6 +114,7 @@ program
104
114
  .option('-p, --preserve', 'Preserve component order in config for forward compile')
105
115
  .action(async (opts) => {
106
116
  await requireLogin('pull');
117
+ await setSentryContextForCommand();
107
118
  await runPull(opts);
108
119
  });
109
120
  program
@@ -116,6 +127,7 @@ program
116
127
  .option('--from-version <number>', 'Base version number (auto-detected from local files if not provided)')
117
128
  .action(async (opts) => {
118
129
  await requireLogin('save');
130
+ await setSentryContextForCommand();
119
131
  await runSave({
120
132
  id: opts.id,
121
133
  label: opts.label,
@@ -136,6 +148,7 @@ program
136
148
  .option('-i, --id <id>', 'Embeddable ID (will prompt if not provided)')
137
149
  .action(async (opts) => {
138
150
  await requireLogin('branch');
151
+ await setSentryContextForCommand();
139
152
  await runBranch(opts);
140
153
  });
141
154
  const experiments = program.command('experiments').description('Manage embeddable experiments');
@@ -147,6 +160,7 @@ experiments
147
160
  .option('--experiment-key <key>', 'Experiment key (required if --experiment-id is set)')
148
161
  .action(async (opts) => {
149
162
  await requireLogin('experiments connect');
163
+ await setSentryContextForCommand();
150
164
  await runExperimentsConnect({
151
165
  id: opts.id,
152
166
  experimentId: opts.experimentId,
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/commands/branch.ts"],"names":[],"mappings":"AAYA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,iBAoEpD"}
@@ -3,6 +3,7 @@ import { isLoggedIn } from '../auth/index.js';
3
3
  import { runPull } from './pull.js';
4
4
  import { promptForLocalEmbeddable, fetchBranches, promptForBranch } from '../prompts/index.js';
5
5
  import { createLogger, exit } from '../logger.js';
6
+ import { getSentryContextFromProjectConfig, setSentryContext, } from '../sentry-context.js';
6
7
  import * as stdout from '../stdout.js';
7
8
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
8
9
  export async function runBranch(opts) {
@@ -20,6 +21,7 @@ export async function runBranch(opts) {
20
21
  if (inferred && !opts.id && embeddableId) {
21
22
  process.chdir(inferred.projectRoot);
22
23
  }
24
+ setSentryContext(getSentryContextFromProjectConfig());
23
25
  if (!embeddableId) {
24
26
  const selected = await promptForLocalEmbeddable();
25
27
  if (!selected) {
@@ -28,6 +30,9 @@ export async function runBranch(opts) {
28
30
  }
29
31
  embeddableId = selected;
30
32
  }
33
+ if (embeddableId) {
34
+ setSentryContext({ embeddable: { id: embeddableId } });
35
+ }
31
36
  stdout.print('');
32
37
  stdout.print(pc.cyan('Fetching branches...'));
33
38
  // Fetch branches for this embeddable
@@ -49,6 +54,9 @@ export async function runBranch(opts) {
49
54
  await runPull({ id: embeddableId, useMain: true });
50
55
  }
51
56
  else {
57
+ setSentryContext({
58
+ branch: { id: selectedBranch.id, name: selectedBranch.name },
59
+ });
52
60
  stdout.print(pc.cyan(`Switching to branch: ${selectedBranch.name}...`));
53
61
  stdout.print('');
54
62
  await runPull({
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAaA,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,iBAoDA"}
@@ -3,6 +3,7 @@ import { compileAllPages } from '../compiler/index.js';
3
3
  import { formatError } from '../compiler/errors.js';
4
4
  import { promptForLocalEmbeddable } from '../prompts/index.js';
5
5
  import { captureException, createLogger, exit } from '../logger.js';
6
+ import { getSentryContextFromEmbeddableConfig, getSentryContextFromProjectConfig, setSentryContext, } from '../sentry-context.js';
6
7
  import * as stdout from '../stdout.js';
7
8
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
8
9
  export async function runBuild(opts) {
@@ -12,6 +13,7 @@ export async function runBuild(opts) {
12
13
  if (inferred && !opts.id && embeddableId) {
13
14
  process.chdir(inferred.projectRoot);
14
15
  }
16
+ setSentryContext(getSentryContextFromProjectConfig());
15
17
  if (!embeddableId) {
16
18
  const selected = await promptForLocalEmbeddable({
17
19
  message: 'Select an embeddable to build:',
@@ -23,7 +25,13 @@ export async function runBuild(opts) {
23
25
  }
24
26
  embeddableId = selected;
25
27
  }
26
- logger.info('build started', { embeddableId });
28
+ if (embeddableId) {
29
+ setSentryContext({
30
+ embeddable: { id: embeddableId },
31
+ ...getSentryContextFromEmbeddableConfig(embeddableId),
32
+ });
33
+ }
34
+ logger.info('build started');
27
35
  const pagesGlob = opts.pages || `embeddables/${embeddableId}/pages/**/*.page.tsx`;
28
36
  const outPath = opts.out || path.join('embeddables', embeddableId, '.generated', 'embeddable.json');
29
37
  const stylesDir = path.join('embeddables', embeddableId, 'styles');
@@ -40,8 +48,8 @@ export async function runBuild(opts) {
40
48
  catch (e) {
41
49
  captureException(e);
42
50
  stdout.error(formatError(e));
43
- logger.error('build failed', { embeddableId });
51
+ logger.error('build failed');
44
52
  await exit(1);
45
53
  }
46
- logger.info('build complete', { embeddableId });
54
+ logger.info('build complete');
47
55
  }
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AA2HA,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,iBAyIA"}
@@ -8,6 +8,7 @@ import { compileAllPages } from '../compiler/index.js';
8
8
  import { startProxyServer } from '../proxy/server.js';
9
9
  import { formatError } from '../compiler/errors.js';
10
10
  import { captureException, createLogger, exit } from '../logger.js';
11
+ import { getSentryContextFromEmbeddableConfig, getSentryContextFromProjectConfig, setSentryContext, } from '../sentry-context.js';
11
12
  import * as stdout from '../stdout.js';
12
13
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
13
14
  /**
@@ -101,6 +102,7 @@ export async function runDev(opts) {
101
102
  if (inferred && !opts.id && embeddableId) {
102
103
  process.chdir(inferred.projectRoot);
103
104
  }
105
+ setSentryContext(getSentryContextFromProjectConfig());
104
106
  if (!embeddableId) {
105
107
  const selected = await promptForEmbeddable();
106
108
  if (!selected) {
@@ -110,7 +112,13 @@ export async function runDev(opts) {
110
112
  }
111
113
  embeddableId = selected;
112
114
  }
113
- logger.info('dev started', { embeddableId, engine: opts.engine, port: opts.port });
115
+ if (embeddableId) {
116
+ setSentryContext({
117
+ embeddable: { id: embeddableId },
118
+ ...getSentryContextFromEmbeddableConfig(embeddableId),
119
+ });
120
+ }
121
+ logger.info('dev started', { engine: opts.engine, port: opts.port });
114
122
  const pagesGlob = opts.pages || `embeddables/${embeddableId}/pages/**/*.page.tsx`;
115
123
  const outPath = opts.out || path.join('embeddables', embeddableId, '.generated', 'embeddable.json');
116
124
  const stylesDir = path.join('embeddables', embeddableId, 'styles');
@@ -135,7 +143,7 @@ export async function runDev(opts) {
135
143
  catch (e) {
136
144
  captureException(e);
137
145
  stdout.error(formatError(e));
138
- logger.error('initial build failed', { embeddableId });
146
+ logger.error('initial build failed');
139
147
  await exit(1);
140
148
  }
141
149
  // Start proxy — find an available port if the requested one is taken
@@ -163,7 +171,7 @@ export async function runDev(opts) {
163
171
  embeddableId,
164
172
  watchWorkbench: true,
165
173
  });
166
- logger.info('dev server running', { embeddableId, port });
174
+ logger.info('dev server running', { port });
167
175
  // Watch all source files: pages, styles, config, global components, computed fields, and actions
168
176
  const watcher = chokidar.watch([pagesGlob, stylesGlob, configPath, globalComponentsGlob, computedFieldsGlob, actionsGlob], {
169
177
  ignoreInitial: true,
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"experiments-connect.d.ts","sourceRoot":"","sources":["../../src/commands/experiments-connect.ts"],"names":[],"mappings":"AAoBA,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"}
@@ -5,6 +5,7 @@ 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
7
  import { createLogger, exit } from '../logger.js';
8
+ import { getSentryContextFromEmbeddableConfig, getSentryContextFromProjectConfig, setSentryContext, } from '../sentry-context.js';
8
9
  import * as stdout from '../stdout.js';
9
10
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
10
11
  export async function runExperimentsConnect(opts) {
@@ -59,6 +60,7 @@ async function runExperimentsConnectInner(opts) {
59
60
  if (inferred && !opts.id && embeddableId) {
60
61
  process.chdir(inferred.projectRoot);
61
62
  }
63
+ setSentryContext(getSentryContextFromProjectConfig());
62
64
  if (!embeddableId) {
63
65
  const selected = await promptForLocalEmbeddable({
64
66
  message: 'Select an embeddable to connect the experiment to:',
@@ -70,6 +72,12 @@ async function runExperimentsConnectInner(opts) {
70
72
  embeddableId = selected;
71
73
  stdout.print('');
72
74
  }
75
+ if (embeddableId) {
76
+ setSentryContext({
77
+ embeddable: { id: embeddableId },
78
+ ...getSentryContextFromEmbeddableConfig(embeddableId),
79
+ });
80
+ }
73
81
  // 5. Get experiment_id and experiment_key (from opts or interactive prompt)
74
82
  let experimentId = opts.experimentId;
75
83
  let experimentKey = opts.experimentKey;
@@ -1 +1 @@
1
- {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAkHA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iHAAiH;IACjH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,iBA8QjD"}
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"AAuHA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iHAAiH;IACjH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,iBAgSjD"}
@@ -7,6 +7,7 @@ import { getAccessToken, isLoggedIn } from '../auth/index.js';
7
7
  import { getProjectId, writeProjectConfig } from '../config/index.js';
8
8
  import { promptForProject, promptForEmbeddable, fetchEmbeddableMetadata } from '../prompts/index.js';
9
9
  import { captureException, createLogger, exit } from '../logger.js';
10
+ import { getSentryContextFromEmbeddableConfig, getSentryContextFromProjectConfig, setSentryContext, } from '../sentry-context.js';
10
11
  import * as stdout from '../stdout.js';
11
12
  import { inferEmbeddableFromCwd } from '../helpers/utils.js';
12
13
  /**
@@ -117,6 +118,8 @@ export async function runPull(opts) {
117
118
  if (inferred && !opts.id && embeddableId) {
118
119
  process.chdir(inferred.projectRoot);
119
120
  }
121
+ // Re-apply project/org from config after chdir so embeddables.json in project root is used
122
+ setSentryContext(getSentryContextFromProjectConfig());
120
123
  // If no ID provided, try to get it interactively
121
124
  if (!embeddableId) {
122
125
  if (!isLoggedIn()) {
@@ -162,6 +165,12 @@ export async function runPull(opts) {
162
165
  embeddableId = selected;
163
166
  stdout.print('');
164
167
  }
168
+ if (embeddableId) {
169
+ setSentryContext({
170
+ embeddable: { id: embeddableId },
171
+ ...getSentryContextFromEmbeddableConfig(embeddableId),
172
+ });
173
+ }
165
174
  // When useMain, pull main and ignore/clear saved branch. Otherwise stay on current branch when no --branch.
166
175
  const currentFromConfig = opts.useMain || opts.branch != null ? null : getCurrentBranchFromConfig(embeddableId);
167
176
  const effectiveBranch = opts.useMain ? undefined : (opts.branch ?? currentFromConfig?.branchId);
@@ -176,7 +185,10 @@ export async function runPull(opts) {
176
185
  ? `${effectiveBranchName} (${effectiveBranch})`
177
186
  : effectiveBranch
178
187
  : 'main';
179
- logger.info('pull started', { embeddableId, branch: effectiveBranch ?? 'main' });
188
+ if (effectiveBranch) {
189
+ setSentryContext({ branch: { id: effectiveBranch, name: effectiveBranchName ?? null } });
190
+ }
191
+ logger.info('pull started');
180
192
  stdout.print(pc.cyan(`Pulling branch: ${pc.bold(branchLabel)}`));
181
193
  stdout.print(`Fetching embeddable from ${url}...`);
182
194
  let pullVersion;
@@ -227,6 +239,11 @@ export async function runPull(opts) {
227
239
  fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf8');
228
240
  stdout.print(pc.cyan(`✓ Wrote flow metadata to ${metadataPath}`));
229
241
  }
242
+ const versionForSentry = pullVersion != null && !isNaN(Number(pullVersion)) ? Number(pullVersion) : undefined;
243
+ setSentryContext({
244
+ embeddable: { id: embeddableId, title: metadata?.title ?? null },
245
+ versionNumber: versionForSentry,
246
+ });
230
247
  // Clear existing pages, styles, computed-fields, actions, and global-components before generating new ones
231
248
  const pagesDir = path.join('embeddables', embeddableId, 'pages');
232
249
  const stylesDir = path.join('embeddables', embeddableId, 'styles');
@@ -334,7 +351,7 @@ export async function runPull(opts) {
334
351
  stdout.print(pc.cyan(`✓ Wrote versioned embeddable JSON to ${versionedPath} (with fixes applied)`));
335
352
  }
336
353
  }
337
- logger.info('pull complete', { embeddableId, version: String(pullVersion ?? 'unknown') });
354
+ logger.info('pull complete', { version: String(pullVersion ?? 'unknown') });
338
355
  }
339
356
  catch (error) {
340
357
  captureException(error);
@@ -1 +1 @@
1
- {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/commands/save.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,qBAAqB,CAAA;AAkC5B,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,CAAA;AAE3B,MAAM,MAAM,2BAA2B,GAAG;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,uBAAuB,CAAC,EAAE,sBAAsB,EAAE,CAAA;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,WAAW,CAAC,EAAE,cAAc,EAAE,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,CAAA;AAkND,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAClC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,iBAqBA"}
1
+ {"version":3,"file":"save.d.ts","sourceRoot":"","sources":["../../src/commands/save.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,qBAAqB,CAAA;AAuC5B,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,CAAA;AAE3B,MAAM,MAAM,2BAA2B,GAAG;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iBAAiB,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,uBAAuB,CAAC,EAAE,sBAAsB,EAAE,CAAA;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,WAAW,CAAC,EAAE,cAAc,EAAE,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,CAAA;AAkND,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAClC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,iBAqBA"}
@@ -9,6 +9,7 @@ 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
11
  import { captureException, createLogger, exit } from '../logger.js';
12
+ import { getSentryContextFromEmbeddableConfig, getSentryContextFromProjectConfig, setSentryContext, } from '../sentry-context.js';
12
13
  import * as stdout from '../stdout.js';
13
14
  import { translateJsonDiffToEditCommands } from '../helpers/json.js';
14
15
  import { generateId, inferEmbeddableFromCwd } from '../helpers/utils.js';
@@ -239,6 +240,7 @@ async function runSaveInner(opts) {
239
240
  if (inferred && !opts.id && embeddableId) {
240
241
  process.chdir(inferred.projectRoot);
241
242
  }
243
+ setSentryContext(getSentryContextFromProjectConfig());
242
244
  if (!embeddableId) {
243
245
  const selected = await promptForLocalEmbeddable({
244
246
  message: 'Select an embeddable to save:',
@@ -250,9 +252,16 @@ async function runSaveInner(opts) {
250
252
  embeddableId = selected;
251
253
  stdout.print('');
252
254
  }
255
+ if (embeddableId) {
256
+ const embeddableCtx = getSentryContextFromEmbeddableConfig(embeddableId);
257
+ setSentryContext({
258
+ embeddable: { id: embeddableId },
259
+ ...embeddableCtx,
260
+ });
261
+ }
253
262
  // Resolve branch: explicit -b flag wins, otherwise use current branch from config (set by `embeddables branch`)
254
263
  const effectiveBranch = opts.branch ?? getBranchFromConfig(embeddableId) ?? undefined;
255
- logger.info('save started', { embeddableId, branch: effectiveBranch, fromVersion: opts.fromVersion });
264
+ logger.info('save started', { fromVersion: opts.fromVersion });
256
265
  // 4. Get project ID (from config or interactive prompt)
257
266
  let projectId = getProjectId();
258
267
  if (!projectId) {
@@ -293,7 +302,7 @@ async function runSaveInner(opts) {
293
302
  catch (e) {
294
303
  captureException(e);
295
304
  stdout.error(formatError(e));
296
- logger.error('build failed', { embeddableId });
305
+ logger.error('build failed');
297
306
  await exit(1);
298
307
  return;
299
308
  }
@@ -351,6 +360,18 @@ async function runSaveInner(opts) {
351
360
  }
352
361
  fromVersionNumber = detectedVersion;
353
362
  }
363
+ const embeddableCtxForBranch = getSentryContextFromEmbeddableConfig(embeddableId);
364
+ setSentryContext({
365
+ versionNumber: fromVersionNumber,
366
+ branch: effectiveBranch ?
367
+ {
368
+ id: effectiveBranch,
369
+ name: embeddableCtxForBranch.branch?.id === effectiveBranch
370
+ ? embeddableCtxForBranch.branch?.name ?? null
371
+ : null,
372
+ }
373
+ : undefined,
374
+ });
354
375
  // 7b. Check for other users' drafts on this version; warn and optionally abort
355
376
  let currentUserId = null;
356
377
  const supabase = await getAuthenticatedSupabaseClient();
@@ -517,7 +538,7 @@ async function runSaveInner(opts) {
517
538
  }
518
539
  const { newVersionNumber } = forceResult.data;
519
540
  stdout.print(pc.green(`✓ Saved as version ${newVersionNumber}`));
520
- logger.info('save complete', { embeddableId, newVersionNumber, forced: true });
541
+ logger.info('save complete', { newVersionNumber, forced: true });
521
542
  setVersionInConfig(embeddableId, newVersionNumber);
522
543
  const branchSlug = getBranchSlugFromConfig(embeddableId);
523
544
  const versionedPath = path.join(generatedDir, `embeddable-${branchSlug}@${newVersionNumber}.json`);
@@ -536,7 +557,7 @@ async function runSaveInner(opts) {
536
557
  }
537
558
  const { newVersionNumber } = result.data;
538
559
  stdout.print(pc.green(`✓ Saved as version ${newVersionNumber}`));
539
- logger.info('save complete', { embeddableId, newVersionNumber });
560
+ logger.info('save complete', { newVersionNumber });
540
561
  // Update _version in config.json so future saves know the base version
541
562
  setVersionInConfig(embeddableId, newVersionNumber);
542
563
  // Also save the versioned file to .generated/ as a snapshot (embeddable-{branch}@{version}.json)
@@ -0,0 +1,48 @@
1
+ export interface SentryContextUser {
2
+ id: string;
3
+ email?: string | null;
4
+ }
5
+ export interface SentryContextProject {
6
+ id: string;
7
+ title?: string | null;
8
+ }
9
+ export interface SentryContextOrg {
10
+ id: string;
11
+ title?: string | null;
12
+ }
13
+ export interface SentryContextEmbeddable {
14
+ id: string;
15
+ title?: string | null;
16
+ }
17
+ export interface SentryContextBranch {
18
+ id: string;
19
+ name?: string | null;
20
+ }
21
+ export interface SentryContextInput {
22
+ user?: SentryContextUser | null;
23
+ project?: SentryContextProject | null;
24
+ org?: SentryContextOrg | null;
25
+ embeddable?: SentryContextEmbeddable | null;
26
+ branch?: SentryContextBranch | null;
27
+ versionNumber?: number | null;
28
+ }
29
+ /**
30
+ * Set Sentry scope with the given context. Only sets keys that are present and have valid values.
31
+ * Uses setUser/setContext for error events and setAttributes on the current scope so values
32
+ * (project, org, embeddable, branch, versionNumber) are included in log events.
33
+ */
34
+ export declare function setSentryContext(ctx: SentryContextInput): void;
35
+ /**
36
+ * Return project and org context from embeddables.json when present.
37
+ */
38
+ export declare function getSentryContextFromProjectConfig(): Partial<SentryContextInput>;
39
+ /**
40
+ * Return embeddable title (from metadata.json), branch and versionNumber (from config.json)
41
+ * for embeddables/{embeddableId}/ when present.
42
+ */
43
+ export declare function getSentryContextFromEmbeddableConfig(embeddableId: string): Partial<SentryContextInput>;
44
+ /**
45
+ * Set Sentry user from auth when logged in. Call after requireLogin for commands that need user context.
46
+ */
47
+ export declare function setSentryUserFromAuth(): Promise<void>;
48
+ //# sourceMappingURL=sentry-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentry-context.d.ts","sourceRoot":"","sources":["../src/sentry-context.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAA;IAC/B,OAAO,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAA;IACrC,GAAG,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAC7B,UAAU,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAA;IAC3C,MAAM,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACnC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,CA4D9D;AAED;;GAEG;AACH,wBAAgB,iCAAiC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAW/E;AAED;;;GAGG;AACH,wBAAgB,oCAAoC,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA6CtG;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAY3D"}
@@ -0,0 +1,156 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import * as Sentry from '@sentry/node';
4
+ import { readProjectConfig } from './config/index.js';
5
+ /**
6
+ * Set Sentry scope with the given context. Only sets keys that are present and have valid values.
7
+ * Uses setUser/setContext for error events and setAttributes on the current scope so values
8
+ * (project, org, embeddable, branch, versionNumber) are included in log events.
9
+ */
10
+ export function setSentryContext(ctx) {
11
+ if (ctx.user?.id) {
12
+ Sentry.setUser({
13
+ id: ctx.user.id,
14
+ email: ctx.user.email ?? undefined,
15
+ });
16
+ }
17
+ if (ctx.project?.id) {
18
+ Sentry.setContext('project', {
19
+ id: ctx.project.id,
20
+ title: ctx.project.title ?? undefined,
21
+ });
22
+ }
23
+ if (ctx.org?.id) {
24
+ Sentry.setContext('org', {
25
+ id: ctx.org.id,
26
+ title: ctx.org.title ?? undefined,
27
+ });
28
+ }
29
+ if (ctx.embeddable?.id) {
30
+ Sentry.setContext('embeddable', {
31
+ id: ctx.embeddable.id,
32
+ title: ctx.embeddable.title ?? undefined,
33
+ });
34
+ }
35
+ if (ctx.branch?.id) {
36
+ Sentry.setContext('branch', {
37
+ id: ctx.branch.id,
38
+ name: ctx.branch.name ?? undefined,
39
+ });
40
+ }
41
+ if (ctx.versionNumber != null && !isNaN(ctx.versionNumber)) {
42
+ Sentry.setContext('version', { versionNumber: ctx.versionNumber });
43
+ }
44
+ // Scope attributes are included in log events; only string/number/boolean are supported
45
+ const scope = Sentry.getCurrentScope();
46
+ const attrs = {};
47
+ if (ctx.project?.id) {
48
+ attrs.projectId = ctx.project.id;
49
+ if (ctx.project.title != null)
50
+ attrs.projectTitle = String(ctx.project.title);
51
+ }
52
+ if (ctx.org?.id) {
53
+ attrs.orgId = ctx.org.id;
54
+ if (ctx.org.title != null)
55
+ attrs.orgTitle = String(ctx.org.title);
56
+ }
57
+ if (ctx.embeddable?.id) {
58
+ attrs.embeddableId = ctx.embeddable.id;
59
+ if (ctx.embeddable.title != null)
60
+ attrs.embeddableTitle = String(ctx.embeddable.title);
61
+ }
62
+ if (ctx.branch?.id) {
63
+ attrs.branchId = ctx.branch.id;
64
+ if (ctx.branch.name != null)
65
+ attrs.branchName = String(ctx.branch.name);
66
+ }
67
+ if (ctx.versionNumber != null && !isNaN(ctx.versionNumber)) {
68
+ attrs.versionNumber = ctx.versionNumber;
69
+ }
70
+ if (Object.keys(attrs).length > 0) {
71
+ scope.setAttributes(attrs);
72
+ }
73
+ }
74
+ /**
75
+ * Return project and org context from embeddables.json when present.
76
+ */
77
+ export function getSentryContextFromProjectConfig() {
78
+ const config = readProjectConfig();
79
+ if (!config)
80
+ return {};
81
+ const out = {};
82
+ if (config.project_id) {
83
+ out.project = { id: config.project_id, title: config.project_name ?? null };
84
+ }
85
+ if (config.org_id) {
86
+ out.org = { id: config.org_id, title: config.org_title ?? null };
87
+ }
88
+ return out;
89
+ }
90
+ /**
91
+ * Return embeddable title (from metadata.json), branch and versionNumber (from config.json)
92
+ * for embeddables/{embeddableId}/ when present.
93
+ */
94
+ export function getSentryContextFromEmbeddableConfig(embeddableId) {
95
+ const out = {};
96
+ const embeddableDir = path.join('embeddables', embeddableId);
97
+ // Embeddable title from metadata.json (written by pull)
98
+ const metadataPath = path.join(embeddableDir, 'metadata.json');
99
+ if (fs.existsSync(metadataPath)) {
100
+ try {
101
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
102
+ const title = metadata.title;
103
+ if (typeof title === 'string' && title) {
104
+ out.embeddable = { id: embeddableId, title };
105
+ }
106
+ }
107
+ catch {
108
+ // ignore
109
+ }
110
+ }
111
+ if (!out.embeddable) {
112
+ out.embeddable = { id: embeddableId };
113
+ }
114
+ const configPath = path.join(embeddableDir, 'config.json');
115
+ if (!fs.existsSync(configPath))
116
+ return out;
117
+ try {
118
+ const raw = fs.readFileSync(configPath, 'utf8');
119
+ const config = JSON.parse(raw);
120
+ const branchId = config._branch_id;
121
+ if (typeof branchId === 'string' && branchId) {
122
+ const branchName = config._branch_name;
123
+ out.branch = {
124
+ id: branchId,
125
+ name: typeof branchName === 'string' ? branchName : null,
126
+ };
127
+ }
128
+ else if (config._version != null) {
129
+ // Config was written by pull; no _branch_id means we're on main
130
+ out.branch = { id: 'main', name: 'main' };
131
+ }
132
+ const ver = config._version;
133
+ if (typeof ver === 'number' && !isNaN(ver)) {
134
+ out.versionNumber = ver;
135
+ }
136
+ return out;
137
+ }
138
+ catch {
139
+ return out;
140
+ }
141
+ }
142
+ /**
143
+ * Set Sentry user from auth when logged in. Call after requireLogin for commands that need user context.
144
+ */
145
+ export async function setSentryUserFromAuth() {
146
+ const { getAuthenticatedSupabaseClient } = await import('./auth/index.js');
147
+ const supabase = await getAuthenticatedSupabaseClient();
148
+ if (!supabase)
149
+ return;
150
+ const { data: { user }, } = await supabase.auth.getUser();
151
+ if (user?.id) {
152
+ setSentryContext({
153
+ user: { id: user.id, email: user.email ?? null },
154
+ });
155
+ }
156
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embeddables/cli",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "embeddables": "./bin/embeddables.mjs"