UNPKG

slater

Version:

A Shopify development toolkit

231 lines (207 loc) 6.38 kB
const fs = require('fs-extra') const path = require('path') const onExit = require('exit-hook') const exit = require('exit') const chokidar = require('chokidar') const webpack = require('webpack') const link = require('terminal-link') /** * internal modules */ const compiler = require('@slater/compiler') const spaghetti = require('@friendsof/spaghetti') const sync = require('@slater/sync') const { logger, match, sanitize, abs, fixPathSeparators } = require('@slater/util') const log = logger('slater') function logStats (stats, opts = {}) { log.info('built', `in ${stats.duration}s\n${stats.assets.reduce((_, asset, i) => { const size = opts.watch ? '' : asset.size + 'kb' return _ += ` > ${log.colors.gray(asset.name)} ${size}${i !== stats.assets.length - 1 ? `\n` : ''}` }, '')}`) } /** * input absolute filepath, return * - its filename (as a Shopify "key") * - where it's coming from * - where it's going * * e.g. "/Users/user/Sites/projects/my-project/src/snippets/snip.liquid" * * { * filename: "snippets/snip.liquid", * src: "/Users/user/Sites/projects/my-project/src/snippets/snip.liquid", * dest: "/Users/user/Sites/projects/my-project/build/snippets/snip.liquid" * } */ function formatFile (filepath, src, dest) { if (!filepath) return {} const filename = sanitize(filepath) return { filename, src: filepath, dest: path.join(dest, filename) } } module.exports = function createApp (config) { return { copy () { return new Promise((res, rej) => { fs.emptyDir(config.out) .then(() => { fs.copy(config.in, config.out, { filter (src, dest) { return !match(src, config.theme.ignore) } }) .then(res) .catch(e => { log.error(e.message || e) rej(e) exit() }) }) .catch(e => { log.error(e.message || e) rej(e) exit() }) }) }, build () { log.info( 'build', link( `${config.theme.name} theme`, `https://${config.theme.store}/?fts=0&preview_theme_id=${config.theme.id}` ) ) console.log('') return new Promise((res, rej) => { if (!fs.existsSync(abs(config.assets.in))) return const bundle = compiler(config.assets) bundle.on('error', e => { log.error(e) rej(e) }) bundle.on('stats', stats => { logStats(stats) res(stats) }) return bundle.build() }) }, watch () { log.info( 'watch', link( `${config.theme.name} theme`, `https://${config.theme.store}/?fts=0&preview_theme_id=${config.theme.id}` ) ) console.log('') let socket let syncTimeout const theme = sync(config.theme) /** * utilities for watch task only */ function copyFile ({ filename, src, dest }) { return fs.copy(src, dest) .catch(e => { log.error(`copying ${filename} failed\n${e.message || e}`) }) } function deleteFile ({ filename, src, dest }) { return fs.remove(dest) .catch(e => { log.error(`deleting ${filename} failed\n${e.message || e}`) }) } function syncFile ({ filename, src, dest }) { if (!filename) return Promise.resolve(true) return theme.sync(dest) .then(() => { if (socket) { syncTimeout && clearTimeout(syncTimeout) syncTimeout = setTimeout(() => { socket.emit('refresh') }, 100) } }) .then(() => { log.info('synced', filename) }) .catch(({ errors, key }) => { log.error(`syncing ${key} failed - ${errors.asset ? errors.asset.join(' ') : errors}`) }) } function unsyncFile ({ filename, src, dest }) { if (!filename) return Promise.resolve(true) return theme.unsync(dest) .then(() => socket && socket.emit('refresh')) .then(() => { log.info('unsynced', filename) }) .catch(({ errors, key }) => { log.error(`syncing ${key} failed - ${errors.asset ? errors.asset.join(' ') : errors}`) }) } // @see https://github.com/paulmillr/chokidar/issues/773 const watchers = [ chokidar.watch(config.in, { persistent: true, ignoreInitial: true, ignore: config.theme.ignore }) .on('add', file => { if (!file) return file = fixPathSeparators(file) if (match(file, config.theme.ignore)) return copyFile(formatFile(file, config.in, config.out)) }) .on('change', file => { if (!file) return file = fixPathSeparators(file) if (match(file, config.theme.ignore)) return copyFile(formatFile(file, config.in, config.out)) }) .on('unlink', file => { if (!file) return file = fixPathSeparators(file) if (match(file, config.theme.ignore)) return deleteFile(formatFile(file, config.in, config.out)) }), chokidar.watch(config.out, { ignore: /DS_Store/, persistent: true, ignoreInitial: true }) .on('add', file => file && syncFile(formatFile(fixPathSeparators(file), config.in, config.out))) .on('change', file => file && syncFile(formatFile(fixPathSeparators(file), config.in, config.out))) .on('unlink', file => file && unsyncFile(formatFile(fixPathSeparators(file), config.in, config.out))) ] onExit(() => { watchers.map(w => w.close()) }) if (fs.existsSync(abs(config.assets.in))) { const bundle = compiler(config.assets) bundle.on('error', e => { log.error(e) }) bundle.on('stats', stats => { logStats(stats, { watch: true }) }) socket = bundle.watch() onExit(() => { bundle.close() }) } } } }