UNPKG

@dscodotco/theme-cli

Version:

A CLI tool for developing Shopify themes

146 lines (129 loc) 3.74 kB
import axios from "axios"; import { createLogger } from "../logger.js"; import { createHmac } from "crypto"; const logger = createLogger("theme-manager"); export interface ShopifyCredentials { storeName: string; apiKey: string; password: string; } export interface ShopifyTheme { id: number; name: string; role: string; theme_store_id?: number; previewable: boolean; processing: boolean; admin_graphql_api_id: string; created_at: string; updated_at: string; } /** * Manages Shopify themes through the Admin API */ export class ThemeManager { private credentials: ShopifyCredentials; private baseUrl: string; private apiUrl: string; private isAccessToken: boolean; /** * Creates a new ThemeManager instance * @param credentials Shopify store credentials */ constructor(credentials: ShopifyCredentials) { this.credentials = credentials; this.baseUrl = `https://${credentials.storeName}.myshopify.com`; this.apiUrl = `${this.baseUrl}/admin/api/2023-04`; // Determine if we're using an access token (shpat_) or API key + secret this.isAccessToken = this.credentials.password.startsWith("shpat_"); if (!this.isAccessToken) { logger.info("Using API key + secret authentication"); } else { logger.info("Using access token authentication"); } } /** * Gets the appropriate authentication configuration for Shopify API calls * @returns Authentication config object */ public getAuthConfig = (): any => { if (this.isAccessToken) { // Access token auth - use headers return { headers: { "X-Shopify-Access-Token": this.credentials.password, "Content-Type": "application/json", }, }; } else { // API key + secret auth - use basic auth return { auth: { username: this.credentials.apiKey, password: this.credentials.password, }, headers: { "Content-Type": "application/json", }, }; } }; /** * Lists all themes in the store * @returns Promise containing array of themes */ async listThemes(): Promise<ShopifyTheme[]> { try { const authConfig = this.getAuthConfig(); const response = await axios.get( `${this.apiUrl}/themes.json`, authConfig ); return response.data.themes as ShopifyTheme[]; } catch (error) { logger.error(`Failed to list themes: ${(error as Error).message}`); throw error; } } /** * Creates a new development theme * @param name The name for the new theme * @returns The created theme */ async createDevelopmentTheme(name?: string): Promise<ShopifyTheme> { const themeName = name || `Development Theme (${new Date().toISOString()})`; const requestBody = { theme: { name: themeName, role: "development", }, }; try { logger.info(`Creating new development theme: ${themeName}`); const authConfig = this.getAuthConfig(); const response = await axios.post( `${this.apiUrl}/themes.json`, requestBody, authConfig ); const newTheme = response.data.theme as ShopifyTheme; logger.success( `Created new development theme: ${newTheme.name} (ID: ${newTheme.id})` ); return newTheme; } catch (error) { logger.error( `Failed to create development theme: ${(error as Error).message}` ); throw error; } } /** * Gets the preview URL for a theme * @param themeId The theme ID * @returns The preview URL */ getThemePreviewUrl(themeId: number): string { return `${this.baseUrl}/?preview_theme_id=${themeId}`; } }