cmd

package
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2025 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Copyright 2025 PRAS

This command implements the Prasmoid CLI update functionality using a remote update script. Instead of embedding complex update logic directly in the Go code, which would increase the binary size by approximately 2MB, this approach leverages a lightweight shell script hosted on GitHub. This design choice ensures that Prasmoid remains lightweight while maintaining robust update capabilities.

Copyright 2025 PRAS

Index

Constants

This section is empty.

Variables

View Source
var BuildCmd = &cobra.Command{
	Use:   "build",
	Short: "Build the project",
	Long:  "Package the project files and generate the deployable .plasmoid archive.",
	Run: func(cmd *cobra.Command, args []string) {
		if !utils.IsValidPlasmoid() {
			color.Red("Current directory is not a valid plasmoid.")
			return
		}

		color.Cyan("→ Compiling translations...")
		if err := CompileI18n(ConfigRC, false); err != nil {
			color.Red("Failed to compile translations: %v", err)

			color.Yellow("Continuing build without translations...")
		} else {
			color.Green("Translations compiled successfully.")
		}

		color.Cyan("→ Starting plasmoid build...")
		plasmoidID, ierr := utils.GetDataFromMetadata("Id")
		version, verr := utils.GetDataFromMetadata("Version")
		if ierr != nil || verr != nil {
			color.Red("Failed to get plasmoid version: %v", fmt.Errorf("%v or %v", ierr, verr))
			return
		}
		zipFileName := plasmoidID.(string) + "-" + version.(string) + ".plasmoid"

		if err := os.RemoveAll(buildOutputDir); err != nil {
			color.Red("Failed to clean build dir: %v", err)
			return
		}
		if err := os.MkdirAll(buildOutputDir, 0755); err != nil {
			color.Red("Failed to create build dir: %v", err)
			return
		}

		outFile, err := os.Create(filepath.Join(buildOutputDir, zipFileName))
		if err != nil {
			color.Red("Failed to create zip file: %v", err)
			return
		}
		defer outFile.Close()

		zipWriter := zip.NewWriter(outFile)
		defer zipWriter.Close()

		if err := addFileToZip(zipWriter, "metadata.json"); err != nil {
			color.Red("Error adding metadata.json: %v", err)
			return
		}

		if err := addDirToZip(zipWriter, "contents"); err != nil {
			color.Red("Error adding contents/: %v", err)
			return
		}

		color.Green("Build complete: %s", color.YellowString(filepath.Join(buildOutputDir, zipFileName)))
	},
}

buildCmd represents the build command

