@@ -5,122 +5,149 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema';
5
5
import * as cxapi from '@aws-cdk/cx-api' ;
6
6
import * as fs from 'fs-extra' ;
7
7
import { 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' ;
9
10
import { splitBySize } from '../../../private/util' ;
10
11
import type { ToolkitServices } from '../../../toolkit/private' ;
11
12
import { IO } from '../../io/private' ;
12
13
import type { IoHelper } from '../../shared-private' ;
13
14
import { ToolkitError } from '../../shared-public' ;
14
15
import type { AppSynthOptions , LoadAssemblyOptions } from '../source-builder' ;
15
16
16
- export { guessExecutable } from '../../../api/aws-cdk' ;
17
-
18
17
type Env = { [ key : string ] : string } ;
19
18
type Context = { [ key : string ] : any } ;
20
19
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 ExecutionEnvironment {
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 ;
47
31
}
48
32
49
- // CLI version information
50
- env [ cxapi . CLI_ASM_VERSION_ENV ] = cxschema . Manifest . version ( ) ;
51
- env [ cxapi . CLI_VERSION_ENV ] = versionNumber ( ) ;
52
-
53
- await logFn ( 'env:' , env ) ;
54
- return env ;
55
- }
56
-
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 ) ;
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 ;
65
40
}
41
+ return this . _outdir ;
42
+ }
66
43
67
- return await block ( ) ;
68
- } finally {
69
- if ( workingDir ) {
70
- process . chdir ( originalWorkingDir ) ;
71
- }
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 ) ;
72
56
}
73
- }
74
57
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 ;
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
+ env [ cxapi . OUTDIR_ENV ] = this . outdir ;
73
+ await debugFn ( format ( 'outdir:' , this . outdir ) ) ;
74
+
75
+ // CLI version information
76
+ env [ cxapi . CLI_ASM_VERSION_ENV ] = cxschema . Manifest . version ( ) ;
77
+ env [ cxapi . CLI_VERSION_ENV ] = versionNumber ( ) ;
78
+
79
+ await debugFn ( format ( 'env:' , env ) ) ;
80
+ return env ;
89
81
}
90
- }
91
82
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 ;
83
+ /**
84
+ * Run code from a different working directory
85
+ */
86
+ public async changeDir < T > ( block : ( ) => Promise < T > , workingDir ?: string ) {
87
+ const originalWorkingDir = process . cwd ( ) ;
88
+ try {
89
+ if ( workingDir ) {
90
+ process . chdir ( workingDir ) ;
91
+ }
92
+
93
+ return await block ( ) ;
94
+ } finally {
95
+ if ( workingDir ) {
96
+ process . chdir ( originalWorkingDir ) ;
97
+ }
98
+ }
99
+ }
103
100
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 ;
101
+ /**
102
+ * Run code with additional environment variables
103
+ */
104
+ public async withEnv < T > ( env : Env = { } , block : ( ) => Promise < T > ) {
105
+ const originalEnv = process . env ;
106
+ try {
107
+ process . env = {
108
+ ...originalEnv ,
109
+ ...env ,
110
+ } ;
111
+
112
+ return await block ( ) ;
113
+ } finally {
114
+ process . env = originalEnv ;
117
115
}
116
+ }
118
117
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 ) ) ;
118
+ /**
119
+ * Run code with context setup inside the environment
120
+ */
121
+ public async withContext < T > (
122
+ inputContext : Context ,
123
+ env : Env ,
124
+ synthOpts : AppSynthOptions = { } ,
125
+ block : ( env : Env , context : Context ) => Promise < T > ,
126
+ ) {
127
+ const context = await prepareContext ( synthOptsDefaults ( synthOpts ) , inputContext , env , this . debugFn ) ;
128
+ let contextOverflowLocation = null ;
129
+
130
+ try {
131
+ const envVariableSizeLimit = os . platform ( ) === 'win32' ? 32760 : 131072 ;
132
+ const [ smallContext , overflow ] = splitBySize ( context , spaceAvailableForContext ( env , envVariableSizeLimit ) ) ;
133
+
134
+ // Store the safe part in the environment variable
135
+ env [ cxapi . CONTEXT_ENV ] = JSON . stringify ( smallContext ) ;
136
+
137
+ // If there was any overflow, write it to a temporary file
138
+ if ( Object . keys ( overflow ?? { } ) . length > 0 ) {
139
+ const contextDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'cdk-context' ) ) ;
140
+ contextOverflowLocation = path . join ( contextDir , 'context-overflow.json' ) ;
141
+ fs . writeJSONSync ( contextOverflowLocation , overflow ) ;
142
+ env [ cxapi . CONTEXT_OVERFLOW_LOCATION_ENV ] = contextOverflowLocation ;
143
+ }
144
+
145
+ // call the block code with new environment
146
+ return await block ( env , context ) ;
147
+ } finally {
148
+ if ( contextOverflowLocation ) {
149
+ fs . removeSync ( path . dirname ( contextOverflowLocation ) ) ;
150
+ }
124
151
}
125
152
}
126
153
}
@@ -130,8 +157,9 @@ export async function withContext<T>(
130
157
*
131
158
* @param assembly the assembly to check
132
159
*/
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 ) ) ) ;
160
+ async function checkContextOverflowSupport ( assembly : cxapi . CloudAssembly , ioHelper : IoHelper ) : Promise < void > {
161
+ const traceFn = ( msg : string ) => ioHelper . notify ( IO . DEFAULT_ASSEMBLY_TRACE . msg ( msg ) ) ;
162
+ const tree = await loadTree ( assembly , traceFn ) ;
135
163
const frameworkDoesNotSupportContextOverflow = some ( tree , node => {
136
164
const fqn = node . constructInfo ?. fqn ;
137
165
const version = node . constructInfo ?. version ;
@@ -149,22 +177,22 @@ export async function checkContextOverflowSupport(assembly: cxapi.CloudAssembly,
149
177
/**
150
178
* Safely create an assembly from a cloud assembly directory
151
179
*/
152
- export async function assemblyFromDirectory ( assemblyDir : string , ioHost : IoHelper , loadOptions : LoadAssemblyOptions = { } ) {
180
+ export async function assemblyFromDirectory ( assemblyDir : string , ioHelper : IoHelper , loadOptions : LoadAssemblyOptions = { } ) {
153
181
try {
154
182
const assembly = new cxapi . CloudAssembly ( assemblyDir , {
155
183
skipVersionCheck : ! ( loadOptions . checkVersion ?? true ) ,
156
184
skipEnumCheck : ! ( loadOptions . checkEnums ?? true ) ,
157
185
// We sort as we deploy
158
186
topoSort : false ,
159
187
} ) ;
160
- await checkContextOverflowSupport ( assembly , ioHost ) ;
188
+ await checkContextOverflowSupport ( assembly , ioHelper ) ;
161
189
return assembly ;
162
190
} catch ( err : any ) {
163
191
if ( err . message . includes ( cxschema . VERSION_MISMATCH ) ) {
164
192
// this means the CLI version is too old.
165
193
// we instruct the user to upgrade.
166
194
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 } ) ) ;
195
+ await ioHelper . notify ( IO . CDK_ASSEMBLY_E1111 . msg ( message , { error : err } ) ) ;
168
196
throw new ToolkitError ( `${ message } \n(${ err . message } ` ) ;
169
197
}
170
198
throw err ;
0 commit comments