Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
8dad596
init
go-to-k Jan 11, 2026
a267160
wip for assetParallelism
go-to-k Jan 11, 2026
4196ccc
modify
go-to-k Jan 12, 2026
8be83e6
integ
go-to-k Jan 12, 2026
ef1e062
tweak
go-to-k Jan 12, 2026
5240325
tweak
go-to-k Jan 12, 2026
7ca3fdf
README
go-to-k Jan 12, 2026
5fa07b2
fix
go-to-k Jan 12, 2026
f137fcc
Merge branch 'main' into publish
go-to-k Jan 12, 2026
78db795
comments
go-to-k Jan 12, 2026
81fa49b
removePublishedAssets
go-to-k Jan 12, 2026
d2abc2b
publish.test
go-to-k Jan 12, 2026
303f186
imports
go-to-k Jan 12, 2026
bd89d27
Merge branch 'main' of https://github.com/go-to-k/aws-cdk-cli into pu…
go-to-k Jan 17, 2026
e592c9e
mock
go-to-k Jan 17, 2026
a0ee290
mock
go-to-k Jan 17, 2026
69980aa
Merge branch 'main' into publish
go-to-k Jan 17, 2026
d2aed1f
README
go-to-k Jan 17, 2026
afb379c
Merge branch 'main' of https://github.com/go-to-k/aws-cdk-cli into pu…
go-to-k Feb 22, 2026
06769f2
implement in toolkit-lib
go-to-k Feb 22, 2026
170fc97
rm success
go-to-k Feb 22, 2026
c039eb6
exclusively
go-to-k Feb 22, 2026
9ac288c
rm toolkitStackName
go-to-k Feb 22, 2026
514989a
use concurrency
go-to-k Feb 22, 2026
198eeef
response
go-to-k Feb 22, 2026
678377f
test
go-to-k Feb 22, 2026
31b4858
publishedAssets
go-to-k Feb 22, 2026
4d8e80f
improve
go-to-k Feb 22, 2026
5a10ccf
fix
go-to-k Feb 22, 2026
c199031
modify
go-to-k Feb 23, 2026
51b4b1d
rm
go-to-k Feb 23, 2026
86ddcff
implement in toolkit-lib
go-to-k Feb 23, 2026
2b4ce75
forgot to build
go-to-k Feb 23, 2026
ff5920e
add createBuildAssetFunction and createPublishAssetFunction
go-to-k Feb 23, 2026
4025ad8
rm assetParallelism
go-to-k Feb 23, 2026
7b00e47
fix lint
go-to-k Feb 23, 2026
9e96c9a
add publish to BUNDLING_COMMANDS
go-to-k Feb 23, 2026
bcfc439
refactor
go-to-k Feb 24, 2026
49d70d0
Merge branch 'main' into publish
go-to-k Feb 27, 2026
b724ab8
Merge branch 'main' of https://github.com/go-to-k/aws-cdk-cli into pu…
go-to-k Mar 31, 2026
f41f437
use concurrency only
go-to-k Mar 31, 2026
577a95d
rm roleArn
go-to-k Mar 31, 2026
4addfb8
All assets are already published
go-to-k Mar 31, 2026
e524311
PublishAssetsPayload
go-to-k Mar 31, 2026
9359fb9
CDK_TOOLKIT_I9402
go-to-k Mar 31, 2026
7ec9139
fix tests
go-to-k Mar 31, 2026
f6ce6e8
message-registry.md
go-to-k Mar 31, 2026
6ccd9b7
fix
go-to-k Mar 31, 2026
169a0aa
fix tests
go-to-k Mar 31, 2026
42ace23
Merge branch 'main' into publish
go-to-k Mar 31, 2026
cb33027
typo
go-to-k Mar 31, 2026
cab10fa
Merge branch 'publish' of https://github.com/go-to-k/aws-cdk-cli into…
go-to-k Mar 31, 2026
9d16908
cdk publish-assets
go-to-k Mar 31, 2026
a047696
tweak for doc
go-to-k Mar 31, 2026
4d71d48
Merge branch 'main' into publish
go-to-k Mar 31, 2026
1d83be9
table align
go-to-k Apr 1, 2026
afd8f3b
unify assets payload
go-to-k Apr 1, 2026
cc2600c
concurrency to 4
go-to-k Apr 1, 2026
3025828
requireUnstableFeature
go-to-k Apr 1, 2026
c6d0df4
use PublishAssetsOptions in toolkit-lib
go-to-k Apr 1, 2026
2ff8cce
Merge branch 'main' into publish
go-to-k Apr 1, 2026
b1b9375
chore: self mutation
github-actions[bot] Apr 1, 2026
999a579
Merge branch 'main' into publish
mrgrain Apr 1, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { integTest, withDefaultFixture } from '../../../lib';

jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime

integTest(
'publish-assets is idempotent and --force re-uploads',
withDefaultFixture(async (fixture) => {
const stackName = 'lambda';
const fullStackName = fixture.fullStackName(stackName);

// First publish
const firstOutput = await fixture.cdk(['publish-assets', fullStackName, '--unstable=publish-assets']);
expect(firstOutput).toMatch('Assets published successfully');

// Second publish without --force should detect nothing to do
const secondOutput = await fixture.cdk(['publish-assets', fullStackName, '--unstable=publish-assets']);
expect(secondOutput).toMatch('All assets are already published');

// Third publish with --force should re-upload
const forceOutput = await fixture.cdk(['publish-assets', fullStackName, '--unstable=publish-assets', '--force']);
expect(forceOutput).toMatch('Assets published successfully');
}),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { integTest, withDefaultFixture } from '../../../lib';

jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime

integTest(
'publish-assets then deploy detects already uploaded assets',
withDefaultFixture(async (fixture) => {
const stackName = 'lambda';
const fullStackName = fixture.fullStackName(stackName);

// First, publish assets
const publishOutput = await fixture.cdk(['publish-assets', fullStackName, '--unstable=publish-assets']);
expect(publishOutput).toMatch('Assets published successfully');

// Then deploy the same stack; it should detect the already published assets and skip re-publishing
const deployOutput = await fixture.cdkDeploy(stackName, { options: ['-v'], captureStderr: true });
expect(deployOutput).toMatch(/0 still need to be published/);

// Clean up
await fixture.cdkDestroy(stackName);
}),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DescribeStacksCommand } from '@aws-sdk/client-cloudformation';
import { integTest, withDefaultFixture } from '../../../lib';

jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime

integTest(
'publish-assets without deploying',
withDefaultFixture(async (fixture) => {
const stackName = 'lambda';
const fullStackName = fixture.fullStackName(stackName);

const output = await fixture.cdk(['publish-assets', fullStackName, '--unstable=publish-assets']);
expect(output).toMatch('Assets published successfully');

// assert the stack was not deployed
await expect(fixture.aws.cloudFormation.send(new DescribeStacksCommand({ StackName: fullStackName })))
.rejects.toThrow(/does not exist/);
}),
);
3 changes: 3 additions & 0 deletions packages/@aws-cdk/toolkit-lib/docs/message-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ Please let us know by [opening an issue](https://github.com/aws/aws-cdk-cli/issu
| `CDK_TOOLKIT_I9900` | Bootstrap results on success | `result` | [cxapi.Environment](https://docs.aws.amazon.com/cdk/api/v2/docs/@aws-cdk_cx-api.Environment.html) |
| `CDK_TOOLKIT_E9900` | Bootstrap failed | `error` | {@link ErrorPayload} |
| `CDK_TOOLKIT_I9300` | Confirm the feature flag configuration changes | `info` | {@link FeatureFlagChangeRequest} |
| `CDK_TOOLKIT_I9400` | All assets are already published | `info` | n/a |
| `CDK_TOOLKIT_I9401` | Publishing assets | `info` | {@link AssetsPayload} |
| `CDK_TOOLKIT_I9402` | Publish assets results on success | `result` | {@link AssetsPayload} |
| `CDK_TOOLKIT_I0100` | Notices decoration (the header or footer of a list of notices) | `info` | n/a |
| `CDK_TOOLKIT_W0101` | A notice that is marked as a warning | `warn` | n/a |
| `CDK_TOOLKIT_E0101` | A notice that is marked as an error | `error` | n/a |
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/toolkit-lib/lib/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './destroy';
export * from './diff';
export * from './drift';
export * from './list';
export * from './publish-assets';
export * from './refactor';
export * from './rollback';
export * from './synth';
Expand Down
32 changes: 32 additions & 0 deletions packages/@aws-cdk/toolkit-lib/lib/actions/publish-assets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { IManifestEntry } from '@aws-cdk/cdk-assets-lib';
import type { StackSelector } from '../../api/cloud-assembly';

export interface PublishAssetsOptions {
/**
* Select stacks to publish assets for
*
* @default - All stacks
*/
readonly stacks?: StackSelector;

/**
* Always publish assets, even if they are already published
*
* @default false
*/
readonly force?: boolean;

/**
* Maximum number of simultaneous asset operations (building and publishing)
*
* @default 4
*/
readonly concurrency?: number;
}

export interface PublishAssetsResult {
/**
* List of assets that were published
*/
readonly publishedAssets: IManifestEntry[];
}
21 changes: 19 additions & 2 deletions packages/@aws-cdk/toolkit-lib/lib/api/io/private/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { HotswapDeploymentDetails, HotswapDeploymentAttempt, HotswappableCh
import type { ResourceIdentificationRequest, ResourceImportRequest } from '../../../payloads/import';
import type { StackDetailsPayload } from '../../../payloads/list';
import type { CloudWatchLogEvent, CloudWatchLogMonitorControlEvent } from '../../../payloads/logs-monitor';
import type { AssetsPayload } from '../../../payloads/publish-assets';
import type { RefactorResult } from '../../../payloads/refactor';
import type { StackRollbackProgress } from '../../../payloads/rollback';
import type { MfaTokenRequest, SdkTrace } from '../../../payloads/sdk';
Expand Down Expand Up @@ -423,7 +424,7 @@ export const IO = {
description: 'Refactor execution not yet supported',
}),

// 9: Bootstrap & gc (9xxx)
// 9: Bootstrap, gc, flags & publish (9xxx)
CDK_TOOLKIT_I9000: make.info<Duration>({
code: 'CDK_TOOLKIT_I9000',
description: 'Provides bootstrap times',
Expand Down Expand Up @@ -453,13 +454,29 @@ export const IO = {
interface: 'ErrorPayload',
}),

// flags (93xxx)
// flags (93xx)
CDK_TOOLKIT_I9300: make.info<FeatureFlagChangeRequest>({
code: 'CDK_TOOLKIT_I9300',
description: 'Confirm the feature flag configuration changes',
interface: 'FeatureFlagChangeRequest',
}),

// publish (94xx)
CDK_TOOLKIT_I9400: make.info({
code: 'CDK_TOOLKIT_I9400',
description: 'All assets are already published',
}),
CDK_TOOLKIT_I9401: make.info<AssetsPayload>({
code: 'CDK_TOOLKIT_I9401',
description: 'Publishing assets',
interface: 'AssetsPayload',
}),
CDK_TOOLKIT_I9402: make.result<AssetsPayload>({
code: 'CDK_TOOLKIT_I9402',
description: 'Publish assets results on success',
interface: 'AssetsPayload',
}),

// Notices
CDK_TOOLKIT_I0100: make.info({
code: 'CDK_TOOLKIT_I0100',
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/toolkit-lib/lib/api/io/toolkit-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type ToolkitAction =
| 'deploy'
| 'drift'
| 'rollback'
| 'publish-assets'
| 'watch'
| 'destroy'
| 'doctor'
Expand Down
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 @@ -10,6 +10,7 @@ export * from './stack-activity';
export * from './synth';
export * from './types';
export * from './progress';
export * from './publish-assets';
export * from './refactor';
export * from './watch';
export * from './stack-details';
Expand Down
8 changes: 8 additions & 0 deletions packages/@aws-cdk/toolkit-lib/lib/payloads/publish-assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { IManifestEntry } from '@aws-cdk/cdk-assets-lib';

export interface AssetsPayload {
/**
* List of assets
*/
readonly assets: IManifestEntry[];
}
160 changes: 127 additions & 33 deletions packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import type { DiffOptions } from '../actions/diff';
import { appendObject, prepareDiff } from '../actions/diff/private';
import type { DriftOptions, DriftResult } from '../actions/drift';
import { type ListOptions } from '../actions/list';
import type { PublishAssetsOptions, PublishAssetsResult } from '../actions/publish-assets';
import type { RefactorOptions } from '../actions/refactor';
import { type RollbackOptions } from '../actions/rollback';
import { type SynthOptions } from '../actions/synth';
Expand Down Expand Up @@ -164,7 +165,7 @@ export interface ToolkitOptions {
* Names of toolkit features that are still under development, and may change in
* the future.
*/
export type UnstableFeature = 'refactor' | 'flags';
export type UnstableFeature = 'refactor' | 'flags' | 'publish-assets';

/**
* The AWS CDK Programmatic Toolkit
Expand Down Expand Up @@ -492,6 +493,80 @@ export class Toolkit extends CloudAssemblySourceBuilder {
return allDriftResults;
}

/**
* Publish Assets Action
*
* Publishes assets for the selected stacks without deploying
*/
public async publishAssets(cx: ICloudAssemblySource, options: PublishAssetsOptions = {}): Promise<PublishAssetsResult> {
this.requireUnstableFeature('publish-assets');

const ioHelper = asIoHelper(this.ioHost, 'publish-assets');
const selectStacks = stacksOpt(options);
await using assembly = await synthAndMeasure(ioHelper, cx, selectStacks);

const stackCollection = await assembly.selectStacksV2(selectStacks);
await this.validateStacksMetadata(stackCollection, ioHelper);

if (stackCollection.stackCount === 0) {
await ioHelper.notify(IO.CDK_TOOLKIT_E5001.msg('No stacks selected'));
return {
publishedAssets: [],
};
}

const deployments = await this.deploymentsForAction('publish-assets');

const stacks = stackCollection.stackArtifacts;
const stacksAndTheirAssetManifests = stacks.flatMap((stack) => [
stack,
...stack.dependencies.filter(x => cxapi.AssetManifestArtifact.isAssetManifestArtifact(x)),
]);

const workGraph = new WorkGraphBuilder(
ioHelper,
true, // prebuild all assets
).build(stacksAndTheirAssetManifests);

if (!options.force) {
await removePublishedAssetsFromWorkGraph(workGraph, deployments, options);
}

const assetNodes = Object.values(workGraph.nodes)
.filter((n): n is AssetPublishNode => n.type === 'asset-publish');
Comment thread
mrgrain marked this conversation as resolved.

if (assetNodes.length === 0) {
await ioHelper.notify(IO.CDK_TOOLKIT_I9400.msg(chalk.green('\n✨ All assets are already published\n')));
return {
publishedAssets: [],
};
}

const assets = assetNodes.map(n => n.asset);
await ioHelper.notify(IO.CDK_TOOLKIT_I9401.msg('Publishing assets', { assets }));

const concurrency = options.concurrency ?? 4;
const graphConcurrency: Concurrency = {
'stack': 1,
'asset-build': concurrency,
'asset-publish': concurrency,
};

await workGraph.doParallel(graphConcurrency, {
deployStack: async () => {
// No-op: we're only publishing assets, not deploying
},
buildAsset: this.createBuildAssetFunction(ioHelper, deployments, undefined),
publishAsset: this.createPublishAssetFunction(ioHelper, deployments, undefined, options.force),
});

await ioHelper.notify(IO.CDK_TOOLKIT_I9402.msg(chalk.green('\n✨ Assets published successfully\n'), { assets }));

return {
publishedAssets: assets,
};
}

/**
* List Action
*
Expand Down Expand Up @@ -558,36 +633,6 @@ export class Toolkit extends CloudAssemblySourceBuilder {
const stackOutputs: { [key: string]: any } = {};
const outputsFile = options.outputsFile;

const buildAsset = async (assetNode: AssetBuildNode) => {
const buildAssetSpan = await ioHelper.span(SPAN.BUILD_ASSET).begin({
asset: assetNode.asset,
});
await deployments.buildSingleAsset(
assetNode.assetManifestArtifact,
assetNode.assetManifest,
assetNode.asset,
{
stack: assetNode.parentStack,
roleArn: options.roleArn,
stackName: assetNode.parentStack.stackName,
},
);
await buildAssetSpan.end();
};

const publishAsset = async (assetNode: AssetPublishNode) => {
const publishAssetSpan = await ioHelper.span(SPAN.PUBLISH_ASSET).begin({
asset: assetNode.asset,
});
await deployments.publishSingleAsset(assetNode.assetManifest, assetNode.asset, {
stack: assetNode.parentStack,
roleArn: options.roleArn,
stackName: assetNode.parentStack.stackName,
forcePublish: options.forceAssetPublishing,
});
await publishAssetSpan.end();
};

const deployStack = async (stackNode: StackNode) => {
const stack = stackNode.stack;
if (stackCollection.stackCount !== 1) {
Expand Down Expand Up @@ -850,8 +895,8 @@ export class Toolkit extends CloudAssemblySourceBuilder {

await workGraph.doParallel(graphConcurrency, {
deployStack,
buildAsset,
publishAsset,
buildAsset: this.createBuildAssetFunction(ioHelper, deployments, options.roleArn),
publishAsset: this.createPublishAssetFunction(ioHelper, deployments, options.roleArn, options.forceAssetPublishing),
});

return ret;
Expand Down Expand Up @@ -1427,6 +1472,55 @@ export class Toolkit extends CloudAssemblySourceBuilder {
throw new ToolkitError('UnstableFeatureNotEnabled', `Unstable feature '${requestedFeature}' is not enabled. Please enable it under 'unstableFeatures'`);
}
}

/**
* Create a buildAsset function for use in WorkGraph
*/
private createBuildAssetFunction(
ioHelper: IoHelper,
deployments: Deployments,
roleArn: string | undefined,
) {
return async (assetNode: AssetBuildNode) => {
const buildAssetSpan = await ioHelper.span(SPAN.BUILD_ASSET).begin({
asset: assetNode.asset,
});
await deployments.buildSingleAsset(
assetNode.assetManifestArtifact,
assetNode.assetManifest,
assetNode.asset,
{
stack: assetNode.parentStack,
roleArn,
stackName: assetNode.parentStack.stackName,
},
);
await buildAssetSpan.end();
};
}

/**
* Create a publishAsset function for use in WorkGraph
*/
private createPublishAssetFunction(
ioHelper: IoHelper,
deployments: Deployments,
roleArn: string | undefined,
forcePublish?: boolean,
) {
return async (assetNode: AssetPublishNode) => {
const publishAssetSpan = await ioHelper.span(SPAN.PUBLISH_ASSET).begin({
asset: assetNode.asset,
});
await deployments.publishSingleAsset(assetNode.assetManifest, assetNode.asset, {
stack: assetNode.parentStack,
roleArn,
stackName: assetNode.parentStack.stackName,
forcePublish,
});
await publishAssetSpan.end();
};
}
}

/**
Expand Down
Loading
Loading