UNPKG

shopify-accelerate

Version:

Shopify Theme development with full Typescript Support

395 lines (374 loc) 13.6 kB
#!/usr/bin/env ts-node-script import fs from "fs"; import path from "path"; import toml from "toml"; import { PresetSchema, ShopifyBlock, ShopifySection, ShopifySettings } from "./@types/shopify"; import { runEsbuild } from "./src/esbuild/esbuild"; import { buildTheme } from "./src/scaffold-theme/build-theme"; import { generateBaseTypes } from "./src/scaffold-theme/generate-base-types"; import { generateBlocksTypes } from "./src/scaffold-theme/generate-blocks-types"; import { generateConfigFiles } from "./src/scaffold-theme/generate-config-files"; import { generateSectionsTypes } from "./src/scaffold-theme/generate-section-types"; import { generateSettingTypes } from "./src/scaffold-theme/generate-setting-types"; import { getSchemaSources, getSources } from "./src/scaffold-theme/parse-files"; import { shopifyCliPull } from "./src/shopify-cli/pull"; import { runTailwindCSSWatcher } from "./src/tailwind/tailwind-watch"; import { capitalize } from "./src/utils/capitalize"; import { readFile, writeCompareFile, writeOnlyNew } from "./src/utils/fs"; import { JSONParse } from "./src/utils/json"; import { validateCliOptions } from "./src/validate-cli-options"; import { watchHeadless } from "./src/watch-headless/watch-headless"; import { watchTheme } from "./src/watch-theme/watch-theme"; const { Command } = require("commander"); const program = new Command(); require("dotenv").config(); export const root_dir = process.env.SHOPIFY_ACCELERATE_ROOT ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_ROOT) : process.cwd(); const tomlFile = fs.existsSync(path.join(process.cwd(), "shopify.theme.toml")) ? toml.parse( readFile(path.join(process.cwd(), "shopify.theme.toml"), { encoding: "utf-8", }) ) : null; const shopify_toml = tomlFile ? JSONParse<{ environments: { [T: string]: { theme: string | number; store: string; path: string; ignore?: string[]; output?: "json"; live?: boolean; "allow-live"?: boolean; all_presets?: boolean; mode: "development" | "production"; ignore_blocks: string; ignore_snippets: string; ignore_layouts: string; ignore_sections: string; ignore_assets: string; }; }; }>(JSON.stringify(tomlFile)) : { environments: {} }; export type GlobalsState = { headless?: boolean; package_root: string; package_templates: string; package_types: string; project_root: ReturnType<typeof process.cwd>; all_presets: boolean; mode: "development" | "production"; theme_id: number; theme_path: string; store: string; environment: string; environments: (typeof shopify_toml)["environments"]; ignore_blocks: string[]; ignore_snippets: string[]; ignore_layouts: string[]; ignore_sections: string[]; ignore_assets: string[]; delete_external_layouts?: boolean; delete_external_sections?: boolean; delete_external_snippets?: boolean; delete_external_blocks?: boolean; delete_external_assets?: boolean; disabled_locales?: boolean; disabled_theme_blocks?: boolean; sources: { snippets: string[]; layouts: string[]; sectionsLiquid: string[]; sectionsSchemaFiles: string[]; sectionsJs: string[]; sectionPresetSchemaFiles: string[]; sectionPresetSchemas: { [T: string]: PresetSchema & { path: string; folder: string } }; blocksLiquid: string[]; blocksSchemaFiles: string[]; blocksJs: string[]; blockSchemas: { [T: string]: ShopifyBlock & { path: string; folder: string } }; assets: string[]; giftCards: string[]; configs: string[]; sectionGroups: string[]; templates: string[]; customerTemplates: string[]; settingsFile: string[]; locale_duplicates: { [T: string]: string[] }; settingsSchema: ShopifySettings; sectionSchemas: { [T: string]: ShopifySection & { path: string; folder: string } }; }; targets: { assets: string[]; dynamicJs: string[]; blocks: string[]; layout: string[]; locales: string[]; snippets: string[]; sections: string[]; settings: string; giftCards: string[]; sectionGroups: string[]; configs: string[]; templates: string[]; customerTemplates: string[]; }; folders: { types: string; utils: string; sections: string; presets: string; layout: string; blocks: string; snippets: string; templates: string; assets: string; config: string; }; }; export const config: GlobalsState = { ignore_blocks: shopify_toml?.environments?.["development"]?.ignore_blocks ?.split(",") .map((str) => str.trim()) ?? [], ignore_snippets: shopify_toml?.environments?.["development"]?.ignore_snippets ?.split(",") .map((str) => str.trim()) ?? [], ignore_layouts: shopify_toml?.environments?.["development"]?.ignore_layouts ?.split(",") .map((str) => str.trim()) ?? [], ignore_sections: shopify_toml?.environments?.["development"]?.ignore_sections ?.split(",") .map((str) => str.trim()) ?? [], ignore_assets: shopify_toml?.environments?.["development"]?.ignore_assets ?.split(",") .map((str) => str.trim()) ?? [], delete_external_layouts: process.env.SHOPIFY_ACCELERATE_DELETE_EXTERNAL_LAYOUTS === "true", delete_external_sections: process.env.SHOPIFY_ACCELERATE_DELETE_EXTERNAL_SECTIONS === "true", delete_external_snippets: process.env.SHOPIFY_ACCELERATE_DELETE_EXTERNAL_SNIPPETS === "true", delete_external_blocks: process.env.SHOPIFY_ACCELERATE_DELETE_EXTERNAL_BLOCKS === "true", delete_external_assets: process.env.SHOPIFY_ACCELERATE_DELETE_EXTERNAL_ASSETS === "true", disabled_locales: process.env.SHOPIFY_ACCELERATE_DISABLED_LOCALES === "true", disabled_theme_blocks: process.env.SHOPIFY_ACCELERATE_DISABLE_THEME_BLOCKS === "true", package_root: path.resolve(__dirname), project_root: root_dir, package_templates: path.join(path.resolve(__dirname), "./src/templates"), package_types: path.join(path.resolve(__dirname), "./@types"), sources: { snippets: [], layouts: [], sectionPresetSchemaFiles: [], sectionPresetSchemas: {}, sectionsLiquid: [], sectionsSchemaFiles: [], sectionsJs: [], blocksLiquid: [], blocksSchemaFiles: [], blocksJs: [], assets: [], giftCards: [], configs: [], sectionGroups: [], templates: [], customerTemplates: [], settingsFile: [], locale_duplicates: {}, settingsSchema: null, sectionSchemas: {}, blockSchemas: {}, }, targets: { assets: [], dynamicJs: [], blocks: [], layout: [], locales: [], snippets: [], sections: [], settings: null, giftCards: [], sectionGroups: [], configs: [], templates: [], customerTemplates: [], }, folders: { types: process.env.SHOPIFY_ACCELERATE_TYPES ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_TYPES) : path.join(root_dir, "@types"), utils: process.env.SHOPIFY_ACCELERATE_UTILS ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_UTILS) : path.join(root_dir, "@utils"), sections: process.env.SHOPIFY_ACCELERATE_SECTIONS ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_SECTIONS) : path.join(root_dir, "sections"), presets: process.env.SHOPIFY_ACCELERATE_PRESETS ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_PRESETS) : path.join(root_dir, "presets"), layout: process.env.SHOPIFY_ACCELERATE_LAYOUT ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_LAYOUT) : path.join(root_dir, "layout"), blocks: process.env.SHOPIFY_ACCELERATE_BLOCKS ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_BLOCKS) : path.join(root_dir, "blocks"), snippets: process.env.SHOPIFY_ACCELERATE_SNIPPETS ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_SNIPPETS) : path.join(root_dir, "snippets"), templates: process.env.SHOPIFY_ACCELERATE_TEMPLATES ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_TEMPLATES) : path.join(root_dir, "templates"), assets: process.env.SHOPIFY_ACCELERATE_ASSETS ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_ASSETS) : path.join(root_dir, "assets"), config: process.env.SHOPIFY_ACCELERATE_CONFIG ? path.join(process.cwd(), process.env.SHOPIFY_ACCELERATE_CONFIG) : path.join(root_dir, "config"), }, environments: Object.entries(shopify_toml?.environments ?? {})?.reduce((acc, [key, val]) => { acc[key] = { ...val, store: val?.store?.replace(/\.myshopify\.com/gi, ""), }; return acc; }, {}), environment: shopify_toml?.environments?.["development"] ? "development" : Object.keys(shopify_toml?.environments)?.[0] ?? "development", theme_id: +shopify_toml?.environments?.["development"]?.theme, theme_path: shopify_toml?.environments?.["development"]?.path ?? "./theme/development", store: shopify_toml?.environments?.["development"]?.store, all_presets: shopify_toml?.environments?.["development"]?.all_presets, mode: shopify_toml?.environments?.["development"]?.mode ?? "production", }; program .name("shopify-accelerate") .description("CLI for Accelerated Shopify Theme development") .version(require(path.join("./", "package.json")).version); program .command("init") .description("Initialize a local development environment from an existing Shopify theme") // .argument("<string>", "string to split") .option("-e, --environment <environment_name>", "Development environment name", "development") .option( "-s, --store <store_id>", "Shopify store id. I.e `https://admin.shopify.com/store/<store_id>` or `https://<store_id>.myshopify.com`" ) .option( "-t, --theme <theme_id>", "Shopify store id. I.e. `https://admin.shopify.com/store/<store_id>/themes/<theme_id>/editor`" ) .action(async (options) => { await validateCliOptions(options); buildTheme(); generateConfigFiles(); await shopifyCliPull(); }); program .command("dev") .description("Shopify Theme Development") // .argument("<string>", "string to split") .option("-e, --environment <environment_name>", "Development environment name", "development") .option( "-s, --store <store_id>", "Shopify store id. I.e `https://admin.shopify.com/store/<store_id>` or `https://<store_id>.myshopify.com`" ) .option( "-t, --theme <theme_id>", "Shopify store id. I.e. `https://admin.shopify.com/store/<store_id>/themes/<theme_id>/editor`" ) .action(async (options) => { await validateCliOptions(options); buildTheme(); generateConfigFiles(); runEsbuild(); runTailwindCSSWatcher(); watchTheme(); }); program .command("tailwind") .description("TailwindCSS Watcher") .option("-e, --environment <environment_name>", "Development environment name", "development") .option( "-s, --store <store_id>", "Shopify store id. I.e `https://admin.shopify.com/store/<store_id>` or `https://<store_id>.myshopify.com`" ) .option( "-t, --theme <theme_id>", "Shopify store id. I.e. `https://admin.shopify.com/store/<store_id>/themes/<theme_id>/editor`" ) .action(async (options) => { await validateCliOptions(options); runTailwindCSSWatcher(); }); program .command("esbuild") .description("ESBuild Watcher") .option("-e, --environment <environment_name>", "Development environment name", "development") .option( "-s, --store <store_id>", "Shopify store id. I.e `https://admin.shopify.com/store/<store_id>` or `https://<store_id>.myshopify.com`" ) .option( "-t, --theme <theme_id>", "Shopify store id. I.e. `https://admin.shopify.com/store/<store_id>/themes/<theme_id>/editor`" ) .action(async (options) => { await validateCliOptions(options); runEsbuild(); }); program .command("headless") .description("Shopify Headless Development") .action(async (options) => { config.headless = true; generateBaseTypes(); getSources(); getSchemaSources(); generateSectionsTypes(); generateSettingTypes(); generateBlocksTypes(); const imports = [`import type { FC } from "react";`]; const renderBlocks = []; Object.entries(config.sources.sectionSchemas ?? {})?.forEach(([key, entry]) => { imports.push( `import { ${capitalize(key)} } from "sections/${entry.folder}/${entry.folder}";` ); renderBlocks.push(` case "${entry.folder}": { return <${capitalize(key)} {...section} />; }`); writeOnlyNew( entry.path?.replace("schema.ts", `${entry.folder}.tsx`), `import type { ${capitalize(key)}Section } from "types/sections"; export const ${capitalize(key)} = ({ id, type, settings, blocks, disabled }: ${capitalize( key )}Section) => { if (disabled) return null return <>${capitalize(key)}</>; }; ` ); }); imports.push('import type { Sections } from "types/sections";'); imports.push(""); imports.push("type RenderSectionProps = { section: Sections; };"); imports.push(""); imports.push("export const RenderSection: FC<RenderSectionProps> = ({ section }) => {"); imports.push(" switch (section.type) {"); imports.push(...renderBlocks); imports.push(" }"); imports.push("};"); writeCompareFile( path.join(process.cwd(), "./app/[...slug]/render-section.tsx"), imports.join("\n") ); watchHeadless(); }); program.parse(process.argv);