Documentation
¶
Overview ¶
Package codemod provides helpers for codemods and project automation.
Package codemod provides helpers for codemods and project automation. This file contains the copilot command and related functionality.
Package codemod provides helpers for codemods and project automation. This file contains the lint-setup command and related functionality.
Package codemod provides helpers for codemods and project automation. This file contains the prettier formatting command.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var AssetsFS embed.FS
var CodemodCmd = &cobra.Command{
Use: "codemod",
Short: "Helpers for codemods and project automation",
Long: `Run codemods or setup helpers for various project types.`,
}
CodemodCmd is the root command for codemod-related helpers and automation.
var CopilotSetupCmd = &cobra.Command{ Use: "copilot", Short: "Setup custom Copilot instructions file", Long: `Create a base custom Copilot instructions file at .github/copilot-instructions.md. By default, this command assumes you are in the root of a Git repository. Use --force to bypass this check.`, Run: func(cmd *cobra.Command, _args []string) { headerStyle := lipgloss.NewStyle(). Bold(true). Foreground(theme.Primary). MarginBottom(1) if !ui.DisableProgress { fmt.Fprintln(log.Out, headerStyle.Render("🤖 GitHub Copilot Custom Instructions Setup")) } force, _ := cmd.Flags().GetBool("force") if !force { if !isGitRepository() { log.Error("Not in a Git repository. Use --force to bypass this check.") return } } if err := createCopilotInstructions(); err != nil { log.Error("Failed to create Copilot instructions: %v", err) return } theme.SuccessMessage("Copilot instructions file created at .github/copilot-instructions.md") }, }
CopilotSetupCmd creates a base custom Copilot instructions file in .github/copilot-instructions.md.
var LintSetupCmd = &cobra.Command{ Use: "lint-setup", Short: "Setup linting and formatting for a Node.js project", Long: `Install and configure linting, formatting, and pre-commit hooks for a Node.js project (eslint, prettier, husky, etc).`, Run: func(cmd *cobra.Command, _args []string) { headerStyle := lipgloss.NewStyle(). Bold(true). Foreground(theme.Primary). MarginBottom(1) if !ui.DisableProgress { fmt.Fprintln(log.Out, headerStyle.Render("🛠️ Node.js Linting & Formatting Setup")) } if _, err := os.Stat("package.json"); errors.Is(err, os.ErrNotExist) { log.Error("package.json not found in current directory") return } if err := installLintDependencies(echo); err != nil { log.Error("package manager install failed: %v", err) return } if err := writeESLintConfig(echo); err != nil { log.Error("Failed to write eslint.config.mjs: %v", err) return } if err := updatePackageJSON(); err != nil { log.Error("Failed to update package.json: %v", err) return } if err := setupHusky(); err != nil { log.Error("Failed to set up Husky: %v", err) return } if !echo { checkRedundantDependencies() } log.Success("Linting, formatting, and pre-commit hooks are set up!") }, }
LintSetupCmd sets up linting, formatting, and pre-commit hooks for a Node.js project.
var NativeCmd = &cobra.Command{ Use: "native [projectName]", Short: "Bootstrap a new React Native Expo project with gvtech-design", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { headerStyle := lipgloss.NewStyle(). Bold(true). Foreground(theme.Primary). MarginBottom(1) if !ui.DisableProgress { fmt.Fprintln(log.Out, headerStyle.Render("🚀 React Native Expo Project Bootstrapper")) } projectName := args[0] targetDir := filepath.Join(os.Getenv("PWD"), projectName) if _, err := os.Stat(targetDir); !os.IsNotExist(err) { return fmt.Errorf("directory %s already exists", projectName) } theme.InfoMessage(fmt.Sprintf("🚀 Bootstrapping Native project: %s...", projectName)) createCmd := execCommand("bunx", "create-expo-app@latest", projectName) createCmd.Stdout = log.Writer() createCmd.Stderr = log.ErrorWriter() if err := createCmd.Run(); err != nil { return fmt.Errorf("failed to create expo app: %w", err) } packageJsonPath := filepath.Join(targetDir, "package.json") packageJsonBytes, err := os.ReadFile(packageJsonPath) if err == nil { var pkg map[string]interface{} if json.Unmarshal(packageJsonBytes, &pkg) == nil { if res, ok := pkg["resolutions"].(map[string]interface{}); ok { res["lightningcss"] = "1.30.1" } else { pkg["resolutions"] = map[string]interface{}{ "lightningcss": "1.30.1", } } if overrides, ok := pkg["overrides"].(map[string]interface{}); ok { overrides["lightningcss"] = "1.30.1" } else { pkg["overrides"] = map[string]interface{}{ "lightningcss": "1.30.1", } } if newBytes, err := json.MarshalIndent(pkg, "", " "); err == nil { _ = os.WriteFile(packageJsonPath, newBytes, 0o644) } } } log.Info("Installing gvtech-design Native dependencies...") installCmd := execCommand( "bun", "add", "nativewind@preview", "tailwindcss@^4.3.0", "@tailwindcss/postcss", "postcss", "react-native-reanimated@4.3.1", "react-native-svg", "react-native-css-interop", "lucide-react-native@^1.8.0", "@gv-tech/ui-native", "@gv-tech/ui-core", "@gv-tech/design-tokens", "clsx", "tailwind-merge", ) installCmd.Dir = targetDir installCmd.Stdout = log.Writer() installCmd.Stderr = log.ErrorWriter() if err := installCmd.Run(); err != nil { return fmt.Errorf("failed to install dependencies: %w", err) } log.Info("Configuring Tailwind & NativeWind v5...") postcssConfig := `export default { plugins: { "@tailwindcss/postcss": {}, }, }; ` if err := os.WriteFile( filepath.Join(targetDir, "postcss.config.mjs"), []byte(postcssConfig), 0o644, ); err != nil { return fmt.Errorf("failed to write postcss.config.mjs: %w", err) } babelConfig := `module.exports = function (api) { api.cache(true); return { presets: [ ["babel-preset-expo"], ], plugins: [ "react-native-reanimated/plugin", ], }; }; ` if err := os.WriteFile(filepath.Join(targetDir, "babel.config.js"), []byte(babelConfig), 0o644); err != nil { return fmt.Errorf("failed to write babel.config.js: %w", err) } metroConfig := `const { getDefaultConfig } = require("expo/metro-config"); const { withNativeWind } = require("nativewind/metro"); const config = getDefaultConfig(__dirname); module.exports = withNativeWind(config, { input: "./global.css" }); ` if err := os.WriteFile(filepath.Join(targetDir, "metro.config.js"), []byte(metroConfig), 0o644); err != nil { return fmt.Errorf("failed to write metro.config.js: %w", err) } _ = os.RemoveAll(filepath.Join(targetDir, "app")) _ = os.RemoveAll(filepath.Join(targetDir, "components")) _ = os.RemoveAll(filepath.Join(targetDir, "constants")) _ = os.RemoveAll(filepath.Join(targetDir, "hooks")) _ = os.RemoveAll(filepath.Join(targetDir, "scripts")) _ = os.RemoveAll(filepath.Join(targetDir, "src")) appDir := filepath.Join(targetDir, "app") if err := os.MkdirAll(appDir, 0o755); err != nil { return fmt.Errorf("failed to create app dir: %w", err) } componentsDir := filepath.Join(targetDir, "components") if err := os.MkdirAll(componentsDir, 0o755); err != nil { return fmt.Errorf("failed to create components dir: %w", err) } globalCss := `@import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/preflight.css" layer(base); @import "tailwindcss/utilities.css"; @import "nativewind/theme"; @import "@gv-tech/design-tokens/theme.css"; @source "./node_modules/@gv-tech/ui-native"; ` if err := os.WriteFile(filepath.Join(targetDir, "global.css"), []byte(globalCss), 0o644); err != nil { return fmt.Errorf("failed to write global.css: %w", err) } headerTsx := `import { View } from "react-native"; import { Text, ThemeToggle } from "@gv-tech/ui-native"; export function Header() { return ( <View className="flex-row items-center justify-between p-4 border-b border-border bg-background pt-12"> <Text className="text-xl font-bold text-foreground">GV Tech</Text> <ThemeToggle /> </View> ); } ` if err := os.WriteFile(filepath.Join(componentsDir, "Header.tsx"), []byte(headerTsx), 0o644); err != nil { return fmt.Errorf("failed to write components/Header.tsx: %w", err) } footerTsx := `import { View } from "react-native"; import { Text } from "@gv-tech/ui-native"; export function Footer() { return ( <View className="p-4 border-t border-border bg-background items-center pb-8"> <Text className="text-sm text-muted-foreground">© 2026 GV Tech</Text> </View> ); } ` if err := os.WriteFile(filepath.Join(componentsDir, "Footer.tsx"), []byte(footerTsx), 0o644); err != nil { return fmt.Errorf("failed to write components/Footer.tsx: %w", err) } layoutTsx := `import { Stack } from "expo-router"; import { ThemeProvider } from "@gv-tech/ui-native"; import { Header } from "../components/Header"; import { Footer } from "../components/Footer"; import { View, Platform, Appearance } from "react-native"; import { cssInterop, colorScheme } from "react-native-css-interop"; import { Sun, Moon, SunMoon, LayoutDashboard } from "lucide-react-native"; if (Platform.OS === "web") { // Polyfill Appearance on web const listeners = new Set<any>(); // @ts-ignore Appearance.setColorScheme = (scheme) => { colorScheme.set(scheme); if (typeof document !== 'undefined') { if (scheme === 'dark') document.documentElement.classList.add('dark'); else document.documentElement.classList.remove('dark'); } for (const listener of listeners) { listener({ colorScheme: scheme }); } }; const originalGetColorScheme = Appearance.getColorScheme; Appearance.getColorScheme = () => { const current = colorScheme.get(); return current !== "system" && current !== undefined ? current : (originalGetColorScheme ? originalGetColorScheme() : "light"); }; const originalAddChangeListener = Appearance.addChangeListener; Appearance.addChangeListener = (listener) => { listeners.add(listener); const result = originalAddChangeListener ? originalAddChangeListener(listener) : { remove: () => {} }; return { remove: () => { listeners.delete(listener); if (result.remove) result.remove(); } }; }; // @ts-ignore if (Appearance.default) { // @ts-ignore Appearance.default.setColorScheme = Appearance.setColorScheme; // @ts-ignore Appearance.default.getColorScheme = Appearance.getColorScheme; // @ts-ignore Appearance.default.addChangeListener = Appearance.addChangeListener; } } cssInterop(Sun, { className: "style" }); cssInterop(Moon, { className: "style" }); cssInterop(SunMoon, { className: "style" }); cssInterop(LayoutDashboard, { className: "style" }); import "../global.css"; export default function Layout() { return ( <ThemeProvider> <View className="flex-1 bg-background"> <Header /> <Stack screenOptions={{ headerShown: false }} /> <Footer /> </View> </ThemeProvider> ); } ` if err := os.WriteFile(filepath.Join(appDir, "_layout.tsx"), []byte(layoutTsx), 0o644); err != nil { return fmt.Errorf("failed to write app/_layout.tsx: %w", err) } appJsonPath := filepath.Join(targetDir, "app.json") appJsonBytes, err := os.ReadFile(appJsonPath) if err == nil { patchedAppJson := strings.Replace(string(appJsonBytes), `"output": "static"`, `"output": "single"`, 1) if !strings.Contains(patchedAppJson, `"output": "single"`) { patchedAppJson = strings.Replace(string(appJsonBytes), `"web": {`, `"web": { "output": "single",`, 1) } _ = os.WriteFile(appJsonPath, []byte(patchedAppJson), 0o644) } appIndex := `import { View, Linking } from "react-native"; import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Button, Text } from "@gv-tech/ui-native"; import { LayoutDashboard } from "lucide-react-native"; export default function Index() { return ( <View className="flex-1 p-4 bg-background justify-center"> <Empty> <EmptyHeader> <EmptyMedia> <LayoutDashboard className="text-primary" size={32} /> </EmptyMedia> <EmptyTitle>Welcome to Your New App</EmptyTitle> <EmptyDescription> This is a fully functional design system boilerplate. Start customizing by editing the <Text className="font-mono text-muted-foreground">app/index.tsx</Text> file. </EmptyDescription> </EmptyHeader> <EmptyContent className="mt-4 flex justify-center"> <Button onPress={() => Linking.openURL("https://design.garciaericn.com/docs/getting-started")}> <Text>Read the Docs</Text> </Button> </EmptyContent> </Empty> </View> ); } ` if err := os.WriteFile(filepath.Join(appDir, "index.tsx"), []byte(appIndex), 0o644); err != nil { return fmt.Errorf("failed to write app/index.tsx: %w", err) } if iconData, err := AssetsFS.ReadFile("assets/icon.png"); err == nil { assetsDir := filepath.Join(targetDir, "assets", "images") if err := os.MkdirAll(assetsDir, 0o755); err != nil { return fmt.Errorf("failed to create assets directory: %w", err) } if err := os.WriteFile(filepath.Join(assetsDir, "icon.png"), iconData, 0o644); err != nil { return fmt.Errorf("failed to write icon.png: %w", err) } if err := os.WriteFile(filepath.Join(assetsDir, "favicon.png"), iconData, 0o644); err != nil { return fmt.Errorf("failed to write favicon.png: %w", err) } if err := os.WriteFile(filepath.Join(assetsDir, "adaptive-icon.png"), iconData, 0o644); err != nil { return fmt.Errorf("failed to write adaptive-icon.png: %w", err) } if err := os.WriteFile(filepath.Join(assetsDir, "splash-icon.png"), iconData, 0o644); err != nil { return fmt.Errorf("failed to write splash-icon.png: %w", err) } } theme.SuccessMessage(fmt.Sprintf("Native project %s bootstrapped successfully!", projectName)) return nil }, }
var PrettierCmd = &cobra.Command{ Use: "prettier [path]", Short: "Format code with prettier using @eng618/prettier-config", Long: `Format code with prettier, automatically installing @eng618/prettier-config if not available. If no path is provided, formats the current directory.`, Run: func(cmd *cobra.Command, args []string) { headerStyle := lipgloss.NewStyle(). Bold(true). Foreground(theme.Primary). MarginBottom(1) if !ui.DisableProgress { fmt.Fprintln(log.Out, headerStyle.Render("✨ Prettier Code Formatter")) } targetPath := "." if len(args) > 0 { targetPath = args[0] } if err := runPrettier(targetPath); err != nil { log.Error("Failed to run prettier: %v", err) return } theme.SuccessMessage("Prettier formatting completed!") }, }
PrettierCmd formats the current directory with prettier, installing @eng618/prettier-config if needed.
var WebCmd = &cobra.Command{ Use: "web [projectName]", Short: "Bootstrap a new React web project with gvtech-design", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { headerStyle := lipgloss.NewStyle(). Bold(true). Foreground(theme.Primary). MarginBottom(1) if !ui.DisableProgress { fmt.Fprintln(log.Out, headerStyle.Render("🌐 Web Project Bootstrapper")) } projectName := args[0] targetDir := filepath.Join(os.Getenv("PWD"), projectName) if _, err := os.Stat(targetDir); !os.IsNotExist(err) { return fmt.Errorf("directory %s already exists", projectName) } log.Info("Bootstrapping Web project: %s...", projectName) createCmd := execCommand("bun", "create", "vite", projectName, "--template", "react-ts") createCmd.Stdout = log.Writer() createCmd.Stderr = log.ErrorWriter() if err := createCmd.Run(); err != nil { return fmt.Errorf("failed to create vite app: %w", err) } log.Info("Installing gvtech-design Web dependencies...") installCmd := execCommand( "bun", "add", "tailwindcss@^4.3.0", "@tailwindcss/vite@^4.3.0", "@gv-tech/ui-web", "@gv-tech/ui-core", "@gv-tech/design-tokens", "clsx", "tailwind-merge", "lucide-react", ) installCmd.Dir = targetDir installCmd.Stdout = log.Writer() installCmd.Stderr = log.ErrorWriter() if err := installCmd.Run(); err != nil { return fmt.Errorf("failed to install dependencies: %w", err) } log.Info("Configuring Tailwind...") viteConfig := `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' // https://vite.dev/config/ export default defineConfig({ plugins: [ tailwindcss(), react() ], }) ` if err := os.WriteFile(filepath.Join(targetDir, "vite.config.ts"), []byte(viteConfig), 0o644); err != nil { return fmt.Errorf("failed to write vite.config.ts: %w", err) } indexCss := `@import "tailwindcss"; @import "@gv-tech/design-tokens/theme.css"; @source "../node_modules/@gv-tech/ui-web"; ` if err := os.WriteFile(filepath.Join(targetDir, "src", "index.css"), []byte(indexCss), 0o644); err != nil { return fmt.Errorf("failed to write index.css: %w", err) } componentsDir := filepath.Join(targetDir, "src", "components") if err := os.MkdirAll(componentsDir, 0o755); err != nil { return fmt.Errorf("failed to create components dir: %w", err) } headerTsx := `import { ThemeToggle } from "@gv-tech/ui-web/theme-toggle"; import { Text } from "@gv-tech/ui-web/text"; export function Header() { return ( <header className="flex items-center justify-between p-4 border-b border-border bg-background"> <Text className="text-xl font-bold text-foreground">GV Tech</Text> <ThemeToggle /> </header> ); } ` if err := os.WriteFile(filepath.Join(componentsDir, "Header.tsx"), []byte(headerTsx), 0o644); err != nil { return fmt.Errorf("failed to write src/components/Header.tsx: %w", err) } footerTsx := `import { Text } from "@gv-tech/ui-web/text"; export function Footer() { return ( <footer className="p-4 border-t border-border bg-background flex items-center justify-center"> <Text className="text-sm text-muted-foreground">© 2026 GV Tech</Text> </footer> ); } ` if err := os.WriteFile(filepath.Join(componentsDir, "Footer.tsx"), []byte(footerTsx), 0o644); err != nil { return fmt.Errorf("failed to write src/components/Footer.tsx: %w", err) } appTsx := `import { ThemeProvider } from "@gv-tech/ui-web/theme-provider"; import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@gv-tech/ui-web/empty"; import { Button } from "@gv-tech/ui-web/button"; import { Text } from "@gv-tech/ui-web/text"; import { Header } from "./components/Header"; import { Footer } from "./components/Footer"; import { LayoutDashboard } from "lucide-react"; function App() { return ( <ThemeProvider> <div className="flex flex-col min-h-screen bg-background"> <Header /> <main className="flex-1 flex flex-col items-center justify-center p-4"> <Empty> <EmptyHeader> <EmptyMedia> <LayoutDashboard className="text-primary" size={32} /> </EmptyMedia> <EmptyTitle>Welcome to Your New Web App</EmptyTitle> <EmptyDescription> This is a fully functional design system boilerplate. Start customizing by editing the <Text className="font-mono text-muted-foreground">src/App.tsx</Text> file. </EmptyDescription> </EmptyHeader> <EmptyContent className="mt-4 flex justify-center"> <Button onClick={() => window.open('https://design.garciaericn.com/docs/getting-started', '_blank')}> Read the Docs </Button> </EmptyContent> </Empty> </main> <Footer /> </div> </ThemeProvider> ) } export default App ` if err := os.WriteFile(filepath.Join(targetDir, "src", "App.tsx"), []byte(appTsx), 0o644); err != nil { return fmt.Errorf("failed to write App.tsx: %w", err) } if iconData, err := AssetsFS.ReadFile("assets/icon.png"); err == nil { publicDir := filepath.Join(targetDir, "public") if err := os.MkdirAll(publicDir, 0o755); err != nil { return fmt.Errorf("failed to create public dir: %w", err) } if err := os.WriteFile(filepath.Join(publicDir, "favicon.png"), iconData, 0o644); err != nil { return fmt.Errorf("failed to write favicon.png: %w", err) } indexPath := filepath.Join(targetDir, "index.html") if htmlData, err := os.ReadFile(indexPath); err == nil { updatedHtml := strings.ReplaceAll(string(htmlData), "/vite.svg", "/favicon.png") updatedHtml = strings.ReplaceAll(updatedHtml, `type="image/svg+xml"`, `type="image/png"`) if err := os.WriteFile(indexPath, []byte(updatedHtml), 0o644); err != nil { return fmt.Errorf("failed to update index.html: %w", err) } } } log.Info("Web project %s bootstrapped successfully!", projectName) return nil }, }
Functions ¶
This section is empty.
Types ¶
This section is empty.