<commit_before>// pub-sub functions for team interaction
/*jslint node: true */

'use strict';

var indexer = require('./indexer'), utils = require('./utils'), auth = require('./auth');
var annotations = require('./annotations.js'), contentLib = require('./content'), searchLib = require('./search.js');

var faye = require('faye'), fs = require('fs');
var bayeux = new faye.NodeAdapter({mount: GLOBAL.config.FAYEMOUNT, timeout: 45});
var fayeClient = new faye.Client(GLOBAL.config.FAYEHOST);

exports.itemIndexed = itemIndexed;
exports.start = start;
exports.publish = publish;

// An item has been created or updated.
//
// Desc contains additional content data.
function itemIndexed(cItem, desc) {
  requestAnnotate(desc);
  updateItem(cItem);
}

// Validates client subscriptions against session / system IDs.
function clientSubscribe(loc, cb) {
  fayeClient.subscribe(loc, function(data) {
    var user = auth.userByClientID(data.clientID), clientID;
    if (user) {
      clientID = data.clientID;
      delete data.clientID;
      cb(data, clientID, user.username);
    } else {
      console.log('invalid client', data);
    }
  });
}

function start(server) {
  bayeux.attach(server);

// user functions

  clientSubscribe('/team/list', function(data, clientID) {
    GLOBAL.info('team/list'.yellow, data);
    fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
  });

  clientSubscribe('/team/remove', function(data, clientID) {
    GLOBAL.info('team/remove'.yellow, data);
    for (var i = 0; i < GLOBAL.config.users.length; i++) {
      if (GLOBAL.config.users[i].username == data.username) {
        GLOBAL.config.users.splice(i, 1);
        saveUsers();
        fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
      }
    }
  });

  clientSubscribe('/team/save', function(data, clientID) {
    GLOBAL.info('team/save'.yellow, data);
    for (var i = 0; i < GLOBAL.config.users.length; i++) {
      if (GLOBAL.config.users[i].username == data.username) {
        GLOBAL.config.users[i] = data;
        saveUsers();
        fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
      }
    }
  });

  clientSubscribe('/team/add', function(data, clientID) {
    GLOBAL.info('team/add'.yellow, data);
    if (data.name && data.type) {
      saveUsers(function() {
        GLOBAL.config.users.push({id : new Date().getTime(), active: new Date(), username : data.name, type: data.type});
      });
      fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
    }
  });

// FIXME log signouts
  clientSubscribe('/logout', function(data) {
    console.log('/logout', data);
  });

// annotations

// retrieve annotations for an individual uri
  clientSubscribe('/annotate', function(data, clientID) {
    GLOBAL.info('/annotate'.yellow, data);
    // FIXME deal with anchors
    indexer.retrieveByURI(data.uri.replace(/#.*$/, ''), function(err, res) {
      if (err) {
        GLOBAL.error('/annotate error' + err);
      } else {
        fayeClient.publish('/annotations/' + clientID, { uri: data.uri, annotations: res._source.annotations});
      }
    });
  });

  // index incoming annotations, adding validated state
  clientSubscribe('/saveAnnotations', function(combo, clientID) {
    GLOBAL.info('saveAnnotations'.yellow, combo);
    var uris = combo.uris || [combo.uri];
    try {
      uris.forEach(function(uri) {
        var annos = [];
    // apply validation state based on user
        combo.annotations.forEach(function(p) {
          var by = p.annotatedBy || combo.annotatedBy;
          p.state = utils.states.annotations.unvalidated;
          GLOBAL.config.users.forEach(function(u) {
            if (u.username === by) {
              p.annotatedBy = by;
              if (!u.needsValidation) {
                p.state = utils.states.annotations.validated;
              }
            }
          });
          p.hasTarget = uri;
          var a = annotations.createAnnotation(p);
          annos.push(a);
        });

        GLOBAL.config.indexer.saveAnnotations(uri, annos, function(err, res, cItem) {
          if (cItem) {
            fayeClient.publish('/annotations/' + clientID, { uri: uri, annotationSummary: cItem.annotationSummary, annotations: cItem.annotations});
          } else {
            utils.passingError('missing cItem');
          }
        });
      });
    } catch (err) {
      console.log('saveAnnotations failed', err);
    }
  });

  // index subscriptions
  clientSubscribe('/subscriptions/save', function(combo, clientID, member) {
    GLOBAL.info('/subscriptions/save'.yellow, combo);
    combo.matches.forEach(function(match) {
      var sub = {member: member, match: match.trim(), created: new Date()};
      GLOBAL.config.indexer.saveSubscription(sub);
    });
  });

  // index subscriptions
  clientSubscribe('/subscriptions/retrieve', function(combo, clientID, member) {
    GLOBAL.info('/subscriptions/retrieve'.yellow, combo);
    indexer.retrieveSubscriptions(member, function(err, results) {
      fayeClient.publish('/subscriptions/results/' + clientID, results);
    }, combo.options);
  });

  // form query
  clientSubscribe('/query', function(data, clientID) {
    GLOBAL.info('/query'.yellow, clientID);
    var query = GLOBAL.config.indexer.formQuery;
    if (data.nav === 'cluster') {
      query = GLOBAL.config.indexer.formCluster;
    }
    query(data, function(err, results) {
      if (err) {
        GLOBAL.error('/query query failed', JSON.stringify((results || {}).query, null, 2));
        return;
      }
      if (results.hits) {
        results.nav = data.nav;
        // create annotation overview
        if (data.annotations) {
          var root = { text : 'Annotations', size: 0, children: [], items: [] };
          // build a size of hierarchical annotations
          results.hits.hits.forEach(function(hit) {
            (hit._source.annotations || []).forEach(function(anno) {
              // for each start at the root
              var last = root;
              // iterate through its annotations
              if (!anno.position) {
                GLOBAL.error('missing anno position', anno);
              } else {
                anno.position.forEach(function(p) {
                  var cur;
                  // find its parent
                  last.children.forEach(function(c) {
                    if (c.text === p) {
                      cur = c;
                      return;
                    }
                  });
                  // or create it
                  if (!cur) {
                    last.children.push({text: p, size: 0, children: [], items: []});
                    cur = last.children[last.children.length - 1];
                  }
                  // increment its instances
                  cur.size += 1;
                  // add uri for item selecting
                  if (cur.items.indexOf(hit._source.uri) < 0) {
                    cur.items.push(hit._source.uri);
                  }
                  // use it as the basis for the cur up
                  last = cur;
                });
              }
              // remove raw annotations
              delete hit._source.annotations;
            });
          });
          results.annotationOverview = root;
        }
        GLOBAL.info('queryResults'.yellow, clientID, results.hits.hits.length);
        fayeClient.publish('/queryResults/' + clientID, results);
      } else {
        fayeClient.publish('/queryResults/' + clientID, []);
        GLOBAL.debug('no query results');
      }
    }, data.annotations ? ({ sourceFields : indexer.sourceFields.concat(['annotations.' + data.annotations])}) : null);
  });

  // form cluster
  clientSubscribe('/cluster', function(data, clientID) {
    GLOBAL.info('/cluster'.yellow, data);
    GLOBAL.config.indexer.formCluster(data, function(err, results) {
      if (results) {
        GLOBAL.info('  /clusterResults'.yellow, err, results.hits.hits.length);
        fayeClient.publish('/clusterResults/' + clientID, results);
      } else {
        fayeClient.publish('/clusterResults/' + clientID, []);
        GLOBAL.debug('no cluster results');
      }
    });
  });

  // update content of existing item by uri
  clientSubscribe('/updateContent', function(desc, clientID, member) {
    GLOBAL.info('/updateContent'.yellow, desc.uri);
    contentLib.indexContentItem(desc, {member: member, isHTML: true});
  });

  // retrieve an existing content item by uri
  clientSubscribe('/getContentItem', function(uri) {
    GLOBAL.info('/getContentItem'.yellow, uri);
    indexer.retrieveByURI(uri, function(err, result) {
      fayeClient.publish('/updateItem', result);
    });
  });

// delete a document
  clientSubscribe('/delete', function(cItemURIs) {
    GLOBAL.info('/delete'.yellow, cItemURIs);
    indexer.deleteContentItems(cItemURIs.selected, function(err, res) {
      if (err) {
        GLOBAL.error('/delete', err, res);
      } else {
        fayeClient.publish('/deletedItem', {_id: res._id});
      }
    });
  });

// query moreLikeThis on document
  clientSubscribe('/moreLikeThis', function(data, clientID) {
    GLOBAL.info('/moreLikeThis'.yellow, data);
    GLOBAL.config.indexer.moreLikeThis(data.uri, function(err, results) {
      if (results) {
        GLOBAL.info('moreLikeThisResults', err, results.hits.hits.length);
        fayeClient.publish('/queryResults/' + clientID, results);
      } else {
        fayeClient.publish('/queryResults/' + clientID, []);
        GLOBAL.debug('NO RESULTS');
      }
    });
  });

// find existing searches
  clientSubscribe('/search/retrieve', function(data, clientID, member) {
    GLOBAL.info('/search/retrieve'.yellow, data);
    publishRetrievedSearches(member);
  });

// save a search
  clientSubscribe('/search/save', function(search, clientID, member) {
    search.member = member;
    GLOBAL.info('/search/save'.yellow, search);
    indexer.saveSearch(search, function(err, res) {
      setTimeout(function() { publishRetrievedSearches(member); } , 1000);
    });
  });

// remove a search
  clientSubscribe('/search/remove', function(data) {
    GLOBAL.info('/search/remove'.yellow, data);
  });

// subscribe to query requests
  clientSubscribe('/search/queue', function(data, clientID, member) {
    data.member = member;
    console.log('/search/queue'.yellow, data);
    searchLib.queueSearcher(data);
  });
}

// Publish an arbitary message
function publish(channel, message) {
  fayeClient.publish(channel, message);
}

// updates clients with a content item
function updateItem (cItem) {
  fayeClient.publish('/updateItem', cItem);
}

// requests annotators to annotate. pass plain text or it will be extracted from cItem
function requestAnnotate(cItem) {
  GLOBAL.info('requestAnnotate', cItem.uri, 'sel', cItem.selector, 'content', cItem.content ? cItem.content.length : 'nocontent');
  fayeClient.publish('/requestAnnotate', cItem);
}

function publishRetrievedSearches(member) {
  indexer.retrieveSearches({member: member}, function(err, results) {
    fayeClient.publish('/search/results', results);
  });
}

function saveUsers(istep) {
  fs.writeFileSync('versions/local-users.' + new Date().getTime() + '.json', JSON.stringify({ logins : GLOBAL.config.users}, null, 2));
  if (istep) { istep(); }
  fs.writeFileSync('local-users.json', JSON.stringify({ logins : GLOBAL.config.users}, null, 2));
}
<commit_msg>remove unused<commit_after>// pub-sub functions for team interaction
/*jslint node: true */

'use strict';

var indexer = require('./indexer'), utils = require('./utils'), auth = require('./auth');
var annotations = require('./annotations.js'), contentLib = require('./content'), searchLib = require('./search.js');

