UNPKG

@dscodotco/theme-cli

Version:

A CLI tool for developing Shopify themes

213 lines (188 loc) 6.53 kB
import path from "path"; import ora from "ora"; import { Command } from "commander"; import { SimpleDevServer } from "../../../utils/shopify/simple-dev-server.js"; import { ThemeManager } from "../../../utils/shopify/theme-manager.js"; import { createLogger } from "../../../utils/logger.js"; import Shopify from "shopify-api-node"; import dotenv from "dotenv"; const logger = createLogger("theme-dev"); /** * Options for the theme development server */ export interface ThemeDevOptions { /** Shopify store name (e.g., my-store) */ store: string; /** Shopify Admin API key */ apiKey: string; /** Shopify Admin API password/token */ password: string; /** Theme ID to use for development (optional) */ themeId?: string; /** Theme directory (defaults to current directory) */ themeDir?: string; /** Port for preview server */ port?: string; /** Name for the development theme if creating a new one */ themeName?: string; /** Enable debug mode */ debug?: boolean; } /** * Load environment variables from .env file */ function loadEnvVariables(): void { const result = dotenv.config(); if (result.error) { logger.warn( "No .env file found. You'll need to provide credentials via command line options." ); return; } if (result.parsed) { logger.info("Loaded environment variables from .env file"); } } /** * Validate required credentials */ function validateCredentials(options: ThemeDevOptions): void { const missingCredentials: string[] = []; if (!options.store) { if (process.env.SHOPIFY_STORE) { options.store = process.env.SHOPIFY_STORE; } else { missingCredentials.push("store (-s, --store)"); } } if (!options.apiKey) { if (process.env.SHOPIFY_API_KEY) { options.apiKey = process.env.SHOPIFY_API_KEY; } else { missingCredentials.push("API key (-k, --api-key)"); } } if (!options.password) { if (process.env.SHOPIFY_PASSWORD) { options.password = process.env.SHOPIFY_PASSWORD; } else { missingCredentials.push("password (-p, --password)"); } } if (missingCredentials.length > 0) { logger.error("Missing required credentials:"); missingCredentials.forEach((cred) => logger.error(` - ${cred}`)); logger.info("\nYou can provide these credentials in two ways:"); logger.info( "1. Create a .env file in your theme directory with the following variables:" ); logger.info(" SHOPIFY_STORE=your-store"); logger.info(" SHOPIFY_API_KEY=your-api-key"); logger.info(" SHOPIFY_PASSWORD=your-password"); logger.info("\n2. Pass them as command line options:"); logger.info( " npx @dscodotco/theme-cli shopify theme dev -s your-store -k your-api-key -p your-password" ); process.exit(1); } } export const dev = new Command() .name("dev") .description("Start a local development server for theme development") .option("-s, --store <store>", "Shopify store name (e.g., my-store)") .option("-k, --api-key <key>", "Shopify Admin API key") .option("-p, --password <password>", "Shopify Admin API password/token") .option("-t, --theme-id <id>", "Theme ID to use for development") .option( "-d, --theme-dir <dir>", "Theme directory (defaults to current directory)" ) .option("--port <port>", "Port for preview server", "3000") .option("--theme-name <n>", "Name for the development theme") .option("--debug", "Enable debug mode with verbose logging", false) .action(async (options: ThemeDevOptions) => { // Load environment variables loadEnvVariables(); // Validate credentials validateCredentials(options); const spinner = ora("Starting development server...").start(); try { if (options.debug) { logger.info("Debug mode enabled"); logger.info("Configuration:"); logger.info(` Store: ${options.store}`); logger.info(` Theme directory: ${options.themeDir || "."}`); logger.info(` Port: ${options.port || "3000"}`); logger.info(` Theme ID: ${options.themeId || "Creating new theme"}`); } // Initialize Shopify client const shopify = new Shopify({ shopName: options.store, accessToken: options.password, apiVersion: "2023-04", }); // Initialize theme manager const themeManager = new ThemeManager({ storeName: options.store, apiKey: options.apiKey, password: options.password, }); // Resolve theme directory const themeDir = path.resolve(options.themeDir || "."); // Get or create theme ID let themeId = options.themeId ? parseInt(options.themeId) : undefined; if (!themeId) { spinner.text = "Creating a new development theme..."; const theme = await themeManager.createDevelopmentTheme( options.themeName || "Development Theme" ); themeId = theme.id; spinner.succeed( `Created new development theme: ${theme.name} (ID: ${themeId})` ); } // Start development server spinner.start("Starting development server..."); const port = parseInt(options.port || "3000"); const server = new SimpleDevServer({ port, themeDir, shopify, themeManager, credentials: { storeName: options.store, apiKey: options.apiKey, password: options.password, }, themeId, debug: options.debug, }); await server.start(); spinner.succeed("Development server running"); // Log preview URL const previewUrl = themeManager.getThemePreviewUrl(themeId); logger.info(`\nShopify theme preview: ${previewUrl}`); if (!options.themeId) { logger.info("\nA new development theme was created for this session."); logger.info( "It will remain in your Shopify admin until you delete it.\n" ); } logger.info("Press Ctrl+C to stop the development server"); // Handle shutdown process.on("SIGINT", () => { logger.info("\nShutting down..."); server.stop(); process.exit(0); }); } catch (error) { spinner.fail( `Failed to start development server: ${(error as Error).message}` ); if (options.debug && error instanceof Error) { logger.error("Stack trace:"); logger.error(error.stack || "No stack trace available"); } process.exit(1); } });