├── README.md ├── index.js ├── lib ├── bundler.js ├── cert │ ├── server.crt │ └── server.key ├── socket.js └── util.js ├── package-lock.json └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # @slater/cli 2 | Shopify theme development toolkit. 3 | 4 | ``` 5 | npm i @slater/cli --save-dev 6 | ``` 7 | 8 | # Usage 9 | Place your entire theme within the `/src` directory, including a 10 | Shopify-standard `config.yml`. 11 | 12 | JS/CSS is compiled using [rollup](https://github.com/rollup/rollup) and 13 | [postcss](https://github.com/postcss/postcss). This library expects a single 14 | entrypoint at `/src/scripts/index.js`, so just import your modules and 15 | stylesheets there and you should be good to go. 16 | 17 | Example structure: 18 | ```bash 19 | - package.json 20 | - src/ 21 | |- config.yml # standard issue Shopify 22 | |- scripts/ 23 | |- index.js 24 | |- styles/ 25 | |- main.css 26 | |- layout/ 27 | |- templates/ 28 | |- sections/ 29 | |- snippets/ 30 | |- locales/ 31 | |- config/ 32 | |- assets/ 33 | ``` 34 | 35 | ## watch 36 | ``` 37 | slater watch 38 | ``` 39 | 40 | ## build 41 | Build JavaScript and CSS, copy theme to `/build` directory. 42 | ``` 43 | slater build 44 | ``` 45 | 46 | ## deploy 47 | Build JavaScript and CSS, copy theme to `/build` directory, push to Shopify. 48 | ``` 49 | slater deploy 50 | ``` 51 | 52 | ## Live-reloading & HTTPS 53 | `slater` uses an local SSL certification to correspond with Shopify's HTTPS 54 | hosted themes. To take advantage of live-reloading, you need to create a 55 | security exception for the `slater` cert (this is safe). To do this, load 56 | [https://localhost:3000](https://localhost:3000) in your browser, and following 57 | the instructions for adding an exception. If it works, you should see this in 58 | your browser window: 59 | ``` 60 | @slater/cli successfully connected 61 | ``` 62 | 63 | ## Options 64 | ### `--env` 65 | Specify a theme from `config.yml`. Defaults to `development`. 66 | ``` 67 | slater deploy --env=production 68 | ``` 69 | 70 | ### Config File 71 | `slater` also supports a `slater.config.js` as well, which supports all the same 72 | options as 73 | [@friendsof/spaghetti](https://github.com/the-couch/spaghetti#config). 74 | 75 | ```javascript 76 | // slater.config.js 77 | module.exports = { 78 | jsx: 'preact.h', 79 | map: 'inline-cheap-source-map', 80 | alias: { 81 | foo: './bar' 82 | } 83 | } 84 | ``` 85 | 86 | ## License 87 | MIT License 88 | (c) 2018 The Couch, LLC 89 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 'use strict' 3 | 4 | const pkg = require('./package.json') 5 | const path = require('path') 6 | const fs = require('fs-extra') 7 | const themekit = require('@slater/themekit') 8 | const queue = require('function-rate-limit') 9 | const c = require('ansi-colors') 10 | const bili = require('bili') 11 | const postcss = require('rollup-plugin-postcss') 12 | const chokidar = require('chokidar') 13 | const mm = require('micromatch') 14 | const yaml = require('yaml').default 15 | const onExit = require('exit-hook') 16 | const exit = require('exit') 17 | const spaghetti = require('@friendsof/spaghetti') 18 | const { socket, closeServer } = require('./lib/socket.js') 19 | const bundler = require('./lib/bundler.js') 20 | const { log, join, resolve } = require('./lib/util.js') 21 | 22 | log(c.gray(`v${pkg.version}`)) 23 | 24 | const { 25 | _: args, 26 | config: configFile = 'slater.config.js', 27 | env = 'development', 28 | debug, 29 | ...props 30 | } = require('minimist')(process.argv.slice(2)) 31 | 32 | if (debug) require('inspector').open() 33 | 34 | const watch = args[0] === 'watch' 35 | const deploy = args[0] === 'deploy' 36 | const build = args[0] === 'build' || (!watch && !deploy) 37 | const gitignore = fs.readFileSync(join('.gitignore')) 38 | const userConfig = fs.existsSync(join(configFile)) ? require(join(configFile)) : {} 39 | const themeConfig = yaml.parse(fs.readFileSync(join('src/config.yml'), 'utf8'))[env] 40 | 41 | let ignoredFiles = [ 42 | '**/scripts/**', 43 | '**/styles/**', 44 | /DS_Store/ 45 | ].concat( 46 | themeConfig.ignore_files || [] 47 | ).concat( 48 | gitignore ? require('parse-gitignore')(gitignore) : [] 49 | ) 50 | 51 | const theme = themekit({ 52 | password: themeConfig.password, 53 | store: themeConfig.store, 54 | theme_id: themeConfig.theme_id, 55 | ignore_files: ignoredFiles 56 | }) 57 | 58 | const config = Object.assign({ 59 | in: '/src/scripts/index.js', 60 | outDir:'/build/assets', 61 | watch, 62 | map: watch ? 'inline-cheap-source-map' : false, 63 | alias: { 64 | scripts: resolve('/src/scripts'), 65 | styles: resolve('/src/styles') 66 | }, 67 | banner: watch ? ` 68 | (function (global) { 69 | try { 70 | const ls = global.localStorage 71 | 72 | const scrollPos = ls.getItem('slater-scroll') 73 | 74 | if (scrollPos) { 75 | global.scrollTo(0, scrollPos) 76 | } 77 | 78 | const socketio = document.createElement('script') 79 | socketio.src = 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.slim.js' 80 | socketio.onload = function init () { 81 | var disconnected = false 82 | var socket = io('https://localhost:3000', { 83 | reconnectionAttempts: 3 84 | }) 85 | socket.on('connect', () => console.log('@slater/cli connected')) 86 | socket.on('refresh', () => { 87 | ls.setItem('slater-scroll', global.scrollY) 88 | global.location.reload() 89 | }) 90 | socket.on('disconnect', () => { 91 | disconnected = true 92 | }) 93 | socket.on('reconnect_failed', e => { 94 | if (disconnected) return 95 | console.error("@slater/cli - Connection to the update server failed. Please visit https://localhost:3000 in your browser to trust the certificate. Then, refresh this page.") 96 | }) 97 | } 98 | document.head.appendChild(socketio) 99 | } catch (e) {} 100 | })(this); 101 | ` : false 102 | }, userConfig) 103 | 104 | config.in = join(config.in) 105 | config.outDir = join(config.outDir) 106 | config.filename = config.filename || path.basename(config.in, '.js') 107 | 108 | const bundle = spaghetti(config) 109 | 110 | function copyTheme () { 111 | return fs.copy(join('src'), join('build'), { 112 | filter: (src, dest) => { 113 | return !/scripts|styles/.test(src) 114 | } 115 | }) 116 | .then(() => { 117 | log(c.green('copied theme to build/')) 118 | }) 119 | .catch(e => { 120 | log(c.red('theme copy failed'), e.message || e) 121 | }) 122 | } 123 | 124 | function watchFiles () { 125 | function match (p) { 126 | return mm.any(p, ignoredFiles) 127 | } 128 | 129 | /** 130 | * From /src dir 131 | */ 132 | const watchSrc = chokidar.watch(join('/src'), { 133 | persistent: true, 134 | ignoreInitial: true, 135 | ignore: [ 136 | '/scripts/**/*.js', 137 | '/styles/**/*.css' 138 | ] 139 | }) 140 | .on('add', p => { 141 | if (match(p)) return 142 | 143 | const pathname = p.split('/src')[1] 144 | const dest = join('/build', pathname) 145 | 146 | fs.copy(p, dest) 147 | .catch(e => { 148 | log( 149 | c.red(`copying ${pathname} failed`), 150 | e.message || e || '' 151 | ) 152 | }) 153 | }) 154 | .on('change', p => { 155 | if (match(p)) return 156 | 157 | const pathname = p.split('/src')[1] 158 | const dest = join('/build', pathname) 159 | 160 | fs.copy(p, dest) 161 | .catch(e => { 162 | log( 163 | c.red(`copying ${pathname} failed`), 164 | e.message || e || '' 165 | ) 166 | }) 167 | }) 168 | .on('unlink', p => { 169 | if (match(p)) return 170 | 171 | const pathname = p.split('/src')[1] 172 | 173 | fs.remove(join('/build', pathname)) 174 | .catch(e => { 175 | log( 176 | c.red(`removing ${pathname} failed`), 177 | e.message || e || '' 178 | ) 179 | }) 180 | }) 181 | 182 | /** 183 | * From /build dir 184 | */ 185 | const watchBuild = chokidar.watch(join('/build'), { 186 | ignore: /DS_Store/, 187 | persistent: true, 188 | ignoreInitial: true 189 | }) 190 | .on('add', p => { 191 | const pathname = p.split('/build')[1] 192 | 193 | theme.upload(pathname, p) 194 | .then(() => socket.emit('refresh')) 195 | }) 196 | .on('change', p => { 197 | const pathname = p.split('/build')[1] 198 | 199 | theme.upload(pathname, p) 200 | .then(() => socket.emit('refresh')) 201 | }) 202 | .on('unlink', p => { 203 | const pathname = p.split('/build')[1] 204 | 205 | theme.remove(pathname) 206 | .then(() => socket.emit('refresh')) 207 | }) 208 | 209 | return [ 210 | watchSrc, 211 | watchBuild 212 | ] 213 | } 214 | 215 | if (watch) { 216 | copyTheme().then(() => { 217 | const watchers = watchFiles() 218 | 219 | onExit(() => { 220 | watchers.map(w => w.close()) 221 | // socket.emit('close') 222 | closeServer() 223 | }) 224 | 225 | bundle.watch() 226 | .end(stats => { 227 | log(c => ([ 228 | c.green(`compiled`), 229 | `in ${stats.duration}ms` 230 | ])) 231 | }) 232 | .error(err => { 233 | log(c => ([ 234 | c.red(`compilation`), 235 | err ? err.message || err : '' 236 | ])) 237 | }) 238 | }) 239 | } else if (build) { 240 | copyTheme().then(() => { 241 | bundle.build() 242 | .end(stats => { 243 | log(c => ([ 244 | c.green(`compiled`), 245 | `in ${stats.duration}ms` 246 | ])) 247 | exit() 248 | }) 249 | .error(err => { 250 | log(c => ([ 251 | c.red(`compilation`), 252 | err ? err.message || err : '' 253 | ])) 254 | exit() 255 | }) 256 | }) 257 | } else if (deploy) { 258 | copyTheme().then(() => { 259 | bundle.build() 260 | .end(stats => { 261 | log(c => ([ 262 | c.green(`compiled`), 263 | `in ${stats.duration}ms` 264 | ])) 265 | 266 | theme.deploy('/build') 267 | .then(() => { 268 | log(c.green(`deployed to ${env} theme`)) 269 | exit() 270 | }) 271 | .catch(e => { 272 | log(c.red('deploy failed'), e) 273 | exit() 274 | }) 275 | }) 276 | .error(err => { 277 | log(c => ([ 278 | c.red(`compilation`), 279 | err ? err.message || err : '' 280 | ])) 281 | }) 282 | }) 283 | } 284 | 285 | -------------------------------------------------------------------------------- /lib/bundler.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const rollup = require('rollup') 3 | const exit = require('exit') 4 | const c = require('ansi-colors') 5 | const { log } = require('./util.js') 6 | 7 | module.exports = function compiler (opts = {}) { 8 | const inputs = { 9 | input: opts.input, 10 | plugins: [ 11 | require('@slater/rollup-plugin-postcss')({ 12 | extensions: [ '.css' ], 13 | extract: true, 14 | minimize: true, 15 | sourceMap: false, 16 | plugins: [ 17 | require('postcss-import')(), 18 | require('postcss-cssnext')({ 19 | warnForDuplicates: false 20 | }), 21 | require('postcss-nested'), 22 | require('postcss-discard-comments') 23 | ] 24 | }), 25 | require('rollup-plugin-babel')({ 26 | exclude: 'node_modules/**', 27 | babelrc: false, 28 | presets: [ 29 | [require('@babel/preset-env').default, { 30 | modules: false 31 | }], 32 | [require('@babel/preset-react').default, { 33 | pragma: opts.jsx 34 | }], 35 | ], 36 | plugins: [ 37 | [require('fast-async'), { 38 | spec: true 39 | }], 40 | [require('@babel/plugin-proposal-object-rest-spread'), { 41 | useBuiltIns: true, 42 | loose: true 43 | }], 44 | require('@babel/plugin-proposal-class-properties') 45 | ] 46 | }), 47 | require('rollup-plugin-node-resolve')({ 48 | jsnext: true, 49 | main: true, 50 | browser: true 51 | }), 52 | require('rollup-plugin-commonjs')(), 53 | require('rollup-plugin-alias')({ 54 | resolve: [ '.js', '.css' ], 55 | ...opts.alias 56 | }), 57 | opts.compress && ( 58 | require('rollup-plugin-uglify')({ 59 | output: { 60 | preamble: opts.banner 61 | } 62 | }) 63 | ) 64 | ].filter(Boolean) 65 | } 66 | 67 | const outputs = { 68 | file: opts.output, 69 | format: 'iife', 70 | banner: opts.banner, 71 | sourcemap: opts.compress ? false : 'inline' 72 | } 73 | 74 | return { 75 | compile () { 76 | return rollup.rollup(inputs) 77 | .then(bundle => { 78 | bundle.write(outputs) 79 | }) 80 | .catch(e => log(c.red('compilation'), e.message || e)) 81 | }, 82 | watch () { 83 | const bundle = rollup.watch({ 84 | ...inputs, 85 | output: outputs 86 | }) 87 | 88 | let listeners = { 89 | ERROR: [ 90 | e => log(c.red('compilation'), e) 91 | ], 92 | FATAL: [ 93 | e => log(c.red('compilation'), e) 94 | ] 95 | } 96 | 97 | bundle.on('event', ({ code, error }) => { 98 | listeners[code] && listeners[code].map(l => l(error)) 99 | }) 100 | 101 | return { 102 | ...bundle, 103 | start (cb) { 104 | listeners.START = (listeners.START || []).concat(cb) 105 | return this 106 | }, 107 | end (cb) { 108 | listeners.END = (listeners.END || []).concat(cb) 109 | return this 110 | }, 111 | error (cb) { 112 | listeners.ERROR = (listeners.ERROR || []).concat(cb) 113 | listeners.FATAL = (listeners.FATAL || []).concat(cb) 114 | return this 115 | } 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/cert/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDijCCAnICCQClrA9v9Wg39zANBgkqhkiG9w0BAQsFADCBhjELMAkGA1UEBhMC 3 | VVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcgWW9yazESMBAGA1UE 4 | ChMJVGhlIENvdWNoMQwwCgYDVQQLEwNEZXYxDTALBgNVBAMTBEVyaWMxIDAeBgkq 5 | hkiG9w0BCQEWEWVyaWNAdGhlY291Y2gubnljMB4XDTE3MDkwODE3NTEyMloXDTE4 6 | MDkwODE3NTEyMlowgYYxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhOZXcgWW9yazER 7 | MA8GA1UEBxMITmV3IFlvcmsxEjAQBgNVBAoTCVRoZSBDb3VjaDEMMAoGA1UECxMD 8 | RGV2MQ0wCwYDVQQDEwRFcmljMSAwHgYJKoZIhvcNAQkBFhFlcmljQHRoZWNvdWNo 9 | Lm55YzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKZdJj6U0QtT+vkO 10 | UmAoeC+P1FZaiMKyiHTmJj38Atf8TTUdRTEuIXY0mpyPCOom0gxwGks/q49EIgNR 11 | 568iZZuxm34KkmWYkRdChovI+mnczPL+8Z779OiHTs1UMod+FesNRAl6pZ97E+4P 12 | dTkxK6DxmOaAE3XqB78rkHOniqLOL7k6T/1XSqYedGkStmZHdzI7nUpjp/lo+n1X 13 | 4BDsydQGgYNdJjLbOUwcBUV6Zz1wme6pC+s005Lfd4KuIk4DcqHJObicCzTbZkTe 14 | cY8/yKxtY1xFNvxyzh5aYz1EgsscyiGqrKsp0bcj48awv+cfu7GH2VRix+s8uj8K 15 | j9wwdUMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEARR99CSaY1fKXkjDu5Ss47Nxk 16 | TkMORwfsHlecTXSGy9t0EKWtsUSRA1r6FqPE4mPefwTPFtEAo4BgrYobrp8PuijN 17 | qKhyOPRoa38aStq6eaXNWNZbjjuWhMMgcODcrAKAwYa6j5jIGAN7nFT0Wr5yWzve 18 | XNqDyojEi8+7MMjz4o6SDWPrmGqY8j/oYPHhMvK78Iv7SDbdneugLU8oNEmdzYr7 19 | EeysK3fm86Dr8/kN2Y5QTJPY2Hut+RUzHp4xQZX5grQ9+rzTMyjIiFvS+wN1FEOc 20 | GHAsGFUwAQ7uGMmJlQCtx874sEKR4+iAZoL806ixpHKMnb4e70Wv6HM7GycbpA== 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /lib/cert/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEApl0mPpTRC1P6+Q5SYCh4L4/UVlqIwrKIdOYmPfwC1/xNNR1F 3 | MS4hdjSanI8I6ibSDHAaSz+rj0QiA1HnryJlm7GbfgqSZZiRF0KGi8j6adzM8v7x 4 | nvv06IdOzVQyh34V6w1ECXqln3sT7g91OTEroPGY5oATdeoHvyuQc6eKos4vuTpP 5 | /VdKph50aRK2Zkd3MjudSmOn+Wj6fVfgEOzJ1AaBg10mMts5TBwFRXpnPXCZ7qkL 6 | 6zTTkt93gq4iTgNyock5uJwLNNtmRN5xjz/IrG1jXEU2/HLOHlpjPUSCyxzKIaqs 7 | qynRtyPjxrC/5x+7sYfZVGLH6zy6PwqP3DB1QwIDAQABAoIBAGvPu+QcfuhvcRqL 8 | 3HhyYYi5FessWqqKFoJjT+8lbRZZbBN6t4dqlMbMylGhJC7SH5dt4qxXQ6/hgIVU 9 | Q+esS9q9G75vzXXHjGdddmIKSbXnIG5tJ1pXf9xdK4VHBkPmQwADpc7ay5Bxq3XA 10 | UMBSjCqNxv/BilsE4iZSbhd1QRPFrYj8wWInN3W3OLIZZEysvo2DFhJljz+eM7Qo 11 | zQZyxLVGyiwlNu4k4i9lhq6zV4r8jUW/u1N1Bq2ocOs9Yc66ZMCKUg5+tSZOtZES 12 | Wb8RhS4rAaBqhwtPhkoeYQsrWvgXAa2bsw7aRQJerfqpH6IYgduZEb/wZQzxROOB 13 | od79LYkCgYEA0RKNE0eRP+wiJqvP9Os7tBEc07vtppDfV1q2R30Kb7uEQFboWRX2 14 | tKBE8SwzS2j4/8N1ySd2rh2A8yEXT9rC+a661yZjR1VvXZS1NcTnOfteJdUWACqd 15 | DVsvKI4Q0HaamrfIzkSdR1o9es49Z1+dcqWcpCjykTwZ70L/X+qY7PcCgYEAy7SI 16 | UlZ5E8fKcMarXPUOpelu3G0Wj1cVvF1kRQh1xOAJ8jfMRh8kX4PieGQtVs1rmYD/ 17 | Z4/u3GSvGOFQJc5JPu2BGXspUYQm/PELCUIW5x544EKCtM3s9jlDscgA1y4w4sGh 18 | ZKB3dOUEpIDkRBH4E+sUo72erkbLpXIcKjUq4xUCgYEAycOLXcW9SOEK/CLm58dz 19 | sIyrMzKzYAGDZ/vk2Qy026stMCIuzHpDwYDBx7UOM4I0ei8ZJmztPY6/eOLAapIF 20 | g4u6SBOFz7uFY9w1HJTPSMdqzjvbpYF6Wv9afVrMo0EyZoliQp044zkVB8SswWwm 21 | uBNXHZ1eqgZESQBxDxGJT3UCgYEAqq+w8l0mwt5L0MeHWzKzfW3lyKlXl+/+dIFE 22 | vVtjYv3fF6iNb8w0bBxULVCzdOJXYJ5oY9yE5wMufIh+4c7CLQpRfIpoirdS++r1 23 | tmI8UpiD7FWOs/VAsug0wsi1e5hmufpJQ8n5jmc/xp+BpU/xMK9v8eu3BIypow3E 24 | b9FxCCkCgYEAt4E+WnAhC1S6UNow/g0zxHOex2xjJK5KQPxzG21Vb5ZL5Wv3Mgve 25 | ze1dIizFyox4s7F1WY2Ee/PACPeSy/9Mf8K5YDvrAyI0bro4BYTiRNRdSZ+jk/1s 26 | /G0sSvKd6uf5z5qblvytFr99wZ0DpQ/XR9BunZruhGO76xwA2urXyJQ= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /lib/socket.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | 3 | const server = require('https').createServer({ 4 | key: fs.readFileSync(__dirname + '/cert/server.key'), 5 | cert: fs.readFileSync(__dirname + '/cert/server.crt') 6 | }, (req, res) => { 7 | res.writeHead(200, { 8 | 'Content-Type': 'text/plain' 9 | }) 10 | res.write('@slater/cli successfully connected') 11 | res.end() 12 | }).listen(3000) 13 | 14 | const socket = require('socket.io')(server, { 15 | serveClient: false 16 | }) 17 | 18 | module.exports = { 19 | socket, 20 | closeServer () { 21 | server.close() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const c = require('ansi-colors') 3 | // const logger = require('log-update') 4 | 5 | function log (...args) { 6 | if (typeof args[0] === 'function') { 7 | console.log( 8 | c.gray(`@slater/cli`), 9 | ...[].concat(args[0](c)) 10 | ) 11 | } else { 12 | console.log( 13 | c.gray(`@slater/cli`), 14 | ...args 15 | ) 16 | } 17 | } 18 | 19 | function resolve (...args) { 20 | return path.resolve(process.cwd(), ...args) 21 | } 22 | 23 | function join (...args) { 24 | return path.join(process.cwd(), ...args) 25 | } 26 | 27 | module.exports = { 28 | log, 29 | resolve, 30 | join 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@slater/cli", 3 | "version": "0.7.4", 4 | "description": "", 5 | "main": "index.js", 6 | "bin": { 7 | "slater": "./index.js" 8 | }, 9 | "author": "slater", 10 | "license": "MIT", 11 | "scripts": { 12 | "publish": "npm publish --access public" 13 | }, 14 | "dependencies": { 15 | "@babel/core": "^7.0.0-beta.39", 16 | "@babel/plugin-proposal-class-properties": "^7.0.0-beta.39", 17 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.39", 18 | "@babel/plugin-transform-flow-strip-types": "^7.0.0-beta.39", 19 | "@babel/plugin-transform-react-jsx": "^7.0.0-beta.39", 20 | "@babel/preset-env": "^7.0.0-beta.39", 21 | "@babel/preset-react": "^7.0.0-rc.1", 22 | "@friendsof/spaghetti": "^1.0.1", 23 | "@slater/rollup-plugin-postcss": "^1.6.2", 24 | "@slater/themekit": "^1.0.2", 25 | "ansi-colors": "^3.0.4", 26 | "anymatch": "^2.0.0", 27 | "bili": "^3.1.2", 28 | "chokidar": "^2.0.4", 29 | "exit": "^0.1.2", 30 | "exit-hook": "^2.0.0", 31 | "fast-async": "^6.3.7", 32 | "fs-extra": "^7.0.0", 33 | "function-rate-limit": "^1.1.0", 34 | "log-update": "^2.3.0", 35 | "micromatch": "^3.1.10", 36 | "minimist": "^1.2.0", 37 | "parse-gitignore": "^1.0.1", 38 | "postcss-cssnext": "^3.1.0", 39 | "postcss-discard-comments": "^4.0.0", 40 | "postcss-import": "^12.0.0", 41 | "postcss-nested": "^3.0.0", 42 | "rollup": "^0.64.1", 43 | "rollup-plugin-alias": "^1.4.0", 44 | "rollup-plugin-babel": "^4.0.0-beta.2", 45 | "rollup-plugin-buble": "^0.19.2", 46 | "rollup-plugin-commonjs": "^9.1.0", 47 | "rollup-plugin-hashbang": "^1.0.1", 48 | "rollup-plugin-json": "^2.3.0", 49 | "rollup-plugin-node-resolve": "^3.3.0", 50 | "rollup-plugin-postcss": "^1.6.2", 51 | "rollup-plugin-replace": "^2.0.0", 52 | "rollup-plugin-uglify": "^3.0.0", 53 | "socket.io": "^2.1.1", 54 | "yaml": "^1.0.0-rc.7" 55 | } 56 | } 57 | --------------------------------------------------------------------------------