1use clap::{Parser, Subcommand};
4
5pub(crate) const EXIT_CODES_HELP: &str = "\
7EXIT CODES:
8 0 Success - Command completed successfully
9 1 Error - Command failed with an error
10 2 Validation failed - Schema or input validation failed";
11
12#[derive(Parser)]
14#[command(name = "fraiseql")]
15#[command(author, version, about, long_about = None)]
16#[command(propagate_version = true)]
17#[command(after_help = EXIT_CODES_HELP)]
18pub(crate) struct Cli {
19 #[arg(short, long, global = true)]
21 pub(crate) verbose: bool,
22
23 #[arg(short, long, global = true)]
25 pub(crate) debug: bool,
26
27 #[arg(long, global = true)]
29 pub(crate) json: bool,
30
31 #[arg(short, long, global = true)]
33 pub(crate) quiet: bool,
34
35 #[command(subcommand)]
36 pub(crate) command: Commands,
37}
38
39#[derive(Subcommand)]
40pub(crate) enum Commands {
41 #[command(after_help = "\
48EXAMPLES:
49 fraiseql compile fraiseql.toml
50 fraiseql compile fraiseql.toml --types types.json
51 fraiseql compile schema.json -o schema.compiled.json
52 fraiseql compile fraiseql.toml --check")]
53 Compile {
54 #[arg(value_name = "INPUT")]
56 input: String,
57
58 #[arg(long, value_name = "TYPES")]
60 types: Option<String>,
61
62 #[arg(long, value_name = "DIR")]
64 schema_dir: Option<String>,
65
66 #[arg(long = "type-file", value_name = "FILE")]
69 type_files: Vec<String>,
70
71 #[arg(long = "query-file", value_name = "FILE")]
73 query_files: Vec<String>,
74
75 #[arg(long = "mutation-file", value_name = "FILE")]
77 mutation_files: Vec<String>,
78
79 #[arg(
81 short,
82 long,
83 value_name = "OUTPUT",
84 default_value = "schema.compiled.json"
85 )]
86 output: String,
87
88 #[arg(long)]
90 check: bool,
91
92 #[arg(long, value_name = "DATABASE_URL")]
95 database: Option<String>,
96 },
97
98 #[command(after_help = "\
103EXAMPLES:
104 fraiseql extract schema/schema.py
105 fraiseql extract schema/ --recursive
106 fraiseql extract schema.rs --language rust -o schema.json")]
107 Extract {
108 #[arg(value_name = "INPUT")]
110 input: Vec<String>,
111
112 #[arg(short, long)]
115 language: Option<String>,
116
117 #[arg(short, long)]
119 recursive: bool,
120
121 #[arg(short, long, default_value = "schema.json")]
123 output: String,
124 },
125
126 #[command(after_help = "\
130EXAMPLES:
131 fraiseql explain '{ users { id name } }'
132 fraiseql explain '{ user(id: 1) { posts { title } } }' --json")]
133 Explain {
134 #[arg(value_name = "QUERY")]
136 query: String,
137 },
138
139 #[command(after_help = "\
143EXAMPLES:
144 fraiseql cost '{ users { id name } }'
145 fraiseql cost '{ deeply { nested { query { here } } } }' --json")]
146 Cost {
147 #[arg(value_name = "QUERY")]
149 query: String,
150 },
151
152 #[command(after_help = "\
157EXAMPLES:
158 fraiseql analyze schema.compiled.json
159 fraiseql analyze schema.compiled.json --json")]
160 Analyze {
161 #[arg(value_name = "SCHEMA")]
163 schema: String,
164 },
165
166 #[command(after_help = "\
171EXAMPLES:
172 fraiseql dependency-graph schema.compiled.json
173 fraiseql dependency-graph schema.compiled.json -f dot > graph.dot
174 fraiseql dependency-graph schema.compiled.json -f mermaid
175 fraiseql dependency-graph schema.compiled.json --json")]
176 DependencyGraph {
177 #[arg(value_name = "SCHEMA")]
179 schema: String,
180
181 #[arg(short, long, value_name = "FORMAT", default_value = "json")]
183 format: String,
184 },
185
186 #[command(after_help = "\
190EXAMPLES:
191 fraiseql federation graph schema.compiled.json
192 fraiseql federation graph schema.compiled.json -f dot
193 fraiseql federation graph schema.compiled.json -f mermaid")]
194 Federation {
195 #[command(subcommand)]
197 command: FederationCommands,
198 },
199
200 #[command(after_help = "\
205EXAMPLES:
206 fraiseql lint schema.json
207 fraiseql lint schema.compiled.json --federation
208 fraiseql lint schema.json --fail-on-critical
209 fraiseql lint schema.json --json")]
210 Lint {
211 #[arg(value_name = "SCHEMA")]
213 schema: String,
214
215 #[arg(long)]
217 federation: bool,
218
219 #[arg(long)]
221 cost: bool,
222
223 #[arg(long)]
225 cache: bool,
226
227 #[arg(long)]
229 auth: bool,
230
231 #[arg(long)]
233 compilation: bool,
234
235 #[arg(long)]
237 fail_on_critical: bool,
238
239 #[arg(long)]
241 fail_on_warning: bool,
242
243 #[arg(long)]
245 verbose: bool,
246 },
247
248 #[command(after_help = "\
250EXAMPLES:
251 fraiseql generate-views -s schema.json -e User --view va_users
252 fraiseql generate-views -s schema.json -e Order --view tv_orders --refresh-strategy scheduled")]
253 GenerateViews {
254 #[arg(short, long, value_name = "SCHEMA")]
256 schema: String,
257
258 #[arg(short, long, value_name = "NAME")]
260 entity: String,
261
262 #[arg(long, value_name = "NAME")]
264 view: String,
265
266 #[arg(long, value_name = "STRATEGY", default_value = "trigger-based")]
268 refresh_strategy: String,
269
270 #[arg(short, long, value_name = "PATH")]
272 output: Option<String>,
273
274 #[arg(long, default_value = "true")]
276 include_composition_views: bool,
277
278 #[arg(long, default_value = "true")]
280 include_monitoring: bool,
281
282 #[arg(long)]
284 validate: bool,
285
286 #[arg(long, action = clap::ArgAction::SetTrue)]
288 gen_verbose: bool,
289 },
290
291 #[command(after_help = "\
299EXAMPLES:
300 fraiseql validate schema.json
301 fraiseql validate schema.json --check-unused
302 fraiseql validate schema.json --strict
303 fraiseql validate facts -s schema.json -d postgres://localhost/db")]
304 Validate {
305 #[command(subcommand)]
306 command: Option<ValidateCommands>,
307
308 #[arg(value_name = "INPUT")]
310 input: Option<String>,
311
312 #[arg(long, default_value = "true")]
314 check_cycles: bool,
315
316 #[arg(long)]
318 check_unused: bool,
319
320 #[arg(long)]
322 strict: bool,
323
324 #[arg(long, value_name = "TYPES", value_delimiter = ',')]
326 types: Vec<String>,
327 },
328
329 #[command(after_help = "\
331EXAMPLES:
332 fraiseql introspect facts -d postgres://localhost/db
333 fraiseql introspect facts -d postgres://localhost/db -f json")]
334 Introspect {
335 #[command(subcommand)]
336 command: IntrospectCommands,
337 },
338
339 #[command(after_help = "\
344EXAMPLES:
345 fraiseql generate schema.json --language python
346 fraiseql generate schema.json --language rust -o schema.rs
347 fraiseql generate schema.json --language typescript")]
348 Generate {
349 #[arg(value_name = "INPUT")]
351 input: String,
352
353 #[arg(short, long)]
355 language: String,
356
357 #[arg(short, long)]
359 output: Option<String>,
360 },
361
362 #[command(after_help = "\
367EXAMPLES:
368 fraiseql init my-app
369 fraiseql init my-app --language typescript --database postgres
370 fraiseql init my-app --size xs --no-git")]
371 Init {
372 #[arg(value_name = "PROJECT_NAME")]
374 project_name: String,
375
376 #[arg(short, long, default_value = "python")]
378 language: String,
379
380 #[arg(long, default_value = "postgres")]
382 database: String,
383
384 #[arg(long, default_value = "s")]
386 size: String,
387
388 #[arg(long)]
390 no_git: bool,
391 },
392
393 #[command(after_help = "\
398EXAMPLES:
399 fraiseql migrate up --database postgres://localhost/mydb
400 fraiseql migrate down --steps 1
401 fraiseql migrate status
402 fraiseql migrate create add_posts_table")]
403 Migrate {
404 #[command(subcommand)]
405 command: MigrateCommands,
406 },
407
408 #[command(after_help = "\
412EXAMPLES:
413 fraiseql sbom
414 fraiseql sbom --format spdx
415 fraiseql sbom --format cyclonedx --output sbom.json")]
416 Sbom {
417 #[arg(short, long, default_value = "cyclonedx")]
419 format: String,
420
421 #[arg(short, long, value_name = "FILE")]
423 output: Option<String>,
424 },
425
426 #[cfg(feature = "run-server")]
436 #[command(after_help = "\
437EXAMPLES:
438 fraiseql run
439 fraiseql run fraiseql.toml --database postgres://localhost/mydb
440 fraiseql run --port 3000 --watch
441 fraiseql run schema.json --introspection
442
443TOML CONFIG:
444 [server]
445 host = \"127.0.0.1\"
446 port = 9000
447
448 [server.cors]
449 origins = [\"https://app.example.com\"]
450
451 [database]
452 url = \"${DATABASE_URL}\"
453 pool_min = 2
454 pool_max = 20")]
455 Run {
456 #[arg(value_name = "INPUT")]
458 input: Option<String>,
459
460 #[arg(short, long, value_name = "DATABASE_URL")]
462 database: Option<String>,
463
464 #[arg(short, long, value_name = "PORT")]
466 port: Option<u16>,
467
468 #[arg(long, value_name = "HOST")]
470 bind: Option<String>,
471
472 #[arg(short, long)]
474 watch: bool,
475
476 #[arg(long)]
478 introspection: bool,
479 },
480
481 #[command(after_help = "\
486EXAMPLES:
487 fraiseql validate-documents manifest.json")]
488 ValidateDocuments {
489 #[arg(value_name = "MANIFEST")]
491 manifest: String,
492 },
493
494 #[command(hide = true)] Serve {
497 #[arg(value_name = "SCHEMA")]
499 schema: String,
500
501 #[arg(short, long, default_value = "8080")]
503 port: u16,
504 },
505
506 #[command(after_help = "\
512EXAMPLES:
513 fraiseql doctor
514 fraiseql doctor --schema schema.compiled.json --config fraiseql.toml
515 fraiseql doctor --db-url postgres://user:pass@host:5432/db
516 fraiseql doctor --json")]
517 Doctor {
518 #[arg(long, default_value = "fraiseql.toml")]
520 config: std::path::PathBuf,
521
522 #[arg(long, default_value = "schema.compiled.json")]
524 schema: std::path::PathBuf,
525
526 #[arg(long)]
528 db_url: Option<String>,
529
530 #[arg(long)]
532 json: bool,
533 },
534}
535
536#[derive(Subcommand)]
537pub(crate) enum ValidateCommands {
538 Facts {
540 #[arg(short, long, value_name = "SCHEMA")]
542 schema: String,
543
544 #[arg(short, long, value_name = "DATABASE_URL")]
546 database: String,
547 },
548}
549
550#[derive(Subcommand)]
551pub(crate) enum FederationCommands {
552 Graph {
554 #[arg(value_name = "SCHEMA")]
556 schema: String,
557
558 #[arg(short, long, value_name = "FORMAT", default_value = "json")]
560 format: String,
561 },
562}
563
564#[derive(Subcommand)]
565pub(crate) enum IntrospectCommands {
566 Facts {
568 #[arg(short, long, value_name = "DATABASE_URL")]
570 database: String,
571
572 #[arg(short, long, value_name = "FORMAT", default_value = "python")]
574 format: String,
575 },
576}
577
578#[derive(Subcommand)]
579pub(crate) enum MigrateCommands {
580 Up {
582 #[arg(long, value_name = "DATABASE_URL")]
584 database: Option<String>,
585
586 #[arg(long, value_name = "DIR")]
588 dir: Option<String>,
589 },
590
591 Down {
593 #[arg(long, value_name = "DATABASE_URL")]
595 database: Option<String>,
596
597 #[arg(long, value_name = "DIR")]
599 dir: Option<String>,
600
601 #[arg(long, default_value = "1")]
603 steps: u32,
604 },
605
606 Status {
608 #[arg(long, value_name = "DATABASE_URL")]
610 database: Option<String>,
611
612 #[arg(long, value_name = "DIR")]
614 dir: Option<String>,
615 },
616
617 Create {
619 #[arg(value_name = "NAME")]
621 name: String,
622
623 #[arg(long, value_name = "DIR")]
625 dir: Option<String>,
626 },
627}