commit a16a54f4d7ef38c4100148139630e2c616d5b266 Author: cha0s Date: Wed Jun 17 11:01:01 2020 -0500 chore: initial diff --git a/.eslint.defaults.js b/.eslint.defaults.js new file mode 100644 index 0000000..d4f5df2 --- /dev/null +++ b/.eslint.defaults.js @@ -0,0 +1,33 @@ +const side = require('./side'); + +const config = { + globals: { + process: true, + AVOCADO_CLIENT: true, + AVOCADO_SERVER: true, + }, + rules: { + 'babel/object-curly-spacing': 'off', + 'brace-style': ['error', 'stroustrup'], + 'no-bitwise': ['error', {int32Hint: true}], + 'no-plusplus': 'off', + 'no-underscore-dangle': 'off', + 'padded-blocks': ['error', {classes: 'always'}], + yoda: 'off', + }, + settings: { + 'import/resolver': { + webpack: { + config: `${__dirname}/webpack.config.js`, + }, + }, + }, +}; + +if ('client' === side) { + config.rules['jsx-a11y/label-has-associated-control'] = [2, { + 'assert': 'either', + }]; +} + +module.exports = config; diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..b73c5f9 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,8 @@ +const neutrino = require('neutrino'); + +// Have to default side for IDE eslint. +process.env.SIDE = process.env.SIDE || 'client'; + +const side = require('./side'); + +module.exports = neutrino(require(`./.neutrinorc.${side}`)).eslintrc(); diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca2395f --- /dev/null +++ b/.gitignore @@ -0,0 +1,119 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.pnp.* + +# Neutrino build directory +build \ No newline at end of file diff --git a/.mocharc.js b/.mocharc.js new file mode 100644 index 0000000..b781ff2 --- /dev/null +++ b/.mocharc.js @@ -0,0 +1,5 @@ +const neutrino = require('neutrino'); + +const side = require('./side'); + +module.exports = neutrino(require(`./.neutrinorc.${side}`)).mocha(); diff --git a/.neutrinorc.client.js b/.neutrinorc.client.js new file mode 100644 index 0000000..d31d2be --- /dev/null +++ b/.neutrinorc.client.js @@ -0,0 +1,80 @@ +const path = require('path'); +const react = require('@neutrinojs/react'); +const styles = require('@neutrinojs/style-loader'); +const globImporter = require('node-sass-glob-importer'); +const {DefinePlugin} = require('webpack'); + +const {afterPlatform, initial} = require('./middleware'); + +module.exports = { + options: { + root: __dirname, + }, + use: [ + initial({ + environmentDefines: ['FRONTEND_ORIGIN'], + scwpPaths: [/^@avocado/], + }), + react({ + devServer: { + contentBase: path.resolve(__dirname, 'resource'), + }, + style: { + test: /\.(css|sass|scss)$/, + modulesTest: /\.module\.(css|sass|scss)$/, + loaders: [ + { + loader: 'postcss-loader', + useId: 'postcss', + options: { + config: { + path: __dirname, + }, + }, + }, + { + loader: 'sass-loader', + useId: 'sass', + options: { + importer: globImporter(), + }, + }, + ], + }, + html: { + template: `${__dirname}/src/client/index.ejs`, + title: 'Persea', + }, + }), + afterPlatform({ + babelPaths: [/^@avocado/, /^contempo/, /^scwp/], + }), + (neutrino) => { + neutrino.config.module.rule('style') + .oneOf('raw') + .before('normal') + .test(/\.raw\.(css|sass|scss)$/) + .use('raw') + .loader('raw-loader') + .end() + .use('postcss') + .loader('postcss-loader') + .options({config: {path: __dirname}}) + .end() + .use('sass') + .loader('sass-loader') + .options({importer: globImporter()}) + .end() + neutrino.config.resolve.modules + .add(`${neutrino.options.source}/client/scss`); + neutrino.config + .plugin('avocado-define') + .use(DefinePlugin, [ + { + AVOCADO_CLIENT: true, + AVOCADO_SERVER: false, + }, + ]); + } + ], +}; diff --git a/.neutrinorc.server.js b/.neutrinorc.server.js new file mode 100644 index 0000000..6049c94 --- /dev/null +++ b/.neutrinorc.server.js @@ -0,0 +1,38 @@ +const {spawn} = require('child_process'); + +const copy = require('@neutrinojs/copy'); +const node = require('@neutrinojs/node'); +const {DefinePlugin} = require('webpack'); + +const {afterPlatform, initial} = require('./middleware'); + +if (!process.argv.find((arg) => arg === 'production')) { + spawn('docker', ['run', '-p', '11346:6379', 'redis'], {stdio: 'inherit'}); +} + +module.exports = { + options: { + root: __dirname, + }, + use: [ + initial({ + environmentDefines: ['REDIS_ORIGIN'], + scwpPaths: [/^@avocado/], + }), + node(), + afterPlatform({ + babelPaths: [/^@avocado/], + externalMatcher: /(?:@avocado|@pixi|scwp|webpack)/, + }), + (neutrino) => { + neutrino.config + .plugin('avocado-define') + .use(DefinePlugin, [ + { + AVOCADO_CLIENT: false, + AVOCADO_SERVER: true, + }, + ]); + }, + ], +}; diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8c2cd66 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + // "preLaunchTask": "Both", + "request": "attach", + "name": "Terrible", + "address": "127.0.0.1", + "port": 43000, + "skipFiles": [ + "/**" + ] + }, + ], +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7bc621b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,79 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Client", + "type": "shell", + "isBackground": true, + "command": "yarn", + "args": [ + "run", + "client" + ], + "problemMatcher": [ + { + "owner": "custom", + "pattern": { + "regexp": "__________" + }, + "background": { + "activeOnStart": true, + "beginsPattern": "(Compiling|Project is running)", + "endsPattern": "(?:Compiled successfully|Failed to compile)" + } + } + ] + }, + { + "label": "Server", + "type": "shell", + "isBackground": true, + "command": "yarn", + "args": [ + "run", + "server", + "--inspect=127.0.0.1:43000" + ], + "problemMatcher": [ + { + "owner": "custom", + "pattern": { + "regexp": "You need to restart the application" + } + }, + { + "owner": "custom", + "pattern": { + "regexp": "__________" + }, + "background": { + "activeOnStart": true, + "beginsPattern": "(Hash:|Updated modules)", + "endsPattern": "(Built at:|Update applied)" + } + } + ] + }, + { + "label": "Both", + "isBackground": true, + "dependsOn": [ + "Client", + "Server" + ], + "problemMatcher": [] + }, + { + "type": "shell", + "command": "yarn", + "args": [ + "run", + "lint" + ], + "problemMatcher": [ + "$eslint-stylish" + ], + "label": "yarn run lint" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..32cbebf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM node:14 +EXPOSE 11344 +COPY ./build /var/www +COPY ./node_modules /var/www/server/node_modules +CMD ["node", "/var/www/server/index.js"] diff --git a/fixtures/kitty.entity.json b/fixtures/kitty.entity.json new file mode 100644 index 0000000..d2d50dd --- /dev/null +++ b/fixtures/kitty.entity.json @@ -0,0 +1 @@ +{"traits":{"alive":{},"animated":{"params":{"animations":{"idle":{"offset":[0,-3],"uri":"/kitty.animation.json"}}}},"audible":{"params":{"sounds":{"deathSound":{"uri":"/ded.sound.json"}}}},"behaved":{"params":{"routines":{"type":"routines","routines":{"initial":{"type":"routine","routine":{"type":"actions","traversals":[{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"direction"}],"value":{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"floor"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":0},{"type":"literal","value":4}]}]}]}]}},{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"moveFor"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"Vector"},{"type":"key","key":"fromDirection"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"direction"}]}]}]},{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":0.25},{"type":"literal","value":2.5}]}]}]}]},{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"isAnimating"}],"value":{"type":"literal","value":false}},{"type":"traversal","steps":[{"type":"key","key":"Timing"},{"type":"key","key":"wait"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":1},{"type":"literal","value":4}]}]}]}]},{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"direction"}],"value":{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"floor"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":0},{"type":"literal","value":4}]}]}]}]}},{"type":"traversal","steps":[{"type":"key","key":"Timing"},{"type":"key","key":"wait"},{"type":"invoke","args":[{"type":"traversal","steps":[{"type":"key","key":"Math"},{"type":"key","key":"randomNumber"},{"type":"invoke","args":[{"type":"literal","value":0.5},{"type":"literal","value":3}]}]}]}]},{"type":"traversal","steps":[{"type":"key","key":"entity"},{"type":"key","key":"isAnimating"}],"value":{"type":"literal","value":true}}]}}}}}},"collider":{"params":{"collidesWithGroups":["default","environmental","projectile"]}},"directional":{"params":{"directionCount":4},"state":{"direction":2}},"emitter":{},"existent":{"state":{"name":"Kitty"}},"layered":{},"listed":{},"lootable":{"params":{"table":[{"perc":70,"json":{"uri":"/yarn-ball.entity.json"}},{"perc":80,"json":{"uri":"/yarn-ball.entity.json"}},{"perc":90,"json":{"uri":"/yarn-ball.entity.json"}}]}},"mobile":{"state":{"speed":40}},"physical":{},"positioned":{},"roomed":{},"shaped":{"params":{"shape":{"type":"rectangle","position":[0,0],"size":[8,4]}}},"visible":{"state":{"visibleScale":[1,1]}},"vulnerable":{"params":{"types":["bio"]}}}} \ No newline at end of file diff --git a/inspect.js b/inspect.js new file mode 100644 index 0000000..3c955ee --- /dev/null +++ b/inspect.js @@ -0,0 +1,5 @@ +const neutrino = require('neutrino'); + +const side = require('./side'); + +neutrino(require(`./.neutrinorc.${side}`)).inspect(); diff --git a/middleware.js b/middleware.js new file mode 100644 index 0000000..54a3d7d --- /dev/null +++ b/middleware.js @@ -0,0 +1,132 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable no-param-reassign */ +const path = require('path'); +const airbnb = require('@neutrinojs/airbnb'); +const airbnbBase = require('@neutrinojs/airbnb-base'); +const mocha = require('@neutrinojs/mocha'); +const scwp = require('scwp/neutrino'); +const {DefinePlugin} = require('webpack'); +const nodeExternals = require('webpack-node-externals'); + +const side = require('./side'); + +const avocadoPackages = {}; +const pkg = require(`${__dirname}/package.json`); + +const gatherPackagePaths = (root, packageMatchers) => { + return packageMatchers.reduce((r, packageMatcher) => ( + r.concat([ + (pkg.dependencies || {}), + (pkg.devDependencies || {}), + ].reduce((r, deps) => { + const packageNames = Object.keys(deps); + const packages = []; + for (let i = 0; i < packageNames.length; i++) { + const packageName = packageNames[i]; + if (packageName.match(packageMatcher)) { + packages.push(path.relative(root, path.dirname(require.resolve(packageName)))); + } + } + return r.concat(packages); + }, [])) + ), []); +} + +exports.initial = (options) => (neutrino) => { + const { + environmentDefines = [], + scwpPaths = [], + } = options; + neutrino.options.mains.index = `${side}/index`; + neutrino.options.output = `build/${side}`; + neutrino.config.resolve.modules + .add(`${neutrino.options.root}/node_modules`); + neutrino.config.resolveLoader.modules + .add(`${neutrino.options.root}/node_modules`); + neutrino.config.resolve.alias + .set('~', neutrino.options.source); + ('server' === side ? airbnbBase : airbnb)({ + eslint: { + cache: false, + // eslint-disable-next-line global-require + baseConfig: require('./.eslint.defaults'), + }, + })(neutrino); + mocha({ + spec: `src/+(${side}|common)/**/*.spec.js`, + })(neutrino); + scwp({ + paths: [ + './src/common', + `./src/${side}`, + ].concat( + gatherPackagePaths(neutrino.options.root, scwpPaths.concat(/^scwp/)) + ), + })(neutrino); + neutrino.config + .plugin('environment-define') + .use(DefinePlugin, [ + environmentDefines.reduce((r, k) => ({...r, [k]: JSON.stringify(process.env[k])}), {}) + ]); +}; + +exports.afterPlatform = (options) => (neutrino) => { + const { + babelPaths = [], + externalMatcher = [], + } = options; + const allBabelPaths = gatherPackagePaths(neutrino.options.root, babelPaths); + neutrino.config.module + .rule('compile') + .use('babel') + .tap((options) => { + options.only = [ + neutrino.options.source, + ].concat(allBabelPaths); + options.ignore = []; + return options; + }); + allBabelPaths.forEach((babelPath) => { + neutrino.config.module + .rule('compile') + .include + .add(path.resolve(neutrino.options.root, babelPath)); + }); + neutrino.config.module + .rule('compile') + .use('babel') + .get('options').plugins.push( + [ + 'babel-plugin-webpack-alias', + { + config: `${__dirname}/webpack.config.js`, + }, + ], + ); + if ('client' === side) { + neutrino.config.node.delete('Buffer'); + } + else /* if ('server' === side) */ { + neutrino.config.stats('normal'); + if ('production' !== process.env.NODE_ENV) { + neutrino.config + .plugin('start-server') + .tap((args) => { + const options = args[0]; + const inspectArg = process.argv.find((arg) => -1 !== arg.indexOf('--inspect')); + if (inspectArg) { + options.nodeArgs.push(inspectArg); + } + const profArg = process.argv.find((arg) => -1 !== arg.indexOf('--prof')); + if (profArg) { + options.nodeArgs.push(profArg); + } + return args; + }); + } + neutrino.config + .externals(nodeExternals({ + whitelist: externalMatcher, + })); + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9968c5b --- /dev/null +++ b/package.json @@ -0,0 +1,71 @@ +{ + "name": "persea", + "version": "1.0.0", + "license": "MIT", + "scripts": { + "build:client": "SIDE=CLIENT webpack --mode production --config webpack.config.js", + "build:docker": "yarn run build:client && yarn run build:server && docker build", + "build:server": "SIDE=SERVER webpack --mode production --config webpack.config.js", + "client": "SIDE=CLIENT webpack-dev-server --verbose --disable-host-check --host persea.cha0sdev --port 11345 --mode development --config webpack.config.js", + "inspect": "node ./inspect.js", + "lint": "eslint --cache --format codeframe --ext mjs,jsx,js src", + "server": "SIDE=SERVER webpack --watch --mode development --config webpack.config.js", + "test:client": "NODE_PRESERVE_SYMLINKS=1 SIDE=client mocha --watch", + "test:server": "NODE_PRESERVE_SYMLINKS=1 SIDE=server mocha --watch" + }, + "dependencies": { + "@avocado/behavior": "1.x", + "@avocado/core": "1.x", + "@avocado/entity": "1.x", + "@avocado/graphics": "1.x", + "@avocado/input": "1.x", + "@avocado/math": "1.x", + "@avocado/net": "1.x", + "@avocado/physics": "1.x", + "@avocado/resource": "1.x", + "@avocado/sound": "1.x", + "@avocado/timing": "1.x", + "@avocado/topdown": "1.x", + "@reduxjs/toolkit": "1.3.6", + "ansi-html": "0.0.7", + "classnames": "2.2.6", + "contempo": "1.x", + "express": "^4.17.1", + "express-session": "^1.17.1", + "express-socket.io-session": "^1.3.5", + "html-entities": "1.3.1", + "memorystore": "^1.6.2", + "prop-types": "^15", + "react": "16.8.6", + "react-dom": "16.8.6", + "react-hot-loader": "^4.12.21", + "react-markdown": "^4.3.1", + "react-tabs": "^3.1.1", + "scwp": "1.x", + "socket.io-redis": "^5.3.0", + "source-map-support": "^0.5.11" + }, + "devDependencies": { + "@neutrinojs/airbnb": "^9.1.0", + "@neutrinojs/airbnb-base": "^9.1.0", + "@neutrinojs/copy": "^9.2.0", + "@neutrinojs/mocha": "^9.1.0", + "@neutrinojs/node": "^9.1.0", + "@neutrinojs/react": "^9.1.0", + "autoprefixer": "9.8.0", + "babel-plugin-webpack-alias": "^2.1.2", + "eslint": "^6", + "eslint-import-resolver-webpack": "^0.12.1", + "mocha": "^7", + "neutrino": "^9.1.0", + "node-sass": "4.12.0", + "node-sass-glob-importer": "5.3.2", + "postcss-loader": "3.0.0", + "raw-loader": "1.x", + "sass-loader": "7.1.0", + "v8-natives": "^1.1.0", + "webpack": "^4", + "webpack-cli": "^3", + "webpack-dev-server": "^3" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..a47ef4f --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {}, + }, +}; diff --git a/side.js b/side.js new file mode 100644 index 0000000..4c4f65b --- /dev/null +++ b/side.js @@ -0,0 +1,8 @@ +const side = (process.env.SIDE || '').toLowerCase(); +if (-1 === ['client', 'server'].indexOf(side)) { + throw new Error( + "You must define the SIDE environment variable as 'server' or 'client' when building!", + ); +} + +module.exports = side; diff --git a/src/client/components/entity.jsx b/src/client/components/entity.jsx new file mode 100644 index 0000000..ea9d828 --- /dev/null +++ b/src/client/components/entity.jsx @@ -0,0 +1,23 @@ +import {compose} from '@avocado/core'; +import contempo from 'contempo'; +import React from 'react'; + +import Traits from './traits'; + +const decorate = compose( + contempo(require('./entity.raw.scss')), +); + +const kitty = require('~/../fixtures/kitty.entity.json'); + +const Entity = () => { + const entity = kitty; + const {traits} = entity; + return ( +
+ +
+ ); +}; + +export default decorate(Entity); diff --git a/src/client/components/entity.raw.scss b/src/client/components/entity.raw.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/client/components/persea.jsx b/src/client/components/persea.jsx new file mode 100644 index 0000000..f260613 --- /dev/null +++ b/src/client/components/persea.jsx @@ -0,0 +1,10 @@ +import {hot} from 'react-hot-loader'; +import React from 'react'; + +import Entity from './entity'; + +const Persea = () => ( + +); + +export default hot(module)(Persea); diff --git a/src/client/components/properties/properties.hooks.jsx b/src/client/components/properties/properties.hooks.jsx new file mode 100644 index 0000000..7bc9feb --- /dev/null +++ b/src/client/components/properties/properties.hooks.jsx @@ -0,0 +1,89 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +const propertyPropTypes = { + name: PropTypes.string.isRequired, + label: PropTypes.string, + options: PropTypes.shape({}), +}; + +const bool = ({ + name, + label, + value, +}) => ( +