sync: CompeyDev/lune-packaging (1ec11d7827ced096882d2da51073819d1e031891)

This commit is contained in:
Erica Marigold 2023-09-30 17:33:50 +05:30
commit b9724397f9
14 changed files with 1809 additions and 0 deletions

11
.editorconfig Normal file
View file

@ -0,0 +1,11 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = tab
[{*.yaml,*.yml, *.md}]
indent_style = space
indent_size = 2

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Cargo
target/
# Downloaded files
lune-*

3
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"recommendations": ["rust-lang.rust-analyzer", "esbenp.prettier-vscode"]
}

12
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,12 @@
{
"rust-analyzer.check.command": "clippy",
"rust-analyzer.linkedProjects": ["./Cargo.toml"],
"editor.formatOnSave": true,
"prettier.tabWidth": 2,
"[json][jsonc][markdown][yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
}
}

1264
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

26
Cargo.toml Normal file
View file

@ -0,0 +1,26 @@
[package]
name = "setup-lune"
version = "0.1.0"
edition = "2021"
[profile.release]
opt-level = "z"
strip = true
lto = true
[dependencies]
actions-core = "0.0.2"
anyhow = "1.0.75"
better-panic = "0.3.0"
colored = "2.0.4"
directories = "5.0.1"
home = "0.5.5"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
tracing = "0.1.37"
tracing-core = "0.1.31"
tracing-subscriber = "0.3.17"
tracing-unwrap = "0.10.0"
ureq = "2.7.1"
url = "2.4.1"
zip = "0.6.6"

16
action.yml Normal file
View file

@ -0,0 +1,16 @@
name: "setup-lune"
description: "Installs lune - the standalone luau runtime"
branding:
icon: "terminal"
color: "purple"
inputs:
version:
description: "Version of lune to install, defaults to latest"
required: false
runs:
using: "composite"
steps:
- run: "wget https://github.com/CompeyDev/setup-lune/releases/download/latest/setup-lune-${{ runner.os }}-x86_64.zip"
- run: "unzip setup-lune-${{ runner.os }}-x86_64.zip"
- run: "chmod +X ./setup-lune && ./setup-lune"
- run: "rm -rf ./setup-lune"

5
rustfmt.toml Normal file
View file

@ -0,0 +1,5 @@
max_width = 150
hard_tabs = true
#normalize_comments = false
#match_block_trailing_comma = true
#closure_block_indent_threshold = 1

175
src/download.rs Normal file
View file

