@softlimit/theme-envy 0.1.2-alpha

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 (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