|
3 | 3 |
|
4 | 4 | package software.aws.toolkits.jetbrains.services.lambda.nodejs
|
5 | 5 |
|
| 6 | +import com.fasterxml.jackson.core.JsonProcessingException |
| 7 | +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper |
| 8 | +import com.fasterxml.jackson.module.kotlin.readValue |
6 | 9 | import com.intellij.lang.annotation.HighlightSeverity
|
7 | 10 | import com.intellij.lang.javascript.DialectDetector
|
8 |
| -import com.intellij.lang.typescript.compiler.TypeScriptCompilerService |
| 11 | +import com.intellij.lang.typescript.compiler.TypeScriptService |
9 | 12 | import com.intellij.lang.typescript.tsconfig.TypeScriptConfig
|
10 | 13 | import com.intellij.openapi.application.runReadAction
|
11 | 14 | import com.intellij.openapi.module.Module
|
12 | 15 | import com.intellij.openapi.project.Project
|
13 | 16 | import com.intellij.openapi.vfs.VfsUtil
|
14 | 17 | import com.intellij.psi.PsiElement
|
| 18 | +import com.intellij.util.castSafelyTo |
15 | 19 | import software.aws.toolkits.core.utils.exists
|
| 20 | +import software.aws.toolkits.core.utils.inputStream |
16 | 21 | import software.aws.toolkits.core.utils.writeText
|
17 | 22 | import software.aws.toolkits.jetbrains.services.lambda.Lambda
|
18 | 23 | import software.aws.toolkits.jetbrains.services.lambda.LambdaBuilder
|
@@ -52,35 +57,64 @@ class NodeJsLambdaBuilder : LambdaBuilder() {
|
52 | 57 | val tsOutput = sourceRoot.resolve(TS_BUILD_DIR).normalize().toAbsolutePath().toString()
|
53 | 58 | // relative to existing tsconfig because there is no other option https://github.com/microsoft/TypeScript/issues/25430
|
54 | 59 | val tsConfig = sourceRoot.resolve(TS_CONFIG_FILE)
|
| 60 | + |
| 61 | + // read base ts config into mutable map |
| 62 | + fun loadBaseConfig(tsConfig: Path): MutableMap<String, Any>? { |
| 63 | + if (!tsConfig.exists()) { |
| 64 | + return null |
| 65 | + } |
| 66 | + |
| 67 | + try { |
| 68 | + val tsConfigMap: MutableMap<String, Any> = MAPPER.readValue(tsConfig.inputStream()) |
| 69 | + stepEmitter.emitMessageLine(message("lambda.build.typescript.compiler.using_base", tsConfig), false) |
| 70 | + return tsConfigMap |
| 71 | + } catch (e: JsonProcessingException) { |
| 72 | + stepEmitter.emitMessageLine(message("lambda.build.typescript.compiler.using_base_error", tsConfig), true) |
| 73 | + } |
| 74 | + |
| 75 | + return null |
| 76 | + } |
| 77 | + val tsConfigMap = loadBaseConfig(tsConfig) |
| 78 | + ?: loadBaseConfig(sourceRoot.resolve(TS_CONFIG_INITIAL_BASE_FILE)) |
| 79 | + ?: mutableMapOf() |
| 80 | + |
| 81 | + // will create config from scratch if no base config has been loaded |
| 82 | + if (tsConfigMap.isEmpty()) { |
| 83 | + stepEmitter.emitMessageLine(message("lambda.build.typescript.compiler.creating_config"), false) |
| 84 | + } |
| 85 | + |
| 86 | + // use initial skeleton for compilerOptions if it does not exist |
| 87 | + val compilerOptions = tsConfigMap[TypeScriptConfig.COMPILER_OPTIONS_PROPERTY].castSafelyTo<MutableMap<String, Any>>() |
| 88 | + ?: mutableMapOf<String, Any>( |
| 89 | + TypeScriptConfig.TARGET_OPTION to TypeScriptConfig.LanguageTarget.ES6.libName, |
| 90 | + TypeScriptConfig.MODULE to TypeScriptConfig.MODULE_COMMON_JS, |
| 91 | + TypeScriptConfig.SOURCE_MAP to true |
| 92 | + ) |
| 93 | + tsConfigMap[TypeScriptConfig.COMPILER_OPTIONS_PROPERTY] = compilerOptions |
| 94 | + |
| 95 | + // overwrite outDir, rootDir, sourceRoot |
| 96 | + compilerOptions[TypeScriptConfig.OUT_DIR] = tsOutput |
| 97 | + compilerOptions[TypeScriptConfig.ROOT_DIR] = "." |
| 98 | + compilerOptions["sourceRoot"] = sourceRoot.toString() |
| 99 | + |
| 100 | + // ensure typeRoots has resolved path |
| 101 | + val typeRoots = compilerOptions[TypeScriptConfig.TYPE_ROOTS].castSafelyTo<MutableList<String>>() ?: mutableListOf() |
| 102 | + typeRoots.add(sourceRoot.resolve(TypeScriptConfig.DEFAULT_TYPES_DIRECTORY).toString()) |
| 103 | + compilerOptions[TypeScriptConfig.TYPE_ROOTS] = typeRoots.toSet().toList() |
| 104 | + |
| 105 | + // ensure types has node |
| 106 | + val types = compilerOptions[TypeScriptConfig.TYPES].castSafelyTo<MutableList<String>>() ?: mutableListOf() |
| 107 | + types.add("node") |
| 108 | + compilerOptions[TypeScriptConfig.TYPES] = types.toSet().toList() |
| 109 | + |
| 110 | + // pretty print the merged result |
55 | 111 | if (!tsConfig.exists()) {
|
56 | 112 | Files.createFile(tsConfig)
|
57 | 113 | }
|
58 |
| - |
59 |
| - // TODO: if there's an existing tsconfig file, should we use it as a base? |
60 |
| - tsConfig.writeText( |
61 |
| - // language=JSON |
62 |
| - """ |
63 |
| - { |
64 |
| - "compilerOptions": { |
65 |
| - "${TypeScriptConfig.TYPE_ROOTS}": [ |
66 |
| - "${sourceRoot.resolve(TypeScriptConfig.DEFAULT_TYPES_DIRECTORY)}" |
67 |
| - ], |
68 |
| - "${TypeScriptConfig.TYPES}": [ |
69 |
| - "node" |
70 |
| - ], |
71 |
| - "${TypeScriptConfig.TARGET_OPTION}": "${TypeScriptConfig.LanguageTarget.ES6.libName}", |
72 |
| - "${TypeScriptConfig.MODULE}": "${TypeScriptConfig.MODULE_COMMON_JS}", |
73 |
| - "${TypeScriptConfig.OUT_DIR}": "$tsOutput", |
74 |
| - "${TypeScriptConfig.ROOT_DIR}": ".", |
75 |
| - "sourceRoot": "$sourceRoot", |
76 |
| - "${TypeScriptConfig.SOURCE_MAP}": true |
77 |
| - } |
78 |
| - } |
79 |
| - """.trimIndent() |
80 |
| - ) |
| 114 | + tsConfig.writeText(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(tsConfigMap)) |
81 | 115 |
|
82 | 116 | val tsConfigVirtualFile = VfsUtil.findFile(tsConfig, true) ?: throw RuntimeException("Could not find temporary tsconfig file using VFS")
|
83 |
| - val tsService = TypeScriptCompilerService.getServiceForFile(project, handlerElement.containingFile.virtualFile) |
| 117 | + val tsService = TypeScriptService.getCompilerServiceForFile(project, handlerElement.containingFile.virtualFile) |
84 | 118 |
|
85 | 119 | stepEmitter.emitMessageLine(message("lambda.build.typescript.compiler.running", tsConfig), false)
|
86 | 120 | val compilerFuture = tsService?.compileConfigProjectAndGetErrors(tsConfigVirtualFile)
|
@@ -141,6 +175,11 @@ class NodeJsLambdaBuilder : LambdaBuilder() {
|
141 | 175 | private const val TS_BUILD_DIR = "aws-toolkit-ts-output"
|
142 | 176 | private const val TS_CONFIG_FILE = "aws-toolkit-tsconfig.json"
|
143 | 177 |
|
| 178 | + // use project tsconfig.json as initial base - if unable to parse existing config |
| 179 | + private const val TS_CONFIG_INITIAL_BASE_FILE = "tsconfig.json" |
| 180 | + |
| 181 | + private val MAPPER = jacksonObjectMapper() |
| 182 | + |
144 | 183 | private fun getSourceRoot(handlerElement: PsiElement): Path {
|
145 | 184 | val handlerVirtualFile = runReadAction { handlerElement.containingFile?.virtualFile }
|
146 | 185 | ?: throw IllegalArgumentException("Handler file must be backed by a VirtualFile")
|
|
0 commit comments