diff --git a/packages/redux-devtools-cli/.eslintignore b/packages/redux-devtools-cli/.eslintignore new file mode 100644 index 00000000..d827b1ac --- /dev/null +++ b/packages/redux-devtools-cli/.eslintignore @@ -0,0 +1,2 @@ +dist +umd diff --git a/packages/redux-devtools-cli/.eslintrc.js b/packages/redux-devtools-cli/.eslintrc.js new file mode 100644 index 00000000..486b7fd4 --- /dev/null +++ b/packages/redux-devtools-cli/.eslintrc.js @@ -0,0 +1,21 @@ +module.exports = { + extends: '../../.eslintrc', + overrides: [ + { + files: ['*.ts', '*.tsx'], + extends: '../../eslintrc.ts.base.json', + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + }, + { + files: ['test/*.ts', 'test/*.tsx'], + extends: '../../eslintrc.ts.jest.base.json', + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./test/tsconfig.json'], + }, + }, + ], +}; diff --git a/packages/redux-devtools-cli/bin/injectServer.js b/packages/redux-devtools-cli/bin/injectServer.js deleted file mode 100644 index 8a4816b6..00000000 --- a/packages/redux-devtools-cli/bin/injectServer.js +++ /dev/null @@ -1,95 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var semver = require('semver'); - -var name = 'redux-devtools-cli'; -var startFlag = '/* ' + name + ' start */'; -var endFlag = '/* ' + name + ' end */'; -var serverFlags = { - 'react-native': { - '0.0.1': ' _server(argv, config, resolve, reject);', - '0.31.0': - " runServer(args, config, () => console.log('\\nReact packager ready.\\n'));", - '0.44.0-rc.0': ' runServer(args, config, startedCallback, readyCallback);', - '0.46.0-rc.0': - ' runServer(runServerArgs, configT, startedCallback, readyCallback);', - '0.57.0': ' runServer(args, configT);', - }, - 'react-native-desktop': { - '0.0.1': ' _server(argv, config, resolve, reject);', - }, -}; - -function getModuleVersion(modulePath) { - return JSON.parse( - fs.readFileSync(path.join(modulePath, 'package.json'), 'utf-8') - ).version; -} - -function getServerFlag(moduleName, version) { - var flags = serverFlags[moduleName || 'react-native']; - var versions = Object.keys(flags); - var flag; - for (var i = 0; i < versions.length; i++) { - if (semver.gte(version, versions[i])) { - flag = flags[versions[i]]; - } - } - return flag; -} - -exports.dir = 'local-cli/server'; -exports.file = 'server.js'; -exports.fullPath = path.join(exports.dir, exports.file); - -exports.inject = function (modulePath, options, moduleName) { - var filePath = path.join(modulePath, exports.fullPath); - if (!fs.existsSync(filePath)) return false; - - var serverFlag = getServerFlag(moduleName, getModuleVersion(modulePath)); - var code = [ - startFlag, - ' require("' + name + '")(' + JSON.stringify(options) + ')', - ' .then(_remotedev =>', - ' _remotedev.on("ready", () => {', - ' if (!_remotedev.portAlreadyUsed) console.log("-".repeat(80));', - ' ' + serverFlag, - ' })', - ' );', - endFlag, - ].join('\n'); - - var serverCode = fs.readFileSync(filePath, 'utf-8'); - var start = serverCode.indexOf(startFlag); // already injected ? - var end = serverCode.indexOf(endFlag) + endFlag.length; - if (start === -1) { - start = serverCode.indexOf(serverFlag); - end = start + serverFlag.length; - } - fs.writeFileSync( - filePath, - serverCode.substr(0, start) + - code + - serverCode.substr(end, serverCode.length) - ); - return true; -}; - -exports.revert = function (modulePath, moduleName) { - var filePath = path.join(modulePath, exports.fullPath); - if (!fs.existsSync(filePath)) return false; - - var serverFlag = getServerFlag(moduleName, getModuleVersion(modulePath)); - var serverCode = fs.readFileSync(filePath, 'utf-8'); - var start = serverCode.indexOf(startFlag); // already injected ? - var end = serverCode.indexOf(endFlag) + endFlag.length; - if (start !== -1) { - fs.writeFileSync( - filePath, - serverCode.substr(0, start) + - serverFlag + - serverCode.substr(end, serverCode.length) - ); - } - return true; -}; diff --git a/packages/redux-devtools-cli/bin/redux-devtools.js b/packages/redux-devtools-cli/bin/redux-devtools.js old mode 100755 new mode 100644 index 093829f2..91be0e79 --- a/packages/redux-devtools-cli/bin/redux-devtools.js +++ b/packages/redux-devtools-cli/bin/redux-devtools.js @@ -1,94 +1,3 @@ #! /usr/bin/env node -var fs = require('fs'); -var path = require('path'); -var argv = require('minimist')(process.argv.slice(2)); -var chalk = require('chalk'); -var injectServer = require('./injectServer'); -var getOptions = require('./../src/options'); -var server = require('../index'); -var openApp = require('./openApp'); -var options = getOptions(argv); - -function readFile(filePath) { - return fs.readFileSync(path.resolve(process.cwd(), filePath), 'utf-8'); -} - -if (argv.protocol === 'https') { - argv.key = argv.key ? readFile(argv.key) : null; - argv.cert = argv.cert ? readFile(argv.cert) : null; -} - -function log(pass, msg) { - var prefix = pass ? chalk.green.bgBlack('PASS') : chalk.red.bgBlack('FAIL'); - var color = pass ? chalk.blue : chalk.red; - console.log(prefix, color(msg)); // eslint-disable-line no-console -} - -function getModuleName(type) { - switch (type) { - case 'macos': - return 'react-native-macos'; - // react-native-macos is renamed from react-native-desktop - case 'desktop': - return 'react-native-desktop'; - case 'reactnative': - default: - return 'react-native'; - } -} - -function getModulePath(moduleName) { - return path.join(process.cwd(), 'node_modules', moduleName); -} - -function getModule(type) { - var moduleName = getModuleName(type); - var modulePath = getModulePath(moduleName); - if (type === 'desktop' && !fs.existsSync(modulePath)) { - moduleName = getModuleName('macos'); - modulePath = getModulePath(moduleName); - } - return { - name: moduleName, - path: modulePath, - }; -} - -function injectRN(type, msg) { - var module = getModule(type); - var fn = type === 'revert' ? injectServer.revert : injectServer.inject; - var pass = fn(module.path, options, module.name); - log( - pass, - msg + - (pass - ? '.' - : ', the file `' + - path.join(module.name, injectServer.fullPath) + - '` not found.') - ); - - process.exit(pass ? 0 : 1); -} - -if (argv.revert) { - injectRN( - argv.revert, - 'Revert injection of ReduxDevTools server from React Native local server' - ); -} -if (argv.injectserver) { - injectRN( - argv.injectserver, - 'Inject ReduxDevTools server into React Native local server' - ); -} - -server(argv).then(function (r) { - if (argv.open && argv.open !== 'false') { - r.on('ready', function () { - openApp(argv.open, options); - }); - } -}); +require('../dist/bin/redux-devtools.js'); diff --git a/packages/redux-devtools-cli/jest.config.js b/packages/redux-devtools-cli/jest.config.js new file mode 100644 index 00000000..8824c114 --- /dev/null +++ b/packages/redux-devtools-cli/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + preset: 'ts-jest', +}; diff --git a/packages/redux-devtools-cli/package.json b/packages/redux-devtools-cli/package.json index b919984a..fb02714e 100644 --- a/packages/redux-devtools-cli/package.json +++ b/packages/redux-devtools-cli/package.json @@ -2,10 +2,12 @@ "name": "redux-devtools-cli", "version": "1.0.0-4", "description": "CLI for remote debugging with Redux DevTools.", - "main": "index.js", - "bin": { - "redux-devtools": "bin/redux-devtools.js" + "homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-cli", + "bugs": { + "url": "https://github.com/reduxjs/redux-devtools/issues" }, + "license": "MIT", + "author": "Mihail Diordiev (https://github.com/zalmoxisus)", "files": [ "bin", "src", @@ -13,25 +15,31 @@ "index.js", "defaultDbOptions.json" ], - "scripts": { - "start": "node ./bin/redux-devtools.js", - "start:electron": "node ./bin/redux-devtools.js --open", - "test": "jest", - "prepublishOnly": "npm run test" + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "redux-devtools": "bin/redux-devtools.js" }, "repository": { "type": "git", "url": "https://github.com/reduxjs/redux-devtools.git" }, + "scripts": { + "build": "tsc && ncp ./src/api/schema_def.graphql ./dist/api/schema_def.graphql", + "start": "node ./bin/redux-devtools.js", + "start:electron": "node ./bin/redux-devtools.js --open", + "clean": "rimraf dist", + "test": "jest", + "lint": "eslint . --ext .ts,.tsx", + "lint:fix": "eslint . --ext .ts,.tsx --fix", + "type-check": "tsc --noEmit", + "type-check:watch": "npm run type-check -- --watch", + "preversion": "npm run type-check && npm run lint && npm run test", + "prepublishOnly": "npm run clean && npm run build" + }, "engines": { - "node": ">=6.0.0" + "node": ">=10.0.0" }, - "author": "Mihail Diordiev (https://github.com/zalmoxisus)", - "license": "MIT", - "bugs": { - "url": "https://github.com/reduxjs/redux-devtools/issues" - }, - "homepage": "https://github.com/reduxjs/redux-devtools", "dependencies": { "apollo-server": "^2.18.2", "apollo-server-express": "^2.18.2", @@ -42,7 +50,7 @@ "electron": "^9.2.0", "express": "^4.17.1", "getport": "^0.1.0", - "graphql": "^15.3.0", + "graphql": "^14.7.0", "knex": "^0.19.5", "lodash": "^4.17.19", "minimist": "^1.2.5", @@ -57,7 +65,13 @@ "uuid": "^8.3.0" }, "devDependencies": { + "@types/cross-spawn": "^6.0.2", + "@types/morgan": "^1.9.1", + "@types/semver": "^7.3.4", + "@types/supertest": "^2.0.10", + "@types/uuid": "^8.3.0", "jest": "^26.2.2", + "ncp": "^2.0.0", "socketcluster-client": "^14.3.1", "supertest": "^4.0.2" } diff --git a/packages/redux-devtools-cli/src/api/schema.js b/packages/redux-devtools-cli/src/api/schema.js deleted file mode 100644 index e92b624d..00000000 --- a/packages/redux-devtools-cli/src/api/schema.js +++ /dev/null @@ -1,21 +0,0 @@ -var makeExecutableSchema = require('apollo-server').makeExecutableSchema; -var requireSchema = require('../utils/requireSchema'); -var schema = requireSchema('./schema_def.graphql', require); - -var resolvers = { - Query: { - reports: function report(source, args, context) { - return context.store.listAll(); - }, - report: function report(source, args, context) { - return context.store.get(args.id); - }, - }, -}; - -var executableSchema = makeExecutableSchema({ - typeDefs: schema, - resolvers: resolvers, -}); - -module.exports = executableSchema; diff --git a/packages/redux-devtools-cli/src/api/schema.ts b/packages/redux-devtools-cli/src/api/schema.ts new file mode 100644 index 00000000..a5236cd2 --- /dev/null +++ b/packages/redux-devtools-cli/src/api/schema.ts @@ -0,0 +1,33 @@ +import fs from 'fs'; +import { makeExecutableSchema } from 'apollo-server'; +import { Store } from '../store'; + +const schema = fs + .readFileSync(require.resolve('./schema_def.graphql')) + .toString(); + +const resolvers = { + Query: { + reports: function report( + source: unknown, + args: unknown, + context: { store: Store } + ) { + return context.store.listAll(); + }, + report: function report( + source: unknown, + args: { id: string }, + context: { store: Store } + ) { + return context.store.get(args.id); + }, + }, +}; + +const executableSchema = makeExecutableSchema({ + typeDefs: schema, + resolvers: resolvers, +}); + +export default executableSchema; diff --git a/packages/redux-devtools-cli/src/bin/injectServer.ts b/packages/redux-devtools-cli/src/bin/injectServer.ts new file mode 100644 index 00000000..c5d04acf --- /dev/null +++ b/packages/redux-devtools-cli/src/bin/injectServer.ts @@ -0,0 +1,104 @@ +import fs from 'fs'; +import path from 'path'; +import semver from 'semver'; +import { Options } from '../options'; + +const name = 'redux-devtools-cli'; +const startFlag = '/* ' + name + ' start */'; +const endFlag = '/* ' + name + ' end */'; +const serverFlags: { [moduleName: string]: { [version: string]: string } } = { + 'react-native': { + '0.0.1': ' _server(argv, config, resolve, reject);', + '0.31.0': + " runServer(args, config, () => console.log('\\nReact packager ready.\\n'));", + '0.44.0-rc.0': ' runServer(args, config, startedCallback, readyCallback);', + '0.46.0-rc.0': + ' runServer(runServerArgs, configT, startedCallback, readyCallback);', + '0.57.0': ' runServer(args, configT);', + }, + 'react-native-desktop': { + '0.0.1': ' _server(argv, config, resolve, reject);', + }, +}; + +function getModuleVersion(modulePath: string) { + return JSON.parse( + fs.readFileSync(path.join(modulePath, 'package.json'), 'utf-8') + ).version; +} + +function getServerFlag(moduleName: string, version: string): string { + const flags = serverFlags[moduleName || 'react-native']; + const versions = Object.keys(flags); + let flag; + for (let i = 0; i < versions.length; i++) { + if (semver.gte(version, versions[i])) { + flag = flags[versions[i]]; + } + } + return flag as string; +} + +export const dir = 'local-cli/server'; +export const file = 'server.js'; +export const fullPath = path.join(exports.dir, exports.file); + +export function inject( + modulePath: string, + options: Options, + moduleName: string +) { + const filePath = path.join(modulePath, exports.fullPath); + if (!fs.existsSync(filePath)) return false; + + const serverFlag = getServerFlag(moduleName, getModuleVersion(modulePath)); + const code = [ + startFlag, + ' require("' + name + '")(' + JSON.stringify(options) + ')', + ' .then(_remotedev =>', + ' _remotedev.on("ready", () => {', + ' if (!_remotedev.portAlreadyUsed) console.log("-".repeat(80));', + ' ' + serverFlag, + ' })', + ' );', + endFlag, + ].join('\n'); + + const serverCode = fs.readFileSync(filePath, 'utf-8'); + let start = serverCode.indexOf(startFlag); // already injected ? + let end = serverCode.indexOf(endFlag) + endFlag.length; + if (start === -1) { + start = serverCode.indexOf(serverFlag); + end = start + serverFlag.length; + } + fs.writeFileSync( + filePath, + serverCode.substr(0, start) + + code + + serverCode.substr(end, serverCode.length) + ); + return true; +} + +export function revert( + modulePath: string, + options: Options, + moduleName: string +) { + const filePath = path.join(modulePath, exports.fullPath); + if (!fs.existsSync(filePath)) return false; + + const serverFlag = getServerFlag(moduleName, getModuleVersion(modulePath)); + const serverCode = fs.readFileSync(filePath, 'utf-8'); + const start = serverCode.indexOf(startFlag); // already injected ? + const end = serverCode.indexOf(endFlag) + endFlag.length; + if (start !== -1) { + fs.writeFileSync( + filePath, + serverCode.substr(0, start) + + serverFlag + + serverCode.substr(end, serverCode.length) + ); + } + return true; +} diff --git a/packages/redux-devtools-cli/bin/openApp.js b/packages/redux-devtools-cli/src/bin/openApp.ts similarity index 62% rename from packages/redux-devtools-cli/bin/openApp.js rename to packages/redux-devtools-cli/src/bin/openApp.ts index f6c47184..1d48ac81 100644 --- a/packages/redux-devtools-cli/bin/openApp.js +++ b/packages/redux-devtools-cli/src/bin/openApp.ts @@ -1,11 +1,13 @@ -var open = require('open'); -var path = require('path'); -var spawn = require('cross-spawn'); +import open from 'open'; +import path from 'path'; +import spawn from 'cross-spawn'; +import { Options } from '../options'; -function openApp(app, options) { +export default function openApp(app: boolean | string, options: Options) { if (app === true || app === 'electron') { try { - var port = options.port ? '--port=' + options.port : ''; + const port = options.port ? `--port=${options.port}` : ''; + // eslint-disable-next-line @typescript-eslint/no-var-requires spawn.sync(require('electron'), [ path.join(__dirname, '..', 'app'), port, @@ -29,10 +31,9 @@ function openApp(app, options) { } return; } + // eslint-disable-next-line @typescript-eslint/no-floating-promises open( - 'http://localhost:' + options.port + '/', - app !== 'browser' ? { app: app } : undefined + `http://localhost:${options.port}/`, + app !== 'browser' ? { app: app as string } : undefined ); } - -module.exports = openApp; diff --git a/packages/redux-devtools-cli/src/bin/redux-devtools.ts b/packages/redux-devtools-cli/src/bin/redux-devtools.ts new file mode 100644 index 00000000..dd8570eb --- /dev/null +++ b/packages/redux-devtools-cli/src/bin/redux-devtools.ts @@ -0,0 +1,97 @@ +#! /usr/bin/env node +import fs from 'fs'; +import path from 'path'; +import parseArgs from 'minimist'; +import chalk from 'chalk'; +import * as injectServer from './injectServer'; +import getOptions from '../options'; +import server from '../index'; +import openApp from './openApp'; + +const argv = parseArgs(process.argv.slice(2)); + +const options = getOptions(argv); + +function readFile(filePath: string) { + return fs.readFileSync(path.resolve(process.cwd(), filePath), 'utf-8'); +} + +if (argv.protocol === 'https') { + argv.key = argv.key ? readFile(argv.key) : null; + argv.cert = argv.cert ? readFile(argv.cert) : null; +} + +function log(pass: boolean, msg: string) { + const prefix = pass ? chalk.green.bgBlack('PASS') : chalk.red.bgBlack('FAIL'); + const color = pass ? chalk.blue : chalk.red; + console.log(prefix, color(msg)); // eslint-disable-line no-console +} + +function getModuleName(type: string) { + switch (type) { + case 'macos': + return 'react-native-macos'; + // react-native-macos is renamed from react-native-desktop + case 'desktop': + return 'react-native-desktop'; + case 'reactnative': + default: + return 'react-native'; + } +} + +function getModulePath(moduleName: string) { + return path.join(process.cwd(), 'node_modules', moduleName); +} + +function getModule(type: string) { + let moduleName = getModuleName(type); + let modulePath = getModulePath(moduleName); + if (type === 'desktop' && !fs.existsSync(modulePath)) { + moduleName = getModuleName('macos'); + modulePath = getModulePath(moduleName); + } + return { + name: moduleName, + path: modulePath, + }; +} + +function injectRN(type: string, msg: string) { + const module = getModule(type); + const fn = type === 'revert' ? injectServer.revert : injectServer.inject; + const pass = fn(module.path, options, module.name); + log( + pass, + msg + + (pass + ? '.' + : ', the file `' + + path.join(module.name, injectServer.fullPath) + + '` not found.') + ); + + process.exit(pass ? 0 : 1); +} + +if (argv.revert) { + injectRN( + argv.revert, + 'Revert injection of ReduxDevTools server from React Native local server' + ); +} +if (argv.injectserver) { + injectRN( + argv.injectserver, + 'Inject ReduxDevTools server into React Native local server' + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +server(argv).then(function (r) { + if (argv.open && argv.open !== 'false') { + r.on('ready', function () { + openApp(argv.open, options); + }); + } +}); diff --git a/packages/redux-devtools-cli/src/db/connector.js b/packages/redux-devtools-cli/src/db/connector.ts similarity index 61% rename from packages/redux-devtools-cli/src/db/connector.js rename to packages/redux-devtools-cli/src/db/connector.ts index 84567527..e162ef1e 100644 --- a/packages/redux-devtools-cli/src/db/connector.js +++ b/packages/redux-devtools-cli/src/db/connector.ts @@ -1,8 +1,9 @@ -var path = require('path'); -var knexModule = require('knex'); +import path from 'path'; +import knexModule from 'knex'; +import { SCServer } from 'socketcluster-server'; -module.exports = function connector(options) { - var dbOptions = options.dbOptions; +export default function connector(options: SCServer.SCServerOptions) { + const dbOptions = options.dbOptions; dbOptions.useNullAsDefault = true; if (!dbOptions.migrate) { return knexModule(dbOptions); @@ -10,13 +11,13 @@ module.exports = function connector(options) { dbOptions.migrations = { directory: path.resolve(__dirname, 'migrations') }; dbOptions.seeds = { directory: path.resolve(__dirname, 'seeds') }; - var knex = knexModule(dbOptions); + const knex = knexModule(dbOptions); /* eslint-disable no-console */ knex.migrate - .latest() + .latest({ loadExtensions: ['.js'] }) .then(function () { - return knex.seed.run(); + return knex.seed.run({ loadExtensions: ['.js'] }); }) .then(function () { console.log(' \x1b[0;32m[Done]\x1b[0m Migrations are finished\n'); @@ -27,4 +28,4 @@ module.exports = function connector(options) { /* eslint-enable no-console */ return knex; -}; +} diff --git a/packages/redux-devtools-cli/src/db/migrations/index.js b/packages/redux-devtools-cli/src/db/migrations/index.ts similarity index 95% rename from packages/redux-devtools-cli/src/db/migrations/index.js rename to packages/redux-devtools-cli/src/db/migrations/index.ts index 6d0beb8d..2795edc3 100644 --- a/packages/redux-devtools-cli/src/db/migrations/index.js +++ b/packages/redux-devtools-cli/src/db/migrations/index.ts @@ -1,4 +1,6 @@ -exports.up = function (knex) { +import type knexModule from 'knex'; + +export function up(knex: knexModule) { return Promise.all([ knex.schema.createTable('remotedev_reports', function (table) { table.uuid('id').primary(); @@ -75,11 +77,11 @@ exports.up = function (knex) { .onUpdate('CASCADE'); }), ]); -}; +} -exports.down = function (knex) { +export function down(knex: knexModule) { return Promise.all([ knex.schema.dropTable('remotedev_reports'), knex.schema.dropTable('remotedev_apps'), ]); -}; +} diff --git a/packages/redux-devtools-cli/src/db/seeds/index.js b/packages/redux-devtools-cli/src/db/seeds/index.ts similarity index 74% rename from packages/redux-devtools-cli/src/db/seeds/index.js rename to packages/redux-devtools-cli/src/db/seeds/index.ts index a829ef67..85eb9266 100644 --- a/packages/redux-devtools-cli/src/db/seeds/index.js +++ b/packages/redux-devtools-cli/src/db/seeds/index.ts @@ -1,4 +1,6 @@ -exports.seed = function (knex) { +import type knexModule from 'knex'; + +export function seed(knex: knexModule) { return Promise.all([knex('remotedev_apps').del()]).then(function () { return Promise.all([ knex('remotedev_apps').insert({ @@ -7,4 +9,4 @@ exports.seed = function (knex) { }), ]); }); -}; +} diff --git a/packages/redux-devtools-cli/src/getport.ts b/packages/redux-devtools-cli/src/getport.ts new file mode 100644 index 00000000..dc6dc87f --- /dev/null +++ b/packages/redux-devtools-cli/src/getport.ts @@ -0,0 +1,6 @@ +declare module 'getport' { + export default function getport( + start: number, + callback: (e: Error | undefined, port: number) => void + ): void; +} diff --git a/packages/redux-devtools-cli/index.js b/packages/redux-devtools-cli/src/index.ts similarity index 53% rename from packages/redux-devtools-cli/index.js rename to packages/redux-devtools-cli/src/index.ts index 71c5cd5b..4b042f89 100644 --- a/packages/redux-devtools-cli/index.js +++ b/packages/redux-devtools-cli/src/index.ts @@ -1,19 +1,29 @@ -var getPort = require('getport'); -var SocketCluster = require('socketcluster'); -var getOptions = require('./src/options'); +import getPort from 'getport'; +import SocketCluster from 'socketcluster'; +import getOptions, { Options } from './options'; // var LOG_LEVEL_NONE = 0; -var LOG_LEVEL_ERROR = 1; -var LOG_LEVEL_WARN = 2; -var LOG_LEVEL_INFO = 3; +const LOG_LEVEL_ERROR = 1; +const LOG_LEVEL_WARN = 2; +const LOG_LEVEL_INFO = 3; -module.exports = function (argv) { - var options = Object.assign(getOptions(argv), { - workerController: __dirname + '/src/worker.js', +export interface ExtendedOptions extends Options { + workerController: string; + allowClientPublish: boolean; +} + +export default function (argv: { + [arg: string]: any; +}): Promise<{ + portAlreadyUsed?: boolean; + on: (status: 'ready', cb: () => void) => void; +}> { + const options = Object.assign(getOptions(argv), { + workerController: __dirname + '/worker.js', allowClientPublish: false, }); - var port = options.port; - var logLevel = + const port = options.port; + const logLevel = options.logLevel === undefined ? LOG_LEVEL_INFO : options.logLevel; return new Promise(function (resolve) { // Check port already used @@ -27,13 +37,11 @@ module.exports = function (argv) { } if (port !== p) { if (logLevel >= LOG_LEVEL_WARN) { - console.log( - '[ReduxDevTools] Server port ' + port + ' is already used.' - ); + console.log(`[ReduxDevTools] Server port ${port} is already used.`); } resolve({ portAlreadyUsed: true, - on: function (status, cb) { + on: function (status: string, cb: () => void) { cb(); }, }); @@ -47,4 +55,4 @@ module.exports = function (argv) { /* eslint-enable no-console */ }); }); -}; +} diff --git a/packages/redux-devtools-cli/src/middleware/graphql.js b/packages/redux-devtools-cli/src/middleware/graphql.ts similarity index 70% rename from packages/redux-devtools-cli/src/middleware/graphql.js rename to packages/redux-devtools-cli/src/middleware/graphql.ts index efd844d1..4bf5b00f 100644 --- a/packages/redux-devtools-cli/src/middleware/graphql.js +++ b/packages/redux-devtools-cli/src/middleware/graphql.ts @@ -1,7 +1,8 @@ -var ApolloServer = require('apollo-server-express').ApolloServer; -var schema = require('../api/schema'); +import { ApolloServer } from 'apollo-server-express'; +import schema from '../api/schema'; +import { Store } from '../store'; -module.exports = function (store) { +export default function (store: Store) { return new ApolloServer({ schema, context: { @@ -24,4 +25,4 @@ module.exports = function (store) { ], }, }); -}; +} diff --git a/packages/redux-devtools-cli/src/options.js b/packages/redux-devtools-cli/src/options.js deleted file mode 100644 index dbf5049e..00000000 --- a/packages/redux-devtools-cli/src/options.js +++ /dev/null @@ -1,33 +0,0 @@ -var path = require('path'); - -module.exports = function getOptions(argv) { - var dbOptions = argv.dbOptions; - if (typeof dbOptions === 'string') { - dbOptions = require(path.resolve(process.cwd(), argv.dbOptions)); - } else if (typeof dbOptions === 'undefined') { - dbOptions = require('../defaultDbOptions.json'); - } - - return { - host: argv.hostname || process.env.npm_package_remotedev_hostname || null, - port: Number(argv.port || process.env.npm_package_remotedev_port) || 8000, - protocol: - argv.protocol || process.env.npm_package_remotedev_protocol || 'http', - protocolOptions: !(argv.protocol === 'https') - ? null - : { - key: argv.key || process.env.npm_package_remotedev_key || null, - cert: argv.cert || process.env.npm_package_remotedev_cert || null, - passphrase: - argv.passphrase || - process.env.npm_package_remotedev_passphrase || - null, - }, - dbOptions: dbOptions, - maxRequestBody: argv.passphrase || '16mb', - logHTTPRequests: argv.logHTTPRequests, - logLevel: argv.logLevel || 3, - wsEngine: - argv.wsEngine || process.env.npm_package_remotedev_wsengine || 'ws', - }; -}; diff --git a/packages/redux-devtools-cli/src/options.ts b/packages/redux-devtools-cli/src/options.ts new file mode 100644 index 00000000..fed19ca5 --- /dev/null +++ b/packages/redux-devtools-cli/src/options.ts @@ -0,0 +1,63 @@ +import path from 'path'; + +interface ProtocolOptions { + key: string | undefined; + cert: string | undefined; + passphrase: string | undefined; +} + +interface DbOptions { + client: string; + connection: { + filename: string; + }; + useNullAsDefault: boolean; + debug: boolean; + migrate: boolean; +} + +export interface Options { + host: string | undefined; + port: number; + protocol: 'http' | 'https'; + protocolOptions: ProtocolOptions | undefined; + dbOptions: DbOptions; + maxRequestBody: string; + logHTTPRequests?: boolean; + logLevel: 0 | 1 | 3 | 2; + wsEngine: string; +} + +export default function getOptions(argv: { [arg: string]: any }): Options { + let dbOptions = argv.dbOptions; + if (typeof dbOptions === 'string') { + dbOptions = require(path.resolve(process.cwd(), argv.dbOptions)); + } else if (typeof dbOptions === 'undefined') { + dbOptions = require('../defaultDbOptions.json'); + } + + return { + host: + argv.hostname || process.env.npm_package_remotedev_hostname || undefined, + port: Number(argv.port || process.env.npm_package_remotedev_port) || 8000, + protocol: + argv.protocol || process.env.npm_package_remotedev_protocol || 'http', + protocolOptions: !(argv.protocol === 'https') + ? undefined + : { + key: argv.key || process.env.npm_package_remotedev_key || undefined, + cert: + argv.cert || process.env.npm_package_remotedev_cert || undefined, + passphrase: + argv.passphrase || + process.env.npm_package_remotedev_passphrase || + undefined, + }, + dbOptions: dbOptions, + maxRequestBody: argv.passphrase || '16mb', + logHTTPRequests: argv.logHTTPRequests, + logLevel: argv.logLevel || 3, + wsEngine: + argv.wsEngine || process.env.npm_package_remotedev_wsengine || 'ws', + }; +} diff --git a/packages/redux-devtools-cli/src/routes.js b/packages/redux-devtools-cli/src/routes.ts similarity index 67% rename from packages/redux-devtools-cli/src/routes.js rename to packages/redux-devtools-cli/src/routes.ts index 8e7e9836..686fa5d8 100644 --- a/packages/redux-devtools-cli/src/routes.js +++ b/packages/redux-devtools-cli/src/routes.ts @@ -1,13 +1,15 @@ -var path = require('path'); -var express = require('express'); -var morgan = require('morgan'); -var bodyParser = require('body-parser'); -var cors = require('cors'); -var graphqlMiddleware = require('./middleware/graphql'); +import path from 'path'; +import express from 'express'; +import morgan from 'morgan'; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import { SCServer } from 'socketcluster-server'; +import graphqlMiddleware from './middleware/graphql'; +import { ReportBaseFields, Store } from './store'; -var app = express.Router(); +const app = express.Router(); -function serveUmdModule(name) { +function serveUmdModule(name: string) { app.use( express.static( path.dirname(require.resolve(name + '/package.json')) + '/umd' @@ -15,9 +17,13 @@ function serveUmdModule(name) { ); } -function routes(options, store, scServer) { - var limit = options.maxRequestBody; - var logHTTPRequests = options.logHTTPRequests; +function routes( + options: SCServer.SCServerOptions, + store: Store, + scServer: SCServer +) { + const limit = options.maxRequestBody; + const logHTTPRequests = options.logHTTPRequests; if (logHTTPRequests) { if (typeof logHTTPRequests === 'object') @@ -25,14 +31,16 @@ function routes(options, store, scServer) { else app.use(morgan('combined')); } - graphqlMiddleware(store).applyMiddleware({ app }); + graphqlMiddleware(store).applyMiddleware({ app } as { + app: express.Application; + }); serveUmdModule('react'); serveUmdModule('react-dom'); serveUmdModule('redux-devtools-core'); app.get('/port.js', function (req, res) { - res.send('reduxDevToolsPort = ' + options.port); + res.send(`reduxDevToolsPort = ${options.port!}`); }); app.get('*', function (req, res) { res.sendFile(path.join(__dirname, '../app/index.html')); @@ -71,7 +79,10 @@ function routes(options, store, scServer) { store .add(req.body) .then(function (r) { - res.send({ id: r.id, error: r.error }); + res.send({ + id: (r as ReportBaseFields).id, + error: (r as { error: string }).error, + }); scServer.exchange.publish('report', { type: 'add', data: r, @@ -86,4 +97,4 @@ function routes(options, store, scServer) { return app; } -module.exports = routes; +export default routes; diff --git a/packages/redux-devtools-cli/src/store.js b/packages/redux-devtools-cli/src/store.js deleted file mode 100644 index 3d2df8f4..00000000 --- a/packages/redux-devtools-cli/src/store.js +++ /dev/null @@ -1,106 +0,0 @@ -var { v4: uuidV4 } = require('uuid'); -var pick = require('lodash/pick'); -var connector = require('./db/connector'); - -var reports = 'remotedev_reports'; -// var payloads = 'remotedev_payloads'; -var knex; - -var baseFields = ['id', 'title', 'added']; - -function error(msg) { - return new Promise(function (resolve) { - return resolve({ error: msg }); - }); -} - -function list(query, fields) { - var r = knex.select(fields || baseFields).from(reports); - if (query) return r.where(query); - return r; -} - -function listAll(query) { - var r = knex.select().from(reports); - if (query) return r.where(query); - return r; -} - -function get(id) { - if (!id) return error('No id specified.'); - - return knex(reports).where('id', id).first(); -} - -function add(data) { - if (!data.type || !data.payload) { - return error("Required parameters aren't specified."); - } - if (data.type !== 'ACTIONS' && data.type !== 'STATE') { - return error('Type ' + data.type + ' is not supported yet.'); - } - - var reportId = uuidV4(); - var report = { - id: reportId, - type: data.type, - title: - data.title || (data.exception && data.exception.message) || data.action, - description: data.description, - action: data.action, - payload: data.payload, - preloadedState: data.preloadedState, - screenshot: data.screenshot, - version: data.version, - userAgent: data.userAgent, - user: data.user, - userId: typeof data.user === 'object' ? data.user.id : data.user, - instanceId: data.instanceId, - meta: data.meta, - exception: composeException(data.exception), - added: new Date().toISOString(), - }; - if (data.appId) report.appId = data.appId; // TODO check if the id exists and we have access to link it - /* - var payload = { - id: uuid.v4(), - reportId: reportId, - state: data.payload - }; - */ - - return knex - .insert(report) - .into(reports) - .then(function () { - return byBaseFields(report); - }); -} - -function byBaseFields(data) { - return pick(data, baseFields); -} - -function createStore(options) { - knex = connector(options); - - return { - list: list, - listAll: listAll, - get: get, - add: add, - }; -} - -function composeException(exception) { - var message = ''; - - if (exception) { - message = 'Exception thrown: '; - if (exception.message) message += exception.message; - if (exception.stack) message += '\n' + exception.stack; - } - return message; -} - -module.exports = createStore; diff --git a/packages/redux-devtools-cli/src/store.ts b/packages/redux-devtools-cli/src/store.ts new file mode 100644 index 00000000..01f3f61f --- /dev/null +++ b/packages/redux-devtools-cli/src/store.ts @@ -0,0 +1,164 @@ +import { v4 as uuidV4 } from 'uuid'; +import pick from 'lodash/pick'; +import { SCServer } from 'socketcluster-server'; +import knexModule from 'knex'; +import connector from './db/connector'; + +const reports = 'remotedev_reports'; +// var payloads = 'remotedev_payloads'; +let knex: knexModule; + +const baseFields = ['id', 'title', 'added']; + +function error(msg: string): Promise<{ error: string }> { + return new Promise(function (resolve) { + return resolve({ error: msg }); + }); +} + +type ReportType = 'STATE' | 'ACTION' | 'STATES' | 'ACTIONS'; + +interface Report { + id: string; + type: ReportType | null; + title: string | null; + description: string | null; + action: string | null; + payload: string; + preloadedState: string | null; + screenshot: string | null; + userAgent: string | null; + version: string | null; + userId: string | null; + user: string | null; + meta: string | null; + exception: string | null; + instanceId: string | null; + added: string | null; + appId?: string | null; +} + +export interface ReportBaseFields { + id: string; + title: string | null; + added: string | null; +} + +function list(query?: unknown, fields?: string[]): Promise { + const r = knex.select(fields || baseFields).from(reports); + if (query) return r.where(query); + return r; +} + +function listAll(query: unknown): Promise { + const r = knex.select().from(reports); + if (query) return r.where(query); + return r; +} + +function get(id: string): Promise { + if (!id) return error('No id specified.'); + + return knex(reports).where('id', id).first(); +} + +interface AddData { + type: ReportType | null; + title: string | null; + description: string | null; + action: string | null; + payload: string; + preloadedState: string | null; + screenshot: string | null; + version: string | null; + userAgent: string | null; + userId: string | null; + user: { id: string } | string | null; + instanceId: string | null; + meta: string | null; + exception?: Error; + appId?: string | null; +} + +function add(data: AddData): Promise { + if (!data.type || !data.payload) { + return error("Required parameters aren't specified."); + } + if (data.type !== 'ACTIONS' && data.type !== 'STATE') { + return error('Type ' + data.type + ' is not supported yet.'); + } + + const reportId = uuidV4(); + const report: Report = { + id: reportId, + type: data.type, + title: + data.title || (data.exception && data.exception.message) || data.action, + description: data.description, + action: data.action, + payload: data.payload, + preloadedState: data.preloadedState, + screenshot: data.screenshot, + version: data.version, + userAgent: data.userAgent, + user: data.user as string, + userId: + typeof data.user === 'object' + ? (data.user as { id: string }).id + : data.user, + instanceId: data.instanceId, + meta: data.meta, + exception: composeException(data.exception), + added: new Date().toISOString(), + }; + if (data.appId) report.appId = data.appId; // TODO check if the id exists and we have access to link it + /* + var payload = { + id: uuid.v4(), + reportId: reportId, + state: data.payload + }; + */ + + return knex + .insert(report) + .into(reports) + .then(function () { + return byBaseFields(report); + }); +} + +function byBaseFields(data: Report): ReportBaseFields { + return pick(data, baseFields) as ReportBaseFields; +} + +export interface Store { + list: (query?: unknown, fields?: string[]) => Promise; + listAll: (query?: unknown) => Promise; + get: (id: string) => Promise; + add: (data: AddData) => Promise; +} + +function createStore(options: SCServer.SCServerOptions): Store { + knex = connector(options); + + return { + list: list, + listAll: listAll, + get: get, + add: add, + }; +} + +function composeException(exception: Error | undefined) { + let message = ''; + + if (exception) { + message = 'Exception thrown: '; + if (exception.message) message += exception.message; + if (exception.stack) message += '\n' + exception.stack; + } + return message; +} + +export default createStore; diff --git a/packages/redux-devtools-cli/src/utils/requireSchema.js b/packages/redux-devtools-cli/src/utils/requireSchema.js deleted file mode 100644 index 08d0a75b..00000000 --- a/packages/redux-devtools-cli/src/utils/requireSchema.js +++ /dev/null @@ -1,6 +0,0 @@ -var fs = require('fs'); - -module.exports = function (name, require) { - return fs.readFileSync(require.resolve(name)).toString(); - // return GraphQL.buildSchema(schema); -}; diff --git a/packages/redux-devtools-cli/src/worker.js b/packages/redux-devtools-cli/src/worker.ts similarity index 72% rename from packages/redux-devtools-cli/src/worker.js rename to packages/redux-devtools-cli/src/worker.ts index 3bf0929a..4c1e3fa3 100644 --- a/packages/redux-devtools-cli/src/worker.js +++ b/packages/redux-devtools-cli/src/worker.ts @@ -1,23 +1,24 @@ -var SCWorker = require('socketcluster/scworker'); -var express = require('express'); -var app = express(); -var routes = require('./routes'); -var createStore = require('./store'); +import SCWorker from 'socketcluster/scworker'; +import express from 'express'; +import routes from './routes'; +import createStore from './store'; + +const app = express(); class Worker extends SCWorker { run() { - var httpServer = this.httpServer; - var scServer = this.scServer; - var options = this.options; - var store = createStore(options); + const httpServer = this.httpServer; + const scServer = this.scServer; + const options = this.options; + const store = createStore(options); httpServer.on('request', app); app.use(routes(options, store, scServer)); scServer.addMiddleware(scServer.MIDDLEWARE_EMIT, function (req, next) { - var channel = req.event; - var data = req.data; + const channel = req.event; + const data = req.data; if ( channel.substr(0, 3) === 'sc-' || channel === 'respond' || @@ -36,7 +37,7 @@ class Worker extends SCWorker { store .list() .then(function (data) { - req.socket.emit(req.channel, { type: 'list', data: data }); + req.socket.emit(req.channel!, { type: 'list', data: data }); }) .catch(function (error) { console.error(error); // eslint-disable-line no-console @@ -45,8 +46,8 @@ class Worker extends SCWorker { }); scServer.on('connection', function (socket) { - var channelToWatch, channelToEmit; - socket.on('login', function (credentials, respond) { + let channelToWatch: string, channelToEmit: string; + socket.on('login', function (this: Worker, credentials, respond) { if (credentials === 'master') { channelToWatch = 'respond'; channelToEmit = 'log'; @@ -69,8 +70,8 @@ class Worker extends SCWorker { console.error(error); // eslint-disable-line no-console }); }); - socket.on('disconnect', function () { - var channel = this.exchange.channel('sc-' + socket.id); + socket.on('disconnect', function (this: Worker) { + const channel = this.exchange.channel('sc-' + socket.id); channel.unsubscribe(); channel.destroy(); scServer.exchange.publish(channelToEmit, { diff --git a/packages/redux-devtools-cli/test/integration.spec.js b/packages/redux-devtools-cli/test/integration.spec.js deleted file mode 100644 index c70556e3..00000000 --- a/packages/redux-devtools-cli/test/integration.spec.js +++ /dev/null @@ -1,199 +0,0 @@ -var childProcess = require('child_process'); -var request = require('supertest'); -var scClient = require('socketcluster-client'); - -describe('Server', function () { - var scServer; - beforeAll(function (done) { - scServer = childProcess.fork(__dirname + '/../bin/redux-devtools.js'); - setTimeout(done, 3000); - }); - - afterAll(function () { - if (scServer) { - scServer.kill(); - } - }); - - describe('Express backend', function () { - it('loads main page', function (done) { - request('http://localhost:8000') - .get('/') - .expect('Content-Type', /text\/html/) - .expect(200) - .then(function (res) { - expect(res.text).toMatch(/Redux DevTools<\/title>/); - done(); - }); - }); - - it('resolves an inexistent url', function (done) { - request('http://localhost:8000/jreerfr/123') - .get('/') - .expect('Content-Type', /text\/html/) - .expect(200, done); - }); - }); - - describe('Realtime monitoring', function () { - var socket, socket2, channel; - beforeAll(function () { - socket = scClient.connect({ hostname: 'localhost', port: 8000 }); - socket.connect(); - socket.on('error', function (error) { - console.error('Socket1 error', error); // eslint-disable-line no-console - }); - socket2 = scClient.connect({ hostname: 'localhost', port: 8000 }); - socket2.connect(); - socket.on('error', function (error) { - console.error('Socket2 error', error); // eslint-disable-line no-console - }); - }); - - afterAll(function () { - socket.disconnect(); - socket2.disconnect(); - }); - - it('should connect', function (done) { - socket.on('connect', function (status) { - expect(status.id).toBeTruthy(); - done(); - }); - }); - - it('should login', function () { - socket.emit('login', 'master', function (error, channelName) { - if (error) { - /* eslint-disable-next-line no-console */ - console.log(error); - return; - } - expect(channelName).toBe('respond'); - channel = socket.subscribe(channelName); - expect(channel.SUBSCRIBED).toBe('subscribed'); - }); - }); - - it('should send message', function (done) { - var data = { - type: 'ACTION', - payload: { - todos: 'do some', - }, - action: { - timestamp: 1483349708506, - action: { - type: 'ADD_TODO', - text: 'hggg', - }, - }, - instanceId: 'tAmA7H5fclyWhvizAAAi', - name: 'LoggerInstance', - id: 'tAmA7H5fclyWhvizAAAi', - }; - - socket2.emit('login', '', function (error, channelName) { - if (error) { - /* eslint-disable-next-line no-console */ - console.log(error); - return; - } - expect(channelName).toBe('log'); - var channel2 = socket2.subscribe(channelName); - expect(channel2.SUBSCRIBED).toBe('subscribed'); - channel2.on('subscribe', function () { - channel2.watch(function (message) { - expect(message).toEqual(data); - done(); - }); - socket.emit(channelName, data); - }); - }); - }); - }); - - describe('REST backend', function () { - var id; - var report = { - type: 'ACTIONS', - title: 'Test report', - description: 'Test body report', - action: 'SOME_FINAL_ACTION', - payload: '[{"type":"ADD_TODO","text":"hi"},{"type":"SOME_FINAL_ACTION"}]', - preloadedState: - '{"todos":[{"text":"Use Redux","completed":false,"id":0}]}', - userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) ' + - 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36', - }; - it('should add a report', function (done) { - request('http://localhost:8000') - .post('/') - .send(report) - .set('Accept', 'application/json') - .expect('Content-Type', /application\/json/) - .expect(200) - .then(function (res) { - id = res.body.id; - expect(id).toBeTruthy(); - done(); - }); - }); - - it('should get the report', function (done) { - request('http://localhost:8000') - .post('/') - .send({ - op: 'get', - id: id, - }) - .set('Accept', 'application/json') - .expect('Content-Type', /application\/json/) - .expect(200) - .then(function (res) { - expect.objectContaining(res.body, report); - done(); - }); - }); - - it('should list reports', function (done) { - request('http://localhost:8000') - .post('/') - .send({ - op: 'list', - }) - .set('Accept', 'application/json') - .expect('Content-Type', /application\/json/) - .expect(200) - .then(function (res) { - expect(res.body.length).toBe(1); - expect(res.body[0].id).toBe(id); - expect(res.body[0].title).toBe('Test report'); - expect(res.body[0].added).toBeTruthy(); - done(); - }); - }); - }); - - describe('GraphQL backend', function () { - it('should get the report', function (done) { - request('http://localhost:8000') - .post('/graphql') - .send({ - query: '{ reports { id, type, title } }', - }) - .set('Accept', 'application/json') - .expect('Content-Type', /application\/json/) - .expect(200) - .then(function (res) { - var reports = res.body.data.reports; - expect(reports.length).toBe(1); - expect(reports[0].id).toBeTruthy(); - expect(reports[0].title).toBe('Test report'); - expect(reports[0].type).toBe('ACTIONS'); - done(); - }); - }); - }); -}); diff --git a/packages/redux-devtools-cli/test/integration.spec.ts b/packages/redux-devtools-cli/test/integration.spec.ts new file mode 100644 index 00000000..ed2c04c6 --- /dev/null +++ b/packages/redux-devtools-cli/test/integration.spec.ts @@ -0,0 +1,241 @@ +import childProcess from 'child_process'; +import request from 'supertest'; +import scClient from 'socketcluster-client'; + +describe('Server', function () { + let scServer: childProcess.ChildProcess; + beforeAll(function (done) { + scServer = childProcess.fork(__dirname + '/../bin/redux-devtools.js'); + setTimeout(done, 3000); + }); + + afterAll(function () { + if (scServer) { + scServer.kill(); + } + }); + + describe('Express backend', function () { + it('loads main page', function () { + return new Promise((done) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + request('http://localhost:8000') + .get('/') + .expect('Content-Type', /text\/html/) + .expect(200) + .then(function (res: { text: string }) { + expect(res.text).toMatch(/<title>Redux DevTools<\/title>/); + done(); + }); + }); + }); + + it('resolves an inexistent url', function () { + return new Promise((done) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + request('http://localhost:8000/jreerfr/123') + .get('/') + .expect('Content-Type', /text\/html/) + .expect(200, done); + }); + }); + }); + + describe('Realtime monitoring', function () { + let socket: scClient.SCClientSocket, + socket2: scClient.SCClientSocket, + channel; + beforeAll(function () { + socket = scClient.connect({ hostname: 'localhost', port: 8000 }); + socket.connect(); + socket.on('error', function (error) { + console.error('Socket1 error', error); // eslint-disable-line no-console + }); + socket2 = scClient.connect({ hostname: 'localhost', port: 8000 }); + socket2.connect(); + socket.on('error', function (error) { + console.error('Socket2 error', error); // eslint-disable-line no-console + }); + }); + + afterAll(function () { + socket.disconnect(); + socket2.disconnect(); + }); + + it('should connect', function () { + return new Promise((done) => { + socket.on('connect', function (status) { + expect(status.id).toBeTruthy(); + done(); + }); + }); + }); + + it('should login', function () { + socket.emit('login', 'master', function ( + error: Error | undefined, + channelName: string + ) { + if (error) { + /* eslint-disable-next-line no-console */ + console.log(error); + return; + } + expect(channelName).toBe('respond'); + channel = socket.subscribe(channelName); + expect(channel.SUBSCRIBED).toBe('subscribed'); + }); + }); + + it('should send message', function () { + return new Promise((done) => { + const data = { + type: 'ACTION', + payload: { + todos: 'do some', + }, + action: { + timestamp: 1483349708506, + action: { + type: 'ADD_TODO', + text: 'hggg', + }, + }, + instanceId: 'tAmA7H5fclyWhvizAAAi', + name: 'LoggerInstance', + id: 'tAmA7H5fclyWhvizAAAi', + }; + + socket2.emit('login', '', function ( + error: Error | undefined, + channelName: string + ) { + if (error) { + /* eslint-disable-next-line no-console */ + console.log(error); + return; + } + expect(channelName).toBe('log'); + const channel2 = socket2.subscribe(channelName); + expect(channel2.SUBSCRIBED).toBe('subscribed'); + channel2.on('subscribe', function () { + channel2.watch(function (message) { + expect(message).toEqual(data); + done(); + }); + socket.emit(channelName, data); + }); + }); + }); + }); + }); + + describe('REST backend', function () { + let id: string; + const report = { + type: 'ACTIONS', + title: 'Test report', + description: 'Test body report', + action: 'SOME_FINAL_ACTION', + payload: '[{"type":"ADD_TODO","text":"hi"},{"type":"SOME_FINAL_ACTION"}]', + preloadedState: + '{"todos":[{"text":"Use Redux","completed":false,"id":0}]}', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36', + }; + it('should add a report', function () { + return new Promise((done) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + request('http://localhost:8000') + .post('/') + .send(report) + .set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200) + .then(function (res: { body: { id: string } }) { + id = res.body.id; + expect(id).toBeTruthy(); + done(); + }); + }); + }); + + it('should get the report', function () { + return new Promise((done) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + request('http://localhost:8000') + .post('/') + .send({ + op: 'get', + id: id, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200) + .then(function (res: { body: unknown }) { + expect(res.body).toMatchObject(report); + done(); + }); + }); + }); + + it('should list reports', function () { + return new Promise((done) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + request('http://localhost:8000') + .post('/') + .send({ + op: 'list', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200) + .then(function (res: { + body: { id: string; title: string | null; added: string | null }[]; + }) { + expect(res.body).toHaveLength(1); + expect(res.body[0].id).toBe(id); + expect(res.body[0].title).toBe('Test report'); + expect(res.body[0].added).toBeTruthy(); + done(); + }); + }); + }); + }); + + describe('GraphQL backend', function () { + it('should get the report', function () { + return new Promise((done) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + request('http://localhost:8000') + .post('/graphql') + .send({ + query: '{ reports { id, type, title } }', + }) + .set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200) + .then(function (res: { + body: { + data: { + reports: { + id: string; + title: string | null; + type: string | null; + }[]; + }; + }; + }) { + const reports = res.body.data.reports; + expect(reports).toHaveLength(1); + expect(reports[0].id).toBeTruthy(); + expect(reports[0].title).toBe('Test report'); + expect(reports[0].type).toBe('ACTIONS'); + done(); + }); + }); + }); + }); +}); diff --git a/packages/redux-devtools-cli/test/tsconfig.json b/packages/redux-devtools-cli/test/tsconfig.json new file mode 100644 index 00000000..b55532d2 --- /dev/null +++ b/packages/redux-devtools-cli/test/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["../src", "."] +} diff --git a/packages/redux-devtools-cli/tsconfig.json b/packages/redux-devtools-cli/tsconfig.json new file mode 100644 index 00000000..1e20cd95 --- /dev/null +++ b/packages/redux-devtools-cli/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src"] +} diff --git a/yarn.lock b/yarn.lock index 80e9176d..97717fda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3493,6 +3493,11 @@ resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.3.tgz#0aa116701955c2faa0717fc69cd1596095e49d96" integrity sha512-P1bffQfhD3O4LW0ioENXUhZ9OIa0Zn+P7M+pWgkCKaT53wVLSq0mrKksCID/FGHpFhRSxRGhgrQmfhRuzwtKdg== +"@types/cookiejar@*": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" + integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== + "@types/cookies@*": version "0.7.4" resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.7.4.tgz#26dedf791701abc0e36b5b79a5722f40e455f87b" @@ -3510,6 +3515,13 @@ dependencies: "@types/express" "*" +"@types/cross-spawn@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.2.tgz#168309de311cd30a2b8ae720de6475c2fbf33ac7" + integrity sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw== + dependencies: + "@types/node" "*" + "@types/d3@^3.5.43": version "3.5.43" resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.43.tgz#e9b4992817e0b6c5efaa7d6e5bb2cee4d73eab58" @@ -3859,6 +3871,13 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= +"@types/morgan@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.1.tgz#6457872df95647c1dbc6b3741e8146b71ece74bf" + integrity sha512-2j5IKrgJpEP6xw/uiVb2Xfga0W0sSVD9JP9t7EZLvpBENdB0OKgcnoKS8IsjNeNnZ/86robdZ61Orl0QCFGOXg== + dependencies: + "@types/node" "*" + "@types/ncom@*": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/ncom/-/ncom-1.0.0.tgz#8e33d06fc4914c941ba40ceca042081b947ba699" @@ -4116,6 +4135,11 @@ resolved "https://registry.yarnpkg.com/@types/sc-errors/-/sc-errors-1.4.0.tgz#dba1309b695ee8aafc3f574dfedfe4f3c5153419" integrity sha512-WfBEiw/SVja1ZvJRdt37dOJFxp2llV35n9cPiDCDsFRSvTTYlO4iMFg+NyeEhiWBk1O4bvmyYpAEYzJx1DbHHQ== +"@types/semver@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb" + integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ== + "@types/serve-static@*": version "1.13.5" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.5.tgz#3d25d941a18415d3ab092def846e135a08bbcf53" @@ -4186,6 +4210,21 @@ "@types/react-native" "*" csstype "^3.0.2" +"@types/superagent@*": + version "4.1.10" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-4.1.10.tgz#5e2cc721edf58f64fe9b819f326ee74803adee86" + integrity sha512-xAgkb2CMWUMCyVc/3+7iQfOEBE75NvuZeezvmixbUw3nmENf2tCnQkW5yQLTYqvXUQ+R6EXxdqKKbal2zM5V/g== + dependencies: + "@types/cookiejar" "*" + "@types/node" "*" + +"@types/supertest@^2.0.10": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.10.tgz#630d79b4d82c73e043e43ff777a9ca98d457cab7" + integrity sha512-Xt8TbEyZTnD5Xulw95GLMOkmjGICrOQyJ2jqgkSjAUR3mm7pAIzSR0NFBaMcwlzVvlpCjNwbATcWWwjNiZiFrQ== + dependencies: + "@types/superagent" "*" + "@types/tapable@*", "@types/tapable@^1.0.5": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" @@ -4210,6 +4249,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== + "@types/warning@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" @@ -9656,6 +9700,13 @@ graphql-upload@^8.0.2: http-errors "^1.7.3" object-path "^0.11.4" +graphql@^14.7.0: + version "14.7.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72" + integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA== + dependencies: + iterall "^1.2.2" + graphql@^15.3.0: version "15.3.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" @@ -11028,7 +11079,7 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterall@^1.1.3, iterall@^1.2.1: +iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2: version "1.3.0" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== @@ -13074,6 +13125,11 @@ ncom@^1.0.2: dependencies: sc-formatter "~3.0.1" +ncp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" + integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= + nearley@^2.7.10: version "2.19.5" resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.5.tgz#6be78e4942eeb9a043b17c563413111d4ad849b7"