2020-04-21 01:36:05 +02:00
// Copyright 2016-2020, Pulumi Corporation.
2020-01-21 23:45:48 +01:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Pulling out some of the repeated strings tokens into constants would harm readability, so we just ignore the
// goconst linter's warning.
//
// nolint: lll, goconst
package nodejs
import (
"bytes"
"encoding/json"
"fmt"
"io"
"path"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"unicode"
"github.com/pkg/errors"
2021-03-17 14:20:05 +01:00
"github.com/pulumi/pulumi/pkg/v3/codegen"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
2020-01-21 23:45:48 +01:00
)
type typeDetails struct {
2021-04-20 01:40:39 +02:00
outputType bool
inputType bool
argsType bool
plainType bool
2020-01-21 23:45:48 +01:00
}
func title ( s string ) string {
if s == "" {
return ""
}
runes := [ ] rune ( s )
return string ( append ( [ ] rune { unicode . ToUpper ( runes [ 0 ] ) } , runes [ 1 : ] ... ) )
}
func camel ( s string ) string {
if s == "" {
return ""
}
runes := [ ] rune ( s )
res := make ( [ ] rune , 0 , len ( runes ) )
for i , r := range runes {
if unicode . IsLower ( r ) {
res = append ( res , runes [ i : ] ... )
break
}
res = append ( res , unicode . ToLower ( r ) )
}
return string ( res )
}
type modContext struct {
2020-06-07 18:21:09 +02:00
pkg * schema . Package
mod string
types [ ] * schema . ObjectType
2020-10-08 03:13:10 +02:00
enums [ ] * schema . EnumType
2020-06-07 18:21:09 +02:00
resources [ ] * schema . Resource
functions [ ] * schema . Function
typeDetails map [ * schema . ObjectType ] * typeDetails
children [ ] * modContext
extraSourceFiles [ ] string
tool string
2020-05-13 19:55:37 +02:00
// Name overrides set in NodeJSInfo
2020-06-03 03:15:21 +02:00
modToPkg map [ string ] string // Module name -> package name
compatibility string // Toggle compatibility mode for a specified target.
disableUnionOutputTypes bool // Disable unions in output types.
2020-01-21 23:45:48 +01:00
}
2020-11-06 18:29:49 +01:00
func ( mod * modContext ) String ( ) string {
return mod . mod
}
2020-01-21 23:45:48 +01:00
func ( mod * modContext ) details ( t * schema . ObjectType ) * typeDetails {
details , ok := mod . typeDetails [ t ]
if ! ok {
details = & typeDetails { }
if mod . typeDetails == nil {
mod . typeDetails = map [ * schema . ObjectType ] * typeDetails { }
}
mod . typeDetails [ t ] = details
}
return details
}
2020-09-25 21:35:27 +02:00
func ( mod * modContext ) tokenToModName ( tok string ) string {
2020-01-21 23:45:48 +01:00
components := strings . Split ( tok , ":" )
contract . Assertf ( len ( components ) == 3 , "malformed token %v" , tok )
2020-09-25 21:35:27 +02:00
modName := mod . pkg . TokenToModule ( tok )
2020-05-13 19:55:37 +02:00
if override , ok := mod . modToPkg [ modName ] ; ok {
modName = override
}
2020-01-21 23:45:48 +01:00
2020-09-25 21:35:27 +02:00
if modName != "" {
modName = strings . Replace ( modName , "/" , "." , - 1 ) + "."
}
return modName
}
2021-01-30 01:52:00 +01:00
func ( mod * modContext ) namingContext ( pkg * schema . Package ) ( namingCtx * modContext , pkgName string , external bool ) {
namingCtx = mod
if pkg != nil && pkg != mod . pkg {
external = true
pkgName = pkg . Name + "."
2020-09-25 21:35:27 +02:00
2021-01-30 01:52:00 +01:00
var info NodePackageInfo
contract . AssertNoError ( pkg . ImportLanguages ( map [ string ] schema . Language { "nodejs" : Importer } ) )
if v , ok := pkg . Language [ "nodejs" ] . ( NodePackageInfo ) ; ok {
info = v
}
namingCtx = & modContext {
pkg : pkg ,
modToPkg : info . ModuleToPackage ,
compatibility : info . Compatibility ,
}
2020-10-08 03:13:10 +02:00
}
2021-01-30 01:52:00 +01:00
return
}
2021-04-16 04:03:28 +02:00
func ( mod * modContext ) objectType ( pkg * schema . Package , tok string , input , args , enum bool ) string {
2020-01-21 23:45:48 +01:00
root := "outputs."
if input {
root = "inputs."
}
2021-01-30 01:52:00 +01:00
namingCtx , pkgName , external := mod . namingContext ( pkg )
if external {
root = "types.output."
if input {
root = "types.input."
}
}
modName , name := namingCtx . tokenToModName ( tok ) , tokenToName ( tok )
if enum {
return "enums." + modName + title ( name )
}
2021-04-16 04:03:28 +02:00
2021-04-16 20:30:25 +02:00
if args && mod . compatibility != tfbridge20 && mod . compatibility != kubernetes20 {
2021-04-16 04:03:28 +02:00
name += "Args"
}
2021-01-30 01:52:00 +01:00
return pkgName + root + modName + title ( name )
2020-01-21 23:45:48 +01:00
}
2021-01-30 01:52:00 +01:00
func ( mod * modContext ) resourceType ( r * schema . ResourceType ) string {
if strings . HasPrefix ( r . Token , "pulumi:providers:" ) {
pkgName := strings . TrimPrefix ( r . Token , "pulumi:providers:" )
return fmt . Sprintf ( "%s.Provider" , pkgName )
}
2020-09-25 21:35:27 +02:00
2021-01-30 01:52:00 +01:00
pkg := mod . pkg
if r . Resource != nil {
pkg = r . Resource . Package
}
namingCtx , pkgName , _ := mod . namingContext ( pkg )
modName , name := namingCtx . tokenToModName ( r . Token ) , tokenToName ( r . Token )
return pkgName + modName + title ( name )
2020-09-25 21:35:27 +02:00
}
2020-01-21 23:45:48 +01:00
func tokenToName ( tok string ) string {
components := strings . Split ( tok , ":" )
contract . Assertf ( len ( components ) == 3 , "malformed token %v" , tok )
return title ( components [ 2 ] )
}
func resourceName ( r * schema . Resource ) string {
if r . IsProvider {
return "Provider"
}
return tokenToName ( r . Token )
}
2020-12-02 22:45:25 +01:00
func ( mod * modContext ) resourceFileName ( r * schema . Resource ) string {
fileName := camel ( resourceName ( r ) ) + ".ts"
if mod . isReservedSourceFileName ( fileName ) {
fileName = camel ( resourceName ( r ) ) + "_.ts"
}
return fileName
}
2020-04-28 02:47:01 +02:00
func tokenToFunctionName ( tok string ) string {
return camel ( tokenToName ( tok ) )
}
2021-04-19 23:05:23 +02:00
func ( mod * modContext ) typeString ( t schema . Type , input , wrapInput , args , optional bool , constValue interface { } ) string {
2020-01-21 23:45:48 +01:00
var typ string
switch t := t . ( type ) {
2020-10-08 03:13:10 +02:00
case * schema . EnumType :
2021-04-16 04:03:28 +02:00
typ = mod . objectType ( nil , t . Token , input , args , true )
2020-01-21 23:45:48 +01:00
case * schema . ArrayType :
2021-04-19 23:05:23 +02:00
typ = mod . typeString ( t . ElementType , input , wrapInput , args , false , constValue ) + "[]"
2020-01-21 23:45:48 +01:00
case * schema . MapType :
2021-04-19 23:05:23 +02:00
typ = fmt . Sprintf ( "{[key: string]: %v}" , mod . typeString ( t . ElementType , input , wrapInput , args , false , constValue ) )
2020-01-21 23:45:48 +01:00
case * schema . ObjectType :
2021-04-16 04:03:28 +02:00
typ = mod . objectType ( t . Package , t . Token , input , args , false )
2020-09-25 21:35:27 +02:00
case * schema . ResourceType :
2021-01-30 01:52:00 +01:00
typ = mod . resourceType ( t )
2020-01-21 23:45:48 +01:00
case * schema . TokenType :
typ = tokenToName ( t . Token )
case * schema . UnionType :
2020-06-03 03:15:21 +02:00
if ! input && mod . disableUnionOutputTypes {
if t . DefaultType != nil {
2021-04-19 23:05:23 +02:00
return mod . typeString ( t . DefaultType , input , wrapInput , args , optional , constValue )
2020-05-13 19:55:37 +02:00
}
2020-06-03 03:15:21 +02:00
typ = "any"
} else {
var elements [ ] string
for _ , e := range t . ElementTypes {
2021-04-19 23:05:23 +02:00
t := mod . typeString ( e , input , wrapInput , args , false , constValue )
2021-04-16 04:03:28 +02:00
if args && strings . HasPrefix ( t , "pulumi.Input<" ) {
2020-06-03 03:15:21 +02:00
contract . Assert ( t [ len ( t ) - 1 ] == '>' )
// Strip off the leading `pulumi.Input<` and the trailing `>`
t = t [ len ( "pulumi.Input<" ) : len ( t ) - 1 ]
}
elements = append ( elements , t )
}
typ = strings . Join ( elements , " | " )
2020-01-21 23:45:48 +01:00
}
default :
switch t {
case schema . BoolType :
typ = "boolean"
case schema . IntType , schema . NumberType :
typ = "number"
case schema . StringType :
typ = "string"
case schema . ArchiveType :
typ = "pulumi.asset.Archive"
case schema . AssetType :
typ = "pulumi.asset.Asset | pulumi.asset.Archive"
2020-05-19 11:41:06 +02:00
case schema . JSONType :
fallthrough
2020-01-21 23:45:48 +01:00
case schema . AnyType :
typ = "any"
}
}
2020-06-05 19:11:18 +02:00
if constValue != nil && typ == "string" {
typ = fmt . Sprintf ( "%q" , constValue . ( string ) )
}
2021-04-19 23:05:23 +02:00
if wrapInput && typ != "any" {
2020-01-21 23:45:48 +01:00
typ = fmt . Sprintf ( "pulumi.Input<%s>" , typ )
}
if optional {
return typ + " | undefined"
}
return typ
}
2020-02-07 18:43:20 +01:00
func isStringType ( t schema . Type ) bool {
for tt , ok := t . ( * schema . TokenType ) ; ok ; tt , ok = t . ( * schema . TokenType ) {
t = tt . UnderlyingType
}
return t == schema . StringType
}
2020-01-21 23:45:48 +01:00
func sanitizeComment ( str string ) string {
return strings . Replace ( str , "*/" , "*/" , - 1 )
}
2020-02-06 18:29:06 +01:00
func printComment ( w io . Writer , comment , deprecationMessage , indent string ) {
if comment == "" && deprecationMessage == "" {
return
}
2020-01-21 23:45:48 +01:00
lines := strings . Split ( sanitizeComment ( comment ) , "\n" )
for len ( lines ) > 0 && lines [ len ( lines ) - 1 ] == "" {
lines = lines [ : len ( lines ) - 1 ]
}
fmt . Fprintf ( w , "%s/**\n" , indent )
for _ , l := range lines {
2020-02-11 23:34:22 +01:00
if l == "" {
fmt . Fprintf ( w , "%s *\n" , indent )
} else {
fmt . Fprintf ( w , "%s * %s\n" , indent , l )
}
2020-01-21 23:45:48 +01:00
}
2020-02-06 18:29:06 +01:00
if deprecationMessage != "" {
if len ( lines ) > 0 {
2020-02-11 23:34:22 +01:00
fmt . Fprintf ( w , "%s *\n" , indent )
2020-02-06 18:29:06 +01:00
}
fmt . Fprintf ( w , "%s * @deprecated %s\n" , indent , deprecationMessage )
}
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , "%s */\n" , indent )
}
2021-04-16 04:03:28 +02:00
func ( mod * modContext ) genPlainType ( w io . Writer , name , comment string , properties [ ] * schema . Property , input , arg , readonly bool , level int ) {
2020-01-21 23:45:48 +01:00
indent := strings . Repeat ( " " , level )
2020-02-06 18:29:06 +01:00
printComment ( w , comment , "" , indent )
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , "%sexport interface %s {\n" , indent , name )
for _ , p := range properties {
2020-02-06 18:29:06 +01:00
printComment ( w , p . Comment , p . DeprecationMessage , indent + " " )
2020-01-21 23:45:48 +01:00
prefix := ""
if readonly {
prefix = "readonly "
}
sigil := ""
if ! p . IsRequired {
sigil = "?"
}
2021-04-19 23:05:23 +02:00
typ := mod . typeString ( p . Type , input , arg && ! p . IsPlain , arg && ! p . IsPlain , false , p . ConstValue )
2021-03-10 16:08:08 +01:00
fmt . Fprintf ( w , "%s %s%s%s: %s;\n" , indent , prefix , p . Name , sigil , typ )
2020-01-21 23:45:48 +01:00
}
fmt . Fprintf ( w , "%s}\n" , indent )
}
func tsPrimitiveValue ( value interface { } ) ( string , error ) {
v := reflect . ValueOf ( value )
if v . Kind ( ) == reflect . Interface {
v = v . Elem ( )
}
switch v . Kind ( ) {
case reflect . Bool :
if v . Bool ( ) {
return "true" , nil
}
return "false" , nil
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 :
return strconv . FormatInt ( v . Int ( ) , 10 ) , nil
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 :
return strconv . FormatUint ( v . Uint ( ) , 10 ) , nil
case reflect . Float32 , reflect . Float64 :
return strconv . FormatFloat ( v . Float ( ) , 'f' , - 1 , 64 ) , nil
case reflect . String :
return fmt . Sprintf ( "%q" , v . String ( ) ) , nil
default :
return "" , errors . Errorf ( "unsupported default value of type %T" , value )
}
}
2020-05-13 19:55:37 +02:00
func ( mod * modContext ) getConstValue ( cv interface { } ) ( string , error ) {
if cv == nil {
return "" , nil
}
return tsPrimitiveValue ( cv )
}
2020-01-21 23:45:48 +01:00
func ( mod * modContext ) getDefaultValue ( dv * schema . DefaultValue , t schema . Type ) ( string , error ) {
var val string
if dv . Value != nil {
v , err := tsPrimitiveValue ( dv . Value )
if err != nil {
return "" , err
}
val = v
}
if len ( dv . Environment ) != 0 {
getType := ""
switch t {
case schema . BoolType :
getType = "Boolean"
case schema . IntType , schema . NumberType :
getType = "Number"
}
envVars := fmt . Sprintf ( "%q" , dv . Environment [ 0 ] )
for _ , e := range dv . Environment [ 1 : ] {
envVars += fmt . Sprintf ( ", %q" , e )
}
cast := ""
if t != schema . StringType {
cast = "<any>"
}
getEnv := fmt . Sprintf ( "%sutilities.getEnv%s(%s)" , cast , getType , envVars )
if val != "" {
val = fmt . Sprintf ( "(%s || %s)" , getEnv , val )
} else {
val = getEnv
}
}
return val , nil
}
func ( mod * modContext ) genAlias ( w io . Writer , alias * schema . Alias ) {
fmt . Fprintf ( w , "{ " )
2020-09-25 21:35:27 +02:00
var parts [ ] string
2020-01-21 23:45:48 +01:00
if alias . Name != nil {
parts = append ( parts , fmt . Sprintf ( "name: \"%v\"" , * alias . Name ) )
}
if alias . Project != nil {
parts = append ( parts , fmt . Sprintf ( "project: \"%v\"" , * alias . Project ) )
}
if alias . Type != nil {
parts = append ( parts , fmt . Sprintf ( "type: \"%v\"" , * alias . Type ) )
}
for i , part := range parts {
if i > 0 {
fmt . Fprintf ( w , ", " )
}
fmt . Fprintf ( w , "%s" , part )
}
2020-02-11 23:34:22 +01:00
fmt . Fprintf ( w , " }" )
2020-01-21 23:45:48 +01:00
}
func ( mod * modContext ) genResource ( w io . Writer , r * schema . Resource ) error {
// Create a resource module file into which all of this resource's types will go.
name := resourceName ( r )
// Write the TypeDoc/JSDoc for the resource class
2020-06-18 21:32:15 +02:00
printComment ( w , codegen . FilterExamples ( r . Comment , "typescript" ) , r . DeprecationMessage , "" )
2020-01-21 23:45:48 +01:00
2020-09-25 21:35:27 +02:00
var baseType , optionsType string
switch {
case r . IsComponent :
baseType , optionsType = "ComponentResource" , "ComponentResourceOptions"
case r . IsProvider :
baseType , optionsType = "ProviderResource" , "ResourceOptions"
default :
baseType , optionsType = "CustomResource" , "CustomResourceOptions"
2020-01-21 23:45:48 +01:00
}
// Begin defining the class.
fmt . Fprintf ( w , "export class %s extends pulumi.%s {\n" , name , baseType )
2020-09-25 21:35:27 +02:00
// Emit a static factory to read instances of this resource unless this is a provider resource or ComponentResource.
2020-01-21 23:45:48 +01:00
stateType := name + "State"
2020-09-25 21:35:27 +02:00
if ! r . IsProvider && ! r . IsComponent {
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , " /**\n" )
fmt . Fprintf ( w , " * Get an existing %s resource's state with the given name, ID, and optional extra\n" , name )
fmt . Fprintf ( w , " * properties used to qualify the lookup.\n" )
fmt . Fprintf ( w , " *\n" )
fmt . Fprintf ( w , " * @param name The _unique_ name of the resulting resource.\n" )
fmt . Fprintf ( w , " * @param id The _unique_ provider ID of the resource to lookup.\n" )
2020-06-05 19:11:18 +02:00
// TODO: Document id format: https://github.com/pulumi/pulumi/issues/4754
if r . StateInputs != nil {
fmt . Fprintf ( w , " * @param state Any extra arguments used during the lookup.\n" )
}
fmt . Fprintf ( w , " * @param opts Optional settings to control the behavior of the CustomResource.\n" )
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , " */\n" )
2020-09-02 17:09:41 +02:00
stateParam , stateRef := "" , "undefined as any, "
2020-01-21 23:45:48 +01:00
if r . StateInputs != nil {
stateParam , stateRef = fmt . Sprintf ( "state?: %s, " , stateType ) , "<any>state, "
}
2020-09-25 21:35:27 +02:00
fmt . Fprintf ( w , " public static get(name: string, id: pulumi.Input<pulumi.ID>, %sopts?: pulumi.%s): %s {\n" ,
stateParam , optionsType , name )
2020-07-01 21:50:26 +02:00
if r . DeprecationMessage != "" && mod . compatibility != kubernetes20 {
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , " pulumi.log.warn(\"%s is deprecated: %s\")\n" , name , r . DeprecationMessage )
}
fmt . Fprintf ( w , " return new %s(name, %s{ ...opts, id: id });\n" , name , stateRef )
fmt . Fprintf ( w , " }\n" )
fmt . Fprintf ( w , "\n" )
}
pulumiType := r . Token
if r . IsProvider {
pulumiType = mod . pkg . Name
}
fmt . Fprintf ( w , " /** @internal */\n" )
fmt . Fprintf ( w , " public static readonly __pulumiType = '%s';\n" , pulumiType )
fmt . Fprintf ( w , "\n" )
fmt . Fprintf ( w , " /**\n" )
fmt . Fprintf ( w , " * Returns true if the given object is an instance of %s. This is designed to work even\n" , name )
fmt . Fprintf ( w , " * when multiple copies of the Pulumi SDK have been loaded into the same process.\n" )
fmt . Fprintf ( w , " */\n" )
fmt . Fprintf ( w , " public static isInstance(obj: any): obj is %s {\n" , name )
fmt . Fprintf ( w , " if (obj === undefined || obj === null) {\n" )
fmt . Fprintf ( w , " return false;\n" )
fmt . Fprintf ( w , " }\n" )
fmt . Fprintf ( w , " return obj['__pulumiType'] === %s.__pulumiType;\n" , name )
fmt . Fprintf ( w , " }\n" )
fmt . Fprintf ( w , "\n" )
// Emit all properties (using their output types).
// TODO[pulumi/pulumi#397]: represent sensitive types using a Secret<T> type.
2020-05-29 01:22:50 +02:00
ins := codegen . NewStringSet ( )
2020-01-21 23:45:48 +01:00
allOptionalInputs := true
for _ , prop := range r . InputProperties {
2020-05-29 01:22:50 +02:00
ins . Add ( prop . Name )
2020-01-21 23:45:48 +01:00
allOptionalInputs = allOptionalInputs && ! prop . IsRequired
}
for _ , prop := range r . Properties {
2020-02-06 18:29:06 +01:00
printComment ( w , prop . Comment , prop . DeprecationMessage , " " )
2020-01-21 23:45:48 +01:00
// Make a little comment in the code so it's easy to pick out output properties.
var outcomment string
2020-05-29 01:22:50 +02:00
if ! ins . Has ( prop . Name ) {
2020-01-21 23:45:48 +01:00
outcomment = "/*out*/ "
}
2020-05-29 21:39:29 +02:00
required := prop . IsRequired
if mod . compatibility == kubernetes20 {
required = true
}
2021-04-19 23:05:23 +02:00
fmt . Fprintf ( w , " public %sreadonly %s!: pulumi.Output<%s>;\n" , outcomment , prop . Name , mod . typeString ( prop . Type , false , false , false , ! required , prop . ConstValue ) )
2020-01-21 23:45:48 +01:00
}
fmt . Fprintf ( w , "\n" )
// Now create a constructor that chains supercalls and stores into properties.
fmt . Fprintf ( w , " /**\n" )
fmt . Fprintf ( w , " * Create a %s resource with the given unique name, arguments, and options.\n" , name )
fmt . Fprintf ( w , " *\n" )
fmt . Fprintf ( w , " * @param name The _unique_ name of the resource.\n" )
fmt . Fprintf ( w , " * @param args The arguments to use to populate this resource's properties.\n" )
fmt . Fprintf ( w , " * @param opts A bag of options that control this resource's behavior.\n" )
fmt . Fprintf ( w , " */\n" )
2020-05-30 00:35:42 +02:00
// k8s provider "get" methods don't require args, so make args optional.
if mod . compatibility == kubernetes20 {
allOptionalInputs = true
}
2020-01-21 23:45:48 +01:00
// Write out callable constructor: We only emit a single public constructor, even though we use a private signature
// as well as part of the implementation of `.get`. This is complicated slightly by the fact that, if there is no
// args type, we will emit a constructor lacking that parameter.
var argsFlags string
2020-05-30 00:35:42 +02:00
if allOptionalInputs {
2020-01-21 23:45:48 +01:00
// If the number of required input properties was zero, we can make the args object optional.
argsFlags = "?"
}
argsType := name + "Args"
2020-09-25 21:35:27 +02:00
var trailingBrace string
switch {
case r . IsProvider , r . StateInputs == nil :
2020-09-02 17:09:41 +02:00
trailingBrace = " {"
2020-09-25 21:35:27 +02:00
default :
trailingBrace = ""
2020-09-02 17:09:41 +02:00
}
2020-01-21 23:45:48 +01:00
if r . DeprecationMessage != "" {
fmt . Fprintf ( w , " /** @deprecated %s */\n" , r . DeprecationMessage )
}
fmt . Fprintf ( w , " constructor(name: string, args%s: %s, opts?: pulumi.%s)%s\n" , argsFlags , argsType ,
optionsType , trailingBrace )
2020-09-02 00:37:03 +02:00
genInputProps := func ( ) error {
for _ , prop := range r . InputProperties {
if prop . IsRequired {
2021-02-12 19:10:59 +01:00
fmt . Fprintf ( w , " if ((!args || args.%s === undefined) && !opts.urn) {\n" , prop . Name )
2020-09-02 00:37:03 +02:00
fmt . Fprintf ( w , " throw new Error(\"Missing required property '%s'\");\n" , prop . Name )
fmt . Fprintf ( w , " }\n" )
}
}
for _ , prop := range r . InputProperties {
2021-05-27 00:00:51 +02:00
var arg string
if prop . Secret {
arg = fmt . Sprintf ( "args?.%[1]s ? pulumi.secret(args.%[1]s) : undefined" , prop . Name )
} else {
arg = fmt . Sprintf ( "args ? args.%[1]s : undefined" , prop . Name )
}
2020-09-02 00:37:03 +02:00
prefix := " "
if prop . ConstValue != nil {
cv , err := mod . getConstValue ( prop . ConstValue )
if err != nil {
return err
}
arg = cv
} else {
if prop . DefaultValue != nil {
dv , err := mod . getDefaultValue ( prop . DefaultValue , prop . Type )
if err != nil {
return err
}
2021-03-10 22:16:18 +01:00
arg = fmt . Sprintf ( "(%s) ?? %s" , arg , dv )
2020-09-02 00:37:03 +02:00
}
// provider properties must be marshaled as JSON strings.
if r . IsProvider && ! isStringType ( prop . Type ) {
arg = fmt . Sprintf ( "pulumi.output(%s).apply(JSON.stringify)" , arg )
}
}
fmt . Fprintf ( w , "%sinputs[\"%s\"] = %s;\n" , prefix , prop . Name , arg )
}
2020-09-02 01:56:38 +02:00
for _ , prop := range r . Properties {
prefix := " "
if ! ins . Has ( prop . Name ) {
fmt . Fprintf ( w , "%sinputs[\"%s\"] = undefined /*out*/;\n" , prefix , prop . Name )
}
}
2020-09-02 00:37:03 +02:00
return nil
}
2020-01-21 23:45:48 +01:00
if ! r . IsProvider {
2020-05-13 19:55:37 +02:00
if r . StateInputs != nil {
if r . DeprecationMessage != "" {
fmt . Fprintf ( w , " /** @deprecated %s */\n" , r . DeprecationMessage )
}
2020-09-25 21:35:27 +02:00
2020-05-13 19:55:37 +02:00
// Now write out a general purpose constructor implementation that can handle the public signature as well as the
// signature to support construction via `.get`. And then emit the body preamble which will pluck out the
// conditional state into sensible variables using dynamic type tests.
2020-09-25 21:35:27 +02:00
fmt . Fprintf ( w , " constructor(name: string, argsOrState?: %s | %s, opts?: pulumi.%s) {\n" ,
argsType , stateType , optionsType )
2020-01-21 23:45:48 +01:00
}
2020-07-01 21:50:26 +02:00
if r . DeprecationMessage != "" && mod . compatibility != kubernetes20 {
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , " pulumi.log.warn(\"%s is deprecated: %s\")\n" , name , r . DeprecationMessage )
}
fmt . Fprintf ( w , " let inputs: pulumi.Inputs = {};\n" )
2021-02-12 19:10:59 +01:00
fmt . Fprintf ( w , " opts = opts || {};\n" )
2020-07-10 20:23:31 +02:00
2020-05-13 19:55:37 +02:00
if r . StateInputs != nil {
// The lookup case:
2021-02-12 19:10:59 +01:00
fmt . Fprintf ( w , " if (opts.id) {\n" )
2020-05-13 19:55:37 +02:00
fmt . Fprintf ( w , " const state = argsOrState as %[1]s | undefined;\n" , stateType )
2020-07-10 20:23:31 +02:00
for _ , prop := range r . StateInputs . Properties {
2020-05-13 19:55:37 +02:00
fmt . Fprintf ( w , " inputs[\"%[1]s\"] = state ? state.%[1]s : undefined;\n" , prop . Name )
}
// The creation case (with args):
fmt . Fprintf ( w , " } else {\n" )
2020-09-02 00:37:03 +02:00
fmt . Fprintf ( w , " const args = argsOrState as %s | undefined;\n" , argsType )
err := genInputProps ( )
if err != nil {
return err
}
2020-07-10 20:23:31 +02:00
} else {
// The creation case:
2021-02-12 19:10:59 +01:00
fmt . Fprintf ( w , " if (!opts.id) {\n" )
2020-09-02 00:37:03 +02:00
err := genInputProps ( )
2020-01-21 23:45:48 +01:00
if err != nil {
return err
}
2020-09-02 00:37:03 +02:00
// The get case:
fmt . Fprintf ( w , " } else {\n" )
for _ , prop := range r . Properties {
fmt . Fprintf ( w , " inputs[\"%[1]s\"] = undefined /*out*/;\n" , prop . Name )
2020-05-13 19:55:37 +02:00
}
2020-01-21 23:45:48 +01:00
}
2020-09-02 00:37:03 +02:00
} else {
fmt . Fprintf ( w , " let inputs: pulumi.Inputs = {};\n" )
2021-02-12 19:10:59 +01:00
fmt . Fprintf ( w , " opts = opts || {};\n" )
2020-09-02 00:37:03 +02:00
fmt . Fprintf ( w , " {\n" )
err := genInputProps ( )
if err != nil {
return err
}
2020-01-21 23:45:48 +01:00
}
2020-07-02 21:30:10 +02:00
var secretProps [ ] string
2020-01-21 23:45:48 +01:00
for _ , prop := range r . Properties {
2020-07-02 21:30:10 +02:00
if prop . Secret {
secretProps = append ( secretProps , prop . Name )
}
2020-01-21 23:45:48 +01:00
}
2020-07-10 20:23:31 +02:00
fmt . Fprintf ( w , " }\n" )
2020-01-21 23:45:48 +01:00
// If the caller didn't request a specific version, supply one using the version of this library.
fmt . Fprintf ( w , " if (!opts.version) {\n" )
2021-02-12 19:10:59 +01:00
fmt . Fprintf ( w , " opts = pulumi.mergeOptions(opts, { version: utilities.getVersion()});\n" )
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , " }\n" )
// Now invoke the super constructor with the type, name, and a property map.
if len ( r . Aliases ) > 0 {
fmt . Fprintf ( w , " const aliasOpts = { aliases: [" )
for i , alias := range r . Aliases {
if i > 0 {
fmt . Fprintf ( w , ", " )
}
mod . genAlias ( w , alias )
}
fmt . Fprintf ( w , "] };\n" )
2021-02-12 19:10:59 +01:00
fmt . Fprintf ( w , " opts = pulumi.mergeOptions(opts, aliasOpts);\n" )
2020-01-21 23:45:48 +01:00
}
2020-07-02 21:30:10 +02:00
if len ( secretProps ) > 0 {
2021-05-27 00:00:51 +02:00
fmt . Fprintf ( w , ` const secretOpts = { additionalSecretOutputs: ["%s"] }; ` , strings . Join ( secretProps , ` ", " ` ) )
fmt . Fprintf ( w , "\n opts = pulumi.mergeOptions(opts, secretOpts);\n" )
2020-07-02 21:30:10 +02:00
}
2020-09-25 21:35:27 +02:00
// If it's a ComponentResource, set the remote option.
if r . IsComponent {
fmt . Fprintf ( w , " super(%s.__pulumiType, name, inputs, opts, true /*remote*/);\n" , name )
} else {
fmt . Fprintf ( w , " super(%s.__pulumiType, name, inputs, opts);\n" , name )
}
2020-01-21 23:45:48 +01:00
// Finish the class.
fmt . Fprintf ( w , " }\n" )
fmt . Fprintf ( w , "}\n" )
// Emit the state type for get methods.
if r . StateInputs != nil {
fmt . Fprintf ( w , "\n" )
2021-05-07 06:59:40 +02:00
mod . genPlainType ( w , stateType , r . StateInputs . Comment , r . StateInputs . Properties , true , true , false , 0 )
2020-01-21 23:45:48 +01:00
}
// Emit the argument type for construction.
fmt . Fprintf ( w , "\n" )
argsComment := fmt . Sprintf ( "The set of arguments for constructing a %s resource." , name )
2021-05-07 06:59:40 +02:00
mod . genPlainType ( w , argsType , argsComment , r . InputProperties , true , true , false , 0 )
2020-01-21 23:45:48 +01:00
return nil
}
func ( mod * modContext ) genFunction ( w io . Writer , fun * schema . Function ) {
2020-04-28 02:47:01 +02:00
name := tokenToFunctionName ( fun . Token )
2020-01-21 23:45:48 +01:00
// Write the TypeDoc/JSDoc for the data source function.
2020-06-18 21:32:15 +02:00
printComment ( w , codegen . FilterExamples ( fun . Comment , "typescript" ) , "" , "" )
2020-01-21 23:45:48 +01:00
if fun . DeprecationMessage != "" {
fmt . Fprintf ( w , "/** @deprecated %s */\n" , fun . DeprecationMessage )
}
// Now, emit the function signature.
var argsig string
argsOptional := true
if fun . Inputs != nil {
for _ , p := range fun . Inputs . Properties {
if p . IsRequired {
argsOptional = false
break
}
}
optFlag := ""
if argsOptional {
optFlag = "?"
}
argsig = fmt . Sprintf ( "args%s: %sArgs, " , optFlag , title ( name ) )
}
var retty string
if fun . Outputs == nil {
retty = "void"
} else {
retty = title ( name ) + "Result"
}
2020-05-13 19:55:37 +02:00
fmt . Fprintf ( w , "export function %s(%sopts?: pulumi.InvokeOptions): Promise<%s> {\n" , name , argsig , retty )
2020-07-01 21:50:26 +02:00
if fun . DeprecationMessage != "" && mod . compatibility != kubernetes20 {
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , " pulumi.log.warn(\"%s is deprecated: %s\")\n" , name , fun . DeprecationMessage )
}
// Zero initialize the args if empty and necessary.
if fun . Inputs != nil && argsOptional {
fmt . Fprintf ( w , " args = args || {};\n" )
}
// If the caller didn't request a specific version, supply one using the version of this library.
fmt . Fprintf ( w , " if (!opts) {\n" )
fmt . Fprintf ( w , " opts = {}\n" )
fmt . Fprintf ( w , " }\n" )
fmt . Fprintf ( w , "\n" )
fmt . Fprintf ( w , " if (!opts.version) {\n" )
fmt . Fprintf ( w , " opts.version = utilities.getVersion();\n" )
fmt . Fprintf ( w , " }\n" )
// Now simply invoke the runtime function with the arguments, returning the results.
2020-05-13 19:55:37 +02:00
fmt . Fprintf ( w , " return pulumi.runtime.invoke(\"%s\", {\n" , fun . Token )
2020-01-21 23:45:48 +01:00
if fun . Inputs != nil {
for _ , p := range fun . Inputs . Properties {
// Pass the argument to the invocation.
fmt . Fprintf ( w , " \"%[1]s\": args.%[1]s,\n" , p . Name )
}
}
fmt . Fprintf ( w , " }, opts);\n" )
fmt . Fprintf ( w , "}\n" )
// If there are argument and/or return types, emit them.
if fun . Inputs != nil {
fmt . Fprintf ( w , "\n" )
2021-05-07 06:59:40 +02:00
mod . genPlainType ( w , title ( name ) + "Args" , fun . Inputs . Comment , fun . Inputs . Properties , true , false , false , 0 )
2020-01-21 23:45:48 +01:00
}
if fun . Outputs != nil {
fmt . Fprintf ( w , "\n" )
mod . genPlainType ( w , title ( name ) + "Result" , fun . Outputs . Comment , fun . Outputs . Properties , false , false , true , 0 )
}
}
2021-04-20 01:40:39 +02:00
func visitObjectTypes ( properties [ ] * schema . Property , visitor func ( * schema . ObjectType , bool ) ) {
codegen . VisitTypeClosure ( properties , func ( t codegen . Type ) {
if o , ok := t . Type . ( * schema . ObjectType ) ; ok {
visitor ( o , t . Plain )
2020-01-21 23:45:48 +01:00
}
2021-04-20 01:40:39 +02:00
} )
2020-01-21 23:45:48 +01:00
}
func ( mod * modContext ) genType ( w io . Writer , obj * schema . ObjectType , input bool , level int ) {
2020-06-01 20:50:10 +02:00
properties := obj . Properties
info , hasInfo := obj . Language [ "nodejs" ]
if hasInfo {
var requiredProperties [ ] string
if input {
requiredProperties = info . ( NodeObjectInfo ) . RequiredInputs
} else {
requiredProperties = info . ( NodeObjectInfo ) . RequiredOutputs
}
if requiredProperties != nil {
required := codegen . StringSet { }
for _ , name := range requiredProperties {
required . Add ( name )
}
properties = make ( [ ] * schema . Property , len ( obj . Properties ) )
for i , p := range obj . Properties {
copy := * p
properties [ i ] = & copy
properties [ i ] . IsRequired = required . Has ( p . Name )
}
}
}
2021-04-16 04:03:28 +02:00
name := tokenToName ( obj . Token )
2021-04-16 20:30:25 +02:00
if mod . compatibility == tfbridge20 || mod . compatibility == kubernetes20 {
2021-04-20 01:40:39 +02:00
wrapInput := input && ! mod . details ( obj ) . plainType
2021-04-16 04:03:28 +02:00
mod . genPlainType ( w , name , obj . Comment , properties , input , wrapInput , false , level )
return
}
if input {
2021-04-20 01:40:39 +02:00
if mod . details ( obj ) . plainType {
2021-04-16 04:03:28 +02:00
mod . genPlainType ( w , name , obj . Comment , properties , true , false , false , level )
}
if mod . details ( obj ) . argsType {
mod . genPlainType ( w , name + "Args" , obj . Comment , properties , true , true , false , level )
}
return
}
mod . genPlainType ( w , name , obj . Comment , properties , false , false , false , level )
2020-01-21 23:45:48 +01:00
}
2021-01-30 01:52:00 +01:00
func ( mod * modContext ) getTypeImports ( t schema . Type , recurse bool , externalImports codegen . StringSet , imports map [ string ] codegen . StringSet , seen codegen . Set ) bool {
2020-05-29 00:37:39 +02:00
if seen . Has ( t ) {
return false
}
seen . Add ( t )
2020-09-25 21:35:27 +02:00
resourceOrTokenImport := func ( tok string ) bool {
modName , name , modPath := mod . pkg . TokenToModule ( tok ) , tokenToName ( tok ) , "./index"
2020-06-05 19:11:18 +02:00
if override , ok := mod . modToPkg [ modName ] ; ok {
modName = override
}
2020-01-21 23:45:48 +01:00
if modName != mod . mod {
mp , err := filepath . Rel ( mod . mod , modName )
contract . Assert ( err == nil )
if path . Base ( mp ) == "." {
mp = path . Dir ( mp )
}
modPath = filepath . ToSlash ( mp )
}
if imports [ modPath ] == nil {
2020-05-29 01:22:50 +02:00
imports [ modPath ] = codegen . NewStringSet ( )
2020-01-21 23:45:48 +01:00
}
2020-05-29 01:22:50 +02:00
imports [ modPath ] . Add ( name )
2020-01-21 23:45:48 +01:00
return false
2020-09-25 21:35:27 +02:00
}
switch t := t . ( type ) {
case * schema . ArrayType :
2021-01-30 01:52:00 +01:00
return mod . getTypeImports ( t . ElementType , recurse , externalImports , imports , seen )
2020-09-25 21:35:27 +02:00
case * schema . MapType :
2021-01-30 01:52:00 +01:00
return mod . getTypeImports ( t . ElementType , recurse , externalImports , imports , seen )
2020-11-06 18:29:49 +01:00
case * schema . EnumType :
return true
2020-09-25 21:35:27 +02:00
case * schema . ObjectType :
2021-01-30 01:52:00 +01:00
// If it's from another package, add an import for the external package.
if t . Package != nil && t . Package != mod . pkg {
pkg := t . Package . Name
externalImports . Add ( fmt . Sprintf ( "import * as %[1]s from \"@pulumi/%[1]s\";" , pkg ) )
return false
}
2020-09-25 21:35:27 +02:00
for _ , p := range t . Properties {
2021-01-30 01:52:00 +01:00
mod . getTypeImports ( p . Type , recurse , externalImports , imports , seen )
2020-09-25 21:35:27 +02:00
}
return true
case * schema . ResourceType :
2021-01-30 01:52:00 +01:00
// If it's from another package, add an import for the external package.
if t . Resource != nil && t . Resource . Package != mod . pkg {
pkg := t . Resource . Package . Name
externalImports . Add ( fmt . Sprintf ( "import * as %[1]s from \"@pulumi/%[1]s\";" , pkg ) )
return false
}
2020-09-25 21:35:27 +02:00
return resourceOrTokenImport ( t . Token )
case * schema . TokenType :
return resourceOrTokenImport ( t . Token )
2020-01-21 23:45:48 +01:00
case * schema . UnionType :
needsTypes := false
for _ , e := range t . ElementTypes {
2021-01-30 01:52:00 +01:00
needsTypes = mod . getTypeImports ( e , recurse , externalImports , imports , seen ) || needsTypes
2020-01-21 23:45:48 +01:00
}
return needsTypes
default :
return false
}
}
2021-01-30 01:52:00 +01:00
func ( mod * modContext ) getImports ( member interface { } , externalImports codegen . StringSet , imports map [ string ] codegen . StringSet ) bool {
2020-05-29 00:37:39 +02:00
seen := codegen . Set { }
2020-01-21 23:45:48 +01:00
switch member := member . ( type ) {
case * schema . ObjectType :
needsTypes := false
for _ , p := range member . Properties {
2021-01-30 01:52:00 +01:00
needsTypes = mod . getTypeImports ( p . Type , true , externalImports , imports , seen ) || needsTypes
2020-01-21 23:45:48 +01:00
}
return needsTypes
2020-09-25 21:35:27 +02:00
case * schema . ResourceType :
2021-01-30 01:52:00 +01:00
mod . getTypeImports ( member , true , externalImports , imports , seen )
2020-09-25 21:35:27 +02:00
return false
2020-01-21 23:45:48 +01:00
case * schema . Resource :
needsTypes := false
for _ , p := range member . Properties {
2021-01-30 01:52:00 +01:00
needsTypes = mod . getTypeImports ( p . Type , false , externalImports , imports , seen ) || needsTypes
2020-01-21 23:45:48 +01:00
}
for _ , p := range member . InputProperties {
2021-01-30 01:52:00 +01:00
needsTypes = mod . getTypeImports ( p . Type , false , externalImports , imports , seen ) || needsTypes
2020-01-21 23:45:48 +01:00
}
return needsTypes
case * schema . Function :
needsTypes := false
if member . Inputs != nil {
2021-01-30 01:52:00 +01:00
needsTypes = mod . getTypeImports ( member . Inputs , false , externalImports , imports , seen ) || needsTypes
2020-01-21 23:45:48 +01:00
}
if member . Outputs != nil {
2021-01-30 01:52:00 +01:00
needsTypes = mod . getTypeImports ( member . Outputs , false , externalImports , imports , seen ) || needsTypes
2020-01-21 23:45:48 +01:00
}
return needsTypes
case [ ] * schema . Property :
needsTypes := false
for _ , p := range member {
2021-01-30 01:52:00 +01:00
needsTypes = mod . getTypeImports ( p . Type , false , externalImports , imports , seen ) || needsTypes
2020-01-21 23:45:48 +01:00
}
return needsTypes
default :
return false
}
}
2021-01-30 01:52:00 +01:00
func ( mod * modContext ) genHeader ( w io . Writer , imports [ ] string , externalImports codegen . StringSet , importedTypes map [ string ] codegen . StringSet ) {
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , "// *** WARNING: this file was generated by %v. ***\n" , mod . tool )
fmt . Fprintf ( w , "// *** Do not edit by hand unless you're certain you know what you are doing! ***\n\n" )
if len ( imports ) > 0 {
for _ , i := range imports {
fmt . Fprintf ( w , "%s\n" , i )
}
fmt . Fprintf ( w , "\n" )
}
2021-01-30 01:52:00 +01:00
if externalImports . Any ( ) {
for _ , i := range externalImports . SortedValues ( ) {
fmt . Fprintf ( w , "%s\n" , i )
}
fmt . Fprintf ( w , "\n" )
}
2020-01-21 23:45:48 +01:00
if len ( importedTypes ) > 0 {
var modules [ ] string
for module := range importedTypes {
modules = append ( modules , module )
}
sort . Strings ( modules )
for _ , module := range modules {
fmt . Fprintf ( w , "import {" )
2021-01-30 01:52:00 +01:00
for i , name := range importedTypes [ module ] . SortedValues ( ) {
2020-01-21 23:45:48 +01:00
if i > 0 {
fmt . Fprint ( w , ", " )
}
fmt . Fprint ( w , name )
}
2021-01-30 01:52:00 +01:00
fmt . Fprintf ( w , "} from \"%s\";\n" , module )
2020-01-21 23:45:48 +01:00
}
fmt . Fprintf ( w , "\n" )
}
}
2020-06-04 23:51:21 +02:00
// configGetter returns the name of the config.get* method used for a configuration variable and the cast necessary
// for the result of the call, if any.
func ( mod * modContext ) configGetter ( v * schema . Property ) ( string , string ) {
if v . Type == schema . StringType {
return "get" , ""
}
if tok , ok := v . Type . ( * schema . TokenType ) ; ok && tok . UnderlyingType == schema . StringType {
2021-04-19 23:05:23 +02:00
return "get" , fmt . Sprintf ( "<%s>" , mod . typeString ( v . Type , false , false , false , false , nil ) )
2020-06-04 23:51:21 +02:00
}
// Only try to parse a JSON object if the config isn't a straight string.
2021-04-19 23:05:23 +02:00
return fmt . Sprintf ( "getObject<%s>" , mod . typeString ( v . Type , false , false , false , false , nil ) ) , ""
2020-06-04 23:51:21 +02:00
}
2020-01-21 23:45:48 +01:00
func ( mod * modContext ) genConfig ( w io . Writer , variables [ ] * schema . Property ) error {
2021-01-30 01:52:00 +01:00
externalImports , imports := codegen . NewStringSet ( ) , map [ string ] codegen . StringSet { }
referencesNestedTypes := mod . getImports ( variables , externalImports , imports )
2020-01-21 23:45:48 +01:00
2021-01-30 01:52:00 +01:00
mod . genHeader ( w , mod . sdkImports ( referencesNestedTypes , true ) , externalImports , imports )
2020-01-21 23:45:48 +01:00
// Create a config bag for the variables to pull from.
fmt . Fprintf ( w , "let __config = new pulumi.Config(\"%v\");\n" , mod . pkg . Name )
fmt . Fprintf ( w , "\n" )
// Emit an entry for all config variables.
for _ , p := range variables {
2020-06-04 23:51:21 +02:00
getfunc , cast := mod . configGetter ( p )
2020-01-21 23:45:48 +01:00
2020-02-06 18:29:06 +01:00
printComment ( w , p . Comment , "" , "" )
2020-01-21 23:45:48 +01:00
2020-06-04 23:51:21 +02:00
configFetch := fmt . Sprintf ( "%s__config.%s(\"%s\")" , cast , getfunc , p . Name )
2020-06-05 19:11:18 +02:00
// TODO: handle ConstValues https://github.com/pulumi/pulumi/issues/4755
2020-01-21 23:45:48 +01:00
if p . DefaultValue != nil {
v , err := mod . getDefaultValue ( p . DefaultValue , p . Type )
if err != nil {
return err
}
2020-06-22 17:38:20 +02:00
// Note: this logic isn't quite correct, but already exists in all of the TF-based providers.
// Specifically, this doesn't work right if the first value is set to false but the default value
// is true.
2020-01-21 23:45:48 +01:00
configFetch += " || " + v
}
fmt . Fprintf ( w , "export let %s: %s = %s;\n" ,
2021-04-19 23:05:23 +02:00
p . Name , mod . typeString ( p . Type , false , false , false , true , nil ) , configFetch )
2020-01-21 23:45:48 +01:00
}
return nil
}
2020-10-08 03:13:10 +02:00
func ( mod * modContext ) getRelativePath ( ) string {
rel , err := filepath . Rel ( mod . mod , "" )
contract . Assert ( err == nil )
return path . Dir ( filepath . ToSlash ( rel ) )
}
2020-01-21 23:45:48 +01:00
func ( mod * modContext ) sdkImports ( nested , utilities bool ) [ ] string {
imports := [ ] string { "import * as pulumi from \"@pulumi/pulumi\";" }
2020-10-08 03:13:10 +02:00
relRoot := mod . getRelativePath ( )
2020-01-21 23:45:48 +01:00
if nested {
2020-11-06 18:29:49 +01:00
enumsImport := ""
2020-10-08 03:13:10 +02:00
containsEnums := mod . pkg . Language [ "nodejs" ] . ( NodePackageInfo ) . ContainsEnums
if containsEnums {
2020-11-06 18:29:49 +01:00
enumsImport = ", enums"
2020-10-08 03:13:10 +02:00
}
2020-11-06 18:29:49 +01:00
imports = append ( imports , fmt . Sprintf ( ` import { input as inputs, output as outputs%s } from "%s/types"; ` , enumsImport , relRoot ) )
2020-01-21 23:45:48 +01:00
}
2020-10-08 03:13:10 +02:00
2020-01-21 23:45:48 +01:00
if utilities {
imports = append ( imports , fmt . Sprintf ( "import * as utilities from \"%s/utilities\";" , relRoot ) )
}
return imports
}
func ( mod * modContext ) genTypes ( ) ( string , string ) {
2021-01-30 01:52:00 +01:00
externalImports , imports := codegen . NewStringSet ( ) , map [ string ] codegen . StringSet { }
2020-01-21 23:45:48 +01:00
for _ , t := range mod . types {
2021-01-30 01:52:00 +01:00
mod . getImports ( t , externalImports , imports )
2020-01-21 23:45:48 +01:00
}
inputs , outputs := & bytes . Buffer { } , & bytes . Buffer { }
2021-01-30 01:52:00 +01:00
mod . genHeader ( inputs , mod . sdkImports ( true , false ) , externalImports , imports )
mod . genHeader ( outputs , mod . sdkImports ( true , false ) , externalImports , imports )
2020-01-21 23:45:48 +01:00
// Build a namespace tree out of the types, then emit them.
2020-08-01 03:29:00 +02:00
namespaces := mod . getNamespaces ( )
mod . genNamespace ( inputs , namespaces [ "" ] , true , 0 )
mod . genNamespace ( outputs , namespaces [ "" ] , false , 0 )
2020-01-21 23:45:48 +01:00
2020-08-01 03:29:00 +02:00
return inputs . String ( ) , outputs . String ( )
}
type namespace struct {
name string
types [ ] * schema . ObjectType
2020-10-08 03:13:10 +02:00
enums [ ] * schema . EnumType
2020-08-01 03:29:00 +02:00
children [ ] * namespace
}
2020-01-21 23:45:48 +01:00
2020-08-01 03:29:00 +02:00
func ( mod * modContext ) getNamespaces ( ) map [ string ] * namespace {
2020-01-21 23:45:48 +01:00
namespaces := map [ string ] * namespace { }
var getNamespace func ( string ) * namespace
getNamespace = func ( mod string ) * namespace {
ns , ok := namespaces [ mod ]
if ! ok {
name := mod
if mod != "" {
name = path . Base ( mod )
}
ns = & namespace { name : name }
if mod != "" {
parentMod := path . Dir ( mod )
if parentMod == "." {
parentMod = ""
}
parent := getNamespace ( parentMod )
parent . children = append ( parent . children , ns )
}
namespaces [ mod ] = ns
}
return ns
}
for _ , t := range mod . types {
2020-06-05 19:11:18 +02:00
modName := mod . pkg . TokenToModule ( t . Token )
if override , ok := mod . modToPkg [ modName ] ; ok {
modName = override
}
ns := getNamespace ( modName )
2020-01-21 23:45:48 +01:00
ns . types = append ( ns . types , t )
}
2020-08-01 03:29:00 +02:00
return namespaces
}
2020-01-21 23:45:48 +01:00
2020-08-01 03:29:00 +02:00
func ( mod * modContext ) genNamespace ( w io . Writer , ns * namespace , input bool , level int ) {
indent := strings . Repeat ( " " , level )
2020-01-21 23:45:48 +01:00
2020-08-01 03:29:00 +02:00
sort . Slice ( ns . types , func ( i , j int ) bool {
return tokenToName ( ns . types [ i ] . Token ) < tokenToName ( ns . types [ j ] . Token )
} )
2020-10-08 03:13:10 +02:00
sort . Slice ( ns . enums , func ( i , j int ) bool {
return tokenToName ( ns . enums [ i ] . Token ) < tokenToName ( ns . enums [ j ] . Token )
} )
2020-08-01 03:29:00 +02:00
for i , t := range ns . types {
if input && mod . details ( t ) . inputType || ! input && mod . details ( t ) . outputType {
mod . genType ( w , t , input , level )
if i != len ( ns . types ) - 1 {
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , "\n" )
}
}
}
2020-08-01 03:29:00 +02:00
sort . Slice ( ns . children , func ( i , j int ) bool {
return ns . children [ i ] . name < ns . children [ j ] . name
} )
2020-08-27 23:50:34 +02:00
for i , child := range ns . children {
fmt . Fprintf ( w , "%sexport namespace %s {\n" , indent , child . name )
mod . genNamespace ( w , child , input , level + 1 )
2020-08-01 03:29:00 +02:00
fmt . Fprintf ( w , "%s}\n" , indent )
if i != len ( ns . children ) - 1 {
fmt . Fprintf ( w , "\n" )
}
}
2020-01-21 23:45:48 +01:00
}
2020-11-09 20:33:22 +01:00
func ( mod * modContext ) genEnum ( w io . Writer , enum * schema . EnumType ) error {
2020-10-09 17:27:01 +02:00
indent := " "
2020-10-08 03:13:10 +02:00
enumName := tokenToName ( enum . Token )
2020-10-09 17:27:01 +02:00
fmt . Fprintf ( w , "export const %s = {\n" , enumName )
2020-10-08 03:13:10 +02:00
for _ , e := range enum . Elements {
2020-11-09 20:33:22 +01:00
// If the enum doesn't have a name, set the value as the name.
2020-10-08 03:13:10 +02:00
if e . Name == "" {
e . Name = fmt . Sprintf ( "%v" , e . Value )
}
2020-12-16 18:22:44 +01:00
safeName , err := makeSafeEnumName ( e . Name , enumName )
2020-11-09 20:33:22 +01:00
if err != nil {
return err
}
e . Name = safeName
2020-10-09 17:27:01 +02:00
printComment ( w , e . Comment , e . DeprecationMessage , indent )
fmt . Fprintf ( w , "%s%s: " , indent , e . Name )
2020-10-08 03:13:10 +02:00
if val , ok := e . Value . ( string ) ; ok {
2020-10-09 17:27:01 +02:00
fmt . Fprintf ( w , "%q,\n" , val )
2020-10-08 03:13:10 +02:00
} else {
2020-10-09 17:27:01 +02:00
fmt . Fprintf ( w , "%v,\n" , e . Value )
2020-10-08 03:13:10 +02:00
}
}
2020-10-09 17:27:01 +02:00
fmt . Fprintf ( w , "} as const;\n" )
2020-10-08 03:13:10 +02:00
fmt . Fprintf ( w , "\n" )
printComment ( w , enum . Comment , "" , "" )
2020-10-09 17:27:01 +02:00
fmt . Fprintf ( w , "export type %[1]s = (typeof %[1]s)[keyof typeof %[1]s];\n" , enumName )
2020-11-09 20:33:22 +01:00
return nil
2020-10-08 03:13:10 +02:00
}
2020-01-21 23:45:48 +01:00
type fs map [ string ] [ ] byte
func ( fs fs ) add ( path string , contents [ ] byte ) {
_ , has := fs [ path ]
contract . Assertf ( ! has , "duplicate file: %s" , path )
fs [ path ] = contents
}
2020-06-02 01:24:21 +02:00
func ( mod * modContext ) isReservedSourceFileName ( name string ) bool {
switch name {
case "index.ts" :
return true
case "input.ts" , "output.ts" :
return len ( mod . types ) != 0
case "utilities.ts" :
return mod . mod == ""
case "vars.ts" :
return len ( mod . pkg . Config ) > 0
default :
return false
}
}
2020-01-21 23:45:48 +01:00
func ( mod * modContext ) gen ( fs fs ) error {
2020-06-07 18:21:09 +02:00
files := append ( [ ] string ( nil ) , mod . extraSourceFiles ... )
2020-01-21 23:45:48 +01:00
2020-07-14 19:58:29 +02:00
modDir := strings . ToLower ( mod . mod )
2020-01-21 23:45:48 +01:00
addFile := func ( name , contents string ) {
2020-07-14 19:58:29 +02:00
p := path . Join ( modDir , name )
2020-01-21 23:45:48 +01:00
files = append ( files , p )
fs . add ( p , [ ] byte ( contents ) )
}
2020-06-05 19:11:18 +02:00
// Utilities, config, readme
switch mod . mod {
case "" :
buffer := & bytes . Buffer { }
2021-01-30 01:52:00 +01:00
mod . genHeader ( buffer , nil , nil , nil )
2020-06-05 19:11:18 +02:00
fmt . Fprintf ( buffer , "%s" , utilitiesFile )
2020-07-14 19:58:29 +02:00
fs . add ( path . Join ( modDir , "utilities.ts" ) , buffer . Bytes ( ) )
2020-06-05 19:11:18 +02:00
// Ensure that the top-level (provider) module directory contains a README.md file.
2020-05-29 21:36:16 +02:00
readme := mod . pkg . Language [ "nodejs" ] . ( NodePackageInfo ) . Readme
if readme == "" {
readme = mod . pkg . Description
if readme != "" && readme [ len ( readme ) - 1 ] != '\n' {
2020-05-13 19:55:37 +02:00
readme += "\n"
}
2020-05-29 21:36:16 +02:00
if mod . pkg . Attribution != "" {
if len ( readme ) != 0 {
readme += "\n"
}
readme += mod . pkg . Attribution
}
2020-05-13 19:55:37 +02:00
}
2020-05-29 21:36:16 +02:00
if readme != "" && readme [ len ( readme ) - 1 ] != '\n' {
readme += "\n"
}
2020-07-14 19:58:29 +02:00
fs . add ( path . Join ( modDir , "README.md" ) , [ ] byte ( readme ) )
2020-01-21 23:45:48 +01:00
case "config" :
if len ( mod . pkg . Config ) > 0 {
buffer := & bytes . Buffer { }
if err := mod . genConfig ( buffer , mod . pkg . Config ) ; err != nil {
return err
}
addFile ( "vars.ts" , buffer . String ( ) )
}
}
// Resources
for _ , r := range mod . resources {
2021-01-30 01:52:00 +01:00
externalImports , imports := codegen . NewStringSet ( ) , map [ string ] codegen . StringSet { }
referencesNestedTypes := mod . getImports ( r , externalImports , imports )
2020-01-21 23:45:48 +01:00
buffer := & bytes . Buffer { }
2021-01-30 01:52:00 +01:00
mod . genHeader ( buffer , mod . sdkImports ( referencesNestedTypes , true ) , externalImports , imports )
2020-01-21 23:45:48 +01:00
if err := mod . genResource ( buffer , r ) ; err != nil {
return err
}
2020-12-02 22:45:25 +01:00
fileName := mod . resourceFileName ( r )
2020-06-02 01:24:21 +02:00
addFile ( fileName , buffer . String ( ) )
2020-01-21 23:45:48 +01:00
}
// Functions
for _ , f := range mod . functions {
2021-01-30 01:52:00 +01:00
externalImports , imports := codegen . NewStringSet ( ) , map [ string ] codegen . StringSet { }
referencesNestedTypes := mod . getImports ( f , externalImports , imports )
2020-01-21 23:45:48 +01:00
buffer := & bytes . Buffer { }
2021-01-30 01:52:00 +01:00
mod . genHeader ( buffer , mod . sdkImports ( referencesNestedTypes , true ) , externalImports , imports )
2020-01-21 23:45:48 +01:00
mod . genFunction ( buffer , f )
2020-06-02 01:24:21 +02:00
fileName := camel ( tokenToName ( f . Token ) ) + ".ts"
if mod . isReservedSourceFileName ( fileName ) {
fileName = camel ( tokenToName ( f . Token ) ) + "_.ts"
}
addFile ( fileName , buffer . String ( ) )
2020-01-21 23:45:48 +01:00
}
2020-10-08 03:13:10 +02:00
if mod . hasEnums ( ) {
buffer := & bytes . Buffer { }
2021-01-30 01:52:00 +01:00
mod . genHeader ( buffer , [ ] string { } , nil , nil )
2020-10-08 03:13:10 +02:00
2020-11-09 20:33:22 +01:00
err := mod . genEnums ( buffer , mod . enums )
if err != nil {
return err
}
2020-10-08 03:13:10 +02:00
var fileName string
if modDir == "" {
fileName = "index.ts"
} else {
fileName = path . Join ( modDir , "index.ts" )
}
fileName = path . Join ( "types" , "enums" , fileName )
fs . add ( fileName , buffer . Bytes ( ) )
}
2020-01-21 23:45:48 +01:00
// Nested types
if len ( mod . types ) > 0 {
input , output := mod . genTypes ( )
2020-07-14 19:58:29 +02:00
fs . add ( path . Join ( modDir , "input.ts" ) , [ ] byte ( input ) )
fs . add ( path . Join ( modDir , "output.ts" ) , [ ] byte ( output ) )
2020-01-21 23:45:48 +01:00
}
// Index
2020-07-14 19:58:29 +02:00
fs . add ( path . Join ( modDir , "index.ts" ) , [ ] byte ( mod . genIndex ( files ) ) )
2020-01-21 23:45:48 +01:00
return nil
}
2020-10-08 03:13:10 +02:00
func getChildMod ( modName string ) string {
child := strings . ToLower ( modName )
// Extract version suffix from child modules. Nested versions will have their own index.ts file.
// Example: apps/v1beta1 -> v1beta1
parts := strings . SplitN ( child , "/" , 2 )
if len ( parts ) == 2 {
child = parts [ 1 ]
}
return child
}
2020-01-21 23:45:48 +01:00
// genIndex emits an index module, optionally re-exporting other members or submodules.
func ( mod * modContext ) genIndex ( exports [ ] string ) string {
w := & bytes . Buffer { }
2020-12-02 22:45:25 +01:00
var imports [ ] string
// Include the SDK import if we'll be registering module resources.
if len ( mod . resources ) != 0 {
imports = mod . sdkImports ( false /*nested*/ , true /*utilities*/ )
}
2021-01-30 01:52:00 +01:00
mod . genHeader ( w , imports , nil , nil )
2020-01-21 23:45:48 +01:00
// Export anything flatly that is a direct export rather than sub-module.
if len ( exports ) > 0 {
2020-07-14 19:58:29 +02:00
modDir := strings . ToLower ( mod . mod )
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , "// Export members:\n" )
sort . Strings ( exports )
for _ , exp := range exports {
2020-07-14 19:58:29 +02:00
rel , err := filepath . Rel ( modDir , exp )
2020-01-21 23:45:48 +01:00
contract . Assert ( err == nil )
if path . Base ( rel ) == "." {
rel = path . Dir ( rel )
}
fmt . Fprintf ( w , "export * from \"./%s\";\n" , strings . TrimSuffix ( rel , ".ts" ) )
}
}
2020-06-05 19:11:18 +02:00
children := codegen . NewStringSet ( )
2020-01-21 23:45:48 +01:00
for _ , mod := range mod . children {
2020-10-08 03:13:10 +02:00
child := getChildMod ( mod . mod )
2020-06-05 19:11:18 +02:00
children . Add ( child )
2020-01-21 23:45:48 +01:00
}
2020-06-05 19:11:18 +02:00
2020-01-21 23:45:48 +01:00
if len ( mod . types ) > 0 {
2020-06-05 19:11:18 +02:00
children . Add ( "input" )
children . Add ( "output" )
2020-01-21 23:45:48 +01:00
}
2020-10-08 03:13:10 +02:00
info , _ := mod . pkg . Language [ "nodejs" ] . ( NodePackageInfo )
if info . ContainsEnums {
if mod . mod == "types" {
children . Add ( "enums" )
} else if len ( mod . enums ) > 0 {
fmt . Fprintf ( w , "\n" )
fmt . Fprintf ( w , "// Export enums:\n" )
rel := mod . getRelativePath ( )
var filePath string
if mod . mod == "" {
filePath = ""
} else {
filePath = fmt . Sprintf ( "/%s" , mod . mod )
}
fmt . Fprintf ( w , "export * from \"%s/types/enums%s\";\n" , rel , filePath )
}
}
2020-12-02 22:45:25 +01:00
// If there are submodules, export them.
2020-01-21 23:45:48 +01:00
if len ( children ) > 0 {
if len ( exports ) > 0 {
fmt . Fprintf ( w , "\n" )
}
fmt . Fprintf ( w , "// Export sub-modules:\n" )
2020-06-05 19:11:18 +02:00
sorted := children . SortedValues ( )
for _ , mod := range sorted {
2020-01-21 23:45:48 +01:00
fmt . Fprintf ( w , "import * as %[1]s from \"./%[1]s\";\n" , mod )
}
2020-11-06 18:29:49 +01:00
printExports ( w , sorted )
2020-01-21 23:45:48 +01:00
}
2020-12-02 22:45:25 +01:00
// If there are resources in this module, register the module with the runtime.
if len ( mod . resources ) != 0 {
mod . genResourceModule ( w )
}
2020-01-21 23:45:48 +01:00
return w . String ( )
}
2020-12-02 22:45:25 +01:00
// genResourceModule generates a ResourceModule definition and the code to register an instance thereof with the
// Pulumi runtime. The generated ResourceModule supports the deserialization of resource references into fully-
// hydrated Resource instances. If this is the root module, this function also generates a ResourcePackage
// definition and its registration to support rehydrating providers.
func ( mod * modContext ) genResourceModule ( w io . Writer ) {
contract . Assert ( len ( mod . resources ) != 0 )
// Check for provider-only modules.
var provider * schema . Resource
if providerOnly := len ( mod . resources ) == 1 && mod . resources [ 0 ] . IsProvider ; providerOnly {
provider = mod . resources [ 0 ]
} else {
registrations , first := codegen . StringSet { } , true
for _ , r := range mod . resources {
if r . IsProvider {
contract . Assert ( provider == nil )
provider = r
continue
}
registrations . Add ( mod . pkg . TokenToRuntimeModule ( r . Token ) )
if first {
first = false
fmt . Fprintf ( w , "\n// Import resources to register:\n" )
}
fileName := strings . TrimSuffix ( mod . resourceFileName ( r ) , ".ts" )
fmt . Fprintf ( w , "import { %s } from \"./%s\";\n" , resourceName ( r ) , fileName )
}
fmt . Fprintf ( w , "\nconst _module = {\n" )
fmt . Fprintf ( w , " version: utilities.getVersion(),\n" )
fmt . Fprintf ( w , " construct: (name: string, type: string, urn: string): pulumi.Resource => {\n" )
fmt . Fprintf ( w , " switch (type) {\n" )
for _ , r := range mod . resources {
if r . IsProvider {
continue
}
fmt . Fprintf ( w , " case \"%v\":\n" , r . Token )
fmt . Fprintf ( w , " return new %v(name, <any>undefined, { urn })\n" , resourceName ( r ) )
}
fmt . Fprintf ( w , " default:\n" )
fmt . Fprintf ( w , " throw new Error(`unknown resource type ${type}`);\n" )
fmt . Fprintf ( w , " }\n" )
fmt . Fprintf ( w , " },\n" )
fmt . Fprintf ( w , "};\n" )
for _ , name := range registrations . SortedValues ( ) {
fmt . Fprintf ( w , "pulumi.runtime.registerResourceModule(\"%v\", \"%v\", _module)\n" , mod . pkg . Name , name )
}
}
if provider != nil {
fmt . Fprintf ( w , "\nimport { Provider } from \"./provider\";\n\n" )
fmt . Fprintf ( w , "pulumi.runtime.registerResourcePackage(\"%v\", {\n" , mod . pkg . Name )
fmt . Fprintf ( w , " version: utilities.getVersion(),\n" )
fmt . Fprintf ( w , " constructProvider: (name: string, type: string, urn: string): pulumi.ProviderResource => {\n" )
fmt . Fprintf ( w , " if (type !== \"%v\") {\n" , provider . Token )
fmt . Fprintf ( w , " throw new Error(`unknown provider type ${type}`);\n" )
fmt . Fprintf ( w , " }\n" )
fmt . Fprintf ( w , " return new Provider(name, <any>undefined, { urn });\n" )
fmt . Fprintf ( w , " },\n" )
fmt . Fprintf ( w , "});\n" )
}
}
2020-11-06 18:29:49 +01:00
func printExports ( w io . Writer , exports [ ] string ) {
fmt . Fprintf ( w , "\n" )
fmt . Fprintf ( w , "export {\n" )
for _ , mod := range exports {
fmt . Fprintf ( w , " %s,\n" , mod )
}
fmt . Fprintf ( w , "};\n" )
}
2020-10-08 03:13:10 +02:00
func ( mod * modContext ) hasEnums ( ) bool {
if mod . mod == "types" {
return false
}
if len ( mod . enums ) > 0 {
return true
}
if len ( mod . children ) > 0 {
for _ , mod := range mod . children {
2020-11-06 18:29:49 +01:00
if mod . hasEnums ( ) {
return true
}
2020-10-08 03:13:10 +02:00
}
}
return false
}
2020-11-09 20:33:22 +01:00
func ( mod * modContext ) genEnums ( buffer * bytes . Buffer , enums [ ] * schema . EnumType ) error {
2020-10-08 03:13:10 +02:00
if len ( mod . children ) > 0 {
children := codegen . NewStringSet ( )
for _ , mod := range mod . children {
child := getChildMod ( mod . mod )
if mod . hasEnums ( ) {
children . Add ( child )
}
}
if len ( children ) > 0 {
fmt . Fprintf ( buffer , "// Export sub-modules:\n" )
sorted := children . SortedValues ( )
for _ , mod := range sorted {
fmt . Fprintf ( buffer , "import * as %[1]s from \"./%[1]s\";\n" , mod )
}
2020-11-06 18:29:49 +01:00
printExports ( buffer , sorted )
2020-10-08 03:13:10 +02:00
}
}
if len ( enums ) > 0 {
fmt . Fprintf ( buffer , "\n" )
for i , enum := range enums {
2020-11-09 20:33:22 +01:00
err := mod . genEnum ( buffer , enum )
if err != nil {
return err
}
2020-10-08 03:13:10 +02:00
if i != len ( enums ) - 1 {
fmt . Fprintf ( buffer , "\n" )
}
}
}
2020-11-09 20:33:22 +01:00
return nil
2020-10-08 03:13:10 +02:00
}
2020-01-21 23:45:48 +01:00
// genPackageMetadata generates all the non-code metadata required by a Pulumi package.
2020-04-11 03:28:14 +02:00
func genPackageMetadata ( pkg * schema . Package , info NodePackageInfo , files fs ) {
2020-01-21 23:45:48 +01:00
// The generator already emitted Pulumi.yaml, so that leaves two more files to write out:
// 1) package.json: minimal NPM package metadata
// 2) tsconfig.json: instructions for TypeScript compilation
files . add ( "package.json" , [ ] byte ( genNPMPackageMetadata ( pkg , info ) ) )
files . add ( "tsconfig.json" , [ ] byte ( genTypeScriptProjectFile ( info , files ) ) )
}
type npmPackage struct {
Name string ` json:"name" `
Version string ` json:"version" `
Description string ` json:"description,omitempty" `
Keywords [ ] string ` json:"keywords,omitempty" `
Homepage string ` json:"homepage,omitempty" `
Repository string ` json:"repository,omitempty" `
License string ` json:"license,omitempty" `
Scripts map [ string ] string ` json:"scripts,omitempty" `
Dependencies map [ string ] string ` json:"dependencies,omitempty" `
DevDependencies map [ string ] string ` json:"devDependencies,omitempty" `
PeerDependencies map [ string ] string ` json:"peerDependencies,omitempty" `
2020-05-02 23:17:31 +02:00
Resolutions map [ string ] string ` json:"resolutions,omitempty" `
2020-01-21 23:45:48 +01:00
Pulumi npmPulumiManifest ` json:"pulumi,omitempty" `
}
type npmPulumiManifest struct {
2020-07-02 18:25:49 +02:00
Resource bool ` json:"resource,omitempty" `
PluginDownloadURL string ` json:"pluginDownloadURL,omitempty" `
2020-01-21 23:45:48 +01:00
}
2020-04-11 03:28:14 +02:00
func genNPMPackageMetadata ( pkg * schema . Package , info NodePackageInfo ) string {
2020-01-21 23:45:48 +01:00
packageName := info . PackageName
if packageName == "" {
packageName = fmt . Sprintf ( "@pulumi/%s" , pkg . Name )
}
devDependencies := map [ string ] string { }
if info . TypeScriptVersion != "" {
devDependencies [ "typescript" ] = info . TypeScriptVersion
}
// Create info that will get serialized into an NPM package.json.
npminfo := npmPackage {
Name : packageName ,
Version : "${VERSION}" ,
Description : info . PackageDescription ,
Keywords : pkg . Keywords ,
Homepage : pkg . Homepage ,
Repository : pkg . Repository ,
License : pkg . License ,
// Ideally, this `scripts` section would include an install script that installs the provider, however, doing
// so causes problems when we try to restore package dependencies, since we must do an install for that. So
// we have another process that adds the install script when generating the package.json that we actually
// publish.
Scripts : map [ string ] string {
"build" : "tsc" ,
} ,
DevDependencies : devDependencies ,
Pulumi : npmPulumiManifest {
2020-07-02 18:25:49 +02:00
Resource : true ,
PluginDownloadURL : pkg . PluginDownloadURL ,
2020-01-21 23:45:48 +01:00
} ,
}
// Copy the overlay dependencies, if any.
for depk , depv := range info . Dependencies {
if npminfo . Dependencies == nil {
npminfo . Dependencies = make ( map [ string ] string )
}
npminfo . Dependencies [ depk ] = depv
}
for depk , depv := range info . DevDependencies {
if npminfo . DevDependencies == nil {
npminfo . DevDependencies = make ( map [ string ] string )
}
npminfo . DevDependencies [ depk ] = depv
}
for depk , depv := range info . PeerDependencies {
if npminfo . PeerDependencies == nil {
npminfo . PeerDependencies = make ( map [ string ] string )
}
npminfo . PeerDependencies [ depk ] = depv
}
2020-05-02 23:17:31 +02:00
for resk , resv := range info . Resolutions {
if npminfo . Resolutions == nil {
npminfo . Resolutions = make ( map [ string ] string )
}
npminfo . Resolutions [ resk ] = resv
}
2020-01-21 23:45:48 +01:00
// If there is no @pulumi/pulumi, add "latest" as a peer dependency (for npm linking style usage).
sdkPack := "@pulumi/pulumi"
if npminfo . Dependencies [ sdkPack ] == "" &&
npminfo . DevDependencies [ sdkPack ] == "" &&
npminfo . PeerDependencies [ sdkPack ] == "" {
if npminfo . PeerDependencies == nil {
npminfo . PeerDependencies = make ( map [ string ] string )
}
npminfo . PeerDependencies [ "@pulumi/pulumi" ] = "latest"
}
// Now write out the serialized form.
npmjson , err := json . MarshalIndent ( npminfo , "" , " " )
contract . Assert ( err == nil )
return string ( npmjson )
}
2020-04-11 03:28:14 +02:00
func genTypeScriptProjectFile ( info NodePackageInfo , files fs ) string {
2020-01-21 23:45:48 +01:00
w := & bytes . Buffer { }
fmt . Fprintf ( w , ` {
"compilerOptions" : {
"outDir" : "bin" ,
"target" : "es2016" ,
"module" : "commonjs" ,
"moduleResolution" : "node" ,
"declaration" : true ,
"sourceMap" : true ,
"stripInternal" : true ,
"experimentalDecorators" : true ,
"noFallthroughCasesInSwitch" : true ,
"forceConsistentCasingInFileNames" : true ,
"strict" : true
} ,
"files" : [
` )
var tsFiles [ ] string
for f := range files {
if path . Ext ( f ) == ".ts" {
tsFiles = append ( tsFiles , f )
}
}
sort . Strings ( tsFiles )
for i , file := range tsFiles {
var suffix string
if i != len ( tsFiles ) - 1 {
suffix = ","
}
fmt . Fprintf ( w , " \"%s\"%s\n" , file , suffix )
}
fmt . Fprintf ( w , ` ]
}
` )
return w . String ( )
}
2020-06-04 03:20:32 +02:00
// generateModuleContextMap groups resources, types, and functions into NodeJS packages.
2020-06-07 18:21:09 +02:00
func generateModuleContextMap ( tool string , pkg * schema . Package , info NodePackageInfo ,
2020-10-08 03:13:10 +02:00
extraFiles map [ string ] [ ] byte ) ( map [ string ] * modContext , NodePackageInfo , error ) {
2020-06-07 18:21:09 +02:00
2020-06-04 03:20:32 +02:00
// group resources, types, and functions into NodeJS packages
2020-01-21 23:45:48 +01:00
modules := map [ string ] * modContext { }
2020-06-03 03:15:21 +02:00
var getMod func ( modName string ) * modContext
getMod = func ( modName string ) * modContext {
2020-06-04 03:20:32 +02:00
if override , ok := info . ModuleToPackage [ modName ] ; ok {
modName = override
}
2020-01-21 23:45:48 +01:00
mod , ok := modules [ modName ]
if ! ok {
mod = & modContext {
2020-06-03 03:15:21 +02:00
pkg : pkg ,
mod : modName ,
tool : tool ,
compatibility : info . Compatibility ,
2020-06-05 19:11:18 +02:00
modToPkg : info . ModuleToPackage ,
2020-06-03 03:15:21 +02:00
disableUnionOutputTypes : info . DisableUnionOutputTypes ,
2020-01-21 23:45:48 +01:00
}
if modName != "" {
parentName := path . Dir ( modName )
2020-06-03 03:15:21 +02:00
if parentName == "." {
parentName = ""
2020-01-21 23:45:48 +01:00
}
parent := getMod ( parentName )
parent . children = append ( parent . children , mod )
}
modules [ modName ] = mod
}
return mod
}
2020-06-03 03:15:21 +02:00
getModFromToken := func ( token string ) * modContext {
return getMod ( pkg . TokenToModule ( token ) )
}
// Create a temporary module for type information.
types := & modContext { }
2020-01-21 23:45:48 +01:00
// Create the config module if necessary.
2020-05-29 21:39:29 +02:00
if len ( pkg . Config ) > 0 &&
2020-06-22 17:38:20 +02:00
info . Compatibility != kubernetes20 { // k8s SDK doesn't use config.
2020-06-03 03:15:21 +02:00
_ = getMod ( "config" )
2020-01-21 23:45:48 +01:00
}
2021-04-20 01:40:39 +02:00
visitObjectTypes ( pkg . Config , func ( t * schema . ObjectType , plain bool ) {
types . details ( t ) . outputType = true
} )
2020-01-21 23:45:48 +01:00
scanResource := func ( r * schema . Resource ) {
2020-06-03 03:15:21 +02:00
mod := getModFromToken ( r . Token )
2020-01-21 23:45:48 +01:00
mod . resources = append ( mod . resources , r )
2021-04-20 01:40:39 +02:00
visitObjectTypes ( r . Properties , func ( t * schema . ObjectType , _ bool ) {
types . details ( t ) . outputType = true
} )
visitObjectTypes ( r . InputProperties , func ( t * schema . ObjectType , plain bool ) {
if r . IsProvider {
types . details ( t ) . outputType = true
}
types . details ( t ) . inputType = true
if plain {
types . details ( t ) . plainType = true
} else {
2021-04-16 04:03:28 +02:00
types . details ( t ) . argsType = true
2021-04-20 01:40:39 +02:00
}
} )
2020-01-21 23:45:48 +01:00
if r . StateInputs != nil {
2021-04-20 01:40:39 +02:00
visitObjectTypes ( r . StateInputs . Properties , func ( t * schema . ObjectType , _ bool ) {
2021-04-16 04:03:28 +02:00
types . details ( t ) . inputType = true
types . details ( t ) . argsType = true
2021-04-20 01:40:39 +02:00
} )
2020-01-21 23:45:48 +01:00
}
}
scanResource ( pkg . Provider )
for _ , r := range pkg . Resources {
scanResource ( r )
}
2021-04-16 04:03:28 +02:00
// Clear the input and outputs sets: we want the visitors below to touch the transitive closure of types reachable
// from function inputs and outputs, including types that have already been visited.
2020-01-21 23:45:48 +01:00
for _ , f := range pkg . Functions {
2020-06-03 03:15:21 +02:00
mod := getModFromToken ( f . Token )
2020-01-21 23:45:48 +01:00
mod . functions = append ( mod . functions , f )
if f . Inputs != nil {
2021-04-20 01:40:39 +02:00
visitObjectTypes ( f . Inputs . Properties , func ( t * schema . ObjectType , _ bool ) {
2020-01-21 23:45:48 +01:00
types . details ( t ) . inputType = true
2021-04-20 01:40:39 +02:00
types . details ( t ) . plainType = true
} )
2020-01-21 23:45:48 +01:00
}
if f . Outputs != nil {
2021-04-20 01:40:39 +02:00
visitObjectTypes ( f . Outputs . Properties , func ( t * schema . ObjectType , _ bool ) {
2020-01-21 23:45:48 +01:00
types . details ( t ) . outputType = true
2021-04-20 01:40:39 +02:00
types . details ( t ) . plainType = true
} )
2020-01-21 23:45:48 +01:00
}
}
if _ , ok := modules [ "types" ] ; ok {
2020-10-08 03:13:10 +02:00
return nil , info , errors . New ( "this provider has a `types` module which is reserved for input/output types" )
2020-01-21 23:45:48 +01:00
}
// Create the types module.
for _ , t := range pkg . Types {
2020-10-08 03:13:10 +02:00
switch typ := t . ( type ) {
case * schema . ObjectType :
types . types = append ( types . types , typ )
case * schema . EnumType :
info . ContainsEnums = true
mod := getModFromToken ( typ . Token )
mod . enums = append ( mod . enums , typ )
default :
continue
2020-01-21 23:45:48 +01:00
}
}
if len ( types . types ) > 0 {
2020-06-03 03:15:21 +02:00
typeDetails , typeList := types . typeDetails , types . types
types = getMod ( "types" )
types . typeDetails , types . types = typeDetails , typeList
2020-01-21 23:45:48 +01:00
}
2020-06-07 18:21:09 +02:00
// Add Typescript source files to the corresponding modules. Note that we only add the file names; the contents are
// still laid out manually in GeneratePackage.
for p := range extraFiles {
if path . Ext ( p ) != ".ts" {
continue
}
modName := path . Dir ( p )
if modName == "/" || modName == "." {
modName = ""
}
mod := getMod ( modName )
mod . extraSourceFiles = append ( mod . extraSourceFiles , p )
}
2020-10-08 03:13:10 +02:00
return modules , info , nil
2020-06-04 03:20:32 +02:00
}
// LanguageResource holds information about a resource to be used by downstream codegen.
type LanguageResource struct {
* schema . Resource
Name string // The resource name (e.g., "FlowSchema")
Package string // The name of the package containing the resource definition (e.g., "flowcontrol.v1alpha1")
Properties [ ] LanguageProperty // Properties of the resource
}
// LanguageProperty holds information about a resource property to be used by downstream codegen.
type LanguageProperty struct {
ConstValue string // If set, the constant value of the property (e.g., "flowcontrol.apiserver.k8s.io/v1alpha1")
Name string // The name of the property (e.g., "FlowSchemaSpec")
Package string // The package path containing the property definition (e.g., "outputs.flowcontrol.v1alpha1")
}
// LanguageResources returns a map of resources that can be used by downstream codegen. The map
// key is the resource schema token.
func LanguageResources ( pkg * schema . Package ) ( map [ string ] LanguageResource , error ) {
resources := map [ string ] LanguageResource { }
if err := pkg . ImportLanguages ( map [ string ] schema . Language { "nodejs" : Importer } ) ; err != nil {
return nil , err
}
info , _ := pkg . Language [ "nodejs" ] . ( NodePackageInfo )
2020-10-08 03:13:10 +02:00
modules , _ , err := generateModuleContextMap ( "" , pkg , info , nil )
2020-06-04 03:20:32 +02:00
if err != nil {
return nil , err
}
for modName , mod := range modules {
for _ , r := range mod . resources {
packagePath := strings . Replace ( modName , "/" , "." , - 1 )
lr := LanguageResource {
Resource : r ,
Name : resourceName ( r ) ,
Package : packagePath ,
}
for _ , p := range r . Properties {
lp := LanguageProperty {
Name : p . Name ,
}
if p . ConstValue != nil {
2021-04-19 23:05:23 +02:00
lp . ConstValue = mod . typeString ( p . Type , false , false , false , false , p . ConstValue )
2020-06-04 03:20:32 +02:00
} else {
2021-04-19 23:05:23 +02:00
lp . Package = mod . typeString ( p . Type , false , false , false , false , nil )
2020-06-04 03:20:32 +02:00
}
lr . Properties = append ( lr . Properties , lp )
}
resources [ r . Token ] = lr
}
}
return resources , nil
}
func GeneratePackage ( tool string , pkg * schema . Package , extraFiles map [ string ] [ ] byte ) ( map [ string ] [ ] byte , error ) {
// Decode node-specific info
if err := pkg . ImportLanguages ( map [ string ] schema . Language { "nodejs" : Importer } ) ; err != nil {
return nil , err
}
info , _ := pkg . Language [ "nodejs" ] . ( NodePackageInfo )
2020-10-08 03:13:10 +02:00
modules , info , err := generateModuleContextMap ( tool , pkg , info , extraFiles )
2020-06-04 03:20:32 +02:00
if err != nil {
return nil , err
}
2020-10-08 03:13:10 +02:00
pkg . Language [ "nodejs" ] = info
2020-06-04 03:20:32 +02:00
2020-01-21 23:45:48 +01:00
files := fs { }
for p , f := range extraFiles {
files . add ( p , f )
}
for _ , mod := range modules {
if err := mod . gen ( files ) ; err != nil {
return nil , err
}
}
// Finally emit the package metadata (NPM, TypeScript, and so on).
genPackageMetadata ( pkg , info , files )
return files , nil
}
const utilitiesFile = `
export function getEnv ( ... vars : string [ ] ) : string | undefined {
for ( const v of vars ) {
const value = process . env [ v ] ;
if ( value ) {
return value ;
}
}
return undefined ;
}
export function getEnvBoolean ( ... vars : string [ ] ) : boolean | undefined {
const s = getEnv ( ... vars ) ;
if ( s != = undefined ) {
// NOTE: these values are taken from https://golang.org/src/strconv/atob.go?s=351:391#L1, which is what
// Terraform uses internally when parsing boolean values.
if ( [ "1" , "t" , "T" , "true" , "TRUE" , "True" ] . find ( v = > v == = s ) != = undefined ) {
return true ;
}
if ( [ "0" , "f" , "F" , "false" , "FALSE" , "False" ] . find ( v = > v == = s ) != = undefined ) {
return false ;
}
}
return undefined ;
}
export function getEnvNumber ( ... vars : string [ ] ) : number | undefined {
const s = getEnv ( ... vars ) ;
if ( s != = undefined ) {
const f = parseFloat ( s ) ;
if ( ! isNaN ( f ) ) {
return f ;
}
}
return undefined ;
}
export function getVersion ( ) : string {
let version = require ( ' . / package . json ' ) . version ;
// Node allows for the version to be prefixed by a "v", while semver doesn't.
// If there is a v, strip it off.
if ( version . indexOf ( 'v' ) == = 0 ) {
version = version . slice ( 1 ) ;
}
return version ;
}
`