rivet-design 0.26.2 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/agent-variants/PreviewRuntime.js +20 -7
  2. package/dist/agent-variants/contracts.d.ts +116 -116
  3. package/dist/agent-variants/variantsTelemetry.d.ts +4 -0
  4. package/dist/agent-variants/variantsTelemetry.js +3 -0
  5. package/dist/agent-variants/workItemInput.d.ts +146 -146
  6. package/dist/cli/commands/mcpServe.js +1 -0
  7. package/dist/cli/commands/serve.js +2 -0
  8. package/dist/cli/handoff.d.ts +1 -0
  9. package/dist/cli/handoff.js +20 -4
  10. package/dist/cli/router.js +1 -0
  11. package/dist/cli/telemetry.d.ts +10 -0
  12. package/dist/cli/telemetry.js +37 -1
  13. package/dist/config/pluginMetrics.js +36 -0
  14. package/dist/index.d.ts +19 -0
  15. package/dist/index.js +14 -5
  16. package/dist/routes/library/middleware.js +11 -5
  17. package/dist/server.js +8 -0
  18. package/dist/services/DependencyPreparationService.js +57 -19
  19. package/dist/services/GuidanceVerifierRunner.js +4 -4
  20. package/dist/services/ProjectPackageManager.d.ts +5 -0
  21. package/dist/services/ProjectPackageManager.js +33 -11
  22. package/dist/services/TelemetryService.d.ts +8 -2
  23. package/dist/services/TelemetryService.js +10 -0
  24. package/dist/services/WorktreeManager.js +5 -0
  25. package/dist/services/profiles/DesignProfiles.js +16 -8
  26. package/dist/types/guidance-verifier.d.ts +7 -5
  27. package/dist/types/guidance-verifier.js +14 -1
  28. package/dist/utils/devServerError.d.ts +5 -0
  29. package/dist/utils/devServerError.js +4 -0
  30. package/dist/utils/failureReason.d.ts +16 -0
  31. package/dist/utils/failureReason.js +106 -0
  32. package/dist/utils/hostedLogPrivacy.d.ts +1 -0
  33. package/dist/utils/hostedLogPrivacy.js +10 -4
  34. package/dist/utils/skills/claude-skill.d.ts +1 -1
  35. package/dist/utils/skills/claude-skill.js +1 -1
  36. package/dist/utils/skills/cli-guidance.d.ts +1 -1
  37. package/dist/utils/skills/cli-guidance.js +3 -3
  38. package/dist/utils/skills/cursor-rules.d.ts +1 -1
  39. package/dist/utils/skills/cursor-rules.js +1 -1
  40. package/package.json +3 -3
  41. package/src/ui/dist/assets/{LibraryEntry-92R_T-Xa.js → LibraryEntry-RI9Lc7P3.js} +9 -9
  42. package/src/ui/dist/assets/main-DgrLzS5j.js +497 -0
  43. package/src/ui/dist/index.html +1 -1
  44. package/src/ui/dist/assets/main-DgEfJvYu.js +0 -497
@@ -182,6 +182,7 @@ const buildMcpServer = async (options = {}) => {
182
182
  durationMs: Date.now() - startedAt,
183
183
  designContextKind: (0, telemetry_1.designContextKindForEnvelope)(result.envelope),
184
184
  ...(0, telemetry_1.variantContextMetadataForArgs)(command, rest),
185
+ ...(0, telemetry_1.verificationMetadataForEnvelope)(result.envelope),
185
186
  ...callerFields,
186
187
  source: 'mcp',
187
188
  });
@@ -113,6 +113,7 @@ const handleServe = async (args, io) => {
113
113
  stage: 'resolve_dev_server',
114
114
  failureReason: 'operation_failed',
115
115
  durationMs: Date.now() - serverStartStartedAt,
116
+ error,
116
117
  });
117
118
  await telemetry.shutdown();
118
119
  await logForwarder.shutdown();
@@ -299,6 +300,7 @@ const handleServe = async (args, io) => {
299
300
  stage: 'start_editor_server',
300
301
  failureReason: 'operation_failed',
301
302
  durationMs: Date.now() - serverStartStartedAt,
303
+ error,
302
304
  });
303
305
  await telemetry.shutdown();
304
306
  await logForwarder.shutdown();
