@@ -5,122 +5,151 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema';
55import * as cxapi from '@aws-cdk/cx-api' ;
66import * as fs from 'fs-extra' ;
77import { lte } from 'semver' ;
8- import { prepareDefaultEnvironment as oldPrepare , prepareContext , spaceAvailableForContext , Settings , loadTree , some , versionNumber } from '../../../api/aws-cdk' ;
8+ import type { SdkProvider } from '../../../api/aws-cdk' ;
9+ import { prepareDefaultEnvironment as oldPrepare , prepareContext , spaceAvailableForContext , Settings , loadTree , some , versionNumber , guessExecutable } from '../../../api/aws-cdk' ;
910import { splitBySize } from '../../../private/util' ;
1011import type { ToolkitServices } from '../../../toolkit/private' ;
1112import { IO } from '../../io/private' ;
1213import type { IoHelper } from '../../shared-private' ;
1314import { ToolkitError } from '../../shared-public' ;
1415import type { AppSynthOptions , LoadAssemblyOptions } from '../source-builder' ;
1516
16- export { guessExecutable } from '../../../api/aws-cdk' ;
17-
1817type Env = { [ key : string ] : string } ;
1918type Context = { [ key : string ] : any } ;
2019
21- /**
22- * Turn the given optional output directory into a fixed output directory
23- */
24- export function determineOutputDirectory ( outdir ?: string ) {
25- return outdir ?? fs . mkdtempSync ( path . join ( fs . realpathSync ( os . tmpdir ( ) ) , 'cdk.out' ) ) ;
26- }
27-
28- /**
29- * If we don't have region/account defined in context, we fall back to the default SDK behavior
30- * where region is retrieved from ~/.aws/config and account is based on default credentials provider
31- * chain and then STS is queried.
32- *
33- * This is done opportunistically: for example, if we can't access STS for some reason or the region
34- * is not configured, the context value will be 'null' and there could failures down the line. In
35- * some cases, synthesis does not require region/account information at all, so that might be perfectly
36- * fine in certain scenarios.
37- *
38- * @param context The context key/value bash.
39- */
40- export async function prepareDefaultEnvironment ( services : ToolkitServices , props : { outdir ?: string } = { } ) : Promise < Env > {
41- const logFn = ( msg : string , ...args : any ) => services . ioHelper . notify ( IO . CDK_ASSEMBLY_I0010 . msg ( format ( msg , ...args ) ) ) ;
42- const env = await oldPrepare ( services . sdkProvider , logFn ) ;
43-
44- if ( props . outdir ) {
45- env [ cxapi . OUTDIR_ENV ] = props . outdir ;
46- await logFn ( 'outdir:' , props . outdir ) ;
20+ export class ExecutionEnviornment {
21+ private readonly ioHelper : IoHelper ;
22+ private readonly sdkProvider : SdkProvider ;
23+ private readonly debugFn : ( msg : string ) => Promise < void > ;
24+ private _outdir : string | undefined ;
25+
26+ public constructor ( services : ToolkitServices , props : { outdir ?: string } = { } ) {
27+ this . ioHelper = services . ioHelper ;
28+ this . sdkProvider = services . sdkProvider ;
29+ this . debugFn = ( msg : string ) => this . ioHelper . notify ( IO . DEFAULT_ASSEMBLY_DEBUG . msg ( msg ) ) ;
30+ this . _outdir = props . outdir ;
4731 }
4832
49- // CLI version information
50- env [ cxapi . CLI_ASM_VERSION_ENV ] = cxschema . Manifest . version ( ) ;
51- env [ cxapi . CLI_VERSION_ENV ] = versionNumber ( ) ;
33+ /**
34+ * Turn the given optional output directory into a fixed output directory
35+ */
36+ public get outdir ( ) : string {
37+ if ( ! this . _outdir ) {
38+ const outdir = fs . mkdtempSync ( path . join ( fs . realpathSync ( os . tmpdir ( ) ) , 'cdk.out' ) ) ;
39+ this . _outdir = outdir ;
40+ }
41+ return this . _outdir ;
42+ }
5243
53- await logFn ( 'env:' , env ) ;
54- return env ;
55- }
44+ /**
45+ * Guess the executable from the command-line argument
46+ *
47+ * Only do this if the file is NOT marked as executable. If it is,
48+ * we'll defer to the shebang inside the file itself.
49+ *
50+ * If we're on Windows, we ALWAYS take the handler, since it's hard to
51+ * verify if registry associations have or have not been set up for this
52+ * file type, so we'll assume the worst and take control.
53+ */
54+ public guessExecutable ( app : string ) {
55+ return guessExecutable ( app , this . debugFn ) ;
56+ }
5657
57- /**
58- * Run code from a different working directory
59- */
60- export async function changeDir < T > ( block : ( ) => Promise < T > , workingDir ?: string ) {
61- const originalWorkingDir = process . cwd ( ) ;
62- try {
63- if ( workingDir ) {
64- process . chdir ( workingDir ) ;
58+ /**
59+ * If we don't have region/account defined in context, we fall back to the default SDK behavior
60+ * where region is retrieved from ~/.aws/config and account is based on default credentials provider
61+ * chain and then STS is queried.
62+ *
63+ * This is done opportunistically: for example, if we can't access STS for some reason or the region
64+ * is not configured, the context value will be 'null' and there could failures down the line. In
65+ * some cases, synthesis does not require region/account information at all, so that might be perfectly
66+ * fine in certain scenarios.
67+ */
68+ public async defaultEnvVars ( ) : Promise < Env > {
69+ const debugFn = ( msg : string ) => this . ioHelper . notify ( IO . CDK_ASSEMBLY_I0010 . msg ( msg ) ) ;
70+ const env = await oldPrepare ( this . sdkProvider , debugFn ) ;
71+
72+ if ( this . outdir ) {
73+ env [ cxapi . OUTDIR_ENV ] = this . outdir ;
74+ await debugFn ( format ( 'outdir:' , this . outdir ) ) ;
6575 }
6676
67- return await block ( ) ;
68- } finally {
69- if ( workingDir ) {
70- process . chdir ( originalWorkingDir ) ;
71- }
72- }
73- }
77+ // CLI version information
78+ env [ cxapi . CLI_ASM_VERSION_ENV ] = cxschema . Manifest . version ( ) ;
79+ env [ cxapi . CLI_VERSION_ENV ] = versionNumber ( ) ;
7480
75- /**
76- * Run code with additional environment variables
77- */
78- export async function withEnv < T > ( env : Env = { } , block : ( ) => Promise < T > ) {
79- const originalEnv = process . env ;
80- try {
81- process . env = {
82- ...originalEnv ,
83- ...env ,
84- } ;
85-
86- return await block ( ) ;
87- } finally {
88- process . env = originalEnv ;
81+ await debugFn ( format ( 'env:' , env ) ) ;
82+ return env ;
8983 }
90- }
9184
92- /**
93- * Run code with context setup inside the environment
94- */
95- export async function withContext < T > (
96- inputContext : Context ,
97- env : Env ,
98- synthOpts : AppSynthOptions = { } ,
99- block : ( env : Env , context : Context ) => Promise < T > ,
100- ) {
101- const context = await prepareContext ( synthOptsDefaults ( synthOpts ) , inputContext , env ) ;
102- let contextOverflowLocation = null ;
85+ /**
86+ * Run code from a different working directory
87+ */
88+ public async changeDir < T > ( block : ( ) => Promise < T > , workingDir ?: string ) {
89+ const originalWorkingDir = process . cwd ( ) ;
90+ try {
91+ if ( workingDir ) {
92+ process . chdir ( workingDir ) ;
93+ }
94+
95+ return await block ( ) ;
96+ } finally {
97+ if ( workingDir ) {
98+ process . chdir ( originalWorkingDir ) ;
99+ }
100+ }
101+ }
103102
104- try {
105- const envVariableSizeLimit = os . platform ( ) === 'win32' ? 32760 : 131072 ;
106- const [ smallContext , overflow ] = splitBySize ( context , spaceAvailableForContext ( env , envVariableSizeLimit ) ) ;
107-
108- // Store the safe part in the environment variable
109- env [ cxapi . CONTEXT_ENV ] = JSON . stringify ( smallContext ) ;
110-
111- // If there was any overflow, write it to a temporary file
112- if ( Object . keys ( overflow ?? { } ) . length > 0 ) {
113- const contextDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'cdk-context' ) ) ;
114- contextOverflowLocation = path . join ( contextDir , 'context-overflow.json' ) ;
115- fs . writeJSONSync ( contextOverflowLocation , overflow ) ;
116- env [ cxapi . CONTEXT_OVERFLOW_LOCATION_ENV ] = contextOverflowLocation ;
103+ /**
104+ * Run code with additional environment variables
105+ */
106+ public async withEnv < T > ( env : Env = { } , block : ( ) => Promise < T > ) {
107+ const originalEnv = process . env ;
108+ try {
109+ process . env = {
110+ ...originalEnv ,
111+ ...env ,
112+ } ;
113+
114+ return await block ( ) ;
115+ } finally {
116+ process . env = originalEnv ;
117117 }
118+ }
118119
119- // call the block code with new environment
120- return await block ( env , context ) ;
121- } finally {
122- if ( contextOverflowLocation ) {
123- fs . removeSync ( path . dirname ( contextOverflowLocation ) ) ;
120+ /**
121+ * Run code with context setup inside the environment
122+ */
123+ public async withContext < T > (
124+ inputContext : Context ,
125+ env : Env ,
126+ synthOpts : AppSynthOptions = { } ,
127+ block : ( env : Env , context : Context ) => Promise < T > ,
128+ ) {
129+ const context = await prepareContext ( synthOptsDefaults ( synthOpts ) , inputContext , env , this . debugFn ) ;
130+ let contextOverflowLocation = null ;
131+
132+ try {
133+ const envVariableSizeLimit = os . platform ( ) === 'win32' ? 32760 : 131072 ;
134+ const [ smallContext , overflow ] = splitBySize ( context , spaceAvailableForContext ( env , envVariableSizeLimit ) ) ;
135+
136+ // Store the safe part in the environment variable
137+ env [ cxapi . CONTEXT_ENV ] = JSON . stringify ( smallContext ) ;
138+
139+ // If there was any overflow, write it to a temporary file
140+ if ( Object . keys ( overflow ?? { } ) . length > 0 ) {
141+ const contextDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'cdk-context' ) ) ;
142+ contextOverflowLocation = path . join ( contextDir , 'context-overflow.json' ) ;
143+ fs . writeJSONSync ( contextOverflowLocation , overflow ) ;
144+ env [ cxapi . CONTEXT_OVERFLOW_LOCATION_ENV ] = contextOverflowLocation ;
145+ }
146+
147+ // call the block code with new environment
148+ return await block ( env , context ) ;
149+ } finally {
150+ if ( contextOverflowLocation ) {
151+ fs . removeSync ( path . dirname ( contextOverflowLocation ) ) ;
152+ }
124153 }
125154 }
126155}
@@ -130,8 +159,9 @@ export async function withContext<T>(
130159 *
131160 * @param assembly the assembly to check
132161 */
133- export async function checkContextOverflowSupport ( assembly : cxapi . CloudAssembly , ioHelper : IoHelper ) : Promise < void > {
134- const tree = loadTree ( assembly , ( msg : string ) => void ioHelper . notify ( IO . DEFAULT_ASSEMBLY_TRACE . msg ( msg ) ) ) ;
162+ async function checkContextOverflowSupport ( assembly : cxapi . CloudAssembly , ioHelper : IoHelper ) : Promise < void > {
163+ const traceFn = ( msg : string ) => ioHelper . notify ( IO . DEFAULT_ASSEMBLY_TRACE . msg ( msg ) ) ;
164+ const tree = await loadTree ( assembly , traceFn ) ;
135165 const frameworkDoesNotSupportContextOverflow = some ( tree , node => {
136166 const fqn = node . constructInfo ?. fqn ;
137167 const version = node . constructInfo ?. version ;
@@ -149,22 +179,22 @@ export async function checkContextOverflowSupport(assembly: cxapi.CloudAssembly,
149179/**
150180 * Safely create an assembly from a cloud assembly directory
151181 */
152- export async function assemblyFromDirectory ( assemblyDir : string , ioHost : IoHelper , loadOptions : LoadAssemblyOptions = { } ) {
182+ export async function assemblyFromDirectory ( assemblyDir : string , ioHelper : IoHelper , loadOptions : LoadAssemblyOptions = { } ) {
153183 try {
154184 const assembly = new cxapi . CloudAssembly ( assemblyDir , {
155185 skipVersionCheck : ! ( loadOptions . checkVersion ?? true ) ,
156186 skipEnumCheck : ! ( loadOptions . checkEnums ?? true ) ,
157187 // We sort as we deploy
158188 topoSort : false ,
159189 } ) ;
160- await checkContextOverflowSupport ( assembly , ioHost ) ;
190+ await checkContextOverflowSupport ( assembly , ioHelper ) ;
161191 return assembly ;
162192 } catch ( err : any ) {
163193 if ( err . message . includes ( cxschema . VERSION_MISMATCH ) ) {
164194 // this means the CLI version is too old.
165195 // we instruct the user to upgrade.
166196 const message = 'This AWS CDK Toolkit is not compatible with the AWS CDK library used by your application. Please upgrade to the latest version.' ;
167- await ioHost . notify ( IO . CDK_ASSEMBLY_E1111 . msg ( message , { error : err } ) ) ;
197+ await ioHelper . notify ( IO . CDK_ASSEMBLY_E1111 . msg ( message , { error : err } ) ) ;
168198 throw new ToolkitError ( `${ message } \n(${ err . message } ` ) ;
169199 }
170200 throw err ;
0 commit comments