@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,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