chore(cli): convert to TypeScript (#656)

* chore(cli): convert to TypeScript

* Fix knex usage

* Fix eslintrc

* Ignore dist
This commit is contained in:
Nathan Bierema 2020-10-26 01:57:34 -04:00 committed by GitHub
parent ee52c29a8d
commit c91471a41d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 934 additions and 643 deletions

View File

@ -0,0 +1,2 @@
dist
umd

View File

@ -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'],
},
},
],
};

View File

@ -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;
};

93
packages/redux-devtools-cli/bin/redux-devtools.js Executable file → Normal file
View File

@ -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');

View File

@ -0,0 +1,3 @@
module.exports = {
preset: 'ts-jest',
};

View File

@ -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 <zalmoxisus@gmail.com> (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 <zalmoxisus@gmail.com> (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"
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
});
}
});

View File

@ -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;
};
}

View File

@ -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'),
]);
};
}

View File

@ -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) {
}),
]);
});
};
}

View File

@ -0,0 +1,6 @@
declare module 'getport' {
export default function getport(
start: number,
callback: (e: Error | undefined, port: number) => void
): void;
}

View File

@ -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 */
});
});
};
}

View File

@ -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) {
],
},
});
};
}

View File

@ -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',
};
};

View File

@ -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',
};
}

View File

@ -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;

View File

@ -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;

View File

@ -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<ReportBaseFields[]> {
const r = knex.select(fields || baseFields).from(reports);
if (query) return r.where(query);
return r;
}
function listAll(query: unknown): Promise<Report[]> {
const r = knex.select().from(reports);
if (query) return r.where(query);
return r;
}
function get(id: string): Promise<Report | { error: string }> {
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<ReportBaseFields | { error: string }> {
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<ReportBaseFields[]>;
listAll: (query?: unknown) => Promise<Report[]>;
get: (id: string) => Promise<Report | { error: string }>;
add: (data: AddData) => Promise<ReportBaseFields | { error: string }>;
}
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;

View File

@ -1,6 +0,0 @@
var fs = require('fs');
module.exports = function (name, require) {
return fs.readFileSync(require.resolve(name)).toString();
// return GraphQL.buildSchema(schema);
};

View File

@ -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, {

View File

@ -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(/<title>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();
});
});
});
});

View File

@ -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();
});
});
});
});
});

View File

@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["../src", "."]
}

View File

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"]
}

View File

@ -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"