posthog_cli/
commands.rs

1use clap::{Parser, Subcommand};
2use tracing::error;
3
4use crate::{
5    error::CapturedError,
6    experimental::{query::command::QueryCommand, tasks::TaskCommand},
7    invocation_context::{context, init_context},
8    sourcemaps::{hermes::HermesSubcommand, plain::SourcemapCommand},
9};
10
11#[derive(Parser)]
12#[command(version, about, long_about = None)]
13pub struct Cli {
14    /// The PostHog host to connect to
15    #[arg(long)]
16    host: Option<String>,
17
18    /// Disable non-zero exit codes on errors. Use with caution.
19    #[arg(long, default_value = "false")]
20    no_fail: bool,
21    #[arg(long, default_value = "false")]
22    skip_ssl_verification: bool,
23
24    #[command(subcommand)]
25    command: Commands,
26}
27
28#[derive(Subcommand)]
29pub enum Commands {
30    /// Interactively authenticate with PostHog, storing a personal API token locally. You can also use the
31    /// environment variables `POSTHOG_CLI_TOKEN` and `POSTHOG_CLI_ENV_ID`
32    Login,
33
34    /// Experimental commands, not quite ready for prime time
35    Exp {
36        #[command(subcommand)]
37        cmd: ExpCommand,
38    },
39
40    #[command(about = "Upload a directory of bundled chunks to PostHog")]
41    Sourcemap {
42        #[command(subcommand)]
43        cmd: SourcemapCommand,
44    },
45}
46
47#[derive(Subcommand)]
48pub enum ExpCommand {
49    /// Manage tasks - list, create, update, delete etc
50    Task {
51        #[command(subcommand)]
52        cmd: TaskCommand,
53        /// Whether to skip SSL verification when talking to the posthog API - only use when using self-signed certificates for
54        /// self-deployed instances
55        // TODO - it seems likely we won't support tasks for self hosted, but I'm putting this here in case we do
56        #[arg(long, default_value = "false")]
57        skip_ssl_verification: bool,
58    },
59
60    /// Run a SQL query against any data you have in posthog. This is mostly for fun, and subject to change
61    Query {
62        #[command(subcommand)]
63        cmd: QueryCommand,
64    },
65
66    #[command(about = "Upload hermes sourcemaps to PostHog")]
67    Hermes {
68        #[command(subcommand)]
69        cmd: HermesSubcommand,
70    },
71}
72
73impl Cli {
74    pub fn run() -> Result<(), CapturedError> {
75        let command = Cli::parse();
76        let no_fail = command.no_fail;
77
78        match command.run_impl() {
79            Ok(_) => Ok(()),
80            Err(e) => {
81                let msg = match &e.exception_id {
82                    Some(id) => format!("Oops! {} (ID: {})", e.inner, id),
83                    None => format!("Oops! {:?}", e.inner),
84                };
85                error!(msg);
86                if no_fail {
87                    Ok(())
88                } else {
89                    Err(e)
90                }
91            }
92        }
93    }
94
95    fn run_impl(self) -> Result<(), CapturedError> {
96        if !matches!(self.command, Commands::Login) {
97            init_context(self.host.clone(), self.skip_ssl_verification)?;
98        }
99
100        match self.command {
101            Commands::Login => {
102                // Notably login doesn't have a context set up going it - it sets one up
103                crate::login::login()?;
104            }
105            Commands::Sourcemap { cmd } => match cmd {
106                SourcemapCommand::Inject(input_args) => {
107                    crate::sourcemaps::plain::inject::inject(&input_args)?;
108                }
109                SourcemapCommand::Upload(upload_args) => {
110                    crate::sourcemaps::plain::upload::upload(&upload_args)?;
111                }
112                SourcemapCommand::Process(args) => {
113                    let (inject, upload) = args.into();
114                    crate::sourcemaps::plain::inject::inject(&inject)?;
115                    crate::sourcemaps::plain::upload::upload(&upload)?;
116                }
117            },
118            Commands::Exp { cmd } => match cmd {
119                ExpCommand::Task {
120                    cmd,
121                    skip_ssl_verification: _,
122                } => {
123                    cmd.run()?;
124                }
125                ExpCommand::Query { cmd } => {
126                    crate::experimental::query::command::query_command(&cmd)?
127                }
128                ExpCommand::Hermes { cmd } => match cmd {
129                    HermesSubcommand::Inject(args) => {
130                        crate::sourcemaps::hermes::inject::inject(&args)?;
131                    }
132                    HermesSubcommand::Upload(args) => {
133                        crate::sourcemaps::hermes::upload::upload(&args)?;
134                    }
135                    HermesSubcommand::Clone(args) => {
136                        crate::sourcemaps::hermes::clone::clone(&args)?;
137                    }
138                },
139            },
140        }
141
142        context().finish();
143
144        Ok(())
145    }
146}