Skip to content

Commit fab8ab2

Browse files
authored
feat: add terraform-ls language server and formatter (sst#5243)
1 parent 09ff8eb commit fab8ab2

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

packages/opencode/src/format/formatter.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,12 @@ export const ocamlformat: Info = {
266266
return items.length > 0
267267
},
268268
}
269+
270+
export const terraform: Info = {
271+
name: "terraform",
272+
command: ["terraform", "fmt", "$FILE"],
273+
extensions: [".tf", ".tfvars"],
274+
async enabled() {
275+
return Bun.which("terraform") !== null
276+
},
277+
}

packages/opencode/src/lsp/language.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,7 @@ export const LANGUAGE_EXTENSIONS: Record<string, string> = {
103103
".zig": "zig",
104104
".zon": "zig",
105105
".astro": "astro",
106+
".tf": "terraform",
107+
".tfvars": "terraform-vars",
108+
".hcl": "hcl",
106109
} as const

packages/opencode/src/lsp/server.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,4 +1223,85 @@ export namespace LSPServer {
12231223
}
12241224
},
12251225
}
1226+
1227+
export const TerraformLS: Info = {
1228+
id: "terraform",
1229+
extensions: [".tf", ".tfvars"],
1230+
root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]),
1231+
async spawn(root) {
1232+
let bin = Bun.which("terraform-ls", {
1233+
PATH: process.env["PATH"] + ":" + Global.Path.bin,
1234+
})
1235+
1236+
if (!bin) {
1237+
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
1238+
log.info("downloading terraform-ls from GitHub releases")
1239+
1240+
const releaseResponse = await fetch("https://api.github.com/repos/hashicorp/terraform-ls/releases/latest")
1241+
if (!releaseResponse.ok) {
1242+
log.error("Failed to fetch terraform-ls release info")
1243+
return
1244+
}
1245+
1246+
const release = (await releaseResponse.json()) as { tag_name?: string; assets?: { name?: string; browser_download_url?: string }[] }
1247+
const version = release.tag_name?.replace("v", "")
1248+
if (!version) {
1249+
log.error("terraform-ls release did not include a version tag")
1250+
return
1251+
}
1252+
1253+
const platform = process.platform
1254+
const arch = process.arch
1255+
1256+
const tfArch = arch === "arm64" ? "arm64" : "amd64"
1257+
const tfPlatform = platform === "win32" ? "windows" : platform
1258+
1259+
const assetName = `terraform-ls_${version}_${tfPlatform}_${tfArch}.zip`
1260+
1261+
const assets = release.assets ?? []
1262+
const asset = assets.find((a) => a.name === assetName)
1263+
if (!asset?.browser_download_url) {
1264+
log.error(`Could not find asset ${assetName} in terraform-ls release`)
1265+
return
1266+
}
1267+
1268+
const downloadResponse = await fetch(asset.browser_download_url)
1269+
if (!downloadResponse.ok) {
1270+
log.error("Failed to download terraform-ls")
1271+
return
1272+
}
1273+
1274+
const tempPath = path.join(Global.Path.bin, assetName)
1275+
await Bun.file(tempPath).write(downloadResponse)
1276+
1277+
await $`unzip -o -q ${tempPath}`.cwd(Global.Path.bin).nothrow()
1278+
await fs.rm(tempPath, { force: true })
1279+
1280+
bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
1281+
1282+
if (!(await Bun.file(bin).exists())) {
1283+
log.error("Failed to extract terraform-ls binary")
1284+
return
1285+
}
1286+
1287+
if (platform !== "win32") {
1288+
await $`chmod +x ${bin}`.nothrow()
1289+
}
1290+
1291+
log.info(`installed terraform-ls`, { bin })
1292+
}
1293+
1294+
return {
1295+
process: spawn(bin, ["serve"], {
1296+
cwd: root,
1297+
}),
1298+
initialization: {
1299+
experimentalFeatures: {
1300+
prefillRequiredFields: true,
1301+
validateOnSave: true,
1302+
},
1303+
},
1304+
}
1305+
},
1306+
}
12261307
}

0 commit comments

Comments
 (0)