diff --git a/bin/maintenance/plugins-table-plugin-collation.js b/bin/maintenance/plugins-table-plugin-collation.js new file mode 100644 index 0000000..431eeb6 --- /dev/null +++ b/bin/maintenance/plugins-table-plugin-collation.js @@ -0,0 +1,57 @@ +#!/usr/bin/env node + +var db, + pluginsDb = require( "../../lib/pluginsdb" ); + +function runQueries( queries, fn ) { + db.run( queries.shift(), function( error ) { + if ( error ) { + return fn( error ); + } + + if ( !queries.length ) { + return fn( null ); + } + + runQueries( queries, fn ); + }); +} + +db = pluginsDb.connect(function( error ) { + if ( error ) { + console.log( "Error connecting to the database." ); + console.log( error ); + process.exit( 1 ); + } + + runQueries([ + "BEGIN TRANSACTION", + "CREATE TEMPORARY TABLE plugins_backup(" + + "plugin TEXT PRIMARY KEY, " + + "owner TEXT, " + + "repo TEXT, " + + "watchers INTEGER DEFAULT 0, " + + "forks INTEGER DEFAULT 0" + + ")", + "INSERT INTO plugins_backup SELECT * FROM plugins", + "DROP TABLE plugins", + "CREATE TABLE plugins (" + + "plugin TEXT PRIMARY KEY COLLATE NOCASE, " + + "owner TEXT, " + + "repo TEXT, " + + "watchers INTEGER DEFAULT 0, " + + "forks INTEGER DEFAULT 0" + + ")", + "INSERT INTO plugins SELECT * FROM plugins_backup", + "DROP TABLE plugins_backup", + "COMMIT" + ], function( error ) { + if ( error ) { + console.log( "Error updating table." ); + console.log( error ); + process.exit( 1 ); + } + + console.log( "Successfully updated table." ); + }); +}); diff --git a/bin/remove-release.js b/bin/remove-release.js new file mode 100755 index 0000000..1585b64 --- /dev/null +++ b/bin/remove-release.js @@ -0,0 +1,85 @@ +#!/usr/bin/env node + +var Step = require( "step" ), + pluginsDb = require( "../lib/pluginsdb" ), + wordpress = require( "../lib/wordpress" ); + +process.stdin.setEncoding( "utf8" ); + +function prompt( message, fn ) { + process.stdout.write( message + " " ); + process.stdin.resume(); + + process.stdin.once( "data", function( chunk ) { + process.stdin.pause(); + fn( null, chunk.trim() ); + }); +} + +function showError( error ) { + console.log( "Error removing release" ); + console.log( error.stack ); + process.exit( 1 ); +} + +function removeRelease() { + var plugin, version; + + Step( + function() { + + // Find out which plugin to remove + prompt( "Plugin:", this ); + }, + + function( error, _plugin ) { + if ( error ) { + return showError( error ); + } + + plugin = _plugin; + + // Find out which version + prompt( "Version:", this ); + }, + + function( error, _version ) { + if ( error ) { + return showError( error ); + } + + version = _version; + + // Verify the release exists in WordPress + wordpress.getPostForRelease( plugin, version, this ); + }, + + function( error, post ) { + if ( error ) { + return showError( error ); + } + + if ( !post.id ) { + console.log( plugin + " " + version + " does not exist in WordPress." ); + process.exit( 1 ); + } + + // Track the removal + pluginsDb.removeRelease({ + plugin: plugin, + version: version, + postId: post.id + }, this ); + }, + + function( error ) { + if ( error ) { + return showError( error ); + } + + console.log( "Removal of " + plugin + " " + version + " has been queued." ); + } + ); +} + +removeRelease(); diff --git a/lib/pluginsdb.js b/lib/pluginsdb.js index 9686f8c..d883990 100644 --- a/lib/pluginsdb.js +++ b/lib/pluginsdb.js @@ -5,6 +5,7 @@ var db; function connect( fn ) { db = new sqlite.Database( config.pluginsDb, fn ); + return db; } function auto( fn ) { @@ -27,6 +28,8 @@ function auto( fn ) { } var pluginsDb = module.exports = { + connect: connect, + getOwner: auto(function( plugin, fn ) { db.get( "SELECT owner FROM plugins WHERE plugin = ?", [ plugin ], function( error, row ) { @@ -110,6 +113,13 @@ var pluginsDb = module.exports = { }); }), + removeRelease: auto(function( data, fn ) { + data = JSON.stringify( data ); + + db.run( "INSERT INTO actions( action, data ) VALUES( ?, ? )", + [ "removeRelease", data ], fn ); + }), + updateRepoMeta: auto(function( repo, data, fn ) { db.run( "UPDATE plugins SET watchers = ?, forks = ? " + "WHERE repo = ?", @@ -155,7 +165,7 @@ var pluginsDb = module.exports = { } db.run( "CREATE TABLE plugins (" + - "plugin TEXT PRIMARY KEY, " + + "plugin TEXT PRIMARY KEY COLLATE NOCASE, " + "owner TEXT, " + "repo TEXT, " + "watchers INTEGER DEFAULT 0, " + diff --git a/lib/wordpress.js b/lib/wordpress.js index 188217b..a2797c6 100644 --- a/lib/wordpress.js +++ b/lib/wordpress.js @@ -1,6 +1,11 @@ var wordpress = require( "wordpress" ), + semver = require( "semver" ), config = require( "./config" ); +function isStable( version ) { + return (/^\d+\.\d+\.\d+$/).test( version ); +} + var client = wordpress.createClient( config.wordpress ); client.getPostForPlugin = function( plugin, fn ) { @@ -13,4 +18,115 @@ client.getPostForPlugin = function( plugin, fn ) { }); }; +client.getPostForRelease = function( plugin, version, fn ) { + client.authenticatedCall( "jq-pjc.getPostForRelease", plugin, version, function( error, post ) { + if ( error ) { + return fn( error ); + } + + fn( null, wordpress.fieldMap.from( post, "post" ) ); + }); +}; + +client.post = { + fromRelease: function( data, fn ) { + var repo = data.repo, + manifest = data.manifest, + tag = data.tag; + + repo.getReleaseDate( tag, function( error, date ) { + if ( error ) { + return fn( error ); + } + + fn( null, { + type: "jquery_plugin", + status: "publish", + title: manifest.title, + content: manifest.description, + date: date, + termNames: { + post_tag: (manifest.keywords || []).map(function( keyword ) { + return keyword.toLowerCase(); + }) + }, + customFields: [ + { key: "download_url", value: manifest.download || repo.downloadUrl( tag ) }, + { key: "repo_url", value: repo.siteUrl }, + { key: "manifest", value: JSON.stringify( manifest ) } + ] + }); + }); + }, + + getVersions: function( page ) { + var versions = (page.customFields || []).filter(function( customField ) { + return customField.key === "versions"; + }); + return versions.length ? JSON.parse( versions[ 0 ].value ) : []; + }, + + parseVersions: function( versions ) { + var listed, latest; + + listed = versions + .sort( semver.compare ) + .reverse() + .filter(function( version ) { + if ( latest ) { + return isStable( version ); + } + if ( isStable( version ) ) { + latest = version; + } + return true; + }) + .reverse(); + + // No stable relases yet, show latest pre-release + if ( !latest ) { + latest = listed[ listed.length - 1 ]; + } + + return { + all: versions, + listed: listed, + latest: latest + }; + }, + + addVersion: function( versions, newVersion ) { + return this.parseVersions( versions.concat( newVersion ) ); + }, + + removeVersion: function( versions, oldVersion ) { + var index = versions.indexOf( oldVersion ); + if ( index !== -1 ) { + // Slice first to avoid modifying the original array + versions = versions.slice(); + versions.splice( index, 1 ); + } + + return this.parseVersions( versions ); + }, + + mergeCustomFields: function( existing, current ) { + current.forEach(function( customField ) { + + // If the field already exists, update the value + for ( var i = 0, length = existing.length - 1; i < length; i++ ) { + if ( existing[ i ].key === customField.key ) { + existing[ i ].value = customField.value; + return; + } + } + + // The field doesn't exist, so add it + existing.push( customField ); + }); + + return existing; + } +}; + module.exports = client; diff --git a/scripts/wordpress-update.js b/scripts/wordpress-update.js index 5abf49a..834ebfa 100644 --- a/scripts/wordpress-update.js +++ b/scripts/wordpress-update.js @@ -1,5 +1,4 @@ var fs = require( "fs" ), - semver = require( "semver" ), Step = require( "step" ), config = require( "../lib/config" ), wordpress = require( "../lib/wordpress" ), @@ -13,9 +12,6 @@ process.on( "uncaughtException", function( error ) { logger.error( "Uncaught exception: " + error.stack ); }); -function isStable( version ) { - return (/^\d+\.\d+\.\d+$/).test( version ); -} function extend( a, b ) { for ( var p in b ) { a[ p ] = b[ p ]; @@ -31,93 +27,13 @@ actions.addRelease = function( data, fn ) { manifest = data.manifest, tag = data.tag; - function getPageDetails( fn ) { - Step( - function() { - repo.getReleaseDate( tag, this ); - }, - - function( error, date ) { - if ( error ) { - return fn( error ); - } - - fn( null, { - type: "jquery_plugin", - status: "publish", - title: manifest.title, - content: manifest.description, - date: date, - termNames: { - post_tag: (manifest.keywords || []).map(function( keyword ) { - return keyword.toLowerCase(); - }) - }, - customFields: [ - { key: "download_url", value: manifest.download || repo.downloadUrl( tag ) }, - { key: "repo_url", value: repo.siteUrl }, - { key: "manifest", value: JSON.stringify( manifest ) } - ] - }); - } - ); - } - - function mergeCustomFields( existing, current ) { - current.forEach(function( customField ) { - // if the field already exists, update the value - for ( var i = 0, length = existing.length - 1; i < length; i++ ) { - if ( existing[ i ].key === customField.key ) { - existing[ i ].value = customField.value; - return; - } - } - - // the field doesn't exist, so add it - existing.push( customField ); - }); - - return existing; - } - - function getVersions( page ) { - var versions, listed, latest; - - versions = (page.customFields || []).filter(function( customField ) { - return customField.key === "versions"; - }); - versions = versions.length ? JSON.parse( versions[ 0 ].value ) : []; - - listed = versions - .concat( manifest.version ) - .sort( semver.compare ) - .reverse() - .filter(function( version ) { - if ( latest ) { - return isStable( version ); - } - if ( isStable( version ) ) { - latest = version; - } - return true; - }) - .reverse(); - - // no stable relases yet, show latest pre-release - if ( !latest ) { - latest = listed[ listed.length - 1 ]; - } - - return { - all: versions, - listed: listed, - latest: latest - }; - } - Step( function getPageData() { - getPageDetails( this.parallel() ); + wordpress.post.fromRelease({ + repo: repo, + manifest: manifest, + tag: tag + }, this.parallel() ); pluginsDb.getMeta( manifest.name, this.parallel() ); wordpress.getPostForPlugin( manifest.name, this.parallel() ); }, @@ -128,30 +44,31 @@ actions.addRelease = function( data, fn ) { } this.parallel()( null, pageDetails ); - var versions = getVersions( existingPage ), - mainPageCallback = this.parallel(), + var mainPageCallback = this.parallel(), existingCustomFields = existingPage.customFields || [], mainPage = { customFields: existingCustomFields - }; + }, + versions = wordpress.post.addVersion( + wordpress.post.getVersions( existingPage ), + manifest.version ); // The main page starts as an empty object so that publishing a new // version which is not the latest version only updates the metadata - // of the main page. If the new version is the latest, then use the + // of the main page. If the new version is the latest, then the // main page is constructed from the new version since pretty much // anything can change between versions. if ( versions.latest === manifest.version ) { extend( mainPage, pageDetails ); - // don't update the post date on main page, and let it be set - // to whenever we processed it, no harm here. + // Don't update the post date on the main page delete mainPage.date; mainPage.name = manifest.name; - mainPage.customFields = mergeCustomFields( + mainPage.customFields = wordpress.post.mergeCustomFields( existingCustomFields, pageDetails.customFields ); } // Always update the metadata for the main page - mainPage.customFields = mergeCustomFields( mainPage.customFields, [ + mainPage.customFields = wordpress.post.mergeCustomFields( mainPage.customFields, [ { key: "versions", value: JSON.stringify( versions.listed ) }, { key: "latest", value: versions.latest }, { key: "watchers", value: repoMeta.watchers }, @@ -192,6 +109,111 @@ actions.addRelease = function( data, fn ) { ); }; +actions.removeRelease = function( data, fn ) { + var mainPost, versions, + plugin = data.plugin, + version = data.version, + versionedPostId = data.postId; + + // Get the main post + wordpress.getPostForPlugin( plugin, function( error, post ) { + if ( error ) { + return fn( error ); + } + + var isLatest; + + mainPost = post; + if ( !mainPost.id ) { + return fn( new Error( "Error getting the main post for " + plugin + "." ) ); + } + + versions = wordpress.post.parseVersions( wordpress.post.getVersions( mainPost ) ); + isLatest = versions.latest === version; + versions = wordpress.post.removeVersion( versions.all, version ); + + if ( isLatest ) { + updateMainPage(); + } else { + updateVersionMetaOnly(); + } + }); + + function updateVersionMetaOnly() { + publishMainPost({ + customFields: wordpress.post.mergeCustomFields( mainPost.customFields, [ + { key: "versions", value: JSON.stringify( versions.listed ) }, + { key: "latest", value: versions.latest } + ]) + }); + } + + function updateMainPage() { + + // Get the post for the new latest release + wordpress.getPostForRelease( plugin, versions.latest, function( error, post ) { + if ( error ) { + return fn( error ); + } + + var mainPostUpdate; + + if ( !post.id ) { + return fn( new Error( "Error getting the post for " + + plugin + " " + versions.latest + "." ) ); + } + + mainPostUpdate = { + title: post.title, + content: post.description, + termNames: { + post_tag: (post.terms || []) + .filter(function( term ) { + return term.taxonomy === "post_tag"; + }) + .map(function( term ) { + return term.name; + }) + }, + customFields: wordpress.post.mergeCustomFields( + mainPost.customFields, post.customFields ) + }; + mainPostUpdate.customFields = wordpress.post.mergeCustomFields( + mainPostUpdate.customFields, + [ + { key: "versions", value: JSON.stringify( versions.listed ) }, + { key: "latest", value: versions.latest } + ]); + + publishMainPost( mainPostUpdate ); + }); + } + + function publishMainPost( post ) { + wordpress.editPost( mainPost.id, post, function( error ) { + if ( error ) { + return fn( error ); + } + }); + + deleteVersionedPost(); + } + + function deleteVersionedPost() { + + // Custom post types don't go through the trash, so only delete once + wordpress.deletePost( versionedPostId, function( error ) { + if ( error ) { + return fn( error ); + } + + logger.log( "Deleted " + plugin + " " + version + + " (" + versionedPostId + ") from WordPress." ); + + fn( null ); + }); + } +};