@ -0,0 +1,175 @@
use std::{
env,
fs::File,
io::{Error, ErrorKind, Read, Seek, Write},
path::PathBuf,
str::FromStr,
};
use anyhow::Result;
use directories::BaseDirs;
use url::Url;
use zip::ZipArchive;
use crate::{
github::{FetchResult, Fetcher, GitHub, Method},
types::{LuneReleaseData, LuneReleaseFetched},
};
fn parse_release_data(data: &LuneReleaseData) -> LuneReleaseFetched {
const TARGET: &str = "download::parse_release_data";
tracing::info!(target: TARGET, "Parsing received LuneReleaseData");
let unknown = &String::from("UNKNOWN");
let mut version = unknown.to_owned();
let mut download = unknown.to_owned();
let mut artifact_name = unknown.to_owned();
tracing::info!(
target: TARGET,
"Defaulting to `unknown` values before parsing"
);
for asset in &data.assets {
let parts = asset.name.split('-').map(|s| s.to_string()).collect::<Vec<String>>();
// [0] => always "lune"
// [1] => version
// [2] => platform
// [3] => arch .. ".zip"
if parts[2] == env::consts::OS && parts[3].trim_end_matches(".zip") == env::consts::ARCH {
tracing::info!(
target: TARGET,
"Supported platform found, overwriting `unknown` values"
);
version = (parts[1]).to_owned();
download = (asset.browser_download_url).to_owned();
artifact_name = (asset.name).to_owned();
} else {
tracing::warn!(
target: TARGET,
"Supported platform not found for asset {:?}",
asset
);
}
}
tracing::info!(target: TARGET, "Done.");
LuneReleaseFetched {
version,
download,
artifact_name,
raw: Some(data.clone()),
}
}
pub fn download_release(version: Option<String>) -> Result<(PathBuf, LuneReleaseFetched)> {
const TARGET: &str = "download::download_release";
tracing::info!(target: TARGET, "Initializing routine");
let parsed_release_data: LuneReleaseFetched;
if let Some(ver) = version {
let artifact_name = format!("lune-{ver}-{}-{}.zip", env::consts::OS, env::consts::ARCH);
parsed_release_data = LuneReleaseFetched {
version: ver.to_string(),
download: format!("https://github.com/filiptibell/lune/releases/download/v{ver}/{artifact_name}"),
artifact_name,
raw: None,
}
} else {
parsed_release_data = parse_release_data(&GitHub::new(("filiptibell", "lune")).fetch_releases()?);
tracing::info!(target: TARGET, "Received API resp and parsed release data");
}
tracing::info!(target: TARGET, "Got lune {}", parsed_release_data.version);
let fetcher = Fetcher::new();
tracing::info!(
target: TARGET,
"Proceeding to download release from API resp (url = {})",
&parsed_release_data.download
);
let resp = fetcher.fetch::<_, ()>(Method::Get, Url::from_str(parsed_release_data.download.as_str())?, false)?;
match resp {
FetchResult::Response(res) => {
tracing::info!(
target: TARGET,
"Received download resp, proceeding to write zip to disk at {}",
&parsed_release_data.artifact_name
);
let mut zip_archive = File::create(&parsed_release_data.artifact_name)?;
let bytes = res.into_reader().bytes().map(|elem| elem.unwrap()).collect::<Vec<u8>>();
zip_archive.write_all(&bytes)?;
tracing::info!(target: TARGET, "Successfully downloaded release");
}
FetchResult::Result(_) => unreachable!("Fetcher returned Result instead of Response"),
};
Ok((PathBuf::from(&parsed_release_data.artifact_name), parsed_release_data))
}
pub fn install_lune(lune_archive_reader: impl Read + Seek, meta: LuneReleaseFetched) -> Result<PathBuf> {
const TARGET: &str = "download::install_lune";
tracing::info!(target: TARGET, "Initializing routine");
let dir_name = meta.artifact_name.trim_end_matches(".zip");
tracing::info!(target: TARGET, "Proceeding to extract release zip");
let mut lune_zip = ZipArchive::new(lune_archive_reader)?;
lune_zip.extract(dir_name)?;
tracing::info!(target: TARGET, "Successfully extracted release zip");
let bin_name = match env::consts::OS {
"windows" => "lune.exe",
"macos" | "linux" => "lune",
_ => panic!("unsupported platform"),
};
let bin_base_dir = BaseDirs::new()
.ok_or(Error::new(ErrorKind::NotFound, "failed to create BaseDirs instance"))?
.home_dir()
.join("bin");
if !bin_base_dir.exists() {
tracing::warn!(target: TARGET, "bin_base_dir nonexistent, creating");
std::fs::create_dir(&bin_base_dir)?;
}
tracing::warn!(
target: TARGET,
"macOS and windows support for this action is incomplete"
);
// TODO: macOS and windows support
let lune_bin_path = bin_base_dir.join(bin_name);
File::create(&lune_bin_path)?;
std::fs::rename(PathBuf::from(dir_name).join(bin_name), &lune_bin_path)?;
tracing::info!(
target: TARGET,
"Installed lune bin to {}",
lune_bin_path.to_string_lossy()
);
Ok(lune_bin_path)
}

56
src/fmt.rs Normal file
View file

@ -0,0 +1,56 @@
use colored::Colorize;
use std::fmt;
use tracing_core::{Event, Level, Subscriber};
use tracing_subscriber::fmt::{
format::{self, FormatEvent, FormatFields},
FmtContext, FormattedFields,
};
use tracing_subscriber::registry::LookupSpan;
pub struct LogFormatter;
impl<S, N> FormatEvent<S, N> for LogFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(&self, ctx: &FmtContext<'_, S, N>, mut writer: format::Writer<'_>, event: &Event<'_>) -> fmt::Result {
let meta = event.metadata();
let scope = match *meta.level() {
Level::DEBUG => "?".purple().bold(),
Level::ERROR => "!".red().bold(),
Level::INFO => "*".green().bold(),
Level::TRACE => ".".white().bold(),
Level::WARN => "#".yellow().bold(),
};
let target = if meta.level() != &Level::INFO {
let mut sep = "::".to_owned();
sep.push_str(&meta.target().italic().underline());
sep
} else {
"::main".to_string()
};
write!(&mut writer, "[{}][{}] ", scope, target)?;
if let Some(scope) = ctx.event_scope() {
for span in scope.from_root() {
write!(writer, "{}", span.name())?;
let ext = span.extensions();
let fields = &ext.get::<FormattedFields<N>>().unwrap();
if !fields.is_empty() {
write!(writer, "{{{}}}", fields)?;
}
write!(writer, " ")?;
}
}
ctx.field_format().format_fields(writer.by_ref(), event)?;
writeln!(writer)
}
}

146
src/github.rs Normal file
View file

