/* Copyright © 2025 Wiki Cafe This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package cmd import ( "fmt" "os" "path/filepath" "strings" "git.coopcloud.tech/wiki-cafe/member-console/internal/config" "git.coopcloud.tech/wiki-cafe/member-console/internal/embeds" "git.coopcloud.tech/wiki-cafe/member-console/internal/integrations" "github.com/spf13/cobra" ) // initCmd represents the init command var initCmd = &cobra.Command{ Use: "init [path]", Short: "Create the directory structure needed for the member-console", Long: `The init command creates the directory structure and files needed for the member-console web application in the current directory. A different directory can be specified using the --dir flag.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { // Create the directories and files needed for the member-console web application targetDir := args[0] // Create the target directory if it doesn't exist if err := os.MkdirAll(targetDir, 0755); err != nil { fmt.Fprintf(os.Stderr, "Error creating target directory: %v\n", err) os.Exit(1) } // Create the templates, static, and assets directories directories := []string{"templates", "static", "assets"} for _, dir := range directories { dirPath := filepath.Join(targetDir, dir) if err := os.MkdirAll(dirPath, 0755); err != nil { fmt.Fprintf(os.Stderr, "Error creating %s directory: %v\n", dir, err) os.Exit(1) } } // Write the embedded starter config template plus one generated // section per installed integration that declares configuration // (console-init spec: integration sections are generated from each // ConfigSpec, never hand-maintained). configFilePath := filepath.Join(targetDir, "mc-config.yaml") configTemplate, err := embeds.Config.ReadFile("mc-config.yaml") if err != nil { fmt.Fprintf(os.Stderr, "Error reading embedded config template: %v\n", err) os.Exit(1) } scaffold := append(configTemplate, []byte(integrationConfigSections())...) if err := os.WriteFile(configFilePath, scaffold, 0o644); err != nil { fmt.Fprintf(os.Stderr, "Error writing config file: %v\n", err) os.Exit(1) } fmt.Printf("Created config file at %s (edit it, then run `member-console start`)\n", configFilePath) fmt.Printf("Project initialized at %s\n", targetDir) }, } // integrationConfigSections renders one commented scaffold section per // registered integration that declares configuration, from its ConfigSpec: // every key with its usage text, its declared default when one exists, and a // "-file" variant note for secrets. Comment lines only, so the scaffold stays // valid YAML regardless of key types, and a new integration appears here with // no change to this file or the embedded template. func integrationConfigSections() string { var b strings.Builder for _, integ := range integrations.All() { cp, ok := integ.(config.ConfigProvider) if !ok { continue } displayName := integ.Provider().ProviderManifest().DisplayName fmt.Fprintf(&b, "\n# --- Optional: %s integration (uncomment and fill in to enable) ---\n", displayName) for _, key := range cp.ConfigSpec() { fmt.Fprintf(&b, "# %s\n", key.Usage) switch def := key.Default.(type) { case nil: fmt.Fprintf(&b, "# %s: \"\"\n", key.Name) case []string: fmt.Fprintf(&b, "# %s: []\n", key.Name) case bool: fmt.Fprintf(&b, "# %s: %v\n", key.Name, def) case string: fmt.Fprintf(&b, "# %s: %q\n", key.Name, def) default: // Durations and any future scalar render via %v inside // quotes; viper parses the string form back. fmt.Fprintf(&b, "# %s: \"%v\"\n", key.Name, def) } if key.Secret { fmt.Fprintf(&b, "# %s-file: \"\" # alternative: read the secret from a file path\n", key.Name) } } } return b.String() } func init() { rootCmd.AddCommand(initCmd) }