@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 +14 -0
- package/dist/commands/branch.d.ts.map +1 -1
- package/dist/commands/branch.js +8 -0
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +11 -3
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +11 -3
- package/dist/commands/experiments-connect.d.ts.map +1 -1
- package/dist/commands/experiments-connect.js +8 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +19 -2
- package/dist/commands/save.d.ts.map +1 -1
- package/dist/commands/save.js +25 -4
- package/dist/sentry-context.d.ts +48 -0
- package/dist/sentry-context.d.ts.map +1 -0
- package/dist/sentry-context.js +156 -0
- package/package.json +1 -1
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":"
|
|
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"}
|
package/dist/commands/branch.js
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/commands/build.js
CHANGED
|
@@ -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
|
-
|
|
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'
|
|
51
|
+
logger.error('build failed');
|
|
44
52
|
await exit(1);
|
|
45
53
|
}
|
|
46
|
-
logger.info('build complete'
|
|
54
|
+
logger.info('build complete');
|
|
47
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/commands/dev.js
CHANGED
|
@@ -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
|
-
|
|
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'
|
|
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', {
|
|
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":"
|
|
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":"
|
|
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"}
|
package/dist/commands/pull.js
CHANGED
|
@@ -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
|
-
|
|
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', {
|
|
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;
|
|
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"}
|
package/dist/commands/save.js
CHANGED
|
@@ -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', {
|
|
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'
|
|
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', {
|
|
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', {
|
|
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
|
+
}
|