libcoreinst/cmdline/
mod.rs

1// Copyright 2019 CoreOS, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// We don't care about the size of enum variants and don't want to box them
16#![allow(clippy::large_enum_variant)]
17
18use clap::Parser;
19use reqwest::Url;
20
21mod console;
22#[cfg(feature = "docgen")]
23mod doc;
24mod install;
25mod serializer;
26mod types;
27
28pub use self::console::*;
29#[cfg(feature = "docgen")]
30pub use self::doc::*;
31pub use self::install::InstallConfig;
32pub use self::types::*;
33
34// Args are listed in --help in the order declared in these structs/enums.
35// Please keep the entire help text to 80 columns.
36
37/// Installer for Fedora CoreOS and RHEL CoreOS
38#[derive(Debug, Parser)]
39#[command(version)]
40#[command(args_conflicts_with_subcommands = true)]
41#[command(disable_help_subcommand = true)]
42#[command(help_expected = true)]
43pub enum Cmd {
44    /// Install Fedora CoreOS or RHEL CoreOS
45    Install(InstallConfig),
46    /// Download a CoreOS image
47    Download(DownloadConfig),
48    /// List available images in a Fedora CoreOS stream
49    ListStream(ListStreamConfig),
50    /// Commands to manage a CoreOS live ISO image
51    #[command(subcommand)]
52    Iso(IsoCmd),
53    /// Commands to manage a CoreOS live PXE image
54    #[command(subcommand)]
55    Pxe(PxeCmd),
56    /// Metadata packing commands used when building an OS image
57    #[command(subcommand)]
58    Pack(PackCmd),
59    /// Development commands (unstable)
60    #[command(subcommand)]
61    Dev(DevCmd),
62}
63
64#[derive(Debug, Parser)]
65pub enum IsoCmd {
66    /// Embed an Ignition config in an ISO image
67    // deprecated
68    #[command(hide = true)]
69    Embed(IsoEmbedConfig),
70    /// Show the embedded Ignition config from an ISO image
71    // deprecated
72    #[command(hide = true)]
73    Show(IsoShowConfig),
74    /// Remove an existing embedded Ignition config from an ISO image
75    // deprecated
76    #[command(hide = true)]
77    Remove(IsoRemoveConfig),
78    /// Customize a CoreOS live ISO image
79    Customize(IsoCustomizeConfig),
80    /// Embed an Ignition config in a CoreOS live ISO image
81    #[command(subcommand)]
82    Ignition(IsoIgnitionCmd),
83    /// Embed network settings in a CoreOS live ISO image
84    #[command(subcommand)]
85    Network(IsoNetworkCmd),
86    /// Modify kernel args in a CoreOS live ISO image
87    #[command(subcommand)]
88    Kargs(IsoKargsCmd),
89    /// Commands to extract files from a CoreOS live ISO image
90    #[command(subcommand)]
91    Extract(IsoExtractCmd),
92    /// Restore a CoreOS live ISO image to default settings
93    Reset(IsoResetConfig),
94}
95
96#[derive(Debug, Parser)]
97pub enum IsoIgnitionCmd {
98    /// Embed an Ignition config in an ISO image
99    Embed(IsoIgnitionEmbedConfig),
100    /// Show the embedded Ignition config from an ISO image
101    Show(IsoIgnitionShowConfig),
102    /// Remove an existing embedded Ignition config from an ISO image
103    Remove(IsoIgnitionRemoveConfig),
104}
105
106#[derive(Debug, Parser)]
107pub enum IsoNetworkCmd {
108    /// Embed network settings in an ISO image
109    Embed(IsoNetworkEmbedConfig),
110    /// Extract embedded network settings from an ISO image
111    Extract(IsoNetworkExtractConfig),
112    /// Remove existing network settings from an ISO image
113    Remove(IsoNetworkRemoveConfig),
114}
115
116#[derive(Debug, Parser)]
117pub enum IsoKargsCmd {
118    /// Modify kernel args in an ISO image
119    Modify(IsoKargsModifyConfig),
120    /// Reset kernel args in an ISO image to defaults
121    Reset(IsoKargsResetConfig),
122    /// Show kernel args from an ISO image
123    Show(IsoKargsShowConfig),
124}
125
126#[derive(Debug, Parser)]
127pub enum IsoExtractCmd {
128    /// Extract PXE files from an ISO image
129    Pxe(IsoExtractPxeConfig),
130    /// Extract a minimal ISO from a CoreOS live ISO image
131    MinimalIso(IsoExtractMinimalIsoConfig),
132}
133
134#[derive(Debug, Parser)]
135pub enum PxeCmd {
136    /// Create a custom live PXE boot config
137    Customize(PxeCustomizeConfig),
138    /// Commands to manage a live PXE Ignition config
139    #[command(subcommand)]
140    Ignition(PxeIgnitionCmd),
141    /// Commands to manage live PXE network settings
142    #[command(subcommand)]
143    Network(PxeNetworkCmd),
144}
145
146#[derive(Debug, Parser)]
147pub enum PxeIgnitionCmd {
148    /// Wrap an Ignition config in an initrd image
149    Wrap(PxeIgnitionWrapConfig),
150    /// Show the wrapped Ignition config in an initrd image
151    Unwrap(PxeIgnitionUnwrapConfig),
152}
153
154#[derive(Debug, Parser)]
155pub enum PxeNetworkCmd {
156    /// Wrap network settings in an initrd image
157    Wrap(PxeNetworkWrapConfig),
158    /// Extract wrapped network settings from an initrd image
159    Unwrap(PxeNetworkUnwrapConfig),
160}
161
162#[derive(Debug, Parser)]
163// users shouldn't be interacting with this command normally
164#[command(hide = true)]
165pub enum PackCmd {
166    /// Create osmet file from CoreOS block device
167    Osmet(PackOsmetConfig),
168    /// Pack a minimal ISO into a CoreOS live ISO image
169    MinimalIso(PackMinimalIsoConfig),
170    /// Generate man pages for coreos-installer
171    #[cfg(feature = "docgen")]
172    Man(PackManConfig),
173    /// Generate example config file for install subcommand
174    #[cfg(feature = "docgen")]
175    ExampleConfig(PackExampleConfigConfig),
176}
177
178#[derive(Debug, Parser)]
179// users shouldn't be interacting with this command normally
180#[command(hide = true)]
181pub enum DevCmd {
182    /// Commands to show metadata
183    #[command(subcommand)]
184    Show(DevShowCmd),
185    /// Commands to extract data
186    #[command(subcommand)]
187    Extract(DevExtractCmd),
188}
189
190#[derive(Debug, Parser)]
191pub enum DevShowCmd {
192    /// Inspect the CoreOS live ISO image
193    Iso(DevShowIsoConfig),
194    /// Show the contents of an initrd image
195    Initrd(DevShowInitrdConfig),
196    /// Print file extent mapping of specific file
197    Fiemap(DevShowFiemapConfig),
198}
199
200#[derive(Debug, Parser)]
201pub enum DevExtractCmd {
202    /// Generate raw metal image from osmet file and OSTree repo
203    Osmet(DevExtractOsmetConfig),
204    /// Extract the contents of an initrd image
205    Initrd(DevExtractInitrdConfig),
206}
207
208#[derive(Debug, Parser)]
209pub struct DownloadConfig {
210    /// Fedora CoreOS stream
211    #[arg(short, long, value_name = "name", default_value = "stable")]
212    pub stream: String,
213    /// Target CPU architecture
214    #[arg(short, long, value_name = "name", default_value_t)]
215    pub architecture: DefaultedString<Architecture>,
216    /// Fedora CoreOS platform name
217    #[arg(short, long, value_name = "name", default_value = "metal")]
218    pub platform: String,
219    /// Image format
220    #[arg(short, long, value_name = "name", default_value = "raw.xz")]
221    pub format: String,
222    /// Manually specify the image URL
223    #[arg(short = 'u', long, value_name = "URL")]
224    pub image_url: Option<Url>,
225    /// Destination directory
226    #[arg(short = 'C', long, value_name = "path", default_value = ".")]
227    pub directory: String,
228    /// Decompress image and don't save signature
229    #[arg(short, long)]
230    pub decompress: bool,
231    /// Allow unsigned image
232    #[arg(long)]
233    pub insecure: bool,
234    /// Base URL for Fedora CoreOS stream metadata
235    #[arg(long, value_name = "URL")]
236    pub stream_base_url: Option<Url>,
237    /// Fetch retries, or "infinite"
238    #[arg(long, value_name = "N", default_value_t)]
239    pub fetch_retries: FetchRetries,
240}
241
242#[derive(Debug, Parser)]
243pub struct ListStreamConfig {
244    /// Fedora CoreOS stream
245    #[arg(short, long, value_name = "name", default_value = "stable")]
246    pub stream: String,
247    /// Base URL for Fedora CoreOS stream metadata
248    #[arg(long, value_name = "URL")]
249    pub stream_base_url: Option<Url>,
250}
251
252#[derive(Debug, Parser)]
253pub struct CommonCustomizeConfig {
254    /// Ignition config fragment for dest sys
255    ///
256    /// Automatically run installer and merge the specified Ignition config
257    /// into the config for the destination system.
258    #[arg(long, value_name = "path")]
259    pub dest_ignition: Vec<String>,
260    /// Install destination device
261    ///
262    /// Automatically run installer, installing to the specified destination
263    /// device.  The resulting boot media will overwrite the destination
264    /// device without confirmation.
265    #[arg(long, value_name = "path")]
266    pub dest_device: Option<String>,
267    /// Kernel and bootloader console for dest
268    ///
269    /// Automatically run installer, configuring the specified kernel and
270    /// bootloader console for the destination system.  The argument uses
271    /// the same syntax as the parameter to the "console=" kernel argument.
272    #[arg(long, value_name = "spec")]
273    pub dest_console: Vec<Console>,
274    /// Destination kernel argument to append
275    ///
276    /// Automatically run installer, adding the specified kernel argument
277    /// for every boot of the destination system.
278    #[arg(long, value_name = "arg")]
279    pub dest_karg_append: Vec<String>,
280    /// Destination kernel argument to delete
281    ///
282    /// Automatically run installer, deleting the specified kernel argument
283    /// for every boot of the destination system.
284    #[arg(long, value_name = "arg")]
285    pub dest_karg_delete: Vec<String>,
286    /// NetworkManager keyfile for live & dest
287    ///
288    /// Configure networking using the specified NetworkManager keyfile.
289    /// Network settings will be applied in the live environment, including
290    /// when Ignition is run.  If installer is enabled via additional options,
291    /// network settings will also be applied in the destination system,
292    /// including when Ignition is run.
293    #[arg(long, value_name = "path")]
294    pub network_keyfile: Vec<String>,
295    /// Nmstate file for live & dest
296    ///
297    /// Configure networking using NetworkManager keyfiles generated from the
298    /// specified Nmstate files. Network settings will be applied in the live
299    /// environment, including when Ignition is run.  If installer is enabled
300    /// via additional options, network settings will also be applied in the
301    /// destination system, including when Ignition is run.
302    #[arg(long, value_name = "path")]
303    pub network_nmstate: Vec<String>,
304    /// Ignition PEM CA bundle for live & dest
305    ///
306    /// Specify additional TLS certificate authorities to be trusted by
307    /// Ignition, in PEM format.  Authorities will be trusted by Ignition
308    /// in the live environment and, if installer is enabled via additional
309    /// options, in the destination system.
310    #[arg(long, value_name = "path")]
311    pub ignition_ca: Vec<String>,
312    /// Script to run before installation
313    ///
314    /// If installer is run at boot, run this script before installation.
315    /// If the script fails, the live environment will stop at an emergency
316    /// shell.
317    #[arg(long, value_name = "path")]
318    pub pre_install: Vec<String>,
319    /// Script to run after installation
320    ///
321    /// If installer is run at boot, run this script after installation.
322    /// If the script fails, the live environment will stop at an emergency
323    /// shell.
324    #[arg(long, value_name = "path")]
325    pub post_install: Vec<String>,
326    /// Installer config file
327    ///
328    /// Automatically run coreos-installer and apply the specified installer
329    /// config file.  Config files are applied in the order that they are
330    /// specified.
331    #[arg(long, value_name = "path")]
332    pub installer_config: Vec<String>,
333    /// Ignition config fragment for live env
334    ///
335    /// Merge the specified Ignition config into the config for the live
336    /// environment.
337    #[arg(long, value_name = "path")]
338    pub live_ignition: Vec<String>,
339}
340
341#[derive(Debug, Parser)]
342pub struct IsoCustomizeConfig {
343    // Customizations
344    #[command(flatten)]
345    pub common: CommonCustomizeConfig,
346    /// Live kernel argument to append
347    ///
348    /// Kernel argument to append to boots of the live environment.
349    #[arg(long, value_name = "arg")]
350    pub live_karg_append: Vec<String>,
351    /// Live kernel argument to delete
352    ///
353    /// Kernel argument to delete from boots of the live environment.
354    #[arg(long, value_name = "arg")]
355    pub live_karg_delete: Vec<String>,
356    /// Live kernel argument to replace
357    ///
358    /// Kernel argument to replace for boots of the live environment, in the
359    /// form key=old=new.  For a default argument "a=b", specifying
360    /// "--live-karg-replace a=b=c" will produce the argument "a=c".
361    #[arg(long, value_name = "k=o=n")]
362    pub live_karg_replace: Vec<String>,
363
364    // I/O configuration
365    /// Overwrite existing customizations
366    #[arg(short, long)]
367    pub force: bool,
368    /// Write ISO to a new output file
369    #[arg(short, long, value_name = "path")]
370    pub output: Option<String>,
371    /// ISO image
372    #[arg(value_name = "ISO")]
373    pub input: String,
374}
375
376#[derive(Debug, Parser)]
377pub struct IsoEmbedConfig {
378    /// Ignition config to embed [default: stdin]
379    #[arg(short, long, value_name = "path")]
380    pub config: Option<String>,
381    /// Overwrite an existing embedded Ignition config
382    #[arg(short, long)]
383    pub force: bool,
384    /// Write ISO to a new output file
385    #[arg(short, long, value_name = "path")]
386    pub output: Option<String>,
387    /// ISO image
388    #[arg(value_name = "ISO")]
389    pub input: String,
390}
391
392#[derive(Debug, Parser)]
393pub struct IsoShowConfig {
394    /// ISO image
395    #[arg(value_name = "ISO")]
396    pub input: String,
397}
398
399#[derive(Debug, Parser)]
400pub struct IsoRemoveConfig {
401    /// Write ISO to a new output file
402    #[arg(short, long, value_name = "path")]
403    pub output: Option<String>,
404    /// ISO image
405    #[arg(value_name = "ISO")]
406    pub input: String,
407}
408
409#[derive(Debug, Parser)]
410pub struct IsoIgnitionEmbedConfig {
411    /// Overwrite an existing Ignition config
412    #[arg(short, long)]
413    pub force: bool,
414    /// Ignition config to embed [default: stdin]
415    #[arg(short, long, value_name = "path")]
416    pub ignition_file: Option<String>,
417    /// Write ISO to a new output file
418    #[arg(short, long, value_name = "path")]
419    pub output: Option<String>,
420    /// ISO image
421    #[arg(value_name = "ISO")]
422    pub input: String,
423}
424
425#[derive(Debug, Parser)]
426pub struct IsoIgnitionShowConfig {
427    /// ISO image
428    #[arg(value_name = "ISO")]
429    pub input: String,
430}
431
432#[derive(Debug, Parser)]
433pub struct IsoIgnitionRemoveConfig {
434    /// Write ISO to a new output file
435    #[arg(short, long, value_name = "path")]
436    pub output: Option<String>,
437    /// ISO image
438    #[arg(value_name = "ISO")]
439    pub input: String,
440}
441
442#[derive(Debug, Parser)]
443pub struct IsoNetworkEmbedConfig {
444    /// NetworkManager keyfile to embed
445    // Required option. :-(  In future we might support other configuration
446    // sources.
447    #[arg(short, long, required = true, value_name = "path")]
448    pub keyfile: Vec<String>,
449    /// Overwrite existing network settings
450    #[arg(short, long)]
451    pub force: bool,
452    /// Write ISO to a new output file
453    #[arg(short, long, value_name = "path")]
454    pub output: Option<String>,
455    /// ISO image
456    #[arg(value_name = "ISO")]
457    pub input: String,
458}
459
460#[derive(Debug, Parser)]
461pub struct IsoNetworkExtractConfig {
462    /// Extract to directory instead of stdout
463    #[arg(short = 'C', long, value_name = "path")]
464    pub directory: Option<String>,
465    /// ISO image
466    #[arg(value_name = "ISO")]
467    pub input: String,
468}
469
470#[derive(Debug, Parser)]
471pub struct IsoNetworkRemoveConfig {
472    /// Write ISO to a new output file
473    #[arg(short, long, value_name = "path")]
474    pub output: Option<String>,
475    /// ISO image
476    #[arg(value_name = "ISO")]
477    pub input: String,
478}
479
480#[derive(Debug, Parser)]
481pub struct IsoKargsModifyConfig {
482    /// Kernel argument to append
483    #[arg(short, long, value_name = "KARG")]
484    pub append: Vec<String>,
485    /// Kernel argument to delete
486    #[arg(short, long, value_name = "KARG")]
487    pub delete: Vec<String>,
488    /// Kernel argument to replace
489    #[arg(short, long, value_name = "KARG=OLDVAL=NEWVAL")]
490    pub replace: Vec<String>,
491    /// Write ISO to a new output file
492    #[arg(short, long, value_name = "PATH")]
493    pub output: Option<String>,
494    /// ISO image
495    #[arg(value_name = "ISO")]
496    pub input: String,
497}
498
499#[derive(Debug, Parser)]
500pub struct IsoKargsResetConfig {
501    /// Write ISO to a new output file
502    #[arg(short, long, value_name = "PATH")]
503    pub output: Option<String>,
504    /// ISO image
505    #[arg(value_name = "ISO")]
506    pub input: String,
507}
508
509#[derive(Debug, Parser)]
510pub struct IsoKargsShowConfig {
511    /// Show default kernel args
512    #[arg(short, long)]
513    pub default: bool,
514    /// ISO image
515    #[arg(value_name = "ISO")]
516    pub input: String,
517}
518
519#[derive(Debug, Parser)]
520pub struct DevShowIsoConfig {
521    /// Show Ignition embed area parameters
522    #[arg(long, conflicts_with = "kargs")]
523    pub ignition: bool,
524    /// Show kargs embed area parameters
525    #[arg(long, conflicts_with = "ignition")]
526    pub kargs: bool,
527    /// ISO image
528    #[arg(value_name = "ISO")]
529    pub input: String,
530}
531
532#[derive(Debug, Parser)]
533pub struct IsoExtractPxeConfig {
534    /// ISO image
535    #[arg(value_name = "ISO")]
536    pub input: String,
537    /// Output directory
538    #[arg(short, long, value_name = "PATH", default_value = ".")]
539    pub output_dir: String,
540}
541
542#[derive(Debug, Parser)]
543pub struct IsoExtractMinimalIsoConfig {
544    /// ISO image
545    #[arg(value_name = "ISO")]
546    pub input: String,
547    /// Extract rootfs image as well
548    #[arg(long, value_name = "PATH")]
549    pub output_rootfs: Option<String>,
550    /// Minimal ISO output file
551    #[arg(value_name = "OUTPUT_ISO", default_value = "-")]
552    pub output: String,
553    /// Inject rootfs URL karg into minimal ISO
554    #[arg(long, value_name = "URL")]
555    pub rootfs_url: Option<String>,
556}
557
558#[derive(Debug, Parser)]
559pub struct PackMinimalIsoConfig {
560    /// ISO image
561    #[arg(value_name = "FULL_ISO")]
562    pub full: String,
563    /// Minimal ISO image
564    #[arg(value_name = "MINIMAL_ISO")]
565    pub minimal: String,
566    /// Delete minimal ISO after packing
567    #[arg(long)]
568    pub consume: bool,
569}
570
571#[derive(Debug, Parser)]
572pub struct IsoResetConfig {
573    /// Write ISO to a new output file
574    #[arg(short, long, value_name = "path")]
575    pub output: Option<String>,
576    /// ISO image
577    #[arg(value_name = "ISO")]
578    pub input: String,
579}
580
581#[derive(Debug, Parser)]
582// default usage line lists all mandatory options and so exceeds 80 characters
583#[command(override_usage = "coreos-installer pack osmet [OPTIONS]")]
584pub struct PackOsmetConfig {
585    /// Path to osmet file to write
586    // could output to stdout if missing?
587    #[arg(long, required = true, value_name = "FILE")]
588    pub output: String,
589    /// Expected SHA256 of block device
590    // XXX: rebase on top of
591    // https://github.com/coreos/coreos-installer/pull/178 and use the same
592    // type-digest format
593    #[arg(long, required = true, value_name = "SHA256")]
594    pub checksum: String,
595    /// Description of OS
596    #[arg(long, required = true, value_name = "TEXT")]
597    pub description: String,
598    /// Use worse compression, for development builds
599    #[arg(long)]
600    pub fast: bool,
601    /// Source device
602    #[arg(value_name = "DEV")]
603    pub device: String,
604}
605
606#[derive(Debug, Parser)]
607pub struct DevExtractOsmetConfig {
608    /// osmet file
609    #[arg(long, required = true, value_name = "PATH")]
610    pub osmet: String,
611    /// OSTree repo
612    #[arg(value_name = "PATH")]
613    pub repo: String,
614    /// Destination device
615    #[arg(value_name = "DEV")]
616    pub device: String,
617}
618
619#[derive(Debug, Parser)]
620pub struct DevShowFiemapConfig {
621    /// File to map
622    #[arg(value_name = "PATH")]
623    pub file: String,
624}
625
626#[derive(Debug, Parser)]
627pub struct PxeCustomizeConfig {
628    // Customizations
629    #[command(flatten)]
630    pub common: CommonCustomizeConfig,
631
632    // I/O configuration
633    /// Output file
634    #[arg(short, long, value_name = "path")]
635    pub output: String,
636    /// CoreOS live initramfs image
637    #[arg(value_name = "path")]
638    pub input: String,
639}
640
641#[derive(Debug, Parser)]
642pub struct PxeIgnitionWrapConfig {
643    /// Ignition config to wrap [default: stdin]
644    #[arg(short, long, value_name = "path")]
645    pub ignition_file: Option<String>,
646    /// Write to a file instead of stdout
647    #[arg(short, long, value_name = "path")]
648    pub output: Option<String>,
649}
650
651#[derive(Debug, Parser)]
652pub struct PxeIgnitionUnwrapConfig {
653    /// initrd image [default: stdin]
654    #[arg(value_name = "initrd")]
655    pub input: Option<String>,
656}
657
658#[derive(Debug, Parser)]
659pub struct PxeNetworkWrapConfig {
660    /// NetworkManager keyfile to embed
661    // Required option. :-(  In future we might support other configuration
662    // sources.
663    #[arg(short, long, required = true, value_name = "path")]
664    pub keyfile: Vec<String>,
665    /// Write to a file instead of stdout
666    #[arg(short, long, value_name = "path")]
667    pub output: Option<String>,
668}
669
670#[derive(Debug, Parser)]
671pub struct PxeNetworkUnwrapConfig {
672    /// Extract to directory instead of stdout
673    #[arg(short = 'C', long, value_name = "path")]
674    pub directory: Option<String>,
675    /// initrd image [default: stdin]
676    #[arg(value_name = "initrd")]
677    pub input: Option<String>,
678}
679
680#[derive(Debug, Parser)]
681pub struct DevShowInitrdConfig {
682    /// initrd image ("-" for stdin)
683    #[arg(value_name = "initrd")]
684    pub input: String,
685    /// Files or globs to list
686    #[arg(value_name = "glob")]
687    pub filter: Vec<String>,
688}
689
690#[derive(Debug, Parser)]
691pub struct DevExtractInitrdConfig {
692    /// Output directory
693    #[arg(short = 'C', long, value_name = "path", default_value = ".")]
694    pub directory: String,
695    /// List extracted contents
696    #[arg(short, long)]
697    pub verbose: bool,
698    /// initrd image ("-" for stdin)
699    #[arg(value_name = "initrd")]
700    pub input: String,
701    /// Files or globs to list
702    #[arg(value_name = "glob")]
703    pub filter: Vec<String>,
704}
705
706#[cfg(feature = "docgen")]
707#[derive(Debug, Parser)]
708pub struct PackManConfig {
709    /// Output directory
710    #[arg(short = 'C', long, value_name = "path", default_value = ".")]
711    pub directory: String,
712}
713
714#[cfg(feature = "docgen")]
715#[derive(Debug, Parser)]
716pub struct PackExampleConfigConfig {}
717
718#[cfg(test)]
719mod test {
720    use super::*;
721    use clap::CommandFactory;
722
723    #[test]
724    fn clap_app() {
725        Cmd::command().debug_assert()
726    }
727}