View Source
var ChangesFolder string = ".changes"
View Source
var ChangesetAddCmd = &cobra.Command{
	Use:   "add",
	Short: "Create a new changeset",
	Run: func(cmd *cobra.Command, args []string) {
		bump = strings.ToLower(bump)
		version, _ := utils.GetDataFromMetadata("Version")
		id, _ := utils.GetDataFromMetadata("Id")
		var next string

		bumpLabels := make(map[string]string)
		for _, bumpType := range []string{"patch", "minor", "major"} {
			if nextVer, err := GetNextVersion(version.(string), bumpType); err == nil {
				bumpLabels[bumpType] = fmt.Sprintf("%s (%s)", bumpType, nextVer)
			} else {
				color.Red("Failed to compute next version for %s: %v", bumpType, err)
				return
			}
		}

		options := []string{bumpLabels["patch"], bumpLabels["minor"], bumpLabels["major"]}

		if !validBumps[bump] {
			var selected string
			err := survey.AskOne(&survey.Select{
				Message: "Select version bump:",
				Options: options,
				Default: bumpLabels["patch"],
			}, &selected)

			if err != nil {
				color.Red("Failed to prompt for version bump: %v", err)
				return
			}

			parts := strings.SplitN(selected, " ", 2)
			bump = parts[0]
		}

		if next == "" {
			var err error
			next, err = GetNextVersion(version.(string), bump)
			if err != nil {
				color.Red("Failed to compute next version for %s: %v", bump, err)
				return
			}
		}

		if summary == "" {
			var err error
			summary, err = openEditor()
			if err != nil || strings.TrimSpace(summary) == "" {

				prompt := &survey.Input{
					Message: "Enter changelog summary:",
				}
				err = survey.AskOne(prompt, &summary)

				if err != nil {
					color.Red("Failed to prompt for changelog summary: %v", err)
					return
				}
			}
		}

		if strings.TrimSpace(summary) == "" {
			color.Red("Empty changelog. Aborting.")
			return
		}

		if err := os.MkdirAll(ChangesFolder, 0755); err != nil {
			color.Red("Failed to create changes directory: %v", err)
			return
		}

		now := time.Now()
		timestamp := now.Format("2006-01-02-15-04")
		dateOnly := now.Format("2006-01-02")
		filename := filepath.Join(ChangesFolder, fmt.Sprintf("%s-%s.mdx", timestamp, bump))

		content := fmt.Sprintf(`---
id: %s
bump: %s
next: %s
date: %s
---		

%s
`, id, bump, next, dateOnly, summary)

		if err := os.WriteFile(filename, []byte(content), 0644); err != nil {
			color.Red("Failed to write changeset: %v", err)
			return
		}

		var applyChanges bool
		applyConfirmPrompt := &survey.Confirm{
			Message: color.YellowString("Changeset created. Would you like to apply it now?"),
			Default: false,
		}
		if err := survey.AskOne(applyConfirmPrompt, &applyChanges); err != nil {
			return
		}
		if applyChanges {
			ApplyChanges()
		}
	},
}
View Source
var ChangesetApplyCmd = &cobra.Command{
	Use:   "apply",
	Short: "Apply all .mdx changesets from the .changes directory",
	Run: func(cmd *cobra.Command, args []string) {
		ApplyChanges()
	},
}

changesetApplyCmd represents the changesetApply command

View Source
var ChangesetRootCmd = &cobra.Command{
	Use:   "changeset",
	Short: "Manage release lifecycle commands",
	Long:  "Handle creating, applying, and managing changesets and version bumps for the plasmoid.",
}

changeset/rootCmd represents the changeset/root command

