@dscodotco/theme-cli
Version:
A CLI tool for developing Shopify themes
187 lines (167 loc) • 4.57 kB
text/typescript
import Shopify from "shopify-api-node";
import path from "path";
import fs from "fs/promises";
import { ThemeManager } from "./theme-manager.js";
import { createLogger } from "../logger.js";
const logger = createLogger("theme-api");
export interface ThemePage {
path: string;
template: string;
type:
| "static"
| "collections"
| "pages"
| "blogs"
| "products"
| "collections";
title: string;
}
export interface ThemeMetadata {
pages: ThemePage[];
templates: {
[key: string]: {
path: string;
sections: string[];
snippets: string[];
};
};
assets: string[];
config: {
settings_schema: any;
settings_data: any;
};
}
export class ThemeAPI {
private shopify: Shopify;
private themeManager: ThemeManager;
private themeDir: string;
private metadata: ThemeMetadata | null = null;
constructor(shopify: Shopify, themeManager: ThemeManager, themeDir: string) {
this.shopify = shopify;
this.themeManager = themeManager;
this.themeDir = themeDir;
logger.info(`ThemeAPI initialized with theme directory: ${themeDir}`);
logger.info(
`Using Shopify client with shop: ${(shopify as any).options.shopName}`
);
}
async getPages(): Promise<ThemePage[]> {
const templates = await this.getStaticTemplates();
const pages: ThemePage[] = [];
// Add static pages
pages.push({
path: "/",
template: "index",
type: "static",
title: "Home",
});
// Add product pages if template exists
if (templates.includes("product")) {
pages.push({
path: "/products/example-product",
template: "product",
type: "products",
title: "Example Product",
});
}
// Add collection pages if template exists
if (templates.includes("collection")) {
pages.push({
path: "/collections/example-collection",
template: "collection",
type: "collections",
title: "Example Collection",
});
}
// Add cart page if template exists
if (templates.includes("cart")) {
pages.push({
path: "/cart",
template: "cart",
type: "static",
title: "Cart",
});
}
// Add search page if template exists
if (templates.includes("search")) {
pages.push({
path: "/search",
template: "search",
type: "static",
title: "Search",
});
}
// Add contact page if it exists
if (templates.includes("page.contact")) {
pages.push({
path: "/pages/contact",
template: "page.contact",
type: "pages",
title: "Contact",
});
}
// Add blog page if it exists
if (templates.includes("blog")) {
pages.push({
path: "/blogs/news",
template: "blog",
type: "blogs",
title: "Blog",
});
}
return pages;
}
private async getStaticTemplates(): Promise<string[]> {
try {
const templateDir = path.join(this.themeDir, "templates");
logger.info(`Reading template directory: ${templateDir}`);
const files = await fs.readdir(templateDir);
const templates = files
.filter((file) => file.endsWith(".json"))
.map((file) => file.replace(".json", ""));
logger.info(`Found templates: ${templates.join(", ")}`);
return templates;
} catch (error) {
logger.error(`Failed to read templates: ${(error as Error).message}`);
throw error;
}
}
async getMetadata(): Promise<ThemeMetadata> {
if (this.metadata) {
logger.info("Returning cached metadata");
return this.metadata;
}
try {
const pages = await this.getPages();
this.metadata = {
pages,
templates: {},
assets: [],
config: {
settings_schema: [],
settings_data: {},
},
};
return this.metadata;
} catch (error) {
logger.error("Failed to fetch metadata:");
logger.error(error);
throw error;
}
}
async getPageMetadata(path: string): Promise<ThemePage | null> {
logger.info(`Getting metadata for page: ${path}`);
const pages = await this.getPages();
const page = pages.find((p) => p.path === path);
if (page) {
logger.info(`Found metadata for page: ${JSON.stringify(page, null, 2)}`);
} else {
logger.warn(`No metadata found for page: ${path}`);
}
return page || null;
}
invalidateCache() {
logger.info("Invalidating metadata cache");
this.metadata = null;
}
}