Skip to content

fix(toolkit-lib): unnecessary dependency on promptly #523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,6 @@ const toolkitLib = configureProject(
'glob',
'minimatch',
'p-limit@^3',
'promptly',
'proxy-agent',
'semver',
'split2',
Expand All @@ -773,6 +772,8 @@ const toolkitLib = configureProject(
'aws-sdk-client-mock-jest',
'fast-check',
'jest-environment-node',
'@types/jest-when',
'jest-when',
'nock@13',
'typedoc',
'xml-js',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ integTest('cdk import prompts the user for sns topic arns', withDefaultFixture(a
await fixture.cdk(['import', fullStackName], {
interact: [
{
prompt: /Topic1.*\(empty to skip\):/,
prompt: /Topic1.*\(empty to skip\)/,
input: topic1Arn,
},
{
prompt: /Topic2.*\(empty to skip\):/,
prompt: /Topic2.*\(empty to skip\)/,
input: topic2Arn,
},
],
Expand Down
12 changes: 8 additions & 4 deletions packages/@aws-cdk/toolkit-lib/.projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/@aws-cdk/toolkit-lib/.projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/@aws-cdk/toolkit-lib/docs/message-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ group: Documents
| `CDK_TOOLKIT_I1901` | Provides stack data | `result` | {@link StackAndAssemblyData} |
| `CDK_TOOLKIT_I1902` | Successfully deployed stacks | `result` | {@link AssemblyData} |
| `CDK_TOOLKIT_I2901` | Provides details on the selected stacks and their dependencies | `result` | {@link StackDetailsPayload} |
| `CDK_TOOLKIT_I3100` | Confirm the import of a specific resource | `info` | {@link ResourceImportRequest} |
| `CDK_TOOLKIT_I3110` | Additional information is needed to identify a resource | `info` | {@link ResourceIdentificationRequest} |
| `CDK_TOOLKIT_E3900` | Resource import failed | `error` | {@link ErrorPayload} |
| `CDK_TOOLKIT_I4000` | Diff stacks is starting | `trace` | {@link StackSelectionDetails} |
| `CDK_TOOLKIT_I4001` | Output of the diff command | `info` | {@link DiffResult} |
Expand Down
11 changes: 11 additions & 0 deletions packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { BuildAsset, DeployConfirmationRequest, PublishAsset, StackDeployPr
import type { StackDestroy, StackDestroyProgress } from '../../../payloads/destroy';
import type { AssetBatchDeletionRequest } from '../../../payloads/gc';
import type { HotswapDeploymentDetails, HotswapDeploymentAttempt, HotswappableChange, HotswapResult } from '../../../payloads/hotswap';
import type { ResourceIdentificationRequest, ResourceImportRequest } from '../../../payloads/import';
import type { StackDetailsPayload } from '../../../payloads/list';
import type { CloudWatchLogEvent, CloudWatchLogMonitorControlEvent } from '../../../payloads/logs-monitor';
import type { RefactorResult } from '../../../payloads/refactor';
Expand Down Expand Up @@ -61,6 +62,16 @@ export const IO = {
}),

// 3: Import & Migrate
CDK_TOOLKIT_I3100: make.confirm<ResourceImportRequest>({
code: 'CDK_TOOLKIT_I3100',
description: 'Confirm the import of a specific resource',
interface: 'ResourceImportRequest',
}),
CDK_TOOLKIT_I3110: make.question<ResourceIdentificationRequest>({
code: 'CDK_TOOLKIT_I3110',
description: 'Additional information is needed to identify a resource',
interface: 'ResourceIdentificationRequest',
}),
CDK_TOOLKIT_E3900: make.error<ErrorPayload>({
code: 'CDK_TOOLKIT_E3900',
description: 'Resource import failed',
Expand Down
46 changes: 23 additions & 23 deletions packages/@aws-cdk/toolkit-lib/lib/api/resource-import/importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type * as cxapi from '@aws-cdk/cx-api';
import type { ResourceIdentifierSummary, ResourceToImport } from '@aws-sdk/client-cloudformation';
import * as chalk from 'chalk';
import * as fs from 'fs-extra';
import * as promptly from 'promptly';
import type { DeploymentMethod } from '../../actions/deploy';
import { ToolkitError } from '../../toolkit/toolkit-error';
import type { Deployments } from '../deployments';
Expand Down Expand Up @@ -347,10 +346,14 @@ export class ResourceImporter {
const candidateProps = Object.fromEntries(satisfiedPropSet.map(p => [p, resourceProps[p]]));
const displayCandidateProps = fmtdict(candidateProps);

if (await promptly.confirm(
`${chalk.blue(resourceName)} (${resourceType}): import with ${chalk.yellow(displayCandidateProps)} (yes/no) [default: yes]? `,
{ default: 'yes' },
)) {
const importTheResource = await this.ioHelper.requestResponse(IO.CDK_TOOLKIT_I3100.req(`${chalk.blue(resourceName)} (${resourceType}): import with ${chalk.yellow(displayCandidateProps)}`, {
resource: {
type: resourceType,
props: candidateProps,
stringifiedProps: displayCandidateProps,
},
}));
if (importTheResource) {
return candidateProps;
}
}
Expand All @@ -364,35 +367,32 @@ export class ResourceImporter {
// We cannot auto-import this, ask the user for one of the props
// The only difference between these cases is what we print: for multiple properties, we print a preamble
const prefix = `${chalk.blue(resourceName)} (${resourceType})`;
let preamble;
let promptPattern;
const promptPattern = `${prefix}: enter %s`;
if (idPropSets.length > 1) {
preamble = `${prefix}: enter one of ${idPropSets.map(x => chalk.blue(x.join('+'))).join(', ')} to import (all empty to skip)`;
promptPattern = `${prefix}: enter %`;
} else {
promptPattern = `${prefix}: enter %`;
const preamble = `${prefix}: enter one of ${idPropSets.map(x => chalk.blue(x.join('+'))).join(', ')} to import (leave all empty to skip)`;
await this.ioHelper.defaults.info(preamble);
}

// Do the input loop here
if (preamble) {
await this.ioHelper.defaults.info(preamble);
}
for (const idProps of idPropSets) {
const input: Record<string, string> = {};
for (const idProp of idProps) {
// If we have a value from the template, use it as default. This will only be a partial
// identifier if present, otherwise we would have done the import already above.
const defaultValue = resourceProps[idProp] ?? '';

const prompt = [
promptPattern.replace(/%/g, chalk.blue(idProp)),
defaultValue
? `[${defaultValue}]`
: '(empty to skip)',
].join(' ') + ':';
const response = await promptly.prompt(prompt,
{ default: defaultValue, trim: true },
);
const response = await this.ioHelper.requestResponse(IO.CDK_TOOLKIT_I3110.req(
format(promptPattern, chalk.blue(idProp)),
{
resource: {
name: resourceName,
type: resourceType,
idProp,
},
responseDescription: defaultValue ? undefined : 'empty to skip',
},
defaultValue,
));

if (!response) {
break;
Expand Down
47 changes: 47 additions & 0 deletions packages/@aws-cdk/toolkit-lib/lib/payloads/import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { DataRequest } from './types';

/**
* A proposed resource import.
*/
export interface ResourceImportRequest {
/**
* The resource to be imported
*/
readonly resource: {
/**
* The CloudFormation resource type of the resource
*/
readonly type: string;
/**
* The properties of the imported resource
*/
readonly props: Record<string, any>;
/**
* A formattated string representation of the props.
*/
readonly stringifiedProps: string;
};
}

/**
* A resource that needs to be identified during an import.
*/
export interface ResourceIdentificationRequest extends DataRequest {
/**
* The resource that needs to be identified.
*/
readonly resource: {
/**
* The construct path or logical id of the resource.
*/
readonly name: string;
/**
* The type of the resource.
*/
readonly type: string;
/**
* The property that we try to identify the resource by.
*/
readonly idProp: string;
};
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/toolkit-lib/lib/payloads/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export * from './diff';
export * from './logs-monitor';
export * from './hotswap';
export * from './gc';
export * from './import';
3 changes: 2 additions & 1 deletion packages/@aws-cdk/toolkit-lib/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 24 additions & 1 deletion packages/@aws-cdk/toolkit-lib/test/_helpers/test-io-host.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { IIoHost, IoMessage, IoMessageLevel, IoRequest } from '../../lib/api/io';
import { when } from 'jest-when';
import type { IIoHost, IoMessage, IoMessageCode, IoMessageLevel, IoRequest } from '../../lib/api/io';
import type { IoHelper } from '../../lib/api/io/private';
import { asIoHelper, isMessageRelevantForLevel } from '../../lib/api/io/private';

Expand Down Expand Up @@ -66,4 +67,26 @@ export class TestIoHost implements IIoHost {
message: expect.stringContaining(m.containing),
}));
}

/**
* Mocks the response for a given message code.
*
* Use `requestSpy.mockReset()` to remove mock.
*/
public mockResponse(code: IoMessageCode, response: any) {
when(this.requestSpy)
.calledWith(expect.objectContaining({ code }))
.mockResolvedValue(response);
}

/**
* Mocks the response for a given message code, only once.
*
* Use `requestSpy.mockReset()` to remove mock.
*/
public mockResponseOnce(code: IoMessageCode, response: any) {
when(this.requestSpy)
.calledWith(expect.objectContaining({ code }))
.mockResolvedValueOnce(response);
}
}
Loading