View Source
var CliConfig struct {
	Version string `yaml:"version"`
	Name    string `yaml:"name"`
	Author  string `yaml:"author"`
	License string `yaml:"license"`
	Github  string `yaml:"github"`
}
View Source
var CommandsAddCmd = &cobra.Command{
	Use:   "add",
	Short: "Add a custom command",
	Long:  "Add a custom command to the project.",
	Run: func(cmd *cobra.Command, args []string) {
		invalidChars := regexp.MustCompile(`[\\/:*?"<>|\s@]`)

		if strings.TrimSpace(commandName) == "" || invalidChars.MatchString(commandName) {
			namePrompt := &survey.Input{
				Message: "Command name:",
			}
			if err := survey.AskOne(namePrompt, &commandName, survey.WithValidator(func(ans interface{}) error {
				name := ans.(string)
				if name == "" {
					return errors.New("command name cannot be empty")
				}

				if invalidChars.MatchString(name) {
					return errors.New("invalid characters in command name")
				}

				baseName := filepath.Join(ConfigRC.Commands.Dir, name)
				if _, err := os.Stat(baseName + ".js"); err == nil {
					return errors.New("command name already exists with extension .js")
				}
				return nil
			})); err != nil {
				return
			}
		}

		template := commandTemplates["js"]

		if _, err := os.Stat(ConfigRC.Commands.Dir); os.IsNotExist(err) {
			os.MkdirAll(ConfigRC.Commands.Dir, 0755)
		}

		commandFile := commandName + ".js"
		filePath := filepath.Join(ConfigRC.Commands.Dir, commandFile)

		file, err := os.Create(filePath)
		if err != nil {
			color.Red("Error creating file: %v", err)
			return
		}
		defer file.Close()

		absCommandFilePath, _ := filepath.Abs(filePath)

		cwd, _ := os.Getwd()
		rootDir, _ := filepath.Abs(cwd)
		prasmoidDef := filepath.Join(rootDir, "prasmoid.d.ts")

		relPath, err := filepath.Rel(filepath.Dir(absCommandFilePath), prasmoidDef)
		if err != nil {
			color.Red("Error calculating relative path: %v", err)
			return
		}

		templateFilled := fmt.Sprintf(template, relPath, commandName)

		_, err = file.WriteString(templateFilled)
		if err != nil {
			color.Red("Error writing to file: %v", err)
			return
		}

		color.Green("Successfully created %s command at %s", commandName, color.BlueString(filePath))
	},
}
View Source
var CommandsRemoveCmd = &cobra.Command{
	Use:   "remove",
	Short: "Remove a custom command",
	Long:  "Remove a custom command to the project.",
	Run: func(cmd *cobra.Command, args []string) {
		availableCmds := []string{}
		filepath.Walk(ConfigRC.Commands.Dir, func(path string, info os.FileInfo, err error) error {
			if info.IsDir() {
				return nil
			}
			availableCmds = append(availableCmds, fmt.Sprintf("%s (%s)", strings.TrimSuffix(info.Name(), filepath.Ext(info.Name())), info.Name()))
			return nil
		})

		if strings.TrimSpace(rmCommandName) == "" {

			runtimePrompt := &survey.Select{
				Message: "Select a command to remove:",
				Options: availableCmds,
			}
			if err := survey.AskOne(runtimePrompt, &rmCommandName); err != nil {
				return
			}

		}

		filenameRegex := regexp.MustCompile(`\(([^)]+)\)$`)
		matches := filenameRegex.FindStringSubmatch(rmCommandName)
		if len(matches) != 2 {
			color.Red("Invalid command format")
			return
		}
		fileName := matches[1]

		filePath := filepath.Join(ConfigRC.Commands.Dir, fileName)

		var confirm bool
		confirmPrompt := &survey.Confirm{
			Message: "Are you sure you want to remove this command?",
			Default: true,
		}
		if err := survey.AskOne(confirmPrompt, &confirm); err != nil {
			return
		}
		if !confirm {
			return
		}

		err := os.Remove(filePath)
		if err != nil {
			color.Red("Error removing file: %v", err)
			return
		}
		color.Green("Successfully removed command: %s", fileName)
	},
}
View Source
var CommandsRootCmd = &cobra.Command{
	Use:   "commands",
	Short: "Manage project-specific custom commands",
}

changeset/rootCmd represents the changeset/root command

View Source
var ConfigRC types.Config

project wise prasmoid config

View Source
var FileTemplates = map[string]string{
	"contents/ui/main.qml":        consts.MAIN_QML,
	"contents/config/main.xml":    consts.MAIN_XML,
	"contents/icons/prasmoid.svg": consts.PRASMOID_SVG,
	".gitignore":                  consts.GITIGNORE,
	"prasmoid.d.ts":               consts.PRASMOID_DTS,
}
View Source
var FormatCmd = &cobra.Command{
	Use:   "format",
	Short: "Prettify QML files",
	Long:  "Automatically format QML source files to ensure consistent style and readability.",
	Run: func(cmd *cobra.Command, args []string) {
		if !utils.IsValidPlasmoid() {
			color.Red("Current directory is not a valid plasmoid.")
			return
		}
		if !utils.IsPackageInstalled(consts.QmlFormatPackageName["binary"]) {
			pm, _ := utils.DetectPackageManager()
			var confirm bool
			confirmPrompt := &survey.Confirm{
				Message: "qmlformat is not installed. Do you want to install it?",
				Default: true,
			}
			if err := survey.AskOne(confirmPrompt, &confirm); err != nil {
				return
			}

			if confirm {
				if err := utils.InstallQmlformat(pm); err != nil {
					color.Red("Failed to install qmlformat.")
					return
				}
				color.Green("qmlformat installed successfully.")
			} else {
				color.Yellow("Operation cancelled.")
				return
			}
		}

		crrPath, _ := os.Getwd()
		relPath := filepath.Join(crrPath, dir)
		if watch {
			prettifyOnWatch(relPath)
		} else {
			prettify(relPath)
		}
	},
}

FormatCmd represents the format command

