Skip to content

feat: ESM plugins #468

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 5 commits into from
Jul 29, 2025
Merged
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
176 changes: 176 additions & 0 deletions v2/guide/plugins.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ To create a CDK Toolkit plugin, you first create a Node.js module, authored in T
[role="tablist"]
TypeScript::
+
*CommonJS*:::
+
[source,typescript,subs="verbatim,attributes"]
----
// Example plugin structure
Expand All @@ -53,9 +55,32 @@ const plugin: Plugin = {

export = plugin;
----
+
*ESM*:::
+
[source,typescript,subs="verbatim,attributes"]
----
// Example plugin structure
import type { IPluginHost, Plugin } from '@aws-cdk/cli-plugin-contract';

const plugin: Plugin = {
// Version of the plugin infrastructure (currently always '1')
version: '1',

// Initialization function called when the plugin is loaded
init(host: IPluginHost): void {
// Register your plugin functionality with the host
// For example, register a custom credential provider
}
};

export { plugin as 'module.exports' };
----

JavaScript::
+
*CommonJS*:::
+
[source,javascript,subs="verbatim,attributes"]
----
const plugin = {
Expand All @@ -71,6 +96,34 @@ const plugin = {

module.exports = plugin;
----
+
*ESM*:::
+
[source,javascript,subs="verbatim,attributes"]
----
const plugin = {
// Version of the plugin infrastructure (currently always '1')
version: '1',

// Initialization function called when the plugin is loaded
init(host) {
// Register your plugin functionality with the host
// For example, register a custom credential provider
}
};

export { plugin as 'module.exports' };
----
====

[NOTE]
====
CDK Toolkit plugins are loaded as CommonJS modules. You can build your plugin as an ECMAScript module (ESM), but have to link:https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require[adhere to some constraints]:

--
* The module cannot contain top-level `await`, or import any modules that contain top-level `await`.
* The module must ensure compatibility by exporting the plugin object under the `module.exports` export name: `export { plugin as 'module.exports' }`.
--
====

[#plugins-load-cli]
Expand Down Expand Up @@ -177,6 +230,8 @@ To implement a custom credential provider, create a class that implements the re
[role="tablist"]
TypeScript::
+
*CommonJS*:::
+
[source,typescript,subs="verbatim,attributes"]
----
import type { CredentialProviderSource, ForReading, ForWriting, IPluginHost, Plugin, PluginProviderResult, SDKv3CompatibleCredentials } from '@aws-cdk/cli-plugin-contract';
Expand Down Expand Up @@ -233,9 +288,70 @@ const plugin: Plugin = {

export = plugin;
----
+
*ESM*:::
+
[source,typescript,subs="verbatim,attributes"]
----
import type { CredentialProviderSource, ForReading, ForWriting, IPluginHost, Plugin, PluginProviderResult, SDKv3CompatibleCredentials } from '@aws-cdk/cli-plugin-contract';

class CustomCredentialProviderSource implements CredentialProviderSource {
// Friendly name for the provider, used in error messages
public readonly name: string = 'custom-credential-provider';

// Check if this provider is available on the current system
public async isAvailable(): Promise<boolean> {
// Return false if the plugin cannot be used
// For example, if it depends on files not present on the host
return true;
}

// Check if this provider can provide credentials for a specific account
public async canProvideCredentials(accountId: string): Promise<boolean> {
// Return false if the plugin cannot provide credentials for this account
// For example, if the account is not managed by this credential system
return true;
// You can use patterns to filter specific accounts
// return accountId.startsWith('123456');
}

// Get credentials for the specified account and access mode
// Returns PluginProviderResult which can be one of:
// - SDKv2CompatibleCredentials (AWS SDK v2 entered maintenance on Sept 8, 2024 and will reach end-of-life on Sept 8, 2025)
// - SDKv3CompatibleCredentialProvider
// - SDKv3CompatibleCredentials
public async getProvider(accountId: string, mode: ForReading | ForWriting): Promise<PluginProviderResult> {
// The access mode can be used to provide different credential sets
const readOnly = mode === 0 satisfies ForReading;

// Create appropriate credentials based on your authentication mechanism
const credentials: SDKv3CompatibleCredentials = {
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
// Add sessionToken if using temporary credentials
// sessionToken: 'AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4Olgk',
// expireTime: new Date(Date.now() + 3600 * 1000), // 1 hour from now
};

return credentials;
}
}

const plugin: Plugin = {
version: '1',
init(host: IPluginHost): void {
// Register the credential provider to the PluginHost.
host.registerCredentialProviderSource(new CustomCredentialProviderSource());
}
};

export { plugin as 'module.exports' };
----

JavaScript::
+
*CommonJS*:::
+
[source,javascript,subs="verbatim,attributes"]
----
// Implement the CredentialProviderSource interface
Expand Down Expand Up @@ -293,6 +409,66 @@ const plugin = {

module.exports = plugin;
----
+
*ESM*:::
+
[source,javascript,subs="verbatim,attributes"]
----
// Implement the CredentialProviderSource interface
class CustomCredentialProviderSource {
constructor() {
// Friendly name for the provider, used in error messages
this.name = 'custom-credential-provider';
}

// Check if this provider is available on the current system
async isAvailable() {
// Return false if the plugin cannot be used
// For example, if it depends on files not present on the host
return true;
}

// Check if this provider can provide credentials for a specific account
async canProvideCredentials(accountId) {
// Return false if the plugin cannot provide credentials for this account
// For example, if the account is not managed by this credential system
return true;
// You can use patterns to filter specific accounts
// return accountId.startsWith('123456');
}

// Get credentials for the specified account and access mode
// Returns PluginProviderResult which can be one of:
// - SDKv2CompatibleCredentials (AWS SDK v2 entered maintenance on Sept 8, 2024 and will reach end-of-life on Sept 8, 2025)
// - SDKv3CompatibleCredentialProvider
// - SDKv3CompatibleCredentials
async getProvider(accountId, mode) {
// The access mode can be used to provide different credential sets
const readOnly = mode === 0; // 0 indicates ForReading; 1 indicates ForWriting

// Create appropriate credentials based on your authentication mechanism
const credentials = {
accessKeyId: 'ASIAIOSFODNN7EXAMPLE',
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
// Add sessionToken if using temporary credentials
// sessionToken: 'AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4Olgk',
// expireTime: new Date(Date.now() + 3600 * 1000), // 1 hour from now
};

return credentials;
}
}

const plugin = {
version: '1',
init(host) {
// Register the credential provider to the PluginHost.
host.registerCredentialProviderSource(new CustomCredentialProviderSource());
}
};

export { plugin as 'module.exports' };
----
====

[IMPORTANT]
Expand Down