@softlimit/theme-envy 0.1.2-alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. package/.eslintrc.js +26 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/workflows/release-please.yml +19 -0
  4. package/LICENSE +21 -0
  5. package/README.md +259 -0
  6. package/build/functions/failed-hook-installs.js +18 -0
  7. package/build/functions/get-all.js +102 -0
  8. package/build/functions/index.js +7 -0
  9. package/build/functions/liquid/functions/extend-liquid.js +77 -0
  10. package/build/functions/liquid/functions/flatten-shopify-directory-structure.js +29 -0
  11. package/build/functions/liquid/functions/index.js +6 -0
  12. package/build/functions/liquid/functions/list-dependencies.js +47 -0
  13. package/build/functions/liquid/functions/section-schema-inject.js +26 -0
  14. package/build/functions/liquid/index.js +36 -0
  15. package/build/functions/parent-theme-files.js +25 -0
  16. package/build/functions/tailwind.js +31 -0
  17. package/build/functions/theme-envy.js +85 -0
  18. package/build/functions/webpack.js +44 -0
  19. package/build/index.js +45 -0
  20. package/build/requires/assets.js +22 -0
  21. package/build/requires/config.js +44 -0
  22. package/build/requires/globals/index.js +10 -0
  23. package/build/requires/globals/parent-theme.js +28 -0
  24. package/build/requires/globals/prepare-install-hooks-schema.js +58 -0
  25. package/build/requires/globals/progress-bar.js +52 -0
  26. package/build/requires/globals/theme-require.js +190 -0
  27. package/build/requires/index.js +21 -0
  28. package/build/requires/locales.js +16 -0
  29. package/build/requires/scripts/index.js +5 -0
  30. package/build/requires/scripts/public-path.js +9 -0
  31. package/build/requires/scripts/script-builders/elements.build.js +44 -0
  32. package/build/requires/scripts/script-builders/features.build.js +15 -0
  33. package/build/requires/scripts/theme-envy.js +7 -0
  34. package/build/requires/snippets/index.js +11 -0
  35. package/build/requires/snippets/liquid-builders/theme-envy.liquid.build.js +21 -0
  36. package/build/requires/styles/index.js +1 -0
  37. package/build/requires/styles/styles-builders/theme-envy.css.js +11 -0
  38. package/build/requires/styles/tailwind-base.css +3 -0
  39. package/build/requires/templates.js +20 -0
  40. package/build/theme-envy.config.js +71 -0
  41. package/convert/functions/convert-sections-to-features.js +56 -0
  42. package/convert/functions/detect-children.js +30 -0
  43. package/convert/functions/index.js +6 -0
  44. package/convert/functions/install-hooks.js +68 -0
  45. package/convert/functions/set-settings-schema-js.js +14 -0
  46. package/convert/index.js +50 -0
  47. package/helpers/functions/dev.js +15 -0
  48. package/helpers/functions/dist-clean.js +19 -0
  49. package/helpers/functions/ensure-directories.js +26 -0
  50. package/helpers/functions/find-orphans.js +20 -0
  51. package/helpers/functions/global-theme-envy.js +20 -0
  52. package/helpers/functions/liquid-prettify.js +24 -0
  53. package/helpers/functions/liquid-tree/functions/count-results.js +13 -0
  54. package/helpers/functions/liquid-tree/functions/get-depth.js +12 -0
  55. package/helpers/functions/liquid-tree/functions/get-file-info.js +11 -0
  56. package/helpers/functions/liquid-tree/functions/index.js +5 -0
  57. package/helpers/functions/liquid-tree/index.js +48 -0
  58. package/helpers/functions/liquid-tree/objects/dependencies.js +74 -0
  59. package/helpers/functions/liquid-tree/objects/index.js +3 -0
  60. package/helpers/functions/log-symbols.js +28 -0
  61. package/helpers/functions/pull-json.js +22 -0
  62. package/helpers/functions/scaffold-new/functions/element.js +15 -0
  63. package/helpers/functions/scaffold-new/functions/feature.js +76 -0
  64. package/helpers/functions/scaffold-new/functions/index.js +6 -0
  65. package/helpers/functions/scaffold-new/functions/load-dir.js +24 -0
  66. package/helpers/functions/scaffold-new/functions/starter-content.js +21 -0
  67. package/helpers/functions/scaffold-new/functions/upper-first-letter.js +3 -0
  68. package/helpers/functions/scaffold-new/index.js +28 -0
  69. package/helpers/functions/scaffold-new/objects/index.js +3 -0
  70. package/helpers/functions/scaffold-new/objects/starter-configs.js +7 -0
  71. package/helpers/functions/unicode-supported.js +21 -0
  72. package/helpers/index.js +10 -0
  73. package/index.js +190 -0
  74. package/init/functions/add-theme-envy-features/features/theme-envy/install.js +6 -0
  75. package/init/functions/add-theme-envy-features/index.js +21 -0
  76. package/init/functions/copy-example-feature/example-feature/config/_example-feature.js +8 -0
  77. package/init/functions/copy-example-feature/example-feature/index.js +5 -0
  78. package/init/functions/copy-example-feature/example-feature/install.js +10 -0
  79. package/init/functions/copy-example-feature/example-feature/partials/_example-feature-partial.liquid +3 -0
  80. package/init/functions/copy-example-feature/example-feature/schema/schema-example-feature-schema-partial.js +16 -0
  81. package/init/functions/copy-example-feature/example-feature/schema/schema-example-feature-section.js +14 -0
  82. package/init/functions/copy-example-feature/example-feature/scripts/example-feature.js +3 -0
  83. package/init/functions/copy-example-feature/example-feature/sections/example-feature-section.liquid +11 -0
  84. package/init/functions/copy-example-feature/example-feature/snippets/example-feature-snippet.liquid +1 -0
  85. package/init/functions/copy-example-feature/index.js +25 -0
  86. package/init/functions/copy-starter-config-files/configs/postcss.config.js +9 -0
  87. package/init/functions/copy-starter-config-files/configs/tailwind.config.js +16 -0
  88. package/init/functions/copy-starter-config-files/configs/theme.config.js +25 -0
  89. package/init/functions/copy-starter-config-files/index.js +28 -0
  90. package/init/functions/copy-starter-config-files/utils/starter-config.js +17 -0
  91. package/init/functions/copy-starter-config-files/utils/starter-element.js +16 -0
  92. package/init/functions/copy-starter-config-files/utils/starter-install.js +14 -0
  93. package/init/functions/copy-starter-config-files/utils/starter-schema.js +32 -0
  94. package/init/functions/copy-starter-config-files/utils/starter-section.js +16 -0
  95. package/init/functions/create-empty-settings-data.js +19 -0
  96. package/init/functions/create-settings-schema.js +29 -0
  97. package/init/functions/if-shopify-theme-exists.js +26 -0
  98. package/init/functions/import-from-git.js +21 -0
  99. package/init/functions/index.js +10 -0
  100. package/init/functions/validate-source-theme.js +28 -0
  101. package/init/index.js +87 -0
  102. package/package.json +88 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @file Processes our liquid files during build