View Source
var I18nCompileCmd = &cobra.Command{
	Use:   "compile",
	Short: "Compile .po files to binary .mo files",
	Run: func(cmd *cobra.Command, args []string) {
		if !utils.IsValidPlasmoid() {
			color.Red("Current directory is not a valid plasmoid.")
			return
		}

		if !utils.IsPackageInstalled("msgfmt") {
			color.Red("msgfmt command not found. Please install gettext.")
			return
		}

		if !silent {
			color.Cyan("Compiling translation files...")
		}

		if err := CompileI18n(ConfigRC, silent); err != nil {
			color.Red("Failed to compile messages: %v", err)
			return
		}

		if !silent {
			color.Green("Successfully compiled all translation files.")
		}

		if restart, _ := cmd.Flags().GetBool("restart"); restart {
			color.Cyan("Restarting plasmashell...")
			if err := restartPlasmashell(); err != nil {
				color.Red("Failed to restart plasmashell: %v", err)
			}
		}
	},
}
View Source
var I18nExtractCmd = &cobra.Command{
	Use:   "extract",
	Short: "Extract translatable strings from source files",
	Run: func(cmd *cobra.Command, args []string) {
		if !utils.IsValidPlasmoid() {
			color.Red("Current directory is not a valid plasmoid.")
			return
		}

		if !utils.IsPackageInstalled("xgettext") {
			color.Red("xgettext command not found. Please install gettext.")
			return
		}

		color.Cyan("Extracting translatable strings...")

		translationsDir := ConfigRC.I18n.Dir
		_ = os.MkdirAll(translationsDir, 0755)

		if err := runXGettext(translationsDir); err != nil {
			color.Red("Failed to extract strings: %v", err)
			return
		}

		color.Green("Successfully extracted strings to %s/template.pot", translationsDir)

		isPoGen, _ := cmd.Flags().GetBool("no-po")
		if !isPoGen {
			if err := generatePoFiles(translationsDir); err != nil {
				color.Red("Failed to generate .po files: %v", err)
				return
			}
		}
	},
}
View Source
var I18nLocalesEditCmd = &cobra.Command{
	Use:   "edit",
	Short: "Edit locales for your plasmoid.",
	Run: func(cmd *cobra.Command, args []string) {
		if !utils.IsValidPlasmoid() {
			color.Red("Current directory is not a valid plasmoid.")
			return
		}

		currentLocales := ConfigRC.I18n.Locales

		locales := utils.AskForLocales(currentLocales)

		if locales != nil {
			ConfigRC.I18n.Locales = locales
			content, _ := json.MarshalIndent(ConfigRC, "", "  ")
			fmt.Println(string(content))
			os.WriteFile("prasmoid.config.js", []byte(`/// <reference path="prasmoid.d.ts" />
/** @type {PrasmoidConfig} */
const config = `+string(content)), 0644)
		}
	},
}
View Source
var InitCmd = &cobra.Command{
	Use:   "init",
	Short: "Initialize a new plasmoid project",
	Run: func(cmd *cobra.Command, args []string) {
		clearLine()
		printHeader()

		if err := gatherProjectConfig(); err != nil {
			color.Red("Failed to gather project config: %v", err)
			return
		}

		color.Yellow("Creating project at: %s", Config.Path)
		fmt.Println()

		if err := InitPlasmoid(); err != nil {
			color.Red("Failed to initialize plasmoid: "+
				"%v", err)
			return
		}

		if Config.InitGit {
			if err := initializeGitRepo(); err != nil {
				color.Yellow("Could not initialize git repository: %v", err)
			} else {
				color.Green("Initialized git repository.")
			}
		}

		fmt.Println()
		color.Green("Plasmoid initialized successfully!")
		fmt.Println()
		printNextSteps()
	},
}
View Source
var InstallCmd = &cobra.Command{
	Use:   "install",
	Short: "Install plasmoid system-wide",
	Long:  "Install the plasmoid to the system directories for production use.",
	Run: func(cmd *cobra.Command, args []string) {
		if !utils.IsValidPlasmoid() {
			color.Red("Current directory is not a valid plasmoid.")
			return
		}
		if err := InstallPlasmoid(); err != nil {
			color.Red("Failed to install plasmoid:", err)
			return
		}

		dest, _ := utils.GetDevDest()
		color.Green("Plasmoid installed successfully in %s", color.BlueString(dest))
		color.Cyan("\n- Please restart plasmashell to apply changes.")
	},
}

