├── .gitignore ├── test ├── fixtures │ ├── unlisted │ │ ├── build │ │ │ └── one.md │ │ └── src │ │ │ └── one.md │ ├── draft-publish │ │ ├── build │ │ │ └── one.md │ │ └── src │ │ │ └── one.md │ ├── future-publish │ │ ├── build │ │ │ └── one.md │ │ └── src │ │ │ └── one.md │ ├── private-publish │ │ ├── build │ │ │ └── one.md │ │ └── src │ │ │ └── one.md │ ├── unlisted-listed │ │ ├── build │ │ │ └── one.md │ │ └── src │ │ │ └── one.md │ ├── draft │ │ └── src │ │ │ └── one.md │ ├── private │ │ └── src │ │ │ └── one.md │ ├── future-meta │ │ └── src │ │ │ └── one.md │ ├── future │ │ └── src │ │ │ └── one.md │ └── future-alert │ │ └── src │ │ └── one.md └── index.js ├── Makefile ├── History.md ├── package.json ├── lib └── index.js └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /test/fixtures/unlisted/build/one.md: -------------------------------------------------------------------------------- 1 | 2 | one 3 | -------------------------------------------------------------------------------- /test/fixtures/draft-publish/build/one.md: -------------------------------------------------------------------------------- 1 | 2 | one 3 | -------------------------------------------------------------------------------- /test/fixtures/future-publish/build/one.md: -------------------------------------------------------------------------------- 1 | 2 | one 3 | -------------------------------------------------------------------------------- /test/fixtures/private-publish/build/one.md: -------------------------------------------------------------------------------- 1 | 2 | one 3 | -------------------------------------------------------------------------------- /test/fixtures/unlisted-listed/build/one.md: -------------------------------------------------------------------------------- 1 | 2 | one 3 | -------------------------------------------------------------------------------- /test/fixtures/draft/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: draft 3 | --- 4 | 5 | one 6 | -------------------------------------------------------------------------------- /test/fixtures/private/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: private 3 | --- 4 | 5 | one 6 | -------------------------------------------------------------------------------- /test/fixtures/draft-publish/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: draft 3 | --- 4 | 5 | one 6 | -------------------------------------------------------------------------------- /test/fixtures/future-meta/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | date: 2021-12-21 3 | --- 4 | 5 | one 6 | -------------------------------------------------------------------------------- /test/fixtures/future/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: 2021-12-21 3 | --- 4 | 5 | one 6 | -------------------------------------------------------------------------------- /test/fixtures/future-alert/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: 2021-12-21 3 | --- 4 | 5 | one 6 | -------------------------------------------------------------------------------- /test/fixtures/future-publish/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: 2021-12-21 3 | --- 4 | 5 | one 6 | -------------------------------------------------------------------------------- /test/fixtures/private-publish/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: private 3 | --- 4 | 5 | one 6 | -------------------------------------------------------------------------------- /test/fixtures/unlisted/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: unlisted 3 | collection: test 4 | --- 5 | 6 | one 7 | -------------------------------------------------------------------------------- /test/fixtures/unlisted-listed/src/one.md: -------------------------------------------------------------------------------- 1 | --- 2 | publish: unlisted 3 | collection: test 4 | --- 5 | 6 | one 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | node_modules: package.json 3 | @npm install 4 | 5 | test: node_modules 6 | @./node_modules/.bin/mocha --reporter spec 7 | 8 | .PHONY: test -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 0.1.3 - August 30, 2014 2 | ----------------------- 3 | * use file[opts.futureMeta] when file.publish unspecified (default: 'date') 4 | 5 | 0.1.1 - August 30, 2014 6 | ----------------------- 7 | * renamed opts.alert to opts.futureFn in case there's ever a call for other callbacks 8 | 9 | 0.1.0 - August 30, 2014 10 | ----------------------- 11 | * added support for draft, private, and future posts 12 | * added callback for future posts via opts.alert 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metalsmith-publish", 3 | "description": "A Metalsmith plugin that adds support for draft, private, and future-dated posts.", 4 | "repository": "git://github.com/mikestopcontinues/metalsmith-publish.git", 5 | "version": "0.1.6", 6 | "author": "Mike Stop Continues ", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "scripts": { 10 | "test": "mocha --reporter spec test/index.js" 11 | }, 12 | "dependencies": { 13 | "debug": "~4.1.1" 14 | }, 15 | "devDependencies": { 16 | "metalsmith": "2.x", 17 | "mocha": "8.x" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var debug = require('debug')('metalsmith-publish'); 4 | 5 | /** 6 | * Expose `plugin`. 7 | */ 8 | 9 | module.exports = plugin; 10 | 11 | /** 12 | * Metalsmith plugin that adds support for draft, private, and future-dated posts. 13 | * 14 | * In metadata: 15 | * publish: draft 16 | * publish: private 17 | * publish: unlisted 18 | * publish: 2021-12-21 19 | * 20 | * @param {Object} opts (optional) 21 | * @option {Boolean} draft whether to publish draft posts (default: false) 22 | * @option {Boolean} private whether to publish private posts (default: false) 23 | * @option {Boolean} unlisted whether to include unlisted posts on collections (default: false) 24 | * @option {Boolean} future whether to publish future-dated posts (default: false) 25 | * @option {string} futureMeta file meta field to check for future date if publish meta not specified (default: 'date') 26 | * @option {Function} futureFn callback (futureFiles, metalsmith, done) so you can future-pace rebuild via cron job or whatever 27 | * @return {Function} 28 | */ 29 | 30 | function plugin(opts) { 31 | opts = opts || {}; 32 | opts.futureMeta = opts.futureMeta || 'date'; 33 | 34 | return function (files, metalsmith, done) { 35 | var futureFiles = {}; 36 | 37 | Object.keys(files).forEach(function (file) { 38 | debug('analyzing publish state for %s', file); 39 | var data = files[file]; 40 | var pub = data.publish; 41 | 42 | if ((pub === 'draft' && !opts.draft) || (pub === 'private' && !opts.private)) { 43 | return delete files[file]; 44 | } 45 | 46 | if (pub === 'unlisted' && !opts.unlisted) { 47 | delete files[file].collection; 48 | } 49 | 50 | var futureDate = new Date(pub || data[opts.futureMeta]); 51 | 52 | if (futureDate.getTime() > Date.now() && !opts.future) { 53 | futureFiles[file] = data; 54 | return delete files[file] 55 | } 56 | }); 57 | 58 | if (typeof opts.futureFn == 'string') { 59 | opts.futureFn = new Function(opts.futureFn); 60 | } 61 | 62 | if (typeof opts.futureFn == 'function') { 63 | debug('calling futureFn callback for %s files', futureFiles.length); 64 | opts.futureFn(futureFiles, metalsmith, done); 65 | } else { 66 | done(); 67 | } 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # metalsmith-publish 2 | 3 | A [Metalsmith](https://github.com/segmentio/metalsmith) plugin that adds support for draft, private, and future-dated posts. Enables you to do multiple builds for production and development. Gives you a callback so you can automate rebuilding metalsmith with a cron job or node script when future-dated posts become published. 4 | 5 | ## Features 6 | 7 | - works via `publish` metadata 8 | - building draft, private, and future-dated posts enabled independently 9 | - callback serving future-dated posts so you can automate rebuild 10 | 11 | ## Installation 12 | 13 | $ npm install metalsmith-publish 14 | 15 | ## Usage 16 | 17 | ### Draft 18 | 19 | Set post publish state via metadata: 20 | 21 | ```markdown 22 | --- 23 | title: My Article 24 | publish: draft 25 | --- 26 | ``` 27 | 28 | Include in build via config: 29 | 30 | ```js 31 | var publish = require('metalsmith-publish'); 32 | 33 | metalsmith.use(publish({ 34 | draft: true 35 | })); 36 | ``` 37 | 38 | ### Private 39 | 40 | Set post publish state via metadata: 41 | 42 | ```markdown 43 | --- 44 | title: My Article 45 | publish: private 46 | --- 47 | ``` 48 | 49 | Include in build via config: 50 | 51 | ```js 52 | var publish = require('metalsmith-publish'); 53 | 54 | metalsmith.use(publish({ 55 | private: true 56 | })); 57 | ``` 58 | 59 | ### Unlisted 60 | 61 | Removes the `collection` metadata, useful for publishing internally wihtout adding it to your posts lists or RSS feeds. 62 | 63 | ```markdown 64 | --- 65 | title: My Article 66 | publish: unlisted 67 | collection: blog 68 | --- 69 | ``` 70 | 71 | Include in build via config: 72 | 73 | ```js 74 | var publish = require('metalsmith-publish'); 75 | 76 | metalsmith.use(publish({ 77 | unlisted: true 78 | })); 79 | ``` 80 | 81 | ### Future-dated 82 | 83 | Set post publish state via metadata: 84 | 85 | ```markdown 86 | --- 87 | title: My Article 88 | publish: 2021-12-21 89 | --- 90 | ``` 91 | 92 | Include in build via config: 93 | 94 | ```js 95 | var publish = require('metalsmith-publish'); 96 | 97 | metalsmith.use(publish({ 98 | future: true 99 | })); 100 | ``` 101 | 102 | Specify field to use for date when `publish` unspecified (default: 'date'): 103 | 104 | ```js 105 | var publish = require('metalsmith-publish'); 106 | 107 | metalsmith.use(publish({ 108 | futureMeta: 'date' 109 | })); 110 | ``` 111 | 112 | Or pass callback to automate rebuild: 113 | 114 | ```js 115 | metalsmith.use(publish({ 116 | futureFn: function (futureFiles, metalsmith, done) { 117 | Object.keys(futureFiles).forEach(function (file) { 118 | console.log('rebuild ' + file + ' @ ' new Date(futureFiles[file].publish).toTime()); 119 | }); 120 | done(); 121 | } 122 | })); 123 | ``` 124 | 125 | ## CLI Usage 126 | 127 | All of the same options apply, just add them to the `"plugins"` key in your `metalsmith.json` configuration: 128 | 129 | ```json 130 | { 131 | "plugins": { 132 | "metalsmith-publish": { 133 | "articles": { 134 | "draft": false, 135 | "private": false, 136 | "future": false, 137 | "futureFn": "console.log('Callback script passed (futureFiles, metalsmith, done). E.g. futureFiles = ' + Object.keys(futureFiles).join(', ')); done();" 138 | } 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | ## License 145 | 146 | MIT 147 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var Metalsmith = require('metalsmith'); 3 | var publish = require('..'); 4 | 5 | describe('metalsmith-publish', function () { 6 | it('should not publish files with metadata publish: draft', function (done) { 7 | var metalsmith = Metalsmith('test/fixtures/draft'); 8 | metalsmith 9 | .use(publish({})) 10 | .build(function (err, files) { 11 | if (err) { 12 | return done(err); 13 | } 14 | 15 | assert.equal(Object.keys(files).length, 0); 16 | done(); 17 | }); 18 | }); 19 | 20 | it('should publish draft files when opts.draft == true', function (done) { 21 | var metalsmith = Metalsmith('test/fixtures/draft-publish'); 22 | metalsmith 23 | .use(publish({ draft: true })) 24 | .build(function (err, files) { 25 | if (err) { 26 | return done(err); 27 | } 28 | 29 | assert.equal(Object.keys(files).length, 1); 30 | done(); 31 | }); 32 | }); 33 | 34 | it('should not publish files with metadata publish: private', function (done) { 35 | var metalsmith = Metalsmith('test/fixtures/private'); 36 | metalsmith 37 | .use(publish({})) 38 | .build(function (err, files) { 39 | if (err) { 40 | return done(err); 41 | } 42 | 43 | assert.equal(Object.keys(files).length, 0); 44 | done(); 45 | }); 46 | }); 47 | 48 | it('should publish private files when opts.private == true', function (done) { 49 | var metalsmith = Metalsmith('test/fixtures/private-publish'); 50 | metalsmith 51 | .use(publish({ private: true })) 52 | .build(function (err, files) { 53 | if (err) { 54 | return done(err); 55 | } 56 | 57 | assert.equal(Object.keys(files).length, 1); 58 | done(); 59 | }); 60 | }); 61 | 62 | it('should remove collection from files with metadata publish: unlisted', function (done) { 63 | var metalsmith = Metalsmith('test/fixtures/unlisted'); 64 | metalsmith 65 | .use(publish({})) 66 | .build(function (err, files) { 67 | if (err) { 68 | return done(err); 69 | } 70 | 71 | assert.equal(files['one.md'].collection, undefined); 72 | done(); 73 | }); 74 | }); 75 | 76 | it('should not remove collection from files with metadata publish: unlisted', function (done) { 77 | var metalsmith = Metalsmith('test/fixtures/unlisted-listed'); 78 | metalsmith 79 | .use(publish({ unlisted: true })) 80 | .build(function (err, files) { 81 | if (err) { 82 | return done(err); 83 | } 84 | 85 | assert.notEqual(files['one.md'].collection, undefined); 86 | done(); 87 | }); 88 | }); 89 | 90 | it('should not publish files with future-dated metadata publish value', function (done) { 91 | var metalsmith = Metalsmith('test/fixtures/future'); 92 | metalsmith 93 | .use(publish({})) 94 | .build(function (err, files) { 95 | if (err) { 96 | return done(err); 97 | } 98 | 99 | assert.equal(Object.keys(files).length, 0); 100 | done(); 101 | }); 102 | }); 103 | 104 | it('should use file[opts.futureMeta] when file.publish unspecified', function (done) { 105 | var metalsmith = Metalsmith('test/fixtures/future-meta'); 106 | metalsmith 107 | .use(publish({})) 108 | .build(function (err, files) { 109 | if (err) { 110 | return done(err); 111 | } 112 | 113 | assert.equal(Object.keys(files).length, 0); 114 | done(); 115 | }); 116 | }); 117 | 118 | it('should publish future-dated files when opts.future == true', function (done) { 119 | var metalsmith = Metalsmith('test/fixtures/future-publish'); 120 | metalsmith 121 | .use(publish({ future: true })) 122 | .build(function (err, files) { 123 | if (err) { 124 | return done(err); 125 | } 126 | 127 | assert.equal(Object.keys(files).length, 1); 128 | done(); 129 | }); 130 | }); 131 | 132 | it('should call opts.futureFn callback if available', function (done) { 133 | var metalsmith = Metalsmith('test/fixtures/future-alert'); 134 | metalsmith 135 | .use(publish({ futureFn: cb })) 136 | .build(function (err, files) { 137 | if (err) { 138 | return done(err); 139 | } 140 | 141 | done(); 142 | }); 143 | 144 | function cb(_futureFiles, _metalsmith, _done) { 145 | assert.equal(Object.keys(_futureFiles).length, 1); 146 | assert.equal(_metalsmith, metalsmith); 147 | assert.equal(typeof _done, 'function'); 148 | _done(); 149 | } 150 | }); 151 | }); 152 | --------------------------------------------------------------------------------