@@ -35,6 +35,7 @@ export type CallerTelemetryFields = {
35
35
  callerAgent?: string;
36
36
  callerModel?: string;
37
37
  callerAgentSource?: 'handoff' | 'env';
38
+ callerRunId?: string;
38
39
  };
39
40
  export declare const callerTelemetryFieldsFromArgs: (args: string[], env?: NodeJS.ProcessEnv) => CallerTelemetryFields;
40
41
  export declare const initiatorFor: (handoff: CallerHandoff | null) => WorkItemInitiator;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.initiatorFor = exports.callerTelemetryFieldsFromArgs = exports.detectCallerAgentFromEnv = exports.callerAgentEvidenceFromEnv = exports.detectCallerAgentsFromEnv = exports.parseHandoff = exports.HANDOFF_ENV_VARS = exports.CALLER_AGENT_ALLOWLIST = void 0;
4
+ const node_crypto_1 = require("node:crypto");
4
5
  exports.CALLER_AGENT_ALLOWLIST = ['claude', 'codex', 'cursor'];
5
6
  exports.HANDOFF_ENV_VARS = {
6
7
  agent: 'RIVET_CALLER_AGENT',
@@ -84,6 +85,11 @@ const callerAgentEvidenceFromEnv = (env = process.env) => ENV_MARKERS_BY_AGENT.m
84
85
  exports.callerAgentEvidenceFromEnv = callerAgentEvidenceFromEnv;
85
86
  const detectCallerAgentFromEnv = (env = process.env) => (0, exports.detectCallerAgentsFromEnv)(env)[0];
86
87
  exports.detectCallerAgentFromEnv = detectCallerAgentFromEnv;
88
+ const CALLER_RUN_ID_HEX_LENGTH = 16;
89
+ const callerRunIdForSession = (session) => (0, node_crypto_1.createHash)('sha256')
90
+ .update(session)
91
+ .digest('hex')
92
+ .slice(0, CALLER_RUN_ID_HEX_LENGTH);
87
93
  const callerTelemetryFieldsFromArgs = (args, env = process.env) => {
88
94
  const valueOf = (flag) => {
89
95
  const index = args.indexOf(flag);
@@ -95,15 +101,25 @@ const callerTelemetryFieldsFromArgs = (args, env = process.env) => {
95
101
  callerSession: valueOf('--caller-session'),
96
102
  handoffJson: valueOf('--handoff'),
97
103
  }, env);
98
- if (parsed.ok && parsed.handoff) {
104
+ const handoff = parsed.ok ? parsed.handoff : null;
105
+ const session = handoff?.session ??
106
+ nonEmpty(env.CODEX_THREAD_ID) ??
107
+ nonEmpty(env.CLAUDE_CODE_SESSION_ID);
108
+ const callerRunId = session ? callerRunIdForSession(session) : undefined;
109
+ const runId = callerRunId ? { callerRunId } : {};
110
+ if (handoff) {
99
111
  return {
100
- callerAgent: parsed.handoff.agent,
101
- callerModel: parsed.handoff.model,
112
+ callerAgent: handoff.agent,
113
+ callerModel: handoff.model,
102
114
  callerAgentSource: 'handoff',
115
+ ...runId,
103
116
  };
104
117
  }
105
118
  const detected = (0, exports.detectCallerAgentFromEnv)(env);
106
- return detected ? { callerAgent: detected, callerAgentSource: 'env' } : {};
119
+ if (detected) {
120
+ return { callerAgent: detected, callerAgentSource: 'env', ...runId };
121
+ }
122
+ return runId;
107
123
  };
108
124
  exports.callerTelemetryFieldsFromArgs = callerTelemetryFieldsFromArgs;
109
125
  const initiatorFor = (handoff) => handoff
@@ -309,6 +309,7 @@ const runCliControlCommand = async (args, io = io_1.processCliIO) => {
309
309
  designContextKind: (0, telemetry_1.designContextKindForEnvelope)(result.envelope),
310
310
  ...(0, telemetry_1.variantContextMetadataForArgs)(command, rest),
311
311
  ...(0, telemetry_1.browserMetadataForEnvelope)(result.envelope),
312
+ ...(0, telemetry_1.verificationMetadataForEnvelope)(result.envelope),
312
313
  ...(0, handoff_1.callerTelemetryFieldsFromArgs)(rest),
313
314
  });
314
315
  telemetry.reportInstallRefActivations();
@@ -1,6 +1,7 @@
1
1
  import type { CliEnvelope } from './envelope';
2
2
  import type { BrowserOpenResult } from './browser';
3
3
  import { type ServerUnavailableReason } from './serverUnavailable';
4
+ import { type GuidanceVerifierErrorReason } from '../types/guidance-verifier';
4
5
  export type { ServerUnavailableReason } from './serverUnavailable';
5
6
  export type DesignContextKind = 'arena' | 'pinterest' | 'reference' | 'unsupported' | 'website';
6
7
  export type VariantContextKind = 'image' | 'variant' | 'video' | 'website';
@@ -14,6 +15,15 @@ export type BrowserMetadata = {
14
15
  browserAction?: BrowserOpenResult['action'];
15
16
  browserReason?: BrowserOpenResult['reason'];
16
17
  };
18
+ declare const COMPLETED_VERIFICATION_STATUSES: readonly ["pass", "fail", "blocked"];
19
+ type CompletedVerificationStatus = (typeof COMPLETED_VERIFICATION_STATUSES)[number];
20
+ export type VerificationOutcome = CompletedVerificationStatus | GuidanceVerifierErrorReason;
21
+ export type VerificationMetadata = {
22
+ verificationOutcome?: VerificationOutcome;
23
+ verificationCheckCount?: number;
24
+ verificationDurationMs?: number;
25
+ };
26
+ export declare const verificationMetadataForEnvelope: (envelope: CliEnvelope) => VerificationMetadata;
17
27
  export declare const browserMetadataForEnvelope: (envelope: CliEnvelope) => BrowserMetadata;
18
28
  export declare const serverUnavailableReasonForEnvelope: (envelope: CliEnvelope) => ServerUnavailableReason | undefined;
19
29
  export declare const designContextKindForEnvelope: (envelope: CliEnvelope) => DesignContextKind | undefined;
@@ -1,10 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.variantContextMetadataForArgs = exports.designContextKindForEnvelope = exports.serverUnavailableReasonForEnvelope = exports.browserMetadataForEnvelope = void 0;
3
+ exports.variantContextMetadataForArgs = exports.designContextKindForEnvelope = exports.serverUnavailableReasonForEnvelope = exports.browserMetadataForEnvelope = exports.verificationMetadataForEnvelope = void 0;
4
4
  const serverUnavailable_1 = require("./serverUnavailable");
5
+ const guidance_verifier_1 = require("../types/guidance-verifier");
6
+ const COMPLETED_VERIFICATION_STATUSES = ['pass', 'fail', 'blocked'];
7
+ const isCompletedVerificationStatus = (value) => typeof value === 'string' &&
8
+ COMPLETED_VERIFICATION_STATUSES.includes(value);
9
+ const isGuidanceVerifierErrorReason = (value) => typeof value === 'string' &&
10
+ guidance_verifier_1.GUIDANCE_VERIFIER_ERROR_REASONS.includes(value);
11
+ const isNonNegativeDuration = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0;
12
+ const verificationOutcomeForReport = (report) => {
13
+ if (report.execution === 'completed' &&
14
+ isCompletedVerificationStatus(report.status)) {
15
+ return report.status;
16
+ }
17
+ if (report.execution === 'error' &&
18
+ isGuidanceVerifierErrorReason(report.reason)) {
19
+ return report.reason;
20
+ }
21
+ return undefined;
22
+ };
5
23
  const recordOf = (value) => value !== null && typeof value === 'object'
6
24
  ? value
7
25
  : {};
26
+ const verificationMetadataForEnvelope = (envelope) => {
27
+ if (envelope.command !== 'context.verify')
28
+ return {};
29
+ const report = recordOf(envelope.ok ? envelope.data : envelope.error.details);
30
+ const verificationOutcome = verificationOutcomeForReport(report);
31
+ const verificationCheckCount = Array.isArray(report.checks)
32
+ ? report.checks.length
33
+ : undefined;
34
+ const verificationDurationMs = isNonNegativeDuration(report.durationMs)
35
+ ? report.durationMs
36
+ : undefined;
37
+ return {
38
+ ...(verificationOutcome !== undefined ? { verificationOutcome } : {}),
39
+ ...(verificationCheckCount !== undefined ? { verificationCheckCount } : {}),
40
+ ...(verificationDurationMs !== undefined ? { verificationDurationMs } : {}),
41
+ };
42
+ };
43
+ exports.verificationMetadataForEnvelope = verificationMetadataForEnvelope;
8
44
  const browserMetadataForEnvelope = (envelope) => {
9
45
  if (envelope.command !== 'open')
10
46
  return {};
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pluginMetricProperties = void 0;
4
4
  const zod_1 = require("zod");
5
+ const failureReason_1 = require("../utils/failureReason");
5
6
  const count = zod_1.z.number().finite().nonnegative().max(Number.MAX_SAFE_INTEGER);
6
7
  const metricProperties = zod_1.z.object({
7
8
  command: zod_1.z
@@ -19,6 +20,17 @@ const metricProperties = zod_1.z.object({
19
20
  'variants.cancel',
20
21
  'reference.fetch',
21
22
  'evidence.capture',
23
+ 'context.add',
24
+ 'context.update',
25
+ 'context.list',
26
+ 'context.show',
27
+ 'context.enable',
28
+ 'context.disable',
29
+ 'context.remove',
30
+ 'context.resolve',
31
+ 'context.inspect',
32
+ 'context.verifier',
33
+ 'context.verify',
22
34
  ])
23
35
  .optional(),
24
36
  tool_name: zod_1.z
@@ -112,8 +124,12 @@ const metricProperties = zod_1.z.object({
112
124
  'STATIC_PREVIEW_FAILED',
113
125
  'DEV_SERVER_FAILED',
114
126
  'PREVIEW_UNAVAILABLE',
127
+ 'DEPENDENCY_INSTALL_FAILED',
128
+ 'DEV_SERVER_START_FAILED',
129
+ 'DEV_SCRIPT_MISSING',
115
130
  ])
116
131
  .optional(),
132
+ reason_code: zod_1.z.enum(failureReason_1.FAILURE_REASON_CODES).optional(),
117
133
  failure_reason: zod_1.z
118
134
  .enum([
119
135
  'ack_failed',
@@ -169,6 +185,26 @@ const metricProperties = zod_1.z.object({
169
185
  'editor_server_start_failed',
170
186
  ])
171
187
  .optional(),
188
+ verification_outcome: zod_1.z
189
+ .enum([
190
+ 'pass',
191
+ 'fail',
192
+ 'blocked',
193
+ 'package_required',
194
+ 'verifier_missing',
195
+ 'verifier_stale',
196
+ 'cancelled',
197
+ 'timeout',
198
+ 'output_limit',
199
+ 'process_failed',
200
+ 'invalid_result',
201
+ 'invalid_evidence',
202
+ 'cleanup_failed',
203
+ 'setup_failed',
204
+ ])
205
+ .optional(),
206
+ verification_check_count: count.int().optional(),
207
+ verification_duration_ms: count.optional(),
172
208
  ok: zod_1.z.boolean().optional(),
173
209
  is_recoverable: zod_1.z.boolean().optional(),
174
210
  is_fresh: zod_1.z.boolean().optional(),
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ProjectDetectionService } from './index-core';
2
+ import { AuthService } from './services/AuthService';
2
3
  import { SessionBridgeService } from './services/SessionBridgeService';
3
4
  import type { Framework } from './services/ProjectDetectionService';
4
5
  import { DevServerRuntimeService, type StartDevServerResult } from './services/DevServerRuntimeService';
@@ -75,6 +76,24 @@ export declare const authCommandOptionsForArgs: (args: string[]) => {
75
76
  };
76
77
  export declare const rememberInstallRef: (args: string[], downloadType?: string) => string[];
77
78
  export declare const authFailureReasonForError: (error: string | undefined) => "oauth_failed" | "timeout";
79
+ export declare const signInWithoutBrowser: (authService?: AuthService) => Promise<{
80
+ success: boolean;
81
+ token?: string | undefined;
82
+ refreshToken?: string | undefined;
83
+ user?: {
84
+ id: string;
85
+ email: string;
86
+ displayName?: string | undefined;
87
+ } | undefined;
88
+ profile?: {
89
+ remaining_spend_usd?: number | null | undefined;
90
+ monthly_limit_usd?: number | null | undefined;
91
+ current_month_spend_usd?: number | null | undefined;
92
+ total_spend_usd?: number | null | undefined;
93
+ } | null | undefined;
94
+ error?: string | undefined;
95
+ message?: string | undefined;
96
+ }>;
78
97
  export declare const main: (args?: string[]) => Promise<void>;
79
98
  export declare const parseArgs: (args: string[]) => CLIOptions;
80
99
  export declare const handleLogin: (options?: {
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.handleLogin = exports.parseArgs = exports.main = exports.authFailureReasonForError = exports.rememberInstallRef = exports.authCommandOptionsForArgs = exports.registerOwnedDevServerSignalHandlers = exports.isAgentVariantsEnabled = exports.updateOwnedDevServerHealthOnExit = exports.buildAgentDevServerHealth = exports.resolveAgentDevServer = exports.buildAgentDevServerContext = exports.DEFAULT_USER_PORT = exports.DEFAULT_PORT = void 0;
39
+ exports.handleLogin = exports.parseArgs = exports.main = exports.signInWithoutBrowser = exports.authFailureReasonForError = exports.rememberInstallRef = exports.authCommandOptionsForArgs = exports.registerOwnedDevServerSignalHandlers = exports.isAgentVariantsEnabled = exports.updateOwnedDevServerHealthOnExit = exports.buildAgentDevServerHealth = exports.resolveAgentDevServer = exports.buildAgentDevServerContext = exports.DEFAULT_USER_PORT = exports.DEFAULT_PORT = void 0;
40
40
  /* eslint-disable no-console */
41
41
  const dotenv = __importStar(require("dotenv"));
42
42
  const crypto_1 = require("crypto");
@@ -260,6 +260,18 @@ const discardInstallRefForValidatedSession = () => {
260
260
  };
261
261
  const authFailureReasonForError = (error) => error?.trim().toLowerCase() === 'timeout' ? 'timeout' : 'oauth_failed';
262
262
  exports.authFailureReasonForError = authFailureReasonForError;
263
+ const signInWithoutBrowser = async (authService = new AuthService_1.AuthService()) => {
264
+ console.log('\n🔐 Authentication required');
265
+ console.log('Open this link to sign in:');
266
+ return authService.loginWithGoogle({
267
+ source: 'cli',
268
+ noBrowser: true,
269
+ onAuthUrl: (url) => {
270
+ console.log(`\n ${url}\n`);
271
+ },
272
+ });
273
+ };
274
+ exports.signInWithoutBrowser = signInWithoutBrowser;
263
275
  const main = async (args = process.argv.slice(2)) => {
264
276
  if (args.includes('--no-telemetry'))
265
277
  process.env.RIVET_TELEMETRY_DISABLED = '1';
@@ -452,10 +464,7 @@ const main = async (args = process.argv.slice(2)) => {
452
464
  process.exit(1);
453
465
  }
454
466
  if (options.noBrowser) {
455
- console.log('\n🔐 Authentication required');
456
- console.log('Opening browser for Google login...\n');
457
- const authService = new AuthService_1.AuthService();
458
- const loginResult = await authService.loginWithGoogle();
467
+ const loginResult = await (0, exports.signInWithoutBrowser)();
459
468
  if (!loginResult.success) {
460
469
  console.log(`\n❌ Authentication failed: ${loginResult.error || loginResult.message}`);
461
470
  process.exit(1);
@@ -45,13 +45,18 @@ const authenticateLibrary = (token, sessions) => (req, res, next) => {
45
45
  next();
46
46
  };
47
47
  exports.authenticateLibrary = authenticateLibrary;
48
+ const HANDLED_ERROR_KEY = 'libraryError';
48
49
  const trackLibraryOperation = (telemetry) => (operation) => (_req, res, next) => {
49
50
  const startedAt = Date.now();
50
- res.once('finish', () => telemetry?.trackLibraryOperation({
51
- operation,
52
- outcome: res.statusCode < 400 ? 'completed' : 'failed',
53
- durationMs: Date.now() - startedAt,
54
- }));
51
+ res.once('finish', () => {
52
+ const isCompleted = res.statusCode < 400;
53
+ telemetry?.trackLibraryOperation({
54
+ operation,
55
+ outcome: isCompleted ? 'completed' : 'failed',
56
+ durationMs: Date.now() - startedAt,
57
+ error: isCompleted ? undefined : res.locals[HANDLED_ERROR_KEY],
58
+ });
59
+ });
55
60
  next();
56
61
  };
57
62
  exports.trackLibraryOperation = trackLibraryOperation;
@@ -73,6 +78,7 @@ const libraryErrorStatus = (code) => {
73
78
  }
74
79
  };
75
80
  const handleLibraryError = (error, _req, res, _next) => {
81
+ res.locals[HANDLED_ERROR_KEY] = error;
76
82
  const message = (0, errorMessage_1.errorMessage)(error);
77
83
  if (error instanceof libraryErrors_1.LibraryError) {
78
84
  res
package/dist/server.js CHANGED
@@ -370,6 +370,14 @@ const startServer = async (options) => {
370
370
  app.get('/rivet/favicon.ico', (_req, res) => {
371
371
  res.sendFile('favicon.ico', { root: exports.DIST_UI_PATH });
372
372
  });
373
+ app.get(/^\/rivet\/library(?:\/.*)?$/, (req, res) => {
374
+ const resolvedLibraryOrigin = options.resolveLibraryOrigin?.();
375
+ if (!resolvedLibraryOrigin) {
376
+ res.sendStatus(404);
377
+ return;
378
+ }
379
+ res.redirect(302, `${new URL(resolvedLibraryOrigin).origin}${req.path}`);
380
+ });
373
381
  app.get(/^\/rivet(?:\/.*)?$/, (req, res, next) => {
374
382
  if ((0, exports.isRivetUiStaticAssetPath)(req.path)) {
375
383
  next();
@@ -16,6 +16,7 @@ const ProjectPackageManager_1 = require("./ProjectPackageManager");
16
16
  const errnoCode_1 = require("../utils/errnoCode");
17
17
  const pathContainment_1 = require("../utils/pathContainment");
18
18
  const errorMessage_1 = require("../utils/errorMessage");
19
+ const failureReason_1 = require("../utils/failureReason");
19
20
  const subprocessEnvironment_1 = require("../utils/subprocessEnvironment");
20
21
  const MAX_CONCURRENT_INSTALLS = 2;
21
22
  const INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
@@ -43,11 +44,13 @@ const isInstallEnvironmentKey = (key) => {
43
44
  INSTALL_ENVIRONMENT_KEYS.has(lower));
44
45
  };
45
46
  const yarnAgentForVersion = (version) => Number.parseInt(version, 10) > 1 ? 'yarn@berry' : 'yarn';
46
- const preparationError = (message, detail = '') => new devServerError_1.DevServerStartError({
47
+ const preparationError = ({ reasonCode, message, detail = '', }) => new devServerError_1.DevServerStartError({
47
48
  message: detail ? `${message}\n${detail}` : message,
48
49
  reason: 'dependency_install_failed',
49
50
  portInUse: false,
50
51
  detail,
52
+ reasonCode,
53
+ summary: message,
51
54
  });
52
55
  class DependencyPreparationService {
53
56
  pending = new Map();
@@ -78,7 +81,10 @@ class DependencyPreparationService {
78
81
  catch (error) {
79
82
  if (error instanceof devServerError_1.DevServerStartError)
80
83
  throw error;
81
- throw preparationError(`Could not prepare preview dependencies: ${(0, errorMessage_1.errorMessage)(error)}`);
84
+ throw preparationError({
85
+ reasonCode: (0, failureReason_1.failureReasonCode)(error),
86
+ message: `Could not prepare preview dependencies: ${(0, errorMessage_1.errorMessage)(error)}`,
87
+ });
82
88
  }
83
89
  }
84
90
  forget(workspaceRoot) {
@@ -116,9 +122,16 @@ class DependencyPreparationService {
116
122
  .reverse()
117
123
  .find((line) => /^\d+\.\d+\.\d+(?:-.+)?$/.test(line));
118
124
  if (!version)
119
- throw preparationError(`Could not identify the ${project.name} version`, versionOutput);
125
+ throw preparationError({
126
+ reasonCode: 'version_unreadable',
127
+ message: `Could not identify the ${project.name} version`,
128
+ detail: versionOutput,
129
+ });
120
130
  if (project.version && version !== project.version) {
121
- throw preparationError(`This project requires ${project.name}@${project.version}, but the available command reports ${version}. Activate the project's package manager version, then retry the preview.`);
131
+ throw preparationError({
132
+ reasonCode: 'version_mismatch',
133
+ message: `This project requires ${project.name}@${project.version}, but the available command reports ${version}. Activate the project's package manager version, then retry the preview.`,
134
+ });
122
135
  }
123
136
  const agent = project.name === 'yarn' ? yarnAgentForVersion(version) : project.agent;
124
137
  const fingerprint = await this.fingerprint(project, version);
@@ -134,7 +147,10 @@ class DependencyPreparationService {
134
147
  : await this.fingerprint(project, version, true);
135
148
  const command = (0, package_manager_detector_1.resolveCommand)(agent, project.hasLockfile ? 'frozen' : 'install', []);
136
149
  if (!command)
137
- throw preparationError(`No dependency install command is available for ${project.name}`);
150
+ throw preparationError({
151
+ reasonCode: 'no_install_command',
152
+ message: `No dependency install command is available for ${project.name}`,
153
+ });
138
154
  if (project.name === 'npm')
139
155
  command.args.push('--no-audit', '--no-fund');
140
156
  if (this.activeInstalls >= MAX_CONCURRENT_INSTALLS) {
@@ -166,7 +182,10 @@ class DependencyPreparationService {
166
182
  ? completedFingerprint
167
183
  : await this.fingerprint(project, version, true);
168
184
  if (inputsAfterInstall !== inputsBeforeInstall) {
169
- throw preparationError('Dependency inputs changed during installation. Retry the preview to prepare the updated project.');
185
+ throw preparationError({
186
+ reasonCode: 'inputs_changed',
187
+ message: 'Dependency inputs changed during installation. Retry the preview to prepare the updated project.',
188
+ });
170
189
  }
171
190
  this.prepared.set(project.rootPath, {
172
191
  fingerprint: completedFingerprint,
@@ -194,14 +213,20 @@ class DependencyPreparationService {
194
213
  try {
195
214
  const resolved = await promises_1.default.realpath(candidate);
196
215
  if (!(0, pathContainment_1.isPathWithinRoot)(boundary, resolved))
197
- throw preparationError(`Dependency path resolves outside the preview workspace: ${candidate}`);
216
+ throw preparationError({
217
+ reasonCode: 'path_outside_workspace',
218
+ message: `Dependency path resolves outside the preview workspace: ${candidate}`,
219
+ });
198
220
  }
199
221
  catch (error) {
200
222
  if ((0, errnoCode_1.errnoCode)(error) !== 'ENOENT')
201
223
  throw error;
202
224
  const entry = await promises_1.default.lstat(candidate).catch(() => null);
203
225
  if (entry?.isSymbolicLink())
204
- throw preparationError(`Dependency path is a dangling symlink: ${candidate}`);
226
+ throw preparationError({
227
+ reasonCode: 'dangling_symlink',
228
+ message: `Dependency path is a dangling symlink: ${candidate}`,
229
+ });
205
230
  }
206
231
  }
207
232
  }
@@ -284,7 +309,7 @@ class DependencyPreparationService {
284
309
  stdio: 'pipe',
285
310
  });
286
311
  let output = '';
287
- let interrupted = false;
312
+ let interruption;
288
313
  let settleTimer;
289
314
  const drain = (chunk) => {
290
315
  output = (output + chunk.toString()).slice(-OUTPUT_TAIL_LIMIT);
@@ -292,35 +317,48 @@ class DependencyPreparationService {
292
317
  proc.stdout?.on('data', drain);
293
318
  proc.stderr?.on('data', drain);
294
319
  proc.stdin?.end();
295
- const stop = () => {
296
- interrupted = true;
320
+ const stop = (cause) => {
321
+ interruption ??= cause;
297
322
  void OperatingSystemServices_1.selectedOperatingSystemServices.projectProcesses
298
323
  .terminate(proc, { isForceful: true })
299
324
  .catch(() => undefined);
300
325
  settleTimer ??= setTimeout(() => {
301
326
  cleanup();
302
- reject(preparationError(`Dependency preparation could not be stopped: ${project.name} ${args.join(' ')} is still running. Stop the process, then retry the preview.`, output.trim()));
327
+ reject(preparationError({
328
+ reasonCode: 'installer_unstoppable',
329
+ message: `Dependency preparation could not be stopped: ${project.name} ${args.join(' ')} is still running. Stop the process, then retry the preview.`,
330
+ detail: output.trim(),
331
+ }));
303
332
  }, this.terminationSettleTimeoutMs);
304
333
  };
305
- const timer = setTimeout(stop, timeoutMs);
306
- signal?.addEventListener('abort', stop, { once: true });
334
+ const abort = () => stop('aborted');
335
+ const timer = setTimeout(() => stop('timeout'), timeoutMs);
336
+ signal?.addEventListener('abort', abort, { once: true });
307
337
  if (signal?.aborted)
308
- stop();
338
+ abort();
309
339
  const cleanup = () => {
310
340
  clearTimeout(timer);
311
341
  clearTimeout(settleTimer);
312
- signal?.removeEventListener('abort', stop);
342
+ signal?.removeEventListener('abort', abort);
313
343
  };
314
344
  proc.once('error', (error) => {
315
345
  cleanup();
316
- reject(preparationError(`Could not run ${project.name} ${args.join(' ')}: ${error.message}`, output));
346
+ reject(preparationError({
347
+ reasonCode: (0, failureReason_1.failureReasonCode)(error),
348
+ message: `Could not run ${project.name} ${args.join(' ')}: ${error.message}`,
349
+ detail: output,
350
+ }));
317
351
  });
318
352
  proc.once('close', (code) => {
319
353
  cleanup();
320
- if (code === 0 && !interrupted)
354
+ if (code === 0 && !interruption)
321
355
  resolve(output);
322
356
  else
323
- reject(preparationError(`Dependency preparation failed: ${project.name} ${args.join(' ')} (${interrupted ? 'cancelled or timed out' : `exit ${code}`}). Fix the installation error, then retry the preview.`, output.trim()));
357
+ reject(preparationError({
358
+ reasonCode: interruption ?? 'install_failed',
359
+ message: `Dependency preparation failed: ${project.name} ${args.join(' ')} (${interruption ? 'cancelled or timed out' : `exit ${code}`}). Fix the installation error, then retry the preview.`,
360
+ detail: output.trim(),
361
+ }));
324
362
  });
325
363
  });
326
364
  }
@@ -254,16 +254,16 @@ const executeGuidanceVerifier = async (args) => {
254
254
  durationMs: Date.now() - started,
255
255
  });
256
256
  try {
257
- await promises_1.default.mkdir(args.runsDirectory, { recursive: true, mode: 0o700 });
258
- runDirectory = await promises_1.default.mkdtemp(path_1.default.join(await promises_1.default.realpath(args.runsDirectory), 'run-'));
259
- if (args.signal?.aborted)
260
- return failed('cancelled');
261
257
  if (args.packet.verifierState === 'missing')
262
258
  return failed('verifier_missing');
263
259
  if (args.packet.verifierState === 'stale')
264
260
  return failed('verifier_stale');
265
261
  if (!args.packet.verifier.package)
266
262
  return failed('package_required');
263
+ await promises_1.default.mkdir(args.runsDirectory, { recursive: true, mode: 0o700 });
264
+ runDirectory = await promises_1.default.mkdtemp(path_1.default.join(await promises_1.default.realpath(args.runsDirectory), 'run-'));
265
+ if (args.signal?.aborted)
266
+ return failed('cancelled');
267
267
  const executable = guidance_verifier_1.GuidanceVerifierPackageSchema.parse(args.packet.verifier.package);
268
268
  const packageDirectory = await (0, retainedTextFiles_1.materializeTextFiles)({
269
269
  files: executable.files,
@@ -1,5 +1,10 @@
1
1
  import { type Agent } from 'package-manager-detector';
2
+ import type { FailureReasonCode } from '../utils/failureReason';
2
3
  export type PreviewPackageManager = 'npm' | 'yarn' | 'pnpm';
4
+ export declare class PackageManagerError extends Error {
5
+ readonly reasonCode: FailureReasonCode;
6
+ constructor(reasonCode: FailureReasonCode, message: string);
7
+ }
3
8
  export interface ProjectPackageManager {
4
9
  name: PreviewPackageManager;
5
10
  agent: Agent;