var faye = require('faye'), fs = require('fs');
var bayeux = new faye.NodeAdapter({mount: GLOBAL.config.FAYEMOUNT, timeout: 45});
var fayeClient = new faye.Client(GLOBAL.config.FAYEHOST);

exports.itemIndexed = itemIndexed;
exports.start = start;
exports.publish = publish;

// An item has been created or updated.
//
// Desc contains additional content data.
function itemIndexed(cItem, desc) {
  requestAnnotate(desc);
  updateItem(cItem);
}

// Validates client subscriptions against session / system IDs.
function clientSubscribe(loc, cb) {
  fayeClient.subscribe(loc, function(data) {
    var user = auth.userByClientID(data.clientID), clientID;
    if (user) {
      clientID = data.clientID;
      delete data.clientID;
      cb(data, clientID, user.username);
    } else {
      console.log('invalid client', data);
    }
  });
}

function start(server) {
  bayeux.attach(server);

// user functions

  clientSubscribe('/team/list', function(data, clientID) {
    GLOBAL.info('team/list'.yellow, data);
    fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
  });

  clientSubscribe('/team/remove', function(data, clientID) {
    GLOBAL.info('team/remove'.yellow, data);
    for (var i = 0; i < GLOBAL.config.users.length; i++) {
      if (GLOBAL.config.users[i].username == data.username) {
        GLOBAL.config.users.splice(i, 1);
        saveUsers();
        fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
      }
    }
  });

  clientSubscribe('/team/save', function(data, clientID) {
    GLOBAL.info('team/save'.yellow, data);
    for (var i = 0; i < GLOBAL.config.users.length; i++) {
      if (GLOBAL.config.users[i].username == data.username) {
        GLOBAL.config.users[i] = data;
        saveUsers();
        fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
      }
    }
  });

  clientSubscribe('/team/add', function(data, clientID) {
    GLOBAL.info('team/add'.yellow, data);
    if (data.name && data.type) {
      saveUsers(function() {
        GLOBAL.config.users.push({id : new Date().getTime(), active: new Date(), username : data.name, type: data.type});
      });
      fayeClient.publish('/teamList/' + clientID, GLOBAL.config.users);
    }
  });

// FIXME log signouts
  clientSubscribe('/logout', function(data) {
    console.log('/logout', data);
  });

// annotations

// retrieve annotations for an individual uri
  clientSubscribe('/annotate', function(data, clientID) {
    GLOBAL.info('/annotate'.yellow, data);
    // FIXME deal with anchors
    indexer.retrieveByURI(data.uri.replace(/#.*$/, ''), function(err, res) {
      if (err) {
        GLOBAL.error('/annotate error' + err);
      } else {
        fayeClient.publish('/annotations/' + clientID, { uri: data.uri, annotations: res._source.annotations});
      }
    });
  });

  // index incoming annotations, adding validated state
  clientSubscribe('/saveAnnotations', function(combo, clientID) {
    GLOBAL.info('saveAnnotations'.yellow, combo);
    var uris = combo.uris || [combo.uri];
    try {
      uris.forEach(function(uri) {
        var annos = [];
    // apply validation state based on user
        combo.annotations.forEach(function(p) {
          var by = p.annotatedBy || combo.annotatedBy;
          p.state = utils.states.annotations.unvalidated;
          GLOBAL.config.users.forEach(function(u) {
            if (u.username === by) {
              p.annotatedBy = by;
              if (!u.needsValidation) {
                p.state = utils.states.annotations.validated;
              }
            }
          });
          p.hasTarget = uri;
          var a = annotations.createAnnotation(p);
          annos.push(a);
        });

        GLOBAL.config.indexer.saveAnnotations(uri, annos, function(err, res, cItem) {
          if (cItem) {
            fayeClient.publish('/annotations/' + clientID, { uri: uri, annotationSummary: cItem.annotationSummary, annotations: cItem.annotations});
          } else {
            utils.passingError('missing cItem');
          }
        });
      });
    } catch (err) {
      console.log('saveAnnotations failed', err);
    }
  });

  // index subscriptions
  clientSubscribe('/subscriptions/save', function(combo, clientID, member) {
    GLOBAL.info('/subscriptions/save'.yellow, combo);
    combo.matches.forEach(function(match) {
      var sub = {member: member, match: match.trim(), created: new Date()};
      GLOBAL.config.indexer.saveSubscription(sub);
    });
  });

  // index subscriptions
  clientSubscribe('/subscriptions/retrieve', function(combo, clientID, member) {
    GLOBAL.info('/subscriptions/retrieve'.yellow, combo);
    indexer.retrieveSubscriptions(member, function(err, results) {
      fayeClient.publish('/subscriptions/results/' + clientID, results);
    }, combo.options);
  });

  // form query
  clientSubscribe('/query', function(data, clientID) {
    GLOBAL.info('/query'.yellow, clientID);
    var query = GLOBAL.config.indexer.formQuery;
    if (data.nav === 'cluster') {
      query = GLOBAL.config.indexer.formCluster;
    }
    query(data, function(err, results) {
      if (err) {
        GLOBAL.error('/query query failed', JSON.stringify((results || {}).query, null, 2));
        return;
      }
      if (results.hits) {
        results.nav = data.nav;
        // create annotation overview
        if (data.annotations) {
          var root = { text : 'Annotations', size: 0, children: [], items: [] };
          // build a size of hierarchical annotations
          results.hits.hits.forEach(function(hit) {
            (hit._source.annotations || []).forEach(function(anno) {
              // for each start at the root
              var last = root;
              // iterate through its annotations
              if (!anno.position) {
                GLOBAL.error('missing anno position', anno);
              } else {
                anno.position.forEach(function(p) {
                  var cur;
                  // find its parent
                  last.children.forEach(function(c) {
                    if (c.text === p) {
                      cur = c;
                      return;
                    }
                  });
                  // or create it
                  if (!cur) {
                    last.children.push({text: p, size: 0, children: [], items: []});
                    cur = last.children[last.children.length - 1];
                  }
                  // increment its instances
                  cur.size += 1;
                  // add uri for item selecting
                  if (cur.items.indexOf(hit._source.uri) < 0) {
                    cur.items.push(hit._source.uri);
                  }
                  // use it as the basis for the cur up
                  last = cur;
                });
              }
              // remove raw annotations
              delete hit._source.annotations;
            });
          });
          results.annotationOverview = root;
        }
        GLOBAL.info('queryResults'.yellow, clientID, results.hits.hits.length);
        fayeClient.publish('/queryResults/' + clientID, results);
      } else {
        fayeClient.publish('/queryResults/' + clientID, []);
        GLOBAL.debug('no query results');
      }
    }, data.annotations ? ({ sourceFields : indexer.sourceFields.concat(['annotations.' + data.annotations])}) : null);
  });

  // form cluster
  clientSubscribe('/cluster', function(data, clientID) {
    GLOBAL.info('/cluster'.yellow, data);
    GLOBAL.config.indexer.formCluster(data, function(err, results) {
      if (results) {
        GLOBAL.info('  /clusterResults'.yellow, err, results.hits.hits.length);
        fayeClient.publish('/clusterResults/' + clientID, results);
      } else {
        fayeClient.publish('/clusterResults/' + clientID, []);
        GLOBAL.debug('no cluster results');
      }
    });
  });

  // update content of existing item by uri
  clientSubscribe('/updateContent', function(desc, clientID, member) {
    GLOBAL.info('/updateContent'.yellow, desc.uri);
    contentLib.indexContentItem(desc, {member: member, isHTML: true});
  });

  // retrieve an existing content item by uri
  clientSubscribe('/getContentItem', function(uri) {
    GLOBAL.info('/getContentItem'.yellow, uri);
    indexer.retrieveByURI(uri, function(err, result) {
      fayeClient.publish('/updateItem', result);
    });
  });

// delete a document
  clientSubscribe('/delete', function(cItemURIs) {
    GLOBAL.info('/delete'.yellow, cItemURIs);
    indexer.deleteContentItems(cItemURIs.selected, function(err, res) {
      if (err) {
        GLOBAL.error('/delete', err, res);
      } else {
        fayeClient.publish('/deletedItem', {_id: res._id});
      }
    });
  });

// query moreLikeThis on document
  clientSubscribe('/moreLikeThis', function(data, clientID) {
    GLOBAL.info('/moreLikeThis'.yellow, data);
    GLOBAL.config.indexer.moreLikeThis(data.uri, function(err, results) {
      if (results) {
        GLOBAL.info('moreLikeThisResults', err, results.hits.hits.length);
        fayeClient.publish('/queryResults/' + clientID, results);
      } else {
        fayeClient.publish('/queryResults/' + clientID, []);
        GLOBAL.debug('NO RESULTS');
      }
    });
  });

// find existing searches
  clientSubscribe('/search/retrieve', function(data, clientID, member) {
    GLOBAL.info('/search/retrieve'.yellow, data);
    publishRetrievedSearches(member);
  });

// save a search
  clientSubscribe('/search/save', function(search, clientID, member) {
    search.member = member;
    GLOBAL.info('/search/save'.yellow, search);
    indexer.saveSearch(search, function(err, res) {
      setTimeout(function() { publishRetrievedSearches(member); } , 1000);
    });
  });

// subscribe to query requests
  clientSubscribe('/search/queue', function(data, clientID, member) {
    data.member = member;
    console.log('/search/queue'.yellow, data);
    searchLib.queueSearcher(data);
  });
}

// Publish an arbitary message
function publish(channel, message) {
  fayeClient.publish(channel, message);
}

// updates clients with a content item
function updateItem (cItem) {
  fayeClient.publish('/updateItem', cItem);
}

// requests annotators to annotate. pass plain text or it will be extracted from cItem
function requestAnnotate(cItem) {
  GLOBAL.info('requestAnnotate', cItem.uri, 'sel', cItem.selector, 'content', cItem.content ? cItem.content.length : 'nocontent');
  fayeClient.publish('/requestAnnotate', cItem);
}

function publishRetrievedSearches(member) {
  indexer.retrieveSearches({member: member}, function(err, results) {
    fayeClient.publish('/search/results', results);
  });
}

function saveUsers(istep) {
  fs.writeFileSync('versions/local-users.' + new Date().getTime() + '.json', JSON.stringify({ logins : GLOBAL.config.users}, null, 2));
  if (istep) { istep(); }
  fs.writeFileSync('local-users.json', JSON.stringify({ logins : GLOBAL.config.users}, null, 2));
}
<|endoftext|>


<commit_before>"use strict";

let path = require("path");
let process = require("process");
require("dotenv").config({ silent: process.env.NODE_ENV === "production" });
let app = require("express")();
var exphbs = require("express-handlebars");
const fs = require("fs");
const handlebarsHelpers = require("./api/helpers/handlebars-helpers.js");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const passport = require("passport");
const Auth0Strategy = require("passport-auth0");

