diff --git a/bin/cli-plugins b/bin/cli-plugins index b1dee381b..942036d29 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -8,13 +8,14 @@ // https://yarnpkg.com/ const program = require('./commander'); +const inquirer = require('inquirer'); // Make things colorful! require('colors'); const emoji = require('node-emoji'); const dir = process.cwd(); -const fs = require('fs'); +const fs = require('fs-extra'); const path = require('path'); const spawn = require('cross-spawn'); const semver = require('semver'); @@ -272,10 +273,128 @@ async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote console.log(`✨ Done in ${totalTime}s.`); } +async function createSeedPlugin() { + const pluginsDir = path.join(__dirname, 'plugins'); + + function pluginNameExists(pluginName) { + const pluginNames = fs.readdirSync(pluginsDir); + return !!pluginNames + .filter((pn) => pn === pluginName).length; + } + + let answers = await inquirer.prompt([ + { + type: 'input', + name: 'pluginName', + message: 'Plugin Name:', + validate: (input) => { + + if (pluginNameExists(input)) { + return 'Please, choose another name. That name already exists'; + } + + if (input && input.length > 0) { + return true; + } + + return 'Plugin Name is required.'; + } + }, + { + type: 'confirm', + name: 'server', + message: 'Is this plugin extending the server capabilities?' + }, + { + type: 'confirm', + name: 'client', + message: 'Is this plugin extending the client capabilities?' + }, + { + type: 'confirm', + name: 'addPluginsJson', + message: 'Should we add it to the plugins.json?' + } + ]); + + //============================================================================== + // Creating plugin seed + //============================================================================== + + const seedPlugin = path.join(__dirname, 'bin/templates/plugin'); + const newPluginPath = path.join(pluginsDir, answers.pluginName); + + if (fs.existsSync(seedPlugin)) { + + if (answers.server && answers.client) { + + // This is a server-side and client-side plugin!, let's copy the template + fs.copySync(seedPlugin, newPluginPath); + } else { + + fs.copySync(seedPlugin, newPluginPath, {filter: (p) => { + + // Allowing plugin folder and files with no subfolders + const rootRx = /plugin$|plugin\/[^/]*(\.).{2,3}/igm; + if (rootRx.test(p) && (fs.lstatSync(p).isDirectory() || fs.lstatSync(p).isFile())) { + return true; + } + + // If it's a client-side plugin, copying client folder + if (answers.client) { + return /client/.test(p); + } + + // If it's a server-side plugin, copying server folder + if (answers.server) { + return /server/.test(p); + } + + }}); + } + + // Let's add this to the plugins.json + if (answers.addPluginsJson) { + const pluginsJson = path.join(dir, 'plugins.json'); + + fs.readJson(pluginsJson) + .then((j) => { + + // This is a client-side plugin, let's push this. + if (answers.client) { + j.client.push(answers.pluginName); + + const output = JSON.stringify(j, null, 2); + fs.writeFileSync(pluginsJson, output); + } + + // This is a server-side plugin, let's push this. + if (answers.server) { + j.server.push(answers.pluginName); + + const output = JSON.stringify(j, null, 2); + fs.writeFileSync(pluginsJson, output); + } + }) + .catch((err) => { + console.error(err); + }); + } + + console.log(`✨ Yay! Plugin created! Find your plugin: ${answers.pluginName} in the ./plugins folder`); + } + +} + //============================================================================== // Setting up the program command line arguments. //============================================================================== +program + .command('create') + .description('creates a seed plugin') + .action(createSeedPlugin); + program .command('list') .description('') diff --git a/bin/templates/plugin/client/.babelrc b/bin/templates/plugin/client/.babelrc new file mode 100644 index 000000000..60be246eb --- /dev/null +++ b/bin/templates/plugin/client/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} \ No newline at end of file diff --git a/bin/templates/plugin/client/.eslintrc.json b/bin/templates/plugin/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/bin/templates/plugin/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/bin/templates/plugin/client/components/MyPluginComponent.css b/bin/templates/plugin/client/components/MyPluginComponent.css new file mode 100644 index 000000000..187e68750 --- /dev/null +++ b/bin/templates/plugin/client/components/MyPluginComponent.css @@ -0,0 +1,27 @@ +.myPluginContainer { + padding: 10px; + background: #f0f0f0; + border: 1px solid #d6d6d6; + margin: 10px 0; + text-align: center; + border-radius: 3px; +} + +.logo { + position: block; + animation: spin 2s infinite ease; + animation-delay: 1s; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.description { + color: #444444; +} diff --git a/bin/templates/plugin/client/components/MyPluginComponent.js b/bin/templates/plugin/client/components/MyPluginComponent.js new file mode 100644 index 000000000..89e274e1a --- /dev/null +++ b/bin/templates/plugin/client/components/MyPluginComponent.js @@ -0,0 +1,25 @@ +import React from 'react'; +import {CoralLogo} from 'plugin-api/beta/client/components/ui'; +import styles from './MyPluginComponent.css'; + +class MyPluginComponent extends React.Component { + render() { + return ( +
+ +
+

