summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorrari404 <[email protected]>2025-12-08 13:02:25 -0500
committerGitHub <[email protected]>2025-12-08 12:02:25 -0600
commitfab8ab2840e96acab0e4a309990dacee67389cff (patch)
treef13460e5b62c8858f91dd8118bdefbd17c18d20c /packages
parent09ff8eba008da723011eb1912d0110265c1528e4 (diff)
downloadopencode-fab8ab2840e96acab0e4a309990dacee67389cff.tar.gz
opencode-fab8ab2840e96acab0e4a309990dacee67389cff.zip
feat: add terraform-ls language server and formatter (#5243)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/format/formatter.ts9
-rw-r--r--packages/opencode/src/lsp/language.ts3
-rw-r--r--packages/opencode/src/lsp/server.ts81
3 files changed, 93 insertions, 0 deletions
diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts
index d34144e7a..bcde6e7aa 100644
--- a/packages/opencode/src/format/formatter.ts
+++ b/packages/opencode/src/format/formatter.ts
@@ -266,3 +266,12 @@ export const ocamlformat: Info = {
return items.length > 0
},
}
+
+export const terraform: Info = {
+ name: "terraform",
+ command: ["terraform", "fmt", "$FILE"],
+ extensions: [".tf", ".tfvars"],
+ async enabled() {
+ return Bun.which("terraform") !== null
+ },
+}
diff --git a/packages/opencode/src/lsp/language.ts b/packages/opencode/src/lsp/language.ts
index 7980f05e8..918661ed5 100644
--- a/packages/opencode/src/lsp/language.ts
+++ b/packages/opencode/src/lsp/language.ts
@@ -103,4 +103,7 @@ export const LANGUAGE_EXTENSIONS: Record<string, string> = {
".zig": "zig",
".zon": "zig",
".astro": "astro",
+ ".tf": "terraform",
+ ".tfvars": "terraform-vars",
+ ".hcl": "hcl",
} as const
diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts
index 7b250c581..c2c70229d 100644
--- a/packages/opencode/src/lsp/server.ts
+++ b/packages/opencode/src/lsp/server.ts
@@ -1223,4 +1223,85 @@ export namespace LSPServer {
}
},
}
+
+ export const TerraformLS: Info = {
+ id: "terraform",
+ extensions: [".tf", ".tfvars"],
+ root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]),
+ async spawn(root) {
+ let bin = Bun.which("terraform-ls", {
+ PATH: process.env["PATH"] + ":" + Global.Path.bin,
+ })
+
+ if (!bin) {
+ if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
+ log.info("downloading terraform-ls from GitHub releases")
+
+ const releaseResponse = await fetch("https://api.github.com/repos/hashicorp/terraform-ls/releases/latest")
+ if (!releaseResponse.ok) {
+ log.error("Failed to fetch terraform-ls release info")
+ return
+ }
+
+ const release = (await releaseResponse.json()) as { tag_name?: string; assets?: { name?: string; browser_download_url?: string }[] }
+ const version = release.tag_name?.replace("v", "")
+ if (!version) {
+ log.error("terraform-ls release did not include a version tag")
+ return
+ }
+
+ const platform = process.platform
+ const arch = process.arch
+
+ const tfArch = arch === "arm64" ? "arm64" : "amd64"
+ const tfPlatform = platform === "win32" ? "windows" : platform
+
+ const assetName = `terraform-ls_${version}_${tfPlatform}_${tfArch}.zip`
+
+ const assets = release.assets ?? []
+ const asset = assets.find((a) => a.name === assetName)
+ if (!asset?.browser_download_url) {
+ log.error(`Could not find asset ${assetName} in terraform-ls release`)
+ return
+ }
+
+ const downloadResponse = await fetch(asset.browser_download_url)
+ if (!downloadResponse.ok) {
+ log.error("Failed to download terraform-ls")
+ return
+ }
+
+ const tempPath = path.join(Global.Path.bin, assetName)
+ await Bun.file(tempPath).write(downloadResponse)
+
+ await $`unzip -o -q ${tempPath}`.cwd(Global.Path.bin).nothrow()
+ await fs.rm(tempPath, { force: true })
+
+ bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
+
+ if (!(await Bun.file(bin).exists())) {
+ log.error("Failed to extract terraform-ls binary")
+ return
+ }
+
+ if (platform !== "win32") {
+ await $`chmod +x ${bin}`.nothrow()
+ }
+
+ log.info(`installed terraform-ls`, { bin })
+ }
+
+ return {
+ process: spawn(bin, ["serve"], {
+ cwd: root,
+ }),
+ initialization: {
+ experimentalFeatures: {
+ prefillRequiredFields: true,
+ validateOnSave: true,
+ },
+ },
+ }
+ },
+ }
}