2016-08-16 20:28:57 +02:00
'use strict' ;
const BbPromise = require ( 'bluebird' ) ;
const path = require ( 'path' ) ;
const fse = require ( 'fs-extra' ) ;
2017-07-28 10:40:08 +01:00
const glob = require ( 'glob' ) ;
2017-07-26 17:42:15 -03:00
const lib = require ( './index' ) ;
const _ = require ( 'lodash' ) ;
2018-03-07 01:20:15 +01:00
const Configuration = require ( './Configuration' ) ;
2017-07-26 17:42:15 -03:00
2017-07-28 10:40:08 +01:00
/**
* For automatic entry detection we sort the found files to solve ambiguities.
* This should cover most of the cases. For complex setups the user should
* build his own entries with help of the other exports.
*/
const preferredExtensions = [
'.js' ,
'.ts' ,
2017-08-20 18:11:20 +02:00
'.jsx' ,
'.tsx'
2017-07-28 10:40:08 +01:00
] ;
2016-08-16 20:28:57 +02:00
module . exports = {
validate ( ) {
2017-11-11 11:00:43 +00:00
const getHandlerFile = handler => {
// Check if handler is a well-formed path based handler.
const handlerEntry = /(.*)\..*?$/ . exec ( handler ) ;
if ( handlerEntry ) {
return handlerEntry [ 1 ] ;
}
} ;
2017-07-28 10:40:08 +01:00
const getEntryExtension = fileName => {
const files = glob . sync ( ` ${ fileName } .* ` , {
cwd : this . serverless . config . servicePath ,
nodir : true
} ) ;
if ( _ . isEmpty ( files ) ) {
// If we cannot find any handler we should terminate with an error
2018-06-28 19:55:35 +01:00
throw new this . serverless . classes . Error ( ` No matching handler found for ' ${ fileName } ' in ' ${ this . serverless . config . servicePath } '. Check your service definition. ` ) ;
2017-07-28 10:40:08 +01:00
}
// Move preferred file extensions to the beginning
const sortedFiles = _ . uniq (
_ . concat (
_ . sortBy (
_ . filter ( files , file => _ . includes ( preferredExtensions , path . extname ( file ) ) ) ,
a => _ . size ( a )
) ,
files
)
) ;
if ( _ . size ( sortedFiles ) > 1 ) {
this . serverless . cli . log ( ` WARNING: More than one matching handlers found for ' ${ fileName } '. Using ' ${ _ . first ( sortedFiles ) } '. ` ) ;
}
return path . extname ( _ . first ( sortedFiles ) ) ;
2017-07-28 21:40:39 +02:00
} ;
2017-07-28 10:40:08 +01:00
2017-08-20 17:17:14 +02:00
const getEntryForFunction = ( name , serverlessFunction ) => {
2017-07-28 10:40:08 +01:00
const handler = serverlessFunction . handler ;
2017-11-18 13:00:29 +01:00
2017-11-11 11:00:43 +00:00
const handlerFile = getHandlerFile ( handler ) ;
if ( ! handlerFile ) {
2017-08-20 17:17:14 +02:00
_ . get ( this . serverless , 'service.provider.name' ) !== 'google' &&
this . serverless . cli . log ( ` \n WARNING: Entry for ${ name } @ ${ handler } could not be retrieved. \n Please check your service config if you want to use lib.entries. ` ) ;
return { } ;
}
2017-07-28 10:40:08 +01:00
const ext = getEntryExtension ( handlerFile ) ;
// Create a valid entry key
return {
[ handlerFile ] : ` ./ ${ handlerFile } ${ ext } `
} ;
} ;
2018-03-07 01:20:15 +01:00
// Initialize plugin configuration
this . configuration = new Configuration ( this . serverless . service . custom ) ;
this . options . verbose && this . serverless . cli . log ( ` Using configuration: \n ${ JSON . stringify ( this . configuration , null , 2 ) } ` ) ;
if ( this . configuration . hasLegacyConfig ) {
this . serverless . cli . log ( 'Legacy configuration detected. Consider to use "custom.webpack" as object (see README).' ) ;
}
this . webpackConfig = this . configuration . config || this . configuration . webpackConfig ;
2016-08-16 20:28:57 +02:00
2017-07-28 21:40:39 +02:00
// Expose entries - must be done before requiring the webpack configuration
2017-07-26 17:42:15 -03:00
const entries = { } ;
2017-07-28 21:40:39 +02:00
2017-07-26 17:42:15 -03:00
const functions = this . serverless . service . getAllFunctions ( ) ;
if ( this . options . function ) {
2017-07-28 21:40:39 +02:00
const serverlessFunction = this . serverless . service . getFunction ( this . options . function ) ;
2017-08-20 17:17:14 +02:00
const entry = getEntryForFunction . call ( this , this . options . function , serverlessFunction ) ;
2017-07-26 17:42:15 -03:00
_ . merge ( entries , entry ) ;
} else {
2017-08-20 17:17:14 +02:00
_ . forEach ( functions , ( func , index ) => {
const entry = getEntryForFunction . call ( this , functions [ index ] , this . serverless . service . getFunction ( func ) ) ;
2017-07-26 17:42:15 -03:00
_ . merge ( entries , entry ) ;
} ) ;
}
2017-07-28 10:40:08 +01:00
2017-07-27 19:38:04 +01:00
// Expose service file and options
lib . serverless = this . serverless ;
lib . options = this . options ;
2017-07-28 21:40:39 +02:00
lib . entries = entries ;
2017-07-26 17:42:15 -03:00
if ( _ . isString ( this . webpackConfig ) ) {
2016-08-16 20:28:57 +02:00
const webpackConfigFilePath = path . join ( this . serverless . config . servicePath , this . webpackConfig ) ;
if ( ! this . serverless . utils . fileExistsSync ( webpackConfigFilePath ) ) {
2018-07-07 15:23:13 +02:00
return BbPromise . reject ( new this . serverless . classes
. Error ( 'The webpack plugin could not find the configuration file at: ' + webpackConfigFilePath ) ) ;
2016-08-16 20:28:57 +02:00
}
2017-09-21 21:15:57 +02:00
try {
this . webpackConfig = require ( webpackConfigFilePath ) ;
} catch ( err ) {
this . serverless . cli . log ( ` Could not load webpack config ' ${ webpackConfigFilePath } ' ` ) ;
return BbPromise . reject ( err ) ;
}
2016-08-16 20:28:57 +02:00
}
2018-06-20 11:44:12 +02:00
// Intermediate function to handle async webpack config
2018-06-20 11:06:53 +02:00
const processConfig = _config => {
this . webpackConfig = _config ;
// Default context
if ( ! this . webpackConfig . context ) {
this . webpackConfig . context = this . serverless . config . servicePath ;
2017-11-10 07:41:46 +00:00
}
2018-06-20 11:06:53 +02:00
// Default target
if ( ! this . webpackConfig . target ) {
this . webpackConfig . target = 'node' ;
}
2017-07-28 21:40:39 +02:00
2018-06-20 11:06:53 +02:00
// Default output
if ( ! this . webpackConfig . output || _ . isEmpty ( this . webpackConfig . output ) ) {
const outputPath = path . join ( this . serverless . config . servicePath , '.webpack' ) ;
this . webpackConfig . output = {
libraryTarget : 'commonjs' ,
path : outputPath ,
filename : '[name].js' ,
2017-07-28 21:40:39 +02:00
} ;
2018-06-20 11:06:53 +02:00
}
2016-08-16 20:28:57 +02:00
2018-06-20 11:06:53 +02:00
// Custom output path
if ( this . options . out ) {
this . webpackConfig . output . path = path . join ( this . serverless . config . servicePath , this . options . out ) ;
}
if ( this . skipCompile ) {
this . serverless . cli . log ( 'Skipping build and using existing compiled output' ) ;
if ( ! fse . pathExistsSync ( this . webpackConfig . output . path ) ) {
return BbPromise . reject ( new this . serverless . classes
. Error ( 'No compiled output found' ) ) ;
}
this . keepOutputDirectory = true ;
}
if ( ! this . keepOutputDirectory ) {
this . options . verbose && this . serverless . cli . log ( ` Removing ${ this . webpackConfig . output . path } ` ) ;
fse . removeSync ( this . webpackConfig . output . path ) ;
}
this . webpackOutputPath = this . webpackConfig . output . path ;
// In case of individual packaging we have to create a separate config for each function
if ( _ . has ( this . serverless , 'service.package' ) && this . serverless . service . package . individually ) {
this . options . verbose && this . serverless . cli . log ( 'Using multi-compile (individual packaging)' ) ;
this . multiCompile = true ;
if ( this . webpackConfig . entry && ! _ . isEqual ( this . webpackConfig . entry , entries ) ) {
return BbPromise . reject ( new this . serverless . classes
. Error ( 'Webpack entry must be automatically resolved when package.individually is set to true. ' +
'In webpack.config.js, remove the entry declaration or set entry to slsw.lib.entries.' ) ) ;
}
// Lookup associated Serverless functions
const allEntryFunctions = _ . map (
this . serverless . service . getAllFunctions ( ) ,
funcName => {
const func = this . serverless . service . getFunction ( funcName ) ;
const handler = func . handler ;
const handlerFile = path . relative ( '.' , getHandlerFile ( handler ) ) ;
return {
handlerFile ,
funcName ,
func
} ;
}
) ;
this . entryFunctions = _ . flatMap ( entries , ( value , key ) => {
const entry = path . relative ( '.' , value ) ;
const entryFile = _ . replace ( entry , new RegExp ( ` ${ path . extname ( entry ) } $ ` ) , '' ) ;
const entryFuncs = _ . filter ( allEntryFunctions , [ 'handlerFile' , entryFile ] ) ;
if ( _ . isEmpty ( entryFuncs ) ) {
// We have to make sure that for each entry there is an entry function item.
entryFuncs . push ( { } ) ;
}
_ . forEach ( entryFuncs , entryFunc => {
entryFunc . entry = {
key ,
value
} ;
} ) ;
return entryFuncs ;
} ) ;
this . webpackConfig = _ . map ( this . entryFunctions , entryFunc => {
const config = _ . cloneDeep ( this . webpackConfig ) ;
config . entry = {
[ entryFunc . entry . key ] : entryFunc . entry . value
} ;
const compileName = entryFunc . funcName || _ . camelCase ( entryFunc . entry . key ) ;
config . output . path = path . join ( config . output . path , compileName ) ;
return config ;
} ) ;
} else {
this . webpackConfig . output . path = path . join ( this . webpackConfig . output . path , 'service' ) ;
}
return BbPromise . resolve ( ) ;
2018-06-20 12:12:43 +02:00
} ;
2018-06-20 11:44:12 +02:00
// Webpack config can be a Promise, If it's a Promise wait for resolved config object.
2018-06-21 21:49:10 +02:00
if ( this . webpackConfig && _ . isFunction ( this . webpackConfig . then ) ) {
2018-07-07 15:23:13 +02:00
return BbPromise . resolve ( this . webpackConfig . then ( config => processConfig ( config ) ) ) ;
2018-06-20 11:06:53 +02:00
} else {
2018-06-20 12:12:43 +02:00
return processConfig ( this . webpackConfig ) ;
2018-06-20 11:06:53 +02:00
}
2016-08-16 20:28:57 +02:00
} ,
} ;