InstallCmd represents the production command

View Source
var LinkCmd = &cobra.Command{
	Use:   "link",
	Short: "Link plasmoid to local development directory",
	Long:  "Create a symlink to local development folder for easy testing.",
	Run: func(cmd *cobra.Command, args []string) {
		if !utils.IsValidPlasmoid() {
			color.Red("Current directory is not a valid plasmoid.")
			return
		}
		dest, err := utils.GetDevDest()
		if err != nil {
			color.Red(err.Error())
			return
		}
		if linkWhere {
			fmt.Println("Plasmoid linked to:\n", "- ", color.BlueString(dest))
			return
		}
		if err := LinkPlasmoid(dest); err != nil {
			color.Red("Failed to link plasmoid:", err)
			return
		}
		color.Green("Plasmoid linked successfully.")
	},
}

devCmd represents the dev command

View Source
var PreviewCmd = &cobra.Command{
	Use:   "preview",
	Short: "Enter plasmoid preview mode",
	Long:  "Launch the plasmoid in preview mode for testing and development.",
	Run: func(cmd *cobra.Command, args []string) {
		if !utils.IsValidPlasmoid() {
			color.Red("Current directory is not a valid plasmoid.")
			return
		}
		watch, _ := cmd.Flags().GetBool("watch")

		if !utils.IsLinked() {
			var confirm bool
			confirmPrompt := &survey.Confirm{
				Message: "Plasmoid is not linked. Do you want to link it first?",
				Default: true,
			}
			if err := survey.AskOne(confirmPrompt, &confirm); err != nil {
				return
			}

			if confirm {
				dest, err := utils.GetDevDest()
				if err != nil {
					color.Red(err.Error())
					return
				}
				if err := LinkPlasmoid(dest); err != nil {
					color.Red("Failed to link plasmoid:", err)
					return
				}
			} else {
				fmt.Println("Operation cancelled.")
				return
			}
		}

		if !utils.IsPackageInstalled(consts.PlasmoidPreviewPackageName["binary"]) {
			pm, _ := utils.DetectPackageManager()
			var confirm bool
			confirmPrompt := &survey.Confirm{
				Message: "plasmoidviewer is not installed. Do you want to install it first?",
				Default: true,
			}
			if err := survey.AskOne(confirmPrompt, &confirm); err != nil {
				return
			}

			if confirm {
				if err := utils.InstallPackage(pm, consts.PlasmoidPreviewPackageName["binary"], consts.PlasmoidPreviewPackageName); err != nil {
					color.Red("Failed to install plasmoidviewer:", err)
					return
				}
			} else {
				fmt.Println("Operation cancelled.")
				return
			}
		}

		if err := previewPlasmoid(watch); err != nil {
			color.Red("Failed to preview plasmoid:", err)
			return
		}
	},
}

PreviewCmd represents the preview command

View Source
var SetupCmd = &cobra.Command{
	Use:   "setup",
	Short: "Setup development environment",
	Long:  "Install plasmoidviewer and other development dependencies.",
	Run: func(cmd *cobra.Command, args []string) {
		pm, err := utils.DetectPackageManager()
		if err != nil {
			color.Red("Failed to detect package manager.")
			return
		}

		if !utils.IsPackageInstalled(consts.QmlFormatPackageName["binary"]) {
			color.Yellow("Installing qmlformat...")
			if err := utils.InstallQmlformat(pm); err != nil {
				color.Red("Failed to install qmlformat.")
				return
			}
		}

		if !utils.IsPackageInstalled(consts.PlasmoidPreviewPackageName["binary"]) {
			color.Yellow("Installing plasmoidviewer...")
			if err := utils.InstallPlasmoidPreview(pm); err != nil {
				color.Red("Failed to install plasmoidviewer.")
				return
			}
		}

		if !utils.IsPackageInstalled(consts.CurlPackageName["binary"]) {
			color.Yellow("Installing curl...")
			if err := utils.InstallPackage(pm, consts.CurlPackageName["binary"], consts.CurlPackageName); err != nil {
				color.Red("Failed to install curl.")
				return
			}
		}
	},
}

