shopify-accelerate-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/@types/metafields.ts +9 -0
  2. package/@types/sections.ts +1769 -0
  3. package/@types/settings.ts +3 -0
  4. package/@types/shopify.ts +1871 -0
  5. package/@types/types.d.ts +17 -0
  6. package/README.md +2 -0
  7. package/package.json +111 -0
  8. package/shopify-accelerate-app.ts +394 -0
  9. package/shopify.graphql +48866 -0
  10. package/src/esbuild/esbuild.ts +246 -0
  11. package/src/scaffold-theme/build-theme.ts +24 -0
  12. package/src/scaffold-theme/generate-asset-files.ts +48 -0
  13. package/src/scaffold-theme/generate-base-types.ts +23 -0
  14. package/src/scaffold-theme/generate-block-files.ts +194 -0
  15. package/src/scaffold-theme/generate-blocks-types.ts +242 -0
  16. package/src/scaffold-theme/generate-config-files.ts +34 -0
  17. package/src/scaffold-theme/generate-liquid-files.ts +995 -0
  18. package/src/scaffold-theme/generate-schema-locales.ts +195 -0
  19. package/src/scaffold-theme/generate-schema-variables.ts +380 -0
  20. package/src/scaffold-theme/generate-section-files.ts +303 -0
  21. package/src/scaffold-theme/generate-section-preset-files.ts +296 -0
  22. package/src/scaffold-theme/generate-section-types.ts +339 -0
  23. package/src/scaffold-theme/generate-setting-types.ts +123 -0
  24. package/src/scaffold-theme/generate-settings-file.ts +103 -0
  25. package/src/scaffold-theme/parse-files.ts +466 -0
  26. package/src/scaffold-theme/parse-locales.ts +98 -0
  27. package/src/shopify-cli/pull.ts +103 -0
  28. package/src/tailwind/postcss.config.js +8 -0
  29. package/src/tailwind/tailwind-watch.ts +133 -0
  30. package/src/tailwind/tailwind.config.js +261 -0
  31. package/src/telemetry/telemetry.ts +84 -0
  32. package/src/templates/.env.template +12 -0
  33. package/src/templates/shopify.theme.toml +9 -0
  34. package/src/utils/capitalize.ts +3 -0
  35. package/src/utils/delay.ts +3 -0
  36. package/src/utils/fs.ts +87 -0
  37. package/src/utils/is-object.ts +3 -0
  38. package/src/utils/json.ts +8 -0
  39. package/src/utils/to-camel-case.ts +6 -0
  40. package/src/utils/to-kebab-case.ts +6 -0
  41. package/src/utils/to-pascal-case.ts +12 -0
  42. package/src/utils/to-snake-case.ts +45 -0
  43. package/src/validate-cli-options.ts +281 -0
  44. package/src/watch-headless/watch-headless.ts +81 -0
  45. package/src/watch-theme/watch-theme.ts +84 -0
  46. package/tsconfig.json +82 -0
  47. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,281 @@
