Skip to content

Commit

Permalink
feat(cli): bundle dependencies (#18667)
Browse files Browse the repository at this point in the history
Use `esbuild` via a custom new tool to bundle CLI dependencies and release a package with no runtime dependencies.

More details as to reasoning and implementation [here](https://github.com/aws/aws-cdk/blob/epolon/cli-bundle/tools/%40aws-cdk/node-bundle/README.md).

## Note

This PR has some implications on programmatic usage of the CLI. 

Namely, deep imports like so:

```ts
import { PluginHost } from 'aws-cdk/lib/plugin'
```

Will no longer be available. These imports are considered private and should not have been used in the first place. 
Instead, switch to:

```ts
import { PluginHost } from 'aws-cdk'
```

If your import isn't available from the top-level, it means that export is actually private, and should be avoided.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
iliapolo committed Feb 24, 2022
1 parent 14b6c9c commit 31d135f
Show file tree
Hide file tree
Showing 50 changed files with 7,803 additions and 143 deletions.
2 changes: 1 addition & 1 deletion packages/@aws-cdk/cloudformation-diff/lib/format-table.ts
@@ -1,5 +1,5 @@
import * as chalk from 'chalk';
import * as stringWidth from 'string-width';
import stringWidth from 'string-width';
import * as table from 'table';

/**
Expand Down
5 changes: 4 additions & 1 deletion packages/@aws-cdk/cloudformation-diff/lib/iam/statement.ts
@@ -1,6 +1,9 @@
import * as deepEqual from 'fast-deep-equal';
import { deepRemoveUndefined } from '../util';

// namespace object imports won't work in the bundle for function exports
// eslint-disable-next-line @typescript-eslint/no-require-imports
const deepEqual = require('fast-deep-equal');

export class Statement {
/**
* Statement ID
Expand Down
14 changes: 14 additions & 0 deletions packages/aws-cdk/NOTICE
@@ -1,2 +1,16 @@
AWS Cloud Development Kit (AWS CDK)
Copyright 2018-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Third party attributions of this package can be found in the THIRD_PARTY_LICENSES file
3,768 changes: 3,768 additions & 0 deletions packages/aws-cdk/THIRD_PARTY_LICENSES

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts
Expand Up @@ -3,6 +3,7 @@ import * as path from 'path';
import * as cxapi from '@aws-cdk/cx-api';
import { warning } from '../../logging';
import { loadStructuredFile, toYAML } from '../../serialize';
import { rootDir } from '../../util/directories';
import { SdkProvider } from '../aws-auth';
import { DeployStackResult } from '../deploy-stack';
import { BootstrapEnvironmentOptions, BootstrappingParameters } from './bootstrap-props';
Expand Down Expand Up @@ -170,7 +171,7 @@ export class Bootstrapper {
case 'custom':
return loadStructuredFile(this.source.templateFile);
case 'default':
return loadStructuredFile(path.join(__dirname, 'bootstrap-template.yaml'));
return loadStructuredFile(path.join(rootDir(), 'lib', 'api', 'bootstrap', 'bootstrap-template.yaml'));
case 'legacy':
return legacyBootstrapTemplate(params);
}
Expand Down
4 changes: 3 additions & 1 deletion packages/aws-cdk/lib/api/cloudformation-deployments.ts
Expand Up @@ -5,7 +5,9 @@ import * as fs from 'fs-extra';
import { Tag } from '../cdk-toolkit';
import { debug, warning } from '../logging';
import { publishAssets } from '../util/asset-publishing';
import { Mode, SdkProvider, ISDK } from './aws-auth';
import { Mode } from './aws-auth/credentials';
import { ISDK } from './aws-auth/sdk';
import { SdkProvider } from './aws-auth/sdk-provider';
import { deployStack, DeployStackResult, destroyStack } from './deploy-stack';
import { LazyListStackResources, ListStackResources } from './evaluate-cloudformation-template';
import { ToolkitInfo } from './toolkit-info';
Expand Down
6 changes: 5 additions & 1 deletion packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts
@@ -1,11 +1,15 @@
import * as cxapi from '@aws-cdk/cx-api';
import * as chalk from 'chalk';
import * as minimatch from 'minimatch';
import * as semver from 'semver';
import { error, print, warning } from '../../logging';
import { flatten } from '../../util';
import { versionNumber } from '../../version';

// namespace object imports won't work in the bundle for function exports
// eslint-disable-next-line @typescript-eslint/no-require-imports
const minimatch = require('minimatch');


export enum DefaultSelection {
/**
* Returns an empty selection in case there are no selectors.
Expand Down
5 changes: 4 additions & 1 deletion packages/aws-cdk/lib/api/cxapp/environments.ts
@@ -1,8 +1,11 @@
import * as cxapi from '@aws-cdk/cx-api';
import * as minimatch from 'minimatch';
import { SdkProvider } from '../aws-auth';
import { StackCollection } from './cloud-assembly';

// namespace object imports won't work in the bundle for function exports
// eslint-disable-next-line @typescript-eslint/no-require-imports
const minimatch = require('minimatch');

export function looksLikeGlob(environment: string) {
return environment.indexOf('*') > -1;
}
Expand Down
7 changes: 5 additions & 2 deletions packages/aws-cdk/lib/api/hotswap/lambda-functions.ts
@@ -1,11 +1,14 @@
import { Writable } from 'stream';
import * as archiver from 'archiver';
import * as AWS from 'aws-sdk';
import { flatMap } from '../../util';
import { ISDK } from '../aws-auth';
import { CfnEvaluationException, EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template';
import { ChangeHotswapImpact, ChangeHotswapResult, HotswapOperation, HotswappableChangeCandidate } from './common';

// namespace object imports won't work in the bundle for function exports
// eslint-disable-next-line @typescript-eslint/no-require-imports
const archiver = require('archiver');

/**
* Returns `ChangeHotswapImpact.REQUIRES_FULL_DEPLOYMENT` if the change cannot be short-circuited,
* `ChangeHotswapImpact.IRRELEVANT` if the change is irrelevant from a short-circuit perspective
Expand Down Expand Up @@ -366,7 +369,7 @@ function zipString(fileName: string, rawString: string): Promise<Buffer> {

const archive = archiver('zip');

archive.on('error', (err) => {
archive.on('error', (err: any) => {
reject(err);
});

Expand Down
4 changes: 3 additions & 1 deletion packages/aws-cdk/lib/api/util/display.ts
@@ -1,4 +1,6 @@
import * as wrapAnsi from 'wrap-ansi';
// namespace object imports won't work in the bundle for function exports
// eslint-disable-next-line @typescript-eslint/no-require-imports
const wrapAnsi = require('wrap-ansi');

/**
* A class representing rewritable display lines
Expand Down
27 changes: 16 additions & 11 deletions packages/aws-cdk/lib/cli.ts
Expand Up @@ -2,8 +2,8 @@ import 'source-map-support/register';
import * as cxapi from '@aws-cdk/cx-api';
import '@jsii/check-node/run';
import * as chalk from 'chalk';
import * as yargs from 'yargs';

import type { Argv } from 'yargs';
import { SdkProvider } from '../lib/api/aws-auth';
import { BootstrapSource, Bootstrapper } from '../lib/api/bootstrap';
import { CloudFormationDeployments } from '../lib/api/cloudformation-deployments';
Expand All @@ -24,6 +24,11 @@ import { PluginHost } from '../lib/plugin';
import { Command, Configuration, Settings } from '../lib/settings';
import * as version from '../lib/version';

// https://github.com/yargs/yargs/issues/1929
// https://github.com/evanw/esbuild/issues/1492
// eslint-disable-next-line @typescript-eslint/no-require-imports
const yargs = require('yargs');

/* eslint-disable max-len */
/* eslint-disable @typescript-eslint/no-shadow */ // yargs

Expand Down Expand Up @@ -73,14 +78,14 @@ async function parseCommandLineArguments() {
.option('output', { type: 'string', alias: 'o', desc: 'Emits the synthesized cloud assembly into a directory (default: cdk.out)', requiresArg: true })
.option('notices', { type: 'boolean', desc: 'Show relevant notices' })
.option('no-color', { type: 'boolean', desc: 'Removes colors and other style from console output', default: false })
.command(['list [STACKS..]', 'ls [STACKS..]'], 'Lists all stacks in the app', yargs => yargs
.command(['list [STACKS..]', 'ls [STACKS..]'], 'Lists all stacks in the app', (yargs: Argv) => yargs
.option('long', { type: 'boolean', default: false, alias: 'l', desc: 'Display environment information for each stack' }),
)
.command(['synthesize [STACKS..]', 'synth [STACKS..]'], 'Synthesizes and prints the CloudFormation template for this stack', yargs => yargs
.command(['synthesize [STACKS..]', 'synth [STACKS..]'], 'Synthesizes and prints the CloudFormation template for this stack', (yargs: Argv) => yargs
.option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only synthesize requested stacks, don\'t include dependencies' })
.option('validation', { type: 'boolean', desc: 'After synthesis, validate stacks with the "validateOnSynth" attribute set (can also be controlled with CDK_VALIDATION)', default: true })
.option('quiet', { type: 'boolean', alias: 'q', desc: 'Do not output CloudFormation Template to stdout', default: false }))
.command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment', yargs => yargs
.command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment', (yargs: Argv) => yargs
.option('bootstrap-bucket-name', { type: 'string', alias: ['b', 'toolkit-bucket-name'], desc: 'The name of the CDK toolkit bucket; bucket will be created and must not exist', default: undefined })
.option('bootstrap-kms-key-id', { type: 'string', desc: 'AWS KMS master key ID used for the SSE-KMS encryption', default: undefined, conflicts: 'bootstrap-customer-key' })
.option('bootstrap-customer-key', { type: 'boolean', desc: 'Create a Customer Master Key (CMK) for the bootstrap bucket (you will be charged but can customize permissions, modern bootstrapping only)', default: undefined, conflicts: 'bootstrap-kms-key-id' })
Expand All @@ -97,7 +102,7 @@ async function parseCommandLineArguments() {
.option('toolkit-stack-name', { type: 'string', desc: 'The name of the CDK toolkit stack to create', requiresArg: true })
.option('template', { type: 'string', requiresArg: true, desc: 'Use the template from the given file instead of the built-in one (use --show-template to obtain an example)' }),
)
.command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs
.command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', (yargs: Argv) => yargs
.option('all', { type: 'boolean', default: false, desc: 'Deploy all available stacks' })
.option('build-exclude', { type: 'array', alias: 'E', nargs: 1, desc: 'Do not rebuild asset with the given ID. Can be specified multiple times', default: [] })
.option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only deploy requested stacks, don\'t include dependencies' })
Expand Down Expand Up @@ -142,7 +147,7 @@ async function parseCommandLineArguments() {
"Only in effect if specified alongside the '--watch' option",
}),
)
.command('watch [STACKS..]', "Shortcut for 'deploy --watch'", yargs => yargs
.command('watch [STACKS..]', "Shortcut for 'deploy --watch'", (yargs: Argv) => yargs
// I'm fairly certain none of these options, present for 'deploy', make sense for 'watch':
// .option('all', { type: 'boolean', default: false, desc: 'Deploy all available stacks' })
// .option('ci', { type: 'boolean', desc: 'Force CI detection', default: process.env.CI !== undefined })
Expand Down Expand Up @@ -182,11 +187,11 @@ async function parseCommandLineArguments() {
"'true' by default, use --no-logs to turn off",
}),
)
.command('destroy [STACKS..]', 'Destroy the stack(s) named STACKS', yargs => yargs
.command('destroy [STACKS..]', 'Destroy the stack(s) named STACKS', (yargs: Argv) => yargs
.option('all', { type: 'boolean', default: false, desc: 'Destroy all available stacks' })
.option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only destroy requested stacks, don\'t include dependees' })
.option('force', { type: 'boolean', alias: 'f', desc: 'Do not ask for confirmation before destroying the stacks' }))
.command('diff [STACKS..]', 'Compares the specified stack with the deployed stack or a local template file, and returns with status 1 if any difference is found', yargs => yargs
.command('diff [STACKS..]', 'Compares the specified stack with the deployed stack or a local template file, and returns with status 1 if any difference is found', (yargs: Argv) => yargs
.option('exclusively', { type: 'boolean', alias: 'e', desc: 'Only diff requested stacks, don\'t include dependencies' })
.option('context-lines', { type: 'number', desc: 'Number of context lines to include in arbitrary JSON diff rendering', default: 3, requiresArg: true })
.option('template', { type: 'string', desc: 'The path to the CloudFormation template to compare with', requiresArg: true })
Expand All @@ -196,15 +201,15 @@ async function parseCommandLineArguments() {
.command('metadata [STACK]', 'Returns all metadata associated with this stack')
.command(['acknowledge [ID]', 'ack [ID]'], 'Acknowledge a notice so that it does not show up anymore')
.command('notices', 'Returns a list of relevant notices')
.command('init [TEMPLATE]', 'Create a new, empty CDK project from a template.', yargs => yargs
.command('init [TEMPLATE]', 'Create a new, empty CDK project from a template.', (yargs: Argv) => yargs
.option('language', { type: 'string', alias: 'l', desc: 'The language to be used for the new project (default can be configured in ~/.cdk.json)', choices: initTemplateLanguages })
.option('list', { type: 'boolean', desc: 'List the available templates' })
.option('generate-only', { type: 'boolean', default: false, desc: 'If true, only generates project files, without executing additional operations such as setting up a git repo, installing dependencies or compiling the project' }),
)
.command('context', 'Manage cached context values', yargs => yargs
.command('context', 'Manage cached context values', (yargs: Argv) => yargs
.option('reset', { alias: 'e', desc: 'The context key (or its index) to reset', type: 'string', requiresArg: true })
.option('clear', { desc: 'Clear all context', type: 'boolean' }))
.command(['docs', 'doc'], 'Opens the reference documentation in a browser', yargs => yargs
.command(['docs', 'doc'], 'Opens the reference documentation in a browser', (yargs: Argv) => yargs
.option('browser', {
alias: 'b',
desc: 'the command to use to open the browser, using %u as a placeholder for the path of the file to open',
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk/lib/command-api.ts
@@ -1,4 +1,4 @@
import * as yargs from 'yargs';
import type { Arguments } from 'yargs';
import { SdkProvider } from './api/aws-auth';
import { Configuration } from './settings';

Expand All @@ -15,7 +15,7 @@ import { Configuration } from './settings';
* The parts of the world that our command functions have access to
*/
export interface CommandOptions {
args: yargs.Arguments;
args: Arguments;
configuration: Configuration;
aws: SdkProvider;
}
Expand Down
12 changes: 6 additions & 6 deletions packages/aws-cdk/lib/init.ts
Expand Up @@ -5,7 +5,7 @@ import * as chalk from 'chalk';
import * as fs from 'fs-extra';
import * as semver from 'semver';
import { error, print, warning } from './logging';
import { cdkHomeDir } from './util/directories';
import { cdkHomeDir, rootDir } from './util/directories';
import { versionNumber } from './version';

export type InvokeHook = (targetDirectory: string) => Promise<void>;
Expand Down Expand Up @@ -156,11 +156,11 @@ export class InitTemplate {
}

private expand(template: string, project: ProjectInfo) {
const MATCH_VER_BUILD = /\+[a-f0-9]+$/; // Matches "+BUILD" in "x.y.z-beta+BUILD"
// eslint-disable-next-line @typescript-eslint/no-require-imports
const cdkVersion = require('../package.json').version.replace(MATCH_VER_BUILD, '');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const constructsVersion = require('../package.json').devDependencies.constructs.replace(MATCH_VER_BUILD, '');
const manifest = require(path.join(rootDir(), 'package.json'));
const MATCH_VER_BUILD = /\+[a-f0-9]+$/; // Matches "+BUILD" in "x.y.z-beta+BUILD"
const cdkVersion = manifest.version.replace(MATCH_VER_BUILD, '');
const constructsVersion = manifest.devDependencies.constructs.replace(MATCH_VER_BUILD, '');
return template.replace(/%name%/g, project.name)
.replace(/%name\.camelCased%/g, camelCase(project.name))
.replace(/%name\.PascalCased%/g, camelCase(project.name, { pascalCase: true }))
Expand Down Expand Up @@ -212,7 +212,7 @@ function versionedTemplatesDir(): Promise<string> {
currentVersion = '1.0.0';
}
const majorVersion = semver.major(currentVersion);
resolve(path.join(__dirname, 'init-templates', `v${majorVersion}`));
resolve(path.join(rootDir(), 'lib', 'init-templates', `v${majorVersion}`));
});
}

Expand Down
19 changes: 10 additions & 9 deletions packages/aws-cdk/lib/logging.ts
Expand Up @@ -13,6 +13,16 @@ const logger = (stream: Writable, styles?: StyleFn[]) => (fmt: string, ...args:
stream.write(str + '\n');
};

export enum LogLevel {
/** Not verbose at all */
DEFAULT = 0,
/** Pretty verbose */
DEBUG = 1,
/** Extremely verbose */
TRACE = 2
}


export let logLevel = LogLevel.DEFAULT;

export function setLogLevel(newLogLevel: LogLevel) {
Expand Down Expand Up @@ -47,12 +57,3 @@ export type LoggerFunction = (fmt: string, ...args: any[]) => void;
export function prefix(prefixString: string, fn: LoggerFunction): LoggerFunction {
return (fmt: string, ...args: any[]) => fn(`%s ${fmt}`, prefixString, ...args);
}

export const enum LogLevel {
/** Not verbose at all */
DEFAULT = 0,
/** Pretty verbose */
DEBUG = 1,
/** Extremely verbose */
TRACE = 2
}
17 changes: 17 additions & 0 deletions packages/aws-cdk/lib/util/directories.ts
@@ -1,3 +1,4 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

Expand All @@ -9,4 +10,20 @@ export function cdkHomeDir() {

export function cdkCacheDir() {
return path.join(cdkHomeDir(), 'cache');
}

export function rootDir() {

function _rootDir(dirname: string): string {
const manifestPath = path.join(dirname, 'package.json');
if (fs.existsSync(manifestPath)) {
return dirname;
}
if (path.dirname(dirname) === dirname) {
throw new Error('Unable to find package manifest');
}
return _rootDir(path.dirname(dirname));
}

return _rootDir(__dirname);
}
6 changes: 3 additions & 3 deletions packages/aws-cdk/lib/version.ts
Expand Up @@ -4,7 +4,7 @@ import * as fs from 'fs-extra';
import * as semver from 'semver';
import { debug, print } from '../lib/logging';
import { formatAsBanner } from '../lib/util/console-formatters';
import { cdkCacheDir } from './util/directories';
import { cdkCacheDir, rootDir } from './util/directories';
import { getLatestVersionFromNpm } from './util/npm';

const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60;
Expand All @@ -17,12 +17,12 @@ export const DISPLAY_VERSION = `${versionNumber()} (build ${commit()})`;

export function versionNumber(): string {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('../package.json').version.replace(/\+[0-9a-f]+$/, '');
return require(path.join(rootDir(), 'package.json')).version.replace(/\+[0-9a-f]+$/, '');
}

function commit(): string {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('../build-info.json').commit;
return require(path.join(rootDir(), 'build-info.json')).commit;
}

export class VersionCheckTTL {
Expand Down
24 changes: 22 additions & 2 deletions packages/aws-cdk/package.json
Expand Up @@ -10,7 +10,7 @@
"scripts": {
"build": "cdk-build",
"watch": "cdk-watch",
"lint": "cdk-lint && madge --circular --extensions js lib",
"lint": "cdk-lint",
"pkglint": "pkglint -f",
"test": "cdk-test",
"integ": "jest --testMatch '**/?(*.)+(integ-test).js'",
Expand All @@ -28,7 +28,27 @@
"build+test+extract": "yarn build+test"
},
"cdk-package": {
"shrinkWrap": true
"bundle": {
"externals": {
"optionalDependencies": [
"fsevents"
]
},
"resources": {
"../../node_modules/vm2/lib/bridge.js": "lib/bridge.js",
"../../node_modules/vm2/lib/setup-sandbox.js": "lib/setup-sandbox.js"
},
"allowedLicenses": [
"Apache-2.0",
"MIT",
"BSD-3-Clause",
"ISC",
"BSD-2-Clause",
"0BSD"
],
"dontAttribute": "^@aws-cdk/|^cdk-assets$",
"test": "bin/cdk --version"
}
},
"author": {
"name": "Amazon Web Services",
Expand Down

0 comments on commit 31d135f

Please sign in to comment.