bugfix, lint and refactor all bin scripts (#4617)

* bugfix, lint and refactor all bin scripts

* for squash: throw Error(message) rather than log(message); throw Error()

* for squash: Exit non-0 on unhandled Promise rejection

Many of the recent lint changes have converted normal functions to
async functions, and an error thrown in an async function does not
cause Node.js to exit by default.

* for squash: fix `require()` paths

* for squash: remove erroneous `Object.keys()` call

* for squash: fix missing `continue` statements

* for squash: Fix HTTP method for deleteSession

* for squash: delete erroneous throw

Throw is only for errors, not successful completion.

* for squash: redo migrateDirtyDBtoRealDB.js to fix async bugs

* for squash: fix erroneous use of `for..of`

* for squash: Add line break between statements

* for squash: put closing paren on same line as last arg

* for squash: Move `log()` back up where it was

to minimize the diff to develop

* for squash: indentation fixes

* for squash: typo fix

* for squash: wrap long lines

* for squash: use `util.callbackify` to silence promise/no-callback-in-promise warning

* for squash: use double quotes to improve readability

Co-authored-by: Richard Hansen <rhansen@rhansen.org>
This commit is contained in:
John McLear 2021-01-18 08:53:15 +00:00 committed by GitHub
parent c0d9881a62
commit 2fdc737355
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 458 additions and 466 deletions

View file

@ -1,28 +1,31 @@
'use strict';
/* /*
* This is a debug tool. It checks all revisions for data corruption * This is a debug tool. It checks all revisions for data corruption
*/ */
if (process.argv.length != 2) { // As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
console.error('Use: node bin/checkAllPads.js'); // unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.exit(1); process.on('unhandledRejection', (err) => { throw err; });
}
if (process.argv.length !== 2) throw new Error('Use: node bin/checkAllPads.js');
// load and initialize NPM // load and initialize NPM
const npm = require('../src/node_modules/npm'); const npm = require('ep_etherpad-lite/node_modules/npm');
npm.load({}, async () => { npm.load({}, async () => {
try { try {
// initialize the database // initialize the database
const settings = require('../src/node/utils/Settings'); require('ep_etherpad-lite/node/utils/Settings');
const db = require('../src/node/db/DB'); const db = require('ep_etherpad-lite/node/db/DB');
await db.init(); await db.init();
// load modules // load modules
const Changeset = require('../src/static/js/Changeset'); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const padManager = require('../src/node/db/PadManager'); const padManager = require('ep_etherpad-lite/node/db/PadManager');
let revTestedCount = 0;
// get all pads // get all pads
const res = await padManager.listAllPads(); const res = await padManager.listAllPads();
for (const padId of res.padIDs) { for (const padId of res.padIDs) {
const pad = await padManager.getPad(padId); const pad = await padManager.getPad(padId);
@ -31,7 +34,6 @@ npm.load({}, async () => {
console.error(`[${pad.id}] Missing attribute pool`); console.error(`[${pad.id}] Missing attribute pool`);
continue; continue;
} }
// create an array with key kevisions // create an array with key kevisions
// key revisions always save the full pad atext // key revisions always save the full pad atext
const head = pad.getHeadRevisionNumber(); const head = pad.getHeadRevisionNumber();
@ -71,21 +73,23 @@ npm.load({}, async () => {
const apool = pad.pool; const apool = pad.pool;
let atext = revisions[keyRev].meta.atext; let atext = revisions[keyRev].meta.atext;
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) { for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
try { try {
const cs = revisions[rev].changeset; const cs = revisions[rev].changeset;
atext = Changeset.applyToAText(cs, atext, apool); atext = Changeset.applyToAText(cs, atext, apool);
revTestedCount++;
} catch (e) { } catch (e) {
console.error(`[${pad.id}] Bad changeset at revision ${i} - ${e.message}`); console.error(`[${pad.id}] Bad changeset at revision ${rev} - ${e.message}`);
} }
} }
} }
console.log('finished');
process.exit(0);
} }
if (revTestedCount === 0) {
throw new Error('No revisions tested');
}
console.log(`Finished: Tested ${revTestedCount} revisions`);
} catch (err) { } catch (err) {
console.trace(err); console.trace(err);
process.exit(1); throw err;
} }
}); });

View file

@ -1,33 +1,33 @@
'use strict';
/* /*
* This is a debug tool. It checks all revisions for data corruption * This is a debug tool. It checks all revisions for data corruption
*/ */
if (process.argv.length != 3) { // As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
console.error('Use: node bin/checkPad.js $PADID'); // unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.exit(1); process.on('unhandledRejection', (err) => { throw err; });
}
if (process.argv.length !== 3) throw new Error('Use: node bin/checkPad.js $PADID');
// get the padID // get the padID
const padId = process.argv[2]; const padId = process.argv[2];
let checkRevisionCount = 0;
// load and initialize NPM; // load and initialize NPM;
const npm = require('../src/node_modules/npm'); const npm = require('ep_etherpad-lite/node_modules/npm');
npm.load({}, async () => { npm.load({}, async () => {
try { try {
// initialize database // initialize database
const settings = require('../src/node/utils/Settings'); require('ep_etherpad-lite/node/utils/Settings');
const db = require('../src/node/db/DB'); const db = require('ep_etherpad-lite/node/db/DB');
await db.init(); await db.init();
// load modules // load modules
const Changeset = require('ep_etherpad-lite/static/js/Changeset'); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const padManager = require('../src/node/db/PadManager'); const padManager = require('ep_etherpad-lite/node/db/PadManager');
const exists = await padManager.doesPadExists(padId); const exists = await padManager.doesPadExists(padId);
if (!exists) { if (!exists) throw new Error('Pad does not exist');
console.error('Pad does not exist');
process.exit(1);
}
// get the pad // get the pad
const pad = await padManager.getPad(padId); const pad = await padManager.getPad(padId);
@ -41,7 +41,8 @@ npm.load({}, async () => {
} }
// run through all key revisions // run through all key revisions
for (const keyRev of keyRevisions) { for (let keyRev of keyRevisions) {
keyRev = parseInt(keyRev);
// create an array of revisions we need till the next keyRevision or the End // create an array of revisions we need till the next keyRevision or the End
const revisionsNeeded = []; const revisionsNeeded = [];
for (let rev = keyRev; rev <= keyRev + 100 && rev <= head; rev++) { for (let rev = keyRev; rev <= keyRev + 100 && rev <= head; rev++) {
@ -58,13 +59,12 @@ npm.load({}, async () => {
} }
// check if the pad has a pool // check if the pad has a pool
if (pad.pool === undefined) { if (pad.pool === undefined) throw new Error('Attribute pool is missing');
console.error('Attribute pool is missing');
process.exit(1);
}
// check if there is an atext in the keyRevisions // check if there is an atext in the keyRevisions
if (revisions[keyRev] === undefined || revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined) { if (revisions[keyRev] === undefined ||
revisions[keyRev].meta === undefined ||
revisions[keyRev].meta.atext === undefined) {
console.error(`No atext in key revision ${keyRev}`); console.error(`No atext in key revision ${keyRev}`);
continue; continue;
} }
@ -73,8 +73,8 @@ npm.load({}, async () => {
let atext = revisions[keyRev].meta.atext; let atext = revisions[keyRev].meta.atext;
for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) { for (let rev = keyRev + 1; rev <= keyRev + 100 && rev <= head; rev++) {
checkRevisionCount++;
try { try {
// console.log("check revision " + rev);
const cs = revisions[rev].changeset; const cs = revisions[rev].changeset;
atext = Changeset.applyToAText(cs, atext, apool); atext = Changeset.applyToAText(cs, atext, apool);
} catch (e) { } catch (e) {
@ -82,11 +82,10 @@ npm.load({}, async () => {
continue; continue;
} }
} }
console.log('finished'); console.log(`Finished: Checked ${checkRevisionCount} revisions`);
process.exit(0);
} }
} catch (e) { } catch (err) {
console.trace(e); console.trace(err);
process.exit(1); throw err;
} }
}); });

View file

@ -1,111 +1,107 @@
'use strict';
/* /*
* This is a debug tool. It checks all revisions for data corruption * This is a debug tool. It checks all revisions for data corruption
*/ */
if (process.argv.length != 3) { // As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
console.error('Use: node bin/checkPadDeltas.js $PADID'); // unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.exit(1); process.on('unhandledRejection', (err) => { throw err; });
}
if (process.argv.length !== 3) throw new Error('Use: node bin/checkPadDeltas.js $PADID');
// get the padID // get the padID
const padId = process.argv[2]; const padId = process.argv[2];
// load and initialize NPM; // load and initialize NPM;
const expect = require('expect.js'); const expect = require('../tests/frontend/lib/expect');
const diff = require('diff'); const diff = require('ep_etherpad-lite/node_modules/diff');
var async = require('async'); const npm = require('ep_etherpad-lite/node_modules/npm');
const npm = require('../src/node_modules/npm');
var async = require('ep_etherpad-lite/node_modules/async');
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
npm.load({}, async () => { npm.load({}, async () => {
try { // initialize database
// initialize database require('ep_etherpad-lite/node/utils/Settings');
const settings = require('../src/node/utils/Settings'); const db = require('ep_etherpad-lite/node/db/DB');
const db = require('../src/node/db/DB'); await db.init();
await db.init();
// load modules // load modules
const Changeset = require('ep_etherpad-lite/static/js/Changeset'); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const padManager = require('../src/node/db/PadManager'); const padManager = require('ep_etherpad-lite/node/db/PadManager');
const exists = await padManager.doesPadExists(padId); const exists = await padManager.doesPadExists(padId);
if (!exists) { if (!exists) throw new Error('Pad does not exist');
console.error('Pad does not exist');
process.exit(1); // get the pad
const pad = await padManager.getPad(padId);
// create an array with key revisions
// key revisions always save the full pad atext
const head = pad.getHeadRevisionNumber();
const keyRevisions = [];
for (let i = 0; i < head; i += 100) {
keyRevisions.push(i);
}
// create an array with all revisions
const revisions = [];
for (let i = 0; i <= head; i++) {
revisions.push(i);
}
let atext = Changeset.makeAText('\n');
// run trough all revisions
for (const revNum of revisions) {
// console.log('Fetching', revNum)
const revision = await db.get(`pad:${padId}:revs:${revNum}`);
// check if there is a atext in the keyRevisions
if (~keyRevisions.indexOf(revNum) &&
(revision === undefined ||
revision.meta === undefined ||
revision.meta.atext === undefined)) {
console.error(`No atext in key revision ${revNum}`);
continue;
} }
// get the pad // try glue everything together
const pad = await padManager.getPad(padId); try {
// console.log("check revision ", revNum);
// create an array with key revisions const cs = revision.changeset;
// key revisions always save the full pad atext atext = Changeset.applyToAText(cs, atext, pad.pool);
const head = pad.getHeadRevisionNumber(); } catch (e) {
const keyRevisions = []; console.error(`Bad changeset at revision ${revNum} - ${e.message}`);
for (var i = 0; i < head; i += 100) { continue;
keyRevisions.push(i);
} }
// create an array with all revisions // check things are working properly
const revisions = []; if (~keyRevisions.indexOf(revNum)) {
for (var i = 0; i <= head; i++) { try {
revisions.push(i); expect(revision.meta.atext.text).to.eql(atext.text);
} expect(revision.meta.atext.attribs).to.eql(atext.attribs);
} catch (e) {
let atext = Changeset.makeAText('\n'); console.error(`Atext in key revision ${revNum} doesn't match computed one.`);
console.log(diff.diffChars(atext.text, revision.meta.atext.text).map((op) => {
// run trough all revisions if (!op.added && !op.removed) op.value = op.value.length;
async.forEachSeries(revisions, (revNum, callback) => { return op;
// console.log('Fetching', revNum) }));
db.db.get(`pad:${padId}:revs:${revNum}`, (err, revision) => { // console.error(e)
if (err) return callback(err); // console.log('KeyRev. :', revision.meta.atext)
// console.log('Computed:', atext)
// check if there is a atext in the keyRevisions continue;
if (~keyRevisions.indexOf(revNum) && (revision === undefined || revision.meta === undefined || revision.meta.atext === undefined)) {
console.error(`No atext in key revision ${revNum}`);
callback();
return;
}
try {
// console.log("check revision ", revNum);
const cs = revision.changeset;
atext = Changeset.applyToAText(cs, atext, pad.pool);
} catch (e) {
console.error(`Bad changeset at revision ${revNum} - ${e.message}`);
callback();
return;
}
if (~keyRevisions.indexOf(revNum)) {
try {
expect(revision.meta.atext.text).to.eql(atext.text);
expect(revision.meta.atext.attribs).to.eql(atext.attribs);
} catch (e) {
console.error(`Atext in key revision ${revNum} doesn't match computed one.`);
console.log(diff.diffChars(atext.text, revision.meta.atext.text).map((op) => { if (!op.added && !op.removed) op.value = op.value.length; return op; }));
// console.error(e)
// console.log('KeyRev. :', revision.meta.atext)
// console.log('Computed:', atext)
callback();
return;
}
}
setImmediate(callback);
});
}, (er) => {
if (pad.atext.text == atext.text) { console.log('ok'); } else {
console.error('Pad AText doesn\'t match computed one! (Computed ', atext.text.length, ', db', pad.atext.text.length, ')');
console.log(diff.diffChars(atext.text, pad.atext.text).map((op) => { if (!op.added && !op.removed) op.value = op.value.length; return op; }));
} }
callback(er); }
}); }
process.exit(0); // check final text is right...
} catch (e) { if (pad.atext.text === atext.text) {
console.trace(e); console.log('ok');
process.exit(1); } else {
console.error('Pad AText doesn\'t match computed one! (Computed ',
atext.text.length, ', db', pad.atext.text.length, ')');
console.log(diff.diffChars(atext.text, pad.atext.text).map((op) => {
if (!op.added && !op.removed) {
op.value = op.value.length;
return op;
}
}));
} }
}); });

View file

@ -1,15 +1,19 @@
'use strict';
/* /*
* A tool for generating a test user session which can be used for debugging configs * A tool for generating a test user session which can be used for debugging configs
* that require sessions. * that require sessions.
*/ */
const m = (f) => `${__dirname}/../${f}`;
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const querystring = require('querystring'); const querystring = require('querystring');
const request = require(m('src/node_modules/request')); const settings = require('ep_etherpad-lite/node/utils/Settings');
const settings = require(m('src/node/utils/Settings')); const supertest = require('ep_etherpad-lite/node_modules/supertest');
const supertest = require(m('src/node_modules/supertest'));
(async () => { (async () => {
const api = supertest(`http://${settings.ip}:${settings.port}`); const api = supertest(`http://${settings.ip}:${settings.port}`);

View file

@ -1,51 +1,47 @@
'use strict';
/* /*
* A tool for deleting ALL GROUP sessions Etherpad user sessions from the CLI, * A tool for deleting ALL GROUP sessions Etherpad user sessions from the CLI,
* because sometimes a brick is required to fix a face. * because sometimes a brick is required to fix a face.
*/ */
const request = require('../src/node_modules/request'); // As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
const settings = require(`${__dirname}/../tests/container/loadSettings`).loadSettings(); // unhandled rejection into an uncaught exception, which does cause Node.js to exit.
const supertest = require(`${__dirname}/../src/node_modules/supertest`); process.on('unhandledRejection', (err) => { throw err; });
const api = supertest(`http://${settings.ip}:${settings.port}`);
const supertest = require('ep_etherpad-lite/node_modules/supertest');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
// Set a delete counter which will increment on each delete attempt
// TODO: Check delete is successful before incrementing
let deleteCount = 0;
// get the API Key // get the API Key
const filePath = path.join(__dirname, '../APIKEY.txt'); const filePath = path.join(__dirname, '../APIKEY.txt');
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'}); console.log('Deleting all group sessions, please be patient.');
// Set apiVersion to base value, we change this later. (async () => {
let apiVersion = 1; const settings = require('../tests/container/loadSettings').loadSettings();
let guids; const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
const api = supertest(`http://${settings.ip}:${settings.port}`);
// Update the apiVersion const apiVersionResponse = await api.get('/api/');
api.get('/api/') const apiVersion = apiVersionResponse.body.currentVersion; // 1.12.5
.expect((res) => {
apiVersion = res.body.currentVersion; const groupsResponse = await api.get(`/api/${apiVersion}/listAllGroups?apikey=${apikey}`);
if (!res.body.currentVersion) throw new Error('No version set in API'); const groups = groupsResponse.body.data.groupIDs; // ['whateverGroupID']
return;
}) for (const groupID of groups) {
.then(() => { const sessionURI = `/api/${apiVersion}/listSessionsOfGroup?apikey=${apikey}&groupID=${groupID}`;
const guri = `/api/${apiVersion}/listAllGroups?apikey=${apikey}`; const sessionsResponse = await api.get(sessionURI);
api.get(guri) const sessions = sessionsResponse.body.data;
.then((res) => {
guids = res.body.data.groupIDs; for (const sessionID of Object.keys(sessions)) {
guids.forEach((groupID) => { const deleteURI = `/api/${apiVersion}/deleteSession?apikey=${apikey}&sessionID=${sessionID}`;
const luri = `/api/${apiVersion}/listSessionsOfGroup?apikey=${apikey}&groupID=${groupID}`; await api.post(deleteURI); // delete
api.get(luri) deleteCount++;
.then((res) => { }
if (res.body.data) { }
Object.keys(res.body.data).forEach((sessionID) => { console.log(`Deleted ${deleteCount} sessions`);
if (sessionID) { })();
console.log('Deleting', sessionID);
const duri = `/api/${apiVersion}/deleteSession?apikey=${apikey}&sessionID=${sessionID}`;
api.post(duri); // deletes
}
});
} else {
// no session in this group.
}
});
});
});
});

View file

@ -1,18 +1,21 @@
'use strict';
/* /*
* A tool for deleting pads from the CLI, because sometimes a brick is required * A tool for deleting pads from the CLI, because sometimes a brick is required
* to fix a window. * to fix a window.
*/ */
const request = require('../src/node_modules/request'); // As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
const settings = require(`${__dirname}/../tests/container/loadSettings`).loadSettings(); // unhandled rejection into an uncaught exception, which does cause Node.js to exit.
const supertest = require(`${__dirname}/../src/node_modules/supertest`); process.on('unhandledRejection', (err) => { throw err; });
const settings = require('../tests/container/loadSettings').loadSettings();
const supertest = require('ep_etherpad-lite/node_modules/supertest');
const api = supertest(`http://${settings.ip}:${settings.port}`); const api = supertest(`http://${settings.ip}:${settings.port}`);
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
if (process.argv.length != 3) {
console.error('Use: node deletePad.js $PADID'); if (process.argv.length !== 3) throw new Error('Use: node deletePad.js $PADID');
process.exit(1);
}
// get the padID // get the padID
const padId = process.argv[2]; const padId = process.argv[2];
@ -21,28 +24,14 @@ const padId = process.argv[2];
const filePath = path.join(__dirname, '../APIKEY.txt'); const filePath = path.join(__dirname, '../APIKEY.txt');
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'}); const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
// Set apiVersion to base value, we change this later. (async () => {
let apiVersion = 1; let apiVersion = await api.get('/api/');
apiVersion = apiVersion.body.currentVersion;
if (!apiVersion) throw new Error('No version set in API');
// Update the apiVersion // Now we know the latest API version, let's delete pad
api.get('/api/') const uri = `/api/${apiVersion}/deletePad?apikey=${apikey}&padID=${padId}`;
.expect((res) => { const deleteAttempt = await api.post(uri);
apiVersion = res.body.currentVersion; if (deleteAttempt.body.code === 1) throw new Error(`Error deleting pad ${deleteAttempt.body}`);
if (!res.body.currentVersion) throw new Error('No version set in API'); console.log('Deleted pad', deleteAttempt.body);
return; })();
})
.end((err, res) => {
// Now we know the latest API version, let's delete pad
const uri = `/api/${apiVersion}/deletePad?apikey=${apikey}&padID=${padId}`;
api.post(uri)
.expect((res) => {
if (res.body.code === 1) {
console.error('Error deleting pad', res.body);
} else {
console.log('Deleted pad', res.body);
}
return;
})
.end(() => {});
});
// end

View file

@ -1,34 +1,34 @@
'use strict';
/* /*
* This is a debug tool. It helps to extract all datas of a pad and move it from * This is a debug tool. It helps to extract all datas of a pad and move it from
* a productive environment and to a develop environment to reproduce bugs * a productive environment and to a develop environment to reproduce bugs
* there. It outputs a dirtydb file * there. It outputs a dirtydb file
*/ */
if (process.argv.length != 3) { // As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
console.error('Use: node extractPadData.js $PADID'); // unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.exit(1); process.on('unhandledRejection', (err) => { throw err; });
}
if (process.argv.length !== 3) throw new Error('Use: node extractPadData.js $PADID');
// get the padID // get the padID
const padId = process.argv[2]; const padId = process.argv[2];
const npm = require('../src/node_modules/npm'); const npm = require('ep_etherpad-lite/node_modules/npm');
npm.load({}, async (er) => { npm.load({}, async (err) => {
if (er) { if (err) throw err;
console.error(`Could not load NPM: ${er}`);
process.exit(1);
}
try { try {
// initialize database // initialize database
const settings = require('../src/node/utils/Settings'); require('ep_etherpad-lite/node/utils/Settings');
const db = require('../src/node/db/DB'); const db = require('ep_etherpad-lite/node/db/DB');
await db.init(); await db.init();
// load extra modules // load extra modules
const dirtyDB = require('../src/node_modules/dirty'); const dirtyDB = require('ep_etherpad-lite/node_modules/dirty');
const padManager = require('../src/node/db/PadManager'); const padManager = require('ep_etherpad-lite/node/db/PadManager');
const util = require('util'); const util = require('util');
// initialize output database // initialize output database
@ -67,9 +67,8 @@ npm.load({}, async (er) => {
} }
console.log('finished'); console.log('finished');
process.exit(0); } catch (err) {
} catch (er) { console.error(err);
console.error(er); throw err;
process.exit(1);
} }
}); });

View file

@ -1,77 +1,18 @@
'use strict';
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
const startTime = Date.now(); const startTime = Date.now();
require('ep_etherpad-lite/node_modules/npm').load({}, (er, npm) => { const log = (str) => {
const fs = require('fs');
const ueberDB = require('ep_etherpad-lite/node_modules/ueberdb2');
const settings = require('ep_etherpad-lite/node/utils/Settings');
const log4js = require('ep_etherpad-lite/node_modules/log4js');
const dbWrapperSettings = {
cache: 0,
writeInterval: 100,
json: false, // data is already json encoded
};
const db = new ueberDB.database(settings.dbType, settings.dbSettings, dbWrapperSettings, log4js.getLogger('ueberDB'));
const sqlFile = process.argv[2];
// stop if the settings file is not set
if (!sqlFile) {
console.error('Use: node importSqlFile.js $SQLFILE');
process.exit(1);
}
log('initializing db');
db.init((err) => {
// there was an error while initializing the database, output it and stop
if (err) {
console.error('ERROR: Problem while initializing the database');
console.error(err.stack ? err.stack : err);
process.exit(1);
} else {
log('done');
log('open output file...');
const lines = fs.readFileSync(sqlFile, 'utf8').split('\n');
const count = lines.length;
let keyNo = 0;
process.stdout.write(`Start importing ${count} keys...\n`);
lines.forEach((l) => {
if (l.substr(0, 27) == 'REPLACE INTO store VALUES (') {
const pos = l.indexOf("', '");
const key = l.substr(28, pos - 28);
let value = l.substr(pos + 3);
value = value.substr(0, value.length - 2);
console.log(`key: ${key} val: ${value}`);
console.log(`unval: ${unescape(value)}`);
db.set(key, unescape(value), null);
keyNo++;
if (keyNo % 1000 == 0) {
process.stdout.write(` ${keyNo}/${count}\n`);
}
}
});
process.stdout.write('\n');
process.stdout.write('done. waiting for db to finish transaction. depended on dbms this may take some time...\n');
db.close(() => {
log(`finished, imported ${keyNo} keys.`);
process.exit(0);
});
}
});
});
function log(str) {
console.log(`${(Date.now() - startTime) / 1000}\t${str}`); console.log(`${(Date.now() - startTime) / 1000}\t${str}`);
} };
unescape = function (val) { const unescape = (val) => {
// value is a string // value is a string
if (val.substr(0, 1) == "'") { if (val.substr(0, 1) === "'") {
val = val.substr(0, val.length - 1).substr(1); val = val.substr(0, val.length - 1).substr(1);
return val.replace(/\\[0nrbtZ\\'"]/g, (s) => { return val.replace(/\\[0nrbtZ\\'"]/g, (s) => {
@ -88,16 +29,81 @@ unescape = function (val) {
} }
// value is a boolean or NULL // value is a boolean or NULL
if (val == 'NULL') { if (val === 'NULL') {
return null; return null;
} }
if (val == 'true') { if (val === 'true') {
return true; return true;
} }
if (val == 'false') { if (val === 'false') {
return false; return false;
} }
// value is a number // value is a number
return val; return val;
}; };
require('ep_etherpad-lite/node_modules/npm').load({}, (er, npm) => {
const fs = require('fs');
const ueberDB = require('ep_etherpad-lite/node_modules/ueberdb2');
const settings = require('ep_etherpad-lite/node/utils/Settings');
const log4js = require('ep_etherpad-lite/node_modules/log4js');
const dbWrapperSettings = {
cache: 0,
writeInterval: 100,
json: false, // data is already json encoded
};
const db = new ueberDB.database( // eslint-disable-line new-cap
settings.dbType,
settings.dbSettings,
dbWrapperSettings,
log4js.getLogger('ueberDB'));
const sqlFile = process.argv[2];
// stop if the settings file is not set
if (!sqlFile) throw new Error('Use: node importSqlFile.js $SQLFILE');
log('initializing db');
db.init((err) => {
// there was an error while initializing the database, output it and stop
if (err) {
throw err;
} else {
log('done');
log('open output file...');
const lines = fs.readFileSync(sqlFile, 'utf8').split('\n');
const count = lines.length;
let keyNo = 0;
process.stdout.write(`Start importing ${count} keys...\n`);
lines.forEach((l) => {
if (l.substr(0, 27) === 'REPLACE INTO store VALUES (') {
const pos = l.indexOf("', '");
const key = l.substr(28, pos - 28);
let value = l.substr(pos + 3);
value = value.substr(0, value.length - 2);
console.log(`key: ${key} val: ${value}`);
console.log(`unval: ${unescape(value)}`);
db.set(key, unescape(value), null);
keyNo++;
if (keyNo % 1000 === 0) {
process.stdout.write(` ${keyNo}/${count}\n`);
}
}
});
process.stdout.write('\n');
process.stdout.write('done. waiting for db to finish transaction. ' +
'depended on dbms this may take some time..\n');
db.close(() => {
log(`finished, imported ${keyNo} keys.`);
});
}
});
});

View file

@ -1,4 +1,12 @@
require('ep_etherpad-lite/node_modules/npm').load({}, (er, npm) => { 'use strict';
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
const util = require('util');
require('ep_etherpad-lite/node_modules/npm').load({}, async (er, npm) => {
process.chdir(`${npm.root}/..`); process.chdir(`${npm.root}/..`);
// This script requires that you have modified your settings.json file // This script requires that you have modified your settings.json file
@ -10,39 +18,42 @@ require('ep_etherpad-lite/node_modules/npm').load({}, (er, npm) => {
const settings = require('ep_etherpad-lite/node/utils/Settings'); const settings = require('ep_etherpad-lite/node/utils/Settings');
let dirty = require('../src/node_modules/dirty'); const dirtyDb = require('ep_etherpad-lite/node_modules/dirty');
const ueberDB = require('../src/node_modules/ueberdb2'); const ueberDB = require('ep_etherpad-lite/node_modules/ueberdb2');
const log4js = require('../src/node_modules/log4js'); const log4js = require('ep_etherpad-lite/node_modules/log4js');
const dbWrapperSettings = { const dbWrapperSettings = {
cache: '0', // The cache slows things down when you're mostly writing. cache: '0', // The cache slows things down when you're mostly writing.
writeInterval: 0, // Write directly to the database, don't buffer writeInterval: 0, // Write directly to the database, don't buffer
}; };
const db = new ueberDB.database(settings.dbType, settings.dbSettings, dbWrapperSettings, log4js.getLogger('ueberDB')); const db = new ueberDB.database( // eslint-disable-line new-cap
let i = 0; settings.dbType,
let length = 0; settings.dbSettings,
dbWrapperSettings,
log4js.getLogger('ueberDB'));
await db.init();
db.init(() => { console.log('Waiting for dirtyDB to parse its file.');
console.log('Waiting for dirtyDB to parse its file.'); const dirty = dirtyDb('var/dirty.db');
dirty = dirty('var/dirty.db').on('load', () => { const length = await new Promise((resolve) => { dirty.once('load', resolve); });
dirty.forEach(() => {
length++;
});
console.log(`Found ${length} records, processing now.`);
dirty.forEach(async (key, value) => { console.log(`Found ${length} records, processing now.`);
const error = await db.set(key, value); const p = [];
console.log(`Wrote record ${i}`); let numWritten = 0;
i++; dirty.forEach((key, value) => {
let bcb, wcb;
if (i === length) { p.push(new Promise((resolve, reject) => {
console.log('finished, just clearing up for a bit...'); bcb = (err) => { if (err != null) return reject(err); };
setTimeout(() => { wcb = (err) => {
process.exit(0); if (err != null) return reject(err);
}, 5000); if (++numWritten % 100 === 0) console.log(`Wrote record ${numWritten} of ${length}`);
} resolve();
}); };
console.log('Please wait for all records to flush to database, then kill this process.'); }));
}); db.set(key, value, bcb, wcb);
console.log('done?');
}); });
await Promise.all(p);
console.log(`Wrote all ${numWritten} records`);
await util.promisify(db.close.bind(db))();
console.log('Finished.');
}); });

View file

@ -9,16 +9,17 @@
* node bin/plugins/checkPlugin.js ep_whatever autocommit * node bin/plugins/checkPlugin.js ep_whatever autocommit
*/ */
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
const fs = require('fs'); const fs = require('fs');
const childProcess = require('child_process'); const childProcess = require('child_process');
// get plugin name & path from user input // get plugin name & path from user input
const pluginName = process.argv[2]; const pluginName = process.argv[2];
if (!pluginName) { if (!pluginName) throw new Error('no plugin name specified');
console.error('no plugin name specified');
process.exit(1);
}
const pluginPath = `node_modules/${pluginName}`; const pluginPath = `node_modules/${pluginName}`;
@ -107,10 +108,7 @@ fs.readdir(pluginPath, (err, rootFiles) => {
files.push(rootFiles[i].toLowerCase()); files.push(rootFiles[i].toLowerCase());
} }
if (files.indexOf('.git') === -1) { if (files.indexOf('.git') === -1) throw new Error('No .git folder, aborting');
console.error('No .git folder, aborting');
process.exit(1);
}
prepareRepo(); prepareRepo();
try { try {
@ -302,8 +300,10 @@ fs.readdir(pluginPath, (err, rootFiles) => {
if (files.indexOf('contributing') === -1 && files.indexOf('contributing.md') === -1) { if (files.indexOf('contributing') === -1 && files.indexOf('contributing.md') === -1) {
console.warn('CONTRIBUTING.md file not found, please create'); console.warn('CONTRIBUTING.md file not found, please create');
if (autoFix) { if (autoFix) {
console.log('Autofixing missing CONTRIBUTING.md file, please edit the CONTRIBUTING.md file further to include plugin specific details.'); console.log('Autofixing missing CONTRIBUTING.md file, please edit the CONTRIBUTING.md ' +
let contributing = fs.readFileSync('bin/plugins/lib/CONTRIBUTING.md', {encoding: 'utf8', flag: 'r'}); 'file further to include plugin specific details.');
let contributing =
fs.readFileSync('bin/plugins/lib/CONTRIBUTING.md', {encoding: 'utf8', flag: 'r'});
contributing = contributing.replace(/\[plugin_name\]/g, pluginName); contributing = contributing.replace(/\[plugin_name\]/g, pluginName);
fs.writeFileSync(`${pluginPath}/CONTRIBUTING.md`, contributing); fs.writeFileSync(`${pluginPath}/CONTRIBUTING.md`, contributing);
} }
@ -311,7 +311,8 @@ fs.readdir(pluginPath, (err, rootFiles) => {
if (files.indexOf('readme') !== -1 && files.indexOf('readme.md') !== -1) { if (files.indexOf('readme') !== -1 && files.indexOf('readme.md') !== -1) {
const readme = fs.readFileSync(`${pluginPath}/${readMeFileName}`, {encoding: 'utf8', flag: 'r'}); const readme =
fs.readFileSync(`${pluginPath}/${readMeFileName}`, {encoding: 'utf8', flag: 'r'});
if (readme.toLowerCase().indexOf('license') === -1) { if (readme.toLowerCase().indexOf('license') === -1) {
console.warn('No license section in README'); console.warn('No license section in README');
if (autoFix) { if (autoFix) {
@ -335,7 +336,9 @@ fs.readdir(pluginPath, (err, rootFiles) => {
travisConfig = travisConfig.replace(/\[plugin_name\]/g, pluginName); travisConfig = travisConfig.replace(/\[plugin_name\]/g, pluginName);
if (files.indexOf('.travis.yml') === -1) { if (files.indexOf('.travis.yml') === -1) {
console.warn('.travis.yml file not found, please create. .travis.yml is used for automatically CI testing Etherpad. It is useful to know if your plugin breaks another feature for example.'); console.warn('.travis.yml file not found, please create. ' +
'.travis.yml is used for automatically CI testing Etherpad. ' +
'It is useful to know if your plugin breaks another feature for example.');
// TODO: Make it check version of the .travis file to see if it needs an update. // TODO: Make it check version of the .travis file to see if it needs an update.
if (autoFix) { if (autoFix) {
console.log('Autofixing missing .travis.yml file'); console.log('Autofixing missing .travis.yml file');
@ -345,9 +348,11 @@ fs.readdir(pluginPath, (err, rootFiles) => {
} }
if (autoFix) { if (autoFix) {
// checks the file versioning of .travis and updates it to the latest. // checks the file versioning of .travis and updates it to the latest.
const existingConfig = fs.readFileSync(`${pluginPath}/.travis.yml`, {encoding: 'utf8', flag: 'r'}); const existingConfig =
fs.readFileSync(`${pluginPath}/.travis.yml`, {encoding: 'utf8', flag: 'r'});
const existingConfigLocation = existingConfig.indexOf('##ETHERPAD_TRAVIS_V='); const existingConfigLocation = existingConfig.indexOf('##ETHERPAD_TRAVIS_V=');
const existingValue = parseInt(existingConfig.substr(existingConfigLocation + 20, existingConfig.length)); const existingValue =
parseInt(existingConfig.substr(existingConfigLocation + 20, existingConfig.length));
const newConfigLocation = travisConfig.indexOf('##ETHERPAD_TRAVIS_V='); const newConfigLocation = travisConfig.indexOf('##ETHERPAD_TRAVIS_V=');
const newValue = parseInt(travisConfig.substr(newConfigLocation + 20, travisConfig.length)); const newValue = parseInt(travisConfig.substr(newConfigLocation + 20, travisConfig.length));
@ -362,7 +367,8 @@ fs.readdir(pluginPath, (err, rootFiles) => {
} }
if (files.indexOf('.gitignore') === -1) { if (files.indexOf('.gitignore') === -1) {
console.warn(".gitignore file not found, please create. .gitignore files are useful to ensure files aren't incorrectly commited to a repository."); console.warn('.gitignore file not found, please create. .gitignore files are useful to ' +
"ensure files aren't incorrectly commited to a repository.");
if (autoFix) { if (autoFix) {
console.log('Autofixing missing .gitignore file'); console.log('Autofixing missing .gitignore file');
const gitignore = fs.readFileSync('bin/plugins/lib/gitignore', {encoding: 'utf8', flag: 'r'}); const gitignore = fs.readFileSync('bin/plugins/lib/gitignore', {encoding: 'utf8', flag: 'r'});
@ -382,12 +388,15 @@ fs.readdir(pluginPath, (err, rootFiles) => {
// if we include templates but don't have translations... // if we include templates but don't have translations...
if (files.indexOf('templates') !== -1 && files.indexOf('locales') === -1) { if (files.indexOf('templates') !== -1 && files.indexOf('locales') === -1) {
console.warn('Translations not found, please create. Translation files help with Etherpad accessibility.'); console.warn('Translations not found, please create. ' +
'Translation files help with Etherpad accessibility.');
} }
if (files.indexOf('.ep_initialized') !== -1) { if (files.indexOf('.ep_initialized') !== -1) {
console.warn('.ep_initialized found, please remove. .ep_initialized should never be commited to git and should only exist once the plugin has been executed one time.'); console.warn(
'.ep_initialized found, please remove. .ep_initialized should never be commited to git ' +
'and should only exist once the plugin has been executed one time.');
if (autoFix) { if (autoFix) {
console.log('Autofixing incorrectly existing .ep_initialized file'); console.log('Autofixing incorrectly existing .ep_initialized file');
fs.unlinkSync(`${pluginPath}/.ep_initialized`); fs.unlinkSync(`${pluginPath}/.ep_initialized`);
@ -395,7 +404,8 @@ fs.readdir(pluginPath, (err, rootFiles) => {
} }
if (files.indexOf('npm-debug.log') !== -1) { if (files.indexOf('npm-debug.log') !== -1) {
console.warn('npm-debug.log found, please remove. npm-debug.log should never be commited to your repository.'); console.warn('npm-debug.log found, please remove. npm-debug.log should never be commited to ' +
'your repository.');
if (autoFix) { if (autoFix) {
console.log('Autofixing incorrectly existing npm-debug.log file'); console.log('Autofixing incorrectly existing npm-debug.log file');
fs.unlinkSync(`${pluginPath}/npm-debug.log`); fs.unlinkSync(`${pluginPath}/npm-debug.log`);

View file

@ -1,43 +1,39 @@
'use strict';
/* /*
This is a repair tool. It rebuilds an old pad at a new pad location up to a This is a repair tool. It rebuilds an old pad at a new pad location up to a
known "good" revision. known "good" revision.
*/ */
if (process.argv.length != 4 && process.argv.length != 5) { // As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
console.error('Use: node bin/repairPad.js $PADID $REV [$NEWPADID]'); // unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.exit(1); process.on('unhandledRejection', (err) => { throw err; });
if (process.argv.length !== 4 && process.argv.length !== 5) {
throw new Error('Use: node bin/repairPad.js $PADID $REV [$NEWPADID]');
} }
const npm = require('../src/node_modules/npm'); const async = require('ep_etherpad-lite/node_modules/async');
const async = require('../src/node_modules/async'); const npm = require('ep_etherpad-lite/node_modules/npm');
const ueberDB = require('../src/node_modules/ueberdb2'); const util = require('util');
const padId = process.argv[2]; const padId = process.argv[2];
const newRevHead = process.argv[3]; const newRevHead = process.argv[3];
const newPadId = process.argv[4] || `${padId}-rebuilt`; const newPadId = process.argv[4] || `${padId}-rebuilt`;
let db, oldPad, newPad, settings; let db, oldPad, newPad;
let AuthorManager, ChangeSet, Pad, PadManager; let Pad, PadManager;
async.series([ async.series([
function (callback) { (callback) => npm.load({}, callback),
npm.load({}, (err) => { (callback) => {
if (err) {
console.error(`Could not load NPM: ${err}`);
process.exit(1);
} else {
callback();
}
});
},
function (callback) {
// Get a handle into the database // Get a handle into the database
db = require('../src/node/db/DB'); db = require('ep_etherpad-lite/node/db/DB');
db.init(callback); db.init(callback);
}, },
function (callback) { (callback) => {
PadManager = require('../src/node/db/PadManager'); Pad = require('ep_etherpad-lite/node/db/Pad').Pad;
Pad = require('../src/node/db/Pad').Pad; PadManager = require('ep_etherpad-lite/node/db/PadManager');
// Get references to the original pad and to a newly created pad // Get references to the original pad and to a newly created pad
// HACK: This is a standalone script, so we want to write everything // HACK: This is a standalone script, so we want to write everything
// out to the database immediately. The only problem with this is // out to the database immediately. The only problem with this is
@ -46,14 +42,10 @@ async.series([
// Validate the newPadId if specified and that a pad with that ID does // Validate the newPadId if specified and that a pad with that ID does
// not already exist to avoid overwriting it. // not already exist to avoid overwriting it.
if (!PadManager.isValidPadId(newPadId)) { if (!PadManager.isValidPadId(newPadId)) {
console.error('Cannot create a pad with that id as it is invalid'); throw new Error('Cannot create a pad with that id as it is invalid');
process.exit(1);
} }
PadManager.doesPadExists(newPadId, (err, exists) => { PadManager.doesPadExists(newPadId, (err, exists) => {
if (exists) { if (exists) throw new Error('Cannot create a pad with that id as it already exists');
console.error('Cannot create a pad with that id as it already exists');
process.exit(1);
}
}); });
PadManager.getPad(padId, (err, pad) => { PadManager.getPad(padId, (err, pad) => {
oldPad = pad; oldPad = pad;
@ -61,10 +53,10 @@ async.series([
callback(); callback();
}); });
}, },
function (callback) { (callback) => {
// Clone all Chat revisions // Clone all Chat revisions
const chatHead = oldPad.chatHead; const chatHead = oldPad.chatHead;
for (var i = 0, curHeadNum = 0; i <= chatHead; i++) { for (let i = 0, curHeadNum = 0; i <= chatHead; i++) {
db.db.get(`pad:${padId}:chat:${i}`, (err, chat) => { db.db.get(`pad:${padId}:chat:${i}`, (err, chat) => {
db.db.set(`pad:${newPadId}:chat:${curHeadNum++}`, chat); db.db.set(`pad:${newPadId}:chat:${curHeadNum++}`, chat);
console.log(`Created: Chat Revision: pad:${newPadId}:chat:${curHeadNum}`); console.log(`Created: Chat Revision: pad:${newPadId}:chat:${curHeadNum}`);
@ -72,10 +64,10 @@ async.series([
} }
callback(); callback();
}, },
function (callback) { (callback) => {
// Rebuild Pad from revisions up to and including the new revision head // Rebuild Pad from revisions up to and including the new revision head
AuthorManager = require('../src/node/db/AuthorManager'); const AuthorManager = require('ep_etherpad-lite/node/db/AuthorManager');
Changeset = require('ep_etherpad-lite/static/js/Changeset'); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
// Author attributes are derived from changesets, but there can also be // Author attributes are derived from changesets, but there can also be
// non-author attributes with specific mappings that changesets depend on // non-author attributes with specific mappings that changesets depend on
// and, AFAICT, cannot be recreated any other way // and, AFAICT, cannot be recreated any other way
@ -83,7 +75,7 @@ async.series([
for (let curRevNum = 0; curRevNum <= newRevHead; curRevNum++) { for (let curRevNum = 0; curRevNum <= newRevHead; curRevNum++) {
db.db.get(`pad:${padId}:revs:${curRevNum}`, (err, rev) => { db.db.get(`pad:${padId}:revs:${curRevNum}`, (err, rev) => {
if (rev.meta) { if (rev.meta) {
throw 'The specified revision number could not be found.'; throw new Error('The specified revision number could not be found.');
} }
const newRevNum = ++newPad.head; const newRevNum = ++newPad.head;
const newRevId = `pad:${newPad.id}:revs:${newRevNum}`; const newRevId = `pad:${newPad.id}:revs:${newRevNum}`;
@ -91,18 +83,17 @@ async.series([
AuthorManager.addPad(rev.meta.author, newPad.id); AuthorManager.addPad(rev.meta.author, newPad.id);
newPad.atext = Changeset.applyToAText(rev.changeset, newPad.atext, newPad.pool); newPad.atext = Changeset.applyToAText(rev.changeset, newPad.atext, newPad.pool);
console.log(`Created: Revision: pad:${newPad.id}:revs:${newRevNum}`); console.log(`Created: Revision: pad:${newPad.id}:revs:${newRevNum}`);
if (newRevNum == newRevHead) { if (newRevNum === newRevHead) {
callback(); callback();
} }
}); });
} }
}, },
function (callback) { (callback) => {
// Add saved revisions up to the new revision head // Add saved revisions up to the new revision head
console.log(newPad.head); console.log(newPad.head);
const newSavedRevisions = []; const newSavedRevisions = [];
for (const i in oldPad.savedRevisions) { for (const savedRev of oldPad.savedRevisions) {
savedRev = oldPad.savedRevisions[i];
if (savedRev.revNum <= newRevHead) { if (savedRev.revNum <= newRevHead) {
newSavedRevisions.push(savedRev); newSavedRevisions.push(savedRev);
console.log(`Added: Saved Revision: ${savedRev.revNum}`); console.log(`Added: Saved Revision: ${savedRev.revNum}`);
@ -111,16 +102,14 @@ async.series([
newPad.savedRevisions = newSavedRevisions; newPad.savedRevisions = newSavedRevisions;
callback(); callback();
}, },
function (callback) { (callback) => {
// Save the source pad // Save the source pad
db.db.set(`pad:${newPadId}`, newPad, (err) => { db.db.set(`pad:${newPadId}`, newPad, (err) => {
console.log(`Created: Source Pad: pad:${newPadId}`); console.log(`Created: Source Pad: pad:${newPadId}`);
newPad.saveToDatabase().then(() => callback(), callback); util.callbackify(newPad.saveToDatabase.bind(newPad))(callback);
}); });
}, },
], (err) => { ], (err) => {
if (err) { throw err; } else { if (err) throw err;
console.info('finished'); console.info('finished');
process.exit(0);
}
}); });

View file

@ -1,8 +1,12 @@
'use strict'; 'use strict';
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
const fs = require('fs'); const fs = require('fs');
const child_process = require('child_process'); const childProcess = require('child_process');
const semver = require('../src/node_modules/semver'); const semver = require('ep_etherpad-lite/node_modules/semver');
/* /*
@ -15,7 +19,7 @@ const usage = 'node bin/release.js [patch/minor/major] -- example: "node bin/rel
const release = process.argv[2]; const release = process.argv[2];
if(!release) { if (!release) {
console.log(usage); console.log(usage);
throw new Error('No release type included'); throw new Error('No release type included');
} }
@ -26,14 +30,14 @@ packageJson = JSON.parse(packageJson);
const currentVersion = packageJson.version; const currentVersion = packageJson.version;
const newVersion = semver.inc(currentVersion, release); const newVersion = semver.inc(currentVersion, release);
if(!newVersion) { if (!newVersion) {
console.log(usage); console.log(usage);
throw new Error('Unable to generate new version from input'); throw new Error('Unable to generate new version from input');
} }
const changelogIncludesVersion = changelog.indexOf(newVersion) !== -1; const changelogIncludesVersion = changelog.indexOf(newVersion) !== -1;
if(!changelogIncludesVersion) { if (!changelogIncludesVersion) {
throw new Error('No changelog record for ', newVersion, ' - please create changelog record'); throw new Error('No changelog record for ', newVersion, ' - please create changelog record');
} }
@ -44,24 +48,27 @@ packageJson.version = newVersion;
fs.writeFileSync('src/package.json', JSON.stringify(packageJson, null, 2)); fs.writeFileSync('src/package.json', JSON.stringify(packageJson, null, 2));
// run npm version `release` where release is patch, minor or major // run npm version `release` where release is patch, minor or major
child_process.execSync('npm install --package-lock-only', {cwd: `src/`}); childProcess.execSync('npm install --package-lock-only', {cwd: 'src/'});
// run npm install --package-lock-only <-- required??? // run npm install --package-lock-only <-- required???
child_process.execSync(`git checkout -b release/${newVersion}`); childProcess.execSync(`git checkout -b release/${newVersion}`);
child_process.execSync(`git add src/package.json`); childProcess.execSync('git add src/package.json');
child_process.execSync(`git add src/package-lock.json`); childProcess.execSync('git add src/package-lock.json');
child_process.execSync(`git commit -m 'bump version'`); childProcess.execSync('git commit -m "bump version"');
child_process.execSync(`git push origin release/${newVersion}`); childProcess.execSync(`git push origin release/${newVersion}`);
child_process.execSync(`make docs`); childProcess.execSync('make docs');
child_process.execSync(`git clone git@github.com:ether/ether.github.com.git`); childProcess.execSync('git clone git@github.com:ether/ether.github.com.git');
child_process.execSync(`cp -R out/doc/ ether.github.com/doc/v${newVersion}`); childProcess.execSync(`cp -R out/doc/ ether.github.com/doc/v${newVersion}`);
console.log('Once merged into master please run the following commands'); console.log('Once merged into master please run the following commands');
console.log(`git tag -a ${newVersion} -m ${newVersion} && git push origin master`); console.log(`git tag -a ${newVersion} -m ${newVersion} && git push origin master`);
console.log(`cd ether.github.com && git add . && git commit -m '${newVersion} docs'`); console.log(`cd ether.github.com && git add . && git commit -m '${newVersion} docs'`);
console.log(`Build the windows zip`) console.log('Build the windows zip');
console.log(`Visit https://github.com/ether/etherpad-lite/releases/new and create a new release with 'master' as the target and the version is ${newVersion}. Include the windows zip as an assett`) console.log('Visit https://github.com/ether/etherpad-lite/releases/new and create a new release ' +
console.log('Once the new docs are uploaded then modify the download link on etherpad.org and then pull master onto develop'); `with 'master' as the target and the version is ${newVersion}. Include the windows ` +
'zip as an asset');
console.log(`Once the new docs are uploaded then modify the download
link on etherpad.org and then pull master onto develop`);
console.log('Finally go public with an announcement via our comms channels :)'); console.log('Finally go public with an announcement via our comms channels :)');

View file

@ -1,77 +1,59 @@
'use strict';
/* /*
* This is a repair tool. It extracts all datas of a pad, removes and inserts them again. * This is a repair tool. It extracts all datas of a pad, removes and inserts them again.
*/ */
// As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
console.warn('WARNING: This script must not be used while etherpad is running!'); console.warn('WARNING: This script must not be used while etherpad is running!');
if (process.argv.length != 3) { if (process.argv.length !== 3) throw new Error('Use: node bin/repairPad.js $PADID');
console.error('Use: node bin/repairPad.js $PADID');
process.exit(1);
}
// get the padID // get the padID
const padId = process.argv[2]; const padId = process.argv[2];
const npm = require('../src/node_modules/npm'); let valueCount = 0;
npm.load({}, async (er) => {
if (er) { const npm = require('ep_etherpad-lite/node_modules/npm');
console.error(`Could not load NPM: ${er}`); npm.load({}, async (err) => {
process.exit(1); if (err) throw err;
// intialize database
require('ep_etherpad-lite/node/utils/Settings');
const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
// get the pad
const padManager = require('ep_etherpad-lite/node/db/PadManager');
const pad = await padManager.getPad(padId);
// accumulate the required keys
const neededDBValues = [`pad:${padId}`];
// add all authors
neededDBValues.push(...pad.getAllAuthors().map((author) => `globalAuthor:${author}`));
// add all revisions
for (let rev = 0; rev <= pad.head; ++rev) {
neededDBValues.push(`pad:${padId}:revs:${rev}`);
} }
try { // add all chat values
// intialize database for (let chat = 0; chat <= pad.chatHead; ++chat) {
const settings = require('../src/node/utils/Settings'); neededDBValues.push(`pad:${padId}:chat:${chat}`);
const db = require('../src/node/db/DB');
await db.init();
// get the pad
const padManager = require('../src/node/db/PadManager');
const pad = await padManager.getPad(padId);
// accumulate the required keys
const neededDBValues = [`pad:${padId}`];
// add all authors
neededDBValues.push(...pad.getAllAuthors().map((author) => 'globalAuthor:'));
// add all revisions
for (let rev = 0; rev <= pad.head; ++rev) {
neededDBValues.push(`pad:${padId}:revs:${rev}`);
}
// add all chat values
for (let chat = 0; chat <= pad.chatHead; ++chat) {
neededDBValues.push(`pad:${padId}:chat:${chat}`);
}
//
// NB: this script doesn't actually does what's documented
// since the `value` fields in the following `.forEach`
// block are just the array index numbers
//
// the script therefore craps out now before it can do
// any damage.
//
// See gitlab issue #3545
//
console.info('aborting [gitlab #3545]');
process.exit(1);
// now fetch and reinsert every key
neededDBValues.forEach((key, value) => {
console.log(`Key: ${key}, value: ${value}`);
db.remove(key);
db.set(key, value);
});
console.info('finished');
process.exit(0);
} catch (er) {
if (er.name === 'apierror') {
console.error(er);
} else {
console.trace(er);
}
} }
// now fetch and reinsert every key
for (const key of neededDBValues) {
const value = await db.get(key);
// if it isn't a globalAuthor value which we want to ignore..
// console.log(`Key: ${key}, value: ${JSON.stringify(value)}`);
await db.remove(key);
await db.set(key, value);
valueCount++;
}
console.info(`Finished: Replaced ${valueCount} values in the database`);
}); });