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