Skip to main content

lux_cli/
upload.rs

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