SetupCmd represents the setup command

View Source
var UninstallCmd = &cobra.Command{
	Use:   "uninstall",
	Short: "Uninstall plasmoid system-wide",
	Long:  "Uninstall the plasmoid from the system directories.",
	Run: func(cmd *cobra.Command, args []string) {
		if !utils.IsValidPlasmoid() {
			color.Red("Current directory is not a valid plasmoid.")
			return
		}
		if err := UninstallPlasmoid(); err != nil {
			color.Red("Failed to uninstall plasmoid:", err)
			return
		}
		color.Green("Plasmoid uninstalled successfully")
	},
}

UninstallCmd represents the production command

View Source
var UnlinkCmd = &cobra.Command{
	Use:   "unlink",
	Short: "Unlink development plasmoid from the system",
	Long:  "Remove the symlink linking the development plasmoid from the system directories.",
	Run: func(cmd *cobra.Command, args []string) {
		dest, err := utils.GetDevDest()

		if err != nil {
			color.Red(err.Error())
			return
		}

		_ = os.Remove(dest)
		_ = os.RemoveAll(dest)
		color.Green("Plasmoid unlinked successfully.")
	},
}

UnlinkCmd represents the unlink command

View Source
var VersionCmd = &cobra.Command{
	Use:   "version",
	Short: "Show Prasmoid version",
	Long:  "Show Prasmoid version.",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println(internal.AppMeta.Version)
	},
}

Functions

func ApplyChanges

func ApplyChanges()

func CheckForUpdates

func CheckForUpdates()

func CompileI18n added in v0.0.3

func CompileI18n(config types.Config, silent bool) error

func CreateConfigFile added in v0.0.3

func CreateConfigFile(locales []string) error

func DiscoverAndRegisterCustomCommands

func DiscoverAndRegisterCustomCommands(rootCmd *cobra.Command)

DiscoverAndRegisterCustomCommands scans for Js, Go files and registers them as cobra commands.

func Execute

func Execute()

Execute adds all child commands to the root command and sets flags appropriately. This is called by main.main(). It only needs to happen once to the rootCmd.

func GetCacheFilePath added in v0.0.3

func GetCacheFilePath() string

func GetNextVersion

func GetNextVersion(version string, bump string) (string, error)

func InitPlasmoid

func InitPlasmoid() error

func InstallPlasmoid

func InstallPlasmoid() error

func LinkPlasmoid

func LinkPlasmoid(dest string) error

func ReadUpdateCache

func ReadUpdateCache() (map[string]interface{}, error)

func UninstallPlasmoid

func UninstallPlasmoid() error

func UpdateChangelog

func UpdateChangelog(version, date, body string) error

Types

type Author

type Author struct {
	Name  string `json:"Name,omitempty"`
	Email string `json:"Email,omitempty"`
}

type ChangesetMeta

type ChangesetMeta struct {
	ID   string `yaml:"id"`
	Bump string `yaml:"bump"`
	Next string `yaml:"next"`
	Date string `yaml:"date"`
}

ChangesetMeta represents the metadata for a changeset

type KPlugin

type KPlugin struct {
	Authors          []Author `json:"Authors,omitempty"`
	Description      string   `json:"Description"`
	EnabledByDefault bool     `json:"EnabledByDefault"`
	FormFactors      []string `json:"FormFactors"`
	Id               string   `json:"Id"`
	License          string   `json:"License"`
	Name             string   `json:"Name"`
	Version          string   `json:"Version"`
}

type Metadata

type Metadata struct {
	KPackageStructure        string   `json:"KPackageStructure"`
	KPlugin                  KPlugin  `json:"KPlugin"`
	XPlasmaAPIMinimumVersion string   `json:"X-Plasma-API-Minimum-Version"`
	XPlasmaProvides          []string `json:"X-Plasma-Provides"`
}

type ProjectConfig

type ProjectConfig struct {
	Name        string
	Path        string
	ID          string
	Description string
	AuthorName  string
	AuthorEmail string
	License     string
	InitGit     bool
	Locales     []string
}
var Config ProjectConfig

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL