chore: initial
This commit is contained in:
commit
702de1005d
119
.gitignore
vendored
Normal file
119
.gitignore
vendored
Normal file
|
@ -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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# local
|
||||||
|
/build
|
164
README.md
Normal file
164
README.md
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
<div align="center">
|
||||||
|
<h1>flecks</h1>
|
||||||
|
<p>
|
||||||
|
Flecks is a dynamic, configuration-driven, fullstack application production system. Its purpose
|
||||||
|
is to make application development a more joyful endeavor. Intelligent defaults combined with
|
||||||
|
a highly dynamic structure encourage consistency while allowing you to easily express your own
|
||||||
|
opinions.
|
||||||
|
</p>
|
||||||
|
<p>For documentation, see <a href="ADDME">the documentation page</a>.</p>
|
||||||
|
|
||||||
|
## ⚠️ PROCEED AT YOUR OWN RISK ⚠️
|
||||||
|
|
||||||
|
This is alpha software. There are undoubtedly many bugs that haven't yet been found.
|
||||||
|
|
||||||
|
**You've been warned!**
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Install](#install)
|
||||||
|
2. [Introduction](#introduction)
|
||||||
|
3. [Concepts](#concepts)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Quickly scaffold a new application monorepo:
|
||||||
|
```
|
||||||
|
yarn create @flecks/app my-new-app
|
||||||
|
```
|
||||||
|
|
||||||
|
or with `npm`:
|
||||||
|
```
|
||||||
|
npx @flecks/create-app my-new-app
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Quickly scaffold a new fleck:
|
||||||
|
```
|
||||||
|
yarn create @flecks/fleck my-new-fleck
|
||||||
|
```
|
||||||
|
|
||||||
|
or with `npm`:
|
||||||
|
```
|
||||||
|
npx @flecks/create-fleck my-new-fleck
|
||||||
|
```
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
At its core, flecks is a collection of modules that use [hooks](#hooks) to orchestrate everything
|
||||||
|
from building your project to handling the minutia of what happens when your application starts,
|
||||||
|
when a client connects, defining database models, and more.
|
||||||
|
|
||||||
|
All flecks projects, be they an application or another fleck, contain a `build` directory with a
|
||||||
|
[`flecks.yml`](#flecksyml) that defines the flecks use to compose the project, as well as
|
||||||
|
build-time configuration.
|
||||||
|
|
||||||
|
Modern features you expect — like [ESLint](https://eslint.org/),
|
||||||
|
[Mocha tests](https://mochajs.org/),
|
||||||
|
[Hot Module Replacement (HMR)](https://v4.webpack.js.org/guides/hot-module-replacement/),
|
||||||
|
[SSR](https://reactjs.org/docs/react-dom-server.html) — are baked in. Along with some you
|
||||||
|
may not expect — like *server-side* HMR, the ability to define [redux](https://redux.js.org/)
|
||||||
|
application state (and store enhancers/middleware) dynamically with hooks,
|
||||||
|
[REPL](https://nodejs.org/api/repl.html) support, and much more.
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
|
||||||
|
### `build` directory
|
||||||
|
|
||||||
|
The `build` directory contains build directives and run commands. Examples of these would be:
|
||||||
|
|
||||||
|
- `babel.config.js`
|
||||||
|
- `.eslint.defaults.js`
|
||||||
|
- `.neutrinorc.js`
|
||||||
|
- `webpack.config.js`
|
||||||
|
- etc, etc, depending on which flecks you have enabled. Support for the aforementioned
|
||||||
|
configuration comes stock in `@flecks/core`.
|
||||||
|
|
||||||
|
The `build` directory is a solution to the problem of "ejecting" that you run into with
|
||||||
|
e.g. Create React App. Flecks doesn't force you into an all-or-nothing approach. If your project
|
||||||
|
requires advanced configuration for one aspect, you can simply override that aspect of
|
||||||
|
configuration in your `build` directory on a case-by-case basis.
|
||||||
|
|
||||||
|
Of course, flecks strives to provide powerful defaults that minimize the need to override
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
See [the build directory documentation page](packages/core/build/dox/build.md) for more details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `flecks.yml`
|
||||||
|
|
||||||
|
The build directory also contains a special file, `flecks.yml`. This file is the heart of your
|
||||||
|
flecks project's configuration and is how you orchestrate your project.
|
||||||
|
|
||||||
|
The structure of the file is an object whose keys are the flecks composing your application and
|
||||||
|
whose values are the default configuration for those flecks.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
# Specify configuration overrides for this fleck:
|
||||||
|
'my-fleck':
|
||||||
|
some_value: 69
|
||||||
|
some_other_value: 420
|
||||||
|
# Default configuration:
|
||||||
|
'some-other-fleck': {}
|
||||||
|
```
|
||||||
|
|
||||||
|
The simplest example of a flecks server application:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/server': {}
|
||||||
|
```
|
||||||
|
|
||||||
|
Yes, that's it! In fact, when you use `yarn create @flecks/app`, that's what is generated for you
|
||||||
|
by default. Obviously, this doesn't do much on its own. It simply bootstraps flecks and runs a
|
||||||
|
server application with no interesting work to do.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Hooks
|
||||||
|
|
||||||
|
Documentation page: (ADDME)
|
||||||
|
|
||||||
|
Hooks are how everything happens in flecks. There are many hooks and they will not be treated
|
||||||
|
exhaustively here. See the documentation page above.
|
||||||
|
|
||||||
|
To define hooks (and turn your plain ol' boring JS modules into beautiful interesting flecks), you
|
||||||
|
only have to import the `Hooks` symbol and key your default export:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import {Hooks} from '@flecks/core';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Hooks]: {
|
||||||
|
'@flecks/core/starting': () => {
|
||||||
|
console.log('hello, gorgeous');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Now add your newly-minted fleck to [`flecks.yml`](#flecksyml), and let your fledgling fleck treat
|
||||||
|
you the way you deserve to be treated.
|
||||||
|
|
||||||
|
Just to give you an idea of the power of hooks, some will be listed here:
|
||||||
|
|
||||||
|
- `@flecks/core/config`:
|
||||||
|
> Define default configuration.
|
||||||
|
- `@flecks/docker/containers`:
|
||||||
|
> Define [Docker](https://www.docker.com/) containers to run alongside your application to
|
||||||
|
develop e.g. DB models, redis commands, etc. without having to worry about installing stuff.
|
||||||
|
- `@flecks/http/server/request.route`:
|
||||||
|
> Define [Express](http://expressjs.com/) middleware that runs when an HTTP route is hit.
|
||||||
|
- `@flecks/server/up`:
|
||||||
|
> Do things when server comes up (e.g. DB connection, HTTP listener, make you coffee, etc).
|
||||||
|
|
||||||
|
...and so many more.
|
||||||
|
|
||||||
|
We didn't even touch on [gather hooks](ADDME), [provider hooks](ADDME), [decorator hooks](ADDME),
|
||||||
|
and so many more. Please see the [hook documentation page](ADDME) for the full rundown on all of
|
||||||
|
the wonderful things hooks can do for you.
|
15
TODO.md
Normal file
15
TODO.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
- x multi-build lint is broken
|
||||||
|
- x @flecks/db
|
||||||
|
- x @flecks/governor
|
||||||
|
- x @flecks/react
|
||||||
|
- x Use aliasing for self-referential flecks context
|
||||||
|
- x @flecks/redis
|
||||||
|
- x @flecks/redux
|
||||||
|
- x @flecks/socket
|
||||||
|
- x @flecks/user
|
||||||
|
- x flecks aliasing must ensure webpack aliasing and de-externalization into bundles
|
||||||
|
- x `flecks.invokeMiddleware()` should not build every invocation
|
||||||
|
- x `flecks.invokeComposed()` and `flecks.invokeMiddleware()` should not fatal on a missed lookup
|
||||||
|
- x flecks should have a `platforms` setting, so auto-lookups of `/client`, `/server` are less
|
||||||
|
magical
|
||||||
|
- x `flecks.expandedFlecks()` should use `platforms`
|
6
lerna.json
Normal file
6
lerna.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
13
package.json
Normal file
13
package.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "@flecks/monorepo",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "lerna run build",
|
||||||
|
"lint": "lerna run lint",
|
||||||
|
"test": "lerna run test --no-bail -- --silent"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"lerna": "^3.22.1"
|
||||||
|
}
|
||||||
|
}
|
2
packages/core/.gitignore
vendored
Normal file
2
packages/core/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/dist
|
||||||
|
/node_modules
|
4
packages/core/build/.eslintrc.js
Normal file
4
packages/core/build/.eslintrc.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-dynamic-require
|
||||||
|
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).eslintrc();
|
77
packages/core/build/.neutrinorc.js
Normal file
77
packages/core/build/.neutrinorc.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
const {chmod} = require('fs');
|
||||||
|
const {join} = require('path');
|
||||||
|
|
||||||
|
const airbnb = require('@neutrinojs/airbnb');
|
||||||
|
const banner = require('@neutrinojs/banner');
|
||||||
|
const copy = require('@neutrinojs/copy');
|
||||||
|
const node = require('@neutrinojs/node');
|
||||||
|
const glob = require('glob');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_ROOT = process.cwd(),
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
module.exports = require('../src/bootstrap/fleck.neutrinorc');
|
||||||
|
|
||||||
|
// Dotfiles.
|
||||||
|
module.exports.use.push((neutrino) => {
|
||||||
|
['eslintrc', 'eslint.defaults'].forEach((filename) => {
|
||||||
|
neutrino.config
|
||||||
|
.entry(`build/.${filename}`)
|
||||||
|
.clear()
|
||||||
|
.add(`./src/build/${filename}`);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tests.
|
||||||
|
module.exports.use.push((neutrino) => {
|
||||||
|
// Test entrypoint.
|
||||||
|
const testPaths = glob.sync(join(FLECKS_ROOT, 'test/*.js'));
|
||||||
|
if (testPaths.length > 0) {
|
||||||
|
const testEntry = neutrino.config.entry('test').clear();
|
||||||
|
testPaths.forEach((path) => testEntry.add(path));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.use.unshift((neutrino) => {
|
||||||
|
neutrino.config.plugins.delete('start-server');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.use.unshift(node());
|
||||||
|
|
||||||
|
module.exports.use.unshift(
|
||||||
|
airbnb({
|
||||||
|
eslint: {
|
||||||
|
baseConfig: {
|
||||||
|
...require('../src/build/eslint.defaults'),
|
||||||
|
env: {
|
||||||
|
mocha: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports.use.push(banner({
|
||||||
|
banner: '#!/usr/bin/env node',
|
||||||
|
include: /^cli\.js$/,
|
||||||
|
pluginId: 'shebang',
|
||||||
|
raw: true,
|
||||||
|
}))
|
||||||
|
|
||||||
|
module.exports.use.push(({config}) => {
|
||||||
|
config
|
||||||
|
.plugin('executable')
|
||||||
|
.use(class Executable {
|
||||||
|
|
||||||
|
apply(compiler) {
|
||||||
|
compiler.hooks.afterEmit.tapAsync(
|
||||||
|
'Executable',
|
||||||
|
(compilation, callback) => {
|
||||||
|
chmod(join(__dirname, '..', 'dist', 'cli.js'), 0o755, callback);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
96
packages/core/build/dox/build.md
Normal file
96
packages/core/build/dox/build.md
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# Build directory ⚡️
|
||||||
|
|
||||||
|
The `build` directory is where build-time configuration is specified.
|
||||||
|
|
||||||
|
The prime example of this for Flecks is `flecks.yml`, but it extends to other more general
|
||||||
|
configuration such as `.eslintrc.js`, `babel.config.js`, etc.
|
||||||
|
|
||||||
|
## `flecks.yml` ⛏️
|
||||||
|
|
||||||
|
`flecks.yml` specifies the flecks that compose your project.
|
||||||
|
|
||||||
|
Using `@flecks/create-fleck` creates the following `flecks.yml`:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/fleck': {}
|
||||||
|
```
|
||||||
|
|
||||||
|
This means that by default a new fleck will pull in the `@flecks/core` fleck, and the
|
||||||
|
`@flecks/fleck` fleck, both with default configuration.
|
||||||
|
|
||||||
|
### Overriding configuration 💪
|
||||||
|
|
||||||
|
`@flecks/core`'s configuration has an `id` key. Starting from the example above, overriding the
|
||||||
|
ID to, say, `'example'`, would look like this:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
'@flecks/core':
|
||||||
|
id: 'example'
|
||||||
|
'@flecks/fleck': {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aliasing 🕵️♂️
|
||||||
|
|
||||||
|
Flecks may be aliased to alternative paths.
|
||||||
|
|
||||||
|
Say you have an application structured as a monorepo with a `packages` directory. If you have a
|
||||||
|
subpackage named `@my-monorepo/foo`, you could alias your fleck, like so:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/server': {}
|
||||||
|
'@my-monorepo/foo:./packages/foo/src': {}
|
||||||
|
```
|
||||||
|
|
||||||
|
Within your application, the fleck will be referred to as `@my-monorepo/foo` even though
|
||||||
|
`./packages/foo/src` is where the package is actually located.
|
||||||
|
|
||||||
|
This way you can use package structure without having to worry about actually publishing them to
|
||||||
|
npm (or running verdaccio, for instance).
|
||||||
|
|
||||||
|
## On-the-fly compilation(!) 🤯
|
||||||
|
|
||||||
|
If your flecks are aliased (as above) or symlinked (e.g. `yarn link`), they will be treated
|
||||||
|
specially and will be compiled on-the-fly. The flecks are searched for a local `babel.config.js`,
|
||||||
|
which is used to compile the code if present.
|
||||||
|
|
||||||
|
This means you can e.g. develop your `packages` in a monorepo with full HMR support, on both the
|
||||||
|
server and the client, each with their own babel configuration!
|
||||||
|
|
||||||
|
Have fun!
|
||||||
|
|
||||||
|
## Resolution order 🤔
|
||||||
|
|
||||||
|
The flecks server provides an interface (`flecks.localConfig()`) for gathering configuration files
|
||||||
|
from the `build` directory. The resolution order is determined by a few variables:
|
||||||
|
|
||||||
|
- `filename` specifies the name of the configuration file, e.g. `babel.config.js`.
|
||||||
|
|
||||||
|
- `general` specifies a general variation of the given configuration. `@flecks/server` looks for
|
||||||
|
an overridden `server.neutrinorc.js` when building, however `general` is set to `.neutrinorc.js`,
|
||||||
|
so it will also accept overrides of that more general configuration file.
|
||||||
|
|
||||||
|
- `root` specifies an alternative location to search. Defaults to `FLECKS_ROOT`.
|
||||||
|
|
||||||
|
- `fleck` specifies the fleck owning the configuration. `@flecks/core` owns `babel.config.js`,
|
||||||
|
`@flecks/server` owns `server.neutrinorc.js`, etc. This only really matters if you are writing a
|
||||||
|
fleck that owns its configuration.
|
||||||
|
|
||||||
|
Given these considerations, and supposing we had the above variables set like:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const filename = 'server.neutrinorc.js';
|
||||||
|
const general = '.neutrinorc.js';
|
||||||
|
const root = '/foo/bar/baz';
|
||||||
|
const fleck = '@flecks/server';
|
||||||
|
```
|
||||||
|
|
||||||
|
We would then expect flecks to search using the following resolution order:
|
||||||
|
|
||||||
|
- `/foo/bar/baz/build/server.neutrinorc.js`
|
||||||
|
- `/foo/bar/baz/build/.neutrinorc.js`
|
||||||
|
- `${FLECKS_ROOT}/build/server.neutrinorc.js`
|
||||||
|
- `${FLECKS_ROOT}/build/.neutrinorc.js`
|
||||||
|
- `@flecks/server/build/server.neutrinorc.js`
|
||||||
|
- `@flecks/server/build/.neutrinorc.js`
|
59
packages/core/build/dox/hooks.js
Normal file
59
packages/core/build/dox/hooks.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* Hook into neutrino configuration.
|
||||||
|
* @param {string} target - The build target; e.g. `server`.
|
||||||
|
* @param {Object} config - The neutrino configuration.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/build'] = (target, config) => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alter build configurations after they have been hooked.
|
||||||
|
* @param {Object} configs - The neutrino configurations.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/build/alter'] = (configs) => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define CLI commands.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/commands'] = (program) => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define configuration.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/config'] = () => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alter configuration.
|
||||||
|
* @param {Object} config - The neutrino configuration.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/config/alter'] = (config) => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a gathered class is HMR'd.
|
||||||
|
* @param {constructor} Class - The class.
|
||||||
|
* @param {string} hook - The gather hook; e.g. `@flecks/db/server/models`.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/gathered/hmr'] = (Class, hook) => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a fleck is HMR'd
|
||||||
|
* @param {constructor} Class - The class.
|
||||||
|
* @param {string} hook - The gather hook; e.g. `@flecks/db/server/models`.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/hmr'] = (Class, hook) => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the application is starting. Use for order-independent initialization tasks.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/starting'] = () => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define neutrino build targets.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/targets'] = () => {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook into webpack configuration.
|
||||||
|
* @param {string} target - The build target; e.g. `server`.
|
||||||
|
* @param {Object} config - The neutrino configuration.
|
||||||
|
*/
|
||||||
|
hooks['@flecks/core/webpack'] = (target, config) => {};
|
3
packages/core/build/webpack.config.js
Normal file
3
packages/core/build/webpack.config.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
module.exports = neutrino(require('./.neutrinorc')).webpack();
|
74
packages/core/package.json
Normal file
74
packages/core/package.json
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{
|
||||||
|
"name": "@flecks/core",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "cha0s",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"flecks": "./cli.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "NODE_PATH=./node_modules webpack --config ./build/webpack.config.js --mode production",
|
||||||
|
"clean": "rm -rf dist node_modules yarn.lock && yarn",
|
||||||
|
"lint": "NODE_PATH=./node_modules eslint --config ./build/.eslintrc.js --format codeframe --ext mjs,js .",
|
||||||
|
"test": "npm run-script build && mocha --reporter min --colors ./dist/test.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"build",
|
||||||
|
"build/.eslint.defaults.js",
|
||||||
|
"build/.eslint.defaults.js.map",
|
||||||
|
"build/.eslintrc.js",
|
||||||
|
"build/.eslintrc.js.map",
|
||||||
|
"build/babel.config.js",
|
||||||
|
"build/babel.config.js.map",
|
||||||
|
"build/webpack.config.js",
|
||||||
|
"build/webpack.config.js.map",
|
||||||
|
"cli.js",
|
||||||
|
"cli.js.map",
|
||||||
|
"empty.js",
|
||||||
|
"empty.js.map",
|
||||||
|
"index.js",
|
||||||
|
"index.js.map",
|
||||||
|
"server.js",
|
||||||
|
"server.js.map",
|
||||||
|
"src",
|
||||||
|
"start.js",
|
||||||
|
"start.js.map",
|
||||||
|
"test.js",
|
||||||
|
"test.js.map"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.12.10",
|
||||||
|
"@babel/plugin-proposal-optional-chaining": "^7.12.16",
|
||||||
|
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||||
|
"@babel/preset-env": "^7.12.11",
|
||||||
|
"@babel/register": "^7.12.10",
|
||||||
|
"@neutrinojs/airbnb": "^9.4.0",
|
||||||
|
"@neutrinojs/compile-loader": "^9.5.0",
|
||||||
|
"@neutrinojs/copy": "^9.4.0",
|
||||||
|
"@neutrinojs/node": "^9.1.0",
|
||||||
|
"babel-plugin-prepend": "^1.0.2",
|
||||||
|
"chai": "4.2.0",
|
||||||
|
"commander": "^8.3.0",
|
||||||
|
"debug": "4.3.1",
|
||||||
|
"eslint": "^7.0.0",
|
||||||
|
"eslint-import-resolver-webpack": "0.13.0",
|
||||||
|
"js-yaml": "3.14.0",
|
||||||
|
"lodash.flatten": "^4.4.0",
|
||||||
|
"lodash.get": "^4.4.2",
|
||||||
|
"lodash.intersection": "^4.4.0",
|
||||||
|
"lodash.set": "^4.3.2",
|
||||||
|
"lodash.without": "^4.4.0",
|
||||||
|
"neutrino": "^9.4.0",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"source-map-support": "0.5.19",
|
||||||
|
"webpack": "^4",
|
||||||
|
"webpack-cli": "^3",
|
||||||
|
"webpack-node-externals": "2.5.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@neutrinojs/banner": "^9.4.0",
|
||||||
|
"glob": "^7.2.0",
|
||||||
|
"mocha": "^8.3.2"
|
||||||
|
}
|
||||||
|
}
|
43
packages/core/src/bootstrap/autoentry.js
Normal file
43
packages/core/src/bootstrap/autoentry.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
const {
|
||||||
|
basename,
|
||||||
|
dirname,
|
||||||
|
extname,
|
||||||
|
join,
|
||||||
|
} = require('path');
|
||||||
|
|
||||||
|
const R = require('./require');
|
||||||
|
|
||||||
|
const resolver = (source) => (path) => {
|
||||||
|
// Does the file resolve as source?
|
||||||
|
try {
|
||||||
|
R.resolve(`${source}/${path}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const ext = extname(path);
|
||||||
|
// Try the implicit [path]/index[.ext] variation.
|
||||||
|
try {
|
||||||
|
R.resolve(`${source}/${dirname(path)}/${basename(path, ext)}/index${ext}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = () => (neutrino) => {
|
||||||
|
const {packageJson: {files = []}, source} = neutrino.options;
|
||||||
|
// index is not taken for granted.
|
||||||
|
neutrino.config.entryPoints.delete('index');
|
||||||
|
// Calculate entry points from `files`.
|
||||||
|
files
|
||||||
|
.filter(resolver(source))
|
||||||
|
.forEach((file) => {
|
||||||
|
const trimmed = join(dirname(file), basename(file, extname(file)));
|
||||||
|
neutrino.config
|
||||||
|
.entry(trimmed)
|
||||||
|
.clear()
|
||||||
|
.add(`./src/${trimmed}`);
|
||||||
|
});
|
||||||
|
};
|
29
packages/core/src/bootstrap/fleck.js
Normal file
29
packages/core/src/bootstrap/fleck.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
const nodeExternals = require('webpack-node-externals');
|
||||||
|
|
||||||
|
module.exports = () => (neutrino) => {
|
||||||
|
const {name} = neutrino.options.packageJson;
|
||||||
|
/* eslint-disable indent */
|
||||||
|
neutrino.config
|
||||||
|
.devtool('source-map')
|
||||||
|
.target('node')
|
||||||
|
.optimization
|
||||||
|
.splitChunks(false)
|
||||||
|
.runtimeChunk(false)
|
||||||
|
.end()
|
||||||
|
.output
|
||||||
|
.filename('[name].js')
|
||||||
|
.library(name)
|
||||||
|
.libraryTarget('umd')
|
||||||
|
.umdNamedDefine(true)
|
||||||
|
.end()
|
||||||
|
.node
|
||||||
|
.set('__dirname', false)
|
||||||
|
.set('__filename', false);
|
||||||
|
/* eslint-enable indent */
|
||||||
|
const options = neutrino.config.module
|
||||||
|
.rule('compile')
|
||||||
|
.use('babel')
|
||||||
|
.get('options');
|
||||||
|
options.presets[0][1].targets = {esmodules: true};
|
||||||
|
neutrino.config.externals(nodeExternals({importType: 'umd'}));
|
||||||
|
};
|
36
packages/core/src/bootstrap/fleck.neutrinorc.js
Normal file
36
packages/core/src/bootstrap/fleck.neutrinorc.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
const copy = require('@neutrinojs/copy');
|
||||||
|
|
||||||
|
const autoentry = require('./autoentry');
|
||||||
|
const fleck = require('./fleck');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_ROOT = process.cwd(),
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
options: {
|
||||||
|
output: 'dist',
|
||||||
|
root: FLECKS_ROOT,
|
||||||
|
},
|
||||||
|
use: [
|
||||||
|
copy({
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
from: 'package.json',
|
||||||
|
to: '.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'build',
|
||||||
|
to: 'build',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'src',
|
||||||
|
to: 'src',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pluginId: '@flecks/core/copy',
|
||||||
|
}),
|
||||||
|
autoentry(),
|
||||||
|
fleck(),
|
||||||
|
],
|
||||||
|
};
|
2
packages/core/src/bootstrap/require.js
Normal file
2
packages/core/src/bootstrap/require.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// eslint-disable-next-line no-eval
|
||||||
|
module.exports = eval('"undefined" !== typeof require ? require : undefined');
|
18
packages/core/src/build/babel.config.js
Normal file
18
packages/core/src/build/babel.config.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports = (api) => {
|
||||||
|
api.cache(true);
|
||||||
|
return {
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-proposal-optional-chaining',
|
||||||
|
],
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
exclude: [
|
||||||
|
'@babel/plugin-transform-regenerator',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
23
packages/core/src/build/eslint.defaults.js
Normal file
23
packages/core/src/build/eslint.defaults.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
module.exports = {
|
||||||
|
globals: {
|
||||||
|
__non_webpack_require__: true,
|
||||||
|
window: true,
|
||||||
|
},
|
||||||
|
ignorePatterns: [
|
||||||
|
'**/dist/**',
|
||||||
|
'/build/dox/hooks.js',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'babel/object-curly-spacing': 'off',
|
||||||
|
'brace-style': ['error', 'stroustrup'],
|
||||||
|
'no-plusplus': 'off',
|
||||||
|
'no-shadow': 'off',
|
||||||
|
'padded-blocks': ['error', {classes: 'always'}],
|
||||||
|
yoda: 'off',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
node: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
15
packages/core/src/build/eslintrc.js
Normal file
15
packages/core/src/build/eslintrc.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
const R = require('../bootstrap/require');
|
||||||
|
const {targetNeutrino} = require('../server/commands');
|
||||||
|
const {default: Flecks} = require('../server/flecks');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_BUILD_TARGET = 'fleck',
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
const flecks = Flecks.bootstrap();
|
||||||
|
|
||||||
|
const config = R(process.env[targetNeutrino(FLECKS_BUILD_TARGET)]);
|
||||||
|
flecks.invokeFlat('@flecks/core/build', FLECKS_BUILD_TARGET, config);
|
||||||
|
module.exports = neutrino(config).eslintrc();
|
69
packages/core/src/build/webpack.config.js
Normal file
69
packages/core/src/build/webpack.config.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/* eslint-disable import/first */
|
||||||
|
require('source-map-support/register');
|
||||||
|
|
||||||
|
if ('production' !== process.env.NODE_ENV) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line global-require, import/no-unresolved
|
||||||
|
require('dotenv/config');
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
catch (error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
import D from 'debug';
|
||||||
|
import flatten from 'lodash.flatten';
|
||||||
|
import intersection from 'lodash.intersection';
|
||||||
|
import neutrino from 'neutrino';
|
||||||
|
|
||||||
|
import {targetNeutrino} from '../server/commands';
|
||||||
|
import Flecks from '../server/flecks';
|
||||||
|
|
||||||
|
const debug = D('@flecks/core/build/webpack.config.js');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_BUILD_LIST = '',
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
const buildList = FLECKS_BUILD_LIST
|
||||||
|
.split(',')
|
||||||
|
.map((name) => name.trim())
|
||||||
|
.filter((e) => e);
|
||||||
|
|
||||||
|
const flecks = Flecks.bootstrap();
|
||||||
|
|
||||||
|
const buildConfigs = async () => {
|
||||||
|
debug('gathering configs');
|
||||||
|
let targets = flatten(flecks.invokeFlat('@flecks/core/targets'));
|
||||||
|
if (buildList.length > 0) {
|
||||||
|
targets = intersection(targets, buildList);
|
||||||
|
}
|
||||||
|
debug('building: %O', targets);
|
||||||
|
if (0 === targets.length) {
|
||||||
|
debug('no build configuration found! aborting...');
|
||||||
|
await new Promise(() => {});
|
||||||
|
}
|
||||||
|
const entries = await Promise.all(targets.map(
|
||||||
|
async (target) => [
|
||||||
|
target,
|
||||||
|
await __non_webpack_require__(process.env[targetNeutrino(target)]),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
await Promise.all(
|
||||||
|
entries.map(async ([target, config]) => (
|
||||||
|
flecks.invokeFlat('@flecks/core/build', target, config)
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
const neutrinoConfigs = Object.fromEntries(entries);
|
||||||
|
await Promise.all(flecks.invokeFlat('@flecks/core/build/alter', neutrinoConfigs));
|
||||||
|
const webpackConfigs = await Promise.all(
|
||||||
|
Object.entries(neutrinoConfigs)
|
||||||
|
.map(async ([target, config]) => {
|
||||||
|
const webpackConfig = neutrino(config).webpack();
|
||||||
|
await flecks.invokeFlat('@flecks/core/webpack', target, webpackConfig);
|
||||||
|
return webpackConfig;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return webpackConfigs;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default buildConfigs();
|
105
packages/core/src/cli.js
Executable file
105
packages/core/src/cli.js
Executable file
|
@ -0,0 +1,105 @@
|
||||||
|
import {fork} from 'child_process';
|
||||||
|
import {join, resolve, sep} from 'path';
|
||||||
|
|
||||||
|
import {Command} from 'commander';
|
||||||
|
import D from 'debug';
|
||||||
|
|
||||||
|
import Flecks from './server/flecks';
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_ROOT = process.cwd(),
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
const debug = D('@flecks/core/cli');
|
||||||
|
|
||||||
|
// Guarantee local node_modules path.
|
||||||
|
const defaultNodeModules = resolve(join(FLECKS_ROOT, 'node_modules'));
|
||||||
|
const nodePathSeparator = '/' === sep ? ':' : ';';
|
||||||
|
let updatedNodePath;
|
||||||
|
if (!process.env.NODE_PATH) {
|
||||||
|
updatedNodePath = defaultNodeModules;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const parts = process.env.NODE_PATH.split(nodePathSeparator);
|
||||||
|
if (!parts.some((part) => resolve(part) === defaultNodeModules)) {
|
||||||
|
parts.push(defaultNodeModules);
|
||||||
|
updatedNodePath = parts.join(nodePathSeparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Guarantee symlink preservation for linked flecks.
|
||||||
|
const updateSymlinkPreservation = !process.env.NODE_PRESERVE_SYMLINKS;
|
||||||
|
|
||||||
|
const environmentUpdates = (
|
||||||
|
updatedNodePath
|
||||||
|
|| updateSymlinkPreservation
|
||||||
|
)
|
||||||
|
? {
|
||||||
|
NODE_PATH: updatedNodePath,
|
||||||
|
NODE_PRESERVE_SYMLINKS: 1,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
if (environmentUpdates) {
|
||||||
|
debug('updating environment, forking with %O...', environmentUpdates);
|
||||||
|
const forkOptions = {
|
||||||
|
env: {...process.env, ...environmentUpdates},
|
||||||
|
stdio: 'inherit',
|
||||||
|
};
|
||||||
|
fork(__filename, process.argv.slice(2), forkOptions)
|
||||||
|
.on('exit', (code, signal) => {
|
||||||
|
process.exitCode = code;
|
||||||
|
process.exit(signal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Asynchronous command process code forwarding.
|
||||||
|
const forwardProcessCode = (fn) => async (...args) => {
|
||||||
|
const child = await fn(...args);
|
||||||
|
if ('object' !== typeof child) {
|
||||||
|
debug('action returned code %d', child);
|
||||||
|
process.exitCode = child;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const reject = (error) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error);
|
||||||
|
process.exitCode = child.exitCode || 1;
|
||||||
|
};
|
||||||
|
child.on('error', reject);
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
child.off('error', reject);
|
||||||
|
debug('action exited with code %d', code);
|
||||||
|
process.exitCode = code;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Initialize Commander.
|
||||||
|
const program = new Command();
|
||||||
|
program.enablePositionalOptions();
|
||||||
|
// Bootstrap.
|
||||||
|
debug('bootstrapping flecks...');
|
||||||
|
const flecks = Flecks.bootstrap();
|
||||||
|
debug('bootstrapped');
|
||||||
|
// Register commands.
|
||||||
|
const commands = flecks.invokeReduce('@flecks/core/commands', undefined, undefined, program);
|
||||||
|
const keys = Object.keys(commands);
|
||||||
|
for (let i = 0; i < keys.length; ++i) {
|
||||||
|
const {
|
||||||
|
action,
|
||||||
|
args = [],
|
||||||
|
description,
|
||||||
|
name = keys[i],
|
||||||
|
options = [],
|
||||||
|
} = commands[keys[i]];
|
||||||
|
debug('adding command %s...', name);
|
||||||
|
const cmd = program.command(name);
|
||||||
|
cmd.description(description);
|
||||||
|
for (let i = 0; i < args.length; ++i) {
|
||||||
|
cmd.addArgument(args[i]);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < options.length; ++i) {
|
||||||
|
cmd.option(...options[i]);
|
||||||
|
}
|
||||||
|
cmd.action(forwardProcessCode(action));
|
||||||
|
}
|
||||||
|
// Parse commandline.
|
||||||
|
program.parse(process.argv);
|
||||||
|
}
|
9
packages/core/src/compose.js
Normal file
9
packages/core/src/compose.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export default function compose(...funcs) {
|
||||||
|
if (funcs.length === 0) {
|
||||||
|
return (arg) => arg;
|
||||||
|
}
|
||||||
|
if (funcs.length === 1) {
|
||||||
|
return funcs[0];
|
||||||
|
}
|
||||||
|
return funcs.reduce((a, b) => (...args) => a(b(...args)));
|
||||||
|
}
|
0
packages/core/src/empty.js
Normal file
0
packages/core/src/empty.js
Normal file
16
packages/core/src/ensure-unique-reduction.js
Normal file
16
packages/core/src/ensure-unique-reduction.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default async (flecks, hook, ...args) => {
|
||||||
|
const track = {};
|
||||||
|
return Object.entries(flecks.invoke(hook, ...args))
|
||||||
|
.reduce(async (r, [pkg, impl]) => {
|
||||||
|
const aimpl = await impl;
|
||||||
|
Object.keys(aimpl).forEach((key) => {
|
||||||
|
if (track[key]) {
|
||||||
|
throw new ReferenceError(
|
||||||
|
`Conflict in ${hook}: '${track[key]}' implemented '${key}', followed by '${pkg}'`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
track[key] = pkg;
|
||||||
|
});
|
||||||
|
return {...(await r), ...aimpl};
|
||||||
|
}, {});
|
||||||
|
};
|
126
packages/core/src/event-emitter.js
Normal file
126
packages/core/src/event-emitter.js
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
const createListener = (fn, that, type, once) => ({
|
||||||
|
fn,
|
||||||
|
that,
|
||||||
|
type,
|
||||||
|
once,
|
||||||
|
bound: that ? fn.bind(that) : fn,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function EventEmitterDecorator(Superclass) {
|
||||||
|
|
||||||
|
return class EventEmitter extends Superclass {
|
||||||
|
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this.$$events = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
addListener(typesOrType, fn, that) {
|
||||||
|
return this.on(typesOrType, fn, that);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify ALL the listeners!
|
||||||
|
emit(type, ...args) {
|
||||||
|
const typeListeners = this.$$events[type];
|
||||||
|
if (typeListeners && typeListeners.length > 0) {
|
||||||
|
this.emitToListeners(typeListeners, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitToListeners(listeners, args) {
|
||||||
|
for (let i = 0; i < listeners.length; ++i) {
|
||||||
|
const {
|
||||||
|
once,
|
||||||
|
type,
|
||||||
|
fn,
|
||||||
|
bound,
|
||||||
|
that,
|
||||||
|
} = listeners[i];
|
||||||
|
// Remove if only once.
|
||||||
|
if (once) {
|
||||||
|
this.offSingleEvent(type, fn);
|
||||||
|
}
|
||||||
|
// Fast path...
|
||||||
|
if (0 === args.length) {
|
||||||
|
bound();
|
||||||
|
}
|
||||||
|
else if (1 === args.length) {
|
||||||
|
bound(args[0]);
|
||||||
|
}
|
||||||
|
else if (2 === args.length) {
|
||||||
|
bound(args[0], args[1]);
|
||||||
|
}
|
||||||
|
else if (3 === args.length) {
|
||||||
|
bound(args[0], args[1], args[2]);
|
||||||
|
}
|
||||||
|
else if (4 === args.length) {
|
||||||
|
bound(args[0], args[1], args[2], args[3]);
|
||||||
|
}
|
||||||
|
else if (5 === args.length) {
|
||||||
|
bound(args[0], args[1], args[2], args[3], args[4]);
|
||||||
|
}
|
||||||
|
// Slow path...
|
||||||
|
else {
|
||||||
|
fn.apply(that, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
off(typesOrType, fn) {
|
||||||
|
const types = Array.isArray(typesOrType) ? typesOrType : [typesOrType];
|
||||||
|
for (let i = 0; i < types.length; i++) {
|
||||||
|
this.offSingleEvent(types[i], fn);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
offSingleEvent(type, fn) {
|
||||||
|
if ('function' !== typeof fn) {
|
||||||
|
// Only type.
|
||||||
|
if (type in this.$$events) {
|
||||||
|
this.$$events[type] = [];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Function.
|
||||||
|
if (!(type in this.$$events)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$$events[type] = this.$$events[type].filter((listener) => listener.fn !== fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
on(typesOrType, fn, that = undefined) {
|
||||||
|
this.$$on(typesOrType, fn, that, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$$on(typesOrType, fn, that, once) {
|
||||||
|
const types = Array.isArray(typesOrType) ? typesOrType : [typesOrType];
|
||||||
|
for (let i = 0; i < types.length; i++) {
|
||||||
|
this.onSingleEvent(types[i], fn, that, once);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
once(types, fn, that = undefined) {
|
||||||
|
this.$$on(types, fn, that, true);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSingleEvent(type, fn, that, once) {
|
||||||
|
if ('function' !== typeof fn) {
|
||||||
|
throw new TypeError('EventEmitter::onSingleEvent() requires function listener');
|
||||||
|
}
|
||||||
|
const listener = createListener(fn, that, type, once);
|
||||||
|
if (!(type in this.$$events)) {
|
||||||
|
this.$$events[type] = [];
|
||||||
|
}
|
||||||
|
this.$$events[type].push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeListener(...args) {
|
||||||
|
return this.off(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
480
packages/core/src/flecks.js
Normal file
480
packages/core/src/flecks.js
Normal file
|
@ -0,0 +1,480 @@
|
||||||
|
// eslint-disable-next-line max-classes-per-file
|
||||||
|
import {
|
||||||
|
basename,
|
||||||
|
dirname,
|
||||||
|
extname,
|
||||||
|
join,
|
||||||
|
} from 'path';
|
||||||
|
|
||||||
|
import D from 'debug';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
import set from 'lodash.set';
|
||||||
|
import without from 'lodash.without';
|
||||||
|
|
||||||
|
import Middleware from './middleware';
|
||||||
|
|
||||||
|
const debug = D('@flecks/core/flecks');
|
||||||
|
|
||||||
|
export const ById = Symbol.for('@flecks/core/byId');
|
||||||
|
export const ByType = Symbol.for('@flecks/core/byType');
|
||||||
|
export const Hooks = Symbol.for('@flecks/core/hooks');
|
||||||
|
|
||||||
|
const capitalize = (string) => string.substring(0, 1).toUpperCase() + string.substring(1);
|
||||||
|
|
||||||
|
const camelCase = (string) => string.split(/[_-]/).map(capitalize).join('');
|
||||||
|
|
||||||
|
const hotGathered = new Map();
|
||||||
|
|
||||||
|
const wrapperClass = (Class, id, idAttribute, type, typeAttribute) => {
|
||||||
|
class Subclass extends Class {
|
||||||
|
|
||||||
|
static get [idAttribute]() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get [typeAttribute]() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return Subclass;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Flecks {
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
config = {},
|
||||||
|
flecks = {},
|
||||||
|
platforms = [],
|
||||||
|
} = {}) {
|
||||||
|
this.originalConfig = JSON.parse(JSON.stringify(config));
|
||||||
|
this.config = {
|
||||||
|
...Object.fromEntries(Object.keys(flecks).map((path) => [path, {}])),
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
this.hooks = {};
|
||||||
|
this.flecks = {};
|
||||||
|
this.platforms = platforms;
|
||||||
|
const entries = Object.entries(flecks);
|
||||||
|
debug('paths: %O', entries.map(([fleck]) => fleck));
|
||||||
|
for (let i = 0; i < entries.length; i++) {
|
||||||
|
const [fleck, M] = entries[i];
|
||||||
|
this.registerFleck(fleck, M);
|
||||||
|
}
|
||||||
|
this.introduceConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
static decorate(
|
||||||
|
context,
|
||||||
|
{
|
||||||
|
transformer = camelCase,
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
return (Gathered, flecks) => {
|
||||||
|
context.keys()
|
||||||
|
.forEach((path) => {
|
||||||
|
const {default: M} = context(path);
|
||||||
|
if ('function' !== typeof M) {
|
||||||
|
throw new ReferenceError(
|
||||||
|
`Flecks.decorate(): require(${
|
||||||
|
path
|
||||||
|
}).default is not a function (from: ${
|
||||||
|
context.id
|
||||||
|
})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const key = transformer(this.symbolizePath(path));
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
Gathered[key] = M(Gathered[key], flecks);
|
||||||
|
});
|
||||||
|
return Gathered;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedFlecks(hook) {
|
||||||
|
const flecks = this.lookupFlecks(hook);
|
||||||
|
let expanded = [];
|
||||||
|
for (let i = 0; i < flecks.length; ++i) {
|
||||||
|
const fleck = flecks[i];
|
||||||
|
expanded.push(fleck);
|
||||||
|
for (let j = 0; j < this.platforms.length; ++j) {
|
||||||
|
const platform = this.platforms[j];
|
||||||
|
const variant = join(fleck, platform);
|
||||||
|
if (this.fleck(variant)) {
|
||||||
|
expanded.push(variant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const index = expanded.findIndex((fleck) => '...' === fleck);
|
||||||
|
if (-1 !== index) {
|
||||||
|
if (-1 !== expanded.slice(index + 1).findIndex((fleck) => '...' === fleck)) {
|
||||||
|
throw new Error(
|
||||||
|
`Illegal ordering specification: hook '${hook}' has multiple ellipses.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const before = expanded.slice(0, index);
|
||||||
|
const after = expanded.slice(index + 1);
|
||||||
|
const implementing = this.flecksImplementing(hook);
|
||||||
|
const all = [];
|
||||||
|
for (let i = 0; i < implementing.length; ++i) {
|
||||||
|
const fleck = implementing[i];
|
||||||
|
all.push(fleck);
|
||||||
|
for (let j = 0; j < this.platforms.length; ++j) {
|
||||||
|
const platform = this.platforms[j];
|
||||||
|
const variant = join(fleck, platform);
|
||||||
|
if (this.fleck(variant)) {
|
||||||
|
all.push(variant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const rest = without(all, ...before.concat(after));
|
||||||
|
expanded = [...before, ...rest, ...after];
|
||||||
|
}
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
fleck(fleck) {
|
||||||
|
return this.flecks[fleck];
|
||||||
|
}
|
||||||
|
|
||||||
|
fleckImplements(fleck, hook) {
|
||||||
|
return !!this.hooks[hook].find(({fleck: candidate}) => fleck === candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
flecksImplementing(hook) {
|
||||||
|
return this.hooks[hook]?.map(({fleck}) => fleck) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
gather(
|
||||||
|
hook,
|
||||||
|
{
|
||||||
|
idAttribute = 'id',
|
||||||
|
typeAttribute = 'type',
|
||||||
|
check = () => {},
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
if (!hook || 'string' !== typeof hook) {
|
||||||
|
throw new TypeError('Flecks.gather(): Expects parameter 1 (hook) to be string');
|
||||||
|
}
|
||||||
|
const raw = this.invokeReduce(hook);
|
||||||
|
check(raw, hook);
|
||||||
|
const decorated = this.invokeComposed(`${hook}.decorate`, raw);
|
||||||
|
check(decorated, `${hook}.decorate`);
|
||||||
|
let uid = 1;
|
||||||
|
const ids = {};
|
||||||
|
const types = (
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(decorated)
|
||||||
|
.sort(([ltype], [rtype]) => (ltype < rtype ? -1 : 1))
|
||||||
|
.map(([type, Class]) => {
|
||||||
|
const id = uid++;
|
||||||
|
ids[id] = wrapperClass(Class, id, idAttribute, type, typeAttribute);
|
||||||
|
return [type, ids[id]];
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const gathered = {
|
||||||
|
...ids,
|
||||||
|
...types,
|
||||||
|
[ById]: ids,
|
||||||
|
[ByType]: types,
|
||||||
|
};
|
||||||
|
hotGathered.set(hook, {idAttribute, gathered, typeAttribute});
|
||||||
|
debug("gathered '%s': %O", hook, gathered);
|
||||||
|
return gathered;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(path, defaultValue) {
|
||||||
|
return get(this.config, path, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
introduceConfig() {
|
||||||
|
const defaultConfig = this.invoke('@flecks/core/config');
|
||||||
|
this.invokeFlat('@flecks/core/config/alter', defaultConfig);
|
||||||
|
const flecks = Object.keys(defaultConfig);
|
||||||
|
for (let i = 0; i < flecks.length; i++) {
|
||||||
|
const fleck = flecks[i];
|
||||||
|
this.config[fleck] = {
|
||||||
|
...defaultConfig[fleck],
|
||||||
|
...this.config[fleck],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
debug('config: %O', this.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(hook, ...args) {
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.flecksImplementing(hook)
|
||||||
|
.reduce((r, fleck) => ({
|
||||||
|
...r,
|
||||||
|
[fleck]: this.invokeFleck(hook, fleck, ...args),
|
||||||
|
}), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeComposed(hook, arg, ...args) {
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
const flecks = this.expandedFlecks(hook);
|
||||||
|
if (0 === flecks.length) {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
return flecks
|
||||||
|
.filter((fleck) => this.fleckImplements(fleck, hook))
|
||||||
|
.reduce((r, fleck) => this.invokeFleck(hook, fleck, r, ...args), arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeFlat(hook, ...args) {
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.hooks[hook].map(({fleck}) => this.invokeFleck(hook, fleck, ...args));
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeFleck(hook, fleck, ...args) {
|
||||||
|
debug('invokeFleck(%s, %s, ...)', hook, fleck);
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const candidate = this.hooks[hook]
|
||||||
|
.find(({fleck: candidate}) => candidate === fleck);
|
||||||
|
if (!candidate) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return candidate.fn(...(args.concat(this)));
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeParallel(hook, ...args) {
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const flecks = this.flecksImplementing(hook);
|
||||||
|
if (0 === flecks.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const results = [];
|
||||||
|
for (let i = 0; i < flecks.length; ++i) {
|
||||||
|
results.push(this.invokeFleck(hook, flecks[i], ...(args.concat(this))));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeReduce(hook, initial = {}, reducer = (r, o) => ({...r, ...o}), ...args) {
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return initial;
|
||||||
|
}
|
||||||
|
return this.hooks[hook]
|
||||||
|
.reduce((r, {fleck}) => reducer(r, this.invokeFleck(hook, fleck, ...args)), initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
async invokeReduceAsync(hook, initial = {}, reducer = (r, o) => ({...r, ...o}), ...args) {
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return initial;
|
||||||
|
}
|
||||||
|
return this.hooks[hook]
|
||||||
|
.reduce(
|
||||||
|
async (r, {fleck}) => reducer(await r, await this.invokeFleck(hook, fleck, ...args)),
|
||||||
|
initial,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeSequential(hook, ...args) {
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const flecks = this.expandedFlecks(hook);
|
||||||
|
if (0 === flecks.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const results = [];
|
||||||
|
while (flecks.length > 0) {
|
||||||
|
const fleck = flecks.shift();
|
||||||
|
if (this.fleckImplements(fleck, hook)) {
|
||||||
|
results.push(this.invokeFleck(hook, fleck, ...args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async invokeSequentialAsync(hook, ...args) {
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const flecks = this.expandedFlecks(hook);
|
||||||
|
if (0 === flecks.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const results = [];
|
||||||
|
while (flecks.length > 0) {
|
||||||
|
const fleck = flecks.shift();
|
||||||
|
if (this.fleckImplements(fleck, hook)) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
results.push(await this.invokeFleck(hook, fleck, ...args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOnPlatform(platform) {
|
||||||
|
return -1 !== this.platforms.indexOf(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
lookupFlecks(hook) {
|
||||||
|
const parts = hook.split('/');
|
||||||
|
const key = parts.pop();
|
||||||
|
return this.config[parts.join('/')]?.[key]?.concat() || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
makeMiddleware(hook) {
|
||||||
|
debug('makeMiddleware(...): %s', hook);
|
||||||
|
if (!this.hooks[hook]) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const flecks = this.expandedFlecks(hook);
|
||||||
|
if (0 === flecks.length) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const middleware = flecks
|
||||||
|
.filter((fleck) => this.fleckImplements(fleck, hook));
|
||||||
|
debug('middleware: %O', middleware);
|
||||||
|
const instance = new Middleware(middleware.map((fleck) => this.invokeFleck(hook, fleck)));
|
||||||
|
return async (...args) => {
|
||||||
|
const next = args.pop();
|
||||||
|
try {
|
||||||
|
await instance.promise(...args);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static provide(
|
||||||
|
context,
|
||||||
|
{
|
||||||
|
transformer = camelCase,
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
return (flecks) => (
|
||||||
|
Object.fromEntries(
|
||||||
|
context.keys()
|
||||||
|
.map((path) => {
|
||||||
|
const {default: M} = context(path);
|
||||||
|
if ('function' !== typeof M) {
|
||||||
|
throw new ReferenceError(
|
||||||
|
`Flecks.provide(): require(${
|
||||||
|
path
|
||||||
|
}).default is not a function (from: ${
|
||||||
|
context.id
|
||||||
|
})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
transformer(this.symbolizePath(path)),
|
||||||
|
M(flecks),
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(fleck, M) {
|
||||||
|
debug('refreshing %s...', fleck);
|
||||||
|
// Remove old hook implementations.
|
||||||
|
const keys = Object.keys(this.hooks);
|
||||||
|
for (let j = 0; j < keys.length; j++) {
|
||||||
|
const key = keys[j];
|
||||||
|
if (this.hooks[key]) {
|
||||||
|
const index = this.hooks[key].findIndex(({fleck: hookPlugin}) => hookPlugin === fleck);
|
||||||
|
if (-1 !== index) {
|
||||||
|
this.hooks[key].splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Replace the fleck.
|
||||||
|
this.registerFleck(fleck, M);
|
||||||
|
// Write config.
|
||||||
|
const defaultConfig = this.invoke('@flecks/core/config');
|
||||||
|
this.config[fleck] = {
|
||||||
|
...defaultConfig[fleck],
|
||||||
|
...this.config[fleck],
|
||||||
|
};
|
||||||
|
this.invokeFlat('@flecks/core/config/alter', this.config);
|
||||||
|
// HMR.
|
||||||
|
this.updateHotGathered(fleck);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerFleck(fleck, M) {
|
||||||
|
debug('registering %s...', fleck);
|
||||||
|
this.flecks[fleck] = M;
|
||||||
|
if (M.default) {
|
||||||
|
const {default: {[Hooks]: hooks}} = M;
|
||||||
|
if (hooks) {
|
||||||
|
const keys = Object.keys(hooks);
|
||||||
|
debug("hooks for '%s': %O", fleck, keys);
|
||||||
|
for (let j = 0; j < keys.length; j++) {
|
||||||
|
const key = keys[j];
|
||||||
|
if (!this.hooks[key]) {
|
||||||
|
this.hooks[key] = [];
|
||||||
|
}
|
||||||
|
this.hooks[key].push({fleck, fn: hooks[key]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug("'%s' has no default export: %O", fleck, M);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set(path, value) {
|
||||||
|
return set(this.config, path, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static symbolizePath(path) {
|
||||||
|
const parts = dirname(path).split('/');
|
||||||
|
if ('.' === parts[0]) {
|
||||||
|
parts.shift();
|
||||||
|
}
|
||||||
|
if ('index' === parts[parts.length - 1]) {
|
||||||
|
parts.pop();
|
||||||
|
}
|
||||||
|
return join(parts.join('-'), basename(path, extname(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
async up(hook) {
|
||||||
|
await Promise.all(this.invokeFlat('@flecks/core/starting'));
|
||||||
|
await this.invokeSequentialAsync(hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHotGathered(fleck) {
|
||||||
|
const it = hotGathered.entries();
|
||||||
|
for (let current = it.next(); current.done !== true; current = it.next()) {
|
||||||
|
const {
|
||||||
|
value: [
|
||||||
|
hook,
|
||||||
|
{
|
||||||
|
idAttribute,
|
||||||
|
gathered,
|
||||||
|
typeAttribute,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} = current;
|
||||||
|
const updates = this.invokeFleck(hook, fleck);
|
||||||
|
if (updates) {
|
||||||
|
debug('updating gathered %s from %s...', hook, fleck);
|
||||||
|
const entries = Object.entries(updates);
|
||||||
|
for (let i = 0, [type, Class] = entries[i]; i < entries.length; ++i) {
|
||||||
|
const {[type]: {[idAttribute]: id}} = gathered;
|
||||||
|
const Subclass = wrapperClass(Class, id, idAttribute, type, typeAttribute);
|
||||||
|
// eslint-disable-next-line no-multi-assign
|
||||||
|
gathered[type] = gathered[id] = gathered[ById][id] = gathered[ByType][type] = Subclass;
|
||||||
|
this.invoke('@flecks/core/gathered/hmr', Subclass, hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
packages/core/src/index.js
Normal file
22
packages/core/src/index.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {Hooks} from './flecks';
|
||||||
|
|
||||||
|
export {default as autoentry} from './bootstrap/autoentry';
|
||||||
|
export {default as fleck} from './bootstrap/fleck';
|
||||||
|
export {default as compose} from './compose';
|
||||||
|
export {default as ensureUniqueReduction} from './ensure-unique-reduction';
|
||||||
|
export {default as EventEmitter} from './event-emitter';
|
||||||
|
export {
|
||||||
|
default as Flecks,
|
||||||
|
ById,
|
||||||
|
ByType,
|
||||||
|
Hooks,
|
||||||
|
} from './flecks';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Hooks]: {
|
||||||
|
'@flecks/core/config': () => ({
|
||||||
|
'eslint.exclude': [],
|
||||||
|
id: 'flecks',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
64
packages/core/src/middleware.js
Normal file
64
packages/core/src/middleware.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
export default class Middleware {
|
||||||
|
|
||||||
|
constructor(middleware = []) {
|
||||||
|
this.middleware = [];
|
||||||
|
for (let i = 0; i < middleware.length; ++i) {
|
||||||
|
this.middleware.push(this.constructor.check(middleware[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static check(middleware) {
|
||||||
|
if ('function' !== typeof middleware) {
|
||||||
|
if ('undefined' !== typeof middleware.then) {
|
||||||
|
throw new TypeError('middleware expected a function, looks like a promise');
|
||||||
|
}
|
||||||
|
throw new TypeError('middleware expected a function');
|
||||||
|
}
|
||||||
|
return middleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(...args) {
|
||||||
|
const fn = args.pop();
|
||||||
|
const middleware = this.middleware.concat();
|
||||||
|
const invoke = (error) => {
|
||||||
|
if (middleware.length > 0) {
|
||||||
|
const current = middleware.shift();
|
||||||
|
// Check mismatch.
|
||||||
|
if ((args.length + 2 === current.length) === !error) {
|
||||||
|
invoke(error);
|
||||||
|
}
|
||||||
|
// Invoke.
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
current(...args.concat(error ? [error] : []).concat(invoke));
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
invoke(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finish...
|
||||||
|
else if (fn) {
|
||||||
|
setTimeout(() => fn(error), 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
promise(...args) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.dispatch(...(args.concat((error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
use(fn) {
|
||||||
|
this.middleware.push(this.constructor.check(fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
171
packages/core/src/server/commands.js
Normal file
171
packages/core/src/server/commands.js
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import {spawn} from 'child_process';
|
||||||
|
import {join, normalize} from 'path';
|
||||||
|
|
||||||
|
import {Argument} from 'commander';
|
||||||
|
import D from 'debug';
|
||||||
|
import flatten from 'lodash.flatten';
|
||||||
|
import rimraf from 'rimraf';
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_ROOT = process.cwd(),
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
const debug = D('@flecks/core/commands');
|
||||||
|
const flecksRoot = normalize(FLECKS_ROOT);
|
||||||
|
|
||||||
|
export const spawnWith = (cmd, localEnv, spawnArgs) => {
|
||||||
|
debug('spawning:\n%s %s\nwith local environment: %O', cmd, spawnArgs.join(' '), localEnv);
|
||||||
|
const spawnOptions = {
|
||||||
|
env: {...localEnv, ...process.env},
|
||||||
|
stdio: 'inherit',
|
||||||
|
};
|
||||||
|
return spawn('npx', [cmd, ...spawnArgs], spawnOptions);
|
||||||
|
};
|
||||||
|
export const targetNeutrino = (target) => (
|
||||||
|
`FLECKS_CORE_BUILD_TARGET_${
|
||||||
|
target
|
||||||
|
.toUpperCase()
|
||||||
|
.replace(/[^A-Za-z0-9]/g, '_')
|
||||||
|
}_NEUTRINO`
|
||||||
|
);
|
||||||
|
|
||||||
|
export const targetNeutrinos = (flecks) => {
|
||||||
|
const entries = Object.entries(flecks.invoke('@flecks/core/targets'));
|
||||||
|
const targetNeutrinos = {};
|
||||||
|
for (let i = 0; i < entries.length; ++i) {
|
||||||
|
const [fleck, targets] = entries[i];
|
||||||
|
targets
|
||||||
|
.forEach((target) => {
|
||||||
|
targetNeutrinos[targetNeutrino(target)] = flecks.localConfig(
|
||||||
|
`${target}.neutrinorc.js`,
|
||||||
|
fleck,
|
||||||
|
{general: '.neutrinorc.js'},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return targetNeutrinos;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (program, flecks) => {
|
||||||
|
Object.entries(targetNeutrinos(flecks))
|
||||||
|
.forEach(([key, value]) => {
|
||||||
|
process.env[key] = value;
|
||||||
|
});
|
||||||
|
const commands = {
|
||||||
|
clean: {
|
||||||
|
description: 'remove node_modules, lock file, build artifacts, then reinstall',
|
||||||
|
action: (opts) => {
|
||||||
|
const {
|
||||||
|
noYarn,
|
||||||
|
} = opts;
|
||||||
|
rimraf.sync(join(flecksRoot, 'dist'));
|
||||||
|
rimraf.sync(join(flecksRoot, 'node_modules'));
|
||||||
|
if (noYarn) {
|
||||||
|
rimraf.sync(join(flecksRoot, 'package-lock.json'));
|
||||||
|
return spawn('npm', ['install'], {stdio: 'inherit'});
|
||||||
|
}
|
||||||
|
rimraf.sync(join(flecksRoot, 'yarn.lock'));
|
||||||
|
return spawn('yarn', [], {stdio: 'inherit'});
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
['--no-yarn', 'use npm instead of yarn'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const targets = flatten(flecks.invokeFlat('@flecks/core/targets'));
|
||||||
|
if (targets.length > 0) {
|
||||||
|
commands.build = {
|
||||||
|
args: [
|
||||||
|
new Argument('[target]', 'target').choices(targets),
|
||||||
|
],
|
||||||
|
options: [
|
||||||
|
['-d, --no-production', 'dev build'],
|
||||||
|
['-h, --hot', 'build with hot module reloading'],
|
||||||
|
['-w, --watch', 'watch for changes'],
|
||||||
|
['-v, --verbose', 'verbose output'],
|
||||||
|
],
|
||||||
|
description: 'build',
|
||||||
|
action: (target, opts) => {
|
||||||
|
const {
|
||||||
|
hot,
|
||||||
|
production,
|
||||||
|
watch,
|
||||||
|
verbose,
|
||||||
|
} = opts;
|
||||||
|
debug('Building...', opts);
|
||||||
|
const webpackConfig = flecks.localConfig('webpack.config.js', '@flecks/core');
|
||||||
|
const localEnv = {
|
||||||
|
...targetNeutrinos(flecks),
|
||||||
|
...(target ? {FLECKS_BUILD_LIST: target} : {}),
|
||||||
|
...(hot ? {FLECKS_HOT: 1} : {}),
|
||||||
|
};
|
||||||
|
const spawnArgs = [
|
||||||
|
'--config', webpackConfig,
|
||||||
|
'--mode', (production && !hot) ? 'production' : 'development',
|
||||||
|
...(verbose ? ['--stats', 'verbose'] : []),
|
||||||
|
...((watch || hot) ? ['--watch'] : []),
|
||||||
|
];
|
||||||
|
return spawnWith('webpack', localEnv, spawnArgs);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
commands.lint = {
|
||||||
|
description: 'run linter',
|
||||||
|
args: [
|
||||||
|
program.createArgument('[target]', 'target').choices(targets),
|
||||||
|
],
|
||||||
|
action: (targetArgument) => {
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 0; i < targets.length; ++i) {
|
||||||
|
const target = targets[i];
|
||||||
|
if (targetArgument && targetArgument !== target) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
process.env.FLECKS_BUILD_TARGET = target;
|
||||||
|
const spawnArgs = [
|
||||||
|
'--config', flecks.localConfig(
|
||||||
|
`${target}.eslintrc.js`,
|
||||||
|
'@flecks/core',
|
||||||
|
{general: '.eslintrc.js'},
|
||||||
|
),
|
||||||
|
'--format', 'codeframe',
|
||||||
|
'--ext', 'js',
|
||||||
|
'.',
|
||||||
|
];
|
||||||
|
const localEnv = {
|
||||||
|
FLECKS_BUILD_TARGET: target,
|
||||||
|
...targetNeutrinos(flecks),
|
||||||
|
};
|
||||||
|
promises.push(new Promise((resolve, reject) => {
|
||||||
|
const child = spawnWith('eslint', localEnv, spawnArgs);
|
||||||
|
child.on('error', reject);
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
child.off('error', reject);
|
||||||
|
resolve(code);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const promise = Promise.all(promises)
|
||||||
|
.then(
|
||||||
|
(codes) => (
|
||||||
|
codes.every((code) => 0 === parseInt(code, 10))
|
||||||
|
? 0
|
||||||
|
: codes.find((code) => code !== 0)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
off: () => {},
|
||||||
|
on: (type, fn) => {
|
||||||
|
if ('error' === type) {
|
||||||
|
promise.catch(fn);
|
||||||
|
}
|
||||||
|
else if ('exit' === type) {
|
||||||
|
promise.then(fn);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return commands;
|
||||||
|
};
|
463
packages/core/src/server/flecks.js
Normal file
463
packages/core/src/server/flecks.js
Normal file
|
@ -0,0 +1,463 @@
|
||||||
|
import {
|
||||||
|
readFileSync,
|
||||||
|
realpathSync,
|
||||||
|
statSync,
|
||||||
|
} from 'fs';
|
||||||
|
import {
|
||||||
|
basename,
|
||||||
|
dirname,
|
||||||
|
extname,
|
||||||
|
join,
|
||||||
|
} from 'path';
|
||||||
|
|
||||||
|
import compileLoader from '@neutrinojs/compile-loader';
|
||||||
|
import D from 'debug';
|
||||||
|
|
||||||
|
import R from '../bootstrap/require';
|
||||||
|
import Flecks from '../flecks';
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_ROOT = process.cwd(),
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
const debug = D('@flecks/core/flecks/server');
|
||||||
|
|
||||||
|
export default class ServerFlecks extends Flecks {
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
|
const {
|
||||||
|
resolver = {},
|
||||||
|
rcs = {},
|
||||||
|
} = options;
|
||||||
|
this.resolver = resolver;
|
||||||
|
this.rcs = rcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
aliases() {
|
||||||
|
return this.constructor.aliases(this.rcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static aliases(rcs) {
|
||||||
|
const keys = Object.keys(rcs);
|
||||||
|
let aliases = {};
|
||||||
|
for (let i = 0; i < keys.length; ++i) {
|
||||||
|
const key = keys[i];
|
||||||
|
const config = rcs[key];
|
||||||
|
if (config.aliases && Object.keys(config.aliases).length > 0) {
|
||||||
|
aliases = {...aliases, ...config.aliases};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bootstrap({platforms = ['server'], without = []} = {}) {
|
||||||
|
let initial;
|
||||||
|
let configType;
|
||||||
|
try {
|
||||||
|
const {safeLoad} = R('js-yaml');
|
||||||
|
const filename = join(FLECKS_ROOT, 'build', 'flecks.yml');
|
||||||
|
const buffer = readFileSync(filename, 'utf8');
|
||||||
|
debug('parsing configuration from YML...');
|
||||||
|
initial = safeLoad(buffer, {filename}) || {};
|
||||||
|
configType = 'YML';
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if ('ENOENT' !== error.code) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
initial = {'@flecks/core': {}};
|
||||||
|
configType = 'barebones';
|
||||||
|
}
|
||||||
|
debug('bootstrap configuration (%s): %O', configType, initial);
|
||||||
|
// Fleck discovery.
|
||||||
|
const aliased = {};
|
||||||
|
const config = {};
|
||||||
|
const resolver = {};
|
||||||
|
const keys = Object.keys(initial);
|
||||||
|
for (let i = 0; i < keys.length; ++i) {
|
||||||
|
const key = keys[i];
|
||||||
|
const index = key.lastIndexOf(':');
|
||||||
|
const [path, alias] = -1 === index ? [key, key] : [key.slice(0, index), key.slice(index + 1)];
|
||||||
|
if (-1 !== without.indexOf(path.split('/').pop())) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const aliasPath = '.'.charCodeAt(0) === alias.charCodeAt(0)
|
||||||
|
? join(FLECKS_ROOT, alias)
|
||||||
|
: alias;
|
||||||
|
try {
|
||||||
|
config[path] = initial[key];
|
||||||
|
R.resolve(aliasPath);
|
||||||
|
if (path !== alias) {
|
||||||
|
aliased[path] = aliasPath;
|
||||||
|
}
|
||||||
|
resolver[path] = aliasPath;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
catch (error) {}
|
||||||
|
// Discover platform-specific variants.
|
||||||
|
if (platforms) {
|
||||||
|
platforms.forEach((platform) => {
|
||||||
|
try {
|
||||||
|
const platformAliasPath = join(aliasPath, platform);
|
||||||
|
const platformPath = join(path, platform);
|
||||||
|
R.resolve(platformAliasPath);
|
||||||
|
if (path !== alias) {
|
||||||
|
aliased[platformPath] = platformAliasPath;
|
||||||
|
}
|
||||||
|
config[platformPath] = config[platformPath] || {};
|
||||||
|
resolver[platformPath] = platformAliasPath;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
catch (error) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const paths = Object.keys(resolver);
|
||||||
|
const rcs = {};
|
||||||
|
const roots = Array.from(new Set(
|
||||||
|
paths
|
||||||
|
.map((path) => this.root(resolver, path))
|
||||||
|
.filter((e) => !!e),
|
||||||
|
));
|
||||||
|
for (let i = 0; i < roots.length; ++i) {
|
||||||
|
const root = roots[i];
|
||||||
|
try {
|
||||||
|
rcs[root] = R(join(root, 'build', '.flecksrc'));
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if ('MODULE_NOT_FOUND' !== error.code) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Stub platform-unfriendly modules.
|
||||||
|
const stubs = this.stubs(platforms, rcs);
|
||||||
|
if (stubs.length > 0) {
|
||||||
|
debug('stubbing: %O', stubs);
|
||||||
|
const regex = new RegExp(stubs.join('|'));
|
||||||
|
R('pirates').addHook(
|
||||||
|
() => '',
|
||||||
|
{
|
||||||
|
ignoreNodeModules: false,
|
||||||
|
matcher: (path) => !!path.match(regex),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Flecks that are aliased or symlinked need compilation.
|
||||||
|
const flecks = {};
|
||||||
|
const needCompilation = paths
|
||||||
|
.filter((path) => (
|
||||||
|
this.fleckIsAliased(resolver, path) || this.fleckIsSymlinked(resolver, path)
|
||||||
|
));
|
||||||
|
// Lookups redirect require() requests.
|
||||||
|
const lookups = {
|
||||||
|
...Object.fromEntries(
|
||||||
|
needCompilation
|
||||||
|
.map((path) => [
|
||||||
|
R.resolve(this.fleckIsAliased(resolver, path) ? aliased[path] : path),
|
||||||
|
this.fleckIsAliased(resolver, path)
|
||||||
|
? aliased[path]
|
||||||
|
: this.sourcepath(R.resolve(this.resolve(resolver, path))),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
debug('lookups: %O', lookups);
|
||||||
|
R('pirates').addHook(
|
||||||
|
(code, path) => `module.exports = require('${lookups[path]}')`,
|
||||||
|
{
|
||||||
|
ignoreNodeModules: false,
|
||||||
|
// eslint-disable-next-line arrow-body-style
|
||||||
|
matcher: (path) => {
|
||||||
|
return !!lookups[path];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const {Module} = R('module');
|
||||||
|
const aliases = this.aliases(rcs);
|
||||||
|
debug('aliases: %O', aliases);
|
||||||
|
// Nasty hax to give us FULL CONTROL.
|
||||||
|
const {require: Mr} = Module.prototype;
|
||||||
|
const requirers = {
|
||||||
|
...aliased,
|
||||||
|
...aliases,
|
||||||
|
};
|
||||||
|
Module.prototype.require = function hackedRequire(request, options) {
|
||||||
|
if (requirers[request]) {
|
||||||
|
return Mr.call(this, requirers[request], options);
|
||||||
|
}
|
||||||
|
return Mr.call(this, request, options);
|
||||||
|
};
|
||||||
|
// Key flecks needing compilation by their roots, so we can compile all common roots with a
|
||||||
|
// single invocation of `@babel/register`.
|
||||||
|
const compilationRootMap = {};
|
||||||
|
needCompilation.forEach((fleck) => {
|
||||||
|
const root = this.root(resolver, fleck);
|
||||||
|
if (!compilationRootMap[root]) {
|
||||||
|
compilationRootMap[root] = [];
|
||||||
|
}
|
||||||
|
compilationRootMap[root].push(fleck);
|
||||||
|
});
|
||||||
|
// Register a compiler for each root and require() the flecks underneath.
|
||||||
|
Object.entries(compilationRootMap).forEach(([root, compiling]) => {
|
||||||
|
const resolved = dirname(R.resolve(join(root, 'package.json')));
|
||||||
|
const configFile = this.localConfig(
|
||||||
|
resolver,
|
||||||
|
'babel.config.js',
|
||||||
|
'@flecks/core',
|
||||||
|
{root: realpathSync(resolved)},
|
||||||
|
);
|
||||||
|
const register = R('@babel/register');
|
||||||
|
register({
|
||||||
|
cache: true,
|
||||||
|
configFile,
|
||||||
|
only: [this.sourcepath(resolved)],
|
||||||
|
// Make webpack goodies exist in node land.
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
'prepend',
|
||||||
|
{
|
||||||
|
prepend: [
|
||||||
|
'require.context = (',
|
||||||
|
' directory,',
|
||||||
|
' useSubdirectories = true,',
|
||||||
|
' regExp = /^\\.\\/.*$/,',
|
||||||
|
' mode = "sync",',
|
||||||
|
') => {',
|
||||||
|
' const glob = require("glob");',
|
||||||
|
' const {resolve, sep} = require("path");',
|
||||||
|
' const keys = glob.sync(',
|
||||||
|
' useSubdirectories ? "**/*" : "*",',
|
||||||
|
' {cwd: resolve(__dirname, directory)},',
|
||||||
|
' )',
|
||||||
|
' .filter((key) => key.match(regExp))',
|
||||||
|
' .map(',
|
||||||
|
' (key) => (',
|
||||||
|
' -1 !== [".".charCodeAt(0), "/".charCodeAt(0)].indexOf(key.charCodeAt(0))',
|
||||||
|
' ? key',
|
||||||
|
' : ("." + sep + key)',
|
||||||
|
' ),',
|
||||||
|
' );',
|
||||||
|
' const R = (request) => require(keys[request]);',
|
||||||
|
' R.id = __filename',
|
||||||
|
' R.keys = () => keys;',
|
||||||
|
' return R;',
|
||||||
|
'};',
|
||||||
|
].join('\n'),
|
||||||
|
},
|
||||||
|
'require.context',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'prepend',
|
||||||
|
{
|
||||||
|
prepend: 'const __non_webpack_require__ = require;',
|
||||||
|
},
|
||||||
|
'__non_webpack_require__',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
compiling.forEach((fleck) => {
|
||||||
|
flecks[fleck] = R(this.resolve(resolver, fleck));
|
||||||
|
// Remove the required fleck from the list still needing require().
|
||||||
|
paths.splice(paths.indexOf(fleck), 1);
|
||||||
|
});
|
||||||
|
// Don't pollute, kids.
|
||||||
|
register.revert();
|
||||||
|
});
|
||||||
|
// Load the rest of the flecks.
|
||||||
|
paths.forEach((path) => {
|
||||||
|
flecks[path] = R(this.resolve(resolver, path));
|
||||||
|
});
|
||||||
|
return new ServerFlecks({
|
||||||
|
config,
|
||||||
|
flecks,
|
||||||
|
platforms,
|
||||||
|
rcs,
|
||||||
|
resolver,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fleckIsAliased(fleck) {
|
||||||
|
return this.constructor.fleckIsAliased(this.resolver, fleck);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fleckIsAliased(resolver, fleck) {
|
||||||
|
return fleck !== this.resolve(resolver, fleck);
|
||||||
|
}
|
||||||
|
|
||||||
|
fleckIsSymlinked(fleck) {
|
||||||
|
return this.constructor.fleckIsSymlinked(this.resolver, fleck);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fleckIsSymlinked(resolver, fleck) {
|
||||||
|
const resolved = R.resolve(this.resolve(resolver, fleck));
|
||||||
|
const realpath = realpathSync(resolved);
|
||||||
|
return realpath !== resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
localConfig(path, fleck, options) {
|
||||||
|
return this.constructor.localConfig(this.resolver, path, fleck, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static localConfig(resolver, path, fleck, {general = path, root = FLECKS_ROOT} = {}) {
|
||||||
|
let configFile;
|
||||||
|
try {
|
||||||
|
const localConfig = join(root, 'build', path);
|
||||||
|
statSync(localConfig);
|
||||||
|
configFile = localConfig;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
try {
|
||||||
|
const localConfig = join(root, 'build', general);
|
||||||
|
statSync(localConfig);
|
||||||
|
configFile = localConfig;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
try {
|
||||||
|
const localConfig = join(FLECKS_ROOT, 'build', path);
|
||||||
|
statSync(localConfig);
|
||||||
|
configFile = localConfig;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
try {
|
||||||
|
const localConfig = join(FLECKS_ROOT, 'build', general);
|
||||||
|
statSync(localConfig);
|
||||||
|
configFile = localConfig;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const resolved = this.resolve(resolver, fleck);
|
||||||
|
try {
|
||||||
|
configFile = R.resolve(join(resolved, 'build', path));
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
configFile = R.resolve(join(resolved, 'build', general));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return configFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcs() {
|
||||||
|
return this.rcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
sourcepath(fleck) {
|
||||||
|
return this.constructor.sourcepath(fleck);
|
||||||
|
}
|
||||||
|
|
||||||
|
static sourcepath(path) {
|
||||||
|
let sourcepath = realpathSync(path);
|
||||||
|
const parts = sourcepath.split('/');
|
||||||
|
const indexOf = parts.lastIndexOf('dist');
|
||||||
|
if (-1 !== indexOf) {
|
||||||
|
parts.splice(indexOf, 1, 'src');
|
||||||
|
sourcepath = parts.join('/');
|
||||||
|
sourcepath = join(dirname(sourcepath), basename(sourcepath, extname(sourcepath)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sourcepath = join(sourcepath, 'src');
|
||||||
|
}
|
||||||
|
return sourcepath;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(path) {
|
||||||
|
return this.constructor.resolve(this.resolver, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static resolve(resolver, fleck) {
|
||||||
|
return resolver[fleck] || fleck;
|
||||||
|
}
|
||||||
|
|
||||||
|
root(fleck) {
|
||||||
|
return this.constructor.root(this.resolver, fleck);
|
||||||
|
}
|
||||||
|
|
||||||
|
static root(resolver, fleck) {
|
||||||
|
const parts = this.resolve(resolver, fleck).split('/');
|
||||||
|
try {
|
||||||
|
R.resolve(parts.join('/'));
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
while (parts.length > 0) {
|
||||||
|
try {
|
||||||
|
R.resolve(join(parts.join('/'), 'package.json'));
|
||||||
|
return parts.join('/');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
parts.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimeCompiler(runtime, neutrino, allowlist = []) {
|
||||||
|
const {config} = neutrino;
|
||||||
|
// Pull the default compiler.
|
||||||
|
if (config.module.rules.has('compile')) {
|
||||||
|
config.module.rules.delete('compile');
|
||||||
|
}
|
||||||
|
// Flecks that are aliased or symlinked need compilation.
|
||||||
|
const needCompilation = Object.entries(this.resolver)
|
||||||
|
.filter(([fleck]) => this.fleckIsAliased(fleck) || this.fleckIsSymlinked(fleck));
|
||||||
|
// Alias and de-externalize.
|
||||||
|
needCompilation
|
||||||
|
.forEach(([fleck, resolved]) => {
|
||||||
|
const alias = this.fleckIsAliased(fleck)
|
||||||
|
? resolved
|
||||||
|
: this.sourcepath(R.resolve(this.resolve(fleck)));
|
||||||
|
allowlist.push(`${fleck}$`);
|
||||||
|
config.resolve.alias
|
||||||
|
.set(`${fleck}$`, alias);
|
||||||
|
});
|
||||||
|
// Set up compilation at each root.
|
||||||
|
Array.from(new Set(
|
||||||
|
needCompilation
|
||||||
|
.map(([fleck]) => fleck)
|
||||||
|
.map((fleck) => this.root(fleck)),
|
||||||
|
))
|
||||||
|
.forEach((root) => {
|
||||||
|
const resolved = dirname(R.resolve(join(root, 'package.json')));
|
||||||
|
const sourcepath = this.sourcepath(resolved);
|
||||||
|
const configFile = this.localConfig(
|
||||||
|
'babel.config.js',
|
||||||
|
'@flecks/core',
|
||||||
|
{root: resolved},
|
||||||
|
);
|
||||||
|
compileLoader({
|
||||||
|
include: [sourcepath],
|
||||||
|
babel: {configFile},
|
||||||
|
ruleId: `@flecks/${runtime}/runtime/compile[${root}]`,
|
||||||
|
})(neutrino);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stubs() {
|
||||||
|
return this.constructor.stubs(this.platforms, this.rcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static stubs(platforms, rcs) {
|
||||||
|
const keys = Object.keys(rcs);
|
||||||
|
const stubs = {};
|
||||||
|
for (let i = 0; i < keys.length; ++i) {
|
||||||
|
const key = keys[i];
|
||||||
|
const config = rcs[key];
|
||||||
|
if (config.stubs) {
|
||||||
|
Object.entries(config.stubs)
|
||||||
|
.forEach(([platform, paths]) => {
|
||||||
|
if (-1 !== platforms.indexOf(platform)) {
|
||||||
|
paths.forEach((path) => {
|
||||||
|
stubs[path] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.keys(stubs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
packages/core/src/server/index.js
Normal file
46
packages/core/src/server/index.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import airbnb from '@neutrinojs/airbnb';
|
||||||
|
|
||||||
|
import {Hooks} from '../flecks';
|
||||||
|
import commands from './commands';
|
||||||
|
import R from '../bootstrap/require';
|
||||||
|
|
||||||
|
export {
|
||||||
|
default as commands,
|
||||||
|
spawnWith,
|
||||||
|
targetNeutrino,
|
||||||
|
targetNeutrinos,
|
||||||
|
} from './commands';
|
||||||
|
|
||||||
|
export {default as Flecks} from './flecks';
|
||||||
|
export {default as require} from '../bootstrap/require';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Hooks]: {
|
||||||
|
'@flecks/core/build': (target, config, flecks) => {
|
||||||
|
const {'eslint.exclude': exclude} = flecks.get('@flecks/core');
|
||||||
|
if (-1 !== exclude.indexOf(target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const baseConfig = R(
|
||||||
|
flecks.localConfig(
|
||||||
|
`${target}.eslint.defaults.js`,
|
||||||
|
'@flecks/core',
|
||||||
|
{general: '.eslint.defaults.js'},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
config.use.unshift(
|
||||||
|
airbnb({
|
||||||
|
eslint: {
|
||||||
|
baseConfig: {
|
||||||
|
...baseConfig,
|
||||||
|
env: {
|
||||||
|
mocha: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'@flecks/core/commands': commands,
|
||||||
|
},
|
||||||
|
};
|
23
packages/core/test/fleck-one/index.js
Normal file
23
packages/core/test/fleck-one/index.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import Flecks, {Hooks} from '../../src/flecks';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Hooks]: {
|
||||||
|
'@flecks/core/config': () => ({
|
||||||
|
foo: 'bar',
|
||||||
|
'test-gather.decorate': ['...'],
|
||||||
|
}),
|
||||||
|
'./fleck-one/test-gather': (
|
||||||
|
Flecks.provide(require.context('./things', false, /\.js$/))
|
||||||
|
),
|
||||||
|
'./fleck-one/test-gather.decorate': (
|
||||||
|
Flecks.decorate(require.context('./things/decorators', false, /\.js$/))
|
||||||
|
),
|
||||||
|
'flecks-test-invoke': () => 69,
|
||||||
|
'flecks-test-invoke-parallel': (O) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
O.foo *= 2;
|
||||||
|
},
|
||||||
|
'flecks-test-invoke-reduce': () => ({foo: 69}),
|
||||||
|
'flecks-test-invoke-reduce-async': () => new Promise((resolve) => resolve({foo: 69})),
|
||||||
|
},
|
||||||
|
};
|
6
packages/core/test/fleck-one/things/decorators/three.js
Normal file
6
packages/core/test/fleck-one/things/decorators/three.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export default (Three) => class AnotherThree extends Three {
|
||||||
|
|
||||||
|
static bar() {
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
7
packages/core/test/fleck-one/things/one.js
Normal file
7
packages/core/test/fleck-one/things/one.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default () => class One {
|
||||||
|
|
||||||
|
static get foo() {
|
||||||
|
return 'One';
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
7
packages/core/test/fleck-one/things/two.js
Normal file
7
packages/core/test/fleck-one/things/two.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default () => class Two {
|
||||||
|
|
||||||
|
static get foo() {
|
||||||
|
return 'Two';
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
19
packages/core/test/fleck-two/index.js
Normal file
19
packages/core/test/fleck-two/index.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import Flecks, {Hooks} from '../../src/flecks';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Hooks]: {
|
||||||
|
'./fleck-one/test-gather': (
|
||||||
|
Flecks.provide(require.context('./things', false, /\.js$/))
|
||||||
|
),
|
||||||
|
'flecks-test-invoke': () => 420,
|
||||||
|
'flecks-test-invoke-parallel': (O) => new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
O.foo += 2;
|
||||||
|
resolve();
|
||||||
|
}, 0);
|
||||||
|
}),
|
||||||
|
'flecks-test-invoke-reduce': () => ({bar: 420}),
|
||||||
|
'flecks-test-invoke-reduce-async': () => new Promise((resolve) => resolve({bar: 420})),
|
||||||
|
},
|
||||||
|
};
|
7
packages/core/test/fleck-two/things/three.js
Normal file
7
packages/core/test/fleck-two/things/three.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default () => class Three {
|
||||||
|
|
||||||
|
static get foo() {
|
||||||
|
return 'Three';
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
26
packages/core/test/gather.js
Normal file
26
packages/core/test/gather.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import {expect} from 'chai';
|
||||||
|
|
||||||
|
import Flecks, {ById, ByType} from '../src/flecks';
|
||||||
|
|
||||||
|
const testFleckOne = require('./fleck-one');
|
||||||
|
const testFleckTwo = require('./fleck-two');
|
||||||
|
|
||||||
|
it('can gather', () => {
|
||||||
|
const flecks = new Flecks({
|
||||||
|
flecks: {
|
||||||
|
'./fleck-one': testFleckOne,
|
||||||
|
'./fleck-two': testFleckTwo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const Gathered = flecks.gather('./fleck-one/test-gather');
|
||||||
|
expect(Object.keys(Gathered[ByType]).length)
|
||||||
|
.to.equal(Object.keys(Gathered[ById]).length);
|
||||||
|
const typeKeys = Object.keys(Gathered[ByType]);
|
||||||
|
for (let i = 0; i < typeKeys.length; ++i) {
|
||||||
|
const type = typeKeys[i];
|
||||||
|
expect(Gathered[type].foo)
|
||||||
|
.to.equal(type);
|
||||||
|
}
|
||||||
|
expect(typeof Gathered.Three.bar)
|
||||||
|
.to.not.equal('undefined');
|
||||||
|
});
|
32
packages/core/test/instance.js
Normal file
32
packages/core/test/instance.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import {expect} from 'chai';
|
||||||
|
|
||||||
|
import Flecks from '../src/flecks';
|
||||||
|
|
||||||
|
const testFleckOne = require('./fleck-one');
|
||||||
|
|
||||||
|
it('can create an empty instance', () => {
|
||||||
|
const flecks = new Flecks();
|
||||||
|
expect(Object.keys(flecks.originalConfig).length)
|
||||||
|
.to.equal(0);
|
||||||
|
expect(Object.keys(flecks.config).length)
|
||||||
|
.to.equal(0);
|
||||||
|
expect(Object.keys(flecks.hooks).length)
|
||||||
|
.to.equal(0);
|
||||||
|
expect(Object.keys(flecks.flecks).length)
|
||||||
|
.to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can gather config', () => {
|
||||||
|
let flecks;
|
||||||
|
flecks = new Flecks({
|
||||||
|
flecks: {'./fleck-one': testFleckOne},
|
||||||
|
});
|
||||||
|
expect(flecks.get(['./fleck-one']))
|
||||||
|
.to.contain({foo: 'bar'});
|
||||||
|
flecks = new Flecks({
|
||||||
|
config: {'./fleck-one': {foo: 'baz'}},
|
||||||
|
flecks: {'./fleck-one': testFleckOne},
|
||||||
|
});
|
||||||
|
expect(flecks.get(['./fleck-one']))
|
||||||
|
.to.contain({foo: 'baz'});
|
||||||
|
});
|
42
packages/core/test/invoke.js
Normal file
42
packages/core/test/invoke.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import {expect} from 'chai';
|
||||||
|
|
||||||
|
import Flecks from '../src/flecks';
|
||||||
|
|
||||||
|
const testFleckOne = require('./fleck-one');
|
||||||
|
const testFleckTwo = require('./fleck-two');
|
||||||
|
|
||||||
|
let flecks;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
flecks = new Flecks({
|
||||||
|
flecks: {
|
||||||
|
'./fleck-one': testFleckOne,
|
||||||
|
'./fleck-two': testFleckTwo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can invoke', () => {
|
||||||
|
expect(flecks.invoke('flecks-test-invoke'))
|
||||||
|
.to.deep.equal({
|
||||||
|
'./fleck-one': 69,
|
||||||
|
'./fleck-two': 420,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can invoke parallel', async () => {
|
||||||
|
const O = {foo: 3};
|
||||||
|
await Promise.all(flecks.invokeParallel('flecks-test-invoke-parallel', O));
|
||||||
|
expect(O.foo)
|
||||||
|
.to.equal(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can invoke reduced', () => {
|
||||||
|
expect(flecks.invokeReduce('flecks-test-invoke-reduce'))
|
||||||
|
.to.deep.equal({foo: 69, bar: 420});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can invoke reduced async', async () => {
|
||||||
|
expect(await flecks.invokeReduce('flecks-test-invoke-reduce'))
|
||||||
|
.to.deep.equal({foo: 69, bar: 420});
|
||||||
|
});
|
5897
packages/core/yarn.lock
Normal file
5897
packages/core/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
116
packages/create-app/.gitignore
vendored
Normal file
116
packages/create-app/.gitignore
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
44
packages/create-app/build/fleck.neutrinorc.js
Normal file
44
packages/create-app/build/fleck.neutrinorc.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
const {chmod} = require('fs');
|
||||||
|
const {join} = require('path');
|
||||||
|
|
||||||
|
const banner = require('@neutrinojs/banner');
|
||||||
|
const copy = require('@neutrinojs/copy');
|
||||||
|
|
||||||
|
module.exports = require('@flecks/fleck/server/build/fleck.neutrinorc');
|
||||||
|
|
||||||
|
module.exports.use.push(banner({
|
||||||
|
banner: '#!/usr/bin/env node',
|
||||||
|
include: /^cli\.js$/,
|
||||||
|
pluginId: 'shebang',
|
||||||
|
raw: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports.use.push(({config}) => {
|
||||||
|
config
|
||||||
|
.plugin('executable')
|
||||||
|
.use(class Executable {
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
apply(compiler) {
|
||||||
|
compiler.hooks.afterEmit.tapAsync(
|
||||||
|
'Executable',
|
||||||
|
(compilation, callback) => {
|
||||||
|
chmod(join(__dirname, '..', 'dist', 'cli.js'), 0o755, callback);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.use.push(
|
||||||
|
copy({
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
from: 'template',
|
||||||
|
to: 'template',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
2
packages/create-app/build/flecks.yml
Normal file
2
packages/create-app/build/flecks.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/fleck': {}
|
26
packages/create-app/package.json
Normal file
26
packages/create-app/package.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "@flecks/create-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"bin": {
|
||||||
|
"create-app": "./cli.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "flecks build",
|
||||||
|
"clean": "flecks clean",
|
||||||
|
"lint": "flecks lint",
|
||||||
|
"test": "flecks test"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"cli.js",
|
||||||
|
"cli.js.map",
|
||||||
|
"src",
|
||||||
|
"template"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@flecks/core": "^1.0.0",
|
||||||
|
"fs-extra": "10.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flecks/fleck": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
50
packages/create-app/src/cli.js
Normal file
50
packages/create-app/src/cli.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import {spawn} from 'child_process';
|
||||||
|
import {readFileSync, writeFileSync} from 'fs';
|
||||||
|
import {join, normalize} from 'path';
|
||||||
|
|
||||||
|
import {
|
||||||
|
copySync,
|
||||||
|
mkdirpSync,
|
||||||
|
moveSync,
|
||||||
|
} from 'fs-extra';
|
||||||
|
|
||||||
|
const cwd = normalize(process.cwd());
|
||||||
|
|
||||||
|
const forwardProcessCode = (fn) => async (...args) => {
|
||||||
|
process.exitCode = await fn(args.slice(0, -2));
|
||||||
|
};
|
||||||
|
|
||||||
|
const processCode = (child) => new Promise((resolve, reject) => {
|
||||||
|
child.on('error', reject);
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
child.off('error', reject);
|
||||||
|
resolve(code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const create = () => async () => {
|
||||||
|
const name = process.argv[2];
|
||||||
|
const path = name.split('/').pop();
|
||||||
|
copySync(join(__dirname, 'template'), join(cwd, path), {recursive: true});
|
||||||
|
mkdirpSync(join(cwd, path, 'packages'));
|
||||||
|
moveSync(join(cwd, path, '.gitignore.extraneous'), join(cwd, path, '.gitignore'));
|
||||||
|
moveSync(join(cwd, path, 'package.json.extraneous'), join(cwd, path, 'package.json'));
|
||||||
|
writeFileSync(
|
||||||
|
join(cwd, path, 'package.json'),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
name: `@${name}/monorepo`,
|
||||||
|
...JSON.parse(readFileSync(join(cwd, path, 'package.json')).toString()),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const code = await processCode(spawn('yarn', [], {cwd: join(cwd, path), stdio: 'inherit'}));
|
||||||
|
if (0 !== code) {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
return processCode(spawn('yarn', ['build'], {cwd: join(cwd, path), stdio: 'inherit'}));
|
||||||
|
};
|
||||||
|
|
||||||
|
forwardProcessCode(create())();
|
116
packages/create-app/template/.gitignore.extraneous
Normal file
116
packages/create-app/template/.gitignore.extraneous
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
2
packages/create-app/template/build/flecks.yml
Normal file
2
packages/create-app/template/build/flecks.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/server': {}
|
6
packages/create-app/template/lerna.json
Normal file
6
packages/create-app/template/lerna.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
15
packages/create-app/template/package.json.extraneous
Normal file
15
packages/create-app/template/package.json.extraneous
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "flecks build",
|
||||||
|
"dev": "FLECKS_START_SERVER=1 npm run -- build -h"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@flecks/core": "^1.0.0",
|
||||||
|
"@flecks/server": "^1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flecks/create-fleck": "^1.0.0",
|
||||||
|
"lerna": "^3.22.1"
|
||||||
|
}
|
||||||
|
}
|
5965
packages/create-app/yarn.lock
Normal file
5965
packages/create-app/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
116
packages/create-fleck/.gitignore
vendored
Normal file
116
packages/create-fleck/.gitignore
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
44
packages/create-fleck/build/fleck.neutrinorc.js
Normal file
44
packages/create-fleck/build/fleck.neutrinorc.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
const {chmod} = require('fs');
|
||||||
|
const {join} = require('path');
|
||||||
|
|
||||||
|
const banner = require('@neutrinojs/banner');
|
||||||
|
const copy = require('@neutrinojs/copy');
|
||||||
|
|
||||||
|
module.exports = require('@flecks/fleck/server/build/fleck.neutrinorc');
|
||||||
|
|
||||||
|
module.exports.use.push(banner({
|
||||||
|
banner: '#!/usr/bin/env node',
|
||||||
|
include: /^cli\.js$/,
|
||||||
|
pluginId: 'shebang',
|
||||||
|
raw: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports.use.push(({config}) => {
|
||||||
|
config
|
||||||
|
.plugin('executable')
|
||||||
|
.use(class Executable {
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
apply(compiler) {
|
||||||
|
compiler.hooks.afterEmit.tapAsync(
|
||||||
|
'Executable',
|
||||||
|
(compilation, callback) => {
|
||||||
|
chmod(join(__dirname, '..', 'dist', 'cli.js'), 0o755, callback);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.use.push(
|
||||||
|
copy({
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
from: 'template',
|
||||||
|
to: 'template',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
2
packages/create-fleck/build/flecks.yml
Normal file
2
packages/create-fleck/build/flecks.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/fleck': {}
|
27
packages/create-fleck/package.json
Normal file
27
packages/create-fleck/package.json
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "@flecks/create-fleck",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"bin": {
|
||||||
|
"create-fleck": "./cli.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "flecks build",
|
||||||
|
"clean": "flecks clean",
|
||||||
|
"lint": "flecks lint",
|
||||||
|
"test": "flecks test"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"cli.js",
|
||||||
|
"cli.js.map",
|
||||||
|
"src",
|
||||||
|
"template"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@flecks/core": "^1.0.0",
|
||||||
|
"fs-extra": "10.0.0",
|
||||||
|
"validate-npm-package-name": "^3.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flecks/fleck": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
111
packages/create-fleck/src/cli.js
Normal file
111
packages/create-fleck/src/cli.js
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import {spawn} from 'child_process';
|
||||||
|
import {
|
||||||
|
readFileSync,
|
||||||
|
statSync,
|
||||||
|
writeFileSync,
|
||||||
|
} from 'fs';
|
||||||
|
import {join, normalize} from 'path';
|
||||||
|
|
||||||
|
import {copySync, moveSync} from 'fs-extra';
|
||||||
|
import validate from 'validate-npm-package-name';
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_ROOT = process.cwd(),
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
const cwd = normalize(FLECKS_ROOT);
|
||||||
|
|
||||||
|
const forwardProcessCode = (fn) => async (...args) => {
|
||||||
|
process.exitCode = await fn(args.slice(0, -2));
|
||||||
|
};
|
||||||
|
|
||||||
|
const processCode = (child) => new Promise((resolve, reject) => {
|
||||||
|
child.on('error', reject);
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
child.off('error', reject);
|
||||||
|
resolve(code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const monorepoScope = () => {
|
||||||
|
try {
|
||||||
|
statSync(join(cwd, 'packages'));
|
||||||
|
const {name} = __non_webpack_require__(join(cwd, 'package.json'));
|
||||||
|
const [scope] = name.split('/');
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if ('ENOENT' !== error.code) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testDestination = (destination) => {
|
||||||
|
try {
|
||||||
|
statSync(destination);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if ('ENOENT' !== error.code) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = () => async () => {
|
||||||
|
const rawname = process.argv[2];
|
||||||
|
const {errors} = validate(rawname);
|
||||||
|
if (errors) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`@flecks/create-fleck: invalid fleck name: ${errors.join(', ')}`);
|
||||||
|
return 128;
|
||||||
|
}
|
||||||
|
const parts = rawname.split('/');
|
||||||
|
let path = cwd;
|
||||||
|
let pkg;
|
||||||
|
let scope;
|
||||||
|
if (1 === parts.length) {
|
||||||
|
pkg = rawname;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[scope, pkg] = parts;
|
||||||
|
}
|
||||||
|
if (!scope) {
|
||||||
|
scope = monorepoScope();
|
||||||
|
if (scope) {
|
||||||
|
path = join(path, 'packages');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const name = [scope, pkg].filter((e) => !!e).join('/');
|
||||||
|
const destination = join(path, pkg);
|
||||||
|
if (!testDestination(destination)) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`@flecks/create-fleck: destination '${destination} already exists: aborting`);
|
||||||
|
return 129;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-unreachable
|
||||||
|
copySync(join(__dirname, 'template'), destination, {recursive: true});
|
||||||
|
moveSync(join(destination, '.gitignore.extraneous'), join(destination, '.gitignore'));
|
||||||
|
moveSync(join(destination, 'package.json.extraneous'), join(destination, 'package.json'));
|
||||||
|
writeFileSync(
|
||||||
|
join(destination, 'package.json'),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
...JSON.parse(readFileSync(join(destination, 'package.json')).toString()),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const code = await processCode(spawn('yarn', [], {cwd: destination, stdio: 'inherit'}));
|
||||||
|
if (0 !== code) {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
return processCode(spawn('yarn', ['build'], {cwd: destination, stdio: 'inherit'}));
|
||||||
|
};
|
||||||
|
|
||||||
|
forwardProcessCode(create())();
|
116
packages/create-fleck/template/.gitignore.extraneous
Normal file
116
packages/create-fleck/template/.gitignore.extraneous
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
2
packages/create-fleck/template/build/flecks.yml
Normal file
2
packages/create-fleck/template/build/flecks.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/fleck': {}
|
21
packages/create-fleck/template/package.json.extraneous
Normal file
21
packages/create-fleck/template/package.json.extraneous
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "flecks build",
|
||||||
|
"clean": "flecks clean",
|
||||||
|
"lint": "flecks lint",
|
||||||
|
"test": "flecks test"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"index.js",
|
||||||
|
"index.js.map",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@flecks/core": "^1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flecks/fleck": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
0
packages/create-fleck/template/src/index.js
Normal file
0
packages/create-fleck/template/src/index.js
Normal file
5977
packages/create-fleck/yarn.lock
Normal file
5977
packages/create-fleck/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
116
packages/db/.gitignore
vendored
Normal file
116
packages/db/.gitignore
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
2
packages/db/build/flecks.yml
Normal file
2
packages/db/build/flecks.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/fleck': {}
|
25
packages/db/package.json
Normal file
25
packages/db/package.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "@flecks/db",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "flecks build",
|
||||||
|
"clean": "flecks clean",
|
||||||
|
"lint": "flecks lint",
|
||||||
|
"test": "flecks test"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"build",
|
||||||
|
"server.js",
|
||||||
|
"server.js.map",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@flecks/core": "^1.0.0",
|
||||||
|
"debug": "4.3.1",
|
||||||
|
"sequelize": "^6.3.5",
|
||||||
|
"sqlite3": "^5.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flecks/fleck": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
107
packages/db/src/connection.js
Normal file
107
packages/db/src/connection.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import {ByType} from '@flecks/core';
|
||||||
|
import D from 'debug';
|
||||||
|
import Sequelize from 'sequelize';
|
||||||
|
|
||||||
|
import environment from './environment';
|
||||||
|
|
||||||
|
const debug = D('@flecks/db/server/connection');
|
||||||
|
|
||||||
|
export async function createDatabaseConnection(flecks) {
|
||||||
|
const env = environment();
|
||||||
|
debug('environment: %O', {...env, ...(env.password ? {password: '*** REDACTED ***'} : {})});
|
||||||
|
const sequelize = new Sequelize({
|
||||||
|
logging: false,
|
||||||
|
...env,
|
||||||
|
});
|
||||||
|
const Models = flecks.get('$flecks/db.models')[ByType];
|
||||||
|
Object.values(Models)
|
||||||
|
.filter((Model) => Model.attributes)
|
||||||
|
.forEach((Model) => {
|
||||||
|
Model.init(Model.attributes, {
|
||||||
|
sequelize,
|
||||||
|
underscored: true,
|
||||||
|
...(Model.modelOptions || {}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await sequelize.authenticate();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dependencies = {};
|
||||||
|
Object.values(Models).forEach((Model) => {
|
||||||
|
Model.associate(Models);
|
||||||
|
});
|
||||||
|
Object.values(Models).forEach((Model) => {
|
||||||
|
const associations = Object.entries(Model.associations);
|
||||||
|
for (let i = 0; i < associations.length; i++) {
|
||||||
|
if (associations[i][1].isSelfAssociation) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ('BelongsToMany' === associations[i][1].associationType) {
|
||||||
|
if (associations[i][1].through.model.isManagedByFlecks) {
|
||||||
|
const {name} = associations[i][1].through.model;
|
||||||
|
if (!dependencies[name]) {
|
||||||
|
dependencies[name] = new Set();
|
||||||
|
}
|
||||||
|
dependencies[name].add(Model.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('BelongsTo' === associations[i][1].associationType) {
|
||||||
|
if (!dependencies[Model.name]) {
|
||||||
|
dependencies[Model.name] = new Set();
|
||||||
|
}
|
||||||
|
dependencies[Model.name].add(associations[i][1].target.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const entries = Object.values(Models);
|
||||||
|
let lastLength = entries.length;
|
||||||
|
while (entries.length > 0) {
|
||||||
|
for (let i = 0; i < entries.length; i++) {
|
||||||
|
const Model = entries[i];
|
||||||
|
if (
|
||||||
|
!dependencies[Model.name]
|
||||||
|
|| 0 === dependencies[Model.name].length
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await Model.sync();
|
||||||
|
const dependents = Object.keys(dependencies);
|
||||||
|
for (let j = 0; j < dependents.length; j++) {
|
||||||
|
const dependent = dependents[j];
|
||||||
|
if (dependencies[dependent].has(Model.name)) {
|
||||||
|
dependencies[dependent].delete(Model.name);
|
||||||
|
if (0 === dependencies[dependent].size) {
|
||||||
|
delete dependencies[dependent];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entries.length === lastLength) {
|
||||||
|
throw new TypeError(`@flecks/db circular dependencies: '${entries.join("', '")}'`);
|
||||||
|
}
|
||||||
|
lastLength = entries.length;
|
||||||
|
}
|
||||||
|
debug('synchronizing...');
|
||||||
|
await sequelize.sync();
|
||||||
|
debug('synchronized');
|
||||||
|
return sequelize;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroyDatabaseConnection(databaseConnection) {
|
||||||
|
if (!databaseConnection) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return databaseConnection.close();
|
||||||
|
}
|
49
packages/db/src/containers.js
Normal file
49
packages/db/src/containers.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import environment from './environment';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const {
|
||||||
|
dialect,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
port,
|
||||||
|
database,
|
||||||
|
} = environment();
|
||||||
|
let args = [];
|
||||||
|
let image;
|
||||||
|
let mount;
|
||||||
|
switch (dialect) {
|
||||||
|
case 'mysql': {
|
||||||
|
args = [
|
||||||
|
'-e', `MYSQL_USER=${username}`,
|
||||||
|
'-e', `MYSQL_DATABASE=${database}`,
|
||||||
|
'-e', `MYSQL_ROOT_PASSWORD=${password}`,
|
||||||
|
'-p', `${port}:3306`,
|
||||||
|
];
|
||||||
|
image = 'mysql';
|
||||||
|
mount = '/var/lib/mysql';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'postgres': {
|
||||||
|
args = [
|
||||||
|
'-e', `POSTGRES_USER=${username}`,
|
||||||
|
'-e', `POSTGRES_DB=${database}`,
|
||||||
|
'-e', `POSTGRES_PASSWORD=${password}`,
|
||||||
|
'-p', `${port}:5432`,
|
||||||
|
];
|
||||||
|
image = 'postgres';
|
||||||
|
mount = '/var/lib/postgresql/data';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if (!image) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
sequelize: {
|
||||||
|
args,
|
||||||
|
image,
|
||||||
|
mount,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
25
packages/db/src/environment.js
Normal file
25
packages/db/src/environment.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
const {
|
||||||
|
SEQUELIZE_DIALECT = 'sqlite',
|
||||||
|
SEQUELIZE_USER = 'user',
|
||||||
|
SEQUELIZE_PASSWORD = 'Set_The_SEQUELIZE_PASSWORD_Environment_Variable',
|
||||||
|
SEQUELIZE_HOST = 'localhost',
|
||||||
|
SEQUELIZE_PORT,
|
||||||
|
SEQUELIZE_DATABASE = ':memory:',
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
if ('sqlite' === SEQUELIZE_DIALECT) {
|
||||||
|
return ({
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: SEQUELIZE_DATABASE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ({
|
||||||
|
dialect: SEQUELIZE_DIALECT,
|
||||||
|
username: SEQUELIZE_USER,
|
||||||
|
password: SEQUELIZE_PASSWORD,
|
||||||
|
host: SEQUELIZE_HOST,
|
||||||
|
port: SEQUELIZE_PORT,
|
||||||
|
database: SEQUELIZE_DATABASE,
|
||||||
|
});
|
||||||
|
};
|
17
packages/db/src/model.js
Normal file
17
packages/db/src/model.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import {Model as SeqModel} from 'sequelize';
|
||||||
|
|
||||||
|
class Model extends SeqModel {
|
||||||
|
|
||||||
|
static associate() {}
|
||||||
|
|
||||||
|
static get attributes() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get isManagedByFlecks() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Model;
|
32
packages/db/src/server.js
Normal file
32
packages/db/src/server.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import {Hooks} from '@flecks/core';
|
||||||
|
|
||||||
|
import {createDatabaseConnection} from './connection';
|
||||||
|
import containers from './containers';
|
||||||
|
|
||||||
|
export {DataTypes as Types, Op, default as Sequelize} from 'sequelize';
|
||||||
|
|
||||||
|
export {default as Model} from './model';
|
||||||
|
|
||||||
|
export {createDatabaseConnection};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Hooks]: {
|
||||||
|
'@flecks/core/config': () => ({
|
||||||
|
'models.decorate': ['...'],
|
||||||
|
}),
|
||||||
|
'@flecks/core/starting': (flecks) => {
|
||||||
|
flecks.set('$flecks/db.models', flecks.gather(
|
||||||
|
'@flecks/db/server/models',
|
||||||
|
{typeAttribute: 'name'},
|
||||||
|
));
|
||||||
|
},
|
||||||
|
'@flecks/docker/containers': containers,
|
||||||
|
'@flecks/server/up': async (flecks) => {
|
||||||
|
flecks.set('$flecks/db/sequelize', await createDatabaseConnection(flecks));
|
||||||
|
},
|
||||||
|
'@flecks/repl/context': (flecks) => ({
|
||||||
|
Models: flecks.get('$flecks/db.models'),
|
||||||
|
sequelize: flecks.get('$flecks/db/sequelize'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
6624
packages/db/yarn.lock
Normal file
6624
packages/db/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
116
packages/docker/.gitignore
vendored
Normal file
116
packages/docker/.gitignore
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
2
packages/docker/build/flecks.yml
Normal file
2
packages/docker/build/flecks.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/fleck': {}
|
24
packages/docker/package.json
Normal file
24
packages/docker/package.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "@flecks/docker",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "flecks build",
|
||||||
|
"clean": "flecks clean",
|
||||||
|
"lint": "flecks lint",
|
||||||
|
"test": "flecks test"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"build",
|
||||||
|
"server.js",
|
||||||
|
"server.js.map",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@flecks/core": "^1.0.0",
|
||||||
|
"debug": "^4.3.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flecks/fleck": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
15
packages/docker/src/server.js
Normal file
15
packages/docker/src/server.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {Hooks} from '@flecks/core';
|
||||||
|
|
||||||
|
import startContainer from './start-container';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Hooks]: {
|
||||||
|
'@flecks/server/up': async (flecks) => {
|
||||||
|
const containers = await flecks.invokeReduceAsync('@flecks/docker/containers');
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(containers)
|
||||||
|
.map(([key, config]) => startContainer(flecks, key, config)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
81
packages/docker/src/start-container.js
Normal file
81
packages/docker/src/start-container.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import {execSync, spawn} from 'child_process';
|
||||||
|
import {mkdir} from 'fs/promises';
|
||||||
|
import {tmpdir} from 'os';
|
||||||
|
import {join} from 'path';
|
||||||
|
|
||||||
|
import D from 'debug';
|
||||||
|
|
||||||
|
const debug = D('@flecks/docker/container');
|
||||||
|
|
||||||
|
const containerIsRunning = (name) => {
|
||||||
|
try {
|
||||||
|
const output = execSync(
|
||||||
|
`docker container inspect -f '{{.State.Running}}' ${name}`,
|
||||||
|
{stdio: 'pipe'},
|
||||||
|
).toString();
|
||||||
|
if ('true\n' === output) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (1 !== e.status) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (flecks, key, config) => {
|
||||||
|
const {id} = flecks.get('@flecks/core');
|
||||||
|
if (!config.image) {
|
||||||
|
throw new Error(`@flecks/docker: ${key} container has no image specified`);
|
||||||
|
}
|
||||||
|
if (!config.mount) {
|
||||||
|
throw new Error(`@flecks/docker: ${key} container has no mount point specified`);
|
||||||
|
}
|
||||||
|
const name = `${id}_${key}`;
|
||||||
|
if (containerIsRunning(name)) {
|
||||||
|
debug("'%s' already running", key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const args = [
|
||||||
|
'run',
|
||||||
|
'--name', name,
|
||||||
|
'--rm',
|
||||||
|
...(config.args || []),
|
||||||
|
];
|
||||||
|
const datadir = join(tmpdir(), 'flecks', key, 'docker');
|
||||||
|
debug("creating datadir '%s'", datadir);
|
||||||
|
try {
|
||||||
|
await mkdir(datadir, {recursive: true});
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if ('EEXIST' !== error.code) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args.push('-v', `${datadir}:${config.mount}`);
|
||||||
|
args.push(config.image);
|
||||||
|
debug('launching: docker %s', args.join(' '));
|
||||||
|
spawn('docker', args, {
|
||||||
|
detached: true,
|
||||||
|
stdio: 'ignore',
|
||||||
|
}).unref();
|
||||||
|
while (!containerIsRunning(name)) {
|
||||||
|
debug("waiting for '%s' to start...", key);
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
debug("'%s' started", key);
|
||||||
|
if (config.hasConnected) {
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
if (await config.hasConnected()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
debug("waiting for '%s' to connect...", key);
|
||||||
|
}
|
||||||
|
debug("'%s' connected", key);
|
||||||
|
}
|
||||||
|
};
|
5942
packages/docker/yarn.lock
Normal file
5942
packages/docker/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
116
packages/dox/.gitignore
vendored
Normal file
116
packages/dox/.gitignore
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
2
packages/dox/build/flecks.yml
Normal file
2
packages/dox/build/flecks.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/fleck': {}
|
27
packages/dox/package.json
Normal file
27
packages/dox/package.json
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "@flecks/dox",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "flecks build",
|
||||||
|
"clean": "flecks clean",
|
||||||
|
"lint": "flecks lint",
|
||||||
|
"test": "flecks test"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"build",
|
||||||
|
"index.js",
|
||||||
|
"index.js.map",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.17.2",
|
||||||
|
"@babel/traverse": "^7.17.0",
|
||||||
|
"@babel/types": "^7.17.0",
|
||||||
|
"@flecks/core": "^1.0.0",
|
||||||
|
"glob": "^7.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flecks/fleck": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
1
packages/dox/src/index.js
Normal file
1
packages/dox/src/index.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export {parseCode, parseFleck} from './parser';
|
126
packages/dox/src/parser.js
Normal file
126
packages/dox/src/parser.js
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import {readFile} from 'fs/promises';
|
||||||
|
import {dirname, join} from 'path';
|
||||||
|
|
||||||
|
import {transformAsync} from '@babel/core';
|
||||||
|
import traverse from '@babel/traverse';
|
||||||
|
import {
|
||||||
|
isIdentifier,
|
||||||
|
isMemberExpression,
|
||||||
|
isStringLiteral,
|
||||||
|
isThisExpression,
|
||||||
|
} from '@babel/types';
|
||||||
|
import glob from 'glob';
|
||||||
|
|
||||||
|
const flecksCorePath = dirname(__non_webpack_require__.resolve('@flecks/core/package.json'));
|
||||||
|
|
||||||
|
const FlecksInvocations = (state, filename) => ({
|
||||||
|
CallExpression(path) {
|
||||||
|
if (isMemberExpression(path.node.callee)) {
|
||||||
|
if (
|
||||||
|
(isIdentifier(path.node.callee.object) && 'flecks' === path.node.callee.object.name)
|
||||||
|
|| (
|
||||||
|
(
|
||||||
|
isThisExpression(path.node.callee.object)
|
||||||
|
&& (filename === join(flecksCorePath, 'src', 'flecks.js'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (isIdentifier(path.node.callee.property)) {
|
||||||
|
if (path.node.callee.property.name.match(/^invoke.*/)) {
|
||||||
|
if (path.node.arguments.length > 0) {
|
||||||
|
if (isStringLiteral(path.node.arguments[0])) {
|
||||||
|
state.invocations.push({
|
||||||
|
filename,
|
||||||
|
hook: path.node.arguments[0].value,
|
||||||
|
line: path.node.loc.start.line,
|
||||||
|
type: path.node.callee.property.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('up' === path.node.callee.property.name) {
|
||||||
|
if (path.node.arguments.length > 0) {
|
||||||
|
if (isStringLiteral(path.node.arguments[0])) {
|
||||||
|
state.invocations.push({
|
||||||
|
filename,
|
||||||
|
hook: path.node.arguments[0].value,
|
||||||
|
line: path.node.loc.start.line,
|
||||||
|
type: 'invokeSequentialAsync',
|
||||||
|
});
|
||||||
|
state.invocations.push({
|
||||||
|
filename,
|
||||||
|
hook: '@flecks/core/starting',
|
||||||
|
line: path.node.loc.start.line,
|
||||||
|
type: 'invokeFlat',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('gather' === path.node.callee.property.name) {
|
||||||
|
if (path.node.arguments.length > 0) {
|
||||||
|
if (isStringLiteral(path.node.arguments[0])) {
|
||||||
|
state.invocations.push({
|
||||||
|
filename,
|
||||||
|
hook: path.node.arguments[0].value,
|
||||||
|
line: path.node.loc.start.line,
|
||||||
|
type: 'invokeReduce',
|
||||||
|
});
|
||||||
|
state.invocations.push({
|
||||||
|
filename,
|
||||||
|
hook: `${path.node.arguments[0].value}.decorate`,
|
||||||
|
line: path.node.loc.start.line,
|
||||||
|
type: 'invokeComposed',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const parseCode = async (code, state, filename = '<inline>') => {
|
||||||
|
const config = {
|
||||||
|
ast: true,
|
||||||
|
code: false,
|
||||||
|
configFile: join(__dirname, '..', '..', 'core', 'src', 'build', 'babel.config.js'),
|
||||||
|
};
|
||||||
|
const {ast} = await transformAsync(code, config);
|
||||||
|
traverse(ast, FlecksInvocations(state, filename));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseFile = async (filename, state) => {
|
||||||
|
const buffer = await readFile(filename);
|
||||||
|
return parseCode(buffer.toString('utf8'), state, filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fleckSources = (path) => (
|
||||||
|
new Promise((r, e) => (
|
||||||
|
glob(
|
||||||
|
join(path, 'src', '**', '*.js'),
|
||||||
|
(error, result) => (error ? e(error) : r(result)),
|
||||||
|
)
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
export const parseFleck = async (path, state) => {
|
||||||
|
const sources = await fleckSources(path);
|
||||||
|
await Promise.all(sources.map((source) => parseFile(source, state)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
invocations: [],
|
||||||
|
};
|
||||||
|
(async () => {
|
||||||
|
const path = flecksCorePath;
|
||||||
|
await parseFleck(path, state);
|
||||||
|
state.invocations.forEach(({
|
||||||
|
filename,
|
||||||
|
hook,
|
||||||
|
line,
|
||||||
|
type,
|
||||||
|
}) => {
|
||||||
|
console.log(`${type}('${hook}') in ${filename}:${line}`);
|
||||||
|
});
|
||||||
|
})();
|
5942
packages/dox/yarn.lock
Normal file
5942
packages/dox/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
2
packages/fleck/build/flecks.yml
Normal file
2
packages/fleck/build/flecks.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/fleck:./src': {}
|
35
packages/fleck/package.json
Normal file
35
packages/fleck/package.json
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "@flecks/fleck",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "cha0s",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"flecks": "./cli.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "flecks build",
|
||||||
|
"lint": "flecks lint",
|
||||||
|
"test": "flecks test"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"build",
|
||||||
|
"server/build/fleck.neutrinorc.js",
|
||||||
|
"server/build/fleck.neutrinorc.js.map",
|
||||||
|
"server.js",
|
||||||
|
"server.js.map",
|
||||||
|
"src",
|
||||||
|
"test.js",
|
||||||
|
"test.js.map"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@flecks/core": "^1.0.0",
|
||||||
|
"@neutrinojs/node": "^9.4.0",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"debug": "^4.3.3",
|
||||||
|
"glob": "^7.2.0",
|
||||||
|
"mocha": "^8.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"chai": "4.2.0"
|
||||||
|
}
|
||||||
|
}
|
55
packages/fleck/src/server/build/fleck.neutrinorc.js
Normal file
55
packages/fleck/src/server/build/fleck.neutrinorc.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
const {join} = require('path');
|
||||||
|
|
||||||
|
const {Flecks} = require('@flecks/core/server');
|
||||||
|
const node = require('@neutrinojs/node');
|
||||||
|
const D = require('debug');
|
||||||
|
const glob = require('glob');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_ROOT = process.cwd(),
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
const debug = D('@flecks/fleck/fleck.neutrino.js');
|
||||||
|
|
||||||
|
debug('bootstrapping flecks...');
|
||||||
|
const flecks = Flecks.bootstrap();
|
||||||
|
debug('bootstrapped');
|
||||||
|
|
||||||
|
const config = require('../../../../core/src/bootstrap/fleck.neutrinorc');
|
||||||
|
|
||||||
|
config.use.push((neutrino) => {
|
||||||
|
// Test entrypoint.
|
||||||
|
const testPaths = glob.sync(join(FLECKS_ROOT, 'test/*.js'));
|
||||||
|
if (testPaths.length > 0) {
|
||||||
|
const testEntry = neutrino.config.entry('test').clear();
|
||||||
|
testPaths.forEach((path) => testEntry.add(path));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const compiler = flecks.invokeFleck(
|
||||||
|
'@flecks/fleck/compiler',
|
||||||
|
flecks.get('@flecks/fleck.compiler'),
|
||||||
|
);
|
||||||
|
if (compiler) {
|
||||||
|
config.use.unshift(compiler);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
config.use.unshift((neutrino) => {
|
||||||
|
neutrino.config.plugins.delete('start-server');
|
||||||
|
});
|
||||||
|
const configFile = flecks.localConfig('babel.config.js', '@flecks/core');
|
||||||
|
config.use.unshift(node({
|
||||||
|
babel: {configFile},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
config.use.unshift((neutrino) => {
|
||||||
|
// Test entrypoint.
|
||||||
|
const testPaths = glob.sync(join(FLECKS_ROOT, 'test/*.js'));
|
||||||
|
if (testPaths.length > 0) {
|
||||||
|
const testEntry = neutrino.config.entry('test').clear();
|
||||||
|
testPaths.forEach((path) => testEntry.add(path));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = config;
|
91
packages/fleck/src/server/commands.js
Normal file
91
packages/fleck/src/server/commands.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import {stat, unlink} from 'fs/promises';
|
||||||
|
import {join} from 'path';
|
||||||
|
|
||||||
|
import chokidar from 'chokidar';
|
||||||
|
import D from 'debug';
|
||||||
|
import glob from 'glob';
|
||||||
|
|
||||||
|
import {
|
||||||
|
commands as coreCommands,
|
||||||
|
spawnWith,
|
||||||
|
} from '@flecks/core/server';
|
||||||
|
|
||||||
|
const debug = D('@flecks/core/commands');
|
||||||
|
|
||||||
|
const {
|
||||||
|
FLECKS_ROOT = process.cwd(),
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
export default (program, flecks) => {
|
||||||
|
const commands = {};
|
||||||
|
commands.test = {
|
||||||
|
options: [
|
||||||
|
['-d, --no-production', 'dev build'],
|
||||||
|
['-w, --watch', 'watch for changes'],
|
||||||
|
['-v, --verbose', 'verbose output'],
|
||||||
|
],
|
||||||
|
description: 'run tests',
|
||||||
|
action: async (opts) => {
|
||||||
|
const {
|
||||||
|
watch,
|
||||||
|
} = opts;
|
||||||
|
const testPaths = glob.sync(join(FLECKS_ROOT, 'test/*.js'));
|
||||||
|
if (0 === testPaths.length) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('No fleck tests found.');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const testLocation = join(FLECKS_ROOT, 'dist', 'test.js');
|
||||||
|
if (watch) {
|
||||||
|
await unlink(testLocation);
|
||||||
|
}
|
||||||
|
const {build} = coreCommands(program, flecks);
|
||||||
|
const child = build.action(undefined, opts);
|
||||||
|
debug('Testing...', opts);
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await stat(testLocation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const spawnMocha = () => {
|
||||||
|
const localEnv = {};
|
||||||
|
const spawnArgs = [
|
||||||
|
'--colors',
|
||||||
|
'--reporter', 'min',
|
||||||
|
testLocation,
|
||||||
|
];
|
||||||
|
return spawnWith('mocha', localEnv, spawnArgs);
|
||||||
|
};
|
||||||
|
if (!watch) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
reject(code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
child.on('error', reject);
|
||||||
|
});
|
||||||
|
return spawnMocha();
|
||||||
|
}
|
||||||
|
let tester;
|
||||||
|
chokidar.watch(testLocation)
|
||||||
|
.on('all', () => {
|
||||||
|
if (tester) {
|
||||||
|
tester.kill();
|
||||||
|
}
|
||||||
|
tester = spawnMocha();
|
||||||
|
});
|
||||||
|
return new Promise(() => {});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return commands;
|
||||||
|
};
|
10
packages/fleck/src/server/index.js
Normal file
10
packages/fleck/src/server/index.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import {Hooks} from '@flecks/core';
|
||||||
|
|
||||||
|
import commands from './commands';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Hooks]: {
|
||||||
|
'@flecks/core/commands': commands,
|
||||||
|
'@flecks/core/targets': () => ['fleck'],
|
||||||
|
},
|
||||||
|
};
|
5930
packages/fleck/yarn.lock
Normal file
5930
packages/fleck/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
116
packages/governor/.gitignore
vendored
Normal file
116
packages/governor/.gitignore
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
2
packages/governor/build/flecks.yml
Normal file
2
packages/governor/build/flecks.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
'@flecks/core': {}
|
||||||
|
'@flecks/fleck': {}
|
28
packages/governor/package.json
Normal file
28
packages/governor/package.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "@flecks/governor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "flecks build",
|
||||||
|
"clean": "flecks clean",
|
||||||
|
"lint": "flecks lint",
|
||||||
|
"test": "flecks test"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"build",
|
||||||
|
"client.js",
|
||||||
|
"client.js.map",
|
||||||
|
"server.js",
|
||||||
|
"server.js.map",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@flecks/core": "^1.0.0",
|
||||||
|
"@flecks/db": "^1.0.0",
|
||||||
|
"rate-limiter-flexible": "^2.1.13",
|
||||||
|
"redis": "^3.1.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flecks/fleck": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
2
packages/governor/src/client/index.js
Normal file
2
packages/governor/src/client/index.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export {default as createLimiter} from './limiter';
|
3
packages/governor/src/client/limiter.js
Normal file
3
packages/governor/src/client/limiter.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import RateLimiterMemory from 'rate-limiter-flexible/lib/RateLimiterMemory';
|
||||||
|
|
||||||
|
export default (flecks, options) => new RateLimiterMemory(options);
|
30
packages/governor/src/limited-packet.js
Normal file
30
packages/governor/src/limited-packet.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
export default (flecks, [name, Packet]) => {
|
||||||
|
const {ValidationError} = flecks.fleck('@flecks/socket');
|
||||||
|
return class LimitedPacket extends Packet {
|
||||||
|
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
const {[name]: limiter} = flecks.get('$flecks/governor.packet.limiters');
|
||||||
|
this.limit = limiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate(packet, socket) {
|
||||||
|
try {
|
||||||
|
await packet.limit.consume(socket.id);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error.msBeforeNext) {
|
||||||
|
throw new ValidationError({
|
||||||
|
code: 429,
|
||||||
|
ttr: Math.round(error.msBeforeNext / 1000) || 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (super.validate) {
|
||||||
|
await super.validate(packet, socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
12
packages/governor/src/limiter.js
Normal file
12
packages/governor/src/limiter.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import {createClient} from 'redis';
|
||||||
|
import {RateLimiterRedis} from 'rate-limiter-flexible';
|
||||||
|
|
||||||
|
export default async (options) => {
|
||||||
|
const storeClient = createClient();
|
||||||
|
// @todo node-redis@4
|
||||||
|
// await storeClient.connect();
|
||||||
|
return new RateLimiterRedis({
|
||||||
|
...options,
|
||||||
|
storeClient,
|
||||||
|
});
|
||||||
|
};
|
73
packages/governor/src/models/ban.js
Normal file
73
packages/governor/src/models/ban.js
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
export default (flecks) => {
|
||||||
|
const {Model, Op, Types} = flecks.fleck('@flecks/db/server');
|
||||||
|
const {config: {'@flecks/governor/server': {keys}}} = flecks;
|
||||||
|
return class Ban extends Model {
|
||||||
|
|
||||||
|
static get attributes() {
|
||||||
|
return {
|
||||||
|
ttl: {
|
||||||
|
type: Types.INTEGER,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
...Object.fromEntries(keys.map((key) => ([key, {type: Types.STRING}]))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async check(req) {
|
||||||
|
const ban = this.fromRequest(req, keys);
|
||||||
|
const candidates = Object.entries(ban)
|
||||||
|
.reduce((r, [key, value]) => [...r, {[key]: value}], []);
|
||||||
|
const where = {
|
||||||
|
where: {
|
||||||
|
[Op.or]: candidates,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const bans = await this.findAll(where);
|
||||||
|
const pruned = bans
|
||||||
|
.reduce((r, ban) => {
|
||||||
|
if (ban && ban.ttl > 0) {
|
||||||
|
const expiresAt = new Date(ban.createdAt);
|
||||||
|
expiresAt.setSeconds(expiresAt.getSeconds() + ban.ttl);
|
||||||
|
if (Date.now() >= expiresAt.getTime()) {
|
||||||
|
this.destroy({where: {id: ban.id}});
|
||||||
|
return [...r, null];
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
ban.ttl = Math.ceil((expiresAt.getTime() - Date.now()) / 1000);
|
||||||
|
}
|
||||||
|
return [...r, ban];
|
||||||
|
}, [])
|
||||||
|
.filter((ban) => !!ban)
|
||||||
|
.map((ban) => {
|
||||||
|
const {ttl, ...json} = ban.toJSON();
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
if (0 === pruned.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(this.format(pruned));
|
||||||
|
}
|
||||||
|
|
||||||
|
static format(bans) {
|
||||||
|
return [
|
||||||
|
'bans = [',
|
||||||
|
bans.map((ban) => {
|
||||||
|
const entries = Object.entries(ban)
|
||||||
|
.filter(([key]) => -1 === ['id', 'createdAt', 'updatedAt'].indexOf(key));
|
||||||
|
return [
|
||||||
|
' {',
|
||||||
|
entries.map(([key, value]) => ` ${key}: ${value},`).join('\n'),
|
||||||
|
' },',
|
||||||
|
].join('\n');
|
||||||
|
}).join('\n'),
|
||||||
|
'];',
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromRequest(req, keys, ttl = 0) {
|
||||||
|
return keys.reduce((r, key) => ({...r, [key]: req[key]}), ttl ? {ttl} : {});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
123
packages/governor/src/server.js
Normal file
123
packages/governor/src/server.js
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import {ByType, Flecks, Hooks} from '@flecks/core';
|
||||||
|
|
||||||
|
import LimitedPacket from './limited-packet';
|
||||||
|
import createLimiter from './limiter';
|
||||||
|
|
||||||
|
export {default as createLimiter} from './limiter';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
[Hooks]: {
|
||||||
|
'@flecks/core/config': () => ({
|
||||||
|
keys: ['ip'],
|
||||||
|
http: {
|
||||||
|
keys: ['ip'],
|
||||||
|
points: 60,
|
||||||
|
duration: 30,
|
||||||
|
ttl: 30,
|
||||||
|
},
|
||||||
|
socket: {
|
||||||
|
keys: ['ip'],
|
||||||
|
points: 60,
|
||||||
|
duration: 30,
|
||||||
|
ttl: 30,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'@flecks/db/server/models': Flecks.provide(require.context('./models', false, /\.js$/)),
|
||||||
|
'@flecks/http/server/request.route': (flecks) => {
|
||||||
|
const {http} = flecks.get('@flecks/governor/server');
|
||||||
|
const limiter = flecks.get('$flecks/governor.http.limiter');
|
||||||
|
return async (req, res, next) => {
|
||||||
|
const {Ban} = flecks.get('$flecks/db.models');
|
||||||
|
try {
|
||||||
|
await Ban.check(req);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
res.status(403).send(`<pre>${error.message}</pre>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
req.ban = async (keys, ttl = 0) => {
|
||||||
|
const ban = Ban.fromRequest(req, keys, ttl);
|
||||||
|
await Ban.create({...ban});
|
||||||
|
res.status(403).send(`<pre>${Ban.format([ban])}</pre>`);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await limiter.consume(req.ip);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const {ttl, keys} = http;
|
||||||
|
const ban = Ban.fromRequest(req, keys, ttl);
|
||||||
|
await Ban.create({...ban});
|
||||||
|
res.status(429).send(`<pre>${Ban.format([ban])}</pre>`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'@flecks/server/up': async (flecks) => {
|
||||||
|
if (flecks.fleck('@flecks/http/server')) {
|
||||||
|
const {http} = flecks.get('@flecks/governor/server');
|
||||||
|
const limiter = await createLimiter({
|
||||||
|
keyPrefix: '@flecks/governor.http.request.route',
|
||||||
|
...http,
|
||||||
|
});
|
||||||
|
flecks.set('$flecks/governor.http.limiter', limiter);
|
||||||
|
}
|
||||||
|
if (flecks.fleck('@flecks/socket/server')) {
|
||||||
|
const {[ByType]: Packets} = flecks.get('$flecks/socket.packets');
|
||||||
|
const limiters = Object.fromEntries(
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(Packets)
|
||||||
|
.filter(([, Packet]) => Packet.limit)
|
||||||
|
.map(async ([name, Packet]) => (
|
||||||
|
[
|
||||||
|
name,
|
||||||
|
await createLimiter({keyPrefix: `@flecks/governor.packet.${name}`, ...Packet.limit}),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
flecks.set('$flecks/governor.packet.limiters', limiters);
|
||||||
|
const {socket} = flecks.get('@flecks/governor/server');
|
||||||
|
const limiter = await createLimiter({
|
||||||
|
keyPrefix: '@flecks/governor.socket.request.socket',
|
||||||
|
...socket,
|
||||||
|
});
|
||||||
|
flecks.set('$flecks/governor.socket.limiter', limiter);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'@flecks/socket/server/request.socket': (flecks) => {
|
||||||
|
const limiter = flecks.get('$flecks/governor.socket.limiter');
|
||||||
|
return async (socket, next) => {
|
||||||
|
const {handshake: req} = socket;
|
||||||
|
const {Ban} = flecks.get('$flecks/db.models');
|
||||||
|
try {
|
||||||
|
await Ban.check(req);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
next(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
req.ban = async (keys, ttl) => {
|
||||||
|
await Ban.create(Ban.fromRequest(req, keys, ttl));
|
||||||
|
socket.disconnect();
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await limiter.consume(req.ip);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const {ttl, keys} = socket;
|
||||||
|
await Ban.create(Ban.fromRequest(req, keys, ttl));
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'@flecks/socket/packets.decorate': (Packets, flecks) => (
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(Packets).map(([keyPrefix, Packet]) => [
|
||||||
|
keyPrefix,
|
||||||
|
!Packet.limit ? Packet : LimitedPacket(flecks, [keyPrefix, Packet]),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
6671
packages/governor/yarn.lock
Normal file
6671
packages/governor/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
116
packages/http/.gitignore
vendored
Normal file
116
packages/http/.gitignore
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# 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
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
5
packages/http/build/.eslint.defaults.js
Normal file
5
packages/http/build/.eslint.defaults.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
const config = require('@flecks/core/build/.eslint.defaults.js');
|
||||||
|
|
||||||
|
config.globals.window = true;
|
||||||
|
|
||||||
|
module.exports = config;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user