From 7db7406abe0de16eb2cfbe37890e0c0af9fdaa49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=91=A8=F0=9F=8F=BC=E2=80=8D=F0=9F=92=BB=20Romain=20M?= =?UTF-8?q?arcadier-Muller?= Date: Tue, 19 Jun 2018 10:32:52 +0200 Subject: [PATCH 1/3] Adopt SDK-standard behavior when no environment is specified * Use environment variables in a way that is consistent with what the various AWS SDKs and CLIs do: + `AWS_REGION` has precedence over `AWS_DEFAULT_REGION` + `AWS_SHARED_CREDENTIALS_FILE` can be used to override the location of the `~/.aws/credentials` file + `AWS_CONFIG_FILE` can be used to override the location of the `.aws/config` file * Ensure the environment variable instructing the JS AWS SDK to load up data from the `~/.aws/config` file gets set by the toolkit before `aws-sdk` is loaded * Create `~/.aws/credentials` if `~/.aws/config` exists, since the JS AWS SDK will start by loading configuration from there, and will fail if it does not exist * Retire the `default-account` and `default-region` keys from the `context` map, and replace them with `aws:cdk:toolkit:` keys that cannot be user-provided Fixes #59 --- packages/@aws-cdk/assert/lib/integ-helpers.ts | 5 ++- .../@aws-cdk/core/test/test.environment.ts | 11 +++--- packages/@aws-cdk/cx-api/lib/cxapi.ts | 4 +- packages/aws-cdk/bin/cdk.ts | 20 ++++++---- packages/aws-cdk/lib/api/deploy-stack.ts | 5 ++- .../lib/api/util/sdk-load-aws-config.ts | 31 +++++++++++++++ packages/aws-cdk/lib/api/util/sdk.ts | 26 ++++--------- packages/aws-cdk/lib/settings.ts | 38 +++++++++++++++---- 8 files changed, 97 insertions(+), 43 deletions(-) create mode 100644 packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts diff --git a/packages/@aws-cdk/assert/lib/integ-helpers.ts b/packages/@aws-cdk/assert/lib/integ-helpers.ts index e3a817cc3a832..c30b337d80bf9 100644 --- a/packages/@aws-cdk/assert/lib/integ-helpers.ts +++ b/packages/@aws-cdk/assert/lib/integ-helpers.ts @@ -1,4 +1,5 @@ // Helper functions for integration tests +import { DEFAULT_ACCOUNT_CONTEXT_KEY, DEFAULT_REGION_CONTEXT_KEY } from '@aws-cdk/cx-api'; import { spawnSync } from 'child_process'; import fs = require('fs'); import path = require('path'); @@ -80,8 +81,8 @@ export class IntegrationTest { // Default context we run all integ tests with, so they don't depend on the // account of the exercising user. export const STATIC_TEST_CONTEXT = { - "default-account": "12345678", - "default-region": "test-region", + [DEFAULT_ACCOUNT_CONTEXT_KEY]: "12345678", + [DEFAULT_REGION_CONTEXT_KEY]: "test-region", "availability-zones:12345678:test-region": [ "test-region-1a", "test-region-1b", "test-region-1c" ], "ssm:12345678:test-region:/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2": "ami-1234", }; diff --git a/packages/@aws-cdk/core/test/test.environment.ts b/packages/@aws-cdk/core/test/test.environment.ts index 05cdbbbe0bc0f..d6890b4ad3b0e 100644 --- a/packages/@aws-cdk/core/test/test.environment.ts +++ b/packages/@aws-cdk/core/test/test.environment.ts @@ -1,3 +1,4 @@ +import { DEFAULT_ACCOUNT_CONTEXT_KEY, DEFAULT_REGION_CONTEXT_KEY } from '@aws-cdk/cx-api'; import { Test } from 'nodeunit'; import { App, Stack } from '../lib'; @@ -10,11 +11,11 @@ export = { test.done(); }, - 'Default account and region can be set in contrext (`default-account` and `default-region`)'(test: Test) { + 'Default account and region can be set in context (`aws:cdk:toolkit:default-account` and `aws:cdk:toolkit:default-region`)'(test: Test) { const app = new App(); - app.setContext('default-account', 'my-default-account'); - app.setContext('default-region', 'my-default-region'); + app.setContext(DEFAULT_ACCOUNT_CONTEXT_KEY, 'my-default-account'); + app.setContext(DEFAULT_REGION_CONTEXT_KEY, 'my-default-region'); const stack = new Stack(app); @@ -27,8 +28,8 @@ export = { 'If only `env.region` or `env.account` are specified, defaults will be used for the other'(test: Test) { const app = new App(); - app.setContext('default-account', 'my-default-account'); - app.setContext('default-region', 'my-default-region'); + app.setContext(DEFAULT_ACCOUNT_CONTEXT_KEY, 'my-default-account'); + app.setContext(DEFAULT_REGION_CONTEXT_KEY, 'my-default-region'); const stack1 = new Stack(app, 'S1', { env: { region: 'only-region' } }); const stack2 = new Stack(app, 'S2', { env: { account: 'only-account' } }); diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index e03b7b65bd2d2..1ff3d1baeb074 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -88,9 +88,9 @@ export type StackMetadata = { [path: string]: MetadataEntry[] }; /** * Context parameter for the default AWS account to use if a stack's environment is not set. */ -export const DEFAULT_ACCOUNT_CONTEXT_KEY = 'default-account'; +export const DEFAULT_ACCOUNT_CONTEXT_KEY = 'aws:cdk:toolkit:default-account'; /** * Context parameter for the default AWS region to use if a stack's environment is not set. */ -export const DEFAULT_REGION_CONTEXT_KEY = 'default-region'; +export const DEFAULT_REGION_CONTEXT_KEY = 'aws:cdk:toolkit:default-region'; diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 8f1372f64b8b0..8444ee52adb2e 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -1,6 +1,9 @@ #!/usr/bin/env node import 'source-map-support/register'; +// Ensure the AWS SDK is properly initialized before anything else. +import '../lib/api/util/sdk-load-aws-config'; + import * as cxapi from '@aws-cdk/cx-api'; import { deepMerge, isEmpty, partition } from '@aws-cdk/util'; import { exec, spawn } from 'child_process'; @@ -91,8 +94,8 @@ async function initCommandLine() { 'ssm': new contextplugins.SSMContextProviderPlugin(aws), }; - const userConfig = new Settings().load(PER_USER_DEFAULTS); - const projectConfig = new Settings().load(DEFAULTS); + const userConfig = await new Settings().load(PER_USER_DEFAULTS); + const projectConfig = await new Settings().load(DEFAULTS); const commandLineArguments = argumentsToSettings(); const renames = parseRenames(argv.rename); @@ -341,7 +344,7 @@ async function initCommandLine() { await contextplugins.provideContextValues(allMissing, projectConfig, availableContextProviders); // Cache the new context to disk - projectConfig.save(DEFAULTS); + await projectConfig.save(DEFAULTS); continue; } @@ -401,7 +404,7 @@ async function initCommandLine() { for (const stack of synthesizedStacks) { if (stackIds.length !== 1) { highlight(stack.name); } if (!stack.environment) { - throw new Error(`Stack ${stack.name} has no environment`); + throw new Error(`Stack ${stack.name} does not define an environment, and no usable default environment was found.`); } const toolkitInfo = await loadToolkitInfo(stack.environment, aws, toolkitStackName); const deployName = renames.finalName(stack.name); @@ -414,12 +417,12 @@ async function initCommandLine() { try { const result = await deployStack(stack, aws, toolkitInfo, deployName); - const message = result.noOp ? ' ✅ Stack was already up-to-date!' - : ' ✅ Deployment of stack %s completed successfully!'; + const message = result.noOp ? ` ✅ Stack was already up-to-date, it has ARN ${blue(result.stackArn)}` + : ` ✅ Deployment of stack %s completed successfully, it has ARN ${blue(result.stackArn)}`; success(message, blue(stack.name)); for (const name of Object.keys(result.outputs)) { const value = result.outputs[name]; - print('Output %s = %s', blue(name), green(value)); + print('%s.%s = %s', blue(deployName), blue(name), green(value)); } } catch (e) { error(' ❌ Deployment of stack %s failed: %s', blue(stack.name), e); @@ -593,6 +596,9 @@ async function initCommandLine() { const parts = assignment.split('=', 2); if (parts.length === 2) { debug('CLI argument context: %s=%s', parts[0], parts[1]); + if (parts[0].match(/^aws:.+/)) { + throw new Error(`User-provided context cannot use keys prefixed with 'aws:', but ${parts[0]} was provided.`); + } context[parts[0]] = parts[1]; } else { warning('Context argument is not an assignment (key=value): %s', assignment); diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index f7d0ca75507c8..15fb38ea0b420 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -21,6 +21,7 @@ type TemplateBodyParameter = { export interface DeployStackResult { readonly noOp: boolean; readonly outputs: { [name: string]: string }; + readonly stackArn: string; } export async function deployStack(stack: SynthesizedStack, @@ -60,7 +61,7 @@ export async function deployStack(stack: SynthesizedStack, if (!changeSetDescription || !changeSetDescription.Changes || changeSetDescription.Changes.length === 0) { debug('No changes are to be performed on %s, assuming success.', deployName); await cfn.deleteChangeSet({ StackName: deployName, ChangeSetName: changeSetName }).promise(); - return { noOp: true, outputs: await getStackOutputs(cfn, deployName) }; + return { noOp: true, outputs: await getStackOutputs(cfn, deployName), stackArn: changeSet.StackId! }; } debug('Initiating execution of changeset %s on stack %s', changeSetName, deployName); @@ -70,7 +71,7 @@ export async function deployStack(stack: SynthesizedStack, await waitForStack(cfn, deployName); if (monitor) { monitor.stop(); } debug('Stack %s has completed updating', deployName); - return { noOp: false, outputs: await getStackOutputs(cfn, deployName) }; + return { noOp: false, outputs: await getStackOutputs(cfn, deployName), stackArn: changeSet.StackId! }; } async function getStackOutputs(cfn: CloudFormation, stackName: string): Promise<{ [name: string]: string }> { diff --git a/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts b/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts new file mode 100644 index 0000000000000..4e11fb7e953bd --- /dev/null +++ b/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts @@ -0,0 +1,31 @@ +/** + * IMPORTANT: This **must** be required _before_ 'aws-sdk' is. + * + * This ensures the correct environment is set-up so the AWS SDK properly + * loads up configruation stored in the shared credentials file (usually + * found at ~/.aws/credentials) and the aws config file (usually found at + * ~/.aws/config), if either is present. + */ + +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +export const sharedCredentialsFile = + process.env.AWS_SHARED_CREDENTIALS_FILE ? process.env.AWS_SHARED_CREDENTIALS_FILE + : path.join(os.homedir(), '.aws', 'credentials'); +export const awsConfigFile = + process.env.AWS_CONFIG_FILE ? process.env.AWS_CONFIG_FILE + : path.join(os.homedir(), '.aws', 'config'); + +if (fs.existsSync(awsConfigFile) && !fs.existsSync(sharedCredentialsFile)) { + /* + * Write an empty credentials file if there's a config fil, otherwise the SDK will simply bail out, + * since the credentials file is loaded before the config file is. + */ + fs.writeFileSync(sharedCredentialsFile, ''); +} +if (fs.existsSync(sharedCredentialsFile)) { + // Ensures that region is loaded from ~/.aws/config (https://github.com/aws/aws-sdk-js/pull/1391) + process.env.AWS_SDK_LOAD_CONFIG = '1'; +} diff --git a/packages/aws-cdk/lib/api/util/sdk.ts b/packages/aws-cdk/lib/api/util/sdk.ts index 64cac70fa0950..4637930162a3f 100644 --- a/packages/aws-cdk/lib/api/util/sdk.ts +++ b/packages/aws-cdk/lib/api/util/sdk.ts @@ -1,20 +1,9 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - -if (fs.existsSync(path.join(os.homedir(), ".aws", "credentials")) && fs.existsSync(path.join(os.homedir(), ".aws", "config"))) { - // Ensures that region is loaded from ~/.aws/config (https://github.com/aws/aws-sdk-js/pull/1391) - - // Only set this value if if the requisite files exist, otherwise this is - // just going to throw an unhelpful error. - process.env.AWS_SDK_LOAD_CONFIG = '1'; -} - import { Environment} from '@aws-cdk/cx-api'; import { CloudFormation, config, CredentialProviderChain , EC2, S3, SSM, STS } from 'aws-sdk'; import { debug } from '../../logging'; import { PluginHost } from '../../plugin'; import { CredentialProviderSource, Mode } from '../aws-auth/credentials'; +import { awsConfigFile, sharedCredentialsFile } from './sdk-load-aws-config'; /** * Source for SDK client objects @@ -28,12 +17,9 @@ import { CredentialProviderSource, Mode } from '../aws-auth/credentials'; export class SDK { private defaultAccountFetched = false; private defaultAccountId?: string = undefined; - private credentialSources: CredentialProviderSource[]; private readonly userAgent: string; constructor() { - this.credentialSources = PluginHost.instance.credentialProviderSources; - // Find the package.json from the main toolkit const pkg = (require.main as any).require('../package.json'); this.userAgent = `${pkg.name}/${pkg.version}`; @@ -72,12 +58,16 @@ export class SDK { } public defaultRegion() { + if (process.env.AWS_REGION) { + debug('Obtaining default region from environment ($AWS_REGION)'); + return process.env.AWS_DEFAULT_REGION; + } if (process.env.AWS_DEFAULT_REGION) { - debug('Obtaining default region from environment'); + debug('Obtaining default region from environment ($AWS_DEFAULT_REGION)'); return process.env.AWS_DEFAULT_REGION; } if (config.region) { - debug('Obtaining default region from AWS configuration'); + debug(`Obtaining default region from AWS configuration (${sharedCredentialsFile} or ${awsConfigFile})`); return config.region; } return undefined; @@ -113,7 +103,7 @@ export class SDK { const triedSources: CredentialProviderSource[] = []; // Otherwise, inspect the various credential sources we have - for (const source of this.credentialSources) { + for (const source of PluginHost.instance.credentialProviderSources) { if (!(await source.isAvailable())) { debug('Credentials source %s is not available, ignoring it.', source.name); continue; diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index 5bf12c86fe10d..a40d0aaac1f4d 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -1,8 +1,10 @@ import { deepClone, deepGet, deepMerge, deepSet } from '@aws-cdk/util'; import * as fs from 'fs-extra'; import * as os from 'os'; +import * as fs_path from 'path'; +import { warning } from './logging'; -type SettingsMap = {[key: string]: any}; +export type SettingsMap = {[key: string]: any}; export class Settings { public static mergeAll(...settings: Settings[]): Settings { @@ -19,20 +21,42 @@ export class Settings { this.settings = settings || {}; } - public load(fileName: string) { + public async load(fileName: string) { this.settings = {}; const expanded = expandHomeDir(fileName); - if (fs.existsSync(expanded)) { - this.settings = JSON.parse(fs.readFileSync(expanded, { encoding: 'utf-8' })); + if (await fs.pathExists(expanded)) { + this.settings = await fs.readJson(expanded); } + // See https://github.com/awslabs/aws-cdk/issues/59 + prohibitContextKey(this, 'default-account'); + prohibitContextKey(this, 'default-region'); + warnAboutContextKey(this, 'aws:'); + return this; + + function prohibitContextKey(self: Settings, key: string) { + if (!self.settings.context) { return; } + if (key in self.settings.context) { + throw new Error(`The 'context.${key}' key was found in ${fs_path.resolve(fileName)}, but it is illegal. Please remove it.`); + } + } + + function warnAboutContextKey(self: Settings, prefix: string) { + if (!self.settings.context) { return; } + for (const contextKey of Object.keys(self.settings.context)) { + if (contextKey.startsWith(prefix)) { + // tslint:disable-next-line:max-line-length + warning(`A key starting with '${prefix}' key was found in ${fs_path.resolve(fileName)} ('context.${prefix}'), it might cause surprising behavior and should be removed.`); + } + } + } } - public save(fileName: string) { + public async save(fileName: string) { const expanded = expandHomeDir(fileName); - fs.writeFileSync(expanded, JSON.stringify(this.settings, undefined, 2), { encoding: 'utf-8' }); + await fs.writeJson(expanded, this.settings, { spaces: 2 }); return this; } @@ -57,7 +81,7 @@ export class Settings { function expandHomeDir(x: string) { if (x.startsWith('~')) { - return os.homedir + '/' + x.substr(1); + return fs_path.join(os.homedir(), x.substr(1)); } return x; } From 1164d6630ec1ff45ec57a63058557a1b3498d89a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=91=A8=F0=9F=8F=BC=E2=80=8D=F0=9F=92=BB=20Romain=20M?= =?UTF-8?q?arcadier-Muller?= Date: Tue, 19 Jun 2018 15:14:22 +0200 Subject: [PATCH 2/3] Tweaks from feedback. --- packages/aws-cdk/bin/cdk.ts | 3 ++- packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts | 4 +++- packages/aws-cdk/lib/settings.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 8444ee52adb2e..9d7d93584fbea 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -404,7 +404,8 @@ async function initCommandLine() { for (const stack of synthesizedStacks) { if (stackIds.length !== 1) { highlight(stack.name); } if (!stack.environment) { - throw new Error(`Stack ${stack.name} does not define an environment, and no usable default environment was found.`); + // tslint:disable-next-line:max-line-length + throw new Error(`Stack ${stack.name} does not define an environment, and AWS credentails could not be obtained from standard locations.`); } const toolkitInfo = await loadToolkitInfo(stack.environment, aws, toolkitStackName); const deployName = renames.finalName(stack.name); diff --git a/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts b/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts index 4e11fb7e953bd..4e62c0c938d14 100644 --- a/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts +++ b/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts @@ -5,6 +5,8 @@ * loads up configruation stored in the shared credentials file (usually * found at ~/.aws/credentials) and the aws config file (usually found at * ~/.aws/config), if either is present. + * + * @see https://github.com/awslabs/aws-cdk/pull/128 */ import * as fs from 'fs'; @@ -20,7 +22,7 @@ export const awsConfigFile = if (fs.existsSync(awsConfigFile) && !fs.existsSync(sharedCredentialsFile)) { /* - * Write an empty credentials file if there's a config fil, otherwise the SDK will simply bail out, + * Write an empty credentials file if there's a config file, otherwise the SDK will simply bail out, * since the credentials file is loaded before the config file is. */ fs.writeFileSync(sharedCredentialsFile, ''); diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index a40d0aaac1f4d..1e736f5ce22e6 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -48,7 +48,7 @@ export class Settings { for (const contextKey of Object.keys(self.settings.context)) { if (contextKey.startsWith(prefix)) { // tslint:disable-next-line:max-line-length - warning(`A key starting with '${prefix}' key was found in ${fs_path.resolve(fileName)} ('context.${prefix}'), it might cause surprising behavior and should be removed.`); + warning(`A reserved context key ('context.${prefix}') key was found in ${fs_path.resolve(fileName)}, it might cause surprising behavior and should be removed.`); } } } From ce2df4bf6e78710be90db76507adf3d9e4a74d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=91=A8=F0=9F=8F=BC=E2=80=8D=F0=9F=92=BB=20Romain=20M?= =?UTF-8?q?arcadier-Muller?= Date: Tue, 19 Jun 2018 15:40:20 +0200 Subject: [PATCH 3/3] Further edits after feedback --- .../aws-cdk/lib/api/util/sdk-load-aws-config.ts | 4 ++-- packages/aws-cdk/lib/api/util/sdk.ts | 15 +-------------- packages/aws-cdk/lib/settings.ts | 3 ++- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts b/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts index 4e62c0c938d14..d53581f137299 100644 --- a/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts +++ b/packages/aws-cdk/lib/api/util/sdk-load-aws-config.ts @@ -13,10 +13,10 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -export const sharedCredentialsFile = +const sharedCredentialsFile = process.env.AWS_SHARED_CREDENTIALS_FILE ? process.env.AWS_SHARED_CREDENTIALS_FILE : path.join(os.homedir(), '.aws', 'credentials'); -export const awsConfigFile = +const awsConfigFile = process.env.AWS_CONFIG_FILE ? process.env.AWS_CONFIG_FILE : path.join(os.homedir(), '.aws', 'config'); diff --git a/packages/aws-cdk/lib/api/util/sdk.ts b/packages/aws-cdk/lib/api/util/sdk.ts index 4637930162a3f..fe513ffaefa16 100644 --- a/packages/aws-cdk/lib/api/util/sdk.ts +++ b/packages/aws-cdk/lib/api/util/sdk.ts @@ -3,7 +3,6 @@ import { CloudFormation, config, CredentialProviderChain , EC2, S3, SSM, STS } f import { debug } from '../../logging'; import { PluginHost } from '../../plugin'; import { CredentialProviderSource, Mode } from '../aws-auth/credentials'; -import { awsConfigFile, sharedCredentialsFile } from './sdk-load-aws-config'; /** * Source for SDK client objects @@ -58,19 +57,7 @@ export class SDK { } public defaultRegion() { - if (process.env.AWS_REGION) { - debug('Obtaining default region from environment ($AWS_REGION)'); - return process.env.AWS_DEFAULT_REGION; - } - if (process.env.AWS_DEFAULT_REGION) { - debug('Obtaining default region from environment ($AWS_DEFAULT_REGION)'); - return process.env.AWS_DEFAULT_REGION; - } - if (config.region) { - debug(`Obtaining default region from AWS configuration (${sharedCredentialsFile} or ${awsConfigFile})`); - return config.region; - } - return undefined; + return config.region; } public async defaultAccount() { diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index 1e736f5ce22e6..113f8e02d564b 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -39,7 +39,8 @@ export class Settings { function prohibitContextKey(self: Settings, key: string) { if (!self.settings.context) { return; } if (key in self.settings.context) { - throw new Error(`The 'context.${key}' key was found in ${fs_path.resolve(fileName)}, but it is illegal. Please remove it.`); + // tslint:disable-next-line:max-line-length + throw new Error(`The 'context.${key}' key was found in ${fs_path.resolve(fileName)}, but it is no longer supported. Please remove it.`); } }