1
+ import chalk from "chalk";
2
+ import child_process, { exec } from "child_process";
3
+ import fs from "fs";
4
+
5
+ import json2toml from "json2toml";
6
+ import path from "path";
7
+ import userInput from "prompts";
8
+ import { config } from "../shopify-accelerate-app";
9
+ import { buildTheme } from "./scaffold-theme/build-theme";
10
+ import { generateConfigFiles } from "./scaffold-theme/generate-config-files";
11
+ import { delay } from "./utils/delay";
12
+ import { readFile, writeCompareFile, writeOnlyNew } from "./utils/fs";
13
+
14
+ export const validateCliOptions = async (
15
+ {
16
+ store,
17
+ theme: theme_id,
18
+ environment = "development",
19
+ reset_theme_id = false,
20
+ }: {
21
+ store?: string;
22
+ theme?: number;
23
+ environment?: string;
24
+ reset_theme_id?: boolean;
25
+ } = { environment: "development", store: undefined, theme: undefined, reset_theme_id: false }
26
+ ) => {
27
+ const { environments } = config;
28
+ const { ...currentEnvironment } = environments[environment] ?? {
29
+ store: store?.replace(/\.myshopify\.com/gi, ""),
30
+ theme: theme_id,
31
+ path: `./themes/${environment}`,
32
+ all_presets: false,
33
+ mode: environment === "development" ? "development" : "production",
34
+ ignore_blocks: "",
35
+ ignore_snippets: "",
36
+ ignore_layouts: "",
37
+ ignore_sections: "",
38
+ ignore_assets: "custom.css.liquid,custom.css,custom.js",
39
+ };
40
+
41
+ // console.log({ environments, currentEnvironment });
42
+ if (reset_theme_id) {
43
+ currentEnvironment.theme = undefined;
44
+ }
45
+
46
+ if (store && currentEnvironment.store !== store) {
47
+ const { update_store } = await userInput([
48
+ {
49
+ type: "confirm",
50
+ name: "update_store",
51
+ message: `Do you want to update the current (${environment}) environment store from "${
52
+ currentEnvironment.store
53
+ }" to "${store?.replace(/\.myshopify\.com/gi, "")}"?`,
54
+ },
55
+ ]);
56
+ if (update_store) {
57
+ currentEnvironment.store = store;
58
+ }
59
+ }
60
+
61
+ if (theme_id && +currentEnvironment.theme !== +theme_id) {
62
+ const { update_theme_id } = await userInput([
63
+ {
64
+ type: "confirm",
65
+ name: "update_theme_id",
66
+ message: `Do you want to update the current (${environment}) environment theme_id from "${currentEnvironment.theme}" to "${theme_id}"?`,
67
+ },
68
+ ]);
69
+ if (update_theme_id) {
70
+ currentEnvironment.theme = +theme_id;
71
+ }
72
+ }
73
+
74
+ const prompts = [];
75
+ if (!store && !currentEnvironment.store) {
76
+ console.log(
77
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: ${chalk.redBright(
78
+ `Store handle missing.`
79
+ )}`
80
+ );
81
+ const results = await userInput([
82
+ {
83
+ type: "text",
84
+ name: "store",
85
+ message:
86
+ "Please enter the Shopify Store Handle (http://{handle}.myshopify.com/admin / https://admin.shopify.com/store/{handle}) for your setup",
87
+ },
88
+ ]);
89
+ currentEnvironment.store =
90
+ results.store?.replace(/\.myshopify\.com/gi, "") ??
91
+ store?.replace(/\.myshopify\.com/gi, "") ??
92
+ currentEnvironment?.store?.replace(/\.myshopify\.com/gi, "");
93
+ }
94
+
95
+ if (!theme_id && !currentEnvironment.theme) {
96
+ if (currentEnvironment.store) {
97
+ const results = await userInput([
98
+ {
99
+ type: "select",
100
+ name: "theme_action",
101
+ message: "Next step",
102
+ choices: [
103
+ { title: "Create new Theme", value: "create_theme" },
104
+ { title: "List Themes", value: "list_themes" },
105
+ { title: "Add Theme Id", value: "add_theme" },
106
+ ],
107
+ initial: 1,
108
+ },
109
+ ]);
110
+ if (results.theme_action === "create_theme") {
111
+ // eslint-disable-next-line no-async-promise-executor
112
+ await new Promise(async (resolve, reject) => {
113
+ // exec(`shopify theme list -s ${currentEnvironment.store}`, async (error, stdout, stderr) => {
114
+ // console.log(stdout);
115
+ // if (stdout.includes("Press any key to open the login page on your brows")) {
116
+ // console.log("MATCHED!!");
117
+ // }
118
+ // await delay(4500);
119
+ // resolve(true);
120
+ // });
121
+
122
+ process.env["SHOPIFY_ACCELERATE_STORE"] = currentEnvironment.store;
123
+ config.environments[environment] = currentEnvironment;
124
+ config.environment = environment;
125
+ config.theme_path = currentEnvironment?.path;
126
+ config.theme_id = +currentEnvironment?.theme;
127
+ config.store = currentEnvironment?.store?.replace(/\.myshopify\.com/gi, "");
128
+
129
+ buildTheme();
130
+ generateConfigFiles();
131
+ await delay(350);
132
+ buildTheme();
133
+ generateConfigFiles();
134
+
135
+ const cp = child_process.spawn(
136
+ "npx",
137
+ [
138
+ "shopify",
139
+ "theme",
140
+ "share",
141
+ "-s",
142
+ currentEnvironment.store,
143
+ "--path",
144
+ `./themes/${environment}`,
145
+ ],
146
+ {
147
+ shell: true,
148
+ stdio: "inherit",
149
+ }
150
+ );
151
+ cp.on("exit", (e) => {
152
+ resolve(true);
153
+ });
154
+ });
155
+ }
156
+ if (results.theme_action === "list_themes") {
157
+ await new Promise((resolve, reject) => {
158
+ // exec(`shopify theme list -s ${currentEnvironment.store}`, async (error, stdout, stderr) => {
159
+ // console.log(stdout);
160
+ // if (stdout.includes("Press any key to open the login page on your brows")) {
161
+ // console.log("MATCHED!!");
162
+ // }
163
+ // await delay(4500);
164
+ // resolve(true);
165
+ // });
166
+
167
+ const cp = child_process.spawn(
168
+ "npx",
169
+ ["shopify", "theme", "list", "-s", currentEnvironment.store],
170
+ {
171
+ shell: true,
172
+ stdio: "inherit",
173
+ }
174
+ );
175
+ cp.on("exit", () => {
176
+ resolve(true);
177
+ });
178
+ });
179
+ }
180
+ }
181
+ console.log(
182
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: ${chalk.redBright(`Theme ID missing.`)}`
183
+ );
184
+ prompts.push({
185
+ type: "text",
186
+ name: "theme_id",
187
+ message: `Shopify theme id. I.e. \`https://admin.shopify.com/store/${
188
+ currentEnvironment?.store ?? `<store_id>`
189
+ }/themes/${currentEnvironment?.store ? "current" : `<theme_id>`}/editor\``,
190
+ });
191
+ }
192
+
193
+ if (
194
+ (store && currentEnvironment.store !== store) ||
195
+ (theme_id && currentEnvironment.theme !== +theme_id)
196
+ ) {
197
+ prompts.push({
198
+ type: "text",
199
+ name: "environment",
200
+ message: `New environment name:`,
201
+ });
202
+ console.log(
203
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: ${chalk.redBright(
204
+ `Environment mismatch: Please rename the environment or re-run the CLI`
205
+ )}`
206
+ );
207
+ }
208
+
209
+ const results = await userInput(prompts);
210
+
211
+ if (Object.keys(results)?.length) {
212
+ environment = results.environment ?? environment;
213
+ currentEnvironment.path = currentEnvironment.path || `./themes/${environment}`;
214
+ currentEnvironment.theme = +(results.theme_id ?? theme_id ?? currentEnvironment.theme);
215
+ currentEnvironment.store =
216
+ results.store?.replace(/\.myshopify\.com/gi, "") ??
217
+ store?.replace(/\.myshopify\.com/gi, "") ??
218
+ currentEnvironment?.store?.replace(/\.myshopify\.com/gi, "");
219
+ }
220
+
221
+ currentEnvironment.all_presets = !!currentEnvironment?.all_presets;
222
+ currentEnvironment.mode =
223
+ currentEnvironment.mode ?? (environment === "development" ? "development" : "production");
224
+ currentEnvironment.ignore_blocks = currentEnvironment.ignore_blocks ?? "";
225
+ currentEnvironment.ignore_snippets = currentEnvironment.ignore_snippets ?? "";
226
+ currentEnvironment.ignore_layouts = currentEnvironment.ignore_layouts ?? "";
227
+ currentEnvironment.ignore_sections = currentEnvironment.ignore_sections ?? "";
228
+ currentEnvironment.ignore_assets =
229
+ currentEnvironment.ignore_assets ?? "custom.css.liquid,custom.css,custom.js";
230
+
231
+ console.log(currentEnvironment);
232
+ process.env["SHOPIFY_ACCELERATE_STORE"] = currentEnvironment.store;
233
+ config.environments[environment] = currentEnvironment;
234
+ config.environment = environment;
235
+ config.theme_path = currentEnvironment?.path;
236
+ config.theme_id = +currentEnvironment?.theme;
237
+ config.store = currentEnvironment?.store?.replace(/\.myshopify\.com/gi, "");
238
+ config.all_presets = currentEnvironment?.all_presets;
239
+ config.mode = currentEnvironment?.mode;
240
+ config.ignore_blocks =
241
+ currentEnvironment?.ignore_blocks?.split(",").map((str) => str.trim()) ?? [];
242
+ config.ignore_snippets =
243
+ currentEnvironment?.ignore_snippets?.split(",").map((str) => str.trim()) ?? [];
244
+ config.ignore_layouts =
245
+ currentEnvironment?.ignore_layouts?.split(",").map((str) => str.trim()) ?? [];
246
+ config.ignore_sections =
247
+ currentEnvironment?.ignore_sections?.split(",").map((str) => str.trim()) ?? [];
248
+ config.ignore_assets =
249
+ currentEnvironment?.ignore_assets?.split(",").map((str) => str.trim()) ?? [];
250
+
251
+ if (config.mode === "production") {
252
+ config.ignore_layouts.push("theme.liquid");
253
+ config.delete_external_layouts = false;
254
+ config.delete_external_sections = false;
255
+ config.delete_external_snippets = false;
256
+ config.delete_external_blocks = false;
257
+ config.delete_external_assets = false;
258
+ }
259
+
260
+ const { project_root, package_templates } = config;
261
+
262
+ writeOnlyNew(
263
+ path.join(process.cwd(), ".env"),
264
+ readFile(path.join(package_templates, "/.env.template"))
265
+ );
266
+
267
+ if (
268
+ !currentEnvironment?.store ||
269
+ !currentEnvironment?.theme ||
270
+ !currentEnvironment?.path ||
271
+ !environment
272
+ ) {
273
+ throw new Error("Missing information to initialize the theme environment");
274
+ }
275
+
276
+ process.env.THEME_PATH = currentEnvironment?.path;
277
+ writeCompareFile(
278
+ path.join(process.cwd(), "shopify.theme.toml"),
279
+ json2toml({ environments: config.environments }, { newlineAfterSection: true })
280
+ );
281
+ };
@@ -0,0 +1,81 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs";
3
+ import watch from "node-watch";
4
+ import path from "path";
5
+ import { config } from "../../shopify-accelerate-app";
6
+ import { generateBlocksTypes } from "../scaffold-theme/generate-blocks-types";
7
+ import { generateSectionsTypes } from "../scaffold-theme/generate-section-types";
8
+ import { generateSettingTypes } from "../scaffold-theme/generate-setting-types";
9
+ import { getSchemaSources, getSources, isTypeScriptSchema } from "../scaffold-theme/parse-files";
10
+ import { capitalize } from "../utils/capitalize";
11
+ import { writeCompareFile, writeOnlyNew } from "../utils/fs";
12
+
13
+ export const watchHeadless = () => {
14
+ const { folders, theme_path, ignore_assets, delete_external_assets, targets } = config;
15
+
16
+ let running = false;
17
+ watch(
18
+ Object.values(folders)?.filter((folder) => fs.existsSync(folder)),
19
+ { recursive: true },
20
+ (event, name) => {
21
+ if (running) return;
22
+ const startTime = Date.now();
23
+ running = true;
24
+ if (event === "remove") {
25
+ getSources();
26
+ }
27
+ if (isTypeScriptSchema(name)) {
28
+ getSchemaSources();
29
+ generateSectionsTypes();
30
+ generateBlocksTypes();
31
+ generateSettingTypes();
32
+
33
+ const imports = [`import type { FC } from "react";`];
34
+ const renderBlocks = [];
35
+ Object.entries(config.sources.sectionSchemas ?? {})?.forEach(([key, entry]) => {
36
+ imports.push(
37
+ `import { ${capitalize(key)} } from "sections/${entry.folder}/${entry.folder}";`
38
+ );
39
+ renderBlocks.push(` case "${entry.folder}": {
40
+ return <${capitalize(key)} {...section} />;
41
+ }`);
42
+
43
+ writeOnlyNew(
44
+ entry.path?.replace("schema.ts", `${entry.folder}.tsx`),
45
+ `import type { ${capitalize(key)}Section } from "types/sections";
46
+
47
+ export const ${capitalize(key)} = ({ id, type, settings, blocks, disabled }: ${capitalize(
48
+ key
49
+ )}Section) => {
50
+ if (disabled) return null
51
+
52
+ return <>${capitalize(key)}</>;
53
+ };
54
+ `
55
+ );
56
+ });
57
+ imports.push('import type { Sections } from "types/sections";');
58
+ imports.push("");
59
+ imports.push("type RenderSectionProps = { section: Sections; };");
60
+ imports.push("");
61
+ imports.push("export const RenderSection: FC<RenderSectionProps> = ({ section }) => {");
62
+ imports.push(" switch (section.type) {");
63
+ imports.push(...renderBlocks);
64
+ imports.push(" }");
65
+ imports.push("};");
66
+
67
+ writeCompareFile(
68
+ path.join(process.cwd(), "./app/[...slug]/render-section.tsx"),
69
+ imports.join("\n")
70
+ );
71
+
72
+ console.log(
73
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: [${chalk.magentaBright(
74
+ `${Date.now() - startTime}ms`
75
+ )}] ${chalk.cyan(`File modified: ${name.replace(process.cwd(), "")}`)}`
76
+ );
77
+ }
78
+ running = false;
79
+ }
80
+ );
81
+ };
@@ -0,0 +1,84 @@
1
+ import chalk from "chalk";
2
+ import fs from "fs";
3
+ import watch from "node-watch";
4
+ import path from "path";
5
+ import { config, root_dir } from "../../shopify-accelerate-app";
6
+ import { generateBlocksTypes } from "../scaffold-theme/generate-blocks-types";
7
+ import { generateLiquidFiles } from "../scaffold-theme/generate-liquid-files";
8
+ import { generateSchemaLocales } from "../scaffold-theme/generate-schema-locales";
9
+ import { generateSchemaVariables } from "../scaffold-theme/generate-schema-variables";
10
+ import { generateSectionsTypes } from "../scaffold-theme/generate-section-types";
11
+ import { generateSettingTypes } from "../scaffold-theme/generate-setting-types";
12
+ import { getSchemaSources, getSources, getTargets, isAsset, isBlockTs, isLiquid, isSectionTs, isTypeScriptSchema } from "../scaffold-theme/parse-files";
13
+ import { parseLocales } from "../scaffold-theme/parse-locales";
14
+ import { deleteFile, writeCompareFile, writeOnlyNew } from "../utils/fs";
15
+
16
+ export const watchTheme = () => {
17
+ const { folders, theme_path, ignore_assets, delete_external_assets, targets } = config;
18
+
19
+ let running = false;
20
+ watch(Object.values(folders), { recursive: true }, (event, name) => {
21
+ if (running) return;
22
+ const startTime = Date.now();
23
+ running = true;
24
+ if (event === "remove") {
25
+ getSources();
26
+ getTargets();
27
+ }
28
+ if (isTypeScriptSchema(name)) {
29
+ getTargets();
30
+ getSchemaSources();
31
+ parseLocales();
32
+ generateSchemaVariables();
33
+ generateSchemaLocales();
34
+ generateSectionsTypes();
35
+ generateBlocksTypes();
36
+ generateSettingTypes();
37
+ generateLiquidFiles();
38
+ console.log(
39
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: [${chalk.magentaBright(
40
+ `${Date.now() - startTime}ms`
41
+ )}] ${chalk.cyan(`File modified: ${name.replace(process.cwd(), "")}`)}`
42
+ );
43
+ }
44
+ if (isAsset(name)) {
45
+ const fileName = name.split(/[\\/]/gi).at(-1);
46
+ const targetPath = path.join(process.cwd(), theme_path, "assets", fileName);
47
+
48
+ if (event !== "remove") {
49
+ const rawContent = fs.readFileSync(name, { encoding: "utf-8" });
50
+
51
+ if (ignore_assets?.includes(targetPath.split(/[/\\]/)?.at(-1))) {
52
+ console.log(
53
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: ${chalk.greenBright(
54
+ `Ignored: ${targetPath.replace(process.cwd(), "")}`
55
+ )}`
56
+ );
57
+ writeOnlyNew(targetPath, rawContent);
58
+ } else {
59
+ writeCompareFile(targetPath, rawContent);
60
+ }
61
+ }
62
+
63
+ if (event === "remove" && delete_external_assets) {
64
+ const targetFile = fs.existsSync(targetPath);
65
+
66
+ if (targetFile) {
67
+ deleteFile(targetPath);
68
+ }
69
+ }
70
+ }
71
+ if (isLiquid(name) || isSectionTs(name) || isBlockTs(name)) {
72
+ getTargets();
73
+ getSources();
74
+ generateSchemaVariables();
75
+ generateLiquidFiles();
76
+ console.log(
77
+ `[${chalk.gray(new Date().toLocaleTimeString())}]: [${chalk.magentaBright(
78
+ `${Date.now() - startTime}ms`
79
+ )}] ${chalk.cyan(`File modified: ${name.replace(process.cwd(), "")}`)}`
80
+ );
81
+ }
82
+ running = false;
83
+ });
84
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "ts-node": {
3
+ "require": ["tsconfig-paths/register"],
4
+ "exposeGC": true,
5
+ // these options are overrides used only by ts-node
6
+ // same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable
7
+ "compilerOptions": {
8
+ "module": "commonjs",
9
+ "target": "es6",
10
+ // "esModuleInterop": true,
11
+ "baseUrl": ".",
12
+ "paths": {
13
+ "types/*": [
14
+ "./@types/*"
15
+ ],
16
+ "utils/*": [
17
+ "./utils/*"
18
+ ],
19
+ "*": [
20
+ "*"
21
+ ],
22
+ "/*": [
23
+ "./*"
24
+ ]
25
+ },
26
+ "rootDirs": [
27
+ "."
28
+ ],
29
+ "esModuleInterop": true,
30
+ // "module": "esnext",
31
+ "moduleResolution": "node",
32
+ "resolveJsonModule": true,
33
+ "isolatedModules": true
34
+ },
35
+ "swc": true
36
+ },
37
+ "compilerOptions": {
38
+ "baseUrl": ".",
39
+ "allowSyntheticDefaultImports": true,
40
+ "paths": {
41
+ "types/*": [
42
+ "./@types/*"
43
+ ],
44
+ "utils/*": [
45
+ "./utils/*"
46
+ ],
47
+ "*": [
48
+ "*"
49
+ ]
50
+ },
51
+ "downlevelIteration": true,
52
+ "target": "ES2020",
53
+ "lib": [
54
+ "dom",
55
+ "dom.iterable",
56
+ "esnext"
57
+ ],
58
+ "allowJs": true,
59
+ "skipLibCheck": true,
60
+ "strict": false,
61
+ "strictNullChecks": false,
62
+ "forceConsistentCasingInFileNames": true,
63
+ "noEmit": true,
64
+ "incremental": true,
65
+ "esModuleInterop": true,
66
+ "module": "esnext",
67
+ "moduleResolution": "node",
68
+ "resolveJsonModule": true,
69
+ "isolatedModules": true,
70
+ "jsx": "preserve",
71
+ "rootDirs": [
72
+ "."
73
+ ]
74
+ },
75
+ "include": [
76
+ "**/*.ts",
77
+ "**/*.tsx"
78
+ ],
79
+ "exclude": [
80
+ "node_modules"
81
+ ]
82
+ }