@embeddables/cli 0.7.7 → 0.7.9
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 +19 -1
- 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 +2 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +38 -7
- package/dist/commands/save.d.ts.map +1 -1
- package/dist/commands/save.js +25 -4
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +1 -0
- package/dist/prompts/versions.d.ts +18 -0
- package/dist/prompts/versions.d.ts.map +1 -0
- package/dist/prompts/versions.js +99 -0
- 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';
|
|
@@ -99,12 +109,17 @@ program
|
|
|
99
109
|
.description('Pull an embeddable from the cloud')
|
|
100
110
|
.option('-i, --id <id>', 'Embeddable ID to pull (interactive selection if not provided)')
|
|
101
111
|
.option('-o, --out <path>', 'Output json path')
|
|
112
|
+
.option('--version <version>', 'Version to pull (number or "latest"); if omitted, choose from 100 most recent')
|
|
102
113
|
.option('-b, --branch <branch_id>', 'Embeddable branch ID')
|
|
103
114
|
.option('-f, --fix', 'Fix by removing components missing required props (warn instead of error)')
|
|
104
115
|
.option('-p, --preserve', 'Preserve component order in config for forward compile')
|
|
105
116
|
.action(async (opts) => {
|
|
106
117
|
await requireLogin('pull');
|
|
107
|
-
await
|
|
118
|
+
await setSentryContextForCommand();
|
|
119
|
+
await runPull({
|
|
120
|
+
...opts,
|
|
121
|
+
version: opts.version,
|
|
122
|
+
});
|
|
108
123
|
});
|
|
109
124
|
program
|
|
110
125
|
.command('save')
|
|
@@ -116,6 +131,7 @@ program
|
|
|
116
131
|
.option('--from-version <number>', 'Base version number (auto-detected from local files if not provided)')
|
|
117
132
|
.action(async (opts) => {
|
|
118
133
|
await requireLogin('save');
|
|
134
|
+
await setSentryContextForCommand();
|
|
119
135
|
await runSave({
|
|
120
136
|
id: opts.id,
|
|
121
137
|
label: opts.label,
|
|
@@ -136,6 +152,7 @@ program
|
|
|
136
152
|
.option('-i, --id <id>', 'Embeddable ID (will prompt if not provided)')
|
|
137
153
|
.action(async (opts) => {
|
|
138
154
|
await requireLogin('branch');
|
|
155
|
+
await setSentryContextForCommand();
|
|
139
156
|
await runBranch(opts);
|
|
140
157
|
});
|
|
141
158
|
const experiments = program.command('experiments').description('Manage embeddable experiments');
|
|
@@ -147,6 +164,7 @@ experiments
|
|
|
147
164
|
.option('--experiment-key <key>', 'Experiment key (required if --experiment-id is set)')
|
|
148
165
|
.action(async (opts) => {
|
|
149
166
|
await requireLogin('experiments connect');
|
|
167
|
+
await setSentryContextForCommand();
|
|
150
168
|
await runExperimentsConnect({
|
|
151
169
|
id: opts.id,
|
|
152
170
|
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;
|
package/dist/commands/pull.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export type RunPullOptions = {
|
|
|
3
3
|
out?: string;
|
|
4
4
|
branch?: string;
|
|
5
5
|
branchName?: string;
|
|
6
|
+
/** Version to pull (number or "latest"). If omitted and embeddable is known, prompt from 100 most recent. */
|
|
7
|
+
version?: string | number;
|
|
6
8
|
/** When true, pull main and clear saved branch (e.g. when user explicitly selects "main" in branch switcher). */
|
|
7
9
|
useMain?: boolean;
|
|
8
10
|
fix?: boolean;
|
|
@@ -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":"AA6HA,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,6GAA6G;IAC7G,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,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,iBA+SjD"}
|
package/dist/commands/pull.js
CHANGED
|
@@ -5,8 +5,9 @@ import prompts from 'prompts';
|
|
|
5
5
|
import { reverseCompile } from '../compiler/reverse.js';
|
|
6
6
|
import { getAccessToken, isLoggedIn } from '../auth/index.js';
|
|
7
7
|
import { getProjectId, writeProjectConfig } from '../config/index.js';
|
|
8
|
-
import { promptForProject, promptForEmbeddable, fetchEmbeddableMetadata } from '../prompts/index.js';
|
|
8
|
+
import { promptForProject, promptForEmbeddable, fetchEmbeddableMetadata, fetchRecentVersions, promptForVersion, } 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,21 +165,44 @@ 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);
|
|
168
177
|
const effectiveBranchName = opts.useMain ? undefined : (opts.branchName ?? currentFromConfig?.branchName);
|
|
169
|
-
let url = `https://engine.embeddables.com/${embeddableId}?version=latest`;
|
|
170
|
-
if (effectiveBranch) {
|
|
171
|
-
url += `&embeddable_branch=${effectiveBranch}`;
|
|
172
|
-
}
|
|
173
178
|
const outPath = opts.out || path.join('embeddables', embeddableId, '.generated', 'embeddable.json');
|
|
174
179
|
const branchLabel = effectiveBranch
|
|
175
180
|
? effectiveBranchName
|
|
176
181
|
? `${effectiveBranchName} (${effectiveBranch})`
|
|
177
182
|
: effectiveBranch
|
|
178
183
|
: 'main';
|
|
179
|
-
|
|
184
|
+
if (effectiveBranch) {
|
|
185
|
+
setSentryContext({ branch: { id: effectiveBranch, name: effectiveBranchName ?? null } });
|
|
186
|
+
}
|
|
187
|
+
// Resolve version: explicit --version, or interactive choice from 100 most recent
|
|
188
|
+
let versionParam;
|
|
189
|
+
if (opts.version !== undefined && opts.version !== '') {
|
|
190
|
+
const v = String(opts.version).toLowerCase();
|
|
191
|
+
versionParam = v === 'latest' ? 'latest' : v;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
stdout.print(pc.cyan('Fetching recent versions...'));
|
|
195
|
+
const recentVersions = await fetchRecentVersions(embeddableId, effectiveBranch ?? null);
|
|
196
|
+
const selected = await promptForVersion(recentVersions, {
|
|
197
|
+
message: 'Select a version to pull:',
|
|
198
|
+
});
|
|
199
|
+
versionParam = selected === null ? 'latest' : String(selected);
|
|
200
|
+
stdout.print('');
|
|
201
|
+
}
|
|
202
|
+
let url = `https://engine.embeddables.com/${embeddableId}?version=${versionParam}`;
|
|
203
|
+
if (effectiveBranch)
|
|
204
|
+
url += `&embeddable_branch=${effectiveBranch}`;
|
|
205
|
+
logger.info('pull started');
|
|
180
206
|
stdout.print(pc.cyan(`Pulling branch: ${pc.bold(branchLabel)}`));
|
|
181
207
|
stdout.print(`Fetching embeddable from ${url}...`);
|
|
182
208
|
let pullVersion;
|
|
@@ -227,6 +253,11 @@ export async function runPull(opts) {
|
|
|
227
253
|
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n', 'utf8');
|
|
228
254
|
stdout.print(pc.cyan(`✓ Wrote flow metadata to ${metadataPath}`));
|
|
229
255
|
}
|
|
256
|
+
const versionForSentry = pullVersion != null && !isNaN(Number(pullVersion)) ? Number(pullVersion) : undefined;
|
|
257
|
+
setSentryContext({
|
|
258
|
+
embeddable: { id: embeddableId, title: metadata?.title ?? null },
|
|
259
|
+
versionNumber: versionForSentry,
|
|
260
|
+
});
|
|
230
261
|
// Clear existing pages, styles, computed-fields, actions, and global-components before generating new ones
|
|
231
262
|
const pagesDir = path.join('embeddables', embeddableId, 'pages');
|
|
232
263
|
const stylesDir = path.join('embeddables', embeddableId, 'styles');
|
|
@@ -334,7 +365,7 @@ export async function runPull(opts) {
|
|
|
334
365
|
stdout.print(pc.cyan(`✓ Wrote versioned embeddable JSON to ${versionedPath} (with fixes applied)`));
|
|
335
366
|
}
|
|
336
367
|
}
|
|
337
|
-
logger.info('pull complete', {
|
|
368
|
+
logger.info('pull complete', { version: String(pullVersion ?? 'unknown') });
|
|
338
369
|
}
|
|
339
370
|
catch (error) {
|
|
340
371
|
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)
|
package/dist/prompts/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { fetchProjectEmbeddables, fetchEmbeddableMetadata, promptForEmbeddable,
|
|
|
4
4
|
export type { EmbeddableInfo, EmbeddableMetadata, LocalEmbeddable, PromptForEmbeddableOptions, } from './embeddables.js';
|
|
5
5
|
export { fetchBranches, promptForBranch } from './branches.js';
|
|
6
6
|
export type { BranchInfo } from './branches.js';
|
|
7
|
+
export { fetchRecentVersions, promptForVersion } from './versions.js';
|
|
8
|
+
export type { VersionInfo } from './versions.js';
|
|
7
9
|
export { fetchProjectExperiments, promptForExperiment, } from './experiments.js';
|
|
8
10
|
export type { ExperimentInfo, PromptForExperimentOptions } from './experiments.js';
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAC/D,YAAY,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAEzE,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,0BAA0B,GAC3B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE/C,OAAO,EACL,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAC/D,YAAY,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAEzE,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,0BAA0B,GAC3B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE/C,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,EACL,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/prompts/index.js
CHANGED
|
@@ -2,4 +2,5 @@
|
|
|
2
2
|
export { fetchProjects, promptForProject } from './projects.js';
|
|
3
3
|
export { fetchProjectEmbeddables, fetchEmbeddableMetadata, promptForEmbeddable, discoverLocalEmbeddables, promptForLocalEmbeddable, } from './embeddables.js';
|
|
4
4
|
export { fetchBranches, promptForBranch } from './branches.js';
|
|
5
|
+
export { fetchRecentVersions, promptForVersion } from './versions.js';
|
|
5
6
|
export { fetchProjectExperiments, promptForExperiment, } from './experiments.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface VersionInfo {
|
|
2
|
+
version_number: number;
|
|
3
|
+
created_at: string | null;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Fetch up to 100 most recent version numbers for an embeddable (flow).
|
|
7
|
+
* Uses flow_versions for the given flow_id and branch (null = main).
|
|
8
|
+
* Returns unique version numbers in descending order.
|
|
9
|
+
*/
|
|
10
|
+
export declare function fetchRecentVersions(flowId: string, branchId: string | null): Promise<VersionInfo[]>;
|
|
11
|
+
/**
|
|
12
|
+
* Prompt the user to select a version from the list.
|
|
13
|
+
* Returns the selected version number, or null for "Latest".
|
|
14
|
+
*/
|
|
15
|
+
export declare function promptForVersion(versions: VersionInfo[], options?: {
|
|
16
|
+
message?: string;
|
|
17
|
+
}): Promise<number | null>;
|
|
18
|
+
//# sourceMappingURL=versions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../../src/prompts/versions.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAID;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,WAAW,EAAE,CAAC,CA8CxB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkDxB"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import prompts from 'prompts';
|
|
3
|
+
import { getAuthenticatedSupabaseClient } from '../auth/index.js';
|
|
4
|
+
import * as stdout from '../stdout.js';
|
|
5
|
+
import { formatDate } from '../helpers/dates.js';
|
|
6
|
+
const RECENT_VERSIONS_LIMIT = 100;
|
|
7
|
+
/**
|
|
8
|
+
* Fetch up to 100 most recent version numbers for an embeddable (flow).
|
|
9
|
+
* Uses flow_versions for the given flow_id and branch (null = main).
|
|
10
|
+
* Returns unique version numbers in descending order.
|
|
11
|
+
*/
|
|
12
|
+
export async function fetchRecentVersions(flowId, branchId) {
|
|
13
|
+
const supabase = await getAuthenticatedSupabaseClient();
|
|
14
|
+
if (!supabase) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
let query = supabase
|
|
19
|
+
.from('flow_versions')
|
|
20
|
+
.select('version_number, created_at')
|
|
21
|
+
.eq('flow_id', flowId)
|
|
22
|
+
.order('version_number', { ascending: false })
|
|
23
|
+
.limit(RECENT_VERSIONS_LIMIT * 2); // fetch extra then dedupe
|
|
24
|
+
if (branchId === null) {
|
|
25
|
+
query = query.is('branch_id', null);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
query = query.eq('branch_id', branchId);
|
|
29
|
+
}
|
|
30
|
+
const { data, error } = await query;
|
|
31
|
+
if (error) {
|
|
32
|
+
stdout.warn(pc.yellow(`Could not fetch versions: ${error.message}`));
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
// Dedupe by version_number, preserving order (most recent first)
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
const versions = [];
|
|
38
|
+
for (const row of data || []) {
|
|
39
|
+
const v = row.version_number;
|
|
40
|
+
if (typeof v !== 'number' || seen.has(v))
|
|
41
|
+
continue;
|
|
42
|
+
seen.add(v);
|
|
43
|
+
versions.push({
|
|
44
|
+
version_number: v,
|
|
45
|
+
created_at: row.created_at ?? null,
|
|
46
|
+
});
|
|
47
|
+
if (versions.length >= RECENT_VERSIONS_LIMIT)
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
return versions;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
stdout.warn(pc.yellow(`Could not fetch versions: ${err}`));
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Prompt the user to select a version from the list.
|
|
59
|
+
* Returns the selected version number, or null for "Latest".
|
|
60
|
+
*/
|
|
61
|
+
export async function promptForVersion(versions, options = {}) {
|
|
62
|
+
const choices = [];
|
|
63
|
+
if (versions.length === 0) {
|
|
64
|
+
choices.push({
|
|
65
|
+
title: pc.bold('Latest'),
|
|
66
|
+
value: 'latest',
|
|
67
|
+
description: 'Current published version',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
for (let i = 0; i < versions.length; i++) {
|
|
72
|
+
const v = versions[i];
|
|
73
|
+
const date = v.created_at ? formatDate(v.created_at) : '';
|
|
74
|
+
const isLatest = i === 0;
|
|
75
|
+
choices.push({
|
|
76
|
+
title: isLatest ? `Version ${v.version_number} (Latest)` : `Version ${v.version_number}`,
|
|
77
|
+
value: isLatest ? 'latest' : v.version_number,
|
|
78
|
+
description: date || undefined,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const response = await prompts({
|
|
83
|
+
type: 'autocomplete',
|
|
84
|
+
name: 'version',
|
|
85
|
+
message: options.message ?? 'Select a version to pull:',
|
|
86
|
+
choices,
|
|
87
|
+
suggest: (input, choicesList) => Promise.resolve(choicesList.filter((c) => c.value === 'latest' ||
|
|
88
|
+
String(c.value).includes(input) ||
|
|
89
|
+
(typeof c.title === 'string' && c.title.toLowerCase().includes(input.toLowerCase())))),
|
|
90
|
+
}, {
|
|
91
|
+
onCancel: () => {
|
|
92
|
+
process.exit(0);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
if (response.version === 'latest' || response.version === undefined) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return typeof response.version === 'number' ? response.version : null;
|
|
99
|
+
}
|
|
@@ -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
|
+
}
|