diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..1f869bd7 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +S3_BUCKET= +S3_URL= +S3_ACL= +S3_REGION= +S3_ACCESS_KEY= +S3_SECRET_KEY= diff --git a/.gitignore b/.gitignore index 646ac519..5f2003c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store +.env node_modules/ diff --git a/README.md b/README.md index c7aa88cd..f7e70181 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,16 @@ A back to the basics CSS starter kit. For when you don’t need the whole boot. --- +## Developers + +Shoelace ships with a CLI that can be used to build a dist and optionally publish it to an S3 bucket. To see all available flags, run: + +``` +node shoelace.js --help +``` + +--- + Developed by [@claviska](https://twitter.com/claviska) for [Surreal CMS](https://www.surrealcms.com/). © A Beautiful Site, LLC diff --git a/build.js b/build.js deleted file mode 100644 index db5ccc5e..00000000 --- a/build.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -global.__version = require('./package.json').version; - -const CleanCSS = require('clean-css'); -const Chalk = require('chalk'); -const FS = require('fs'); -const Path = require('path'); - -let source = Path.join(__dirname, 'source/css'); -let dist = Path.join(__dirname, 'dist'); -let docsFile = Path.join(__dirname, 'docs/index.html'); -let inFile = Path.join(source, 'shoelace.css'); -let outFile = Path.join(dist, 'shoelace.css'); - -const clean = new CleanCSS({ - // format: 'beautify', - inline: ['local'], - rebaseTo: Path.dirname(dist), - specialComments: 'all' -}); - -// Generate minified version -clean.minify({ - [inFile]: { styles: FS.readFileSync(inFile, 'utf8') } -}, (errors, output) => { - // Show errors - if(errors) { - errors.forEach((err) => console.log(Chalk.red(err))); - return; - } - - // Get stats - let originalSize = parseInt(output.stats.originalSize / 1000) + 'KB'; // KB - let minifiedSize = parseInt(output.stats.minifiedSize / 1000) + 'KB'; // KB - - // Show output warnings and errors - output.warnings.forEach((err) => console.log(Chalk.red(err))); - output.errors.forEach((err) => console.log(Chalk.red(err))); - - // Update placeholders in CSS - output.styles = output.styles - .replace(/\{version\}/g, __version) - .replace(/\{originalSize\}/, originalSize) - .replace(/\{minifiedSize\}/, minifiedSize); - - // Write output file - FS.writeFile(outFile, output.styles, 'utf8', (err) => { - if(err) { - console.error(Chalk.red(err)); - return; - } - console.log(Chalk.green('CSS Minified at %s! πŸ’ͺ'), Path.relative(__dirname, outFile)); - }); - - // Update placeholders in docs - let content = FS.readFileSync(docsFile, 'utf8'); - content = content - .replace(/(.*?)<\/span>/g, '' + __version + '') - .replace(/(.*?)<\/span>/g, '' + originalSize + '') - .replace(/(.*?)<\/span>/g, '' + minifiedSize + ''); - - // Write docs file - FS.writeFile(docsFile, content, 'utf8', (err) => { - if(err) { - console.error(Chalk.red(err)); - return; - } - console.log(Chalk.green('Docs have been updated! πŸ“š')); - }); - -}); diff --git a/package-lock.json b/package-lock.json index 166e7b4b..a73a27db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "shoelace-css", - "version": "1.0.0-beta2", + "version": "1.0.0-beta3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -13,6 +13,47 @@ "color-convert": "1.9.0" } }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "aws-sdk": { + "version": "2.0.31", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.0.31.tgz", + "integrity": "sha1-5yzx/caQFb2f0r3z07iMFlB9Jo4=", + "dev": true, + "requires": { + "xml2js": "0.2.6", + "xmlbuilder": "0.4.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, "chalk": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.0.1.tgz", @@ -48,30 +89,298 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "6.1.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "p-map": "1.1.1", + "pify": "3.0.0", + "rimraf": "2.2.8" + } + }, + "dotenv": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", + "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "1.2.0" + } + }, + "findit2": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", + "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=", + "dev": true + }, "fs": { "version": "0.0.1-security", "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=", "dev": true }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "dev": true, + "requires": { + "natives": "1.1.0" + } + }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "dev": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "mime": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "natives": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.0.tgz", + "integrity": "sha1-6f+EFBimsux6SV6TmYT3jxY+bjE=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "p-map": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.1.1.tgz", + "integrity": "sha1-BfXkrpegaDcbwqXMhr+9vBnErno=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + }, + "s3": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/s3/-/s3-4.4.0.tgz", + "integrity": "sha1-VqT3dVFae2ucjlxrGrUfkDdmnx8=", + "dev": true, + "requires": { + "aws-sdk": "2.0.31", + "fd-slicer": "1.0.1", + "findit2": "2.2.3", + "graceful-fs": "3.0.11", + "mime": "1.2.11", + "mkdirp": "0.5.1", + "pend": "1.2.0", + "rimraf": "2.2.8", + "streamsink": "1.2.0" + } + }, + "sax": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.4.2.tgz", + "integrity": "sha1-OfO2AXM9a+yXEFskKipA/Wl4rDw=", + "dev": true + }, "source-map": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true }, + "streamsink": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/streamsink/-/streamsink-1.2.0.tgz", + "integrity": "sha1-76/unx4i01ke1949yqlcP1559zw=", + "dev": true + }, "supports-color": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz", @@ -80,6 +389,27 @@ "requires": { "has-flag": "2.0.0" } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xml2js": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.6.tgz", + "integrity": "sha1-0gnE5N2h/JxFIUHvQcB39a399sQ=", + "dev": true, + "requires": { + "sax": "0.4.2" + } + }, + "xmlbuilder": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-0.4.2.tgz", + "integrity": "sha1-F3bWXz/brUcKCNhgTN6xxOVA/4M=", + "dev": true } } } diff --git a/package.json b/package.json index 919c3f5b..3d5c67a1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "shoelace-css", "description": "A back to the basics CSS starter kit.", - "version": "1.0.0-beta2", + "version": "1.0.0-beta3", "author": "Cory LaViska", "homepage": "https://shoelace.style/", "license": "MIT", @@ -9,12 +9,13 @@ "type": "git", "url": "https://github.com/claviska/shoelace-css" }, - "scripts": { - "build": "node build.js" - }, "devDependencies": { "chalk": "^2.0.1", "clean-css": "^4.1.7", - "fs": "0.0.1-security" + "commander": "^2.11.0", + "del": "^3.0.0", + "dotenv": "^4.0.0", + "fs": "0.0.1-security", + "s3": "^4.4.0" } } diff --git a/shoelace.js b/shoelace.js new file mode 100644 index 00000000..018605ea --- /dev/null +++ b/shoelace.js @@ -0,0 +1,178 @@ +'use strict'; + +global.__version = require('./package.json').version; + +require('dotenv').config(); +const CleanCSS = require('clean-css'); +const Chalk = require('chalk'); +const Del = require('del'); +const FS = require('fs'); +const Path = require('path'); +const Program = require('commander'); +const S3 = require('s3'); + +let source = Path.join(__dirname, 'source/css'); +let dist = Path.join(__dirname, 'dist'); +let docsFile = Path.join(__dirname, 'index.html'); +let inFile = Path.join(source, 'shoelace.css'); +let outFile = Path.join(dist, 'shoelace.css'); + +// Initialize CLI +Program + .version(__version) + .option('--clean', 'Removes the local dist directory') + .option('--dist', 'Builds a dist') + .option('--s3', 'Publish latest release to an S3 bucket (requires .env file)') + .on('--help', () => { + console.log(Chalk.cyan('\n Version %s\n'), __version); + process.exit(1); + }) + .parse(process.argv); + +// Show help by default +if(!process.argv.slice(2).length) { + Program.outputHelp(); + process.exit(1); +} + +// Run dist task +if(Program.dist) { + const clean = new CleanCSS({ + // format: 'beautify', + inline: ['local'], + rebaseTo: Path.dirname(dist), + specialComments: 'all' + }); + + Promise.resolve() + // Generate minified version + .then(() => new Promise((resolve, reject) => { + clean.minify({ + [inFile]: { styles: FS.readFileSync(inFile, 'utf8') } + }, (errors, output) => { + // Show errors + if(errors) { + errors.forEach((err) => console.log(Chalk.red(err))); + reject(new Error('Failed to minify styles.')); + return; + } + + resolve(output); + }); + + })) + // Write dist files + .then((output) => new Promise((resolve, reject) => { + // Get stats + let stats = { + originalSize: parseInt(output.stats.originalSize / 1000) + 'KB', // KB + minifiedSize: parseInt(output.stats.minifiedSize / 1000) + 'KB' // KB + }; + + // Show output warnings and errors + output.warnings.forEach((err) => console.log(Chalk.red(err))); + output.errors.forEach((err) => console.log(Chalk.red(err))); + + // Update placeholders in CSS + output.styles = output.styles + .replace(/\{version\}/g, __version) + .replace(/\{originalSize\}/, stats.originalSize) + .replace(/\{minifiedSize\}/, stats.minifiedSize); + + // Create the dist folder if it doesn't exist + try { + FS.statSync(dist); + } catch(err) { + FS.mkdirSync(dist); + } + + // Write output file + FS.writeFile(outFile, output.styles, 'utf8', (err) => { + if(err) { + reject(err); + return; + } + console.log(Chalk.green('CSS Minified: %s! πŸ’ͺ'), Path.relative(__dirname, outFile)); + + resolve(stats); + }); + })) + // Update docs + .then((stats) => new Promise((resolve, reject) => { + // Update placeholders + let content = FS.readFileSync(docsFile, 'utf8'); + content = content + .replace(/(.*?)<\/span>/g, '' + __version + '') + .replace(/(.*?)<\/span>/g, '' + stats.originalSize + '') + .replace(/(.*?)<\/span>/g, '' + stats.minifiedSize + ''); + + // Write docs file + FS.writeFile(docsFile, content, 'utf8', (err) => { + if(err) { + reject(err); + return; + } + console.log(Chalk.green('Docs have been updated! πŸ“š')); + + resolve(); + }); + })) + // Publish to S3 + .then(() => new Promise((resolve, reject) => { + // Skip if the --s3 flag is missing + if(!Program.s3) { + resolve(); + return; + } + + const client = S3.createClient({ + s3Options: { + accessKeyId: process.env.S3_ACCESS_KEY, + secretAccessKey: process.env.S3_SECRET_KEY + } + }); + + // Sync the local /dist directory to /{version} in the S3 bucket + let uploader = client.uploadDir({ + localDir: dist, + deleteRemoved: true, + s3Params: { + ACL: process.env.S3_ACL, + Prefix: __version, + Bucket: process.env.S3_BUCKET + } + }); + uploader.on('error', (err) => { + reject('Unable to publish to S3: ' + err); + return; + }); + uploader.on('end', () => { + console.log(Chalk.green('%s has been published to S3! ☁️'), __version); + resolve(); + }); + })) + .then(() => process.exit(1)) + .catch((err) => { + console.error(Chalk.red(err)); + process.exit(-1); + }); +} else { + // Can't use the --s3 options without --dist + if(Program.s3) { + console.error(Chalk.yellow('The --s3 flag can only be used with --dist')); + process.exit(-1); + } +} + +// Clean task +if(Program.clean) { + Del(dist) + .then(() => { + console.log(Chalk.green('%s has been removed.'), dist); + process.exit(1); + }) + .catch((err) => { + console.error(Chalk.red('Unable to delete dist directory: ' + err)); + process.exit(-1); + }); +}