3
+ * @description replaces our custom hooks, partials, and theme tags, and injects section schema into section files
4
+ * @param {string} file - path to file
5
+ * @param {string} mode - 'development' or 'production'
6
+ * @returns {Void}
7
+ */
8
+ const path = require('path')
9
+ const fs = require('fs-extra')
10
+ const { extendLiquid, flattenShopifyDirectoryStructure, sectionSchemaInject } = require('./functions')
11
+ const { liquidPrettify } = require('#Helpers')
12
+
13
+ module.exports = function({ file, mode, verbose }) {
14
+ const shopifyPath = flattenShopifyDirectoryStructure(file)
15
+ // skip files that don't need to be processed because they don't have an output path
16
+ if (!shopifyPath) return
17
+
18
+ const outputPath = `${ThemeEnvy.outputPath}/${shopifyPath}`
19
+ let source = fs.readFileSync(file, 'utf8')
20
+
21
+ // inject schema .js into liquid section files
22
+ if (file.includes('sections/')) source = sectionSchemaInject({ source, filePath: file })
23
+
24
+ // apply our custom liquid tags: partial, hook, theme
25
+ source = extendLiquid({ source, filePath: file })
26
+
27
+ // prettify our liquid if possible
28
+ if (mode === 'production') source = liquidPrettify({ source, pathname: outputPath, verbose })
29
+
30
+ // create output directory if it doesn't exist
31
+ fs.ensureDirSync(path.dirname(outputPath))
32
+ // save our file
33
+ fs.writeFileSync(outputPath, source)
34
+ // update progress bar
35
+ ThemeEnvy.progress.increment('liquid', 1)
36
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Get all files from the parent theme that are not in the child
3
+ * @private
4
+ * @param {function} func - function to get files with a glob, should return an array of file paths
5
+ * @param {array} childFiles - array of file paths from the child theme that we check against
6
+ * @returns {array} - array of file paths from the parent theme that are not in the child
7
+ */
8
+ const path = require('path')
9
+ const { directories } = require('#EnsureDirectories')
10
+
11
+ module.exports = (func, childFiles, type) => {
12
+ const childRelative = childFiles.map(file => path.relative(ThemeEnvy.themePath, file))
13
+ // get all files from the parent theme, using only directories listed in ThemeConfig
14
+ const only = [...ThemeEnvy.parentTheme.elements, ...ThemeEnvy.parentTheme.features]
15
+ if (type === 'schema') {
16
+ only.push('schema')
17
+ }
18
+ if (type === 'liquid') {
19
+ only.push(...directories.map(dir => path.resolve(ThemeEnvy.parentTheme, dir)))
20
+ }
21
+ if (type === 'sectionGroups') {
22
+ only.push(path.resolve(ThemeEnvy.parentTheme, 'sections'))
23
+ }
24
+ return func(ThemeEnvy.parentTheme, only).filter(file => !childRelative.includes(path.relative(ThemeEnvy.parentTheme, file)))
25
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @private
3
+ * @file Runs Tailwind using the Tailwind CLI during build
4
+ * @see https://tailwindcss.com/docs/installation#using-tailwind-cli
5
+ */
6
+ const { spawn } = require('child_process')
7
+ const path = require('path')
8
+
9
+ module.exports = function({ mode, opts }) {
10
+ return new Promise((resolve, reject) => {
11
+ const watch = opts.watch || false
12
+ const verbose = opts.verbose || false
13
+
14
+ // run tailwind
15
+ const tailwindCss = path.resolve(__dirname, '../requires/styles/theme-envy.css')
16
+ const tailwindOpts = ['tailwindcss', 'build', '-i', tailwindCss, '-o', `${ThemeEnvy.outputPath}/assets/theme-envy.critical.css`]
17
+ if (mode === 'production') tailwindOpts.push('--minify')
18
+ if (watch) tailwindOpts.push('--watch')
19
+ const tailwindOutput = verbose ? { stdio: 'inherit' } : {}
20
+ const tailwindProcess = spawn('npx', tailwindOpts, tailwindOutput)
21
+ if (watch) {
22
+ // watch process does not exit, so we need to increment the progress bar and resolve the promise
23
+ ThemeEnvy.progress.increment('tailwind')
24
+ resolve()
25
+ }
26
+ tailwindProcess.on('exit', () => {
27
+ ThemeEnvy.progress.increment('tailwind')
28
+ resolve()
29
+ })
30
+ })
31
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @private
3
+ * @file Manages ThemeEnvy build process
4
+ * @param {Object} opts
5
+ * @param {Boolean} opts.watch
6
+ * @param {Boolean} opts.verbose
7
+ * @returns {Promise}
8
+ */
9
+ const fs = require('fs-extra')
10
+ const path = require('path')
11
+ const liquid = require('./liquid')
12
+ const getAll = require('./get-all')
13
+ const failedHookInstalls = require('./failed-hook-installs')
14
+
15
+ module.exports = function({ mode, opts }) {
16
+ return new Promise((resolve, reject) => {
17
+ const verbose = opts.verbose || false
18
+ // build files
19
+ build({ mode, verbose })
20
+ .then(() => {
21
+ // watch for changes
22
+ if (opts.watch) {
23
+ ThemeEnvy.events.on('build:complete', () => {
24
+ watch({ mode, verbose })
25
+ })
26
+ }
27
+ resolve()
28
+ })
29
+ })
30
+ }
31
+
32
+ function build({ mode, files = [], verbose }) {
33
+ return new Promise((resolve, reject) => {
34
+ if (files.length > 0) {
35
+ // remove partials and schema = require(files list)
36
+ files = files.filter((file) => !file.includes('partials/') && !file.includes('schema/'))
37
+ }
38
+ /*
39
+ if we have files passed in (during watch process), use those
40
+ otherwise glob for all liquid files
41
+ */
42
+ const liquidFiles = files.length > 0
43
+ ? files.filter(file => file.includes('.liquid'))
44
+ : getAll('liquid')
45
+
46
+ const sectionGroups = files.length > 0
47
+ ? files.filter(file => file.includes('.json'))
48
+ : getAll('sectionGroups')
49
+
50
+ // process all liquid files and output to dist directory
51
+ if (liquidFiles.length > 0) {
52
+ liquidFiles.forEach((file) => {
53
+ liquid({ file, mode, verbose })
54
+ })
55
+ }
56
+ // check for install hooks that reference non-existent hooks
57
+ failedHookInstalls()
58
+ ThemeEnvy.progress.increment('failedHookInstalls')
59
+
60
+ // copy sectionGroup files to dist
61
+ if (sectionGroups.length > 0) {
62
+ sectionGroups.forEach((file) => {
63
+ fs.copyFileSync(file, path.resolve(ThemeEnvy.outputPath, 'sections', path.basename(file)))
64
+ })
65
+ }
66
+ ThemeEnvy.progress.increment('sectionGroups')
67
+ resolve()
68
+ })
69
+ }
70
+
71
+ function watch({ mode, verbose }) {
72
+ const chokidar = require('chokidar')
73
+ console.log('watching for changes...')
74
+ chokidar.watch(ThemeEnvy.themePath).on('change', (path) => {
75
+ ThemeEnvy.events.emit('watch:start')
76
+ const isJSONTemplate = path.includes('templates/') && path.extname(path) === '.json'
77
+ if (!isJSONTemplate) {
78
+ console.log(`updated: ${path.split(ThemeEnvy.themePath + '/')[1]}`)
79
+ build({ files: [path], mode })
80
+ .then(() => {
81
+ // rebuild complete
82
+ })
83
+ }
84
+ })
85
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @private
3
+ * @file Runs webpack during ThemeEnvy build
4
+ * @param {Object} options - options object
5
+ * @param {string} options.mode - webpack mode, either 'development' or 'production'
6
+ * @param {Object} options.opts - options object
7
+ * @param {Boolean} options.opts.watch - whether or not to run webpack in watch mode
8
+ * @returns {Promise}
9
+ */
10
+
11
+ const webpack = require('webpack')
12
+ const getAll = require('./get-all')
13
+
14
+ module.exports = function({ mode, opts }) {
15
+ return new Promise((resolve, reject) => {
16
+ const watch = opts.watch || false
17
+
18
+ const webpackConfig = require('#Build/theme-envy.config.js')
19
+ // run webpack
20
+ // set our webpack mode
21
+ webpackConfig.mode = mode
22
+ // set our webpack optimization
23
+ webpackConfig.optimization.minimize = mode === 'production'
24
+ // set our webpack watch flag
25
+ webpackConfig.watch = watch
26
+ // merge our theme config named entries into webackConfig.entry
27
+ webpackConfig.entry = { ...webpackConfig.entry, ...ThemeEnvy.entry }
28
+
29
+ if (ThemeEnvy?.tailwind === false && getAll('criticalCSS').length > 0) {
30
+ // if tailwind is disabled, we need to add our critical css to the entry list
31
+ webpackConfig.entry['theme-envy.critical'] = `${ThemeEnvy.paths.build}/requires/styles/theme-envy.css`
32
+ }
33
+
34
+ webpack(webpackConfig, (err, stats) => {
35
+ if (err || stats.hasErrors()) {
36
+ console.log(stats, err)
37
+ reject(err)
38
+ }
39
+ // Done processing - finish up progress bar and make up for our initial increment
40
+ ThemeEnvy.progress.increment('webpack')
41
+ resolve(stats)
42
+ })
43
+ })
44
+ }
package/build/index.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @file Processes all liquid files in the themePath directory and output them to the outputPath directory.
3
+ * @param {string} env - The mode to build in, either 'development' or 'production'
4
+ * @param {Object} opts - The options object
5
+ * @param {boolean} opts.verbose - Whether or not to log warnings
6
+ * @param {boolean} opts.watch - Whether or not to watch for changes
7
+ * @example
8
+ * // build in production mode
9
+ * build('production')
10
+ * @example
11
+ * // run from the CLI
12
+ * npx theme-envy build [production|development] [--watch|w] [--verbose|v]
13
+ */
14
+
15
+ const path = require('path')
16
+ const chalk = require('chalk')
17
+ const emoji = require('node-emoji')
18
+ const { themeEnvy, webpack, tailwind } = require('#Build/functions')
19
+ const { distClean } = require('#Helpers')
20
+
21
+ module.exports = async function(env, opts = {}) {
22
+ const mode = env || 'production'
23
+
24
+ // empty dist folder for a clean build, do not log the clean message
25
+ distClean({ quiet: true })
26
+
27
+ // our pretty build message
28
+ const relativeDistPath = path.relative(process.cwd(), ThemeEnvy.outputPath)
29
+ console.log(
30
+ emoji.get('hammer'),
31
+ chalk.cyan(`Building ./${relativeDistPath} in`),
32
+ mode === 'development' ? chalk.yellow.bold(mode) : chalk.magenta.bold(mode),
33
+ chalk.cyan('mode')
34
+ )
35
+
36
+ require('./requires')
37
+
38
+ await themeEnvy({ mode, opts })
39
+
40
+ if (ThemeEnvy?.tailwind !== false) await tailwind({ mode, opts })
41
+
42
+ await webpack({ mode, opts })
43
+
44
+ ThemeEnvy.events.emit('build:complete')
45
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @file Copies all locales files from the src folder to the dist folder with no transformations
3
+ */
4
+
5
+ const fs = require('fs-extra')
6
+ const path = require('path')
7
+ const glob = require('glob')
8
+
9
+ const assets = glob.sync(path.resolve(ThemeEnvy.themePath, '**/assets/**/*.*'))
10
+
11
+ // if dist/assets doesn't exist create it
12
+ fs.ensureDirSync(path.resolve(ThemeEnvy.outputPath, 'assets'))
13
+
14
+ assets.forEach(asset => {
15
+ try {
16
+ fs.copySync(asset, path.resolve(ThemeEnvy.outputPath, 'assets', path.basename(asset)))
17
+ // update progress bar
18
+ ThemeEnvy.progress.increment('assets', 1)
19
+ } catch (err) {
20
+ console.error(err)
21
+ }
22
+ })
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @file collects and concatenates all config/*.js files into a single settings_schema.json file
3
+ */
4
+
5
+ const path = require('path')
6
+ const fs = require('fs-extra')
7
+ const { getAll } = require('#Build/functions')
8
+ const requiredModules = []
9
+ // glob all our config/*.js files
10
+ const writeSettingsSchema = () => {
11
+ const configInputPath = path.resolve(ThemeEnvy.themePath, 'config')
12
+ const configOutputPath = path.resolve(ThemeEnvy.outputPath, 'config')
13
+
14
+ const settingsSchemaPath = path.resolve(configInputPath, 'settings_schema.js')
15
+ const settingsSchema = require(settingsSchemaPath)
16
+ if (requiredModules.indexOf(settingsSchemaPath) === -1) requiredModules.push(settingsSchemaPath)
17
+ const globbedConfigs = getAll('config')
18
+ .map(file => {
19
+ if (requiredModules.indexOf(file) === -1) requiredModules.push(file)
20
+ return require(file)
21
+ })
22
+ .flat()
23
+ // sort alphabetically by name
24
+ .sort((a, b) => (a.name > b.name) ? 1 : -1)
25
+
26
+ const config = [...settingsSchema, ...globbedConfigs].flat()
27
+
28
+ fs.ensureDirSync(configOutputPath)
29
+ fs.writeFileSync(path.resolve(configOutputPath, 'settings_schema.json'), JSON.stringify(config, null, 2))
30
+ fs.copyFileSync(path.resolve(configInputPath, 'settings_data.json'), path.resolve(configOutputPath, 'settings_data.json'))
31
+ // update progress bar
32
+ ThemeEnvy.progress.increment('config')
33
+ }
34
+
35
+ writeSettingsSchema()
36
+
37
+ ThemeEnvy.events.on('watch:start', () => {
38
+ // clear node cache of ThemeRequired modules
39
+ requiredModules.forEach(module => {
40
+ delete require.cache[require.resolve(module)]
41
+ })
42
+ requiredModules.length = 0
43
+ writeSettingsSchema()
44
+ })
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @private
3
+ * @file Our global requires
4
+ */
5
+
6
+ require('./progress-bar')
7
+ require('./parent-theme')
8
+ require('./theme-require')
9
+ require('./prepare-install-hooks-schema')
10
+ ThemeEnvy.progress.increment('globals')
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @private
3
+ * @file Checks for a parent theme in node_modules or relative path and sets ThemeEnvy.parentTheme to the path
4
+ */
5
+
6
+ const fs = require('fs-extra')
7
+ const path = require('path');
8
+
9
+ (() => {
10
+ const ThemeConfigPath = path.resolve(process.cwd(), 'theme.config.js')
11
+ if (!fs.existsSync(ThemeConfigPath)) return
12
+ ThemeEnvy.childPreferred = ThemeEnvy.childPreferred || []
13
+
14
+ // look for parent theme in node_modules
15
+ const parentThemePath = ThemeEnvy.parentTheme?.path
16
+ if (!parentThemePath) return
17
+ const parentThemePkg = fs.existsSync(path.resolve(process.cwd(), 'node_modules', parentThemePath))
18
+ ? path.resolve(process.cwd(), 'node_modules', parentThemePath)
19
+ : false
20
+ // look for parent theme in relative path
21
+ const parentThemeRelative = fs.existsSync(path.resolve(process.cwd(), parentThemePath))
22
+ ? path.resolve(process.cwd(), parentThemePath)
23
+ : false
24
+ // prefer node_modules over relative path
25
+ const parentTheme = parentThemePkg || parentThemeRelative
26
+
27
+ ThemeEnvy.parentTheme = parentTheme
28
+ })()
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @private
3
+ * @file Collect all the install.js files that insert code into the theme hooks
4
+ * @description
5
+ * Find all files in the src directory that end with install.js
6
+ * These files should export an array of objects with the following properties:
7
+ * hook: the name of the hook to insert the code into
8
+ * content: the code to insert
9
+ * priority: the priority of the code, 0-100, default 50
10
+ *
11
+ module.exports = [
12
+ {
13
+ hook: 'head-start',
14
+ content: '{% partial "_test-partial" %}',
15
+ priority: 50
16
+ }
17
+ ]
18
+ */
19
+ const { getAll } = require('#Build/functions')
20
+ const path = require('path');
21
+ (() => {
22
+ const installs = getAll('installs')
23
+ // collect all installs
24
+ ThemeEnvy.installs = installs.map(file => require(path.resolve(file))).flat()
25
+
26
+ const hooks = ThemeEnvy.installs.filter(entry => entry.hook)
27
+ const schema = ThemeEnvy.installs.filter(entry => !entry.hook)
28
+
29
+ ThemeEnvy.hooks = {}
30
+ ThemeEnvy.schema = {}
31
+ // group all hooks together by target file and hook
32
+ // This is so we can install hooks once per destination file...
33
+ hooks.forEach(entry => {
34
+ ThemeEnvy.hooks[entry.hook] = ThemeEnvy.hooks[entry.hook] || []
35
+ // add the hook to the destination, process entry.content for softlimit partials
36
+ // priority 0-100, default 50
37
+ ThemeEnvy.hooks[entry.hook].push({ content: entry.content, priority: entry.priority || 50 })
38
+ })
39
+ // sort hook content by priority and concat into a single string
40
+ Object.keys(ThemeEnvy.hooks).forEach(hook => {
41
+ ThemeEnvy.hooks[hook] = {
42
+ content: ThemeEnvy.hooks[hook].sort((a, b) => a.priority - b.priority).map(entry => entry.content).join('\n')
43
+ }
44
+ })
45
+
46
+ // TODO: LOOK INTO THIS
47
+ schema.forEach(entry => {
48
+ const file = `${entry.file}.liquid`
49
+ ThemeEnvy.schema[file] = ThemeEnvy.schema[file] || {
50
+ settings: [],
51
+ blocks: [],
52
+ }
53
+ // add the schema to the destination, process entry.content for softlimit partials
54
+ // target is either 'settings' or 'blocks'
55
+ const target = entry.target.split('.')[1]
56
+ ThemeEnvy.schema[file][target].push(entry.content)
57
+ })
58
+ })()
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @private
3
+ * @file Manages our progress bar during the build process
4
+ */
5
+ const { getAll } = require('#Build/functions')
6
+ const cliProgress = require('cli-progress')
7
+ const colors = require('ansi-colors');
8
+
9
+ (() => {
10
+ // setup all of our progress bar steps
11
+ // these are then used to increment the progress bar
12
+ // ThemeEnvy.progress.increment('label', step)
13
+ const counts = {
14
+ assets: getAll('assets').length,
15
+ config: 1,
16
+ failedHookInstalls: 1,
17
+ globals: 1,
18
+ liquid: getAll('liquid').length,
19
+ locales: 1,
20
+ requires: 7,
21
+ sectionGroups: 1,
22
+ tailwind: ThemeEnvy?.tailwind !== false ? 1 : 0,
23
+ templates: getAll('templates').length,
24
+ webpack: 1,
25
+ }
26
+ const bar = new cliProgress.SingleBar({
27
+ format: colors.cyan('{bar}') + ' {percentage}% | {duration_formatted}',
28
+ barCompleteChar: '\u2588',
29
+ barIncompleteChar: '\u2591',
30
+ hideCursor: true,
31
+ }, cliProgress.Presets.shades_classic)
32
+
33
+ ThemeEnvy.progress = {
34
+ bar,
35
+ increment(label, step) {
36
+ /**
37
+ * @param {string} label - the label of the progress bar to increment
38
+ * @param {number} step - the number of steps to increment the progress bar by if different than the entry in counts
39
+ */
40
+ step = step || (counts[label] ? counts[label] : 1)
41
+ bar.increment(step)
42
+ }
43
+ }
44
+
45
+ const totalBuildSteps = Object.values(counts).reduce((a, b) => a + b, 0)
46
+
47
+ bar.start(totalBuildSteps, 0)
48
+
49
+ ThemeEnvy.events.on('build:complete', () => {
50
+ bar.stop()
51
+ })
52
+ })()
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @file defines our global ThemeRequire function used for loading schema files
3
+ @param {string} file - the filename to load
4
+ @param {object} options - options to pass to the ThemeRequire function
5
+ @param {array} options.delete - an array of schema keys to delete
6
+ @param {object} options.extend - an object of schema keys to extend
7
+ @param {number} options.loop - a number of times to loop the schema
8
+ @param {object} options.suffix
9
+ @returns {object} - the schema object
10
+ */
11
+
12
+ const glob = require('glob')
13
+ const path = require('path')
14
+ const requiredModules = []
15
+ const { getAll } = require('#Build/functions')
16
+ const chalk = require('chalk')
17
+ const logSymbols = require('#LogSymbols')
18
+
19
+ let allSchema
20
+
21
+ const setPreGlobs = () => {
22
+ allSchema = getAll('schema')
23
+ }
24
+
25
+ // uncache all required files after a build is complete so changes can be made to partials and schemas in between watch events
26
+ ThemeEnvy.events.on('watch:start', () => {
27
+ // clear node cache of ThemeRequired modules
28
+ requiredModules.forEach(module => {
29
+ delete require.cache[require.resolve(module)]
30
+ })
31
+ requiredModules.length = 0
32
+ setPreGlobs()
33
+ })
34
+
35
+ const ThemeRequire = (file, options) => {
36
+ // check for file in our pre-globbed arrays
37
+ const ref = getFile(file)
38
+
39
+ if (ref.length === 0) {
40
+ console.error(logSymbols.error, chalk.red('Error:'), `Could not find file ${file}`)
41
+ process.exit()
42
+ }
43
+
44
+ const filePath = path.resolve(process.cwd(), ref[0])
45
+ let content
46
+ try {
47
+ content = require(filePath)
48
+ if (requiredModules.indexOf(filePath) === -1) requiredModules.push(filePath)
49
+ } catch (error) {
50
+ console.error('\n', logSymbols.error, chalk.red('Error:'), `Invalid JSON, ${error}\n`, chalk.dim(error.stack.split(error)[0]))
51
+ process.exit()
52
+ }
53
+
54
+ // Add schema extend/delete support
55
+ /*
56
+ * ***********************
57
+ * Example usage:
58
+ * ***********************
59
+ const textLockupBlock = ThemeRequire(
60
+ 'schema/schema-text-lockup-block.json',
61
+ {
62
+ delete: ['Text Background', 'text_bg_opacity'],
63
+ extend: {
64
+ title: {
65
+ default: 'Hello World'
66
+ }
67
+ }
68
+ loop: 3,
69
+ suffix: 2
70
+ })
71
+ */
72
+
73
+ // if module.export is a function, call it with options.args
74
+ if (typeof content === 'function') {
75
+ content = content(options?.args)
76
+ }
77
+
78
+ if (options?.extend || options?.delete || options?.loop || options?.suffix || options?.args) {
79
+ // make copy of the schema to modify so we don't transform the original
80
+ content = parseJson(content)
81
+ }
82
+
83
+ if (options?.delete) {
84
+ content = schemaDelete(content, options.delete)
85
+ }
86
+ if (options?.extend) {
87
+ content = schemaExtend(content, options.extend)
88
+ }
89
+ if (options?.loop) {
90
+ content = schemaLoop(file, content, options.loop)
91
+ }
92
+ if (options?.suffix) {
93
+ content = schemaSuffix(file, content, options.suffix)
94
+ }
95
+
96
+ if (options?.loader) {
97
+ ThemeEnvy.dependencies[options.loader] = ThemeEnvy.dependencies[options.loader] || []
98
+ ThemeEnvy.dependencies[options.loader].push(filePath)
99
+ }
100
+
101
+ return content
102
+ }
103
+
104
+ function schemaDelete(file, src, del) {
105
+ const replaceWith = src.filter(entry => {
106
+ if (entry.settings) {
107
+ entry.settings = schemaDelete(entry.settings, del)
108
+ return entry
109
+ }
110
+ return ((entry.id ? !del.includes(entry.id) : false) || (entry.content ? !del.includes(entry.content) : false))
111
+ })
112
+ return replaceWith
113
+ }
114
+
115
+ function schemaExtend(src, exts) {
116
+ let replaceWith
117
+ Object.entries(exts).forEach(ext => {
118
+ // map extension to schema
119
+ replaceWith = src.map(entry => {
120
+ if (entry.settings) {
121
+ schemaExtend(entry.settings, exts)
122
+ }
123
+ if (entry.id !== ext[0] && entry.content !== ext[0]) return entry
124
+ Object.entries(ext[1]).forEach(val => {
125
+ entry[val[0]] = val[1]
126
+ })
127
+ return entry
128
+ })
129
+ })
130
+ return replaceWith
131
+ }
132
+
133
+ function schemaLoop(file, src, loop) {
134
+ const replaceWith = []
135
+ for (let i = 1; i < (loop + 1); i++) {
136
+ const srcCopy = parseJson(src)
137
+ srcCopy.map(entry => {
138
+ if (entry.id) entry.id = `${entry.id}_${i}`
139
+ if (entry.label) entry.label = `${entry.label} ${i}`
140
+ if (entry.type === 'header') entry.content = `${entry.content} ${i}`
141
+ return entry
142
+ })
143
+ replaceWith.push(srcCopy)
144
+ }
145
+ return replaceWith
146
+ }
147
+
148
+ function schemaSuffix(file, src, suffix) {
149
+ const replaceWith = parseJson(src)
150
+ replaceWith.map(entry => {
151
+ if (entry.id) entry.id = `${entry.id}_${suffix}`
152
+ if (entry.label) entry.label = `${entry.label} ${suffix}`
153
+ if (entry.type === 'header') entry.content = `${entry.content} ${suffix}`
154
+ return entry
155
+ })
156
+ return replaceWith
157
+ }
158
+
159
+ function getSchemaFile(file) {
160
+ return allSchema.filter(schema => path.basename(schema) === path.basename(file))
161
+ }
162
+
163
+ function getFile(file) {
164
+ if (file.includes('schema')) {
165
+ return getSchemaFile(file)
166
+ } else {
167
+ const res = glob.sync(path.resolve(ThemeEnvy.themePath, `**/${file}`))
168
+ if (ThemeEnvy.parentTheme && res.length === 0) {
169
+ res.push(...glob.sync(path.resolve(ThemeEnvy.parentTheme, `**/${file}`)))
170
+ }
171
+ return res
172
+ }
173
+ }
174
+
175
+ /* parse JSON for schema extend functions, return error if JSON not readable
176
+ @param src [object] - the file contents to be extended
177
+ @param schemaRef [string] - source schema file name being extended
178
+ */
179
+ function parseJson(src, schemaRef) {
180
+ try {
181
+ return JSON.parse(JSON.stringify(src))
182
+ } catch (error) {
183
+ console.error('\n', logSymbols.error, chalk.red('Error:'), `Invalid JSON, ${error}\n`, chalk.dim(error.stack.split(error)[0]))
184
+ }
185
+ }
186
+
187
+ // run setPreGlobs on initial load
188
+ setPreGlobs()
189
+ // set global access to ThemeRequire
190
+ global.ThemeRequire = ThemeRequire