// static text js objects
const sharedStaticText = require("./static-text/shared-static-text.js");
const aboutStaticText = require("./static-text/about-static-text.js");
const researchStaticText = require("./static-text/research-static-text.js");
const teachingStaticText = require("./static-text/teaching-static-text.js");
const contentTypesText = require("./static-text/content-types-static-text.js");

// config express-session
const sess = {
  secret: "THIS IS A RANDOM KEY",
  cookie: {},
  resave: false,
  saveUninitialized: true
};

if (app.get("env") === "production") {
  sess.cookie.secure = true; // serve secure cookies, requires https
}

app.use(session(sess));

// Configure Passport to use Auth0
const strategy = new Auth0Strategy({
  domain: process.env.AUTH0_DOMAIN,
  clientID: process.env.AUTH0_CLIENT_ID,
  clientSecret: process.env.AUTH0_CLIENT_SECRET,
  callbackURL: process.env.AUTH0_CALLBACK_URL || "/redirect"
  },
  function (accessToken, refreshToken, extraParams, profile, done) {
    // accessToken is the token to call Auth0 API (not needed in the most cases)
    // extraParams.id_token has the JSON Web Token
    // profile has all the information from the user
    return done(null, profile);
  }
);

passport.use(strategy);

app.use(passport.initialize());
app.use(passport.session());

// You can use this section to keep a smaller payload
passport.serializeUser(function (user, done) {
  done(null, user);
});

passport.deserializeUser(function (user, done) {
  done(null, user);
});

app.use((req, res, next) => {
  res.locals.user = req.user;
  next();
});

// Perform the login, after login Auth0 will redirect to callback
app.get("/login", passport.authenticate("auth0", {
  scope: "openid email profile"
}), function (req, res) {
  res.redirect("/");
});

// Perform the final stage of authentication and redirect to previously requested URL or '/user'
app.get("/redirect", function (req, res, next) {
  passport.authenticate("auth0", function (err, user, info) {
    if (err) { return next(err); }
    if (!user) { return res.redirect("/login"); }
    req.logIn(user, function (err) {
      if (err) { return next(err); }
      const returnTo = req.session.returnTo;
      delete req.session.returnTo;
      // todo: return to original url where /login was requested from
      res.redirect(returnTo || '/');
    });
  })(req, res, next);
});

// Perform session logout and redirect to homepage
app.get("/logout", (req, res) => {
  req.logout();
  res.redirect("/");
});

var hbs = exphbs.create({
  // Specify helpers which are only registered on this instance.
  defaultLayout: "main",
  extname: ".html",
  helpers: handlebarsHelpers
});

// make data available as local vars in templates
app.use((req, res, next) => {
  const getGATrackingId = () => {
    if (app.get("env") === "production") {
      return "UA-132033152-1";
    } else {
      // development or staging
      return "UA-132033152-2";
    }
  };

  res.locals.req = req;
  res.locals.GA_TRACKING_ID = getGATrackingId();
  next();
});

app.engine(".html", hbs.engine);
app.set("view engine", ".html");

if (
  process.env.NODE_ENV === "test" &&
  process.env.AUTH0_CLIENT_SECRET !== "notasecret"
) {
  console.error(
    "CODING ERROR: Someone imported 'app' before 'setupenv' in the test suite"
  );
  process.exit(1);
}

// Better logging of "unhandled" promise exceptions
process.on("unhandledRejection", function(reason, p) {
  console.warn(
    "Possibly Unhandled Rejection at: Promise ",
    p,
    " reason: ",
    reason
  );
  // application specific logging here
});

let express = require("express");
let compression = require("compression");
let AWS = require("aws-sdk");
AWS.config.update({ region: "us-east-1" });
app.use(compression());
let port = process.env.PORT || 3001;

// Actual Participedia APIS vs. Nodejs gunk
let case_ = require("./api/controllers/case");
let method = require("./api/controllers/method");
let organization = require("./api/controllers/organization");
let bookmark = require("./api/controllers/bookmark");
let search = require("./api/controllers/search");
let list = require("./api/controllers/list");
let user = require("./api/controllers/user");

let errorhandler = require("errorhandler");
let morgan = require("morgan");
let bodyParser = require("body-parser");
let methodOverride = require("method-override");
let cors = require("cors");
let isUser = require("./api/middleware/isUser");
const {
  checkJwtRequired,
  checkJwtOptional
} = require("./api/helpers/checkJwt");
let { ensureUser, preferUser } = require("./api/helpers/user");

app.set("port", port);
app.use(express.static("public", { index: false }));
app.use(morgan("dev")); // request logging
app.use(methodOverride()); // Do we actually use/need this?
app.use(cors());
app.use(bodyParser.json({ limit: "5mb" }));
app.use(bodyParser.urlencoded({ limit: "5mb", extended: true }));
app.use(cookieParser());
// handle expired login tokens more gracefully
app.use(ensureUser.unless({ method: ["OPTIONS", "GET"] }));
app.use(
  preferUser.unless({ method: ["OPTIONS", "POST", "PUT", "DELETE", "PATCH"] })
);
app.use(errorhandler());

const apicache = require("apicache");
const cache = apicache.middleware;
apicache.options({
  debug: true,
  enabled: false,
  successCodes: [200, 201]
});
// TODO Invalidate apicache on PUT/POST/DELETE using apicache.clear(req.params.collection);

app.use("/", cache("5 minutes"), search);

app.use("/case", case_);
app.use("/organization", organization);
app.use("/method", method);
app.use("/list", list);
app.use("/user", user);
app.use("/bookmark", bookmark);

app.get("/about", function(req, res) {
  const staticText = Object.assign({}, sharedStaticText, aboutStaticText);
  res.status(200).render("about-view", { static: staticText });
});
app.get("/legal", function(req, res) {
  res.status(200).render("legal-view");
});
app.get("/research", function(req, res) {
  const staticText = Object.assign({}, sharedStaticText, researchStaticText);
  res.status(200).render("research-view", { static: staticText });
});
app.get("/teaching", function(req, res) {
  const staticText = Object.assign({}, sharedStaticText, teachingStaticText);
  res.status(200).render("teaching-view", {
    static: staticText
  });
});
app.get("/content-chooser", function(req, res) {
  const staticText = Object.assign({}, sharedStaticText, contentTypesText);
  res.status(200).render("content-chooser", {
    static: staticText
  });
});

app.use("/s3/:path", checkJwtRequired);
app.use(
  "/s3",
  require("react-dropzone-s3-uploader/s3router")({
    bucket: "uploads.participedia.xyz",
    region: "us-east-1", // optional
    headers: { "Access-Control-Allow-Origin": "*" }, // optional
    ACL: "private" // this is default
  })
);

module.exports = app;
<commit_msg>enable offline_access so user doesn't get logged out at short intervals<commit_after>"use strict";

let path = require("path");
let process = require("process");
require("dotenv").config({ silent: process.env.NODE_ENV === "production" });
let app = require("express")();
var exphbs = require("express-handlebars");
const fs = require("fs");
const handlebarsHelpers = require("./api/helpers/handlebars-helpers.js");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const passport = require("passport");
const Auth0Strategy = require("passport-auth0");

// static text js objects
const sharedStaticText = require("./static-text/shared-static-text.js");
const aboutStaticText = require("./static-text/about-static-text.js");
const researchStaticText = require("./static-text/research-static-text.js");
const teachingStaticText = require("./static-text/teaching-static-text.js");
const contentTypesText = require("./static-text/content-types-static-text.js");

// config express-session
const sess = {
  secret: "THIS IS A RANDOM KEY",
  cookie: {},
  resave: false,
  saveUninitialized: true
};

if (app.get("env") === "production") {
  sess.cookie.secure = true; // serve secure cookies, requires https
}

app.use(session(sess));

// Configure Passport to use Auth0
const strategy = new Auth0Strategy({
  domain: process.env.AUTH0_DOMAIN,
  clientID: process.env.AUTH0_CLIENT_ID,
  clientSecret: process.env.AUTH0_CLIENT_SECRET,
  callbackURL: process.env.AUTH0_CALLBACK_URL || "/redirect"
  },
  function (accessToken, refreshToken, extraParams, profile, done) {
    // accessToken is the token to call Auth0 API (not needed in the most cases)
    // extraParams.id_token has the JSON Web Token
    // profile has all the information from the user
    return done(null, profile);
  }
);

passport.use(strategy);

app.use(passport.initialize());
app.use(passport.session());

// You can use this section to keep a smaller payload
passport.serializeUser(function (user, done) {
  done(null, user);
});

passport.deserializeUser(function (user, done) {
  done(null, user);
});

app.use((req, res, next) => {
  res.locals.user = req.user;
  next();
});

// Perform the login, after login Auth0 will redirect to callback
app.get("/login", passport.authenticate("auth0", {
  scope: "offline_access openid email profile"
}), function (req, res) {
  res.redirect("/");
});

// Perform the final stage of authentication and redirect to previously requested URL or '/user'
app.get("/redirect", function (req, res, next) {
  passport.authenticate("auth0", function (err, user, info) {
    if (err) { return next(err); }
    if (!user) { return res.redirect("/login"); }
    req.logIn(user, function (err) {
      if (err) { return next(err); }
      const returnTo = req.session.returnTo;
      delete req.session.returnTo;
      // todo: return to original url where /login was requested from
      res.redirect(returnTo || '/');
    });
  })(req, res, next);
});

// Perform session logout and redirect to homepage
app.get("/logout", (req, res) => {
  req.logout();
  res.redirect("/");
});

var hbs = exphbs.create({
  // Specify helpers which are only registered on this instance.
  defaultLayout: "main",
  extname: ".html",
  helpers: handlebarsHelpers
});

// make data available as local vars in templates
app.use((req, res, next) => {
  const getGATrackingId = () => {
    if (app.get("env") === "production") {
      return "UA-132033152-1";
    } else {
      // development or staging
      return "UA-132033152-2";
    }
  };

  res.locals.req = req;
  res.locals.GA_TRACKING_ID = getGATrackingId();
  next();
});

app.engine(".html", hbs.engine);
app.set("view engine", ".html");

if (
  process.env.NODE_ENV === "test" &&
  process.env.AUTH0_CLIENT_SECRET !== "notasecret"
) {
  console.error(
    "CODING ERROR: Someone imported 'app' before 'setupenv' in the test suite"
  );
  process.exit(1);
}

// Better logging of "unhandled" promise exceptions
process.on("unhandledRejection", function(reason, p) {
  console.warn(
    "Possibly Unhandled Rejection at: Promise ",
    p,
    " reason: ",
    reason
  );
  // application specific logging here
});

let express = require("express");
let compression = require("compression");
let AWS = require("aws-sdk");
AWS.config.update({ region: "us-east-1" });
app.use(compression());
let port = process.env.PORT || 3001;

// Actual Participedia APIS vs. Nodejs gunk
let case_ = require("./api/controllers/case");
let method = require("./api/controllers/method");
let organization = require("./api/controllers/organization");
let bookmark = require("./api/controllers/bookmark");
let search = require("./api/controllers/search");
let list = require("./api/controllers/list");
let user = require("./api/controllers/user");

