Files
member-console/cmd/init.go
T
cgalo5758 6dbce6140f Type the ConfigSpec seam and move the connect target to core
Register bool and duration ConfigSpec keys from the Default's type, move
fedwiki's four sync knobs and discourse's two into their integrations'
ConfigSpecs, and replace core's read of fedwiki-custom-domain-target
with a core domains-connect-target key resolved once and threaded
through server and worker config.

Generate init's optional-integration scaffold sections from each
registered ConfigSpec instead of the hand-maintained list, and reword
the Temporal boot warning generically.

Archives the integration-config-parity change; status bookkeeping and
the verify-skill doc follow with the test-stack commit.
2026-08-01 04:13:48 -05:00

123 lines
4.4 KiB
Go

/*
Copyright © 2025 Wiki Cafe <mail@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 <http://www.gnu.org/licenses/>.
*/
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)
}