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