@ -0,0 +1,146 @@
use std::{collections::HashMap, str::FromStr, time::Duration};
use anyhow::Result;
use serde::{de::DeserializeOwned, Deserialize};
use ureq::Response;
use url::Url;
use crate::types::LuneReleaseData;
#[derive(Debug, Default, Clone)]
pub struct GitHub {
fetcher: Fetcher,
repository: Repository,
}
impl GitHub {
pub fn new(repo: (&str, &str)) -> Self {
let fetcher = Fetcher::new();
Self {
fetcher: fetcher.to_owned(),
repository: Repository::new(repo.0, repo.1, fetcher),
}
}
pub fn fetch_releases(&self) -> Result<LuneReleaseData> {
let api_uri = Url::from_str(self.repository.api_url().as_str())?.join("releases/")?.join("latest")?;
Ok(match self.fetcher.fetch::<_, LuneReleaseData>(Method::Get, api_uri, true)? {
FetchResult::Result(res) => res,
FetchResult::Response(_) => {
unreachable!("Fetcher returned Response instead of Result")
}
})
}
}
#[derive(Debug, Default, Clone)]
pub struct Repository {
fetcher: Fetcher,
scope: String,
repo: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct RepositoryMeta {
pub full_name: String,
pub name: String,
pub description: String,
pub license: HashMap<String, String>,
pub topics: Vec<String>,
pub forks: u32,
pub open_issues: u32,
pub stars: u32,
}
impl Repository {
pub fn new<T>(scope: T, repo: T, fetcher: Fetcher) -> Self
where
T: ToString,
{
Self {
fetcher,
scope: scope.to_string(),
repo: repo.to_string(),
}
}
pub fn scope(&self) -> &String {
&self.scope
}
pub fn repo(&self) -> &String {
&self.repo
}
pub fn api_url(&self) -> String {
format!("https://api.github.com/repos/{}/{}/", self.scope, self.repo)
}
pub fn fetch_meta(&self) -> Result<RepositoryMeta> {
Ok(
match self
.fetcher
.fetch::<_, RepositoryMeta>(Method::Get, Url::from_str(self.api_url().as_str())?, true)?
{
FetchResult::Result(deserialized) => deserialized,
FetchResult::Response(_) => {
unreachable!("Fetcher returned Response instead of Result")
}
},
)
}
}
#[derive(Debug, Clone)]
pub struct Fetcher {
client: ureq::Agent,
}
pub enum Method {
Get,
Post,
}
pub enum FetchResult<D> {
Result(D),
Response(Box<Response>),
}
impl Default for Fetcher {
fn default() -> Self {
Self::new()
}
}
impl Fetcher {
pub fn new() -> Self {
Self {
client: ureq::builder()
.redirects(2)
.https_only(true)
.timeout(Duration::from_secs(30))
.user_agent("lune-action/0.1.0")
.build(),
}
}
pub fn fetch<U, D>(&self, method: Method, uri: U, to_deserialize: bool) -> Result<FetchResult<D>>
where
U: Into<url::Url>,
D: DeserializeOwned,
{
let method = match method {
Method::Get => "GET",
Method::Post => "POST",
};
let req = self.client.request_url(method, &uri.into());
Ok(match to_deserialize {
true => FetchResult::Result(serde_json::from_reader::<_, D>(req.call()?.into_reader())?),
false => FetchResult::Response(Box::new(req.call()?)),
})
}
}

4
src/lib.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod download;
pub mod fmt;
pub mod github;
pub mod types;

65
src/main.rs Normal file
View file

@ -0,0 +1,65 @@
use std::fs::{read_dir, remove_dir_all, remove_file, DirEntry, File};
use actions_core as core;
use setup_lune::{
download::{download_release, install_lune},
fmt::LogFormatter,
};
use tracing::Level;
use tracing_unwrap::ResultExt;
fn main() {
let version = match core::input("version") {
Ok(val) => Some(val),
Err(_) => None,
};
if cfg!(debug_assertions) {
better_panic::install();
}
tracing_subscriber::fmt()
.with_max_level(match core::is_debug() || cfg!(debug_assertions) {
true => Level::DEBUG,
false => Level::INFO,
})
.event_format(LogFormatter)
.init();
let (zip_path, meta) = download_release(version).expect_or_log("failed to download latest lune release");
let mut install_path = install_lune(
File::open(&zip_path).unwrap_or_else(|_| panic!("failed to open downloaded lune release zip file @ {}", zip_path.to_string_lossy())),
meta,
)
.expect_or_log("failed to install lune. did we not have perms to write to the required directories?");
install_path.pop();
core::add_path(install_path.to_string_lossy());
// Cleanup downloaded & unzipped stuff
let to_remove = read_dir(".")
.expect_or_log("failed to read current working directory")
.filter(|f| {
f.as_ref()
.expect_or_log("failed to read file of directory")
.file_name()
.to_string_lossy()
.starts_with("lune-")
})
.map(|elem| elem.unwrap())
.collect::<Vec<DirEntry>>();
for entry in to_remove {
let file_type = entry.file_type().expect_or_log("failed to get filetype for cleanup");
if file_type.is_file() {
remove_file(entry.path()).expect("failed to cleanup after installation!")
}
if file_type.is_dir() {
remove_dir_all(entry.path()).expect("failed to cleanup after installation!")
}
}
}

21
src/types.rs Normal file
View file

@ -0,0 +1,21 @@
use serde::Deserialize;
#[derive(Deserialize, Debug, Clone)]
pub struct LuneReleaseData {
pub name: String,
pub assets: Vec<LuneAssetData>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct LuneAssetData {
pub browser_download_url: String,
pub name: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct LuneReleaseFetched {
pub version: String,
pub download: String,
pub artifact_name: String,
pub raw: Option<LuneReleaseData>,
}