@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,21 @@
1
+ /**
2
+ * @file Require all files in the requires directory
3
+ */
4
+
5
+ require('./globals')
6
+
7
+ const requires = [
8
+ './assets',
9
+ './config',
10
+ './locales',
11
+ './scripts',
12
+ './snippets',
13
+ './styles',
14
+ './templates',
15
+ ]
16
+
17
+ // iterate over requires so we can fire progress one at a time
18
+ requires.forEach((requirePath) => {
19
+ require(requirePath)
20
+ ThemeEnvy.progress.increment('requires', 1)
21
+ })
@@ -0,0 +1,16 @@
1
+ /**
2
+ @file Copies all locales files from the src folder to the dist folder with no transformations
3
+ */
4
+ const fs = require('fs-extra')
5
+ const path = require('path')
6
+
7
+ // if dist/locales does not exist, create it
8
+ fs.ensureDirSync(path.resolve(ThemeEnvy.outputPath, 'locales'))
9
+
10
+ try {
11
+ fs.copySync(path.resolve(ThemeEnvy.themePath, 'locales'), path.resolve(ThemeEnvy.outputPath, 'locales'))
12
+ // update progress bar
13
+ ThemeEnvy.progress.increment('locales')
14
+ } catch (err) {
15
+ console.error(err)
16
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @file Includes our script builders for elements.js and features.js files to be included in our Webpack theme-envy.js entrypoint
3
+ */
4
+ require('./script-builders/elements.build.js')
5
+ require('./script-builders/features.build.js')
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @file Sets the public path for Webpack to use in dynamic imports
3
+ * @see https://webpack.js.org/guides/public-path/#on-the-fly
4
+ */
5
+
6
+ /* eslint-disable no-undef */
7
+ /* eslint-disable camelcase */
8
+ // window.ThemeEnvy.publicPath is set in the theme-envy.liquid snippet
9
+ __webpack_public_path__ = window.ThemeEnvy.publicPath
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @file Builds our elements.js file based on the elements in our src/elements directory
3
+ * @description Lazy loads our elements based on their presence in the DOM and the loading attribute
4
+ */
5
+
6
+ const path = require('path')
7
+ const fs = require('fs-extra')
8
+ const { getAll } = require('#Build/functions')
9
+ const elements = getAll('elements').map(file => {
10
+ const name = path.basename === 'index.js' ? path.basename(path.dirname(file)) : path.basename(file, '.js')
11
+ return `'${name}': () => import(/* webpackChunkName: "${name}" */ '${file}')`
12
+ })
13
+
14
+ const markup = elements.length
15
+ ? `const elements = {${elements.join(',\n')}}
16
+ // check all elements for presence in the Document and load if they are there
17
+ Object.entries(elements).forEach(elm => {
18
+ const domElm = document.querySelector(elm[0])
19
+ if (domElm) {
20
+ if (domElm.getAttribute('loading') === 'lazy') {
21
+ const observer = new IntersectionObserver((entries, observer) => {
22
+ entries.forEach(entry => {
23
+ if (entry.isIntersecting) {
24
+ loadElement(elm)
25
+ domElm.setAttribute('loading', 'loaded')
26
+ observer.disconnect()
27
+ }
28
+ })
29
+ }, { rootMargin: '0px 0px 500px 0px', threshold: 0 })
30
+ observer.observe(domElm)
31
+ return
32
+ }
33
+ loadElement(elm)
34
+ }
35
+ })
36
+ function loadElement(elm) {
37
+ // load the element
38
+ elm[1]()
39
+ // remove from Object so we don't need to check again
40
+ delete elements[elm[0]]
41
+ }
42
+ `
43
+ : ''
44
+ fs.writeFileSync(path.resolve(__dirname, '../elements.js'), markup, 'utf8')
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @file Builds our features.js file imports based on the features directory
3
+ */
4
+
5
+ const path = require('path')
6
+ const fs = require('fs-extra')
7
+ const { getAll } = require('#Build/functions')
8
+
9
+ const features = getAll('features').map(file => {
10
+ return `import '${file}'`
11
+ })
12
+
13
+ const markup = features.join('\n')
14
+
15
+ fs.writeFileSync(path.resolve(__dirname, '../features.js'), markup, 'utf8')
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Our Webpack entrypoint for ThemeEnvy
3
+ */
4
+
5
+ import './public-path.js'
6
+ import './elements.js'
7
+ import './features.js'
@@ -0,0 +1,11 @@
1
+ /**
2
+ @file Pre-builds the theme-envy.liquid snippet and adds it to dist/snippets
3
+ */
4
+
5
+ const fs = require('fs-extra')
6
+ const path = require('path')
7
+ require('./liquid-builders/theme-envy.liquid.build')
8
+
9
+ fs.ensureDirSync(path.resolve(ThemeEnvy.outputPath, 'snippets'))
10
+
11
+ fs.copyFileSync(path.resolve(__dirname, 'theme-envy.liquid'), path.resolve(ThemeEnvy.outputPath, 'snippets/theme-envy.liquid'))
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @file Builds the theme-envy.liquid snippet, which is used to load the theme-envy.js script and critical css
3
+ */
4
+
5
+ const path = require('path')
6
+ const fs = require('fs-extra')
7
+ const getAll = require('#Build/functions/get-all.js')
8
+ const hasCriticalCSS = getAll('criticalCSS').length > 0 || ThemeEnvy?.tailwind !== false
9
+
10
+ const markup = `{% comment %} This file is auto-generated by theme-envy. DO NOT EDIT. {% endcomment %}
11
+ ${hasCriticalCSS ? '{{ \'theme-envy.critical.css\' | asset_url | stylesheet_tag: preload: true }}' : ''}
12
+ <script>
13
+ (function() {
14
+ const assetUrl = {{ 'image.jpg' | asset_url | json }};
15
+ window.ThemeEnvy = window.ThemeEnvy || {};
16
+ window.ThemeEnvy.publicPath = assetUrl.substring(0, assetUrl.lastIndexOf('/') + 1);
17
+ })();
18
+ </script>
19
+ <script src="{{ 'theme-envy.js' | asset_url }}" defer></script>`
20
+
21
+ fs.writeFileSync(path.resolve(__dirname, '../theme-envy.liquid'), markup, 'utf8')
@@ -0,0 +1 @@
1
+ require('./styles-builders/theme-envy.css.js')
@@ -0,0 +1,11 @@
1
+ const fs = require('fs-extra')
2
+ const path = require('path')
3
+ const getAll = require('#Build/functions/get-all.js')
4
+
5
+ const criticalCSS = getAll('criticalCSS')
6
+
7
+ const markup = `${ThemeEnvy.tailwind !== false ? "@import './tailwind-base.css'" : ''};
8
+ ${criticalCSS.map(file => `@import '${file}';`).join('\n')}
9
+ `
10
+
11
+ fs.writeFileSync(path.resolve(__dirname, '../theme-envy.css'), markup, 'utf8')
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @file Copies all templates from the src folder to the dist folder with no transformations
3
+ */
4
+
5
+ const path = require('path')
6
+ const fs = require('fs-extra')
7
+ const { getAll } = require('#Build/functions')
8
+ // copy all templates to dist
9
+ const globbedTemplates = getAll('templates')
10
+
11
+ const templateOutputPath = path.resolve(ThemeEnvy.outputPath, 'templates')
12
+
13
+ // if dist/templates doesn't exist, create it
14
+ fs.ensureDirSync(templateOutputPath)
15
+ globbedTemplates.forEach(file => {
16
+ // write each file to dist
17
+ fs.copyFileSync(file, path.resolve(templateOutputPath, path.basename(file)))
18
+ // update progress bar
19
+ ThemeEnvy.progress.increment('templates', 1)
20
+ })
@@ -0,0 +1,71 @@
1
+ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
2
+ const TerserPlugin = require('terser-webpack-plugin')
3
+ const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts')
4
+ const { ESBuildMinifyPlugin } = require('esbuild-loader')
5
+ const path = require('path')
6
+
7
+ module.exports = {
8
+ entry: {
9
+ 'theme-envy': path.resolve(__dirname, 'requires/scripts/theme-envy.js'),
10
+ },
11
+ output: {
12
+ path: path.resolve(ThemeEnvy.outputPath, 'assets'),
13
+ publicPath: '',
14
+ filename: '[name].js?h=[contenthash:5]',
15
+ chunkFilename: (pathData) => {
16
+ // trim filenames so they're not super long... append 5 character hash
17
+ const name = pathData.chunk.name
18
+ const id = String(name || pathData.chunk.id).replace('node_modules_', '').substring(0, 25)
19
+ if (name === undefined) {
20
+ return `shared-${id}${pathData.chunk.contentHash.javascript.substring(0, 5)}.js?h=[contenthash:5]`
21
+ }
22
+ if (id.length > 25) {
23
+ return `${id}${pathData.chunk.contentHash.javascript.substring(0, 5)}.js?h=[contenthash:5]`
24
+ }
25
+ return '[name].js?h=[contenthash:5]'
26
+ }
27
+ },
28
+ module: {
29
+ rules: [
30
+ {
31
+ test: /(\.css)$/,
32
+ use: [
33
+ MiniCssExtractPlugin.loader,
34
+ { loader: 'css-loader', options: { url: false } },
35
+ 'postcss-loader',
36
+ ],
37
+ },
38
+ ]
39
+ },
40
+ optimization: {
41
+ splitChunks: {
42
+ automaticNameDelimiter: '_'
43
+ },
44
+ sideEffects: false,
45
+ minimizer: [
46
+ (process.env.mode === 'production')
47
+ ? new ESBuildMinifyPlugin({
48
+ target: 'es2015', // Syntax to compile to (see options below for possible values)
49
+ })
50
+ : new TerserPlugin({
51
+ extractComments: false,
52
+ }),
53
+ ]
54
+ },
55
+ resolve: {
56
+ alias: {
57
+ Build: ThemeEnvy.paths.build,
58
+ Helpers: ThemeEnvy.paths.helpers,
59
+ Elements: path.resolve(ThemeEnvy.themePath, 'theme-envy/elements/'),
60
+ Features: path.resolve(ThemeEnvy.themePath, 'theme-envy/features/'),
61
+ Root: path.resolve(process.cwd()),
62
+ Scripts: path.resolve(ThemeEnvy.themePath, 'scripts/'),
63
+ },
64
+ },
65
+ plugins: [
66
+ new RemoveEmptyScriptsPlugin(),
67
+ new MiniCssExtractPlugin({
68
+ filename: '[name].css?h=[chunkhash:5]',
69
+ }),
70
+ ]
71
+ }
@@ -0,0 +1,56 @@
1
+ const fs = require('fs-extra')
2
+ const path = require('path')
3
+ const glob = require('glob')
4
+ const logSymbols = require('#LogSymbols')
5
+ const detectChildren = require('./detect-children')
6
+
7
+ module.exports = function({ sourceTheme }) {
8
+ // figure out if we can separate any sections into features
9
+ const sections = glob.sync(path.resolve(sourceTheme, 'sections/*.liquid'))
10
+
11
+ const children = sections.map(section => {
12
+ // regexp matching for render and include tags
13
+ let results = detectChildren({ section, filePath: section, root: sourceTheme })
14
+ if (results.snippets.length) {
15
+ results.snippets.forEach(snippet => {
16
+ const snippetPath = path.resolve(sourceTheme, `snippets/${snippet}.liquid`)
17
+ const snippetChildren = detectChildren({ section, filePath: snippetPath })
18
+ const snippets = [...results.snippets, ...snippetChildren.snippets]
19
+ const assets = [...results.assets, ...snippetChildren.assets]
20
+ results = { section, snippets: [...new Set(snippets)], assets: [...new Set(assets)] }
21
+ })
22
+ }
23
+ return results
24
+ })
25
+ // determine if any sections have any snippets or any assets that are unique to that section
26
+ const sectionsWithUniqueChildren = children.map(child => {
27
+ const snippets = child.snippets.filter(snippet => {
28
+ return !children.find(section => section.section !== child.section && section.snippets.includes(snippet))
29
+ })
30
+ const assets = child.assets.filter(asset => {
31
+ return !children.find(section => section.section !== child.section && section.assets.includes(asset))
32
+ })
33
+ return { section: child.section, snippets, assets }
34
+ }).filter(section => section.snippets.length || section.assets.length)
35
+
36
+ // move sections with unique children to theme-envy/features
37
+ sectionsWithUniqueChildren.forEach(section => {
38
+ const sectionName = path.basename(section.section, '.liquid')
39
+ fs.ensureDirSync(path.resolve(sourceTheme, 'theme-envy/features', sectionName))
40
+ fs.moveSync(section.section, path.resolve(sourceTheme, 'theme-envy/features', sectionName, `sections/${sectionName}.liquid`))
41
+ if (section.snippets.length) {
42
+ fs.ensureDirSync(path.resolve(sourceTheme, 'theme-envy/features', sectionName, 'snippets'))
43
+ section.snippets.forEach(snippet => {
44
+ fs.moveSync(path.resolve(sourceTheme, `snippets/${snippet}.liquid`), path.resolve(sourceTheme, 'theme-envy/features', sectionName, 'snippets', `${snippet}.liquid`))
45
+ })
46
+ }
47
+ if (section.assets.length) {
48
+ fs.ensureDirSync(path.resolve(sourceTheme, 'theme-envy/features', sectionName, 'assets'))
49
+ section.assets.forEach(asset => {
50
+ fs.moveSync(path.resolve(sourceTheme, `assets/${asset}`), path.resolve(sourceTheme, 'theme-envy/features', sectionName, 'assets', `${asset}`))
51
+ })
52
+ }
53
+ })
54
+
55
+ console.log(`${logSymbols.success} Theme sections converted to features`)
56
+ }
@@ -0,0 +1,30 @@
1
+ const fs = require('fs-extra')
2
+ const path = require('path')
3
+
4
+ module.exports = function({ section, filePath, root }) {
5
+ try {
6
+ const source = fs.readFileSync(filePath, 'utf8')
7
+ const snippetTags = /\s*((include|render)\s['|"](\S*)['|"])\s*/gm
8
+ const snippets = [...source.matchAll(snippetTags)].map(tag => tag[3]).filter(snippet => {
9
+ // file exists
10
+ return fs.existsSync(path.resolve(process.cwd(), root, `snippets/${snippet}.liquid`))
11
+ })
12
+ const assetUrl = /['|"](\S*)['|"]\s*[|]\s*asset_url/gm
13
+ const assets = [...source.matchAll(assetUrl)].map(tag => tag[1]).filter(asset => {
14
+ // file exists
15
+ return fs.existsSync(path.resolve(process.cwd(), root, `assets/${asset}`))
16
+ })
17
+ // return unique values
18
+ return {
19
+ section,
20
+ snippets: [...new Set(snippets)],
21
+ assets: [...new Set(assets)]
22
+ }
23
+ } catch (e) {
24
+ return {
25
+ section,
26
+ snippets: [],
27
+ assets: []
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ convertSectionsToFeatures: require('./convert-sections-to-features'),
3
+ detectChildren: require('./detect-children'),
4
+ installHooks: require('./install-hooks'),
5
+ setSettingsSchemaJs: require('./set-settings-schema-js'),
6
+ }
@@ -0,0 +1,68 @@
1
+ const path = require('path')
2
+ const fs = require('fs-extra')
3
+ const logSymbols = require('#LogSymbols')
4
+ const { liquidPrettify } = require('#Helpers')
5
+
6
+ const hooks = {
7
+ '<head>': {
8
+ after: "{% hook 'head-start' %}",
9
+ },
10
+ '</head>': {
11
+ before: "{% hook 'head-end' %}",
12
+ },
13
+ '<body>': {
14
+ after: "{% hook 'body-start' %}",
15
+ },
16
+ '</body>': {
17
+ before: "{% hook 'body-end' %}",
18
+ },
19
+ '<header>': {
20
+ after: "{% hook 'header-start' %}",
21
+ },
22
+ '</header>': {
23
+ before: "{% hook 'header-end' %}",
24
+ },
25
+ '<main>': {
26
+ before: "{% hook 'before-main' %}",
27
+ after: "{% hook 'main-start' %}",
28
+ },
29
+ '</main>': {
30
+ before: "{% hook 'main-end' %}",
31
+ after: "{% hook 'after-main' %}",
32
+ },
33
+ '<footer>': {
34
+ before: "{% hook 'before-footer' %}",
35
+ after: "{% hook 'footer-start' %}",
36
+ },
37
+ '</footer>': {
38
+ before: "{% hook 'footer-end' %}",
39
+ after: "{% hook 'after-footer' %}",
40
+ },
41
+ }
42
+
43
+ module.exports = function() {
44
+ const themeLiquid = path.resolve(ThemeEnvy.themePath, 'layout/theme.liquid')
45
+ if (fs.existsSync(themeLiquid)) {
46
+ const theme = fs.readFileSync(themeLiquid, 'utf8')
47
+ let source = theme
48
+ Object.entries(hooks).forEach(([tag, { after, before }]) => {
49
+ const type = tag.includes('/') ? 'closing' : 'opening'
50
+ const tagName = type === 'closing' ? tag.replace('</', '').replace('>', '') : tag.replace('<', '').replace('>', '')
51
+ // regexp for tag
52
+ const checkTag = type === 'closing' ? new RegExp(`</${tagName}>`, 'gm') : new RegExp(`<${tagName}[^>]*>`, 'gm')
53
+ const matches = source.match(checkTag)
54
+ if (matches) {
55
+ // insert before and after attributes around the tag matches
56
+ matches.forEach(match => {
57
+ let newMatch = match
58
+ if (after && !source.includes(after)) newMatch = newMatch.replace('>', `>\n${after}`)
59
+ if (before && !source.includes(before)) newMatch = newMatch.replace('<', `${before}\n<`)
60
+ source = source.replace(match, newMatch)
61
+ })
62
+ }
63
+ })
64
+ source = liquidPrettify({ source, pathname: themeLiquid })
65
+ fs.writeFileSync(themeLiquid, source, 'utf8')
66
+ }
67
+ console.log(`${logSymbols.success} Hooks installed\n`)
68
+ }
@@ -0,0 +1,14 @@
1
+ const path = require('path')
2
+ const fs = require('fs-extra')
3
+ const logSymbols = require('#LogSymbols')
4
+
5
+ module.exports = function({ sourceTheme }) {
6
+ // Set config/settings_schema to .js if settings_schema is json
7
+ const settingsSchema = path.resolve(sourceTheme, 'config/settings_schema.json')
8
+ if (fs.existsSync(settingsSchema)) {
9
+ // rename settings_schema.json to settings_schema.js
10
+ fs.writeFileSync(path.resolve(sourceTheme, 'config/settings_schema.json'), `module.exports = ${JSON.stringify(require(settingsSchema), null, 2)}`)
11
+ fs.renameSync(settingsSchema, path.resolve(sourceTheme, 'config/settings_schema.js'))
12
+ }
13
+ console.log(`${logSymbols.success} settings_schema converted to JS`)
14
+ }
@@ -0,0 +1,50 @@
1
+ /*
2
+ theme-envy convert --(source|src|S)=path/to/theme
3
+
4
+ converts a Shopify theme to Theme Envy directory structure
5
+ - should be run after theme-envy-import, by using the --convert flag
6
+ - can be run independently, node bin/theme-envy convert --source=path/to/theme, or --src=path/to/theme, or -S=path/to/theme
7
+ */
8
+ const path = require('path')
9
+ const fs = require('fs-extra')
10
+ const chalk = require('chalk')
11
+ const logSymbols = require('#LogSymbols')
12
+ const { directories, ensureDirectories } = require('#EnsureDirectories')
13
+ const { setSettingsSchemaJs, convertSectionsToFeatures, installHooks } = require('#Convert/functions')
14
+ const { addThemeEnvyFeatures } = require('#Init/functions')
15
+
16
+ module.exports = async function(src, opts = {}) {
17
+ console.log(`\n ${logSymbols.info} ${chalk.cyan('Converting theme structure...')}`)
18
+ const source = src || './src'
19
+ const sourceTheme = path.resolve(process.cwd(), source)
20
+ // verify source theme exists
21
+ if (!fs.existsSync(sourceTheme)) {
22
+ console.error(
23
+ logSymbols.error,
24
+ chalk.red.bold('Error:'),
25
+ `Source theme directory not found: ${sourceTheme}`
26
+ )
27
+ process.exit(1)
28
+ }
29
+
30
+ // validate directory structure of source theme
31
+ directories.forEach(dir => {
32
+ if (!fs.existsSync(path.resolve(sourceTheme, dir))) {
33
+ console.error(`${logSymbols.error} ${chalk.red.bold('Error:')} Source theme required directory not found: ${path.resolve(sourceTheme, dir)}`)
34
+ process.exit(1)
35
+ }
36
+ })
37
+
38
+ // Create theme-envy directories
39
+ ensureDirectories({ root: sourceTheme, envy: true })
40
+ console.log(`${logSymbols.success} ${chalk.green('theme-envy')} directories created`)
41
+
42
+ if (opts.addThemeEnvy !== false) addThemeEnvyFeatures({ dest: sourceTheme })
43
+
44
+ convertSectionsToFeatures({ sourceTheme })
45
+
46
+ installHooks()
47
+
48
+ // convert settings_schema.json to .js
49
+ if (fs.existsSync(path.join(source, 'config/settings_schema.json'))) setSettingsSchemaJs({ sourceTheme })
50
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @description theme-envy dev starts development process and syncs with Shopify using the Shopify CLI
3
+ * @example npx theme-envy dev
4
+ * @returns {Void}
5
+ */
6
+
7
+ const { spawn } = require('child_process')
8
+ const path = require('path')
9
+
10
+ module.exports = function() {
11
+ const relativeDistPath = path.relative(process.cwd(), ThemeEnvy.outputPath)
12
+ const shopifyDev = `shopify theme dev --store=${ThemeEnvy.store} --path=${relativeDistPath}`
13
+ const build = 'theme-envy build development --watch'
14
+ spawn('stmux', ['-w', 'always', '-e', 'ERROR', '-M', '-m', 'beep,system', '--', '[', '[', shopifyDev, '..', build, ']', ']'], { stdio: 'inherit' })
15
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @file Deletes and rebuilds the output directory before a build
3
+ * @param {Object} options
4
+ * @param {boolean} options.quiet - Do not log the clean message
5
+ * @returns {Void}
6
+ * @example distClean()
7
+ * @example distClean({ quiet: true })
8
+ */
9
+
10
+ const path = require('path')
11
+ const fs = require('fs-extra')
12
+ const emoji = require('node-emoji')
13
+
14
+ module.exports = function({ quiet } = {}) {
15
+ fs.removeSync(ThemeEnvy.outputPath)
16
+ fs.mkdirSync(ThemeEnvy.outputPath)
17
+ const relativeDistPath = path.relative(process.cwd(), ThemeEnvy.outputPath)
18
+ if (!quiet) console.log(emoji.get('sparkles'), `./${relativeDistPath} directory cleaned`)
19
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @description Exports a function that ensures our directory structure is in place.
3
+ * @param {string} root - The root directory to ensure the directories are in place.
4
+ * @param {boolean} envy - Whether or not to ensure the theme-envy specific directories.
5
+ * @example
6
+ * ensureDirectories({ root: ThemeEnvy.themePath, envy: true })
7
+ * @returns {Void}
8
+ */
9
+
10
+ const fs = require('fs-extra')
11
+ const path = require('path')
12
+
13
+ const directories = ['assets', 'config', 'layout', 'locales', 'sections', 'snippets', 'templates']
14
+ const envyDirectories = ['theme-envy/elements', 'theme-envy/features', 'theme-envy/partials', 'theme-envy/schema']
15
+ function ensureDirectory(root, dir) {
16
+ fs.ensureDirSync(path.resolve(root, dir))
17
+ }
18
+ module.exports = {
19
+ directories,
20
+ envyDirectories,
21
+ ensureDirectories({ root = ThemeEnvy.themePath, envy = false }) {
22
+ directories.forEach(dir => ensureDirectory(root, dir))
23
+ if (!envy) return
24
+ envyDirectories.forEach(dir => ensureDirectory(root, dir))
25
+ }
26
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @file finds orphaned snippets, partials, and assets that have no references in the theme
3
+ */
4
+ const chalk = require('chalk')
5
+ const path = require('path')
6
+ const { dependencies } = require('./liquid-tree/objects')
7
+ const { countResults } = require('./liquid-tree/functions')
8
+ const getAll = require('#Build/functions/get-all.js')
9
+
10
+ module.exports = () => {
11
+ const files = [...getAll('snippets'), ...getAll('partials'), ...getAll('assets')]
12
+ const results = files.filter(file => countResults(dependencies.check(file)) <= 1)
13
+ if (!results.length) {
14
+ console.log('No orphaned snippets, partials, or assets found.')
15
+ return
16
+ }
17
+
18
+ console.log(chalk.yellow.bold('Orphaned files found:'))
19
+ results.forEach(file => console.log(`${chalk.red('*')} ${path.relative(ThemeEnvy.themePath, file)}`))
20
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @file Establishes our global ThemeEnvy object based on theme.config.js
3
+ */
4
+
5
+ const path = require('path')
6
+ const fs = require('fs-extra')
7
+ const EventEmitter = require('events')
8
+
9
+ const themeConfigPath = path.resolve(process.cwd(), 'theme.config.js')
10
+ global.ThemeEnvy = fs.existsSync(themeConfigPath) ? require(themeConfigPath) : {}
11
+
12
+ ThemeEnvy.dependencies = {}
13
+ ThemeEnvy.events = new EventEmitter()
14
+ ThemeEnvy.themePath = path.resolve(process.cwd(), (ThemeEnvy.themePath || 'src'))
15
+ ThemeEnvy.outputPath = path.resolve(process.cwd(), (ThemeEnvy.outputPath || 'dist'))
16
+
17
+ ThemeEnvy.paths = {
18
+ build: path.resolve(__dirname, '../../build'),
19
+ helpers: path.resolve(__dirname, '../../helpers'),
20
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @file Attempts to run prettier-plugin-liquid on source string
3
+ * @param {Object} options
4
+ * @param {string} options.source - The source string to prettify
5
+ * @param {string} options.pathname - The pathname of the file being prettified
6
+ * @param {boolean} options.verbose - Whether or not to log warnings
7
+ * @returns {string} - The prettified source string or the original source string if prettier fails
8
+ */
9
+
10
+ const liquidPlugin = require('@shopify/prettier-plugin-liquid/standalone')
11
+ const prettier = require('prettier/standalone')
12
+ const chalk = require('chalk')
13
+ const logSymbols = require('#LogSymbols')
14
+
15
+ module.exports = function({ source, pathname, verbose }) {
16
+ let prettified = false
17
+ try {
18
+ prettified = prettier.format(source, { plugins: [liquidPlugin], parser: 'liquid-html' })
19
+ } catch (error) {
20
+ if (verbose) console.log(logSymbols.warning, chalk.yellow('Warning:'), chalk.dim(`there was an issue prettifying ${pathname}`))
21
+ }
22
+ if (prettified) return prettified
23
+ return source
24
+ }
@@ -0,0 +1,13 @@
1
+ const countResults = (obj) => {
2
+ // deep count number of entries in results object
3
+ let count = 0
4
+ Object.keys(obj).forEach(key => {
5
+ if (obj[key].tree) {
6
+ count += countResults(obj[key].tree)
7
+ }
8
+ count++
9
+ })
10
+ return count
11
+ }
12
+
13
+ module.exports = countResults