Skip to main content

lux_cli/
upload.rs

1use clap::Args;
2use lux_lib::{
3    config::Config, package::PackageName, remote_package_db::RemotePackageDB,
4    upload::ProjectUpload, workspace::Workspace,
5};
6
7use miette::{IntoDiagnostic, Result};
8
9#[cfg(feature = "gpgme")]
10use lux_lib::upload::SignatureProtocol;
11
12#[derive(Args)]
13pub struct Upload {
14    /// The protocol to use when signing upload artefacts
15    #[cfg(feature = "gpgme")]
16    #[arg(long, default_value_t)]
17    sign_protocol: SignatureProtocol,
18
19    /// Package to upload.
20    #[arg(short, long, visible_short_alias = 'p')]
21    package: Option<PackageName>,
22
23    /// Code to use for two-factor authentication (2FA).{n}
24    /// It is recommended to enable 2FA for luarocks uploads (see https://luarocks.org/settings/two-factor-auth).{n}
25    /// Lux can also generate a TOTP code if you expose your luarocks.org 2FA secret via the 'LUAROCKS_2FA_SECRET' environment variable.{n}
26    #[arg(short, long, visible_short_alias = 'c')]
27    tfa_code: Option<String>,
28}
29
30#[cfg(feature = "gpgme")]
31pub async fn upload(data: Upload, config: Config) -> Result<()> {
32    let workspace = Workspace::current_or_err()?;
33
34    let package_db = RemotePackageDB::from_config(&config).await?;
35    let tfa_code = tfa_code_from_args_or_secret(&data)?;
36    if let Some(package) = data.package {
37        let project = workspace.select_member(&package)?;
38        ProjectUpload::new()
39            .project(project)
40            .config(&config)
41            .sign_protocol(data.sign_protocol.clone())
42            .maybe_tfa_code(tfa_code)
43            .package_db(&package_db)
44            .upload_to_luarocks()
45            .await?;
46    } else {
47        for project in workspace.members() {
48            ProjectUpload::new()
49                .project(project)
50                .config(&config)
51                .sign_protocol(data.sign_protocol.clone())
52                .maybe_tfa_code(tfa_code.clone())
53                .package_db(&package_db)
54                .upload_to_luarocks()
55                .await?;
56        }
57    }
58
59    Ok(())
60}
61
62#[cfg(not(feature = "gpgme"))]
63pub async fn upload(data: Upload, config: Config) -> Result<()> {
64    let workspace = Workspace::current_or_err()?;
65    let package_db = RemotePackageDB::from_config(&config).await?;
66    let tfa_code = tfa_code_from_args_or_secret(&data)?;
67    if let Some(package) = data.package {
68        let project = workspace.select_member(&package)?;
69        ProjectUpload::new()
70            .project(project)
71            .config(&config)
72            .maybe_tfa_code(tfa_code)
73            .package_db(&package_db)
74            .upload_to_luarocks()
75            .await?;
76    } else {
77        for project in workspace.members() {
78            ProjectUpload::new()
79                .project(project)
80                .config(&config)
81                .maybe_tfa_code(tfa_code.clone())
82                .package_db(&package_db)
83                .upload_to_luarocks()
84                .await?;
85        }
86    }
87
88    Ok(())
89}
90
91fn tfa_code_from_args_or_secret(data: &Upload) -> Result<Option<String>> {
92    match &data.tfa_code {
93        Some(code) => Ok(Some(code.to_owned())),
94        None => match std::env::var("LUAROCKS_2FA_SECRET") {
95            Ok(secret) => {
96                let secret = base32::decode(base32::Alphabet::Crockford, &secret)
97                    .ok_or(totp_rs::SecretParseError::ParseBase32)
98                    .into_diagnostic()?;
99                let totp = totp_rs::TOTP::new(totp_rs::Algorithm::SHA1, 6, 1, 30, secret)
100                    .into_diagnostic()?;
101                Ok(Some(totp.generate_current().into_diagnostic()?))
102            }
103            Err(_) => Ok(None),
104        },
105    }
106}