Skip to main content

changeset_operations/operations/
init.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use changeset_manifest::{InitConfig, MetadataSection};
5use changeset_project::{CargoProject, ProjectKind, RootChangesetConfig};
6
7use crate::Result;
8use crate::traits::{
9    ChangelogSettingsInput, GitSettingsInput, InitInteractionProvider, ManifestMetadataWriter,
10    ProjectContext, ProjectProvider, VersionSettingsInput,
11};
12
13/// Configuration sources have the following precedence (highest to lowest):
14/// 1. `defaults: true` - Uses all default values, ignores other fields
15/// 2. Explicit `git_config`, `changelog_config`, `version_config` fields
16/// 3. Interactive prompts via `InitInteractionProvider` (only if no explicit config)
17#[derive(Debug, Default)]
18pub struct InitInput {
19    pub defaults: bool,
20    pub git_config: Option<GitSettingsInput>,
21    pub changelog_config: Option<ChangelogSettingsInput>,
22    pub version_config: Option<VersionSettingsInput>,
23}
24
25#[derive(Debug)]
26pub struct InitPlan {
27    pub changeset_dir: PathBuf,
28    pub dir_exists: bool,
29    pub gitkeep_exists: bool,
30    pub metadata_section: MetadataSection,
31    pub config: InitConfig,
32}
33
34#[derive(Debug)]
35#[must_use]
36pub struct InitOutput {
37    pub changeset_dir: PathBuf,
38    pub created_dir: bool,
39    pub created_gitkeep: bool,
40    pub wrote_config: bool,
41    pub config_location: Option<MetadataSection>,
42}
43
44pub struct InitOperation<P, M = (), I = ()> {
45    project_provider: P,
46    manifest_writer: Option<M>,
47    interaction_provider: Option<I>,
48}
49
50impl<P> InitOperation<P, (), ()>
51where
52    P: ProjectProvider,
53{
54    pub fn new(project_provider: P) -> Self {
55        Self {
56            project_provider,
57            manifest_writer: None,
58            interaction_provider: None,
59        }
60    }
61}
62
63impl<P, M, I> InitOperation<P, M, I>
64where
65    P: ProjectProvider,
66{
67    #[must_use]
68    pub fn with_manifest_writer<M2>(self, writer: M2) -> InitOperation<P, M2, I> {
69        InitOperation {
70            project_provider: self.project_provider,
71            manifest_writer: Some(writer),
72            interaction_provider: self.interaction_provider,
73        }
74    }
75
76    #[must_use]
77    pub fn with_interaction_provider<I2>(self, provider: I2) -> InitOperation<P, M, I2> {
78        InitOperation {
79            project_provider: self.project_provider,
80            manifest_writer: self.manifest_writer,
81            interaction_provider: Some(provider),
82        }
83    }
84}
85
86impl<P, M, I> InitOperation<P, M, I>
87where
88    P: ProjectProvider,
89    M: ManifestMetadataWriter,
90    I: InitInteractionProvider,
91{
92    /// Prepares an initialization plan by collecting all configuration without
93    /// performing any file system operations.
94    ///
95    /// # Errors
96    ///
97    /// Returns an error if the project cannot be discovered or configuration
98    /// cannot be built (e.g., interactive prompts fail).
99    pub fn prepare(&self, start_path: &Path, input: &InitInput) -> Result<InitPlan> {
100        let project = self.project_provider.discover_project(start_path)?;
101        let (root_config, _) = self.project_provider.load_configs(&project)?;
102
103        let context = ProjectContext {
104            is_single_package: *project.kind() == ProjectKind::SinglePackage,
105        };
106        let config = self.build_config(input, context)?;
107
108        Ok(build_init_plan(&project, &root_config, config))
109    }
110
111    /// Executes the init operation using a pre-built plan.
112    ///
113    /// # Errors
114    ///
115    /// Returns an error if the changeset directory cannot be created or
116    /// configuration cannot be written.
117    pub fn execute_plan(&self, start_path: &Path, plan: &InitPlan) -> Result<InitOutput> {
118        let project = self.project_provider.discover_project(start_path)?;
119        let (root_config, _) = self.project_provider.load_configs(&project)?;
120
121        let changeset_dir = self
122            .project_provider
123            .ensure_changeset_dir(&project, &root_config)?;
124
125        let gitkeep_path = changeset_dir.join(".gitkeep");
126        if !plan.gitkeep_exists {
127            fs::write(&gitkeep_path, "")?;
128        }
129
130        let wrote_config = if let Some(ref writer) = self.manifest_writer {
131            if plan.config.is_empty() {
132                false
133            } else {
134                let manifest_path = project.root().join("Cargo.toml");
135                writer.write_metadata(&manifest_path, plan.metadata_section, &plan.config)?;
136                true
137            }
138        } else {
139            false
140        };
141
142        Ok(InitOutput {
143            changeset_dir,
144            created_dir: !plan.dir_exists,
145            created_gitkeep: !plan.gitkeep_exists,
146            wrote_config,
147            config_location: if wrote_config {
148                Some(plan.metadata_section)
149            } else {
150                None
151            },
152        })
153    }
154
155    /// Executes the full init operation (prepare + execute).
156    ///
157    /// # Errors
158    ///
159    /// Returns an error if the project cannot be discovered, the changeset
160    /// directory cannot be created, or configuration cannot be written.
161    pub fn execute(&self, start_path: &Path, input: &InitInput) -> Result<InitOutput> {
162        let plan = self.prepare(start_path, input)?;
163        self.execute_plan(start_path, &plan)
164    }
165
166    fn build_config(&self, input: &InitInput, context: ProjectContext) -> Result<InitConfig> {
167        let mut config = build_config_from_input(input, context);
168
169        if config.is_empty() {
170            if let Some(ref provider) = self.interaction_provider {
171                if let Some(git) = provider.configure_git_settings(context)? {
172                    config.commit = Some(git.commit);
173                    config.tags = Some(git.tags);
174                    config.keep_changesets = Some(git.keep_changesets);
175                    config.tag_format = Some(git.tag_format);
176                }
177
178                if let Some(changelog) = provider.configure_changelog_settings(context)? {
179                    config.changelog = Some(changelog.changelog);
180                    config.comparison_links = Some(changelog.comparison_links);
181                }
182
183                if let Some(version) = provider.configure_version_settings()? {
184                    config.zero_version_behavior = Some(version.zero_version_behavior);
185                }
186            }
187        }
188
189        Ok(config)
190    }
191}
192
193fn build_init_plan(
194    project: &CargoProject,
195    root_config: &RootChangesetConfig,
196    config: InitConfig,
197) -> InitPlan {
198    let changeset_dir_path = root_config.changeset_dir();
199    let full_changeset_dir = project.root().join(changeset_dir_path);
200    let dir_exists = full_changeset_dir.exists();
201    let gitkeep_exists = full_changeset_dir.join(".gitkeep").exists();
202
203    let metadata_section = match project.kind() {
204        ProjectKind::VirtualWorkspace | ProjectKind::WorkspaceWithRoot => {
205            MetadataSection::Workspace
206        }
207        ProjectKind::SinglePackage => MetadataSection::Package,
208    };
209
210    InitPlan {
211        changeset_dir: full_changeset_dir,
212        dir_exists,
213        gitkeep_exists,
214        metadata_section,
215        config,
216    }
217}
218
219/// Builds the default configuration with all options set to their defaults.
220///
221/// The tag format default varies by project type:
222/// - Single package: `version-only` (e.g., `v1.0.0`)
223/// - Workspace: `crate-prefixed` (e.g., `crate-name@1.0.0`)
224#[must_use]
225pub(crate) fn build_default_config(context: ProjectContext) -> InitConfig {
226    let tag_format = if context.is_single_package {
227        changeset_manifest::TagFormat::VersionOnly
228    } else {
229        changeset_manifest::TagFormat::CratePrefixed
230    };
231
232    InitConfig {
233        commit: Some(true),
234        tags: Some(true),
235        keep_changesets: Some(false),
236        tag_format: Some(tag_format),
237        changelog: Some(changeset_manifest::ChangelogLocation::default()),
238        comparison_links: Some(changeset_manifest::ComparisonLinks::default()),
239        zero_version_behavior: Some(changeset_manifest::ZeroVersionBehavior::default()),
240    }
241}
242
243#[must_use]
244pub fn build_config_from_input(input: &InitInput, context: ProjectContext) -> InitConfig {
245    if input.defaults {
246        return build_default_config(context);
247    }
248
249    let mut config = InitConfig::default();
250
251    if let Some(ref git) = input.git_config {
252        config.commit = Some(git.commit);
253        config.tags = Some(git.tags);
254        config.keep_changesets = Some(git.keep_changesets);
255        config.tag_format = Some(git.tag_format);
256    }
257
258    if let Some(ref changelog) = input.changelog_config {
259        config.changelog = Some(changelog.changelog);
260        config.comparison_links = Some(changelog.comparison_links);
261    }
262
263    if let Some(ref version) = input.version_config {
264        config.zero_version_behavior = Some(version.zero_version_behavior);
265    }
266
267    config
268}
269
270impl<P> InitOperation<P, (), ()>
271where
272    P: ProjectProvider,
273{
274    /// Prepares a simple initialization plan without configuration.
275    ///
276    /// # Errors
277    ///
278    /// Returns an error if the project cannot be discovered.
279    pub fn prepare_simple(&self, start_path: &Path) -> Result<InitPlan> {
280        let project = self.project_provider.discover_project(start_path)?;
281        let (root_config, _) = self.project_provider.load_configs(&project)?;
282
283        Ok(build_init_plan(
284            &project,
285            &root_config,
286            InitConfig::default(),
287        ))
288    }
289
290    /// Executes the simple init operation using a pre-built plan.
291    ///
292    /// # Errors
293    ///
294    /// Returns an error if the changeset directory cannot be created.
295    pub fn execute_simple_plan(&self, start_path: &Path, plan: &InitPlan) -> Result<InitOutput> {
296        let project = self.project_provider.discover_project(start_path)?;
297        let (root_config, _) = self.project_provider.load_configs(&project)?;
298
299        let changeset_dir = self
300            .project_provider
301            .ensure_changeset_dir(&project, &root_config)?;
302
303        let gitkeep_path = changeset_dir.join(".gitkeep");
304        if !plan.gitkeep_exists {
305            fs::write(&gitkeep_path, "")?;
306        }
307
308        Ok(InitOutput {
309            changeset_dir,
310            created_dir: !plan.dir_exists,
311            created_gitkeep: !plan.gitkeep_exists,
312            wrote_config: false,
313            config_location: None,
314        })
315    }
316
317    /// Simple execute method for backward compatibility when no manifest writer
318    /// or interaction provider is configured.
319    ///
320    /// # Errors
321    ///
322    /// Returns an error if the project cannot be discovered or the changeset
323    /// directory cannot be created.
324    pub fn execute_simple(&self, start_path: &Path) -> Result<InitOutput> {
325        let plan = self.prepare_simple(start_path)?;
326        self.execute_simple_plan(start_path, &plan)
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use std::sync::Arc;
333
334    use changeset_manifest::{ChangelogLocation, ComparisonLinks, TagFormat, ZeroVersionBehavior};
335
336    use super::*;
337    use crate::mocks::{MockInitInteractionProvider, MockManifestWriter, MockProjectProvider};
338
339    #[test]
340    fn returns_changeset_dir_path() {
341        let dir = tempfile::tempdir().expect("create temp dir");
342        let changeset_dir = dir.path().join(".changeset");
343        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
344
345        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
346            .with_changeset_dir(changeset_dir.clone());
347
348        let operation = InitOperation::new(project_provider);
349
350        let result = operation
351            .execute_simple(Path::new("/any"))
352            .expect("InitOperation failed for single-package project");
353
354        assert_eq!(result.changeset_dir, changeset_dir);
355    }
356
357    #[test]
358    fn works_with_workspace_projects() {
359        let dir = tempfile::tempdir().expect("create temp dir");
360        let changeset_dir = dir.path().join(".changeset");
361        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
362
363        let project_provider =
364            MockProjectProvider::workspace(vec![("crate-a", "1.0.0"), ("crate-b", "2.0.0")])
365                .with_changeset_dir(changeset_dir.clone());
366
367        let operation = InitOperation::new(project_provider);
368
369        let result = operation
370            .execute_simple(Path::new("/any"))
371            .expect("InitOperation failed for workspace project");
372
373        assert!(
374            result
375                .changeset_dir
376                .to_string_lossy()
377                .contains(".changeset")
378        );
379    }
380
381    #[test]
382    fn creates_gitkeep_file() {
383        let dir = tempfile::tempdir().expect("create temp dir");
384        let changeset_dir = dir.path().join(".changeset");
385        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
386
387        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
388            .with_changeset_dir(changeset_dir.clone());
389
390        let operation = InitOperation::new(project_provider);
391
392        let result = operation
393            .execute_simple(Path::new("/any"))
394            .expect("InitOperation failed");
395
396        assert!(result.created_gitkeep);
397        assert!(changeset_dir.join(".gitkeep").exists());
398    }
399
400    #[test]
401    fn creates_gitkeep_even_when_dir_exists() {
402        let dir = tempfile::tempdir().expect("create temp dir");
403        let changeset_dir = dir.path().join(".changeset");
404        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
405
406        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
407            .with_changeset_dir(changeset_dir.clone());
408
409        let operation = InitOperation::new(project_provider);
410        let result = operation
411            .execute_simple(Path::new("/any"))
412            .expect("InitOperation failed");
413
414        assert!(!result.created_dir);
415        assert!(result.created_gitkeep);
416        assert!(changeset_dir.join(".gitkeep").exists());
417    }
418
419    #[test]
420    fn writes_config_with_defaults() {
421        let dir = tempfile::tempdir().expect("create temp dir");
422        let changeset_dir = dir.path().join(".changeset");
423        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
424
425        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
426            .with_changeset_dir(changeset_dir.clone());
427        let manifest_writer = Arc::new(MockManifestWriter::new());
428        let interaction_provider = Arc::new(MockInitInteractionProvider::new());
429
430        let operation = InitOperation::new(project_provider)
431            .with_manifest_writer(Arc::clone(&manifest_writer))
432            .with_interaction_provider(Arc::clone(&interaction_provider));
433
434        let input = InitInput {
435            defaults: true,
436            ..Default::default()
437        };
438
439        let result = operation
440            .execute(Path::new("/any"), &input)
441            .expect("InitOperation failed");
442
443        assert!(result.wrote_config);
444        assert_eq!(result.config_location, Some(MetadataSection::Package));
445
446        let written = manifest_writer.written_metadata();
447        assert_eq!(written.len(), 1);
448        let (_, section, config) = &written[0];
449        assert_eq!(*section, MetadataSection::Package);
450        assert_eq!(config.commit, Some(true));
451        assert_eq!(config.tags, Some(true));
452        assert_eq!(config.keep_changesets, Some(false));
453    }
454
455    #[test]
456    fn writes_config_from_input() {
457        let dir = tempfile::tempdir().expect("create temp dir");
458        let changeset_dir = dir.path().join(".changeset");
459        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
460
461        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
462            .with_changeset_dir(changeset_dir.clone());
463        let manifest_writer = Arc::new(MockManifestWriter::new());
464        let interaction_provider = Arc::new(MockInitInteractionProvider::new());
465
466        let operation = InitOperation::new(project_provider)
467            .with_manifest_writer(Arc::clone(&manifest_writer))
468            .with_interaction_provider(Arc::clone(&interaction_provider));
469
470        let input = InitInput {
471            defaults: false,
472            git_config: Some(GitSettingsInput {
473                commit: false,
474                tags: true,
475                keep_changesets: true,
476                tag_format: TagFormat::CratePrefixed,
477            }),
478            changelog_config: Some(ChangelogSettingsInput {
479                changelog: ChangelogLocation::PerPackage,
480                comparison_links: ComparisonLinks::Enabled,
481            }),
482            version_config: Some(VersionSettingsInput {
483                zero_version_behavior: ZeroVersionBehavior::AutoPromoteOnMajor,
484            }),
485        };
486
487        let result = operation
488            .execute(Path::new("/any"), &input)
489            .expect("InitOperation failed");
490
491        assert!(result.wrote_config);
492
493        let written = manifest_writer.written_metadata();
494        assert_eq!(written.len(), 1);
495        let (_, _, config) = &written[0];
496        assert_eq!(config.commit, Some(false));
497        assert_eq!(config.tags, Some(true));
498        assert_eq!(config.keep_changesets, Some(true));
499        assert_eq!(config.tag_format, Some(TagFormat::CratePrefixed));
500        assert_eq!(config.changelog, Some(ChangelogLocation::PerPackage));
501        assert_eq!(config.comparison_links, Some(ComparisonLinks::Enabled));
502        assert_eq!(
503            config.zero_version_behavior,
504            Some(ZeroVersionBehavior::AutoPromoteOnMajor)
505        );
506    }
507
508    #[test]
509    fn writes_partial_config() {
510        let dir = tempfile::tempdir().expect("create temp dir");
511        let changeset_dir = dir.path().join(".changeset");
512        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
513
514        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
515            .with_changeset_dir(changeset_dir.clone());
516        let manifest_writer = Arc::new(MockManifestWriter::new());
517        let interaction_provider = Arc::new(MockInitInteractionProvider::new());
518
519        let operation = InitOperation::new(project_provider)
520            .with_manifest_writer(Arc::clone(&manifest_writer))
521            .with_interaction_provider(Arc::clone(&interaction_provider));
522
523        let input = InitInput {
524            defaults: false,
525            git_config: Some(GitSettingsInput {
526                commit: true,
527                tags: false,
528                keep_changesets: false,
529                tag_format: TagFormat::VersionOnly,
530            }),
531            changelog_config: None,
532            version_config: None,
533        };
534
535        let result = operation
536            .execute(Path::new("/any"), &input)
537            .expect("InitOperation failed");
538
539        assert!(result.wrote_config);
540
541        let written = manifest_writer.written_metadata();
542        assert_eq!(written.len(), 1);
543        let (_, _, config) = &written[0];
544        assert_eq!(config.commit, Some(true));
545        assert_eq!(config.tags, Some(false));
546        assert!(config.changelog.is_none());
547        assert!(config.zero_version_behavior.is_none());
548    }
549
550    #[test]
551    fn interactive_mode_collects_all_groups() {
552        let dir = tempfile::tempdir().expect("create temp dir");
553        let changeset_dir = dir.path().join(".changeset");
554        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
555
556        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
557            .with_changeset_dir(changeset_dir.clone());
558        let manifest_writer = Arc::new(MockManifestWriter::new());
559        let interaction_provider = Arc::new(MockInitInteractionProvider::all_defaults());
560
561        let operation = InitOperation::new(project_provider)
562            .with_manifest_writer(Arc::clone(&manifest_writer))
563            .with_interaction_provider(Arc::clone(&interaction_provider));
564
565        let input = InitInput::default();
566
567        let result = operation
568            .execute(Path::new("/any"), &input)
569            .expect("InitOperation failed");
570
571        assert!(result.wrote_config);
572
573        let written = manifest_writer.written_metadata();
574        assert_eq!(written.len(), 1);
575        let (_, _, config) = &written[0];
576        assert!(config.commit.is_some());
577        assert!(config.tags.is_some());
578        assert!(config.changelog.is_some());
579        assert!(config.zero_version_behavior.is_some());
580    }
581
582    #[test]
583    fn interactive_mode_skips_declined_groups() {
584        let dir = tempfile::tempdir().expect("create temp dir");
585        let changeset_dir = dir.path().join(".changeset");
586        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
587
588        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
589            .with_changeset_dir(changeset_dir.clone());
590        let manifest_writer = Arc::new(MockManifestWriter::new());
591        let interaction_provider = Arc::new(
592            MockInitInteractionProvider::new()
593                .with_git_settings(Some(GitSettingsInput::default()))
594                .with_changelog_settings(None)
595                .with_version_settings(None),
596        );
597
598        let operation = InitOperation::new(project_provider)
599            .with_manifest_writer(Arc::clone(&manifest_writer))
600            .with_interaction_provider(Arc::clone(&interaction_provider));
601
602        let input = InitInput::default();
603
604        let result = operation
605            .execute(Path::new("/any"), &input)
606            .expect("InitOperation failed");
607
608        assert!(result.wrote_config);
609
610        let written = manifest_writer.written_metadata();
611        assert_eq!(written.len(), 1);
612        let (_, _, config) = &written[0];
613        assert!(config.commit.is_some());
614        assert!(config.changelog.is_none());
615        assert!(config.zero_version_behavior.is_none());
616    }
617
618    #[test]
619    fn skips_config_write_when_empty() {
620        let dir = tempfile::tempdir().expect("create temp dir");
621        let changeset_dir = dir.path().join(".changeset");
622        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
623
624        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
625            .with_changeset_dir(changeset_dir.clone());
626        let manifest_writer = Arc::new(MockManifestWriter::new());
627        let interaction_provider = Arc::new(MockInitInteractionProvider::all_skipped());
628
629        let operation = InitOperation::new(project_provider)
630            .with_manifest_writer(Arc::clone(&manifest_writer))
631            .with_interaction_provider(Arc::clone(&interaction_provider));
632
633        let input = InitInput::default();
634
635        let result = operation
636            .execute(Path::new("/any"), &input)
637            .expect("InitOperation failed");
638
639        assert!(!result.wrote_config);
640        assert!(result.config_location.is_none());
641
642        let written = manifest_writer.written_metadata();
643        assert!(written.is_empty());
644    }
645
646    #[test]
647    fn workspace_uses_workspace_metadata() {
648        let dir = tempfile::tempdir().expect("create temp dir");
649        let changeset_dir = dir.path().join(".changeset");
650        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
651
652        let project_provider =
653            MockProjectProvider::workspace(vec![("crate-a", "1.0.0"), ("crate-b", "2.0.0")])
654                .with_changeset_dir(changeset_dir.clone());
655        let manifest_writer = Arc::new(MockManifestWriter::new());
656        let interaction_provider = Arc::new(MockInitInteractionProvider::new());
657
658        let operation = InitOperation::new(project_provider)
659            .with_manifest_writer(Arc::clone(&manifest_writer))
660            .with_interaction_provider(Arc::clone(&interaction_provider));
661
662        let input = InitInput {
663            defaults: true,
664            ..Default::default()
665        };
666
667        let result = operation
668            .execute(Path::new("/any"), &input)
669            .expect("InitOperation failed");
670
671        assert!(result.wrote_config);
672        assert_eq!(result.config_location, Some(MetadataSection::Workspace));
673
674        let written = manifest_writer.written_metadata();
675        assert_eq!(written.len(), 1);
676        let (_, section, _) = &written[0];
677        assert_eq!(*section, MetadataSection::Workspace);
678    }
679
680    #[test]
681    fn single_package_uses_package_metadata() {
682        let dir = tempfile::tempdir().expect("create temp dir");
683        let changeset_dir = dir.path().join(".changeset");
684        std::fs::create_dir_all(&changeset_dir).expect("create changeset dir");
685
686        let project_provider = MockProjectProvider::single_package("my-crate", "1.0.0")
687            .with_changeset_dir(changeset_dir.clone());
688        let manifest_writer = Arc::new(MockManifestWriter::new());
689        let interaction_provider = Arc::new(MockInitInteractionProvider::new());
690
691        let operation = InitOperation::new(project_provider)
692            .with_manifest_writer(Arc::clone(&manifest_writer))
693            .with_interaction_provider(Arc::clone(&interaction_provider));
694
695        let input = InitInput {
696            defaults: true,
697            ..Default::default()
698        };
699
700        let result = operation
701            .execute(Path::new("/any"), &input)
702            .expect("InitOperation failed");
703
704        assert!(result.wrote_config);
705        assert_eq!(result.config_location, Some(MetadataSection::Package));
706
707        let written = manifest_writer.written_metadata();
708        assert_eq!(written.len(), 1);
709        let (_, section, _) = &written[0];
710        assert_eq!(*section, MetadataSection::Package);
711    }
712}