Plugin created by Talk CLI

+ + + To read more about plugins check{' '} + + our docs and guides! + + +
+
+ ); + } +} + +export default MyPluginComponent; diff --git a/bin/templates/plugin/client/index.js b/bin/templates/plugin/client/index.js new file mode 100644 index 000000000..acd0910be --- /dev/null +++ b/bin/templates/plugin/client/index.js @@ -0,0 +1,26 @@ + +/** + This is a client index example file and it could look like this: + + ``` + import LoveButton from './components/LoveButton'; + + export default { + slots: { + commentReactions: [LoveButton] + }, + reducer, + translations + }; + ``` + + To read more info on how to build client plugins. Please, go to: https://coralproject.github.io/talk/plugins-client.html + */ + +import MyPluginComponent from './components/MyPluginComponent'; + +export default { + slots: { + stream: [MyPluginComponent] + } +}; diff --git a/bin/templates/plugin/client/translations.yml b/bin/templates/plugin/client/translations.yml new file mode 100644 index 000000000..d8407b801 --- /dev/null +++ b/bin/templates/plugin/client/translations.yml @@ -0,0 +1,15 @@ +# +# This file is for translations and they should look like this: +# +# +# ``` +# en: +# coral-plugin-respect: +# respect: Respect +# respected: Respected +# es: +# coral-plugin-respect: +# respect: Respetar +# respected: Respetado +# ``` +# diff --git a/bin/templates/plugin/index.js b/bin/templates/plugin/index.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/bin/templates/plugin/index.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/client/coral-framework/helpers/plugins.js b/client/coral-framework/helpers/plugins.js index ab5e3080e..2e5934bc4 100644 --- a/client/coral-framework/helpers/plugins.js +++ b/client/coral-framework/helpers/plugins.js @@ -15,9 +15,9 @@ function getSlotComponents(slot) { // Filter out components that have been disabled in `plugin_config` return flatten(plugins - - // Filter out components that have been disabled in `plugin_config` - .filter((o) => !pluginConfig || !pluginConfig[o.plugin] || !pluginConfig[o.plugin].disable_components) + + // Filter out components that have slots and have been disabled in `plugin_config` + .filter((o) => o.module.slots && (!pluginConfig || !pluginConfig[o.plugin] || !pluginConfig[o.plugin].disable_components)) .filter((o) => o.module.slots[slot]) .map((o) => o.module.slots[slot])); @@ -78,7 +78,7 @@ export function getSlotsFragments(slots) { } const components = uniq(flattenDeep(slots.map((slot) => { return plugins - .filter((o) => o.module.slots[slot]) + .filter((o) => o.module.slots ? o.module.slots[slot] : false) .map((o) => o.module.slots[slot]); }))); diff --git a/package.json b/package.json index 0fe387185..8573fb191 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "express": "^4.15.2", "express-session": "^1.15.1", "form-data": "^2.1.2", + "fs-extra": "^3.0.1", "gql-merge": "^0.0.4", "graphql": "^0.9.1", "graphql-errors": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index 9de2c35fa..c536e2901 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3290,6 +3290,14 @@ fs-extra@^0.26.4: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-extra@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^3.0.0" + universalify "^0.1.0" + fs-promise@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/fs-promise/-/fs-promise-0.3.1.tgz#bf34050368f24d6dc9dfc6688ab5cead8f86842a" @@ -4614,6 +4622,10 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" +json-stringify-pretty-compact@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.0.4.tgz#d5161131be27fd9748391360597fcca250c6c5ce" + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -4632,6 +4644,12 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -8263,6 +8281,10 @@ uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" +universalify@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.0.tgz#9eb1c4651debcc670cc94f1a75762332bb967778" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"