Packages

Gleam modules and functions for installing and invoking TailwindCSS

Current section

Files

Jump to
glailglind src tailwind.gleam
Raw

src/tailwind.gleam

//// Contains all the functions necessary to install and execute the TailwindCSS CLI.
////
import gleam/io
import gleam/result
import gleam/pair
import gleam/string
import gleam/list
import gleam/dict.{type Dict}
import simplifile
import shellout
import tom.{type Toml, String}
@target(erlang)
import gleam/bit_array
@target(erlang)
import gleam/httpc
@target(erlang)
import gleam/http.{Get}
@target(erlang)
import gleam/http/request
const tailwind_config_path = "./tailwind.config.js"
const tailwindcli_path = "./build/bin/tailwindcss-cli"
const config_path = "./gleam.toml"
const latest_version = "3.4.1"
@external(erlang, "tailwind_erl", "os_arch")
@external(javascript, "./tailwind_js.mjs", "os_arch")
fn os_arch() -> String
@external(erlang, "tailwind_erl", "os_platform")
@external(javascript, "./tailwind_js.mjs", "os_platform")
fn os_platform() -> String
/// Downloads the TailwindCSS CLI matching your operating system and architecture.
pub fn install() {
io.println("Installing TailwindCSS...")
let output =
generate_config()
|> result.try(fn(_) {
let version = get_tailwind_version()
download_tailwind(version, target())
})
case output {
Ok(_) -> {
io.println("TailwindCSS installed!")
Ok(Nil)
}
Error(err) -> {
io.println(err)
Error(err)
}
}
}
/// Executes the TailwindCSS CLI with the passed arguments.
/// # Example
/// ```gleam
/// > run(["--config=tailwind.config.js", "--input=./test/input.css", "--output=./build/css/output.css"])
/// Rebuilding...
/// Done in 90ms.
/// ```
pub fn run(args: List(String)) -> Result(String, String) {
let cli = get_cli_path()
case simplifile.verify_is_file(cli) {
Ok(True) -> {
shellout.command(run: cli, with: args, in: ".", opt: [])
|> result.map_error(fn(err) { pair.second(err) })
}
_otherwise -> {
Error("Error: TailwindCSS CLI isn't installed.")
}
}
}
/// Installs and then executes the TailwindCSS CLI with the passed arguments.
///
/// Check `install()` and `run()`.
pub fn install_and_run(args: List(String)) -> Result(String, String) {
install()
|> result.try(fn(_) { run(args) })
}
fn generate_config() -> Result(Nil, String) {
case simplifile.verify_is_file(tailwind_config_path) {
Ok(True) -> {
io.println("TailwindCSS config already exists.")
Ok(Nil)
}
_otherwise ->
"
// See the Tailwind configuration guide for advanced usage
// https://tailwindcss.com/docs/configuration
let plugin = require('tailwindcss/plugin')
module.exports = {
content: ['./src/**/*.{html,gleam}'],
theme: {
extend: {},
},
plugins: [require('@tailwindcss/forms')],
}
"
|> simplifile.write(to: tailwind_config_path)
|> result.map_error(fn(err) {
"Error: Couldn't create tailwind config. Reason: "
<> string.inspect(err)
})
|> result.map(fn(_) {
io.println("TailwindCSS config created.")
Nil
})
}
}
fn get_config() -> Result(Dict(String, Toml), String) {
simplifile.read(config_path)
|> result.map_error(fn(err) {
"Error: Couldn't read config. Reason: " <> string.inspect(err)
})
|> result.try(fn(config) {
tom.parse(config)
|> result.replace_error("Error: Couldn't parse config.")
})
}
fn get_config_string(key: String) -> Result(String, String) {
get_config()
|> result.try(fn(parsed) {
tom.get_string(parsed, ["tailwind", key])
|> result.replace_error("Error: Config key \"" <> key <> "\" not found.")
})
}
/// Fetches the argument list from the `gleam.toml`.
///
/// Public because it's needed in `tailwind/run`.
pub fn get_args() -> Result(List(String), String) {
get_config()
|> result.try(fn(parsed) {
tom.get_array(parsed, ["tailwind", "args"])
|> result.replace_error(
"Error: Config arguments not found. Is the \"args\" key set in the \"gleam.toml\"?",
)
|> result.map(fn(args) {
list.map(args, fn(arg) {
case arg {
String(a) -> a
_ -> ""
}
})
})
})
}
fn get_cli_path() -> String {
get_config_string("path")
|> result.unwrap(tailwindcli_path)
}
fn get_tailwind_version() -> String {
get_config_string("version")
|> result.unwrap(latest_version)
}
fn target() -> String {
case #(os_platform(), os_arch()) {
#("win32", "x86_64") | #("win32", "x64") -> "windows-x64.exe"
#("win32", "arm" <> _) -> "windows-arm64.exe"
#("darwin", "aarch64") | #("darwin", "arm" <> _) -> "macos-arm64"
#("darwin", "x86_64") | #("darwin", "x64") -> "macos-x64"
#("linux", "aarch64") | #("linux", "arm64") -> "linux-arm64"
#("linux", "armv7" <> _) -> "linux-armv7"
#("linux", "x86_64") | #("linux", "x64") | #("linux", "amd64") ->
"linux-x64"
#(os, arch) ->
panic as string.join(
["Error: TailwindCSS CLI is not available for", os, arch],
with: " ",
)
}
}
fn download_tailwind(version: String, target: String) -> Result(Nil, String) {
case simplifile.verify_is_file(tailwindcli_path) {
Ok(True) -> {
io.println("TailwindCSS CLI already exists.")
Ok(Nil)
}
_otherwise -> {
let url_path =
string.concat([
"/tailwindlabs/tailwindcss/releases/download/v",
version,
"/tailwindcss-",
target,
])
let assert Ok(Nil) = simplifile.create_directory_all("./build/bin/")
io.println("Downloading TailwindCSS " <> target <> "...")
download_bin(url_path)
|> result.try(fn(_) {
simplifile.set_permissions_octal(tailwindcli_path, 0o755)
|> result.map_error(fn(err) {
"Error: Can't change tailwindcli permissions. Reason: "
<> string.inspect(err)
})
})
}
}
}
@target(javascript)
fn download_bin(path: String) -> Result(Nil, String) {
shellout.command(
run: "curl",
with: ["-sL", "https://github.com" <> path, "-o", tailwindcli_path],
in: ".",
opt: [],
)
|> result.replace(Nil)
|> result.map_error(fn(err) {
"Error: Couldn't download tailwindcss cli. Reason: " <> pair.second(err)
})
}
@target(erlang)
fn download_bin(path: String) -> Result(Nil, String) {
request.new()
|> request.set_method(Get)
|> request.set_host("github.com")
|> request.set_path(path)
|> request.map(bit_array.from_string)
|> httpc.send_bits()
|> result.map_error(fn(err) {
"Error: Couldn't download tailwind. Reason: " <> string.inspect(err)
})
|> result.try(fn(resp) {
simplifile.write_bits(resp.body, to: tailwindcli_path)
|> result.map_error(fn(err) {
"Error: Couldn't write tailwind binary. Reason: " <> string.inspect(err)
})
})
}