let errorhandler = require("errorhandler");
let morgan = require("morgan");
let bodyParser = require("body-parser");
let methodOverride = require("method-override");
let cors = require("cors");
let isUser = require("./api/middleware/isUser");
const {
  checkJwtRequired,
  checkJwtOptional
} = require("./api/helpers/checkJwt");
let { ensureUser, preferUser } = require("./api/helpers/user");

app.set("port", port);
app.use(express.static("public", { index: false }));
app.use(morgan("dev")); // request logging
app.use(methodOverride()); // Do we actually use/need this?
app.use(cors());
app.use(bodyParser.json({ limit: "5mb" }));
app.use(bodyParser.urlencoded({ limit: "5mb", extended: true }));
app.use(cookieParser());
// handle expired login tokens more gracefully
app.use(ensureUser.unless({ method: ["OPTIONS", "GET"] }));
app.use(
  preferUser.unless({ method: ["OPTIONS", "POST", "PUT", "DELETE", "PATCH"] })
);
app.use(errorhandler());

const apicache = require("apicache");
const cache = apicache.middleware;
apicache.options({
  debug: true,
  enabled: false,
  successCodes: [200, 201]
});
// TODO Invalidate apicache on PUT/POST/DELETE using apicache.clear(req.params.collection);

app.use("/", cache("5 minutes"), search);

app.use("/case", case_);
app.use("/organization", organization);
app.use("/method", method);
app.use("/list", list);
app.use("/user", user);
app.use("/bookmark", bookmark);

app.get("/about", function(req, res) {
  const staticText = Object.assign({}, sharedStaticText, aboutStaticText);
  res.status(200).render("about-view", { static: staticText });
});
app.get("/legal", function(req, res) {
  res.status(200).render("legal-view");
});
app.get("/research", function(req, res) {
  const staticText = Object.assign({}, sharedStaticText, researchStaticText);
  res.status(200).render("research-view", { static: staticText });
});
app.get("/teaching", function(req, res) {
  const staticText = Object.assign({}, sharedStaticText, teachingStaticText);
  res.status(200).render("teaching-view", {
    static: staticText
  });
});
app.get("/content-chooser", function(req, res) {
  const staticText = Object.assign({}, sharedStaticText, contentTypesText);
  res.status(200).render("content-chooser", {
    static: staticText
  });
});

app.use("/s3/:path", checkJwtRequired);
app.use(
  "/s3",
  require("react-dropzone-s3-uploader/s3router")({
    bucket: "uploads.participedia.xyz",
    region: "us-east-1", // optional
    headers: { "Access-Control-Allow-Origin": "*" }, // optional
    ACL: "private" // this is default
  })
);

module.exports = app;
<|endoftext|>


<commit_before>var fs = require('fs'),
    xml2js = require('xml2js'),
    inspect = require('eyes').inspector({maxLength: false}),
    datastore = require('../../lib/datastore'),
    Q = require('q');

var parser = new xml2js.Parser();
var recs = { };
var promises = [ ];
var filenames = process.argv.slice(2);
datastore.query('SELECT id, controlno FROM records', [ ], function (err, results) {
    recs = { };
    if (err) {
        throw('unable to get subjects');
    } else {
        for (var ii in results) {
            recs[results[ii].controlno] = results[ii].id;
        }
        filenames.forEach(function (filename) {
            console.log('Opening file ' + filename);
            fs.readFile(filename, function(err, data) {
                parser.parseString(data, function (err, result) {
                    var record;
                    console.log('Processing ' + result.records.record.length + ' records in ' + filename);
                    for (var ii in result.records.record) {
                        if (ii % 1000 === 0 && ii > 0) {
                            console.log('... ' + ii + ' of ' + result.records.record.length);
                        }
                        record = result.records.record[ii];
                        var rec = {
                            title: record.metadata[0]['dc:title'][0],
                            source: record.metadata[0]['dc:source'][0],
                            citation: record.metadata[0]['eric:citation'][0],
                            description: record.metadata[0]['dc:description'][0],
                            publisher: record.metadata[0]['dc:publisher'][0],
                            subjects: record.metadata[0]['dc:subject'],
                            types: record.metadata[0]['dc:type'],
                        };
                        for (var jj in record.metadata[0]['dc:identifier']) {
                            if (record.metadata[0]['dc:identifier'][jj]['$'].scheme === 'dcterms:URI') {
                                rec.doi = record.metadata[0]['dc:identifier'][jj]['_'];
                            } else if (record.metadata[0]['dc:identifier'][jj]['$'].scheme === 'eric_accno') {
                                rec.accno = record.metadata[0]['dc:identifier'][jj]['_'];
                            }
                        }
                        rec.creators = [];
                        for (var jj in record.metadata[0]['dc:creator']) {
                            if (record.metadata[0]['dc:creator'][jj]['_']) {
                                rec.creators.push(record.metadata[0]['dc:creator'][jj]['_']);
                                if (!recs[record.metadata[0]['dc:creator'][jj]['_']]) {
                                    if (record.metadata[0]['dc:creator'][jj]['$'].scheme === 'personal author') {
                                        recs[record.metadata[0]['dc:creator'][jj]['_']] = 1;
                                        promises.push(addRecord({ "article":{ "children":[ { "header":{ "children":[ { "span":{ "children":[ record.metadata[0]['dc:creator'][jj]['_'] ] } }, ] } }, ] } }, 2, record.metadata[0]['dc:creator'][jj]['_'], 'bnjson'));
                                    } else if (record.metadata[0]['dc:creator'][jj]['$'].scheme === 'institution') {
                                        recs[record.metadata[0]['dc:creator'][jj]['_']] = 1;
                                        promises.push(addRecord({ "article":{ "children":[ { "header":{ "children":[ { "span":{ "children":[ record.metadata[0]['dc:creator'][jj]['_'] ] } }, ] } }, ] } }, 19, record.metadata[0]['dc:creator'][jj]['_'], 'bnjson'));
                                    }
                                }
                            }
                        }
                        promises.push(addRecord(rec, 20, rec.accno));
                    }

                    console.log('Creating ' + promises.length + ' records for ' + filename);
                    Q.all(promises).then(function (data) {
                        promises = [];
                        for (var ii in data) {
                            if (data[ii].accno) {
                                recs[data[ii].accno] = data[ii];
                            } else if (data[ii].article) {
                                recs[data[ii].article.children[0].header.children[0].span.children[0]] = data[ii].id;
                            }
                        }
                        for (var rec in recs) {
                            if (typeof recs[rec] === 'object') {
                                if (recs[rec].creators) {
                                    for (var ref in recs[rec].creators) {
                                        ref = recs[rec].creators[ref];
                                        if (recs[ref]) {
                                            promises.push(addLink(recs[rec].id, recs[ref], 1, 'Created', 'By'));
                                        }
                                    }
                                }
                                if (recs[rec].subjects) {
                                    for (var ref in recs[rec].subjects) {
                                        ref = recs[rec].subjects[ref];
                                        if (recs[ref]) {
                                            promises.push(addLink(recs[rec].id, recs[ref], 8, 'Topic Of', 'About'));
                                        }
                                    }
                                }
                            }
                        }
                        console.log('Creating ' + promises.length + ' links for ' + filename);
                        return Q.all(promises);
                    }).then(function () {
                        console.log('done');
                        process.exit();
                    });
                });
            });
        });
    }
});

function addLink(source, target, field, in_label, out_label) {
    var deferred = Q.defer();
    datastore.query('INSERT INTO record_links (source_id, target_id, field_id, in_label, out_label, created_at, updated_at) VALUES (?, ?, ?, ?, ?, NOW(), NOW())', [ source, target, field, in_label, out_label ], function (err, results) {
        if (err) {
            console.log(err);
            deferred.reject(err);
        } else {
            deferred.resolve(results);
        }
    });
    return deferred.promise;
}

function addRecord(rec, recordtype, controlno, format) {
    var deferred = Q.defer();
    format = format || 'eric';
    datastore.query('INSERT INTO records (data, recordtype_id, collection_id, controlno, format, created_at, updated_at, deleted) VALUES (?, ?, ?, ?, ?, NOW(), NOW(), 0)', [ JSON.stringify(rec), recordtype, 1, controlno, format ], function (err, results) {
        if (err) {
            console.log(err);
            deferred.reject(err);
        } else {
            rec.id = results.insertId;
            deferred.resolve(rec);
        }
    });
    return deferred.promise;
}

<commit_msg>Don't block all linking for parallel record loads<commit_after>var fs = require('fs'),
    xml2js = require('xml2js'),
    inspect = require('eyes').inspector({maxLength: false}),
    datastore = require('../../lib/datastore'),
    Q = require('q');

var parser = new xml2js.Parser();
var recs = { };
var filenames = process.argv.slice(2);
datastore.query('SELECT id, controlno FROM records', [ ], function (err, results) {
    recs = { };
    if (err) {
        throw('unable to get subjects');
    } else {
        for (var ii in results) {
            recs[results[ii].controlno] = results[ii].id;
        }
        filenames.forEach(function (filename) {
            var promises = [ ];
            console.log('Opening file ' + filename);
            fs.readFile(filename, function(err, data) {
                parser.parseString(data, function (err, result) {
                    var record;
                    console.log('Processing ' + result.records.record.length + ' records in ' + filename);
                    for (var ii in result.records.record) {
                        if (ii % 1000 === 0 && ii > 0) {
                            console.log('... ' + ii + ' of ' + result.records.record.length);
                        }
                        record = result.records.record[ii];
                        var rec = {
                            title: record.metadata[0]['dc:title'][0],
                            source: record.metadata[0]['dc:source'][0],
                            citation: record.metadata[0]['eric:citation'][0],
                            description: record.metadata[0]['dc:description'][0],
                            publisher: record.metadata[0]['dc:publisher'][0],
                            subjects: record.metadata[0]['dc:subject'],
                            types: record.metadata[0]['dc:type'],
                        };
                        for (var jj in record.metadata[0]['dc:identifier']) {
                            if (record.metadata[0]['dc:identifier'][jj]['$'].scheme === 'dcterms:URI') {
                                rec.doi = record.metadata[0]['dc:identifier'][jj]['_'];
                            } else if (record.metadata[0]['dc:identifier'][jj]['$'].scheme === 'eric_accno') {
                                rec.accno = record.metadata[0]['dc:identifier'][jj]['_'];
                            }
                        }
                        rec.creators = [];
                        for (var jj in record.metadata[0]['dc:creator']) {
                            if (record.metadata[0]['dc:creator'][jj]['_']) {
                                rec.creators.push(record.metadata[0]['dc:creator'][jj]['_']);
                                if (!recs[record.metadata[0]['dc:creator'][jj]['_']]) {
                                    if (record.metadata[0]['dc:creator'][jj]['$'].scheme === 'personal author') {
                                        recs[record.metadata[0]['dc:creator'][jj]['_']] = 1;
                                        promises.push(addRecord({ "article":{ "children":[ { "header":{ "children":[ { "span":{ "children":[ record.metadata[0]['dc:creator'][jj]['_'] ] } }, ] } }, ] } }, 2, record.metadata[0]['dc:creator'][jj]['_'], 'bnjson'));
                                    } else if (record.metadata[0]['dc:creator'][jj]['$'].scheme === 'institution') {
                                        recs[record.metadata[0]['dc:creator'][jj]['_']] = 1;
                                        promises.push(addRecord({ "article":{ "children":[ { "header":{ "children":[ { "span":{ "children":[ record.metadata[0]['dc:creator'][jj]['_'] ] } }, ] } }, ] } }, 19, record.metadata[0]['dc:creator'][jj]['_'], 'bnjson'));
                                    }
                                }
                            }
                        }
                        promises.push(addRecord(rec, 20, rec.accno));
                    }

                    console.log('Creating ' + promises.length + ' records for ' + filename);
                    Q.all(promises).then(function (data) {
                        promises = [];
                        for (var ii in data) {
                            if (data[ii].accno) {
                                recs[data[ii].accno] = data[ii];
                            } else if (data[ii].article) {
                                recs[data[ii].article.children[0].header.children[0].span.children[0]] = data[ii].id;
                            }
                        }
                        for (var rec in recs) {
                            if (typeof recs[rec] === 'object') {
                                if (recs[rec].creators) {
                                    for (var ref in recs[rec].creators) {
                                        ref = recs[rec].creators[ref];
                                        if (recs[ref]) {
                                            promises.push(addLink(recs[rec].id, recs[ref], 1, 'Created', 'By'));
                                        }
                                    }
                                }
                                if (recs[rec].subjects) {
                                    for (var ref in recs[rec].subjects) {
                                        ref = recs[rec].subjects[ref];
                                        if (recs[ref]) {
                                            promises.push(addLink(recs[rec].id, recs[ref], 8, 'Topic Of', 'About'));
                                        }
                                    }
                                }
                            }
                        }
                        console.log('Creating ' + promises.length + ' links for ' + filename);
                        return Q.all(promises);
                    }).then(function () {
                        console.log('done');
                        process.exit();
                    });
                });
            });
        });
    }
});

function addLink(source, target, field, in_label, out_label) {
    var deferred = Q.defer();
    datastore.query('INSERT INTO record_links (source_id, target_id, field_id, in_label, out_label, created_at, updated_at) VALUES (?, ?, ?, ?, ?, NOW(), NOW())', [ source, target, field, in_label, out_label ], function (err, results) {
        if (err) {
            console.log(err);
            deferred.reject(err);
        } else {
            deferred.resolve(results);
        }
    });
    return deferred.promise;
}

function addRecord(rec, recordtype, controlno, format) {
    var deferred = Q.defer();
    format = format || 'eric';
    datastore.query('INSERT INTO records (data, recordtype_id, collection_id, controlno, format, created_at, updated_at, deleted) VALUES (?, ?, ?, ?, ?, NOW(), NOW(), 0)', [ JSON.stringify(rec), recordtype, 1, controlno, format ], function (err, results) {
        if (err) {
            console.log(err);
            deferred.reject(err);
        } else {
            rec.id = results.insertId;
            deferred.resolve(rec);
        }
    });
    return deferred.promise;
}

<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');
const {
  exec, exit, cp, test,
} = require('shelljs');
const semver = require('semver');
const readline = require('readline-sync');
const pick = require('lodash/pick');
const buildSite = require('./buildSite');
const ghpages = require('gh-pages');

const {
  BASE_PACKAGE,
  PACKAGE_NAME,
  LIB_DIR,
  OUT_DIR,
  SITE_DIR,
  SITE_JS_DIR,
  log,
  logSuccess,
  logError,
  execFail,
} = require('./util');

const PACKAGE_JSON = {
  keepFields: [
    'name',
    'version',
    'license',
    'homepage',
    'repository',
    'description',
    'keywords',
    'bugs',
  ],
  extraFields: {
    engines: { node: '>=8' },
    main: `${LIB_DIR}/wanakana.js`,
    browser: `${LIB_DIR}/wanakana.min.js`,
    unpkg: `${LIB_DIR}/wanakana.min.js`,
    module: `${LIB_DIR}/wanakana.esm.js`,
  },
};

const writePackage = (outDir, packageData) =>
  fs.writeFileSync(path.resolve(outDir, 'package.json'), JSON.stringify(packageData, null, 2));

try {
  log('Preparing release...');
  if (execFail(exec('git diff-files --quiet'))) {
    logError(
      'You have unsaved changes in the working tree. Commit or stash changes before releasing.'
    );
    exit(1);
  }

  log('Have you updated the changelog?');
  if (!readline.keyInYN('Yes I have!')) {
    log('OK. Do that, then try again!');
    exit(0);
  }

  log('Running tests...');
  if (execFail(exec('npm run lint && npm test'))) {
    logError('The test command did not exit cleanly. Aborting release.');
    exit(1);
  }
  logSuccess('Tests were successful.');

  log('Building dist files...');
  if (execFail(exec('npm run build'))) {
    logError('The build command did not exit cleanly. Aborting release.');
    exit(1);
  }
  log('Compilation was successful.');

  log('Copying additional project files...');
  const additionalProjectFiles = ['README.md', 'CHANGELOG.md', 'package.json', 'LICENSE'];

  additionalProjectFiles.forEach((filename) => {
    const src = path.resolve(process.cwd(), filename);
    if (!test('-e', src)) {
      logError(`Unable to resolve ${src}`);
      exit(1);
    }
    cp('-Rf', src, path.resolve(OUT_DIR));
  });

  const versionLoc = path.resolve('VERSION');
  const version = fs.readFileSync(versionLoc, 'utf8').trim();
  let nextVersion = readline.question(
    `Next version of ${PACKAGE_NAME} (current version is ${version}): `
  );

  while (!(!nextVersion || (semver.valid(nextVersion) && semver.gt(nextVersion, version)))) {
    nextVersion = readline.question(
      `Must provide a valid version that is greater than ${version}, or leave blank to skip: `
    );
  }

  const distTag = readline.question(
    'Do you want to add an npm dist-tag other than latest? Leave blank to skip: '
  );

  log('Updating package.json...');
  const updatedPackage = Object.assign({}, BASE_PACKAGE, { version: nextVersion });
  const releasePackage = Object.assign(
    {},
    pick(updatedPackage, PACKAGE_JSON.keepFields),
    PACKAGE_JSON.extraFields
  );

  writePackage(OUT_DIR, releasePackage);

  log(`About to publish ${PACKAGE_NAME}@${nextVersion}${distTag && `.${distTag}`} to npm.`);
  if (!readline.keyInYN('Sound good? ')) {
    log('OK. Stopping release.');
    exit(0);
  }

  log('Publishing...');
  if (execFail(exec(`cd ${OUT_DIR} && npm publish${distTag && ` --tag ${distTag}`}`))) {
    logError('Publish failed. Aborting release.');
    exit(1);
  }
  logSuccess(`${PACKAGE_NAME}@${nextVersion} was successfully published.`);

  log('Updating VERSION file...');
  fs.writeFileSync(versionLoc, `${nextVersion}\n`);

  log('Copying dynamic version for demo site');
  fs.writeFileSync(
    path.resolve(SITE_JS_DIR, 'version.js'),
    `document.querySelector('#wk-version').textContent = '${nextVersion}'`
  );

  log('Updating repo package.json');
  writePackage(process.cwd(), updatedPackage);

  log('Rebuilding docs');
  if (execFail(exec('npm run docs'))) {
    logError('Building docs failed.');
    exit(1);
  }

  log('Rebuilding demo site');
  buildSite(nextVersion + distTag && `.${distTag}`);

  if (!distTag) {
    log('Publishing github-pages demo & docs');
    ghpages.publish(SITE_DIR, (err) => {
      if (err) {
        logError(err);
        logError('Publish github pages failed.');
        exit(1);
      } else {
        logSuccess('Published demo and docs to gh-pages branch');
        exit(0);
      }
    });
  }

  log('Committing changes...');
  const newTagName = `${nextVersion}${distTag && `.${distTag}`}`;
  exec(`git add ${versionLoc} package.json`);
  exec(`git commit -m "Version ${newTagName}"`);

  if (!distTag) {
    log(`Tagging release... (${newTagName})`);
    exec(`git tag ${newTagName}`);

    log('Pushing to GitHub...');
    exec('git push');
    exec('git push --tags');
  }

  logSuccess('Done.');
} catch (error) {
  logError('Release failed due to an error', error);
}
<commit_msg>fix: pre-release dist-tag naming<commit_after>const fs = require('fs');
const path = require('path');
const {
  exec, exit, cp, test,
} = require('shelljs');
const semver = require('semver');
const readline = require('readline-sync');
const pick = require('lodash/pick');
const buildSite = require('./buildSite');
const ghpages = require('gh-pages');

const {
  BASE_PACKAGE,
  PACKAGE_NAME,
  LIB_DIR,
  OUT_DIR,
  SITE_DIR,
  SITE_JS_DIR,
  log,
  logSuccess,
  logError,
  execFail,
} = require('./util');

const PACKAGE_JSON = {
  keepFields: [
    'name',
    'version',
    'license',
    'homepage',
    'repository',
    'description',
    'keywords',
    'bugs',
  ],
  extraFields: {
    engines: { node: '>=8' },
    main: `${LIB_DIR}/wanakana.js`,
    browser: `${LIB_DIR}/wanakana.min.js`,
    unpkg: `${LIB_DIR}/wanakana.min.js`,
    module: `${LIB_DIR}/wanakana.esm.js`,
  },
};

const writePackage = (outDir, packageData) =>
  fs.writeFileSync(path.resolve(outDir, 'package.json'), JSON.stringify(packageData, null, 2));

