@dscodotco/theme-cli
Version:
A CLI tool for developing Shopify themes
155 lines (154 loc) • 6.14 kB
JavaScript
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");
/**
* Load environment variables from .env file
*/
function loadEnvVariables() {
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) {
const missingCredentials = [];
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) => {
// 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.message}`);
if (options.debug && error instanceof Error) {
logger.error("Stack trace:");
logger.error(error.stack || "No stack trace available");
}
process.exit(1);
}
});