feat(server): simplified test running & IDE integrations

This commit is contained in:
Kristaps Fabians Geikins
2022-03-16 17:31:53 +02:00
parent 8010bf3c4c
commit b157a98901
28 changed files with 59806 additions and 59555 deletions
+7 -1
View File
@@ -3,7 +3,13 @@
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "octref.vetur"],
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"octref.vetur",
"hbenl.vscode-mocha-test-adapter",
"ryanluker.vscode-coverage-gutters"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}
+1
View File
@@ -13,6 +13,7 @@
"source.fixAll.eslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"search.useParentIgnoreFiles": true,
"cSpell.enableFiletypes": ["vue-html", "vue-postcss"],
"cSpell.userWords": ["Matomo", "SUPPRESSMSGBOXES", "matomo"],
"cSpell.words": ["mixpanel"]
-56
View File
@@ -10,7 +10,6 @@
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"graphql-tag": "^2.11.0",
"lerna": "^3.22.1",
"prettier": "^2.5.1"
}
@@ -5610,37 +5609,6 @@
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
"dev": true
},
"node_modules/graphql": {
"version": "16.3.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.3.0.tgz",
"integrity": "sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==",
"dev": true,
"peer": true,
"engines": {
"node": "^12.22.0 || ^14.16.0 || >=16.0.0"
}
},
"node_modules/graphql-tag": {
"version": "2.12.6",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
"integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==",
"dev": true,
"dependencies": {
"tslib": "^2.1.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
}
},
"node_modules/graphql-tag/node_modules/tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
},
"node_modules/handlebars": {
"version": "4.7.7",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
@@ -15311,30 +15279,6 @@
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
"dev": true
},
"graphql": {
"version": "16.3.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.3.0.tgz",
"integrity": "sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==",
"dev": true,
"peer": true
},
"graphql-tag": {
"version": "2.12.6",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
"integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
},
"dependencies": {
"tslib": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"dev": true
}
}
},
"handlebars": {
"version": "4.7.7",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
-1
View File
@@ -10,7 +10,6 @@
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1",
"graphql-tag": "^2.11.0",
"lerna": "^3.22.1"
},
"config": {
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -29,6 +29,8 @@
"devDependencies": {
"cross-env": "^7.0.3",
"nodemon": "^2.0.13",
"eslint": "^8.11.0"
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1"
}
}
+4 -4
View File
@@ -47,17 +47,17 @@
"@vue/cli-plugin-vuex": "~4.3.0",
"@vue/cli-service": "~4.3.0",
"babel-eslint": "^10.1.0",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-vue": "^8.5.0",
"graphql-tag": "^2.11.0",
"prettier": "^2.5.1",
"sass": "~1.32.6",
"sass-loader": "^8.0.0",
"vue-cli-plugin-apollo": "~0.21.3",
"vue-cli-plugin-vuetify": "^2.0.8",
"vue-template-compiler": "^2.6.12",
"vuetify-loader": "^1.6.0"
"vuetify-loader": "^1.6.0",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1"
}
}
+42
View File
@@ -10,6 +10,8 @@
"license": "Apache-2.0",
"devDependencies": {
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1",
"undici": "^4.14.1"
}
},
@@ -298,6 +300,18 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-scope": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
@@ -729,6 +743,21 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -1134,6 +1163,13 @@
"v8-compile-cache": "^2.0.3"
}
},
"eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"requires": {}
},
"eslint-scope": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
@@ -1468,6 +1504,12 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
"prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+3 -1
View File
@@ -23,6 +23,8 @@
"license": "Apache-2.0",
"devDependencies": {
"undici": "^4.14.1",
"eslint": "^8.11.0"
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1"
}
}
+13546 -13550
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -35,13 +35,15 @@
},
"devDependencies": {
"@babel/core": "^7.17.5",
"eslint": "^8.11.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^4.0.0-alpha.0",
"html-webpack-plugin": "^5.3.1",
"nodemon": "^2.0.7",
"webpack": "^5.69.1",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^4.6.0"
"webpack-dev-server": "^4.6.0",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1"
}
}
+7
View File
@@ -0,0 +1,7 @@
###############################################
# Env overrides when running app in test mode #
###############################################
PORT=0
POSTGRES_URL=postgres://speckle:speckle@localhost/speckle2_test
POSTGRES_USER=''
+8 -29
View File
@@ -1,33 +1,12 @@
'use strict'
const fs = require( 'fs' )
const path = require( 'path' )
function walk( dir ) {
let results = [ ]
let list = fs.readdirSync( dir )
list.forEach( function ( file ) {
let fullFile = path.join( dir, file )
let stat = fs.statSync( fullFile )
if ( stat && stat.isDirectory( ) ) {
if ( file === 'tests' )
results.push( fullFile )
else
results = results.concat( walk( fullFile ) )
}
} )
return results
/** @type {import("mocha").MochaOptions} */
const config = {
spec: ['modules/**/*.spec.js'],
require: 'test/hooks.js',
slow: 0,
timeout: '10000',
exit: true
}
let testDirs = walk( './modules' )
console.log( '\n' )
console.log( `📝 Found ${testDirs.length} test dirs:` )
console.log( testDirs )
console.log( '\n' )
module.exports = {
spec: testDirs,
require: 'test/hooks.js'
}
module.exports = config
+3
View File
@@ -0,0 +1,3 @@
{
"mochaExplorer.env": { "NODE_ENV": "test" }
}
+99 -82
View File
@@ -1,29 +1,28 @@
/* istanbul ignore file */
'use strict'
const http = require( 'http' )
const url = require( 'url' )
const express = require( 'express' )
const compression = require( 'compression' )
const appRoot = require( 'app-root-path' )
const logger = require( 'morgan-debug' )
const bodyParser = require( 'body-parser' )
const path = require( 'path' )
const debug = require( 'debug' )
const { createTerminus } = require( '@godaddy/terminus' )
require('./bootstrap')
const http = require('http')
const url = require('url')
const express = require('express')
const compression = require('compression')
const appRoot = require('app-root-path')
const logger = require('morgan-debug')
const bodyParser = require('body-parser')
const path = require('path')
const debug = require('debug')
const { createTerminus } = require('@godaddy/terminus')
const Sentry = require( '@sentry/node' )
const Tracing = require( '@sentry/tracing' )
const Logging = require( `${appRoot}/logging` )
const { startup: MatStartup } = require( `${appRoot}/logging/matomoHelper` )
const prometheusClient = require( 'prom-client' )
const Sentry = require('@sentry/node')
const Tracing = require('@sentry/tracing')
const Logging = require(`${appRoot}/logging`)
const { startup: MatStartup } = require(`${appRoot}/logging/matomoHelper`)
const prometheusClient = require('prom-client')
const { ApolloServer, ForbiddenError } = require( 'apollo-server-express' )
const { ApolloServer, ForbiddenError } = require('apollo-server-express')
require( 'dotenv' ).config( { path: `${appRoot}/.env` } )
const { contextApiTokenHelper } = require( './modules/shared' )
const knex = require( './db/knex' )
const { contextApiTokenHelper } = require('./modules/shared')
const knex = require('./db/knex')
let graphqlServer
@@ -31,10 +30,10 @@ let graphqlServer
* Initialises the express application together with the graphql server middleware.
* @return {[type]} an express application and the graphql server
*/
exports.init = async ( ) => {
const app = express( )
exports.init = async () => {
const app = express()
Logging( app )
Logging(app)
MatStartup()
// Initialise prometheus metrics
@@ -43,98 +42,112 @@ exports.init = async ( ) => {
// Moves things along automatically on restart.
// Should perhaps be done manually?
await knex.migrate.latest( )
await knex.migrate.latest()
if ( process.env.NODE_ENV !== 'test' ) {
app.use( logger( 'speckle', 'dev', {} ) )
if (process.env.NODE_ENV !== 'test') {
app.use(logger('speckle', 'dev', {}))
}
if ( process.env.COMPRESSION ) {
app.use( compression( ) )
if (process.env.COMPRESSION) {
app.use(compression())
}
app.use( bodyParser.json( { limit: '100mb' } ) )
app.use( bodyParser.urlencoded( { limit: '100mb', extended: false } ) )
app.use(bodyParser.json({ limit: '100mb' }))
app.use(bodyParser.urlencoded({ limit: '100mb', extended: false }))
const { init, graph } = require( './modules' )
const { init, graph } = require('./modules')
// Initialise default modules, including rest api handlers
await init( app )
await init(app)
// Initialise graphql server
const metricConnectCounter = new prometheusClient.Counter( { name: 'speckle_server_apollo_connect', help: 'Number of connects' } )
const metricConnectedClients = new prometheusClient.Gauge( { name: 'speckle_server_apollo_clients', help: 'Number of currently connected clients' } )
graphqlServer = new ApolloServer( {
...graph( ),
const metricConnectCounter = new prometheusClient.Counter({
name: 'speckle_server_apollo_connect',
help: 'Number of connects'
})
const metricConnectedClients = new prometheusClient.Gauge({
name: 'speckle_server_apollo_clients',
help: 'Number of currently connected clients'
})
graphqlServer = new ApolloServer({
...graph(),
context: contextApiTokenHelper,
subscriptions: {
onConnect: ( connectionParams, webSocket, context ) => {
onConnect: (connectionParams, webSocket, context) => {
metricConnectCounter.inc()
metricConnectedClients.inc()
try {
if ( connectionParams.Authorization || connectionParams.authorization || connectionParams.headers.Authorization ) {
let header = connectionParams.Authorization || connectionParams.authorization || connectionParams.headers.Authorization
let token = header.split( ' ' )[ 1 ]
if (
connectionParams.Authorization ||
connectionParams.authorization ||
connectionParams.headers.Authorization
) {
let header =
connectionParams.Authorization ||
connectionParams.authorization ||
connectionParams.headers.Authorization
let token = header.split(' ')[1]
return { token: token }
}
} catch ( e ) {
throw new ForbiddenError( 'You need a token to subscribe' )
} catch (e) {
throw new ForbiddenError('You need a token to subscribe')
}
},
onDisconnect: ( webSocket, context ) => {
onDisconnect: (webSocket, context) => {
metricConnectedClients.dec()
// debug( `speckle:debug` )( 'ws on disconnect connect event' )
},
}
},
plugins: [
require( `${appRoot}/logging/apolloPlugin` )
],
plugins: [require(`${appRoot}/logging/apolloPlugin`)],
tracing: process.env.NODE_ENV === 'development',
introspection: true,
playground: true
} )
})
graphqlServer.applyMiddleware( { app: app } )
graphqlServer.applyMiddleware({ app: app })
// Expose prometheus metrics
app.get( '/metrics', async ( req, res ) => {
app.get('/metrics', async (req, res) => {
try {
res.set( 'Content-Type', prometheusClient.register.contentType )
res.end( await prometheusClient.register.metrics() )
} catch ( ex ) {
res.status( 500 ).end( ex.message )
res.set('Content-Type', prometheusClient.register.contentType)
res.end(await prometheusClient.register.metrics())
} catch (ex) {
res.status(500).end(ex.message)
}
} )
})
// Trust X-Forwarded-* headers (for https protocol detection)
app.enable( 'trust proxy' )
app.enable('trust proxy')
return { app, graphqlServer }
}
/**
* Starts a http server, hoisting the express app to it.
* @param {[type]} app [description]
* @return {[type]} [description]
*/
exports.startHttp = async ( app, customPortOverride ) => {
exports.startHttp = async (app, customPortOverride) => {
let bindAddress = process.env.BIND_ADDRESS || '127.0.0.1'
let port = process.env.PORT || 3000
let frontendHost = process.env.FRONTEND_HOST || 'localhost'
let frontendPort = process.env.FRONTEND_PORT || 8080
// Handles frontend proxying:
// Dev mode -> proxy form the local webpack server
if ( process.env.NODE_ENV === 'development' ) {
const { createProxyMiddleware } = require( 'http-proxy-middleware' )
const frontendProxy = createProxyMiddleware( { target: `http://${frontendHost}:${frontendPort}`, changeOrigin: true, ws: false, logLevel: 'silent' } )
app.use( '/', frontendProxy )
if (process.env.NODE_ENV === 'development') {
const { createProxyMiddleware } = require('http-proxy-middleware')
const frontendProxy = createProxyMiddleware({
target: `http://${frontendHost}:${frontendPort}`,
changeOrigin: true,
ws: false,
logLevel: 'silent'
})
app.use('/', frontendProxy)
debug( 'speckle:startup' )( '✨ Proxying frontend (dev mode):' )
debug( 'speckle:startup' )( `👉 main application: http://localhost:${port}/` )
debug('speckle:startup')('✨ Proxying frontend (dev mode):')
debug('speckle:startup')(`👉 main application: http://localhost:${port}/`)
}
// Production mode
@@ -142,39 +155,43 @@ exports.startHttp = async ( app, customPortOverride ) => {
bindAddress = process.env.BIND_ADDRESS || '0.0.0.0'
}
let server = http.createServer( app )
let server = http.createServer(app)
if ( customPortOverride || customPortOverride === 0 ) port = customPortOverride
app.set( 'port', port )
if (customPortOverride || customPortOverride === 0) port = customPortOverride
app.set('port', port)
// Final apollo server setup
graphqlServer.installSubscriptionHandlers( server )
graphqlServer.applyMiddleware( { app: app } )
graphqlServer.installSubscriptionHandlers(server)
graphqlServer.applyMiddleware({ app: app })
app.use( Sentry.Handlers.errorHandler( ) )
app.use(Sentry.Handlers.errorHandler())
// large timeout to allow large downloads on slow connections to finish
createTerminus( server, {
signals: [ 'SIGTERM', 'SIGINT' ],
createTerminus(server, {
signals: ['SIGTERM', 'SIGINT'],
timeout: 5 * 60 * 1000,
beforeShutdown: () => {
debug( 'speckle:shutdown' )( 'Shutting down (signal received)...' )
debug('speckle:shutdown')('Shutting down (signal received)...')
},
onSignal: () => {
// Other custom cleanup after connections are finished
},
onShutdown: () => {
debug( 'speckle:shutdown' )( 'Shutdown completed' )
process.exit( 0 )
debug('speckle:shutdown')('Shutdown completed')
process.exit(0)
}
} )
})
server.on( 'listening', ( ) => {
debug( 'speckle:startup' )( `🚀 My name is Speckle Server, and I'm running at ${server.address().address}:${server.address().port}` )
app.emit( 'appStarted' )
} )
server.on('listening', () => {
debug('speckle:startup')(
`🚀 My name is Speckle Server, and I'm running at ${server.address().address}:${
server.address().port
}`
)
app.emit('appStarted')
})
server.listen( port, bindAddress )
server.listen(port, bindAddress)
server.keepAliveTimeout = 61 * 1000
server.headersTimeout = 65 * 1000
+13
View File
@@ -0,0 +1,13 @@
/**
* Bootstrap module that should be imported at the very top of each entry point module
*/
const appRoot = require('app-root-path')
const dotenv = require('dotenv')
const { isTestEnv } = require('./modules/core/helpers/envHelper')
// If running in test env, load .env.test first
if (isTestEnv()) {
dotenv.config({ path: `${appRoot}/.env.test` })
}
dotenv.config({ path: `${appRoot}/.env` })
+10 -6
View File
@@ -2,16 +2,20 @@
'use strict'
let env = process.env.NODE_ENV || 'development'
let conf = require( '../knexfile.js' )[ env ]
let conf = require('../knexfile.js')[env]
conf.log = {
warn( message ) {
if ( message === 'FS-related option specified for migration configuration. This resets migrationSource to default FsMigrations' ) return
warn(message) {
if (
message ===
'FS-related option specified for migration configuration. This resets migrationSource to default FsMigrations'
)
return
}
}
const debug = require( 'debug' )
const debug = require('debug')
debug( 'speckle:db-startup' )( `Loaded knex conf for ${env}` )
debug('speckle:db-startup')(`Loaded knex conf for ${env}`)
module.exports = require( 'knex' )( conf )
module.exports = require('knex')(conf)
+20 -23
View File
@@ -1,37 +1,34 @@
/* istanbul ignore file */
'use strict'
const fs = require( 'fs' )
const path = require( 'path' )
const appRoot = require( 'app-root-path' )
// Load up .ENV file
require('./bootstrap')
const fs = require('fs')
const path = require('path')
require( 'dotenv' ).config( { path: `${appRoot}/.env` } )
function walk( dir ) {
let results = [ ]
let list = fs.readdirSync( dir )
list.forEach( function ( file ) {
let fullFile = path.join( dir, file )
let stat = fs.statSync( fullFile )
if ( stat && stat.isDirectory( ) ) {
if ( file === 'migrations' )
results.push( fullFile )
else
results = results.concat( walk( fullFile ) )
function walk(dir) {
let results = []
let list = fs.readdirSync(dir)
list.forEach(function (file) {
let fullFile = path.join(dir, file)
let stat = fs.statSync(fullFile)
if (stat && stat.isDirectory()) {
if (file === 'migrations') results.push(fullFile)
else results = results.concat(walk(fullFile))
}
} )
})
return results
}
let migrationDirs = walk( './modules' )
let migrationDirs = walk('./modules')
// this is for readability, many users struggle to set the postgres connection uri
// in the env variables. This way its a bit easier to understand, also backward compatible.
let env = process.env
let connectionUri
if ( env.POSTGRES_USER && env.POSTGRES_PASSWORD ) {
connectionUri = `postgres://${encodeURIComponent( env.POSTGRES_USER )}:${encodeURIComponent( env.POSTGRES_PASSWORD )}@${env.POSTGRES_URL}/${encodeURIComponent( env.POSTGRES_DB )}`
if (env.POSTGRES_USER && env.POSTGRES_PASSWORD) {
connectionUri = `postgres://${encodeURIComponent(env.POSTGRES_USER)}:${encodeURIComponent(
env.POSTGRES_PASSWORD
)}@${env.POSTGRES_URL}/${encodeURIComponent(env.POSTGRES_DB)}`
} else {
connectionUri = env.POSTGRES_URL
}
@@ -42,14 +39,14 @@ module.exports = {
connection: connectionUri || 'postgres://localhost/speckle2_test',
migrations: {
directory: migrationDirs
},
}
},
development: {
client: 'pg',
connection: connectionUri || 'postgres://localhost/speckle2_dev',
migrations: {
directory: migrationDirs
},
}
},
production: {
client: 'pg',
@@ -0,0 +1,13 @@
function isTestEnv() {
return process.env.NODE_ENV === 'test'
}
function isDevEnv() {
return process.env.NODE_ENV === 'development'
}
function isProdEnv() {
return process.env.NODE_ENV === 'production'
}
module.exports = { isTestEnv, isDevEnv, isProdEnv }
@@ -1,42 +1,41 @@
const appRoot = require( 'app-root-path' )
require( 'dotenv' ).config( { 'path': `${appRoot}/.env` } )
const crs = require( 'crypto-random-string' )
require('../../../bootstrap')
const knex = require( `${appRoot}/db/knex` )
const Verifications = () => knex( 'email_verifications' )
const appRoot = require('app-root-path')
const crs = require('crypto-random-string')
const { getServerInfo } = require( `${appRoot}/modules/core/services/generic` )
const { sendEmail } = require( `${appRoot}/modules/emails` )
const knex = require(`${appRoot}/db/knex`)
const Verifications = () => knex('email_verifications')
const sendEmailVerification = async ( { recipient } ) => {
const { getServerInfo } = require(`${appRoot}/modules/core/services/generic`)
const { sendEmail } = require(`${appRoot}/modules/emails`)
const sendEmailVerification = async ({ recipient }) => {
// we need to validate email here, since we'll send it out,
// even if technically there is no chance ATM that an incorrect addr comes in
const serverInfo = await getServerInfo()
const existingVerifications = await Verifications()
.where( { 'email': recipient } )
if ( existingVerifications.some( ver => isVerificationValid( ver ) ) )
throw new Error( 'You already have a valid verification message, please check your inbox' )
const verificationId = await createEmailVerification( { 'email': recipient } )
const existingVerifications = await Verifications().where({ email: recipient })
if (existingVerifications.some((ver) => isVerificationValid(ver)))
throw new Error('You already have a valid verification message, please check your inbox')
const verificationId = await createEmailVerification({ email: recipient })
const verificationLink = new URL(
`auth/verifyemail?t=${verificationId}`, process.env.CANONICAL_URL,
`auth/verifyemail?t=${verificationId}`,
process.env.CANONICAL_URL
)
const { text, html, subject } = await prepareMessage(
{ verificationLink, serverInfo },
)
return await sendEmail( {
'to': recipient,
const { text, html, subject } = await prepareMessage({ verificationLink, serverInfo })
return await sendEmail({
to: recipient,
subject,
text,
html,
} )
html
})
}
const isVerificationValid = ( { createdAt } ) => {
const timeDiff = Math.abs( Date.now() - new Date( createdAt ) )
return timeDiff < 8.64e+7
const isVerificationValid = ({ createdAt }) => {
const timeDiff = Math.abs(Date.now() - new Date(createdAt))
return timeDiff < 8.64e7
}
const prepareMessage = async ( { verificationLink, serverInfo } ) => {
const prepareMessage = async ({ verificationLink, serverInfo }) => {
const subject = `Speckle Server ${serverInfo.name} email verification`
const text = `
Hello!
@@ -90,12 +89,12 @@ This email was sent from the
return { text, html, subject }
}
const createEmailVerification = async ( { email } ) => {
const createEmailVerification = async ({ email }) => {
const verification = {
'id': crs( { 'length': 20 } ),
email,
id: crs({ length: 20 }),
email
}
await Verifications().insert( verification )
await Verifications().insert(verification)
return verification.id
}
+157 -126
View File
@@ -1,89 +1,97 @@
/* istanbul ignore file */
const expect = require( 'chai' ).expect
const expect = require('chai').expect
const appRoot = require( 'app-root-path' )
const appRoot = require('app-root-path')
const { createUser } = require( `${appRoot}/modules/core/services/users` )
const { createPersonalAccessToken } = require( `${appRoot}/modules/core/services/tokens` )
const { createStream } = require( `${appRoot}/modules/core/services/streams` )
const { createObjects } = require( `${appRoot}/modules/core/services/objects` )
const { createCommitByBranchName } = require( `${appRoot}/modules/core/services/commits` )
const { createUser } = require(`${appRoot}/modules/core/services/users`)
const { createPersonalAccessToken } = require(`${appRoot}/modules/core/services/tokens`)
const { createStream } = require(`${appRoot}/modules/core/services/streams`)
const { createObjects } = require(`${appRoot}/modules/core/services/objects`)
const { createCommitByBranchName } = require(`${appRoot}/modules/core/services/commits`)
const { beforeEachContext, initializeTestServer } = require( `${appRoot}/test/hooks` )
const { createManyObjects } = require( `${appRoot}/test/helpers` )
const { beforeEachContext, initializeTestServer } = require(`${appRoot}/test/hooks`)
const { createManyObjects } = require(`${appRoot}/test/helpers`)
const { getStreamHistory, getCommitHistory, getObjectHistory, getUserHistory, getTotalStreamCount, getTotalCommitCount, getTotalObjectCount, getTotalUserCount } = require( '../services' )
const {
getStreamHistory,
getCommitHistory,
getObjectHistory,
getUserHistory,
getTotalStreamCount,
getTotalCommitCount,
getTotalObjectCount,
getTotalUserCount
} = require('../services')
const params = { numUsers: 25, numStreams: 30, numObjects: 100, numCommits: 100 }
describe( 'Server stats services @stats-services', function() {
before( async function() {
this.timeout( 10000 )
describe('Server stats services @stats-services', function () {
before(async function () {
this.timeout(10000)
await beforeEachContext( )
await seedDb( params )
} )
await beforeEachContext()
await seedDb(params)
})
it( 'should return the total number of users on this server', async () => {
it('should return the total number of users on this server', async () => {
let res = await getTotalUserCount()
expect( res ).to.equal( params.numUsers )
} )
expect(res).to.equal(params.numUsers)
})
it( 'should return the total number of streams on this server', async() => {
it('should return the total number of streams on this server', async () => {
let res = await getTotalStreamCount()
expect( res ).to.equal( params.numStreams )
} )
expect(res).to.equal(params.numStreams)
})
it( 'should return the total number of commits on this server', async() => {
it('should return the total number of commits on this server', async () => {
let res = await getTotalCommitCount()
expect( res ).to.equal( params.numCommits )
} )
expect(res).to.equal(params.numCommits)
})
it( 'should return the total number of objects on this server', async() => {
it('should return the total number of objects on this server', async () => {
let res = await getTotalObjectCount()
expect( res ).to.equal( params.numObjects )
} )
expect(res).to.equal(params.numObjects)
})
it( 'should return the stream creation history by month', async() => {
it('should return the stream creation history by month', async () => {
let res = await getStreamHistory()
expect( res ).to.be.an( 'array' )
expect( res[0] ).to.have.property( 'count' )
expect( res[0] ).to.have.property( 'created_month' )
expect( res[0].count ).to.be.a( 'number' )
expect( res[0].count ).to.equal( params.numStreams )
} )
expect(res).to.be.an('array')
expect(res[0]).to.have.property('count')
expect(res[0]).to.have.property('created_month')
expect(res[0].count).to.be.a('number')
expect(res[0].count).to.equal(params.numStreams)
})
it( 'should return the commit creation history by month', async() => {
it('should return the commit creation history by month', async () => {
let res = await getCommitHistory()
expect( res ).to.be.an( 'array' )
expect( res[0] ).to.have.property( 'count' )
expect( res[0] ).to.have.property( 'created_month' )
expect( res[0].count ).to.be.a( 'number' )
expect( res[0].count ).to.equal( params.numCommits )
} )
expect(res).to.be.an('array')
expect(res[0]).to.have.property('count')
expect(res[0]).to.have.property('created_month')
expect(res[0].count).to.be.a('number')
expect(res[0].count).to.equal(params.numCommits)
})
it( 'should return the object creation history by month', async() => {
it('should return the object creation history by month', async () => {
let res = await getObjectHistory()
expect( res ).to.be.an( 'array' )
expect( res[0] ).to.have.property( 'count' )
expect( res[0] ).to.have.property( 'created_month' )
expect( res[0].count ).to.be.a( 'number' )
expect( res[0].count ).to.equal( params.numObjects )
} )
expect(res).to.be.an('array')
expect(res[0]).to.have.property('count')
expect(res[0]).to.have.property('created_month')
expect(res[0].count).to.be.a('number')
expect(res[0].count).to.equal(params.numObjects)
})
it( 'should return the user creation history by month', async() => {
it('should return the user creation history by month', async () => {
let res = await getUserHistory()
expect( res ).to.be.an( 'array' )
expect( res[0] ).to.have.property( 'count' )
expect( res[0] ).to.have.property( 'created_month' )
expect( res[0].count ).to.be.a( 'number' )
expect( res[0].count ).to.equal( params.numUsers )
} )
} )
expect(res).to.be.an('array')
expect(res[0]).to.have.property('count')
expect(res[0]).to.have.property('created_month')
expect(res[0].count).to.be.a('number')
expect(res[0].count).to.equal(params.numUsers)
})
})
describe( 'Server stats api @stats-api', function() {
let server
describe('Server stats api @stats-api', function () {
let server, sendRequest
let adminUser = {
name: 'Dimitrie',
@@ -112,100 +120,123 @@ describe( 'Server stats api @stats-api', function() {
}
`
before( async function() {
this.timeout( 10000 )
before(async function () {
this.timeout(10000)
let { app } = await beforeEachContext( );
( { server, sendRequest } = await initializeTestServer( app ) )
let { app } = await beforeEachContext()
;({ server, sendRequest } = await initializeTestServer(app))
adminUser.id = await createUser( adminUser )
adminUser.goodToken = `Bearer ${( await createPersonalAccessToken( adminUser.id, 'test token user A', [ 'server:stats' ] ) )}`
adminUser.badToken = `Bearer ${( await createPersonalAccessToken( adminUser.id, 'test token user A', [ 'streams:read' ] ) )}`
adminUser.id = await createUser(adminUser)
adminUser.goodToken = `Bearer ${await createPersonalAccessToken(
adminUser.id,
'test token user A',
['server:stats']
)}`
adminUser.badToken = `Bearer ${await createPersonalAccessToken(
adminUser.id,
'test token user A',
['streams:read']
)}`
notAdminUser.id = await createUser( notAdminUser )
notAdminUser.goodToken = `Bearer ${( await createPersonalAccessToken( notAdminUser.id, 'test token user A', [ 'server:stats' ] ) )}`
notAdminUser.badToken = `Bearer ${( await createPersonalAccessToken( notAdminUser.id, 'test token user A', [ 'streams:read' ] ) )}`
notAdminUser.id = await createUser(notAdminUser)
notAdminUser.goodToken = `Bearer ${await createPersonalAccessToken(
notAdminUser.id,
'test token user A',
['server:stats']
)}`
notAdminUser.badToken = `Bearer ${await createPersonalAccessToken(
notAdminUser.id,
'test token user A',
['streams:read']
)}`
await seedDb( params )
} )
await seedDb(params)
})
after( async function() {
await server.close( )
} )
after(async function () {
await server.close()
})
it( 'Should not get stats if user is not admin', async() => {
let res = await sendRequest( adminUser.badToken, { query: fullQuery } )
expect( res.body.errors ).to.exist
expect( res.body.errors[0].extensions.code ).to.equal( 'FORBIDDEN' )
} )
it('Should not get stats if user is not admin', async () => {
let res = await sendRequest(adminUser.badToken, { query: fullQuery })
expect(res.body.errors).to.exist
expect(res.body.errors[0].extensions.code).to.equal('FORBIDDEN')
})
it( 'Should not get stats if user is not admin even if the token has the correct scopes', async() => {
let res = await sendRequest( notAdminUser.goodToken, { query: fullQuery } )
expect( res.body.errors ).to.exist
expect( res.body.errors[0].extensions.code ).to.equal( 'FORBIDDEN' )
} )
it('Should not get stats if user is not admin even if the token has the correct scopes', async () => {
let res = await sendRequest(notAdminUser.goodToken, { query: fullQuery })
expect(res.body.errors).to.exist
expect(res.body.errors[0].extensions.code).to.equal('FORBIDDEN')
})
it( 'Should not get stats if token does not have required scope', async() => {
let res = await sendRequest( adminUser.badToken, { query: fullQuery } )
expect( res ).to.be.json
expect( res.body.errors ).to.exist
expect( res.body.errors[0].extensions.code ).to.equal( 'FORBIDDEN' )
} )
it('Should not get stats if token does not have required scope', async () => {
let res = await sendRequest(adminUser.badToken, { query: fullQuery })
expect(res).to.be.json
expect(res.body.errors).to.exist
expect(res.body.errors[0].extensions.code).to.equal('FORBIDDEN')
})
it( 'Should get server stats', async() => {
let res = await sendRequest( adminUser.goodToken, { query: fullQuery } )
expect( res ).to.be.json
expect( res.body.errors ).to.not.exist
it('Should get server stats', async () => {
let res = await sendRequest(adminUser.goodToken, { query: fullQuery })
expect(res).to.be.json
expect(res.body.errors).to.not.exist
expect( res.body.data ).to.have.property( 'serverStats' )
expect( res.body.data.serverStats ).to.have.property( 'totalStreamCount' )
expect( res.body.data.serverStats ).to.have.property( 'totalCommitCount' )
expect( res.body.data.serverStats ).to.have.property( 'totalObjectCount' )
expect( res.body.data.serverStats ).to.have.property( 'totalUserCount' )
expect( res.body.data.serverStats ).to.have.property( 'streamHistory' )
expect( res.body.data.serverStats ).to.have.property( 'commitHistory' )
expect( res.body.data.serverStats ).to.have.property( 'userHistory' )
expect(res.body.data).to.have.property('serverStats')
expect(res.body.data.serverStats).to.have.property('totalStreamCount')
expect(res.body.data.serverStats).to.have.property('totalCommitCount')
expect(res.body.data.serverStats).to.have.property('totalObjectCount')
expect(res.body.data.serverStats).to.have.property('totalUserCount')
expect(res.body.data.serverStats).to.have.property('streamHistory')
expect(res.body.data.serverStats).to.have.property('commitHistory')
expect(res.body.data.serverStats).to.have.property('userHistory')
expect( res.body.data.serverStats.totalStreamCount ).to.equal( params.numStreams )
expect( res.body.data.serverStats.totalCommitCount ).to.equal( params.numCommits )
expect( res.body.data.serverStats.totalObjectCount ).to.equal( params.numObjects )
expect( res.body.data.serverStats.totalUserCount ).to.equal( params.numUsers + 2 ) // we're registering two extra users in the before hook
expect(res.body.data.serverStats.totalStreamCount).to.equal(params.numStreams)
expect(res.body.data.serverStats.totalCommitCount).to.equal(params.numCommits)
expect(res.body.data.serverStats.totalObjectCount).to.equal(params.numObjects)
expect(res.body.data.serverStats.totalUserCount).to.equal(params.numUsers + 2) // we're registering two extra users in the before hook
expect( res.body.data.serverStats.streamHistory ).to.be.an( 'array' )
expect( res.body.data.serverStats.commitHistory ).to.be.an( 'array' )
expect( res.body.data.serverStats.userHistory ).to.be.an( 'array' )
} )
} )
expect(res.body.data.serverStats.streamHistory).to.be.an('array')
expect(res.body.data.serverStats.commitHistory).to.be.an('array')
expect(res.body.data.serverStats.userHistory).to.be.an('array')
})
})
async function seedDb( { numUsers = 10, numStreams = 10, numObjects = 10, numCommits = 10 } = {} ) {
async function seedDb({ numUsers = 10, numStreams = 10, numObjects = 10, numCommits = 10 } = {}) {
let users = []
let streams = []
// create users
for ( let i = 0; i < numUsers; i++ ) {
let id = await createUser( { name: `User ${i}`, password: `SuperSecure${i}${i*3.14}`, email: `user${i}@speckle.systems` } )
users.push( id )
for (let i = 0; i < numUsers; i++) {
let id = await createUser({
name: `User ${i}`,
password: `SuperSecure${i}${i * 3.14}`,
email: `user${i}@speckle.systems`
})
users.push(id)
}
// create streams
for ( let i = 0; i < numStreams; i++ ) {
let id = await createStream( { name: `Stream ${i}`, ownerId: users[i >= users.length ? users.length - 1 : i ] } )
streams.push( id )
for (let i = 0; i < numStreams; i++) {
let id = await createStream({
name: `Stream ${i}`,
ownerId: users[i >= users.length ? users.length - 1 : i]
})
streams.push(id)
}
// create a objects
let mockObjects = createManyObjects( numObjects - 1 )
let objs = await createObjects( streams[ 0 ], mockObjects )
let mockObjects = createManyObjects(numObjects - 1)
let objs = await createObjects(streams[0], mockObjects)
let commits = []
// create commits referencing those objects
for ( let i = 0; i < numCommits; i++ ) {
let id = await createCommitByBranchName( {
for (let i = 0; i < numCommits; i++) {
let id = await createCommitByBranchName({
streamId: streams[0],
branchName: 'main',
sourceApplication: 'tests',
objectId: objs[ i >= objs.length ? objs.length - 1 : i ]
} )
commits.push( id )
objectId: objs[i >= objs.length ? objs.length - 1 : i]
})
commits.push(id)
}
}
+40795 -40740
View File
File diff suppressed because it is too large Load Diff
+7 -5
View File
@@ -14,8 +14,8 @@
"scripts": {
"dev": "cross-env NODE_ENV=development DEBUG=speckle:* nodemon ./bin/www --watch . --watch ./bin/www -e js,graphql,env",
"dev:server:test": "cross-env NODE_ENV=test DEBUG=speckle:* node ./bin/www",
"test": "cross-env PORT=0 NODE_ENV=test POSTGRES_URL=postgres://speckle:speckle@localhost/speckle2_test POSTGRES_USER='' nyc --reporter html --reporter lcov mocha -s 0 --timeout 10000 --exit",
"test:report": "npm run test -- --reporter mocha-junit-reporter --reporter-options mochaFile=reports/test-results.xml",
"test": "cross-env NODE_ENV=test mocha",
"test:coverage": "cross-env NODE_ENV=test nyc --reporter lcov mocha",
"lint": "eslint . --ext .js,.ts"
},
"dependencies": {
@@ -82,15 +82,17 @@
"chai-http": "^4.3.0",
"concurrently": "^7.0.0",
"cross-env": "^7.0.3",
"eslint": "^8.11.0",
"http-proxy-middleware": "^1.0.6",
"lerna": "^3.22.1",
"mocha": "^7.2.0",
"mocha-junit-reporter": "^2.0.2",
"@types/mocha": "^7.0.2",
"nodemon": "^2.0.6",
"nyc": "^15.0.1",
"supertest": "^4.0.2",
"ws": "^7.5.7"
"ws": "^7.5.7",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1"
},
"config": {
"commitizen": {
+42
View File
@@ -32,9 +32,11 @@
"clean-webpack-plugin": "^4.0.0",
"cross-env": "7.0.3",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"html-webpack-plugin": "^5.3.2",
"jest": "27.2.5",
"mocha": "^9.1.2",
"prettier": "^2.5.1",
"webpack": "5.58.2",
"webpack-cli": "^4.9.0",
"webpack-dev-server": "^4.3.1",
@@ -5213,6 +5215,18 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -9928,6 +9942,21 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-error": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
@@ -16242,6 +16271,13 @@
}
}
},
"eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"requires": {}
},
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -19645,6 +19681,12 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
"prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
"dev": true
},
"pretty-error": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
+12 -10
View File
@@ -29,6 +29,14 @@
"3d viewer",
"threejs"
],
"dependencies": {
"@speckle/objectloader": "^2.3.0",
"camera-controls": "^1.33.1",
"hold-event": "^0.1.0",
"lodash.debounce": "^4.0.8",
"rainbowvis.js": "^1.0.1",
"three": "^0.136.0"
},
"devDependencies": {
"@babel/cli": "7.15.7",
"@babel/core": "7.15.8",
@@ -45,21 +53,15 @@
"babel-plugin-transform-class-properties": "6.24.1",
"clean-webpack-plugin": "^4.0.0",
"cross-env": "7.0.3",
"eslint": "^8.11.0",
"html-webpack-plugin": "^5.3.2",
"jest": "27.2.5",
"mocha": "^9.1.2",
"webpack": "5.58.2",
"webpack-cli": "^4.9.0",
"webpack-dev-server": "^4.3.1",
"yargs": "^17.2.1"
},
"dependencies": {
"@speckle/objectloader": "^2.3.0",
"camera-controls": "^1.33.1",
"hold-event": "^0.1.0",
"lodash.debounce": "^4.0.8",
"rainbowvis.js": "^1.0.1",
"three": "^0.136.0"
"yargs": "^17.2.1",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1"
}
}
+43 -1
View File
@@ -15,7 +15,9 @@
},
"devDependencies": {
"cross-env": "^7.0.3",
"eslint": "^8.11.0"
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1"
}
},
"node_modules/@eslint/eslintrc": {
@@ -349,6 +351,18 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-scope": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
@@ -1063,6 +1077,21 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/private-ip": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/private-ip/-/private-ip-2.3.3.tgz",
@@ -1586,6 +1615,13 @@
"v8-compile-cache": "^2.0.3"
}
},
"eslint-config-prettier": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
"dev": true,
"requires": {}
},
"eslint-scope": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
@@ -2107,6 +2143,12 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
"prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
"dev": true
},
"private-ip": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/private-ip/-/private-ip-2.3.3.tgz",
+3 -1
View File
@@ -24,6 +24,8 @@
},
"devDependencies": {
"cross-env": "^7.0.3",
"eslint": "^8.11.0"
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"prettier": "^2.5.1"
}
}
+9 -2
View File
@@ -48,14 +48,21 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "esbenp.prettier-vscode",
"search.useParentIgnoreFiles": true
},
"extensions": {
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "octref.vetur"],
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"octref.vetur",
"hbenl.vscode-mocha-test-adapter",
"ryanluker.vscode-coverage-gutters"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}