Skip to content

Commit 3d7bcdd

Browse files
committed
chore: merge main into preview with Result refactor
Brings in all remaining main commits including: - refactor: unify result types with Result<T, E> (#1125) - feat: instrument telemetry for deploy command (#1206) - feat: record command attrs on failure (#1204) - feat: instrument telemetry for create command (#1202) Adapts preview's deploy flow to use OperationResult with string errors for compatibility with the telemetry layer.
1 parent de28f21 commit 3d7bcdd

4 files changed

Lines changed: 43 additions & 11 deletions

File tree

src/cli/commands/deploy/command.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ConfigIO, serializeResult } from '../../../lib';
1+
import { ConfigIO } from '../../../lib';
22
import { getErrorMessage } from '../../errors';
33
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
44
import { COMMAND_DESCRIPTIONS } from '../../tui/copy';
@@ -50,28 +50,28 @@ async function handleDeployCLI(options: DeployOptions): Promise<void> {
5050

5151
const { deployResult } = await withCommandRunTelemetry('deploy', attrs, async () => {
5252
const result = await executeDeploy(options).catch(
53-
(e): DeployResult => ({ success: false, error: e instanceof Error ? e : new Error(getErrorMessage(e)) })
53+
(e): DeployResult => ({ success: false, error: getErrorMessage(e) })
5454
);
5555
if (!result.success) {
56-
return { success: false as const, error: result.error, deployResult: result };
56+
return { success: false as const, error: result.error ?? 'Deploy failed', deployResult: result };
5757
}
5858
return { success: true as const, deployResult: result };
5959
});
6060

6161
// ALL output happens here, after telemetry
6262
if (!deployResult.success) {
6363
if (options.json) {
64-
console.log(JSON.stringify(serializeResult(deployResult)));
64+
console.log(JSON.stringify(deployResult));
6565
} else {
66-
console.error(deployResult.error.message);
66+
console.error(deployResult.error);
6767
if (deployResult.logPath) {
6868
console.error(`Log: ${deployResult.logPath}`);
6969
}
7070
}
7171
process.exit(1);
7272
}
7373

74-
printDeployResult(deployResult, options);
74+
printDeployResult(deployResult as DeployResult & { success: true }, options);
7575

7676
if (deployResult.postDeployWarnings && deployResult.postDeployWarnings.length > 0) {
7777
process.exit(2);

src/cli/tui/screens/deploy/useDeployFlow.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
} from '../../../operations/deploy/post-deploy-config-bundles';
2626
import { setupHttpGateways } from '../../../operations/deploy/post-deploy-http-gateways';
2727
import { enableOnlineEvalConfigs } from '../../../operations/deploy/post-deploy-online-evals';
28-
import { withCommandRunTelemetry } from '../../../telemetry/cli-command-run.js';
28+
import { type OperationResult, withCommandRunTelemetry } from '../../../telemetry/cli-command-run.js';
2929
import {
3030
type StackDiffSummary,
3131
type Step,
@@ -607,7 +607,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
607607

608608
const attrs = context ? computeDeployAttrs(context.projectSpec, 'deploy') : { ...DEFAULT_DEPLOY_ATTRS };
609609

610-
const run = async (): Promise<{ success: true } | { success: false; error: Error }> => {
610+
const run = async (): Promise<OperationResult> => {
611611
// Run diff before deploy to capture pre-deploy differences
612612
if (!isDiffRunningRef.current) {
613613
isDiffRunningRef.current = true;
@@ -787,7 +787,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
787787
error: logger.getFailureMessage('Publish assets'),
788788
}));
789789
}
790-
return { success: false, error: err instanceof Error ? err : new Error(errorMsg) } as const;
790+
return { success: false, error: errorMsg };
791791
} finally {
792792
// Disable verbose output and clear callback after deploy
793793
switchableIoHost?.setVerbose(false);
@@ -826,7 +826,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
826826
? computeDeployAttrs(context.projectSpec, 'diff')
827827
: { ...DEFAULT_DEPLOY_ATTRS, mode: 'diff' as const };
828828

829-
const run = async (): Promise<{ success: true } | { success: false; error: Error }> => {
829+
const run = async (): Promise<OperationResult> => {
830830
setDiffStep(prev => ({ ...prev, status: 'running' }));
831831
setShouldStartDeploy(false);
832832
setDiffSummaries([]);
@@ -862,7 +862,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
862862
status: 'error',
863863
error: logger.getFailureMessage('Run CDK diff'),
864864
}));
865-
return { success: false, error: err instanceof Error ? err : new Error(errorMsg) };
865+
return { success: false, error: errorMsg };
866866
} finally {
867867
switchableIoHost?.setVerbose(false);
868868
switchableIoHost?.setOnRawMessage(null);

src/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ export * from './utils';
2828
// Schema I/O utilities
2929
export * from './schemas/io';
3030
export * from './time-constants';
31+
export { serializeResult } from './result';
32+
export type { Result } from './result';

src/lib/result.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Discriminated union for fallible operations, inspired by Rust's Result<T, E>.
3+
*
4+
* Success branch spreads T onto the result; failure branch carries an Error.
5+
* E extends Error so callers always get stack traces, cause chains, and instanceof narrowing.
6+
*
7+
* @example
8+
* Result // { success: true } | { success: false; error: Error }
9+
* Result<{ name: string }> // { success: true; name: string } | { success: false; error: Error }
10+
* Result<{ name: string }, ValidationError> // { success: true; name: string } | { success: false; error: ValidationError }
11+
*/
12+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
13+
export type Result<T extends Record<string, unknown> = {}, E extends Error = Error> =
14+
| ({ success: true } & T)
15+
| { success: false; error: E };
16+
17+
/**
18+
* Converts a Result object to a JSON-serializable form.
19+
* Error objects have non-enumerable properties, so JSON.stringify produces `{}`.
20+
* This replaces the `error` field with the error message string.
21+
*/
22+
export function serializeResult<T extends Record<string, unknown>>(
23+
result: ({ success: true } & T) | ({ success: false; error: Error } & Record<string, unknown>)
24+
): Record<string, unknown> {
25+
if (!result.success) {
26+
const { error, ...rest } = result;
27+
return { ...rest, error: error.message };
28+
}
29+
return result;
30+
}

0 commit comments

Comments
 (0)