shopify-accelerate
Version:
Shopify Theme development with full Typescript Support
395 lines (374 loc) • 13.6 kB
text/typescript
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);