Ported bin folder to typescript.

This commit is contained in:
SamTV12345 2024-03-13 20:31:29 +01:00
parent a768b322cf
commit f9e3416d78
24 changed files with 346 additions and 1123 deletions

View file

@ -10,14 +10,14 @@ process.on('unhandledRejection', (err) => { throw err; });
if (process.argv.length !== 2) throw new Error('Use: node bin/checkAllPads.js');
(async () => {
const db = require('./src/node/db/DB');
const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
const padManager = require('./src/node/db/PadManager');
await Promise.all((await padManager.listAllPads()).padIDs.map(async (padId) => {
const padManager = require('ep_etherpad-lite/node/db/PadManager');
await Promise.all((await padManager.listAllPads()).padIDs.map(async (padId: string) => {
const pad = await padManager.getPad(padId);
try {
await pad.check();
} catch (err) {
} catch (err:any) {
console.error(`Error in pad ${padId}: ${err.stack || err}`);
return;
}

View file

@ -8,11 +8,12 @@
process.on('unhandledRejection', (err) => { throw err; });
if (process.argv.length !== 3) throw new Error('Use: node bin/checkPad.js $PADID');
// @ts-ignore
const padId = process.argv[2];
(async () => {
const db = require('./src/node/db/DB');
const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
const padManager = require('./src/node/db/PadManager');
const padManager = require('ep_etherpad-lite/node/db/PadManager');
if (!await padManager.doesPadExists(padId)) throw new Error('Pad does not exist');
const pad = await padManager.getPad(padId);
await pad.check();

View file

@ -7,16 +7,20 @@
// 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.
import fs from "fs";
import path from "path";
import querystring from "querystring";
import axios from 'axios'
process.on('unhandledRejection', (err) => { throw err; });
const fs = require('fs');
const path = require('path');
const querystring = require('querystring');
const settings = require('./src/node/utils/Settings');
const supertest = require('supertest');
const settings = require('ep_etherpad-lite/node/utils/Settings');
(async () => {
const api = supertest(`http://${settings.ip}:${settings.port}`);
axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;
const api = axios;
const filePath = path.join(__dirname, '../APIKEY.txt');
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
@ -24,28 +28,29 @@ const supertest = require('supertest');
let res;
res = await api.get('/api/');
const apiVersion = res.body.currentVersion;
const apiVersion = res.data.currentVersion;
if (!apiVersion) throw new Error('No version set in API');
const uri = (cmd, args) => `/api/${apiVersion}/${cmd}?${querystring.stringify(args)}`;
console.log('apiVersion', apiVersion);
const uri = (cmd: string, args: querystring.ParsedUrlQueryInput ) => `/api/${apiVersion}/${cmd}?${querystring.stringify(args)}`;
res = await api.post(uri('createGroup', {apikey}));
if (res.body.code === 1) throw new Error(`Error creating group: ${res.body}`);
const groupID = res.body.data.groupID;
if (res.data.code === 1) throw new Error(`Error creating group: ${res.data}`);
const groupID = res.data.data.groupID;
console.log('groupID', groupID);
res = await api.post(uri('createGroupPad', {apikey, groupID}));
if (res.body.code === 1) throw new Error(`Error creating group pad: ${res.body}`);
console.log('Test Pad ID ====> ', res.body.data.padID);
if (res.data.code === 1) throw new Error(`Error creating group pad: ${res.data}`);
console.log('Test Pad ID ====> ', res.data.data.padID);
res = await api.post(uri('createAuthor', {apikey}));
if (res.body.code === 1) throw new Error(`Error creating author: ${res.body}`);
const authorID = res.body.data.authorID;
if (res.data.code === 1) throw new Error(`Error creating author: ${res.data}`);
const authorID = res.data.data.authorID;
console.log('authorID', authorID);
const validUntil = Math.floor(new Date() / 1000) + 60000;
const validUntil = Math.floor(new Date().getTime() / 1000) + 60000;
console.log('validUntil', validUntil);
res = await api.post(uri('createSession', {apikey, groupID, authorID, validUntil}));
if (res.body.code === 1) throw new Error(`Error creating session: ${res.body}`);
if (res.data.code === 1) throw new Error(`Error creating session: ${JSON.stringify(res.data)}`);
console.log('Session made: ====> create a cookie named sessionID and set the value to',
res.body.data.sessionID);
res.data.data.sessionID);
})();

View file

@ -1,5 +1,3 @@
'use strict';
/*
* A tool for deleting ALL GROUP sessions Etherpad user sessions from the CLI,
* because sometimes a brick is required to fix a face.
@ -7,12 +5,13 @@
// 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.
import path from "path";
import fs from "fs";
import process from "process";
process.on('unhandledRejection', (err) => { throw err; });
const path = require('path');
const fs = require('fs');
const supertest = require('supertest');
import axios from 'axios'
// Set a delete counter which will increment on each delete attempt
// TODO: Check delete is successful before incrementing
let deleteCount = 0;
@ -20,27 +19,33 @@ let deleteCount = 0;
// get the API Key
const filePath = path.join(__dirname, '../APIKEY.txt');
console.log('Deleting all group sessions, please be patient.');
const settings = require('ep_etherpad-lite/tests/container/loadSettings').loadSettings();
(async () => {
const settings = require('./src/tests/container/loadSettings').loadSettings();
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
const api = supertest(`http://${settings.ip}:${settings.port}`);
axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;
const apiVersionResponse = await api.get('/api/');
const apiVersion = apiVersionResponse.body.currentVersion; // 1.12.5
const apiVersionResponse = await axios.get('/api/');
const apiVersion = apiVersionResponse.data.currentVersion; // 1.12.5
console.log('apiVersion', apiVersion);
const groupsResponse = await api.get(`/api/${apiVersion}/listAllGroups?apikey=${apikey}`);
const groups = groupsResponse.body.data.groupIDs; // ['whateverGroupID']
const groupsResponse = await axios.get(`/api/${apiVersion}/listAllGroups?apikey=${apikey}`);
const groups = groupsResponse.data.data.groupIDs; // ['whateverGroupID']
for (const groupID of groups) {
const sessionURI = `/api/${apiVersion}/listSessionsOfGroup?apikey=${apikey}&groupID=${groupID}`;
const sessionsResponse = await api.get(sessionURI);
const sessions = sessionsResponse.body.data;
const sessionsResponse = await axios.get(sessionURI);
const sessions = sessionsResponse.data.data;
for (const sessionID of Object.keys(sessions)) {
if(sessions == null) continue;
for (const [sessionID, val] of Object.entries(sessions)) {
if(val == null) continue;
const deleteURI = `/api/${apiVersion}/deleteSession?apikey=${apikey}&sessionID=${sessionID}`;
await api.post(deleteURI); // delete
await axios.post(deleteURI).then(c=>{
console.log(c.data)
deleteCount++;
}); // delete
}
}
console.log(`Deleted ${deleteCount} sessions`);

View file

@ -7,14 +7,17 @@
// 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.
import path from "path";
import fs from "fs";
import process from "process";
import axios from "axios";
process.on('unhandledRejection', (err) => { throw err; });
const settings = require('./src/tests/container/loadSettings').loadSettings();
const path = require('path');
const fs = require('fs');
const supertest = require('supertest');
const settings = require('ep_etherpad-lite/tests/container/loadSettings').loadSettings();
const api = supertest(`http://${settings.ip}:${settings.port}`);
axios.defaults.baseURL = `http://${settings.ip}:${settings.port}`;
if (process.argv.length !== 3) throw new Error('Use: node deletePad.js $PADID');
@ -26,13 +29,13 @@ const filePath = path.join(__dirname, '../APIKEY.txt');
const apikey = fs.readFileSync(filePath, {encoding: 'utf-8'});
(async () => {
let apiVersion = await api.get('/api/');
apiVersion = apiVersion.body.currentVersion;
let apiVersion = await axios.get('/api/');
apiVersion = apiVersion.data.currentVersion;
if (!apiVersion) throw new Error('No version set in API');
// Now we know the latest API version, let's delete pad
const uri = `/api/${apiVersion}/deletePad?apikey=${apikey}&padID=${padId}`;
const deleteAttempt = await api.post(uri);
if (deleteAttempt.body.code === 1) throw new Error(`Error deleting pad ${deleteAttempt.body}`);
console.log('Deleted pad', deleteAttempt.body);
const deleteAttempt = await axios.post(uri);
if (deleteAttempt.data.code === 1) throw new Error(`Error deleting pad ${deleteAttempt.data}`);
console.log('Deleted pad', deleteAttempt.data);
})();

View file

@ -1,18 +0,0 @@
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

View file

@ -1,76 +0,0 @@
Here's how the node docs work.
Each type of heading has a description block.
## module
Stability: 3 - Stable
description and examples.
### module.property
* Type
description of the property.
### module.someFunction(x, y, [z=100])
* `x` {String} the description of the string
* `y` {Boolean} Should I stay or should I go?
* `z` {Number} How many zebras to bring.
A description of the function.
### Event: 'blerg'
* Argument: SomeClass object.
Modules don't usually raise events on themselves. `cluster` is the
only exception.
## Class: SomeClass
description of the class.
### Class Method: SomeClass.classMethod(anArg)
* `anArg` {Object} Just an argument
* `field` {String} anArg can have this field.
* `field2` {Boolean} Another field. Default: `false`.
* Return: {Boolean} `true` if it worked.
Description of the method for humans.
### someClass.nextSibling()
* Return: {SomeClass object | null} The next someClass in line.
### someClass.someProperty
* String
The indication of what someProperty is.
### Event: 'grelb'
* `isBlerg` {Boolean}
This event is emitted on instances of SomeClass, not on the module itself.
* Modules have (description, Properties, Functions, Classes, Examples)
* Properties have (type, description)
* Functions have (list of arguments, description)
* Classes have (description, Properties, Methods, Events)
* Events have (list of arguments, description)
* Methods have (list of arguments, description)
* Properties have (type, description)
# CLI usage
Run the following from the etherpad-lite root directory:
```sh
$ node src/bin/doc/generate doc/index.md --format=html --template=doc/template.html > out.html
```

View file

@ -1,122 +0,0 @@
#!/usr/bin/env node
'use strict';
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
const fs = require('fs');
const path = require('path');
// parse the args.
// Don't use nopt or whatever for this. It's simple enough.
const args = process.argv.slice(2);
let format = 'json';
let template = null;
let inputFile = null;
args.forEach((arg) => {
if (!arg.match(/^--/)) {
inputFile = arg;
} else if (arg.match(/^--format=/)) {
format = arg.replace(/^--format=/, '');
} else if (arg.match(/^--template=/)) {
template = arg.replace(/^--template=/, '');
}
});
if (!inputFile) {
throw new Error('No input file specified');
}
console.error('Input file = %s', inputFile);
fs.readFile(inputFile, 'utf8', (er, input) => {
if (er) throw er;
// process the input for @include lines
processIncludes(inputFile, input, next);
});
const includeExpr = /^@include\s+([A-Za-z0-9-_/]+)(?:\.)?([a-zA-Z]*)$/gmi;
const includeData = {};
const processIncludes = (inputFile, input, cb) => {
const includes = input.match(includeExpr);
if (includes == null) return cb(null, input);
let errState = null;
console.error(includes);
let incCount = includes.length;
if (incCount === 0) cb(null, input);
includes.forEach((include) => {
let fname = include.replace(/^@include\s+/, '');
if (!fname.match(/\.md$/)) fname += '.md';
if (Object.prototype.hasOwnProperty.call(includeData, fname)) {
input = input.split(include).join(includeData[fname]);
incCount--;
if (incCount === 0) {
return cb(null, input);
}
}
const fullFname = path.resolve(path.dirname(inputFile), fname);
fs.readFile(fullFname, 'utf8', (er, inc) => {
if (errState) return;
if (er) return cb(errState = er);
processIncludes(fullFname, inc, (er, inc) => {
if (errState) return;
if (er) return cb(errState = er);
incCount--;
includeData[fname] = inc;
input = input.split(include).join(includeData[fname]);
if (incCount === 0) {
return cb(null, input);
}
});
});
});
};
const next = (er, input) => {
if (er) throw er;
switch (format) {
case 'json':
require('./json.js')(input, inputFile, (er, obj) => {
console.log(JSON.stringify(obj, null, 2));
if (er) throw er;
});
break;
case 'html':
require('./html.js')(input, inputFile, template, (er, html) => {
if (er) throw er;
console.log(html);
});
break;
default:
throw new Error(`Invalid format: ${format}`);
}
};

View file

@ -1,165 +0,0 @@
'use strict';
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
const fs = require('fs');
const marked = require('marked');
const path = require('path');
const toHTML = (input, filename, template, cb) => {
const lexed = marked.lexer(input);
fs.readFile(template, 'utf8', (er, template) => {
if (er) return cb(er);
render(lexed, filename, template, cb);
});
};
module.exports = toHTML;
const render = (lexed, filename, template, cb) => {
// get the section
const section = getSection(lexed);
filename = path.basename(filename, '.md');
lexed = parseLists(lexed);
// generate the table of contents.
// this mutates the lexed contents in-place.
buildToc(lexed, filename, (er, toc) => {
if (er) return cb(er);
template = template.replace(/__FILENAME__/g, filename);
template = template.replace(/__SECTION__/g, section);
template = template.replace(/__TOC__/g, toc);
// content has to be the last thing we do with
// the lexed tokens, because it's destructive.
const content = marked.parser(lexed);
template = template.replace(/__CONTENT__/g, content);
cb(null, template);
});
};
// just update the list item text in-place.
// lists that come right after a heading are what we're after.
const parseLists = (input) => {
let state = null;
let depth = 0;
const output = [];
output.links = input.links;
input.forEach((tok) => {
if (state == null) {
if (tok.type === 'heading') {
state = 'AFTERHEADING';
}
output.push(tok);
return;
}
if (state === 'AFTERHEADING') {
if (tok.type === 'list_start') {
state = 'LIST';
if (depth === 0) {
output.push({type: 'html', text: '<div class="signature">'});
}
depth++;
output.push(tok);
return;
}
state = null;
output.push(tok);
return;
}
if (state === 'LIST') {
if (tok.type === 'list_start') {
depth++;
output.push(tok);
return;
}
if (tok.type === 'list_end') {
depth--;
if (depth === 0) {
state = null;
output.push({type: 'html', text: '</div>'});
}
output.push(tok);
return;
}
if (tok.text) {
tok.text = parseListItem(tok.text);
}
}
output.push(tok);
});
return output;
};
const parseListItem = (text) => {
text = text.replace(/\{([^}]+)\}/, '<span class="type">$1</span>');
// XXX maybe put more stuff here?
return text;
};
// section is just the first heading
const getSection = (lexed) => {
for (let i = 0, l = lexed.length; i < l; i++) {
const tok = lexed[i];
if (tok.type === 'heading') return tok.text;
}
return '';
};
const buildToc = (lexed, filename, cb) => {
let toc = [];
let depth = 0;
marked.setOptions({
headerIds: true,
headerPrefix: `${filename}_`,
});
lexed.forEach((tok) => {
if (tok.type !== 'heading') return;
if (tok.depth - depth > 1) {
return cb(new Error(`Inappropriate heading level\n${JSON.stringify(tok)}`));
}
depth = tok.depth;
const slugger = new marked.Slugger();
const id = slugger.slug(`${filename}_${tok.text.trim()}`);
toc.push(`${new Array((depth - 1) * 2 + 1).join(' ')}* <a href="#${id}">${tok.text}</a>`);
tok.text += `<span><a class="mark" href="#${id}" ` +
`id="${id}">#</a></span>`;
});
toc = marked.parse(toc.join('\n'));
cb(null, toc);
};

View file

@ -1,556 +0,0 @@
'use strict';
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
module.exports = doJSON;
// Take the lexed input, and return a JSON-encoded object
// A module looks like this: https://gist.github.com/1777387
const marked = require('marked');
const doJSON = (input, filename, cb) => {
const root = {source: filename};
const stack = [root];
let depth = 0;
let current = root;
let state = null;
const lexed = marked.lexer(input);
lexed.forEach((tok) => {
const type = tok.type;
let text = tok.text;
// <!-- type = module -->
// This is for cases where the markdown semantic structure is lacking.
if (type === 'paragraph' || type === 'html') {
const metaExpr = /<!--([^=]+)=([^-]+)-->\n*/g;
text = text.replace(metaExpr, (_0, k, v) => {
current[k.trim()] = v.trim();
return '';
});
text = text.trim();
if (!text) return;
}
if (type === 'heading' &&
!text.trim().match(/^example/i)) {
if (tok.depth - depth > 1) {
return cb(new Error(`Inappropriate heading level\n${
JSON.stringify(tok)}`));
}
// Sometimes we have two headings with a single
// blob of description. Treat as a clone.
if (current &&
state === 'AFTERHEADING' &&
depth === tok.depth) {
const clone = current;
current = newSection(tok);
current.clone = clone;
// don't keep it around on the stack.
stack.pop();
} else {
// if the level is greater than the current depth,
// then it's a child, so we should just leave the stack
// as it is.
// However, if it's a sibling or higher, then it implies
// the closure of the other sections that came before.
// root is always considered the level=0 section,
// and the lowest heading is 1, so this should always
// result in having a valid parent node.
let d = tok.depth;
while (d <= depth) {
finishSection(stack.pop(), stack[stack.length - 1]);
d++;
}
current = newSection(tok);
}
depth = tok.depth;
stack.push(current);
state = 'AFTERHEADING';
return;
} // heading
// Immediately after a heading, we can expect the following
//
// { type: 'code', text: 'Stability: ...' },
//
// a list: starting with list_start, ending with list_end,
// maybe containing other nested lists in each item.
//
// If one of these isn't found, then anything that comes between
// here and the next heading should be parsed as the desc.
let stability;
if (state === 'AFTERHEADING') {
if (type === 'code' &&
(stability = text.match(/^Stability: ([0-5])(?:\s*-\s*)?(.*)$/))) {
current.stability = parseInt(stability[1], 10);
current.stabilityText = stability[2].trim();
return;
} else if (type === 'list_start' && !tok.ordered) {
state = 'AFTERHEADING_LIST';
current.list = current.list || [];
current.list.push(tok);
current.list.level = 1;
} else {
current.desc = current.desc || [];
if (!Array.isArray(current.desc)) {
current.shortDesc = current.desc;
current.desc = [];
}
current.desc.push(tok);
state = 'DESC';
}
return;
}
if (state === 'AFTERHEADING_LIST') {
current.list.push(tok);
if (type === 'list_start') {
current.list.level++;
} else if (type === 'list_end') {
current.list.level--;
}
if (current.list.level === 0) {
state = 'AFTERHEADING';
processList(current);
}
return;
}
current.desc = current.desc || [];
current.desc.push(tok);
});
// finish any sections left open
while (root !== (current = stack.pop())) {
finishSection(current, stack[stack.length - 1]);
}
return cb(null, root);
};
// go from something like this:
// [ { type: 'list_item_start' },
// { type: 'text',
// text: '`settings` Object, Optional' },
// { type: 'list_start', ordered: false },
// { type: 'list_item_start' },
// { type: 'text',
// text: 'exec: String, file path to worker file. Default: `__filename`' },
// { type: 'list_item_end' },
// { type: 'list_item_start' },
// { type: 'text',
// text: 'args: Array, string arguments passed to worker.' },
// { type: 'text',
// text: 'Default: `process.argv.slice(2)`' },
// { type: 'list_item_end' },
// { type: 'list_item_start' },
// { type: 'text',
// text: 'silent: Boolean, whether or not to send output to parent\'s stdio.' },
// { type: 'text', text: 'Default: `false`' },
// { type: 'space' },
// { type: 'list_item_end' },
// { type: 'list_end' },
// { type: 'list_item_end' },
// { type: 'list_end' } ]
// to something like:
// [ { name: 'settings',
// type: 'object',
// optional: true,
// settings:
// [ { name: 'exec',
// type: 'string',
// desc: 'file path to worker file',
// default: '__filename' },
// { name: 'args',
// type: 'array',
// default: 'process.argv.slice(2)',
// desc: 'string arguments passed to worker.' },
// { name: 'silent',
// type: 'boolean',
// desc: 'whether or not to send output to parent\'s stdio.',
// default: 'false' } ] } ]
const processList = (section) => {
const list = section.list;
const values = [];
let current;
const stack = [];
// for now, *just* build the hierarchical list
list.forEach((tok) => {
const type = tok.type;
if (type === 'space') return;
if (type === 'list_item_start') {
if (!current) {
const n = {};
values.push(n);
current = n;
} else {
current.options = current.options || [];
stack.push(current);
const n = {};
current.options.push(n);
current = n;
}
return;
} else if (type === 'list_item_end') {
if (!current) {
throw new Error(`invalid list - end without current item\n${
JSON.stringify(tok)}\n${
JSON.stringify(list)}`);
}
current = stack.pop();
} else if (type === 'text') {
if (!current) {
throw new Error(`invalid list - text without current item\n${
JSON.stringify(tok)}\n${
JSON.stringify(list)}`);
}
current.textRaw = current.textRaw || '';
current.textRaw += `${tok.text} `;
}
});
// shove the name in there for properties, since they are always
// just going to be the value etc.
if (section.type === 'property' && values[0]) {
values[0].textRaw = `\`${section.name}\` ${values[0].textRaw}`;
}
// now pull the actual values out of the text bits.
values.forEach(parseListItem);
// Now figure out what this list actually means.
// depending on the section type, the list could be different things.
switch (section.type) {
case 'ctor':
case 'classMethod':
case 'method': {
// each item is an argument, unless the name is 'return',
// in which case it's the return value.
section.signatures = section.signatures || [];
const sig = {};
section.signatures.push(sig);
sig.params = values.filter((v) => {
if (v.name === 'return') {
sig.return = v;
return false;
}
return true;
});
parseSignature(section.textRaw, sig);
break;
}
case 'property': {
// there should be only one item, which is the value.
// copy the data up to the section.
const value = values[0] || {};
delete value.name;
section.typeof = value.type;
delete value.type;
Object.keys(value).forEach((k) => {
section[k] = value[k];
});
break;
}
case 'event': {
// event: each item is an argument.
section.params = values;
break;
}
}
delete section.list;
};
// textRaw = "someobject.someMethod(a, [b=100], [c])"
const parseSignature = (text, sig) => {
let params = text.match(paramExpr);
if (!params) return;
params = params[1];
// the ] is irrelevant. [ indicates optionalness.
params = params.replace(/\]/g, '');
params = params.split(/,/);
params.forEach((p, i, _) => {
p = p.trim();
if (!p) return;
let param = sig.params[i];
let optional = false;
let def;
// [foo] -> optional
if (p.charAt(0) === '[') {
optional = true;
p = p.substr(1);
}
const eq = p.indexOf('=');
if (eq !== -1) {
def = p.substr(eq + 1);
p = p.substr(0, eq);
}
if (!param) {
param = sig.params[i] = {name: p};
}
// at this point, the name should match.
if (p !== param.name) {
console.error('Warning: invalid param "%s"', p);
console.error(` > ${JSON.stringify(param)}`);
console.error(` > ${text}`);
}
if (optional) param.optional = true;
if (def !== undefined) param.default = def;
});
};
const parseListItem = (item) => {
if (item.options) item.options.forEach(parseListItem);
if (!item.textRaw) return;
// the goal here is to find the name, type, default, and optional.
// anything left over is 'desc'
let text = item.textRaw.trim();
// text = text.replace(/^(Argument|Param)s?\s*:?\s*/i, '');
text = text.replace(/^, /, '').trim();
const retExpr = /^returns?\s*:?\s*/i;
const ret = text.match(retExpr);
if (ret) {
item.name = 'return';
text = text.replace(retExpr, '');
} else {
const nameExpr = /^['`"]?([^'`": {]+)['`"]?\s*:?\s*/;
const name = text.match(nameExpr);
if (name) {
item.name = name[1];
text = text.replace(nameExpr, '');
}
}
text = text.trim();
const defaultExpr = /\(default\s*[:=]?\s*['"`]?([^, '"`]*)['"`]?\)/i;
const def = text.match(defaultExpr);
if (def) {
item.default = def[1];
text = text.replace(defaultExpr, '');
}
text = text.trim();
const typeExpr = /^\{([^}]+)\}/;
const type = text.match(typeExpr);
if (type) {
item.type = type[1];
text = text.replace(typeExpr, '');
}
text = text.trim();
const optExpr = /^Optional\.|(?:, )?Optional$/;
const optional = text.match(optExpr);
if (optional) {
item.optional = true;
text = text.replace(optExpr, '');
}
text = text.replace(/^\s*-\s*/, '');
text = text.trim();
if (text) item.desc = text;
};
const finishSection = (section, parent) => {
if (!section || !parent) {
throw new Error(`Invalid finishSection call\n${
JSON.stringify(section)}\n${
JSON.stringify(parent)}`);
}
if (!section.type) {
section.type = 'module';
if (parent && (parent.type === 'misc')) {
section.type = 'misc';
}
section.displayName = section.name;
section.name = section.name.toLowerCase()
.trim().replace(/\s+/g, '_');
}
if (section.desc && Array.isArray(section.desc)) {
section.desc.links = section.desc.links || [];
section.desc = marked.parser(section.desc);
}
if (!section.list) section.list = [];
processList(section);
// classes sometimes have various 'ctor' children
// which are actually just descriptions of a constructor
// class signature.
// Merge them into the parent.
if (section.type === 'class' && section.ctors) {
section.signatures = section.signatures || [];
const sigs = section.signatures;
section.ctors.forEach((ctor) => {
ctor.signatures = ctor.signatures || [{}];
ctor.signatures.forEach((sig) => {
sig.desc = ctor.desc;
});
sigs.push(...ctor.signatures);
});
delete section.ctors;
}
// properties are a bit special.
// their "type" is the type of object, not "property"
if (section.properties) {
section.properties.forEach((p) => {
if (p.typeof) p.type = p.typeof;
else delete p.type;
delete p.typeof;
});
}
// handle clones
if (section.clone) {
const clone = section.clone;
delete section.clone;
delete clone.clone;
deepCopy(section, clone);
finishSection(clone, parent);
}
let plur;
if (section.type.slice(-1) === 's') {
plur = `${section.type}es`;
} else if (section.type.slice(-1) === 'y') {
plur = section.type.replace(/y$/, 'ies');
} else {
plur = `${section.type}s`;
}
// if the parent's type is 'misc', then it's just a random
// collection of stuff, like the "globals" section.
// Make the children top-level items.
if (section.type === 'misc') {
Object.keys(section).forEach((k) => {
switch (k) {
case 'textRaw':
case 'name':
case 'type':
case 'desc':
case 'miscs':
return;
default:
if (parent.type === 'misc') {
return;
}
if (Array.isArray(k) && parent[k]) {
parent[k] = parent[k].concat(section[k]);
} else if (!parent[k]) {
parent[k] = section[k];
} else {
// parent already has, and it's not an array.
return;
}
}
});
}
parent[plur] = parent[plur] || [];
parent[plur].push(section);
};
// Not a general purpose deep copy.
// But sufficient for these basic things.
const deepCopy = (src, dest) => {
Object.keys(src).filter((k) => !Object.prototype.hasOwnProperty.call(dest, k)).forEach((k) => {
dest[k] = deepCopy_(src[k]);
});
};
const deepCopy_ = (src) => {
if (!src) return src;
if (Array.isArray(src)) {
const c = new Array(src.length);
src.forEach((v, i) => {
c[i] = deepCopy_(v);
});
return c;
}
if (typeof src === 'object') {
const c = {};
Object.keys(src).forEach((k) => {
c[k] = deepCopy_(src[k]);
});
return c;
}
return src;
};
// these parse out the contents of an H# tag
const eventExpr = /^Event(?::|\s)+['"]?([^"']+).*$/i;
const classExpr = /^Class:\s*([^ ]+).*?$/i;
const propExpr = /^(?:property:?\s*)?[^.]+\.([^ .()]+)\s*?$/i;
const braceExpr = /^(?:property:?\s*)?[^.[]+(\[[^\]]+\])\s*?$/i;
const classMethExpr =
/^class\s*method\s*:?[^.]+\.([^ .()]+)\([^)]*\)\s*?$/i;
const methExpr =
/^(?:method:?\s*)?(?:[^.]+\.)?([^ .()]+)\([^)]*\)\s*?$/i;
const newExpr = /^new ([A-Z][a-z]+)\([^)]*\)\s*?$/;
const paramExpr = /\((.*)\);?$/;
const newSection = (tok) => {
const section = {};
// infer the type from the text.
const text = section.textRaw = tok.text;
if (text.match(eventExpr)) {
section.type = 'event';
section.name = text.replace(eventExpr, '$1');
} else if (text.match(classExpr)) {
section.type = 'class';
section.name = text.replace(classExpr, '$1');
} else if (text.match(braceExpr)) {
section.type = 'property';
section.name = text.replace(braceExpr, '$1');
} else if (text.match(propExpr)) {
section.type = 'property';
section.name = text.replace(propExpr, '$1');
} else if (text.match(classMethExpr)) {
section.type = 'classMethod';
section.name = text.replace(classMethExpr, '$1');
} else if (text.match(methExpr)) {
section.type = 'method';
section.name = text.replace(methExpr, '$1');
} else if (text.match(newExpr)) {
section.type = 'ctor';
section.name = text.replace(newExpr, '$1');
} else {
section.name = text;
}
return section;
};

View file

@ -1,26 +0,0 @@
#!/usr/bin/env node
// Checks the health of Etherpad by visiting http://localhost:9001/health. Returns 0 on success, 1
// on error as required by the Dockerfile HEALTHCHECK instruction.
'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 assert = require('assert').strict;
const superagent = require('superagent');
(async () => {
const res = await superagent.get('http://localhost:9001/health')
.accept('application/health+json')
.buffer(true)
.parse(superagent.parse['application/json']);
assert(res.ok, `Unexpected HTTP status: ${res.status}`);
assert.equal(res.type, 'application/health+json');
const {body: {status} = {}} = res;
assert(status != null);
assert.equal(typeof status, 'string');
assert(['pass', 'ok', 'up'].includes(status.toLowerCase()), `Unexpected status: ${status}`);
})();

View file

@ -8,10 +8,10 @@
// 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.
import util from "util";
import process from "process";
import dirtyDB from "ueberdb2";
process.on('unhandledRejection', (err) => { throw err; });
const util = require('util');
if (process.argv.length !== 3) throw new Error('Use: node extractPadData.js $PADID');
// get the padID
@ -19,13 +19,13 @@ const padId = process.argv[2];
(async () => {
// initialize database
require('./src/node/utils/Settings');
const db = require('./src/node/db/DB');
require('ep_etherpad-lite/node/utils/Settings');
const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
// load extra modules
const dirtyDB = require('dirty');
const padManager = require('./src/node/db/PadManager');
const padManager = require('ep_etherpad-lite/node/db/PadManager');
// initialize output database
const dirty = dirtyDB(`${padId}.db`);

View file

@ -2,20 +2,24 @@
// 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.
import util from "util";
const fs = require('fs');
import log4js from 'log4js';
import readline from 'readline';
import ueberDB from "ueberdb2";
const settings = require('ep_etherpad-lite/node/utils/Settings');
process.on('unhandledRejection', (err) => { throw err; });
const util = require('util');
const startTime = Date.now();
const log = (str) => {
const log = (str:string) => {
console.log(`${(Date.now() - startTime) / 1000}\t${str}`);
};
const unescape = (val) => {
const unescape = (val: string) => {
// value is a string
if (val.substr(0, 1) === "'") {
val = val.substr(0, val.length - 1).substr(1);
if (val.substring(0, 1) === "'") {
val = val.substring(0, val.length - 1).substring(1);
return val.replace(/\\[0nrbtZ\\'"]/g, (s) => {
switch (s) {
@ -25,7 +29,7 @@ const unescape = (val) => {
case '\\b': return '\b';
case '\\t': return '\t';
case '\\Z': return '\x1a';
default: return s.substr(1);
default: return s.substring(1);
}
});
}
@ -46,18 +50,13 @@ const unescape = (val) => {
};
(async () => {
const fs = require('fs');
const log4js = require('log4js');
const readline = require('readline');
const settings = require('./src/node/utils/Settings');
const ueberDB = require('ueberdb2');
const dbWrapperSettings = {
cache: 0,
writeInterval: 100,
json: false, // data is already json encoded
};
const db = new ueberDB.database( // eslint-disable-line new-cap
const db = new ueberDB.Database( // eslint-disable-line new-cap
settings.dbType,
settings.dbSettings,
dbWrapperSettings,
@ -69,7 +68,8 @@ const unescape = (val) => {
if (!sqlFile) throw new Error('Use: node importSqlFile.js $SQLFILE');
log('initializing db');
await util.promisify(db.init.bind(db))();
const initDb = await util.promisify(db.init.bind(db));
await initDb(null);
log('done');
log(`Opening ${sqlFile}...`);
@ -78,13 +78,14 @@ const unescape = (val) => {
log(`Reading ${sqlFile}...`);
let keyNo = 0;
for await (const l of readline.createInterface({input: stream, crlfDelay: Infinity})) {
if (l.substr(0, 27) === 'REPLACE INTO store VALUES (') {
if (l.substring(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);
const key = l.substring(28, pos - 28);
let value = l.substring(pos + 3);
value = value.substring(0, value.length - 2);
console.log(`key: ${key} val: ${value}`);
console.log(`unval: ${unescape(value)}`);
// @ts-ignore
db.set(key, unescape(value), null);
keyNo++;
if (keyNo % 1000 === 0) log(` ${keyNo}`);
@ -94,6 +95,7 @@ const unescape = (val) => {
process.stdout.write('done. waiting for db to finish transaction. ' +
'depended on dbms this may take some time..\n');
await util.promisify(db.close.bind(db))();
const closeDB = util.promisify(db.close.bind(db));
await closeDB(null);
log(`finished, imported ${keyNo} keys.`);
})();

View file

@ -1,5 +1,11 @@
'use strict';
import process from 'node:process';
import ueberDB from "ueberdb2";
import log4js from 'log4js';
import util from 'util';
const settings = require('ep_etherpad-lite/node/utils/Settings');
// 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; });
@ -12,17 +18,12 @@ process.on('unhandledRejection', (err) => { throw err; });
// It might be necessary to run the script using more memory:
// `node --max-old-space-size=4096 src/bin/migrateDirtyDBtoRealDB.js`
const dirtyDb = require('dirty');
const log4js = require('log4js');
const settings = require('./src/node/utils/Settings');
const ueberDB = require('ueberdb2');
const util = require('util');
const dbWrapperSettings = {
cache: '0', // The cache slows things down when you're mostly writing.
writeInterval: 0, // Write directly to the database, don't buffer
};
const db = new ueberDB.database( // eslint-disable-line new-cap
const db = new ueberDB.Database( // eslint-disable-line new-cap
settings.dbType,
settings.dbSettings,
dbWrapperSettings,
@ -30,27 +31,30 @@ process.on('unhandledRejection', (err) => { throw err; });
await db.init();
console.log('Waiting for dirtyDB to parse its file.');
const dirty = dirtyDb(`${__dirname}/../var/dirty.db`);
const length = await new Promise((resolve) => { dirty.once('load', resolve); });
const dirty = await new ueberDB.Database('dirty',`${__dirname}/../var/dirty.db`);
await dirty.init();
const keys = await dirty.findKeys('*', '')
console.log(`Found ${length} records, processing now.`);
const p = [];
console.log(`Found ${keys.length} records, processing now.`);
const p: Promise<void>[] = [];
let numWritten = 0;
dirty.forEach((key, value) => {
for (const key of keys) {
let value = await dirty.get(key);
let bcb, wcb;
p.push(new Promise((resolve, reject) => {
bcb = (err) => { if (err != null) return reject(err); };
wcb = (err) => {
bcb = (err:any) => { if (err != null) return reject(err); };
wcb = (err:any) => {
if (err != null) return reject(err);
if (++numWritten % 100 === 0) console.log(`Wrote record ${numWritten} of ${length}`);
resolve();
};
}));
db.set(key, value, bcb, wcb);
});
}
await Promise.all(p);
console.log(`Wrote all ${numWritten} records`);
await util.promisify(db.close.bind(db))();
await db.close(null);
await dirty.close(null);
console.log('Finished.');
})();

37
bin/package.json Normal file
View file

@ -0,0 +1,37 @@
{
"name": "bin",
"version": "1.0.0",
"description": "",
"main": "checkAllPads.js",
"directories": {
"doc": "doc"
},
"dependencies": {
"axios": "^1.6.7",
"ep_etherpad-lite": "workspace:../src",
"log4js": "^6.9.1",
"semver": "^7.6.0",
"tsx": "^4.7.1",
"ueberdb2": "^4.2.63"
},
"devDependencies": {
"@types/node": "^20.11.27",
"@types/semver": "^7.5.8",
"typescript": "^5.4.2"
},
"scripts": {
"checkPad": "node --import tsx checkPad.ts",
"checkAllPads": "node --import tsx checkAllPads.ts",
"createUserSession": "node --import tsx createUserSession.ts",
"deletePad": "node --import tsx deletePad.ts",
"repairPad": "node --import tsx repairPad.ts",
"deleteAllGroupSessions": "node --import tsx deleteAllGroupSessions.ts",
"importSqlFile": "node --import tsx importSqlFile.ts",
"migrateDirtyDBtoRealDB": "node --import tsx migrateDirtyDBtoRealDB.ts",
"rebuildPad": "node --import tsx rebuildPad.ts",
"stalePlugins": "node --import tsx ./plugins/stalePlugins.ts",
"checkPlugins": "node --import tsx ./plugins/checkPlugins.ts"
},
"author": "",
"license": "ISC"
}

View file

@ -10,18 +10,18 @@
* node bin/plugins/checkPlugin.js ep_whatever autopush
*/
const process = require('process');
import process from 'process';
// 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 assert = require('assert').strict;
const fs = require('fs');
import {strict as assert} from 'assert';
import fs from 'fs';
const fsp = fs.promises;
const childProcess = require('child_process');
const log4js = require('log4js');
const path = require('path');
import childProcess from 'child_process';
import log4js from 'log4js';
import path from 'path';
const logger = log4js.getLogger('checkPlugin');
@ -44,23 +44,23 @@ const logger = log4js.getLogger('checkPlugin');
const autoCommit = autoPush || optArgs.includes('autocommit');
const autoFix = autoCommit || optArgs.includes('autofix');
const execSync = (cmd, opts = {}) => (childProcess.execSync(cmd, {
const execSync = (cmd:string, opts = {}) => (childProcess.execSync(cmd, {
cwd: `${pluginPath}/`,
...opts,
}) || '').toString().replace(/\n+$/, '');
const writePackageJson = async (obj) => {
const writePackageJson = async (obj: object) => {
let s = JSON.stringify(obj, null, 2);
if (s.length && s.slice(s.length - 1) !== '\n') s += '\n';
return await fsp.writeFile(`${pluginPath}/package.json`, s);
};
const checkEntries = (got, want) => {
const checkEntries = (got: any, want:any) => {
let changed = false;
for (const [key, val] of Object.entries(want)) {
try {
assert.deepEqual(got[key], val);
} catch (err) {
} catch (err:any) {
logger.warn(`${key} possibly outdated.`);
logger.warn(err.message);
if (autoFix) {
@ -72,7 +72,9 @@ const logger = log4js.getLogger('checkPlugin');
return changed;
};
const updateDeps = async (parsedPackageJson, key, wantDeps) => {
const updateDeps = async (parsedPackageJson: any, key: string, wantDeps: {
[key: string]: string | {ver?: string, overwrite?: boolean}|null
}) => {
const {[key]: deps = {}} = parsedPackageJson;
let changed = false;
for (const [pkg, verInfo] of Object.entries(wantDeps)) {
@ -115,14 +117,14 @@ const logger = log4js.getLogger('checkPlugin');
execSync('git config --get user.email');
}
if (autoPush) {
if (!['master', 'main'].includes(br)) throw new Error('master/main not checked out');
if (!['master', 'main'].includes(br!)) throw new Error('master/main not checked out');
execSync('git rev-parse --verify @{u}');
execSync('git pull --ff-only', {stdio: 'inherit'});
if (execSync('git rev-list @{u}...') !== '') throw new Error('repo contains unpushed commits');
}
};
const checkFile = async (srcFn, dstFn, overwrite = true) => {
const checkFile = async (srcFn: string, dstFn:string, overwrite = true) => {
const outFn = path.join(pluginPath, dstFn);
const wantContents = await fsp.readFile(srcFn, {encoding: 'utf8'});
let gotContents = null;
@ -131,7 +133,7 @@ const logger = log4js.getLogger('checkPlugin');
} catch (err) { /* treat as if the file doesn't exist */ }
try {
assert.equal(gotContents, wantContents);
} catch (err) {
} catch (err:any) {
logger.warn(`File ${dstFn} does not match the default`);
logger.warn(err.message);
if (!overwrite && gotContents != null) {
@ -238,11 +240,11 @@ const logger = log4js.getLogger('checkPlugin');
}
}
const fillTemplate = async (templateFilename, outputFilename) => {
const fillTemplate = async (templateFilename: string, outputFilename: string) => {
const contents = (await fsp.readFile(templateFilename, 'utf8'))
.replace(/\[name of copyright owner\]/g, execSync('git config user.name'))
.replace(/\[plugin_name\]/g, pluginName)
.replace(/\[yyyy\]/g, new Date().getFullYear());
.replace(/\[yyyy\]/g, new Date().getFullYear().toString());
await fsp.writeFile(outputFilename, contents);
};

View file

@ -1,20 +0,0 @@
'use strict';
// Returns a list of stale plugins and their authors email
const superagent = require('superagent');
const currentTime = new Date();
(async () => {
const res = await superagent.get('https://static.etherpad.org/plugins.full.json');
const plugins = JSON.parse(res.text);
for (const plugin of Object.keys(plugins)) {
const name = plugins[plugin].data.name;
const date = new Date(plugins[plugin].time);
const diffTime = Math.abs(currentTime - date);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays > (365 * 2)) {
console.log(`${name}, ${plugins[plugin].data.maintainers[0].email}`);
}
}
})();

View file

@ -0,0 +1,22 @@
'use strict';
// Returns a list of stale plugins and their authors email
import axios from 'axios'
const currentTime = new Date();
(async () => {
const res = await axios.get<string>('https://static.etherpad.org/plugins.full.json');
for (const plugin of Object.keys(res.data)) {
// @ts-ignore
const name = res.data[plugin].data.name;
// @ts-ignore
const date = new Date(res.data[plugin].time);
const diffTime = Math.abs(currentTime.getTime() - date.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
if (diffDays > (365 * 2)) {
// @ts-ignore
console.log(`${name}, ${res.data[plugin].data.maintainers[0].email}`);
}
}
})();

View file

@ -13,16 +13,17 @@ if (process.argv.length !== 4 && process.argv.length !== 5) {
throw new Error('Use: node bin/repairPad.js $PADID $REV [$NEWPADID]');
}
// @ts-ignore
const padId = process.argv[2];
const newRevHead = process.argv[3];
const newRevHead = Number(process.argv[3]);
const newPadId = process.argv[4] || `${padId}-rebuilt`;
(async () => {
const db = require('./src/node/db/DB');
const db = require('ep_etherpad-lite/node/db/DB');
await db.init();
const PadManager = require('./src/node/db/PadManager');
const Pad = require('./src/node/db/Pad').Pad;
const PadManager = require('ep_etherpad-lite/node/db/PadManager');
const Pad = require('ep_etherpad-lite/node/db/Pad').Pad;
// Validate the newPadId if specified and that a pad with that ID does
// not already exist to avoid overwriting it.
if (!PadManager.isValidPadId(newPadId)) {
@ -43,8 +44,8 @@ const newPadId = process.argv[4] || `${padId}-rebuilt`;
}));
// Rebuild Pad from revisions up to and including the new revision head
const AuthorManager = require('./src/node/db/AuthorManager');
const Changeset = require('./src/static/js/Changeset');
const AuthorManager = require('ep_etherpad-lite/node/db/AuthorManager');
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
// Author attributes are derived from changesets, but there can also be
// non-author attributes with specific mappings that changesets depend on
// and, AFAICT, cannot be recreated any other way

View file

@ -4,12 +4,12 @@
// unhandled rejection into an uncaught exception, which does cause Node.js to exit.
process.on('unhandledRejection', (err) => { throw err; });
const fs = require('fs');
const childProcess = require('child_process');
const log4js = require('log4js');
const path = require('path');
const semver = require('semver');
const {exec} = require('child_process');
import fs from 'fs';
import childProcess from 'child_process';
import log4js from 'log4js';
import path from 'path';
import semver from 'semver';
import {exec} from 'child_process';
log4js.configure({appenders: {console: {type: 'console'}},
categories: {
@ -33,23 +33,31 @@ if (!release) {
throw new Error('No release type included');
}
if (release !== 'patch' && release !== 'minor' && release !== 'major') {
console.log(usage);
throw new Error('Invalid release type');
}
const cwd = path.join(fs.realpathSync(__dirname), '../');
process.chdir(cwd);
// Run command capturing stdout. Trailing newlines are stripped (like the shell does).
const runc =
(cmd, opts = {}) => childProcess.execSync(cmd, {encoding: 'utf8', ...opts}).replace(/\n+$/, '');
(cmd:string, opts = {}) => childProcess.execSync(cmd, {encoding: 'utf8', ...opts}).replace(/\n+$/, '');
// Run command without capturing stdout.
const run = (cmd, opts = {}) => childProcess.execSync(cmd, {stdio: 'inherit', ...opts});
const run = (cmd: string, opts = {}) => childProcess.execSync(cmd, {stdio: 'inherit', ...opts});
const readJson = (filename) => JSON.parse(fs.readFileSync(filename, {encoding: 'utf8', flag: 'r'}));
const writeJson = (filename, obj) => {
const readJson = (filename: string) => JSON.parse(fs.readFileSync(filename, {encoding: 'utf8', flag: 'r'}));
const writeJson = (filename: string, obj:object) => {
let json = JSON.stringify(obj, null, 2);
if (json !== '' && !json.endsWith('\n')) json += '\n';
fs.writeFileSync(filename, json);
};
const assertWorkDirClean = (opts = {}) => {
const assertWorkDirClean = (opts:{
cwd?: string;
} = {}) => {
opts.cwd = runc('git rev-parse --show-cdup', opts) || cwd;
const m = runc('git diff-files --name-status', opts);
if (m !== '') throw new Error(`modifications in working directory ${opts.cwd}:\n${m}`);
@ -59,7 +67,9 @@ const assertWorkDirClean = (opts = {}) => {
if (s !== '') throw new Error(`uncommitted changes in working directory ${opts.cwd}:\n${s}`);
};
const assertBranchCheckedOut = (branch, opts = {}) => {
const assertBranchCheckedOut = (branch: string, opts:{
cwd?: string;
} = {}) => {
const b = runc('git symbolic-ref HEAD', opts);
if (b !== `refs/heads/${branch}`) {
const d = opts.cwd ? path.resolve(cwd, opts.cwd) : cwd;
@ -67,21 +77,23 @@ const assertBranchCheckedOut = (branch, opts = {}) => {
}
};
const assertUpstreamOk = (branch, opts = {}) => {
const assertUpstreamOk = (branch: string, opts:{
cwd?: string;
} = {}) => {
const upstream = runc(`git rev-parse --symbolic-full-name ${branch}@{u}`, opts);
if (!(new RegExp(`^refs/remotes/[^/]+/${branch}`)).test(upstream)) {
throw new Error(`${branch} should track origin/${branch}; see git branch --set-upstream-to`);
}
try {
run(`git merge-base --is-ancestor ${branch} ${branch}@{u}`);
} catch (err) {
} catch (err:any) {
if (err.status !== 1) throw err;
throw new Error(`${branch} is ahead of origin/${branch}; do you need to push?`);
}
};
// Check if asciidoctor is installed
exec('asciidoctor -v', (err, stdout) => {
exec('asciidoctor -v', (err) => {
if (err) {
console.log('Please install asciidoctor');
console.log('https://asciidoctor.org/docs/install-toolchain/');
@ -89,10 +101,10 @@ exec('asciidoctor -v', (err, stdout) => {
}
});
const dirExists = (dir) => {
const dirExists = (dir: string) => {
try {
return fs.statSync(dir).isDirectory();
} catch (err) {
} catch (err:any) {
if (err.code !== 'ENOENT') throw err;
return false;
}
@ -165,7 +177,7 @@ try {
run('git checkout develop');
console.log('Merging master into develop...');
run('git merge --no-ff --no-edit master');
} catch (err) {
} catch (err:any) {
console.error(err.toString());
console.warn('Resetting repository...');
console.warn('Resetting master...');
@ -192,7 +204,7 @@ try {
run(`npm version ${newVersion}`, {cwd: '../ether.github.com'});
run('git add .', {cwd: '../ether.github.com/'});
run(`git commit -m '${newVersion} docs'`, {cwd: '../ether.github.com/'});
} catch (err) {
} catch (err:any) {
console.error(err.toString());
console.warn('Resetting repository...');
console.warn('Resetting master...');

View file

@ -1,5 +1,7 @@
'use strict';
import process from "process";
/*
* This is a repair tool. It extracts all datas of a pad, removes and inserts them again.
*/
@ -19,19 +21,19 @@ let valueCount = 0;
(async () => {
// initialize database
require('./src/node/utils/Settings');
const db = require('./src/node/db/DB');
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('./src/node/db/PadManager');
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}`));
neededDBValues.push(...pad.getAllAuthors().map((author: string) => `globalAuthor:${author}`));
// add all revisions
for (let rev = 0; rev <= pad.head; ++rev) {

109
bin/tsconfig.json Normal file
View file

@ -0,0 +1,109 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View file

@ -8,4 +8,4 @@ OUTDATED=$(npm outdated --depth=0 | awk '{print $1}' | grep '^ep_') || {
}
set -- ${OUTDATED}
echo "Updating plugins: $*"
exec npm install --no-save "$@"
exec pnpm install "$@"

View file

@ -1,3 +1,4 @@
packages:
- src
- admin
- bin