try {
  log('Preparing release...');
  if (execFail(exec('git diff-files --quiet'))) {
    logError(
      'You have unsaved changes in the working tree. Commit or stash changes before releasing.'
    );
    exit(1);
  }

  log('Have you updated the changelog?');
  if (!readline.keyInYN('Yes I have!')) {
    log('OK. Do that, then try again!');
    exit(0);
  }

  log('Running tests...');
  if (execFail(exec('npm run lint && npm test'))) {
    logError('The test command did not exit cleanly. Aborting release.');
    exit(1);
  }
  logSuccess('Tests were successful.');

  log('Building dist files...');
  if (execFail(exec('npm run build'))) {
    logError('The build command did not exit cleanly. Aborting release.');
    exit(1);
  }
  log('Compilation was successful.');

  log('Copying additional project files...');
  const additionalProjectFiles = ['README.md', 'CHANGELOG.md', 'package.json', 'LICENSE'];

  additionalProjectFiles.forEach((filename) => {
    const src = path.resolve(process.cwd(), filename);
    if (!test('-e', src)) {
      logError(`Unable to resolve ${src}`);
      exit(1);
    }
    cp('-Rf', src, path.resolve(OUT_DIR));
  });

  const versionLoc = path.resolve('VERSION');
  const version = fs.readFileSync(versionLoc, 'utf8').trim();
  let nextVersion = readline.question(
    `Next version of ${PACKAGE_NAME} (current version is ${version}): `
  );

  const distTag = readline.question(
    'Do you want to add an npm dist-tag other than latest(beta.0, rc.1 etc)? Leave blank to skip: '
  );

  if (distTag) {
    nextVersion = `${nextVersion}-${distTag}`;
  }

  while (!(!nextVersion || (semver.valid(nextVersion) && semver.gt(nextVersion, version)))) {
    nextVersion = readline.question(
      `Must provide a valid version that is greater than ${version}, or leave blank to skip: `
    );
  }
  log('Updating release package.json...');
  const updatedPackage = Object.assign({}, BASE_PACKAGE, { version: nextVersion });
  const releasePackage = Object.assign(
    {},
    pick(updatedPackage, PACKAGE_JSON.keepFields),
    PACKAGE_JSON.extraFields
  );

  writePackage(OUT_DIR, releasePackage);

  log(`About to publish ${PACKAGE_NAME}@${nextVersion} to npm.`);
  if (!readline.keyInYN('Sound good? ')) {
    log('OK. Stopping release.');
    exit(0);
  }

  log('Publishing...');
  if (execFail(exec(`cd ${OUT_DIR} && npm publish${distTag && ` --tag ${distTag}`}`))) {
    logError('Publish failed. Aborting release.');
    exit(1);
  }
  logSuccess(`${PACKAGE_NAME}@${nextVersion} was successfully published.`);

  log('Updating VERSION file...');
  fs.writeFileSync(versionLoc, `${nextVersion}\n`);

  log('Copying dynamic version for demo site');
  fs.writeFileSync(
    path.resolve(SITE_JS_DIR, 'version.js'),
    `document.querySelector('#wk-version').textContent = '${nextVersion}'`
  );

  log('Updating repo package.json');
  writePackage(process.cwd(), updatedPackage);

  log('Rebuilding docs');
  if (execFail(exec('npm run docs'))) {
    logError('Building docs failed.');
    exit(1);
  }

  log('Rebuilding demo site');
  buildSite(nextVersion);

  if (!distTag) {
    log('Publishing github-pages demo & docs');
    ghpages.publish(SITE_DIR, (err) => {
      if (err) {
        logError(err);
        logError('Publish github pages failed.');
        exit(1);
      } else {
        logSuccess('Published demo and docs to gh-pages branch');
        exit(0);
      }
    });
  }

  log('Committing changes...');
  exec(`git add ${versionLoc} package.json`);
  exec(`git commit -m "Version ${nextVersion}"`);

  if (!distTag) {
    log(`Tagging release... (${nextVersion})`);
    exec(`git tag ${nextVersion}`);

    log('Pushing to GitHub...');
    exec('git push');
    exec('git push --tags');
  }

  logSuccess('Done.');
} catch (error) {
  logError('Release failed due to an error', error);
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { BrowserRouter, Route, Link } from "react-router-dom";
import classnames from 'classnames';
import _ from 'lodash';

import { api } from '/api';
import { subscription } from '/subscription';
import { store } from '/store';
import { Skeleton } from '/components/skeleton';
import { NewScreen } from '/components/new';
import { Links } from '/components/links-list';
import { LinkDetail } from '/components/link';
import { base64urlDecode } from '../lib/util';

export class Root extends Component {
  constructor(props) {
    super(props);

    this.state = store.state;
    store.setStateHandler(this.setState.bind(this));
  }

  render() {
    const { state } = this;

    let contacts = !!state.contacts ? state.contacts : {};
    const groups = !!state.groups ? state.groups : {};

    const resources = !!state.resources ? state.resources : {};
    let links = !!state.links ? state.links : {};
    let comments = !!state.comments ? state.comments : {};
    const seen = !!state.seen ? state.seen : {};

    const invites = '/link' in state.invites ?
      state.invites['/link'] : {};

    return (
      <BrowserRouter>
        <Route exact path="/~link"
          render={ (props) => {
            return (
              <Skeleton
                active="channels"
                spinner={state.spinner}
                resources={resources}
                invites={invites}
                groups={groups}
                rightPanelHide={true}
                sidebarShown={true}
                links={links}>

                        Select or create a collection to begin.

              </Skeleton>
            );
          }} />
        <Route exact path="/~link/new"
          render={(props) => {
            return (
              <Skeleton
                active="channels"
                spinner={state.spinner}
                resources={resources}
                invites={invites}
                groups={groups}
                rightPanelHide={true}
                sidebarShown={true}
                links={links}>
                <NewScreen
                  resources={resources}
                  groups={groups}
                  contacts={contacts}
                  {...props}
                />
              </Skeleton>
            );
          }}/>
          <Route exact path="/~link/(popout)?/list/:page/:resource([^#]+)"
            render={ (props) => {
              const resourcePath = '/' + props.match.params.resource;
              const resource = resources[resourcePath] || {};

              let contactDetails = contacts[resource.group] || {};

              let page = props.match.params.page || 0;

              let popout = props.match.url.includes("/popout/");

              let channelLinks = !!links[resourcePath]
              ? links[resourcePath]
              : {local: {}};

              let channelComments = !!comments[resourcePath]
                ? comments[resourcePath]
                : {};

              const channelSeen = !!seen[resourcePath]
                ? seen[resourcePath]
                : {};

              return (
                <Skeleton
                  spinner={state.spinner}
                  resources={resources}
                  invites={invites}
                  groups={groups}
                  active="links"
                  selected={resourcePath}
                  sidebarShown={state.sidebarShown}
                  sidebarHideMobile={true}
                  popout={popout}
                  links={links}>
                  <Links
                  {...props}
                  contacts={contactDetails}
                  links={channelLinks}
                  comments={channelComments}
                  seen={channelSeen}
                  page={page}
                  resourcePath={resourcePath}
                  resource={resource}
                  popout={popout}
                  sidebarShown={state.sidebarShown}
                  />
                </Skeleton>
              )
            }}
          />
          <Route exact path="/~link/(popout)?/item/:page/:index/:commentpage/:encodedUrl/:resource([^#]+)"
            render={ (props) => {
              const resourcePath = '/' + props.match.params.resource;
              const resource = resources[resourcePath] || {};

              let popout = props.match.url.includes("/popout/");

              let contactDetails = contacts[resource.group] || {};

              let index = props.match.params.index || 0;
              let page = props.match.params.page || 0;
              let url = base64urlDecode(props.match.params.encodedUrl);

              let data = !!links[resourcePath]
                ? !!links[resourcePath][page]
                  ? links[resourcePath][page][index]
                  : {}
                : {};
              let coms = !comments[resourcePath]
                ? undefined
                : comments[resourcePath][url];

              let commentPage = props.match.params.commentpage || 0;

              return (
                <Skeleton
                  spinner={state.spinner}
                  resources={resources}
                  invites={invites}
                  groups={groups}
                  active="links"
                  selected={resourcePath}
                  sidebarShown={state.sidebarShown}
                  sidebarHideMobile={true}
                  popout={popout}
                  links={links}>
                  <LinkDetail
                  {...props}
                  page={page}
                  url={url}
                  linkIndex={index}
                  contacts={contactDetails}
                  resourcePath={resourcePath}
                  groupPath={resource.group}
                  popout={popout}
                  sidebarShown={state.sidebarShown}
                  data={data}
                  comments={coms}
                  commentPage={commentPage}
                  />
                </Skeleton>
              )
            }}
          />
      </BrowserRouter>
    )
  }
}<commit_msg>link fe: support /join route<commit_after>import React, { Component } from 'react';
import { BrowserRouter, Route, Link } from "react-router-dom";
import classnames from 'classnames';
import _ from 'lodash';

import { api } from '/api';
import { subscription } from '/subscription';
import { store } from '/store';
import { Skeleton } from '/components/skeleton';
import { NewScreen } from '/components/new';
import { Links } from '/components/links-list';
import { LinkDetail } from '/components/link';
import { base64urlDecode } from '../lib/util';

export class Root extends Component {
  constructor(props) {
    super(props);

    this.state = store.state;
    store.setStateHandler(this.setState.bind(this));
  }

  render() {
    const { state } = this;

    let contacts = !!state.contacts ? state.contacts : {};
    const groups = !!state.groups ? state.groups : {};

    const resources = !!state.resources ? state.resources : {};
    let links = !!state.links ? state.links : {};
    let comments = !!state.comments ? state.comments : {};
    const seen = !!state.seen ? state.seen : {};

    const invites = '/link' in state.invites ?
      state.invites['/link'] : {};

    return (
      <BrowserRouter>
        <Route exact path="/~link"
          render={ (props) => {
            return (
              <Skeleton
                active="channels"
                spinner={state.spinner}
                resources={resources}
                invites={invites}
                groups={groups}
                rightPanelHide={true}
                sidebarShown={true}
                links={links}>

                        Select or create a collection to begin.

              </Skeleton>
            );
          }} />
        <Route exact path="/~link/new"
          render={(props) => {
            return (
              <Skeleton
                active="channels"
                spinner={state.spinner}
                resources={resources}
                invites={invites}
                groups={groups}
                rightPanelHide={true}
                sidebarShown={true}
                links={links}>
                <NewScreen
                  resources={resources}
                  groups={groups}
                  contacts={contacts}
                  {...props}
                />
              </Skeleton>
            );
          }}
        />
        <Route exact path="/~link/join/:resource([^#]+)"
          render={ (props) => {
            const resourcePath = '/' + props.match.params.resource;
            props.history.push(`/~link/list/0${resourcePath}`);
          }}
        />
          <Route exact path="/~link/(popout)?/list/:page/:resource([^#]+)"
            render={ (props) => {
              const resourcePath = '/' + props.match.params.resource;
              const resource = resources[resourcePath] || {};

              let contactDetails = contacts[resource.group] || {};

              let page = props.match.params.page || 0;

              let popout = props.match.url.includes("/popout/");

              let channelLinks = !!links[resourcePath]
              ? links[resourcePath]
              : {local: {}};

              let channelComments = !!comments[resourcePath]
                ? comments[resourcePath]
                : {};

              const channelSeen = !!seen[resourcePath]
                ? seen[resourcePath]
                : {};

              return (
                <Skeleton
                  spinner={state.spinner}
                  resources={resources}
                  invites={invites}
                  groups={groups}
                  active="links"
                  selected={resourcePath}
                  sidebarShown={state.sidebarShown}
                  sidebarHideMobile={true}
                  popout={popout}
                  links={links}>
                  <Links
                  {...props}
                  contacts={contactDetails}
                  links={channelLinks}
                  comments={channelComments}
                  seen={channelSeen}
                  page={page}
                  resourcePath={resourcePath}
                  resource={resource}
                  popout={popout}
                  sidebarShown={state.sidebarShown}
                  />
                </Skeleton>
              )
            }}
          />
          <Route exact path="/~link/(popout)?/item/:page/:index/:commentpage/:encodedUrl/:resource([^#]+)"
            render={ (props) => {
              const resourcePath = '/' + props.match.params.resource;
              const resource = resources[resourcePath] || {};

              let popout = props.match.url.includes("/popout/");

              let contactDetails = contacts[resource.group] || {};

              let index = props.match.params.index || 0;
              let page = props.match.params.page || 0;
              let url = base64urlDecode(props.match.params.encodedUrl);

              let data = !!links[resourcePath]
                ? !!links[resourcePath][page]
                  ? links[resourcePath][page][index]
                  : {}
                : {};
              let coms = !comments[resourcePath]
                ? undefined
                : comments[resourcePath][url];

              let commentPage = props.match.params.commentpage || 0;

              return (
                <Skeleton
                  spinner={state.spinner}
                  resources={resources}
                  invites={invites}
                  groups={groups}
                  active="links"
                  selected={resourcePath}
                  sidebarShown={state.sidebarShown}
                  sidebarHideMobile={true}
                  popout={popout}
                  links={links}>
                  <LinkDetail
                  {...props}
                  page={page}
                  url={url}
                  linkIndex={index}
                  contacts={contactDetails}
                  resourcePath={resourcePath}
                  groupPath={resource.group}
                  popout={popout}
                  sidebarShown={state.sidebarShown}
                  data={data}
                  comments={coms}
                  commentPage={commentPage}
                  />
                </Skeleton>
              )
            }}
          />
      </BrowserRouter>
    )
  }
}<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');
const { exec, exit, cp, test } = require('shelljs');
const semver = require('semver');
const readline = require('readline-sync');
const pick = require('lodash/pick');
const buildSite = require('./buildSite');
const ghpages = require('gh-pages');

const {
  BASE_PACKAGE,
  PACKAGE_NAME,
  LIB_DIR,
  OUT_DIR,
  SITE_DIR,
  SITE_JS_DIR,
  log,
  logSuccess,
  logError,
  execSuccess,
  execFail,
} = require('./util');

const PACKAGE_JSON = {
  keepFields: [
    'name',
    'version',
    'license',
    'homepage',
    'repository',
    'description',
    'keywords',
    'bugs',
  ],
  extraFields: {
    engines: { "node": ">=8" },
    main: `${LIB_DIR}/wanakana.js`,
    browser: `${LIB_DIR}/wanakana.min.js`,
    unpkg: `${LIB_DIR}/wanakana.min.js`,
    module: `${LIB_DIR}/wanakana.esm.js`,
  },
};

const writePackage = (outDir, packageData) =>
  fs.writeFileSync(
    path.resolve(outDir, 'package.json'),
    JSON.stringify(packageData, null, 2)
  );

try {
  log('Preparing release...');
  if (execFail(exec('git diff-files --quiet'))) {
    logError(
      'You have unsaved changes in the working tree. ' +
        'Commit or stash changes before releasing.'
    );
    exit(1);
  }

  log('Have you updated the changelog?');
  if (!readline.keyInYN('Yes I have!')) {
    log('OK. Do that, then try again!');
    exit(0);
  }

  log('Running tests...');
  if (execFail(exec('npm run lint && npm test'))) {
    logError('The test command did not exit cleanly. Aborting release.');
    exit(1);
  }
  logSuccess('Tests were successful.');

  log('Building dist files...');
  if (execFail(exec('npm run build'))) {
    logError('The build command did not exit cleanly. Aborting release.');
    exit(1);
  }
  log('Compilation was successful.');

  log('Copying additional project files...');
  const additionalProjectFiles = ['README.md', 'CHANGELOG.md', 'package.json', 'LICENSE'];

  additionalProjectFiles.forEach((filename) => {
    const src = path.resolve(process.cwd(), filename);
    if (!test('-e', src)) {
      logError(`Unable to resolve ${src}`);
      exit(1);
    }
    cp('-Rf', src, path.resolve(OUT_DIR));
  });

  const versionLoc = path.resolve('VERSION');
  const version = fs.readFileSync(versionLoc, 'utf8').trim();
  let nextVersion = readline.question(
    `Next version of ${PACKAGE_NAME} (current version is ${version}): `
  );

  while (
    !(!nextVersion || (semver.valid(nextVersion) && semver.gt(nextVersion, version)))
  ) {
    nextVersion = readline.question(
      `Must provide a valid version that is greater than ${version}, ` +
        'or leave blank to skip: '
    );
  }

  log('Updating package.json...');
  const updatedPackage = Object.assign({}, BASE_PACKAGE, { version: nextVersion });
  const releasePackage = Object.assign(
    {},
    pick(updatedPackage, PACKAGE_JSON.keepFields),
    PACKAGE_JSON.extraFields
  );

  writePackage(OUT_DIR, releasePackage);

  log(`About to publish ${PACKAGE_NAME}@${nextVersion} to npm.`);
  if (!readline.keyInYN('Sound good? ')) {
    log('OK. Stopping release.');
    exit(0);
  }

  log('Publishing...');
  if (execFail(exec(`cd ${OUT_DIR} && npm publish`))) {
    logError('Publish failed. Aborting release.');
    exit(1);
  }
  logSuccess(`${PACKAGE_NAME}@${nextVersion} was successfully published.`);

  log('Updating VERSION file...');
  fs.writeFileSync(versionLoc, `${nextVersion}\n`);

  log('Copying dynamic version for demo site');
  fs.writeFileSync(
    path.resolve(SITE_JS_DIR, 'version.js'),
    `document.querySelector('#wk-version').textContent = '${nextVersion}'`
  );

  log('Updating repo package.json');
  writePackage(process.cwd(), updatedPackage);

  log('Rebuilding docs');
  if (execFail(exec('npm run docs'))) {
    logError('Building docs failed.');
    exit(1);
  }

  log('Rebuilding demo site');
  buildSite(nextVersion);

  log('Publishing github-pages demo & docs');
  ghpages.publish(SITE_DIR, (err) => {
    if (err) {
      logError(err);
      logError('Publish github pages failed.');
      exit(1);
    } else {
      logSuccess('Published demo and docs to gh-pages branch');
      exit(0);
    }
  });

  log('Committing changes...');
  const newTagName = `${nextVersion}`;
  exec(`git add ${versionLoc} package.json`);
  exec(`git commit -m "Version ${newTagName}"`);

  log(`Tagging release... (${newTagName})`);
  exec(`git tag ${newTagName}`);

  log('Pushing to GitHub...');
  exec('git push');
  exec('git push --tags');

  logSuccess('Done.');
} catch (error) {
  logError('Release failed due to an error', error);
}
<commit_msg>build: add dist-tag option to release script [ci skip]<commit_after>const fs = require('fs');
const path = require('path');
const {
  exec, exit, cp, test,
} = require('shelljs');
const semver = require('semver');
const readline = require('readline-sync');
const pick = require('lodash/pick');
const buildSite = require('./buildSite');
const ghpages = require('gh-pages');

const {
  BASE_PACKAGE,
  PACKAGE_NAME,
  LIB_DIR,
  OUT_DIR,
  SITE_DIR,
  SITE_JS_DIR,
  log,
  logSuccess,
  logError,
  execFail,
} = require('./util');

const PACKAGE_JSON = {
  keepFields: [
    'name',
    'version',
    'license',
    'homepage',
    'repository',
    'description',
    'keywords',
    'bugs',
  ],
  extraFields: {
    engines: { node: '>=8' },
    main: `${LIB_DIR}/wanakana.js`,
    browser: `${LIB_DIR}/wanakana.min.js`,
    unpkg: `${LIB_DIR}/wanakana.min.js`,
    module: `${LIB_DIR}/wanakana.esm.js`,
  },
};

const writePackage = (outDir, packageData) =>
  fs.writeFileSync(
    path.resolve(outDir, 'package.json'),
    JSON.stringify(packageData, null, 2)
  );

try {
  log('Preparing release...');
  if (execFail(exec('git diff-files --quiet'))) {
    logError(
      'You have unsaved changes in the working tree. ' +
        'Commit or stash changes before releasing.'
    );
    exit(1);
  }

  log('Have you updated the changelog?');
  if (!readline.keyInYN('Yes I have!')) {
    log('OK. Do that, then try again!');
    exit(0);
  }

  log('Running tests...');
  if (execFail(exec('npm run lint && npm test'))) {
    logError('The test command did not exit cleanly. Aborting release.');
    exit(1);
  }
  logSuccess('Tests were successful.');

  log('Building dist files...');
  if (execFail(exec('npm run build'))) {
    logError('The build command did not exit cleanly. Aborting release.');
    exit(1);
  }
  log('Compilation was successful.');

  log('Copying additional project files...');
  const additionalProjectFiles = ['README.md', 'CHANGELOG.md', 'package.json', 'LICENSE'];

  additionalProjectFiles.forEach((filename) => {
    const src = path.resolve(process.cwd(), filename);
    if (!test('-e', src)) {
      logError(`Unable to resolve ${src}`);
      exit(1);
    }
    cp('-Rf', src, path.resolve(OUT_DIR));
  });

  const versionLoc = path.resolve('VERSION');
  const version = fs.readFileSync(versionLoc, 'utf8').trim();
  let nextVersion = readline.question(
    `Next version of ${PACKAGE_NAME} (current version is ${version}): `
  );

  while (
    !(!nextVersion || (semver.valid(nextVersion) && semver.gt(nextVersion, version)))
  ) {
    nextVersion = readline.question(
      `Must provide a valid version that is greater than ${version}, ` +
        'or leave blank to skip: '
    );
  }

  const distTag = readline.question(
    'Do you want to add an npm dist-tag other than latest? Leave blank to skip.'
  );

  log('Updating package.json...');
  const updatedPackage = Object.assign({}, BASE_PACKAGE, { version: nextVersion });
  const releasePackage = Object.assign(
    {},
    pick(updatedPackage, PACKAGE_JSON.keepFields),
    PACKAGE_JSON.extraFields
  );

  writePackage(OUT_DIR, releasePackage);

  log(
    `About to publish ${PACKAGE_NAME}@${nextVersion}${distTag && `.${distTag}`} to npm.`
  );
  if (!readline.keyInYN('Sound good? ')) {
    log('OK. Stopping release.');
    exit(0);
  }

  log('Publishing...');
  if (execFail(exec(`cd ${OUT_DIR} && npm publish${distTag && ` --tag ${distTag}`}`))) {
    logError('Publish failed. Aborting release.');
    exit(1);
  }
  logSuccess(`${PACKAGE_NAME}@${nextVersion} was successfully published.`);

  log('Updating VERSION file...');
  fs.writeFileSync(versionLoc, `${nextVersion}\n`);

  log('Copying dynamic version for demo site');
  fs.writeFileSync(
    path.resolve(SITE_JS_DIR, 'version.js'),
    `document.querySelector('#wk-version').textContent = '${nextVersion}'`
  );

  log('Updating repo package.json');
  writePackage(process.cwd(), updatedPackage);

  log('Rebuilding docs');
  if (execFail(exec('npm run docs'))) {
    logError('Building docs failed.');
    exit(1);
  }

  log('Rebuilding demo site');
  buildSite(nextVersion + distTag && `.${distTag}`);

  if (!distTag) {
    log('Publishing github-pages demo & docs');
    ghpages.publish(SITE_DIR, (err) => {
      if (err) {
        logError(err);
        logError('Publish github pages failed.');
        exit(1);
      } else {
        logSuccess('Published demo and docs to gh-pages branch');
        exit(0);
      }
    });
  }

  log('Committing changes...');
  const newTagName = `${nextVersion}${distTag && `.${distTag}`}`;
  exec(`git add ${versionLoc} package.json`);
  exec(`git commit -m "Version ${newTagName}"`);

  if (!distTag) {
    log(`Tagging release... (${newTagName})`);
    exec(`git tag ${newTagName}`);

    log('Pushing to GitHub...');
    exec('git push');
    exec('git push --tags');
  }

  logSuccess('Done.');
} catch (error) {
  logError('Release failed due to an error', error);
}
<|endoftext|>


<commit_before>var schedule = require('node-schedule');
var Trigger = require('./lib/relay');

var waterpumps = [1, 2, 3, 4, 5];<commit_msg>All on<commit_after>var schedule = require('node-schedule');
var Trigger = require('./lib/relay');

var allRelays = [3, 5, 7, 8, 10, 11, 12, 13, 15, 16, 18, 19, 21, 22, 23, 24, 26, 29, 31, 32, 33, 35, 36, 37, 38, 40];

Trigger(allRelays, true, comp);

function comp(){

  console.log("complete");
}<|endoftext|>


<commit_before>#!/usr/bin/env node

var os = require('os')
var path = require('path')
var pathExists = require('path-exists')
var mkdir = require('mkdirp')
var nugget = require('nugget')
var extract = require('extract-zip')
var fs = require('fs')
var getHomePath = require('home-path')()
var platform = os.platform()
var arch = os.arch()
var version = '0.25.3'
var filename = 'electron-v' + version + '-' + platform + '-' + arch + '.zip'
var url = 'https://github.com/atom/electron/releases/download/v' + version + '/electron-v' + version + '-' + platform + '-' + arch + '.zip'

function onerror (err) {
  throw err
}

var paths = {
  darwin: path.join(__dirname, './dist/Electron.app/Contents/MacOS/Electron'),
  linux: path.join(__dirname, './dist/electron'),
  win32: path.join(__dirname, './dist/electron.exe')
}

var cache = path.join(getHomePath, './.electron')

if (!paths[platform]) throw new Error('Unknown platform: ' + platform)

// use cache if possible
if (pathExists.sync(path.join(cache, filename))) {
  extractFile()
} else {
  mkdir(cache, function(err) {
    if (err) return onerror(err)
    nugget(url, {target: filename, dir: cache, resume: true, verbose: true}, extractFile)
  })
}

function extractFile (err) {
  if (err) return onerror(err)
  fs.writeFileSync(path.join(__dirname, 'path.txt'), paths[platform])
  extract(path.join(cache, filename), {dir: path.join(__dirname, 'dist')}, function (err) {
    if (err) return onerror(err)
  })
}
<commit_msg>reset version for #18<commit_after>#!/usr/bin/env node

var os = require('os')
var path = require('path')
var pathExists = require('path-exists')
var mkdir = require('mkdirp')
var nugget = require('nugget')
var extract = require('extract-zip')
var fs = require('fs')
var getHomePath = require('home-path')()
var platform = os.platform()
var arch = os.arch()
var version = '0.25.2'
var filename = 'electron-v' + version + '-' + platform + '-' + arch + '.zip'
var url = 'https://github.com/atom/electron/releases/download/v' + version + '/electron-v' + version + '-' + platform + '-' + arch + '.zip'

function onerror (err) {
  throw err
}

var paths = {
  darwin: path.join(__dirname, './dist/Electron.app/Contents/MacOS/Electron'),
  linux: path.join(__dirname, './dist/electron'),
  win32: path.join(__dirname, './dist/electron.exe')
}

var cache = path.join(getHomePath, './.electron')

if (!paths[platform]) throw new Error('Unknown platform: ' + platform)

// use cache if possible
if (pathExists.sync(path.join(cache, filename))) {
  extractFile()
} else {
  mkdir(cache, function(err) {
    if (err) return onerror(err)
    nugget(url, {target: filename, dir: cache, resume: true, verbose: true}, extractFile)
  })
}

function extractFile (err) {
  if (err) return onerror(err)
  fs.writeFileSync(path.join(__dirname, 'path.txt'), paths[platform])
  extract(path.join(cache, filename), {dir: path.join(__dirname, 'dist')}, function (err) {
    if (err) return onerror(err)
  })
}
<|endoftext|>


<commit_before>var inquirer = require('inquirer');
var cli = require('commander');
var exec = require('child_process').exec;
var q = require('q');

/**
 * Deferize a function running exec(), resloving and logging stdout on success, and rejecting and logging stderr on fails.
 * @function
 * @inner
 * @param {string} The command to pass to exec().
 * @returns {function} the deferized function.
 */
function deferizeExec(cmd){
  /**
   * The deferized function.
   * @function
   * @inner
   * @returns {Promise} the promise of the exec() call.
   */
  return function deferizedExec(){
    console.log('>>> Running command «'+cmd+'» ...');
    var d = q.defer();
    exec(cmd,function(err,stdout,stderr){
      if (err) {
        console.error(stderr);
        console.log('... FAIL ! <<<');
        d.reject(err);
      } else {
        console.log(stdout);
        console.log('... SUCCES ! <<<');
        d.resolve();
      }
    });
    return d.promise;
  };
}

function confirmPrompt(msg){
  var d = q.defer();
  if (cli.continue) {
    d.resolve(true);
  } else {
    try {
      inquirer.prompt({
        name: 'confirmRelease',
        type: 'confirm',
        message: msg,
        'default': false
      },function(confirmation){
        d.resolve(confirmation.confirmRelease);
      });
    } catch(err){
      d.reject(err);
    }
  }
  return d.promise;
}

cli.option('-c --continue', 'Do not prompt for confirmation');

cli.arguments('<semverLevel>').action(function(semverLevel){

  console.log('Prepare to release a new tag...');

  var confirmPromptMessage = 'Did you made a pull request of your last commits to upstream before building a new release ?';

  confirmPrompt(confirmPromptMessage) // if -c or --continue is not defined in the command line.
  .then(function(confirmation){
    if (confirmation) {
      return deferizeExec('git stash')()
        .then(deferizeExec('git checkout master'))
        .then(deferizeExec('git pull gh-sirap-group master'))
        .then(deferizeExec('grunt build'))
        .then(deferizeExec('git add . --all'))
        .then((function(level){
          return deferizeExec('git commit -m "build dist and docs to release '+level+'"')
          .catch(function(err){
            // if there is nothing to commit, the child_process will end with error code at 1
            // but we want to continue, its not really an error, but a warning.
            // We will ask to confirm for continuing.
            confirmPromptMessage = 'WARNING: It seems there is nothing to commit. Do you want to continue ?';
            return confirmRelease(confirmPromptMessage);
          })
          .then(function(confirmation){
            if (!confirmation) throw new Error('Aborted by user because there is nothing to commit for this release.');
            else return true;
          });
        })(semverLevel))
        .then((function(level){
          return deferizeExec('grunt bump:'+level);
        })(semverLevel))
        .then(deferizeExec('git push origin master'))
        .then(deferizeExec('git push gl-open-source master'))
        .then(deferizeExec('git push origin --tags'))
        .then(deferizeExec('git push gl-open-source --tags'))
      ;
    } else {
      console.log('Aborted by user');
    }
  }).catch(function(err){
    console.error('Error');
    console.error(err);
    console.error(err.stack);
  });

}).parse(process.argv);
<commit_msg>fix important typos on calling deferezeExec()<commit_after>var inquirer = require('inquirer');
var cli = require('commander');
var exec = require('child_process').exec;
var q = require('q');

/**
 * Deferize a function running exec(), resloving and logging stdout on success, and rejecting and logging stderr on fails.
 * @function
 * @inner
 * @param {string} The command to pass to exec().
 * @returns {function} the deferized function.
 */
function deferizeExec(cmd){
  /**
   * The deferized function.
   * @function
   * @inner
   * @returns {Promise} the promise of the exec() call.
   */
  return function deferizedExec(){
    console.log('>>> Running command «'+cmd+'» ...');
    var d = q.defer();
    exec(cmd,function(err,stdout,stderr){
      if (err) {
        console.error(stderr);
        console.log('... FAIL ! <<<');
        d.reject(err);
      } else {
        console.log(stdout);
        console.log('... SUCCES ! <<<');
        d.resolve();
      }
    });
    return d.promise;
  };
}

function confirmPrompt(msg){
  var d = q.defer();
  if (cli.continue) {
    d.resolve(true);
  } else {
    try {
      inquirer.prompt({
        name: 'confirmRelease',
        type: 'confirm',
        message: msg,
        'default': false
      },function(confirmation){
        d.resolve(confirmation.confirmRelease);
      });
    } catch(err){
      d.reject(err);
    }
  }
  return d.promise;
}

cli.option('-c --continue', 'Do not prompt for confirmation');

cli.arguments('<semverLevel>').action(function(semverLevel){

  console.log('Prepare to release a new tag...');

  var confirmPromptMessage = 'Did you made a pull request of your last commits to upstream before building a new release ?';

  confirmPrompt(confirmPromptMessage) // if -c or --continue is not defined in the command line.
  .then(function(confirmation){
    if (confirmation) {
      return deferizeExec('git stash')()
        .then(deferizeExec('git checkout master'))
        .then(deferizeExec('git pull gh-sirap-group master'))
        .then(deferizeExec('grunt build'))
        .then(deferizeExec('git add . --all'))
        .then((function(level){
          return deferizeExec('git commit -m "build dist and docs to release '+level+'"')();
          .catch(function(err){
            // if there is nothing to commit, the child_process will end with error code at 1
            // but we want to continue, its not really an error, but a warning.
            // We will ask to confirm for continuing.
            confirmPromptMessage = 'WARNING: It seems there is nothing to commit. Do you want to continue ?';
            return confirmPrompt(confirmPromptMessage);
          })
          .then(function(confirmation){
            if (!confirmation) throw new Error('Aborted by user because there is nothing to commit for this release.');
            else return true;
          });
        })(semverLevel))
        .then((function(level){
          return deferizeExec('grunt bump:'+level)();
        })(semverLevel))
        .then(deferizeExec('git push origin master'))
        .then(deferizeExec('git push gl-open-source master'))
        .then(deferizeExec('git push origin --tags'))
        .then(deferizeExec('git push gl-open-source --tags'))
      ;
    } else {
      console.log('Aborted by user');
    }
  }).catch(function(err){
    console.error('Error');
    console.error(err);
    console.error(err.stack);
  });

}).parse(process.argv);
<|endoftext|>