use std::path::PathBuf;
use super::Provider;
use crate::nixpacks::{
app::App,
environment::Environment,
nix::pkg::Pkg,
plan::{
phase::{Phase, StartPhase},
BuildPlan,
},
};
use anyhow::{Context, Result};
use path_slash::PathBufExt;
use regex::Regex;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct DenoTasks {
pub start: Option<String>,
}
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct DenoJson {
pub tasks: Option<DenoTasks>,
}
pub struct DenoProvider {}
impl Provider for DenoProvider {
fn name(&self) -> &str {
"deno"
}
fn detect(&self, app: &App, _env: &Environment) -> Result<bool> {
let re = Regex::new(
r##"import .+ from (?:"|'|`)https://deno.land/[^"`']+\.(?:ts|js|tsx|jsx)(?:"|'|`);?"##,
)
.unwrap();
Ok(app.includes_file("deno.json")
|| app.includes_file("deno.jsonc")
|| app.find_match(&re, "**/*.{tsx,ts,js,jsx}")?)
}
fn get_build_plan(&self, app: &App, _env: &Environment) -> Result<Option<BuildPlan>> {
let mut plan = BuildPlan::default();
let setup_phase = Phase::setup(Some(vec![Pkg::new("deno")]));
plan.add_phase(setup_phase);
if let Some(build_cmd) = DenoProvider::get_build_cmd(app)? {
let build_phase = Phase::build(Some(build_cmd));
plan.add_phase(build_phase);
};
if let Some(start_cmd) = DenoProvider::get_start_cmd(app)? {
let start_phase = StartPhase::new(start_cmd);
plan.set_start_phase(start_phase);
}
Ok(Some(plan))
}
}
impl DenoProvider {
fn get_build_cmd(app: &App) -> Result<Option<String>> {
if let Some(start_file) = DenoProvider::get_start_file(app)? {
Ok(Some(format!(
"deno cache {}",
start_file
.to_slash()
.context("Failed to convert start_file to slash_path")?
)))
} else {
Ok(None)
}
}
fn get_start_cmd(app: &App) -> Result<Option<String>> {
if app.includes_file("deno.json") {
let deno_json: DenoJson = app.read_json("deno.json")?;
if let Some(tasks) = deno_json.tasks {
if let Some(start) = tasks.start {
return Ok(Some(start));
}
}
}
match DenoProvider::get_start_file(app)? {
Some(start_file) => Ok(Some(format!(
"deno run --allow-all {}",
start_file
.to_slash()
.context("Failed to convert start_file to slash_path")?
))),
None => Ok(None),
}
}
fn get_start_file(app: &App) -> Result<Option<PathBuf>> {
let matches = app.find_files("**/index.[tj]s")?;
let path_to_index = match matches.first() {
Some(m) => m,
None => return Ok(None),
};
let relative_path_to_index = app.strip_source_path(path_to_index)?;
Ok(Some(relative_path_to_index))
}
}