@@ -5,8 +5,8 @@ import * as cxapi from '@aws-cdk/cx-api';
55import { Construct } from 'constructs' ;
66import * as fs from 'fs-extra' ;
77import * as minimatch from 'minimatch' ;
8- import { AssetHashType , AssetOptions } from './assets' ;
9- import { BundlingOptions } from './bundling' ;
8+ import { AssetHashType , AssetOptions , FileAssetPackaging } from './assets' ;
9+ import { BundlingOptions , BundlingOutput } from './bundling' ;
1010import { FileSystem , FingerprintOptions } from './fs' ;
1111import { Names } from './names' ;
1212import { Cache } from './private/cache' ;
@@ -17,6 +17,8 @@ import { Stage } from './stage';
1717// eslint-disable-next-line
1818import { Construct as CoreConstruct } from './construct-compat' ;
1919
20+ const ARCHIVE_EXTENSIONS = [ '.zip' , '.jar' ] ;
21+
2022/**
2123 * A previously staged asset
2224 */
@@ -30,6 +32,16 @@ interface StagedAsset {
3032 * The hash we used previously
3133 */
3234 readonly assetHash : string ;
35+
36+ /**
37+ * The packaging of the asset
38+ */
39+ readonly packaging : FileAssetPackaging ,
40+
41+ /**
42+ * Whether this asset is an archive
43+ */
44+ readonly isArchive : boolean ;
3345}
3446
3547/**
@@ -124,6 +136,16 @@ export class AssetStaging extends CoreConstruct {
124136 */
125137 public readonly assetHash : string ;
126138
139+ /**
140+ * How this asset should be packaged.
141+ */
142+ public readonly packaging : FileAssetPackaging ;
143+
144+ /**
145+ * Whether this asset is an archive (zip or jar).
146+ */
147+ public readonly isArchive : boolean ;
148+
127149 private readonly fingerprintOptions : FingerprintOptions ;
128150
129151 private readonly hashType : AssetHashType ;
@@ -138,12 +160,20 @@ export class AssetStaging extends CoreConstruct {
138160
139161 private readonly cacheKey : string ;
140162
163+ private readonly sourceStats : fs . Stats ;
164+
141165 constructor ( scope : Construct , id : string , props : AssetStagingProps ) {
142166 super ( scope , id ) ;
143167
144168 this . sourcePath = path . resolve ( props . sourcePath ) ;
145169 this . fingerprintOptions = props ;
146170
171+ if ( ! fs . existsSync ( this . sourcePath ) ) {
172+ throw new Error ( `Cannot find asset at ${ this . sourcePath } ` ) ;
173+ }
174+
175+ this . sourceStats = fs . statSync ( this . sourcePath ) ;
176+
147177 const outdir = Stage . of ( this ) ?. assetOutdir ;
148178 if ( ! outdir ) {
149179 throw new Error ( 'unable to determine cloud assembly asset output directory. Assets must be defined indirectly within a "Stage" or an "App" scope' ) ;
@@ -192,6 +222,8 @@ export class AssetStaging extends CoreConstruct {
192222 this . stagedPath = staged . stagedPath ;
193223 this . absoluteStagedPath = staged . stagedPath ;
194224 this . assetHash = staged . assetHash ;
225+ this . packaging = staged . packaging ;
226+ this . isArchive = staged . isArchive ;
195227 }
196228
197229 /**
@@ -248,8 +280,18 @@ export class AssetStaging extends CoreConstruct {
248280 ? this . sourcePath
249281 : path . resolve ( this . assetOutdir , renderAssetFilename ( assetHash , path . extname ( this . sourcePath ) ) ) ;
250282
283+ if ( ! this . sourceStats . isDirectory ( ) && ! this . sourceStats . isFile ( ) ) {
284+ throw new Error ( `Asset ${ this . sourcePath } is expected to be either a directory or a regular file` ) ;
285+ }
286+
251287 this . stageAsset ( this . sourcePath , stagedPath , 'copy' ) ;
252- return { assetHash, stagedPath } ;
288+
289+ return {
290+ assetHash,
291+ stagedPath,
292+ packaging : this . sourceStats . isDirectory ( ) ? FileAssetPackaging . ZIP_DIRECTORY : FileAssetPackaging . FILE ,
293+ isArchive : this . sourceStats . isDirectory ( ) || ARCHIVE_EXTENSIONS . includes ( path . extname ( this . sourcePath ) . toLowerCase ( ) ) ,
294+ } ;
253295 }
254296
255297 /**
@@ -258,6 +300,10 @@ export class AssetStaging extends CoreConstruct {
258300 * Optionally skip, in which case we pretend we did something but we don't really.
259301 */
260302 private stageByBundling ( bundling : BundlingOptions , skip : boolean ) : StagedAsset {
303+ if ( ! this . sourceStats . isDirectory ( ) ) {
304+ throw new Error ( `Asset ${ this . sourcePath } is expected to be a directory when bundling` ) ;
305+ }
306+
261307 if ( skip ) {
262308 // We should have bundled, but didn't to save time. Still pretend to have a hash.
263309 // If the asset uses OUTPUT or BUNDLE, we use a CUSTOM hash to avoid fingerprinting
@@ -270,6 +316,8 @@ export class AssetStaging extends CoreConstruct {
270316 return {
271317 assetHash : this . calculateHash ( hashType , bundling ) ,
272318 stagedPath : this . sourcePath ,
319+ packaging : FileAssetPackaging . ZIP_DIRECTORY ,
320+ isArchive : true ,
273321 } ;
274322 }
275323
@@ -281,12 +329,21 @@ export class AssetStaging extends CoreConstruct {
281329 const bundleDir = this . determineBundleDir ( this . assetOutdir , assetHash ) ;
282330 this . bundle ( bundling , bundleDir ) ;
283331
284- // Calculate assetHash afterwards if we still must
285- assetHash = assetHash ?? this . calculateHash ( this . hashType , bundling , bundleDir ) ;
286- const stagedPath = path . resolve ( this . assetOutdir , renderAssetFilename ( assetHash ) ) ;
332+ // Check bundling output content and determine if we will need to archive
333+ const bundlingOutputType = bundling . outputType ?? BundlingOutput . AUTO_DISCOVER ;
334+ const bundledAsset = determineBundledAsset ( bundleDir , bundlingOutputType ) ;
287335
288- this . stageAsset ( bundleDir , stagedPath , 'move' ) ;
289- return { assetHash, stagedPath } ;
336+ // Calculate assetHash afterwards if we still must
337+ assetHash = assetHash ?? this . calculateHash ( this . hashType , bundling , bundledAsset . path ) ;
338+ const stagedPath = path . resolve ( this . assetOutdir , renderAssetFilename ( assetHash , bundledAsset . extension ) ) ;
339+
340+ this . stageAsset ( bundledAsset . path , stagedPath , 'move' ) ;
341+ return {
342+ assetHash,
343+ stagedPath,
344+ packaging : bundledAsset . packaging ,
345+ isArchive : true , // bundling always produces an archive
346+ } ;
290347 }
291348
292349 /**
@@ -320,10 +377,9 @@ export class AssetStaging extends CoreConstruct {
320377 }
321378
322379 // Copy file/directory to staging directory
323- const stat = fs . statSync ( sourcePath ) ;
324- if ( stat . isFile ( ) ) {
380+ if ( this . sourceStats . isFile ( ) ) {
325381 fs . copyFileSync ( sourcePath , targetPath ) ;
326- } else if ( stat . isDirectory ( ) ) {
382+ } else if ( this . sourceStats . isDirectory ( ) ) {
327383 fs . mkdirSync ( targetPath ) ;
328384 FileSystem . copyDirectory ( sourcePath , targetPath , this . fingerprintOptions ) ;
329385 } else {
@@ -502,3 +558,57 @@ function sortObject(object: { [key: string]: any }): { [key: string]: any } {
502558 }
503559 return ret ;
504560}
561+
562+ /**
563+ * Returns the single archive file of a directory or undefined
564+ */
565+ function singleArchiveFile ( directory : string ) : string | undefined {
566+ if ( ! fs . existsSync ( directory ) ) {
567+ throw new Error ( `Directory ${ directory } does not exist.` ) ;
568+ }
569+
570+ if ( ! fs . statSync ( directory ) . isDirectory ( ) ) {
571+ throw new Error ( `${ directory } is not a directory.` ) ;
572+ }
573+
574+ const content = fs . readdirSync ( directory ) ;
575+ if ( content . length === 1 ) {
576+ const file = path . join ( directory , content [ 0 ] ) ;
577+ const extension = path . extname ( content [ 0 ] ) . toLowerCase ( ) ;
578+ if ( fs . statSync ( file ) . isFile ( ) && ARCHIVE_EXTENSIONS . includes ( extension ) ) {
579+ return file ;
580+ }
581+ }
582+
583+ return undefined ;
584+ }
585+
586+ interface BundledAsset {
587+ path : string ,
588+ packaging : FileAssetPackaging ,
589+ extension ?: string
590+ }
591+
592+ /**
593+ * Returns the bundled asset to use based on the content of the bundle directory
594+ * and the type of output.
595+ */
596+ function determineBundledAsset ( bundleDir : string , outputType : BundlingOutput ) : BundledAsset {
597+ const archiveFile = singleArchiveFile ( bundleDir ) ;
598+
599+ // auto-discover means that if there is an archive file, we take it as the
600+ // bundle, otherwise, we will archive here.
601+ if ( outputType === BundlingOutput . AUTO_DISCOVER ) {
602+ outputType = archiveFile ? BundlingOutput . ARCHIVED : BundlingOutput . NOT_ARCHIVED ;
603+ }
604+
605+ switch ( outputType ) {
606+ case BundlingOutput . NOT_ARCHIVED :
607+ return { path : bundleDir , packaging : FileAssetPackaging . ZIP_DIRECTORY } ;
608+ case BundlingOutput . ARCHIVED :
609+ if ( ! archiveFile ) {
610+ throw new Error ( 'Bundling output directory is expected to include only a single .zip or .jar file when `output` is set to `ARCHIVED`' ) ;
611+ }
612+ return { path : archiveFile , packaging : FileAssetPackaging . FILE , extension : path . extname ( archiveFile ) } ;
613+ }
614+ }
0 commit comments