cmd

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2025 License: MIT Imports: 31 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(" 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 + "-" + version + ".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", 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, 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, 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) {
		config, _ := utils.LoadConfigRC()

		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(config.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(config.Commands.Dir); os.IsNotExist(err) {
			os.MkdirAll(config.Commands.Dir, 0755)
		}

		commandFile := commandName + ".js"
		filePath := filepath.Join(config.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) {
		config, _ := utils.LoadConfigRC()
		availableCmds := []string{}
		filepath.Walk(config.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(config.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 FileTemplates = map[string]string{
	"contents/ui/main.qml": `import QtQuick 6.5
import QtQuick.Layouts 6.5
import org.kde.kirigami 2.20 as Kirigami
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.plasmoid 2.0

PlasmoidItem {
    id: root

    Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground

    RowLayout {
        id: rowLayout
        anchors.centerIn: root

        Text {
            text: "Hello from {{.Name}}!"
            color: "white"
            font.pointSize: 18
        }
    }
}`,
	"contents/config/main.xml": `<kcfg>
  <group name="General">
    <entry name="exampleOption" type="Bool">
      <default>true</default>
    </entry>
  </group>
</kcfg>
`,
	"contents/icons/plasmoid.svg": `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
  <circle cx="16" cy="16" r="14" fill="#4A90E2"/>
  <text x="16" y="21" text-anchor="middle" font-size="12" fill="#fff">P</text>
</svg>
`,
	".gitignore": `# Build artifacts
build/
*.plasmoid

# IDE files
.vscode/
.idea/

# OS-specific
.DS_Store
`,
	"prasmoid.config.js": `const config = {
  commands: {
    dir: ".prasmoid/commands",
    ignore: []
  },
};
`,
	"prasmoid.d.ts": `/**
 * This file provides type definitions for the custom command environment in Prasmoid.
 * It is used by code editors like VS Code to provide autocompletion and type-checking.
 *
 * @see https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html
 */

/**
 * The context object passed to every custom command's Run function.
 */
interface CommandContext {
  /**
   * Returns the command-line arguments passed after the command name.
   * @returns {string[]} An array of arguments.
   * @example
   * const args = ctx.Args();
   * console.log(args[0]);
   */
  Args(): string[];

  /**
   * Provides access to the flags passed to the command.
   */
  Flags(): {
    /**
     * Retrieves the value of a specific flag.
     * @param name The name of the flag to retrieve.
     * @returns {string | boolean | undefined} The value of the flag, or undefined if not found.
     * @example
     * const name = ctx.Flags().get("name");
     */
    get(name: string): string | boolean | undefined;

    /**
     * You can also access flags directly as properties, but using get() is recommended for better type safety.
     * @example
     * const myFlag = ctx.Flags().myFlagName; // Type is 'any'
     */
    [key: string]: any;
  };
}

/**
 * The global prasmoid module for interacting with the project.
 */
declare module "prasmoid" {
  /**
   * Retrieves a value from the project's metadata.json file.
   * @param key The key from the "KPlugin" section of metadata.json (e.g., "Id", "Version").
   * @returns {string | undefined} The value from the metadata, or undefined if not found.
   */
  export function getMetadata(key: string): string | undefined;
  /**
   * Registers a custom command.
   * @param config The configuration for the command.
   */
  export function Command(config: Config): void;
}

/**
 * Configuration for the custom command.
 */
interface Config {
  run: (ctx: CommandContext) => void;
  /** A brief description of your command. */
  short: string;
  /** A longer description that spans multiple lines. */
  long: string;
  /** Optional aliases for the command. */
  alias?: string[];
  /** Flag definitions for the command. */
  flags?: {
    name: string;
    shorthand?: string;
    type: "string" | "boolean";
    default?: string | boolean;
    description: string;
  }[];
}

interface Console {
  /**
   * Logs a red-colored message.
   */
  red(...message: any[]): void;

  /**
   * Logs a green-colored message.
   */
  green(...message: any[]): void;

  /**
   * Logs a yellow-colored message.
   */
  yellow(...message: any[]): void;

  /**
   * Flexible color logger. Last argument must be a color string.
   * @example
   * console.color("Hey", "you!", "red")
   */
  color(
    ...message: any[],
    color: "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "black"
  ): void;
}

declare var console: Console;
`,
}
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(deps.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 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
		}

		fmt.Println()
		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(deps.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, deps.PlasmoidPreviewPackageName["binary"], deps.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(deps.QmlFormatPackageName["binary"]) {
			color.Yellow("Installing qmlformat...")
			if err := utils.InstallQmlformat(pm); err != nil {
				color.Red("Failed to install qmlformat.")
				return
			}
		}

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

		if !utils.IsPackageInstalled(deps.CurlPackageName["binary"]) {
			color.Yellow("Installing curl...")
			if err := utils.InstallPackage(pm, deps.CurlPackageName["binary"], deps.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 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 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
}
var Config ProjectConfig

Jump to

Keyboard shortcuts

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