Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

469 Zeilen
19 KiB

vor 4 Jahren
  1. /*
  2. *
  3. * Usage -- see README.md
  4. *
  5. * Normal usage: node bin/plugins/checkPlugins.js ep_whatever
  6. * Auto fix the things it can: node bin/plugins/checkPlugins.js ep_whatever autofix
  7. * Auto commit, push and publish(to npm) * highly dangerous:
  8. node bin/plugins/checkPlugins.js ep_whatever autofix autocommit
  9. */
  10. const fs = require('fs');
  11. const {exec} = require('child_process');
  12. // get plugin name & path from user input
  13. const pluginName = process.argv[2];
  14. if (!pluginName) {
  15. console.error('no plugin name specified');
  16. process.exit(1);
  17. }
  18. const pluginPath = `node_modules/${pluginName}`;
  19. console.log(`Checking the plugin: ${pluginName}`);
  20. // Should we autofix?
  21. if (process.argv[3] && process.argv[3] === 'autofix') var autoFix = true;
  22. // Should we update files where possible?
  23. if (process.argv[5] && process.argv[5] === 'autoupdate') var autoUpdate = true;
  24. // Should we automcommit and npm publish?!
  25. if (process.argv[4] && process.argv[4] === 'autocommit') var autoCommit = true;
  26. if (autoCommit) {
  27. console.warn('Auto commit is enabled, I hope you know what you are doing...');
  28. }
  29. fs.readdir(pluginPath, (err, rootFiles) => {
  30. // handling error
  31. if (err) {
  32. return console.log(`Unable to scan directory: ${err}`);
  33. }
  34. // rewriting files to lower case
  35. const files = [];
  36. // some files we need to know the actual file name. Not compulsory but might help in the future.
  37. let readMeFileName;
  38. let repository;
  39. let hasAutoFixed = false;
  40. for (let i = 0; i < rootFiles.length; i++) {
  41. if (rootFiles[i].toLowerCase().indexOf('readme') !== -1) readMeFileName = rootFiles[i];
  42. files.push(rootFiles[i].toLowerCase());
  43. }
  44. if (files.indexOf('.git') === -1) {
  45. console.error('No .git folder, aborting');
  46. process.exit(1);
  47. }
  48. // do a git pull...
  49. var child_process = require('child_process');
  50. try {
  51. child_process.execSync('git pull ', {cwd: `${pluginPath}/`});
  52. } catch (e) {
  53. console.error('Error git pull', e);
  54. }
  55. try {
  56. const path = `${pluginPath}/.github/workflows/npmpublish.yml`;
  57. if (!fs.existsSync(path)) {
  58. console.log('no .github/workflows/npmpublish.yml, create one and set npm secret to auto publish to npm on commit');
  59. if (autoFix) {
  60. const npmpublish =
  61. fs.readFileSync('bin/plugins/lib/npmpublish.yml', {encoding: 'utf8', flag: 'r'});
  62. fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
  63. fs.writeFileSync(path, npmpublish);
  64. hasAutoFixed = true;
  65. console.log("If you haven't already, setup autopublish for this plugin https://github.com/ether/etherpad-lite/wiki/Plugins:-Automatically-publishing-to-npm-on-commit-to-Github-Repo");
  66. } else {
  67. console.log('Setup autopublish for this plugin https://github.com/ether/etherpad-lite/wiki/Plugins:-Automatically-publishing-to-npm-on-commit-to-Github-Repo');
  68. }
  69. } else {
  70. // autopublish exists, we should check the version..
  71. // checkVersion takes two file paths and checks for a version string in them.
  72. const currVersionFile = fs.readFileSync(path, {encoding: 'utf8', flag: 'r'});
  73. const existingConfigLocation = currVersionFile.indexOf('##ETHERPAD_NPM_V=');
  74. const existingValue = parseInt(currVersionFile.substr(existingConfigLocation + 17, existingConfigLocation.length));
  75. const reqVersionFile = fs.readFileSync('bin/plugins/lib/npmpublish.yml', {encoding: 'utf8', flag: 'r'});
  76. const reqConfigLocation = reqVersionFile.indexOf('##ETHERPAD_NPM_V=');
  77. const reqValue = parseInt(reqVersionFile.substr(reqConfigLocation + 17, reqConfigLocation.length));
  78. if (!existingValue || (reqValue > existingValue)) {
  79. const npmpublish =
  80. fs.readFileSync('bin/plugins/lib/npmpublish.yml', {encoding: 'utf8', flag: 'r'});
  81. fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
  82. fs.writeFileSync(path, npmpublish);
  83. hasAutoFixed = true;
  84. }
  85. }
  86. } catch (err) {
  87. console.error(err);
  88. }
  89. try {
  90. const path = `${pluginPath}/.github/workflows/backend-tests.yml`;
  91. if (!fs.existsSync(path)) {
  92. console.log('no .github/workflows/backend-tests.yml, create one and set npm secret to auto publish to npm on commit');
  93. if (autoFix) {
  94. const backendTests =
  95. fs.readFileSync('bin/plugins/lib/backend-tests.yml', {encoding: 'utf8', flag: 'r'});
  96. fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
  97. fs.writeFileSync(path, backendTests);
  98. hasAutoFixed = true;
  99. }
  100. } else {
  101. // autopublish exists, we should check the version..
  102. // checkVersion takes two file paths and checks for a version string in them.
  103. const currVersionFile = fs.readFileSync(path, {encoding: 'utf8', flag: 'r'});
  104. const existingConfigLocation = currVersionFile.indexOf('##ETHERPAD_NPM_V=');
  105. const existingValue = parseInt(currVersionFile.substr(existingConfigLocation + 17, existingConfigLocation.length));
  106. const reqVersionFile = fs.readFileSync('bin/plugins/lib/backend-tests.yml', {encoding: 'utf8', flag: 'r'});
  107. const reqConfigLocation = reqVersionFile.indexOf('##ETHERPAD_NPM_V=');
  108. const reqValue = parseInt(reqVersionFile.substr(reqConfigLocation + 17, reqConfigLocation.length));
  109. if (!existingValue || (reqValue > existingValue)) {
  110. const backendTests =
  111. fs.readFileSync('bin/plugins/lib/backend-tests.yml', {encoding: 'utf8', flag: 'r'});
  112. fs.mkdirSync(`${pluginPath}/.github/workflows`, {recursive: true});
  113. fs.writeFileSync(path, backendTests);
  114. hasAutoFixed = true;
  115. }
  116. }
  117. } catch (err) {
  118. console.error(err);
  119. }
  120. if (files.indexOf('package.json') === -1) {
  121. console.warn('no package.json, please create');
  122. }
  123. if (files.indexOf('package.json') !== -1) {
  124. const packageJSON = fs.readFileSync(`${pluginPath}/package.json`, {encoding: 'utf8', flag: 'r'});
  125. const parsedPackageJSON = JSON.parse(packageJSON);
  126. if (autoFix) {
  127. let updatedPackageJSON = false;
  128. if (!parsedPackageJSON.funding) {
  129. updatedPackageJSON = true;
  130. parsedPackageJSON.funding = {
  131. type: 'individual',
  132. url: 'https://etherpad.org/',
  133. };
  134. }
  135. if (updatedPackageJSON) {
  136. hasAutoFixed = true;
  137. fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
  138. }
  139. }
  140. if (packageJSON.toLowerCase().indexOf('repository') === -1) {
  141. console.warn('No repository in package.json');
  142. if (autoFix) {
  143. console.warn('Repository not detected in package.json. Please add repository section manually.');
  144. }
  145. } else {
  146. // useful for creating README later.
  147. repository = parsedPackageJSON.repository.url;
  148. }
  149. // include lint config
  150. if (packageJSON.toLowerCase().indexOf('devdependencies') === -1 || !parsedPackageJSON.devDependencies.eslint) {
  151. console.warn('Missing eslint reference in devDependencies');
  152. if (autoFix) {
  153. const devDependencies = {
  154. 'eslint': '^7.14.0',
  155. 'eslint-config-etherpad': '^1.0.13',
  156. 'eslint-plugin-mocha': '^8.0.0',
  157. 'eslint-plugin-node': '^11.1.0',
  158. 'eslint-plugin-prefer-arrow': '^1.2.2',
  159. 'eslint-plugin-promise': '^4.2.1',
  160. };
  161. hasAutoFixed = true;
  162. parsedPackageJSON.devDependencies = devDependencies;
  163. fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
  164. const child_process = require('child_process');
  165. try {
  166. child_process.execSync('npm install', {cwd: `${pluginPath}/`});
  167. hasAutoFixed = true;
  168. } catch (e) {
  169. console.error('Failed to create package-lock.json');
  170. }
  171. }
  172. }
  173. // include peer deps config
  174. if (packageJSON.toLowerCase().indexOf('peerdependencies') === -1 || !parsedPackageJSON.peerDependencies) {
  175. console.warn('Missing peer deps reference in package.json');
  176. if (autoFix) {
  177. const peerDependencies = {
  178. 'ep_etherpad-lite': '>=1.8.6',
  179. };
  180. hasAutoFixed = true;
  181. parsedPackageJSON.peerDependencies = peerDependencies;
  182. fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
  183. const child_process = require('child_process');
  184. try {
  185. child_process.execSync('npm install --no-save ep_etherpad-lite@file:../../src', {cwd: `${pluginPath}/`});
  186. hasAutoFixed = true;
  187. } catch (e) {
  188. console.error('Failed to create package-lock.json');
  189. }
  190. }
  191. }
  192. if (packageJSON.toLowerCase().indexOf('eslintconfig') === -1) {
  193. console.warn('No esLintConfig in package.json');
  194. if (autoFix) {
  195. const eslintConfig = {
  196. root: true,
  197. extends: 'etherpad/plugin',
  198. };
  199. hasAutoFixed = true;
  200. parsedPackageJSON.eslintConfig = eslintConfig;
  201. fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
  202. }
  203. }
  204. if (packageJSON.toLowerCase().indexOf('scripts') === -1) {
  205. console.warn('No scripts in package.json');
  206. if (autoFix) {
  207. const scripts = {
  208. 'lint': 'eslint .',
  209. 'lint:fix': 'eslint --fix .',
  210. };
  211. hasAutoFixed = true;
  212. parsedPackageJSON.scripts = scripts;
  213. fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
  214. }
  215. }
  216. if ((packageJSON.toLowerCase().indexOf('engines') === -1) || !parsedPackageJSON.engines.node) {
  217. console.warn('No engines or node engine in package.json');
  218. if (autoFix) {
  219. const engines = {
  220. node: '>=10.13.0',
  221. };
  222. hasAutoFixed = true;
  223. parsedPackageJSON.engines = engines;
  224. fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify(parsedPackageJSON, null, 2));
  225. }
  226. }
  227. }
  228. if (files.indexOf('package-lock.json') === -1) {
  229. console.warn('package-lock.json file not found. Please run npm install in the plugin folder and commit the package-lock.json file.');
  230. if (autoFix) {
  231. var child_process = require('child_process');
  232. try {
  233. child_process.execSync('npm install', {cwd: `${pluginPath}/`});
  234. console.log('Making package-lock.json');
  235. hasAutoFixed = true;
  236. } catch (e) {
  237. console.error('Failed to create package-lock.json');
  238. }
  239. }
  240. }
  241. if (files.indexOf('readme') === -1 && files.indexOf('readme.md') === -1) {
  242. console.warn('README.md file not found, please create');
  243. if (autoFix) {
  244. console.log('Autofixing missing README.md file, please edit the README.md file further to include plugin specific details.');
  245. let readme = fs.readFileSync('bin/plugins/lib/README.md', {encoding: 'utf8', flag: 'r'});
  246. readme = readme.replace(/\[plugin_name\]/g, pluginName);
  247. if (repository) {
  248. const org = repository.split('/')[3];
  249. const name = repository.split('/')[4];
  250. readme = readme.replace(/\[org_name\]/g, org);
  251. readme = readme.replace(/\[repo_url\]/g, name);
  252. fs.writeFileSync(`${pluginPath}/README.md`, readme);
  253. } else {
  254. console.warn('Unable to find repository in package.json, aborting.');
  255. }
  256. }
  257. }
  258. if (files.indexOf('contributing') === -1 && files.indexOf('contributing.md') === -1) {
  259. console.warn('CONTRIBUTING.md file not found, please create');
  260. if (autoFix) {
  261. console.log('Autofixing missing CONTRIBUTING.md file, please edit the CONTRIBUTING.md file further to include plugin specific details.');
  262. let contributing = fs.readFileSync('bin/plugins/lib/CONTRIBUTING.md', {encoding: 'utf8', flag: 'r'});
  263. contributing = contributing.replace(/\[plugin_name\]/g, pluginName);
  264. fs.writeFileSync(`${pluginPath}/CONTRIBUTING.md`, contributing);
  265. }
  266. }
  267. if (files.indexOf('readme') !== -1 && files.indexOf('readme.md') !== -1) {
  268. const readme = fs.readFileSync(`${pluginPath}/${readMeFileName}`, {encoding: 'utf8', flag: 'r'});
  269. if (readme.toLowerCase().indexOf('license') === -1) {
  270. console.warn('No license section in README');
  271. if (autoFix) {
  272. console.warn('Please add License section to README manually.');
  273. }
  274. }
  275. }
  276. if (files.indexOf('license') === -1 && files.indexOf('license.md') === -1) {
  277. console.warn('LICENSE.md file not found, please create');
  278. if (autoFix) {
  279. hasAutoFixed = true;
  280. console.log('Autofixing missing LICENSE.md file, including Apache 2 license.');
  281. exec('git config user.name', (error, name, stderr) => {
  282. if (error) {
  283. console.log(`error: ${error.message}`);
  284. return;
  285. }
  286. if (stderr) {
  287. console.log(`stderr: ${stderr}`);
  288. return;
  289. }
  290. let license = fs.readFileSync('bin/plugins/lib/LICENSE.md', {encoding: 'utf8', flag: 'r'});
  291. license = license.replace('[yyyy]', new Date().getFullYear());
  292. license = license.replace('[name of copyright owner]', name);
  293. fs.writeFileSync(`${pluginPath}/LICENSE.md`, license);
  294. });
  295. }
  296. }
  297. let travisConfig = fs.readFileSync('bin/plugins/lib/travis.yml', {encoding: 'utf8', flag: 'r'});
  298. travisConfig = travisConfig.replace(/\[plugin_name\]/g, pluginName);
  299. if (files.indexOf('.travis.yml') === -1) {
  300. 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.');
  301. // TODO: Make it check version of the .travis file to see if it needs an update.
  302. if (autoFix) {
  303. hasAutoFixed = true;
  304. console.log('Autofixing missing .travis.yml file');
  305. fs.writeFileSync(`${pluginPath}/.travis.yml`, travisConfig);
  306. console.log('Travis file created, please sign into travis and enable this repository');
  307. }
  308. }
  309. if (autoFix && autoUpdate) {
  310. // checks the file versioning of .travis and updates it to the latest.
  311. const existingConfig = fs.readFileSync(`${pluginPath}/.travis.yml`, {encoding: 'utf8', flag: 'r'});
  312. const existingConfigLocation = existingConfig.indexOf('##ETHERPAD_TRAVIS_V=');
  313. const existingValue = parseInt(existingConfig.substr(existingConfigLocation + 20, existingConfig.length));
  314. const newConfigLocation = travisConfig.indexOf('##ETHERPAD_TRAVIS_V=');
  315. const newValue = parseInt(travisConfig.substr(newConfigLocation + 20, travisConfig.length));
  316. if (existingConfigLocation === -1) {
  317. console.warn('no previous .travis.yml version found so writing new.');
  318. // we will write the newTravisConfig to the location.
  319. fs.writeFileSync(`${pluginPath}/.travis.yml`, travisConfig);
  320. } else if (newValue > existingValue) {
  321. console.log('updating .travis.yml');
  322. fs.writeFileSync(`${pluginPath}/.travis.yml`, travisConfig);
  323. hasAutoFixed = true;
  324. }//
  325. }
  326. if (files.indexOf('.gitignore') === -1) {
  327. console.warn(".gitignore file not found, please create. .gitignore files are useful to ensure files aren't incorrectly commited to a repository.");
  328. if (autoFix) {
  329. hasAutoFixed = true;
  330. console.log('Autofixing missing .gitignore file');
  331. const gitignore = fs.readFileSync('bin/plugins/lib/gitignore', {encoding: 'utf8', flag: 'r'});
  332. fs.writeFileSync(`${pluginPath}/.gitignore`, gitignore);
  333. }
  334. } else {
  335. let gitignore =
  336. fs.readFileSync(`${pluginPath}/.gitignore`, {encoding: 'utf8', flag: 'r'});
  337. if (gitignore.indexOf('node_modules/') === -1) {
  338. console.warn('node_modules/ missing from .gitignore');
  339. if (autoFix) {
  340. gitignore += 'node_modules/';
  341. fs.writeFileSync(`${pluginPath}/.gitignore`, gitignore);
  342. hasAutoFixed = true;
  343. }
  344. }
  345. }
  346. // if we include templates but don't have translations...
  347. if (files.indexOf('templates') !== -1 && files.indexOf('locales') === -1) {
  348. console.warn('Translations not found, please create. Translation files help with Etherpad accessibility.');
  349. }
  350. if (files.indexOf('.ep_initialized') !== -1) {
  351. 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.');
  352. if (autoFix) {
  353. hasAutoFixed = true;
  354. console.log('Autofixing incorrectly existing .ep_initialized file');
  355. fs.unlinkSync(`${pluginPath}/.ep_initialized`);
  356. }
  357. }
  358. if (files.indexOf('npm-debug.log') !== -1) {
  359. console.warn('npm-debug.log found, please remove. npm-debug.log should never be commited to your repository.');
  360. if (autoFix) {
  361. hasAutoFixed = true;
  362. console.log('Autofixing incorrectly existing npm-debug.log file');
  363. fs.unlinkSync(`${pluginPath}/npm-debug.log`);
  364. }
  365. }
  366. if (files.indexOf('static') !== -1) {
  367. fs.readdir(`${pluginPath}/static`, (errRead, staticFiles) => {
  368. if (staticFiles.indexOf('tests') === -1) {
  369. console.warn('Test files not found, please create tests. https://github.com/ether/etherpad-lite/wiki/Creating-a-plugin#writing-and-running-front-end-tests-for-your-plugin');
  370. }
  371. });
  372. } else {
  373. console.warn('Test files not found, please create tests. https://github.com/ether/etherpad-lite/wiki/Creating-a-plugin#writing-and-running-front-end-tests-for-your-plugin');
  374. }
  375. // linting begins
  376. if (autoFix) {
  377. var lintCmd = 'npm run lint:fix';
  378. } else {
  379. var lintCmd = 'npm run lint';
  380. }
  381. try {
  382. child_process.execSync(lintCmd, {cwd: `${pluginPath}/`});
  383. console.log('Linting...');
  384. if (autoFix) {
  385. // todo: if npm run lint doesn't do anything no need for...
  386. hasAutoFixed = true;
  387. }
  388. } catch (e) {
  389. // it is gonna throw an error anyway
  390. console.log('Manual linting probably required, check with: npm run lint');
  391. }
  392. // linting ends.
  393. if (hasAutoFixed) {
  394. console.log('Fixes applied, please check git diff then run the following command:\n\n');
  395. // bump npm Version
  396. if (autoCommit) {
  397. // holy shit you brave.
  398. console.log('Attempting autocommit and auto publish to npm');
  399. // github should push to npm for us :)
  400. exec(`cd node_modules/${pluginName} && git rm -rf node_modules --ignore-unmatch && git add -A && git commit --allow-empty -m 'autofixes from Etherpad checkPlugins.js' && git push && cd ../..`, (error, name, stderr) => {
  401. if (error) {
  402. console.log(`error: ${error.message}`);
  403. return;
  404. }
  405. if (stderr) {
  406. console.log(`stderr: ${stderr}`);
  407. return;
  408. }
  409. console.log("I think she's got it! By George she's got it!");
  410. process.exit(0);
  411. });
  412. } else {
  413. console.log(`cd node_modules/${pluginName} && git add -A && git commit --allow-empty -m 'autofixes from Etherpad checkPlugins.js' && npm version patch && git add package.json && git commit --allow-empty -m 'bump version' && git push && npm publish && cd ../..`);
  414. }
  415. }
  416. console.log('Finished');
  417. });