@softlimit/theme-envy 0.1.2-alpha
Sign up to get free protection for your applications and to get access to all the features.
- package/.eslintrc.js +26 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/workflows/release-please.yml +19 -0
- package/LICENSE +21 -0
- package/README.md +259 -0
- package/build/functions/failed-hook-installs.js +18 -0
- package/build/functions/get-all.js +102 -0
- package/build/functions/index.js +7 -0
- package/build/functions/liquid/functions/extend-liquid.js +77 -0
- package/build/functions/liquid/functions/flatten-shopify-directory-structure.js +29 -0
- package/build/functions/liquid/functions/index.js +6 -0
- package/build/functions/liquid/functions/list-dependencies.js +47 -0
- package/build/functions/liquid/functions/section-schema-inject.js +26 -0
- package/build/functions/liquid/index.js +36 -0
- package/build/functions/parent-theme-files.js +25 -0
- package/build/functions/tailwind.js +31 -0
- package/build/functions/theme-envy.js +85 -0
- package/build/functions/webpack.js +44 -0
- package/build/index.js +45 -0
- package/build/requires/assets.js +22 -0
- package/build/requires/config.js +44 -0
- package/build/requires/globals/index.js +10 -0
- package/build/requires/globals/parent-theme.js +28 -0
- package/build/requires/globals/prepare-install-hooks-schema.js +58 -0
- package/build/requires/globals/progress-bar.js +52 -0
- package/build/requires/globals/theme-require.js +190 -0
- package/build/requires/index.js +21 -0
- package/build/requires/locales.js +16 -0
- package/build/requires/scripts/index.js +5 -0
- package/build/requires/scripts/public-path.js +9 -0
- package/build/requires/scripts/script-builders/elements.build.js +44 -0
- package/build/requires/scripts/script-builders/features.build.js +15 -0
- package/build/requires/scripts/theme-envy.js +7 -0
- package/build/requires/snippets/index.js +11 -0
- package/build/requires/snippets/liquid-builders/theme-envy.liquid.build.js +21 -0
- package/build/requires/styles/index.js +1 -0
- package/build/requires/styles/styles-builders/theme-envy.css.js +11 -0
- package/build/requires/styles/tailwind-base.css +3 -0
- package/build/requires/templates.js +20 -0
- package/build/theme-envy.config.js +71 -0
- package/convert/functions/convert-sections-to-features.js +56 -0
- package/convert/functions/detect-children.js +30 -0
- package/convert/functions/index.js +6 -0
- package/convert/functions/install-hooks.js +68 -0
- package/convert/functions/set-settings-schema-js.js +14 -0
- package/convert/index.js +50 -0
- package/helpers/functions/dev.js +15 -0
- package/helpers/functions/dist-clean.js +19 -0
- package/helpers/functions/ensure-directories.js +26 -0
- package/helpers/functions/find-orphans.js +20 -0
- package/helpers/functions/global-theme-envy.js +20 -0
- package/helpers/functions/liquid-prettify.js +24 -0
- package/helpers/functions/liquid-tree/functions/count-results.js +13 -0
- package/helpers/functions/liquid-tree/functions/get-depth.js +12 -0
- package/helpers/functions/liquid-tree/functions/get-file-info.js +11 -0
- package/helpers/functions/liquid-tree/functions/index.js +5 -0
- package/helpers/functions/liquid-tree/index.js +48 -0
- package/helpers/functions/liquid-tree/objects/dependencies.js +74 -0
- package/helpers/functions/liquid-tree/objects/index.js +3 -0
- package/helpers/functions/log-symbols.js +28 -0
- package/helpers/functions/pull-json.js +22 -0
- package/helpers/functions/scaffold-new/functions/element.js +15 -0
- package/helpers/functions/scaffold-new/functions/feature.js +76 -0
- package/helpers/functions/scaffold-new/functions/index.js +6 -0
- package/helpers/functions/scaffold-new/functions/load-dir.js +24 -0
- package/helpers/functions/scaffold-new/functions/starter-content.js +21 -0
- package/helpers/functions/scaffold-new/functions/upper-first-letter.js +3 -0
- package/helpers/functions/scaffold-new/index.js +28 -0
- package/helpers/functions/scaffold-new/objects/index.js +3 -0
- package/helpers/functions/scaffold-new/objects/starter-configs.js +7 -0
- package/helpers/functions/unicode-supported.js +21 -0
- package/helpers/index.js +10 -0
- package/index.js +190 -0
- package/init/functions/add-theme-envy-features/features/theme-envy/install.js +6 -0
- package/init/functions/add-theme-envy-features/index.js +21 -0
- package/init/functions/copy-example-feature/example-feature/config/_example-feature.js +8 -0
- package/init/functions/copy-example-feature/example-feature/index.js +5 -0
- package/init/functions/copy-example-feature/example-feature/install.js +10 -0
- package/init/functions/copy-example-feature/example-feature/partials/_example-feature-partial.liquid +3 -0
- package/init/functions/copy-example-feature/example-feature/schema/schema-example-feature-schema-partial.js +16 -0
- package/init/functions/copy-example-feature/example-feature/schema/schema-example-feature-section.js +14 -0
- package/init/functions/copy-example-feature/example-feature/scripts/example-feature.js +3 -0
- package/init/functions/copy-example-feature/example-feature/sections/example-feature-section.liquid +11 -0
- package/init/functions/copy-example-feature/example-feature/snippets/example-feature-snippet.liquid +1 -0
- package/init/functions/copy-example-feature/index.js +25 -0
- package/init/functions/copy-starter-config-files/configs/postcss.config.js +9 -0
- package/init/functions/copy-starter-config-files/configs/tailwind.config.js +16 -0
- package/init/functions/copy-starter-config-files/configs/theme.config.js +25 -0
- package/init/functions/copy-starter-config-files/index.js +28 -0
- package/init/functions/copy-starter-config-files/utils/starter-config.js +17 -0
- package/init/functions/copy-starter-config-files/utils/starter-element.js +16 -0
- package/init/functions/copy-starter-config-files/utils/starter-install.js +14 -0
- package/init/functions/copy-starter-config-files/utils/starter-schema.js +32 -0
- package/init/functions/copy-starter-config-files/utils/starter-section.js +16 -0
- package/init/functions/create-empty-settings-data.js +19 -0
- package/init/functions/create-settings-schema.js +29 -0
- package/init/functions/if-shopify-theme-exists.js +26 -0
- package/init/functions/import-from-git.js +21 -0
- package/init/functions/index.js +10 -0
- package/init/functions/validate-source-theme.js +28 -0
- package/init/index.js +87 -0
- 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,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,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,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,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
|
+
}
|
package/convert/index.js
ADDED
@@ -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
|