flow:
dox, hook registration, ensureUniqueReduction, middleware, ...
This commit is contained in:
parent
23f2fae001
commit
c3910ba5f0
|
@ -1,7 +1,7 @@
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>flecks</h1>
|
<h1>flecks</h1>
|
||||||
<p>
|
<p>
|
||||||
Flecks is a dynamic, configuration-driven, fullstack application production system. Its purpose
|
Flecks is an exceptionally extensible fullstack application production system. Its true purpose
|
||||||
is to make application development a more joyful endeavor. Intelligent defaults combined with
|
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
|
a highly dynamic structure encourage consistency while allowing you to easily express your own
|
||||||
opinions.
|
opinions.
|
||||||
|
|
3
TODO.md
3
TODO.md
|
@ -22,7 +22,7 @@
|
||||||
- [x] remove `invokeParallel()`
|
- [x] remove `invokeParallel()`
|
||||||
- [x] Specialize `invokeReduce()` with `invokeMerge()`.
|
- [x] Specialize `invokeReduce()` with `invokeMerge()`.
|
||||||
- [x] Rename all hooks to dot-first notation; rewrite `lookupFlecks()`.
|
- [x] Rename all hooks to dot-first notation; rewrite `lookupFlecks()`.
|
||||||
- [ ] ensureUniqueReduction moved into invokeMerge
|
- [x] ensureUniqueReduction moved into invokeMerge
|
||||||
- [x] `bootstrap({without: ['badplatform']})` should be handled by passing `{platforms: ['!badplatform']}`
|
- [x] `bootstrap({without: ['badplatform']})` should be handled by passing `{platforms: ['!badplatform']}`
|
||||||
- [ ] user redux server hydrate fails if no user in req
|
- [ ] user redux server hydrate fails if no user in req
|
||||||
- [ ] governor fails if not in server up
|
- [ ] governor fails if not in server up
|
||||||
|
@ -31,3 +31,4 @@
|
||||||
- [ ] rename `@flecks/web` to `@flecks/web`
|
- [ ] rename `@flecks/web` to `@flecks/web`
|
||||||
- [ ] simultaneous babel compilation across all compiled flecks
|
- [ ] simultaneous babel compilation across all compiled flecks
|
||||||
- [ ] add building to publish process ...
|
- [ ] add building to publish process ...
|
||||||
|
- [ ] @babel/register@7.18.x has a bug
|
||||||
|
|
1
packages/core/QUIRKS.md
Normal file
1
packages/core/QUIRKS.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
- I use the variable `r` a lot when referencing a reducer's accumulator value
|
|
@ -35,8 +35,10 @@ config.use.push(({config}) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fleck build configuration.
|
||||||
config.use.unshift(fleck());
|
config.use.unshift(fleck());
|
||||||
|
|
||||||
|
// AirBnb linting.
|
||||||
config.use.unshift(
|
config.use.unshift(
|
||||||
airbnb({
|
airbnb({
|
||||||
eslint: {
|
eslint: {
|
||||||
|
@ -45,13 +47,13 @@ config.use.unshift(
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Include a shebang and set the executable bit..
|
||||||
config.use.push(banner({
|
config.use.push(banner({
|
||||||
banner: '#!/usr/bin/env node',
|
banner: '#!/usr/bin/env node',
|
||||||
include: /^cli\.js$/,
|
include: /^cli\.js$/,
|
||||||
pluginId: 'shebang',
|
pluginId: 'shebang',
|
||||||
raw: true,
|
raw: true,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
config.use.push(({config}) => {
|
config.use.push(({config}) => {
|
||||||
config
|
config
|
||||||
.plugin('executable')
|
.plugin('executable')
|
||||||
|
|
|
@ -2,16 +2,12 @@
|
||||||
|
|
||||||
Hooks are how everything happens in flecks. There are many hooks and the hooks provided by flecks are documented at the [hooks reference page](https://github.com/cha0s/flecks/blob/gh-pages/hooks.md).
|
Hooks are how everything happens in flecks. There are many hooks and the hooks provided by flecks are documented at the [hooks reference page](https://github.com/cha0s/flecks/blob/gh-pages/hooks.md).
|
||||||
|
|
||||||
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:
|
To define hooks (and turn your plain ol' boring JS modules into beautiful interesting flecks), you only have to export a `hooks` object:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
'@flecks/core.starting': () => {
|
||||||
export default {
|
console.log('hello, gorgeous');
|
||||||
[Hooks]: {
|
|
||||||
'@flecks/core.starting': () => {
|
|
||||||
console.log('hello, gorgeous');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
@ -133,15 +129,15 @@ assert(foo.type === 'Foo');
|
||||||
```javascript
|
```javascript
|
||||||
{
|
{
|
||||||
// The property added when extending the class to return the numeric ID.
|
// The property added when extending the class to return the numeric ID.
|
||||||
idAttribute = 'id',
|
idProperty = 'id',
|
||||||
// The property added when extending the class to return the type.
|
// The property added when extending the class to return the type.
|
||||||
typeAttribute = 'type',
|
typeProperty = 'type',
|
||||||
// A function called with the `Gathered` object to allow checking validity.
|
// A function called with the `Gathered` object to allow checking validity.
|
||||||
check = () => {},
|
check = () => {},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
As an example, when `@flecks/db/server` gathers models, `typeAttribute` is set to `name`, because Sequelize requires its model classes to have a unique `name` property.
|
As an example, when `@flecks/db/server` gathers models, `typeProperty` is set to `name`, because Sequelize requires its model classes to have a unique `name` property.
|
||||||
|
|
||||||
**Note:** the numeric IDs are useful for efficient serialization between the client and server, but **if you are using this property, ensure that `flecks.gather()` is called equivalently on both the client and the server**. As a rule of thumb, if you have serializable `Gathered`s, they should be invoked and defined in `your-fleck`, and not in `your-fleck/[platform]`, so that they are invoked for every platform.
|
**Note:** the numeric IDs are useful for efficient serialization between the client and server, but **if you are using this property, ensure that `flecks.gather()` is called equivalently on both the client and the server**. As a rule of thumb, if you have serializable `Gathered`s, they should be invoked and defined in `your-fleck`, and not in `your-fleck/[platform]`, so that they are invoked for every platform.
|
||||||
|
|
||||||
|
@ -152,19 +148,15 @@ Complementary to gather hooks above, `Flecks.provide()` allows you to ergonomica
|
||||||
Here's an example of how you could manually provide `@flecks/db/server.models` in your own fleck:
|
Here's an example of how you could manually provide `@flecks/db/server.models` in your own fleck:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import {Hooks} foom '@flecks/core';
|
|
||||||
|
|
||||||
import SomeModel from './models/some-model';
|
import SomeModel from './models/some-model';
|
||||||
import AnotherModel from './models/another-model';
|
import AnotherModel from './models/another-model';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/db/server.models': () => ({
|
||||||
'@flecks/db/server.models': () => ({
|
SomeModel,
|
||||||
SomeModel,
|
AnotherModel,
|
||||||
AnotherModel,
|
}),
|
||||||
}),
|
}
|
||||||
},
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you think about the example above, you might realize that it will become a lot of typing to keep adding new models over time. Provider hooks exist to reduce this maintenance burden for you.
|
If you think about the example above, you might realize that it will become a lot of typing to keep adding new models over time. Provider hooks exist to reduce this maintenance burden for you.
|
||||||
|
@ -183,12 +175,10 @@ models/
|
||||||
then, this `index.js`:
|
then, this `index.js`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import {Flecks, Hooks} from '@flecks/core';
|
import {Flecks} from '@flecks/core';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
|
||||||
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -212,31 +202,27 @@ is *exactly equivalent* to the gather example above. By default, `Flecks.provide
|
||||||
When a Model (or any other) is gathered as above, an implicit hook is called: `${hook}.decorate`. This allows other flecks to decorate whatever has been gathered:
|
When a Model (or any other) is gathered as above, an implicit hook is called: `${hook}.decorate`. This allows other flecks to decorate whatever has been gathered:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
'@flecks/db/server.models.decorate': (Models) => {
|
||||||
|
return {
|
||||||
|
...Models,
|
||||||
|
User: class extends Models.User {
|
||||||
|
|
||||||
export default {
|
// Let's mix in some logging...
|
||||||
[Hooks]: {
|
constructor(...args) {
|
||||||
'@flecks/db/server.models.decorate': (Models) => {
|
super(...args);
|
||||||
return {
|
console.log ('Another user decorated!');
|
||||||
...Models,
|
}
|
||||||
User: class extends Models.User {
|
|
||||||
|
|
||||||
// Let's mix in some logging...
|
},
|
||||||
constructor(...args) {
|
};
|
||||||
super(...args);
|
|
||||||
console.log ('Another user decorated!');
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `Flecks.decorate(context, options)`
|
#### `Flecks.decorate(context, options)`
|
||||||
|
|
||||||
As with above, there exists an API for making the maintenance of decorators more ergonomic.
|
As with above, there exists an API for making the maintenance of decorators even more ergonomic.
|
||||||
|
|
||||||
Supposing our fleck is structured like so:
|
Supposing our fleck is structured like so:
|
||||||
|
|
||||||
|
@ -266,12 +252,12 @@ export default (User) => {
|
||||||
then, this `index.js`:
|
then, this `index.js`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import {Flecks, Hooks} from '@flecks/core';
|
import {Flecks} from '@flecks/core';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/db/server.models.decorate': (
|
||||||
'@flecks/db/server.models.decorate': Flecks.decorate(require.context('./models/decorators', false, /\.js$/)),
|
Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
|
||||||
},
|
),
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -307,7 +293,7 @@ Our `flecks.yml` could be configured like so:
|
||||||
|
|
||||||
In this application, when `@flecks/http/server.request.route` is invoked, `@flecks/user/session`'s implementation is invoked (which reifies the user's session from cookies), followed by `my-cool-fleck`'s (which, we assume, does some kind of very cool dark mode check).
|
In this application, when `@flecks/http/server.request.route` is invoked, `@flecks/user/session`'s implementation is invoked (which reifies the user's session from cookies), followed by `my-cool-fleck`'s (which, we assume, does some kind of very cool dark mode check).
|
||||||
|
|
||||||
### Ellipses
|
### Ellipses/elision
|
||||||
|
|
||||||
It may not always be ergonomic to configure the order of every single implementation, but enough to specify which implementations must run first (or last).
|
It may not always be ergonomic to configure the order of every single implementation, but enough to specify which implementations must run first (or last).
|
||||||
|
|
||||||
|
|
|
@ -1,122 +1,118 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
|
||||||
export default {
|
/**
|
||||||
[Hooks]: {
|
* Hook into neutrino configuration.
|
||||||
/**
|
* @param {string} target The build target; e.g. `server`.
|
||||||
* Hook into neutrino configuration.
|
* @param {Object} config The neutrino configuration.
|
||||||
* @param {string} target The build target; e.g. `server`.
|
*/
|
||||||
* @param {Object} config The neutrino configuration.
|
|
||||||
*/
|
|
||||||
'@flecks/core.build': (target, config) => {
|
'@flecks/core.build': (target, config) => {
|
||||||
if ('something' === target) {
|
if ('something' === target) {
|
||||||
config[target].use.push(someNeutrinoMiddleware);
|
config[target].use.push(someNeutrinoMiddleware);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alter build configurations after they have been hooked.
|
* Alter build configurations after they have been hooked.
|
||||||
* @param {Object} configs The neutrino configurations.
|
* @param {Object} configs The neutrino configurations.
|
||||||
*/
|
|
||||||
'@flecks/core.build.alter': (configs) => {
|
|
||||||
// Maybe we want to do something if a config exists..?
|
|
||||||
if (configs.something) {
|
|
||||||
// Do something...
|
|
||||||
// And then maybe we want to remove it from the build configuration..?
|
|
||||||
delete configs.something;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register build configuration.
|
|
||||||
*/
|
|
||||||
'@flecks/core.build.config': () => [
|
|
||||||
/**
|
|
||||||
* If you document your config files like this, documentation will be automatically
|
|
||||||
* generated.
|
|
||||||
*/
|
|
||||||
'.myrc.js',
|
|
||||||
/**
|
|
||||||
* Make sure you return them as an array expression, like this.
|
|
||||||
*/
|
|
||||||
['mygeneralrc.js', {specifier: (specific) => `${specific}.mygeneralrc.js`}],
|
|
||||||
],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define CLI commands.
|
|
||||||
*/
|
|
||||||
'@flecks/core.commands': (program) => ({
|
|
||||||
// So this could be invoked like:
|
|
||||||
// npx flecks something -t --blow-up blah
|
|
||||||
something: {
|
|
||||||
action: (...args) => {
|
|
||||||
// Run the command...
|
|
||||||
},
|
|
||||||
args: [
|
|
||||||
'<somearg>',
|
|
||||||
],
|
|
||||||
description: 'This sure is some command',
|
|
||||||
options: [
|
|
||||||
'-t, --test', 'Do a test',
|
|
||||||
'-b, --blow-up', 'Blow up instead of running the command',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define configuration.
|
|
||||||
*/
|
|
||||||
'@flecks/core.config': () => ({
|
|
||||||
whatever: 'configuration',
|
|
||||||
your: 1337,
|
|
||||||
fleck: 'needs',
|
|
||||||
/**
|
|
||||||
* Also, comments like this will be used to automatically generate documentation.
|
|
||||||
*/
|
|
||||||
though: 'you should keep the values serializable',
|
|
||||||
}),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when a fleck is HMR'd
|
|
||||||
* @param {string} path The path of the fleck
|
|
||||||
* @param {Module} updatedFleck The updated fleck module.
|
|
||||||
*/
|
*/
|
||||||
'@flecks/core.hmr': (path, updatedFleck) => {
|
'@flecks/core.build.alter': (configs) => {
|
||||||
if ('my-fleck' === path) {
|
// Maybe we want to do something if a config exists..?
|
||||||
updatedFleck.doSomething();
|
if (configs.something) {
|
||||||
}
|
// Do something...
|
||||||
},
|
// And then maybe we want to remove it from the build configuration..?
|
||||||
|
delete configs.something;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register build configuration.
|
||||||
|
*/
|
||||||
|
'@flecks/core.build.config': () => [
|
||||||
/**
|
/**
|
||||||
* Invoked when a gathered class is HMR'd.
|
* If you document your config files like this, documentation will be automatically
|
||||||
* @param {constructor} Class The class.
|
* generated.
|
||||||
* @param {string} hook The gather hook; e.g. `@flecks/db/server.models`.
|
*/
|
||||||
*/
|
'.myrc.js',
|
||||||
'@flecks/core.hmr.gathered': (Class, hook) => {
|
/**
|
||||||
// Do something with Class...
|
* Make sure you return them as an array expression, like this.
|
||||||
},
|
*/
|
||||||
|
['mygeneralrc.js', {specifier: (specific) => `${specific}.mygeneralrc.js`}],
|
||||||
|
],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when the application is starting. Use for order-independent initialization tasks.
|
* Define CLI commands.
|
||||||
*/
|
*/
|
||||||
'@flecks/core.starting': (flecks) => {
|
'@flecks/core.commands': (program) => ({
|
||||||
flecks.set('$my-fleck/value', initializeMyValue());
|
// So this could be invoked like:
|
||||||
|
// npx flecks something -t --blow-up blah
|
||||||
|
something: {
|
||||||
|
action: (...args) => {
|
||||||
|
// Run the command...
|
||||||
|
},
|
||||||
|
args: [
|
||||||
|
'<somearg>',
|
||||||
|
],
|
||||||
|
description: 'This command does tests and also blows up',
|
||||||
|
options: [
|
||||||
|
'-t, --test', 'Do a test',
|
||||||
|
'-b, --blow-up', 'Blow up instead of running the command',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define configuration.
|
||||||
|
*/
|
||||||
|
'@flecks/core.config': () => ({
|
||||||
|
whatever: 'configuration',
|
||||||
|
your: 1337,
|
||||||
|
fleck: 'needs',
|
||||||
/**
|
/**
|
||||||
* Define neutrino build targets.
|
* Also, comments like this will be used to automatically generate documentation.
|
||||||
*/
|
*/
|
||||||
'@flecks/core.targets': () => ['sometarget'],
|
though: 'you should keep the values serializable',
|
||||||
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook into webpack configuration.
|
* Invoked when a fleck is HMR'd
|
||||||
* @param {string} target The build target; e.g. `server`.
|
* @param {string} path The path of the fleck
|
||||||
* @param {Object} config The neutrino configuration.
|
* @param {Module} updatedFleck The updated fleck module.
|
||||||
*/
|
*/
|
||||||
'@flecks/core.webpack': (target, config) => {
|
'@flecks/core.hmr': (path, updatedFleck) => {
|
||||||
if ('something' === target) {
|
if ('my-fleck' === path) {
|
||||||
config.stats = 'verbose';
|
updatedFleck.doSomething();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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`.
|
||||||
|
*/
|
||||||
|
'@flecks/core.hmr.gathered': (Class, hook) => {
|
||||||
|
// Do something with Class...
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when the application is starting. Use for order-independent initialization tasks.
|
||||||
|
*/
|
||||||
|
'@flecks/core.starting': (flecks) => {
|
||||||
|
flecks.set('$my-fleck/value', initializeMyValue());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define neutrino build targets.
|
||||||
|
*/
|
||||||
|
'@flecks/core.targets': () => ['sometarget'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook into webpack configuration.
|
||||||
|
* @param {string} target The build target; e.g. `server`.
|
||||||
|
* @param {Object} config The neutrino configuration.
|
||||||
|
*/
|
||||||
|
'@flecks/core.webpack': (target, config) => {
|
||||||
|
if ('something' === target) {
|
||||||
|
config.stats = 'verbose';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
"babel-merge": "^3.0.0",
|
"babel-merge": "^3.0.0",
|
||||||
"babel-plugin-prepend": "^1.0.2",
|
"babel-plugin-prepend": "^1.0.2",
|
||||||
"chai": "4.2.0",
|
"chai": "4.2.0",
|
||||||
|
"chai-as-promised": "7.1.1",
|
||||||
"commander": "^8.3.0",
|
"commander": "^8.3.0",
|
||||||
"debug": "4.3.1",
|
"debug": "4.3.1",
|
||||||
"enhanced-resolve": "^5.9.2",
|
"enhanced-resolve": "^5.9.2",
|
||||||
|
|
|
@ -11,7 +11,7 @@ const {
|
||||||
FLECKS_CORE_ROOT = process.cwd(),
|
FLECKS_CORE_ROOT = process.cwd(),
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
const resolver = (source) => (path) => {
|
const resolveValidModulePath = (source) => (path) => {
|
||||||
// Does the file resolve as source?
|
// Does the file resolve as source?
|
||||||
try {
|
try {
|
||||||
R.resolve(`${source}/${path}`);
|
R.resolve(`${source}/${path}`);
|
||||||
|
@ -39,7 +39,7 @@ module.exports = () => ({config, options}) => {
|
||||||
.set(name, join(FLECKS_CORE_ROOT, 'src'));
|
.set(name, join(FLECKS_CORE_ROOT, 'src'));
|
||||||
// Calculate entry points from `files`.
|
// Calculate entry points from `files`.
|
||||||
files
|
files
|
||||||
.filter(resolver(source))
|
.filter(resolveValidModulePath(source))
|
||||||
.forEach((file) => {
|
.forEach((file) => {
|
||||||
const trimmed = join(dirname(file), basename(file, extname(file)));
|
const trimmed = join(dirname(file), basename(file, extname(file)));
|
||||||
config
|
config
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
|
// Get a runtime require function by hook or by crook. :)
|
||||||
|
|
||||||
// eslint-disable-next-line no-eval
|
// eslint-disable-next-line no-eval
|
||||||
module.exports = eval('"undefined" !== typeof require ? require : undefined');
|
module.exports = eval('"undefined" !== typeof require ? require : undefined');
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
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};
|
|
||||||
}, {});
|
|
||||||
};
|
|
|
@ -16,24 +16,38 @@ import Middleware from './middleware';
|
||||||
const debug = D('@flecks/core/flecks');
|
const debug = D('@flecks/core/flecks');
|
||||||
const debugSilly = debug.extend('silly');
|
const debugSilly = debug.extend('silly');
|
||||||
|
|
||||||
|
// Symbols for Gathered classes.
|
||||||
export const ById = Symbol.for('@flecks/core.byId');
|
export const ById = Symbol.for('@flecks/core.byId');
|
||||||
export const ByType = Symbol.for('@flecks/core.byType');
|
export const ByType = Symbol.for('@flecks/core.byType');
|
||||||
export const Hooks = Symbol.for('@flecks/core.hooks');
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalize a string.
|
||||||
|
*
|
||||||
|
* @param {string} string
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
const capitalize = (string) => string.substring(0, 1).toUpperCase() + string.substring(1);
|
const capitalize = (string) => string.substring(0, 1).toUpperCase() + string.substring(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CamelCase a string.
|
||||||
|
*
|
||||||
|
* @param {string} string
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
const camelCase = (string) => string.split(/[_-]/).map(capitalize).join('');
|
const camelCase = (string) => string.split(/[_-]/).map(capitalize).join('');
|
||||||
|
|
||||||
|
// Track gathered for HMR.
|
||||||
const hotGathered = new Map();
|
const hotGathered = new Map();
|
||||||
|
|
||||||
const wrapperClass = (Class, id, idAttribute, type, typeAttribute) => {
|
// Wrap classes to expose their flecks ID and type.
|
||||||
|
const wrapGathered = (Class, id, idProperty, type, typeProperty) => {
|
||||||
class Subclass extends Class {
|
class Subclass extends Class {
|
||||||
|
|
||||||
static get [idAttribute]() {
|
static get [idProperty]() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get [typeAttribute]() {
|
static get [typeProperty]() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,72 +57,121 @@ const wrapperClass = (Class, id, idAttribute, type, typeAttribute) => {
|
||||||
|
|
||||||
export default class Flecks {
|
export default class Flecks {
|
||||||
|
|
||||||
|
config = {};
|
||||||
|
|
||||||
|
flecks = {};
|
||||||
|
|
||||||
|
hooks = {};
|
||||||
|
|
||||||
|
platforms = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} init
|
||||||
|
* @param {object} init.config The Flecks configuration (e.g. loaded from `flecks.yml`).
|
||||||
|
* @param {string[]} init.platforms Platforms this instance is running on.
|
||||||
|
*/
|
||||||
constructor({
|
constructor({
|
||||||
config = {},
|
config = {},
|
||||||
flecks = {},
|
flecks = {},
|
||||||
platforms = [],
|
platforms = [],
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.config = {
|
const emptyConfigForAllFlecks = Object.fromEntries(
|
||||||
...Object.fromEntries(Object.keys(flecks).map((path) => [path, {}])),
|
Object.keys(flecks).map((path) => [path, {}]),
|
||||||
...config,
|
);
|
||||||
};
|
this.config = {...emptyConfigForAllFlecks, ...config};
|
||||||
this.hooks = {};
|
|
||||||
this.flecks = {};
|
|
||||||
this.platforms = platforms;
|
this.platforms = platforms;
|
||||||
const entries = Object.entries(flecks);
|
const entries = Object.entries(flecks);
|
||||||
debugSilly('paths: %O', entries.map(([fleck]) => fleck));
|
debugSilly('paths: %O', entries.map(([fleck]) => fleck));
|
||||||
for (let i = 0; i < entries.length; i++) {
|
for (let i = 0; i < entries.length; i++) {
|
||||||
const [fleck, M] = entries[i];
|
const [fleck, M] = entries[i];
|
||||||
this.registerFleck(fleck, M);
|
this.registerFleckHooks(fleck, M);
|
||||||
|
this.invoke('@flecks/core.registered', fleck, M);
|
||||||
}
|
}
|
||||||
this.configureFlecks();
|
this.configureFlecksDefaults();
|
||||||
debugSilly('config: %O', this.config);
|
debugSilly('config: %O', this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
configureFleck(fleck) {
|
/**
|
||||||
|
* Configure defaults for a fleck.
|
||||||
|
*
|
||||||
|
* @param {string} fleck
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
configureFleckDefaults(fleck) {
|
||||||
this.config[fleck] = {
|
this.config[fleck] = {
|
||||||
...this.invokeFleck('@flecks/core.config', fleck),
|
...this.invokeFleck('@flecks/core.config', fleck),
|
||||||
...this.config[fleck],
|
...this.config[fleck],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
configureFlecks() {
|
/**
|
||||||
const defaultConfig = this.invoke('@flecks/core.config');
|
* Configure defaults for all flecks.
|
||||||
const flecks = Object.keys(defaultConfig);
|
*
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
configureFlecksDefaults() {
|
||||||
|
const flecks = this.flecksImplementing('@flecks/core.config');
|
||||||
for (let i = 0; i < flecks.length; i++) {
|
for (let i = 0; i < flecks.length; i++) {
|
||||||
this.configureFleck(flecks[i]);
|
this.configureFleckDefaults(flecks[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Dasherize]{@link https://en.wiktionary.org/wiki/dasherize} a fleck path.
|
||||||
|
*
|
||||||
|
* @param {string} path The path to dasherize.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static dasherizePath(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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a decorator from a require context.
|
||||||
|
*
|
||||||
|
* @param {*} context @see {@link https://webpack.js.org/guides/dependency-management/#requirecontext}
|
||||||
|
* @param {object} config
|
||||||
|
* @param {function} [config.transformer = {@link camelCase}]
|
||||||
|
* Function to run on each context path.
|
||||||
|
* @returns {function} The decorator.
|
||||||
|
*/
|
||||||
static decorate(
|
static decorate(
|
||||||
context,
|
context,
|
||||||
{
|
{
|
||||||
transformer = camelCase,
|
transformer = camelCase,
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
return (Gathered, flecks) => {
|
return (Gathered, flecks) => (
|
||||||
context.keys()
|
context.keys()
|
||||||
.forEach((path) => {
|
.reduce(
|
||||||
const {default: M} = context(path);
|
(Gathered, path) => {
|
||||||
if ('function' !== typeof M) {
|
const key = transformer(this.dasherizePath(path));
|
||||||
throw new ReferenceError(
|
if (!Gathered[key]) {
|
||||||
`Flecks.decorate(): require(${
|
return Gathered;
|
||||||
path
|
}
|
||||||
}).default is not a function (from: ${
|
const {default: M} = context(path);
|
||||||
context.id
|
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));
|
}
|
||||||
if (Gathered[key]) {
|
return {...Gathered, [key]: M(Gathered[key], flecks)};
|
||||||
// eslint-disable-next-line no-param-reassign
|
},
|
||||||
Gathered[key] = M(Gathered[key], flecks);
|
Gathered,
|
||||||
}
|
)
|
||||||
});
|
);
|
||||||
return Gathered;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy this instance.
|
||||||
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
this.config = {};
|
this.config = {};
|
||||||
this.hooks = {};
|
this.hooks = {};
|
||||||
|
@ -116,12 +179,20 @@ export default class Flecks {
|
||||||
this.platforms = [];
|
this.platforms = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all flecks implementing a hook, including platform-specific and elided variants.
|
||||||
|
*
|
||||||
|
* @param {string} hook
|
||||||
|
* @returns {string[]} The expanded list of flecks.
|
||||||
|
*/
|
||||||
expandedFlecks(hook) {
|
expandedFlecks(hook) {
|
||||||
const flecks = this.lookupFlecks(hook);
|
const flecks = this.lookupFlecks(hook);
|
||||||
let expanded = [];
|
let expanded = [];
|
||||||
for (let i = 0; i < flecks.length; ++i) {
|
for (let i = 0; i < flecks.length; ++i) {
|
||||||
const fleck = flecks[i];
|
const fleck = flecks[i];
|
||||||
|
// Just the fleck.
|
||||||
expanded.push(fleck);
|
expanded.push(fleck);
|
||||||
|
// Platform-specific variants.
|
||||||
for (let j = 0; j < this.platforms.length; ++j) {
|
for (let j = 0; j < this.platforms.length; ++j) {
|
||||||
const platform = this.platforms[j];
|
const platform = this.platforms[j];
|
||||||
const variant = join(fleck, platform);
|
const variant = join(fleck, platform);
|
||||||
|
@ -130,6 +201,7 @@ export default class Flecks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Expand elided flecks.
|
||||||
const index = expanded.findIndex((fleck) => '...' === fleck);
|
const index = expanded.findIndex((fleck) => '...' === fleck);
|
||||||
if (-1 !== index) {
|
if (-1 !== index) {
|
||||||
if (-1 !== expanded.slice(index + 1).findIndex((fleck) => '...' === fleck)) {
|
if (-1 !== expanded.slice(index + 1).findIndex((fleck) => '...' === fleck)) {
|
||||||
|
@ -158,33 +230,66 @@ export default class Flecks {
|
||||||
return expanded;
|
return expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the module for a fleck.
|
||||||
|
*
|
||||||
|
* @param {*} fleck
|
||||||
|
*
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
fleck(fleck) {
|
fleck(fleck) {
|
||||||
return this.flecks[fleck];
|
return this.flecks[fleck];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a fleck implements a hook.
|
||||||
|
*
|
||||||
|
* @param {*} fleck
|
||||||
|
* @param {string} hook
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
fleckImplements(fleck, hook) {
|
fleckImplements(fleck, hook) {
|
||||||
return !!this.hooks[hook].find(({fleck: candidate}) => fleck === candidate);
|
return !!this.hooks[hook].find(({fleck: candidate}) => fleck === candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of flecks implementing a hook.
|
||||||
|
*
|
||||||
|
* @param {string} hook
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
flecksImplementing(hook) {
|
flecksImplementing(hook) {
|
||||||
return this.hooks[hook]?.map(({fleck}) => fleck) || [];
|
return this.hooks[hook]?.map(({fleck}) => fleck) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gather and register class types.
|
||||||
|
*
|
||||||
|
* @param {string} hook
|
||||||
|
* @param {object} config
|
||||||
|
* @param {string} [config.idProperty='id'] The property used to get/set the class ID.
|
||||||
|
* @param {string} [config.typeProperty='type'] The property used to get/set the class type.
|
||||||
|
* @param {function} [config.check=() => {}] Check the validity of the gathered classes.
|
||||||
|
* @returns {object} An object with keys for ID, type, {@link ById}, and {@link ByType}.
|
||||||
|
*/
|
||||||
gather(
|
gather(
|
||||||
hook,
|
hook,
|
||||||
{
|
{
|
||||||
idAttribute = 'id',
|
idProperty = 'id',
|
||||||
typeAttribute = 'type',
|
typeProperty = 'type',
|
||||||
check = () => {},
|
check = () => {},
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
if (!hook || 'string' !== typeof hook) {
|
if (!hook || 'string' !== typeof hook) {
|
||||||
throw new TypeError('Flecks.gather(): Expects parameter 1 (hook) to be string');
|
throw new TypeError('Flecks.gather(): Expects parameter 1 (hook) to be string');
|
||||||
}
|
}
|
||||||
|
// Gather classes and check.
|
||||||
const raw = this.invokeMerge(hook);
|
const raw = this.invokeMerge(hook);
|
||||||
check(raw, hook);
|
check(raw, hook);
|
||||||
|
// Decorate and check.
|
||||||
const decorated = this.invokeComposed(`${hook}.decorate`, raw);
|
const decorated = this.invokeComposed(`${hook}.decorate`, raw);
|
||||||
check(decorated, `${hook}.decorate`);
|
check(decorated, `${hook}.decorate`);
|
||||||
|
// Assign unique IDs to each class and sort by type.
|
||||||
let uid = 1;
|
let uid = 1;
|
||||||
const ids = {};
|
const ids = {};
|
||||||
const types = (
|
const types = (
|
||||||
|
@ -193,50 +298,78 @@ export default class Flecks {
|
||||||
.sort(([ltype], [rtype]) => (ltype < rtype ? -1 : 1))
|
.sort(([ltype], [rtype]) => (ltype < rtype ? -1 : 1))
|
||||||
.map(([type, Class]) => {
|
.map(([type, Class]) => {
|
||||||
const id = uid++;
|
const id = uid++;
|
||||||
ids[id] = wrapperClass(Class, id, idAttribute, type, typeAttribute);
|
ids[id] = wrapGathered(Class, id, idProperty, type, typeProperty);
|
||||||
return [type, ids[id]];
|
return [type, ids[id]];
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
// Conglomerate all ID and type keys along with Symbols for accessing either/or.
|
||||||
const gathered = {
|
const gathered = {
|
||||||
...ids,
|
...ids,
|
||||||
...types,
|
...types,
|
||||||
[ById]: ids,
|
[ById]: ids,
|
||||||
[ByType]: types,
|
[ByType]: types,
|
||||||
};
|
};
|
||||||
hotGathered.set(hook, {idAttribute, gathered, typeAttribute});
|
// Register for HMR?
|
||||||
|
if (module.hot) {
|
||||||
|
hotGathered.set(hook, {idProperty, gathered, typeProperty});
|
||||||
|
}
|
||||||
debug("gathered '%s': %O", hook, Object.keys(gathered[ByType]));
|
debug("gathered '%s': %O", hook, Object.keys(gathered[ByType]));
|
||||||
return gathered;
|
return gathered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a configuration value.
|
||||||
|
*
|
||||||
|
* @param {string} path The configuration path e.g. `@flecks/example.config`.
|
||||||
|
* @param {*} defaultValue The default value if no configuration value is found.
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
get(path, defaultValue) {
|
get(path, defaultValue) {
|
||||||
return get(this.config, path, defaultValue);
|
return get(this.config, path, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an object whose keys are fleck paths and values are the result of invoking the hook.
|
||||||
|
* @param {string} hook
|
||||||
|
* @param {...any} args Arguments passed to each implementation.
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
invoke(hook, ...args) {
|
invoke(hook, ...args) {
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return this.flecksImplementing(hook)
|
return this.flecksImplementing(hook)
|
||||||
.reduce((r, fleck) => ({
|
.reduce((r, fleck) => ({...r, [fleck]: this.invokeFleck(hook, fleck, ...args)}), {});
|
||||||
...r,
|
|
||||||
[fleck]: this.invokeFleck(hook, fleck, ...args),
|
|
||||||
}), {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
invokeComposed(hook, arg, ...args) {
|
/**
|
||||||
|
* See: [function composition](https://www.educative.io/edpresso/function-composition-in-javascript).
|
||||||
|
*
|
||||||
|
* @configurable
|
||||||
|
* @param {string} hook
|
||||||
|
* @param {*} initial The initial value passed to the composition chain.
|
||||||
|
* @param {...any} args The arguments passed after the accumulator to each implementation.
|
||||||
|
* @returns {*} The final composed value.
|
||||||
|
*/
|
||||||
|
invokeComposed(hook, initial, ...args) {
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
return arg;
|
return initial;
|
||||||
}
|
}
|
||||||
const flecks = this.expandedFlecks(hook);
|
const flecks = this.expandedFlecks(hook);
|
||||||
if (0 === flecks.length) {
|
if (0 === flecks.length) {
|
||||||
return arg;
|
return initial;
|
||||||
}
|
}
|
||||||
return flecks
|
return flecks
|
||||||
.filter((fleck) => this.fleckImplements(fleck, hook))
|
.filter((fleck) => this.fleckImplements(fleck, hook))
|
||||||
.reduce((r, fleck) => this.invokeFleck(hook, fleck, r, ...args), arg);
|
.reduce((r, fleck) => this.invokeFleck(hook, fleck, r, ...args), initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An async version of `invokeComposed`.
|
||||||
|
*
|
||||||
|
* @see {@link Flecks#invokeComposed}
|
||||||
|
*/
|
||||||
async invokeComposedAsync(hook, arg, ...args) {
|
async invokeComposedAsync(hook, arg, ...args) {
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
return arg;
|
return arg;
|
||||||
|
@ -250,6 +383,13 @@ export default class Flecks {
|
||||||
.reduce(async (r, fleck) => this.invokeFleck(hook, fleck, await r, ...args), arg);
|
.reduce(async (r, fleck) => this.invokeFleck(hook, fleck, await r, ...args), arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes a hook and returns a flat array of results.
|
||||||
|
*
|
||||||
|
* @param {string} hook
|
||||||
|
* @param {...any} args The arguments passed to each implementation.
|
||||||
|
* @returns {any[]}
|
||||||
|
*/
|
||||||
invokeFlat(hook, ...args) {
|
invokeFlat(hook, ...args) {
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -257,6 +397,14 @@ export default class Flecks {
|
||||||
return this.hooks[hook].map(({fleck}) => this.invokeFleck(hook, fleck, ...args));
|
return this.hooks[hook].map(({fleck}) => this.invokeFleck(hook, fleck, ...args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes a hook on a single fleck.
|
||||||
|
*
|
||||||
|
* @param {string} hook
|
||||||
|
* @param {*} fleck
|
||||||
|
* @param {...any} args
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
invokeFleck(hook, fleck, ...args) {
|
invokeFleck(hook, fleck, ...args) {
|
||||||
debugSilly('invokeFleck(%s, %s, ...)', hook, fleck);
|
debugSilly('invokeFleck(%s, %s, ...)', hook, fleck);
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
|
@ -270,33 +418,116 @@ export default class Flecks {
|
||||||
return candidate.fn(...(args.concat(this)));
|
return candidate.fn(...(args.concat(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static $$invokeMerge(r, o) {
|
||||||
|
return {...r, ...o};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization of `invokeReduce`. Invokes a hook and reduces an object from all the resulting
|
||||||
|
* objects.
|
||||||
|
*
|
||||||
|
* @param {string} hook
|
||||||
|
* @param {...any} args
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
invokeMerge(hook, ...args) {
|
invokeMerge(hook, ...args) {
|
||||||
return this.invokeReduce(hook, (r, o) => ({...r, ...o}), {}, ...args);
|
return this.invokeReduce(hook, this.constructor.$$invokeMerge, {}, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An async version of `invokeMerge`.
|
||||||
|
*
|
||||||
|
* @see {@link Flecks#invokeMerge}
|
||||||
|
*/
|
||||||
async invokeMergeAsync(hook, ...args) {
|
async invokeMergeAsync(hook, ...args) {
|
||||||
return this.invokeReduceAsync(hook, (r, o) => ({...r, ...o}), {}, ...args);
|
return this.invokeReduceAsync(hook, this.constructor.$$invokeMerge, {}, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static $$invokeMergeUnique() {
|
||||||
|
const track = {};
|
||||||
|
return (r, o, fleck, hook) => {
|
||||||
|
const keys = Object.keys(o);
|
||||||
|
for (let i = 0; i < keys.length; ++i) {
|
||||||
|
const key = keys[i];
|
||||||
|
if (track[key]) {
|
||||||
|
throw new ReferenceError(
|
||||||
|
`Conflict in ${hook}: '${track[key]}' implemented '${key}', followed by '${fleck}'`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
track[key] = fleck;
|
||||||
|
}
|
||||||
|
return ({...r, ...o});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization of `invokeMerge`. Invokes a hook and reduces an object from all the resulting
|
||||||
|
* objects.
|
||||||
|
*
|
||||||
|
* @param {string} hook
|
||||||
|
* @param {...any} args
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
invokeMergeUnique(hook, ...args) {
|
||||||
|
return this.invokeReduce(hook, this.constructor.$$invokeMergeUnique(), {}, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An async version of `invokeMergeUnique`.
|
||||||
|
*
|
||||||
|
* @see {@link Flecks#invokeMergeUnique}
|
||||||
|
*/
|
||||||
|
async invokeMergeUniqueAsync(hook, ...args) {
|
||||||
|
return this.invokeReduceAsync(hook, this.constructor.$$invokeMergeUnique(), {}, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See: [Array.prototype.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)
|
||||||
|
*
|
||||||
|
* @param {string} hook
|
||||||
|
* @param {*} reducer
|
||||||
|
* @param {*} initial
|
||||||
|
* @param {...any} args The arguments passed after the accumulator to each implementation.
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
invokeReduce(hook, reducer, initial, ...args) {
|
invokeReduce(hook, reducer, initial, ...args) {
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
return initial;
|
return initial;
|
||||||
}
|
}
|
||||||
return this.hooks[hook]
|
return this.hooks[hook]
|
||||||
.reduce((r, {fleck}) => reducer(r, this.invokeFleck(hook, fleck, ...args)), initial);
|
.reduce(
|
||||||
|
(r, {fleck}) => reducer(r, this.invokeFleck(hook, fleck, ...args), fleck, hook),
|
||||||
|
initial,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An async version of `invokeReduce`.
|
||||||
|
*
|
||||||
|
* @see {@link Flecks#invokeReduce}
|
||||||
|
*/
|
||||||
async invokeReduceAsync(hook, reducer, initial, ...args) {
|
async invokeReduceAsync(hook, reducer, initial, ...args) {
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
return initial;
|
return initial;
|
||||||
}
|
}
|
||||||
return this.hooks[hook]
|
return this.hooks[hook]
|
||||||
.reduce(
|
.reduce(
|
||||||
async (r, {fleck}) => reducer(await r, await this.invokeFleck(hook, fleck, ...args)),
|
async (r, {fleck}) => (
|
||||||
|
reducer(await r, await this.invokeFleck(hook, fleck, ...args), fleck, hook)
|
||||||
|
),
|
||||||
initial,
|
initial,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes hooks on a fleck one after another. This is effectively a configurable version of
|
||||||
|
* {@link Flecks#invokeFlat}.
|
||||||
|
*
|
||||||
|
* @configurable
|
||||||
|
* @param {string} hook
|
||||||
|
* @param {...any} args The arguments passed to each implementation.
|
||||||
|
* @returns {any[]}
|
||||||
|
*/
|
||||||
invokeSequential(hook, ...args) {
|
invokeSequential(hook, ...args) {
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -315,6 +546,11 @@ export default class Flecks {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An async version of `invokeSequential`.
|
||||||
|
*
|
||||||
|
* @see {@link Flecks#invokeSequential}
|
||||||
|
*/
|
||||||
async invokeSequentialAsync(hook, ...args) {
|
async invokeSequentialAsync(hook, ...args) {
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -334,10 +570,18 @@ export default class Flecks {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
isOnPlatform(platform) {
|
/**
|
||||||
return -1 !== this.platforms.indexOf(platform);
|
* Lookup flecks configured for a hook.
|
||||||
}
|
*
|
||||||
|
* If no configuration is found, defaults to ellipses.
|
||||||
|
*
|
||||||
|
* @param {string} hook
|
||||||
|
* @example
|
||||||
|
* # Given hook @flecks/example.hook, `flecks.yml` could be configured as such:
|
||||||
|
* '@flecks/example':
|
||||||
|
* hook: ['...']
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
lookupFlecks(hook) {
|
lookupFlecks(hook) {
|
||||||
const index = hook.indexOf('.');
|
const index = hook.indexOf('.');
|
||||||
if (-1 === index) {
|
if (-1 === index) {
|
||||||
|
@ -346,31 +590,37 @@ export default class Flecks {
|
||||||
return this.get([hook.slice(0, index), hook.slice(index + 1)], ['...']);
|
return this.get([hook.slice(0, index), hook.slice(index + 1)], ['...']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a middleware function from configured middleware.
|
||||||
|
* @param {string} hook
|
||||||
|
* @returns {function}
|
||||||
|
*/
|
||||||
makeMiddleware(hook) {
|
makeMiddleware(hook) {
|
||||||
debugSilly('makeMiddleware(...): %s', hook);
|
debugSilly('makeMiddleware(...): %s', hook);
|
||||||
if (!this.hooks[hook]) {
|
if (!this.hooks[hook]) {
|
||||||
return Promise.resolve();
|
return (...args) => args.pop()();
|
||||||
}
|
}
|
||||||
const flecks = this.expandedFlecks(hook);
|
const flecks = this.expandedFlecks(hook);
|
||||||
if (0 === flecks.length) {
|
if (0 === flecks.length) {
|
||||||
return Promise.resolve();
|
return (...args) => args.pop()();
|
||||||
}
|
}
|
||||||
const middleware = flecks
|
const middleware = flecks
|
||||||
.filter((fleck) => this.fleckImplements(fleck, hook));
|
.filter((fleck) => this.fleckImplements(fleck, hook));
|
||||||
debugSilly('middleware: %O', middleware);
|
debugSilly('middleware: %O', middleware);
|
||||||
const instance = new Middleware(middleware.map((fleck) => this.invokeFleck(hook, fleck)));
|
const instance = new Middleware(middleware.map((fleck) => this.invokeFleck(hook, fleck)));
|
||||||
return async (...args) => {
|
return instance.dispatch.bind(instance);
|
||||||
const next = args.pop();
|
|
||||||
try {
|
|
||||||
await instance.promise(...args);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide classes for e.g. {@link Flecks#gather}
|
||||||
|
*
|
||||||
|
* @param {*} context @see {@link https://webpack.js.org/guides/dependency-management/#requirecontext}
|
||||||
|
* @param {object} config
|
||||||
|
* @param {function} [config.invoke = true] Invoke the default exports as a function?
|
||||||
|
* @param {function} [config.transformer = {@link camelCase}]
|
||||||
|
* Function to run on each context path.
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
static provide(
|
static provide(
|
||||||
context,
|
context,
|
||||||
{
|
{
|
||||||
|
@ -393,7 +643,7 @@ export default class Flecks {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
transformer(this.symbolizePath(path)),
|
transformer(this.dasherizePath(path)),
|
||||||
invoke ? M(flecks) : M,
|
invoke ? M(flecks) : M,
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
|
@ -401,9 +651,103 @@ export default class Flecks {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh a fleck's hooks, configuration, and any gathered classes.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* module.hot.accept('@flecks/example', async () => {
|
||||||
|
* flecks.refresh('@flecks/example', require('@flecks/example'));
|
||||||
|
* });
|
||||||
|
* @param {string} fleck
|
||||||
|
* @param {object} M The fleck module
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
refresh(fleck, M) {
|
refresh(fleck, M) {
|
||||||
debug('refreshing %s...', fleck);
|
debug('refreshing %s...', fleck);
|
||||||
// Remove old hook implementations.
|
// Remove old hook implementations.
|
||||||
|
this.unregisterFleckHooks(fleck);
|
||||||
|
// Replace the fleck.
|
||||||
|
this.registerFleckHooks(fleck, M);
|
||||||
|
// Write config.
|
||||||
|
this.configureFleckDefaults(fleck);
|
||||||
|
// HMR.
|
||||||
|
if (module.hot) {
|
||||||
|
this.refreshGathered(fleck);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh gathered classes for a fleck.
|
||||||
|
*
|
||||||
|
* @param {string} fleck
|
||||||
|
*/
|
||||||
|
refreshGathered(fleck) {
|
||||||
|
const it = hotGathered.entries();
|
||||||
|
for (let current = it.next(); current.done !== true; current = it.next()) {
|
||||||
|
const {
|
||||||
|
value: [
|
||||||
|
hook,
|
||||||
|
{
|
||||||
|
idProperty,
|
||||||
|
gathered,
|
||||||
|
typeProperty,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} = 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]: {[idProperty]: id}} = gathered;
|
||||||
|
const Subclass = wrapGathered(Class, id, idProperty, type, typeProperty);
|
||||||
|
// eslint-disable-next-line no-multi-assign
|
||||||
|
gathered[type] = gathered[id] = gathered[ById][id] = gathered[ByType][type] = Subclass;
|
||||||
|
this.invoke('@flecks/core.hmr.gathered', Subclass, hook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register hooks for a fleck.
|
||||||
|
*
|
||||||
|
* @param {string} fleck
|
||||||
|
* @param {object} M The fleck module
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
registerFleckHooks(fleck, M) {
|
||||||
|
debugSilly('registering %s...', fleck);
|
||||||
|
this.flecks[fleck] = M;
|
||||||
|
if (M.hooks) {
|
||||||
|
const keys = Object.keys(M.hooks);
|
||||||
|
debugSilly("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: M.hooks[key]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a configuration value.
|
||||||
|
*
|
||||||
|
* @param {string} path The configuration path e.g. `@flecks/example.config`.
|
||||||
|
* @param {*} value The value to set.
|
||||||
|
* @returns {*} The value that was set.
|
||||||
|
*/
|
||||||
|
set(path, value) {
|
||||||
|
return set(this.config, path, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister hooks for a fleck.
|
||||||
|
* @param {*} fleck
|
||||||
|
*/
|
||||||
|
unregisterFleckHooks(fleck) {
|
||||||
const keys = Object.keys(this.hooks);
|
const keys = Object.keys(this.hooks);
|
||||||
for (let j = 0; j < keys.length; j++) {
|
for (let j = 0; j < keys.length; j++) {
|
||||||
const key = keys[j];
|
const key = keys[j];
|
||||||
|
@ -414,82 +758,6 @@ export default class Flecks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Replace the fleck.
|
|
||||||
this.registerFleck(fleck, M);
|
|
||||||
// Write config.
|
|
||||||
this.configureFleck(fleck);
|
|
||||||
// HMR.
|
|
||||||
this.updateHotGathered(fleck);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerFleck(fleck, M) {
|
|
||||||
debugSilly('registering %s...', fleck);
|
|
||||||
this.flecks[fleck] = M;
|
|
||||||
if (M.default) {
|
|
||||||
const {default: {[Hooks]: hooks}} = M;
|
|
||||||
if (hooks) {
|
|
||||||
const keys = Object.keys(hooks);
|
|
||||||
debugSilly("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 {
|
|
||||||
debugSilly("'%s' has no default export", fleck);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.hmr.gathered', Subclass, hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,18 @@
|
||||||
import {Hooks} from './flecks';
|
|
||||||
|
|
||||||
export {default as Class} from './class';
|
export {default as Class} from './class';
|
||||||
export {default as compose} from './compose';
|
export {default as compose} from './compose';
|
||||||
export {default as D} from './debug';
|
export {default as D} from './debug';
|
||||||
export {default as ensureUniqueReduction} from './ensure-unique-reduction';
|
|
||||||
export {default as EventEmitter} from './event-emitter';
|
export {default as EventEmitter} from './event-emitter';
|
||||||
export {
|
export {
|
||||||
default as Flecks,
|
default as Flecks,
|
||||||
ById,
|
ById,
|
||||||
ByType,
|
ByType,
|
||||||
Hooks,
|
|
||||||
} from './flecks';
|
} from './flecks';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* The ID of your application.
|
||||||
* The ID of your application.
|
*/
|
||||||
*/
|
id: 'flecks',
|
||||||
id: 'flecks',
|
}),
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,6 +34,7 @@ module.exports = {
|
||||||
rules: {
|
rules: {
|
||||||
'babel/object-curly-spacing': 'off',
|
'babel/object-curly-spacing': 'off',
|
||||||
'brace-style': ['error', 'stroustrup'],
|
'brace-style': ['error', 'stroustrup'],
|
||||||
|
'import/prefer-default-export': 'off',
|
||||||
'jsx-a11y/control-has-associated-label': ['error', {assert: 'either'}],
|
'jsx-a11y/control-has-associated-label': ['error', {assert: 'either'}],
|
||||||
'jsx-a11y/label-has-associated-control': ['error', {assert: 'either'}],
|
'jsx-a11y/label-has-associated-control': ['error', {assert: 'either'}],
|
||||||
'no-plusplus': 'off',
|
'no-plusplus': 'off',
|
||||||
|
|
|
@ -22,6 +22,7 @@ const {
|
||||||
FLECKS_CORE_SYNC_FOR_ESLINT = false,
|
FLECKS_CORE_SYNC_FOR_ESLINT = false,
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
|
// This is kinda nuts, but ESLint doesn't support its configuration files returning a promise!
|
||||||
if (FLECKS_CORE_SYNC_FOR_ESLINT) {
|
if (FLECKS_CORE_SYNC_FOR_ESLINT) {
|
||||||
(async () => {
|
(async () => {
|
||||||
debug('bootstrapping flecks...');
|
debug('bootstrapping flecks...');
|
||||||
|
@ -50,6 +51,7 @@ else {
|
||||||
module.exports = JSON.parse(readFileSync(join(cacheDirectory, 'eslintrc.json')).toString());
|
module.exports = JSON.parse(readFileSync(join(cacheDirectory, 'eslintrc.json')).toString());
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
// Just silly. By synchronously spawning... ourselves, the spawned copy can use async.
|
||||||
const {stderr, stdout} = spawnSync('node', [__filename], {
|
const {stderr, stdout} = spawnSync('node', [__filename], {
|
||||||
env: {
|
env: {
|
||||||
FLECKS_CORE_SYNC_FOR_ESLINT: true,
|
FLECKS_CORE_SYNC_FOR_ESLINT: true,
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {inspect} from 'util';
|
||||||
import airbnb from '@neutrinojs/airbnb';
|
import airbnb from '@neutrinojs/airbnb';
|
||||||
import neutrino from 'neutrino';
|
import neutrino from 'neutrino';
|
||||||
|
|
||||||
import {Hooks} from '../flecks';
|
|
||||||
import commands from './commands';
|
import commands from './commands';
|
||||||
import R from '../bootstrap/require';
|
import R from '../bootstrap/require';
|
||||||
|
|
||||||
|
@ -31,81 +30,79 @@ export {default as fleck} from '../bootstrap/fleck';
|
||||||
export {default as require} from '../bootstrap/require';
|
export {default as require} from '../bootstrap/require';
|
||||||
export {JsonStream, transform} from './stream';
|
export {JsonStream, transform} from './stream';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.build': (target, config, flecks) => {
|
||||||
'@flecks/core.build': (target, config, flecks) => {
|
const {
|
||||||
const {
|
'eslint.exclude': exclude,
|
||||||
'eslint.exclude': exclude,
|
profile,
|
||||||
profile,
|
} = flecks.get('@flecks/core/server');
|
||||||
} = flecks.get('@flecks/core/server');
|
if (-1 !== profile.indexOf(target)) {
|
||||||
if (-1 !== profile.indexOf(target)) {
|
config.use.push(({config}) => {
|
||||||
config.use.push(({config}) => {
|
config
|
||||||
config
|
.plugin('profiler')
|
||||||
.plugin('profiler')
|
.use(
|
||||||
.use(
|
R.resolve('webpack/lib/debug/ProfilingPlugin'),
|
||||||
R.resolve('webpack/lib/debug/ProfilingPlugin'),
|
[{outputPath: join(FLECKS_CORE_ROOT, `profile.build-${target}.json`)}],
|
||||||
[{outputPath: join(FLECKS_CORE_ROOT, `profile.build-${target}.json`)}],
|
);
|
||||||
);
|
});
|
||||||
});
|
}
|
||||||
}
|
if (-1 === exclude.indexOf(target)) {
|
||||||
if (-1 === exclude.indexOf(target)) {
|
const baseConfig = R(flecks.buildConfig('.eslint.defaults.js', target));
|
||||||
const baseConfig = R(flecks.buildConfig('.eslint.defaults.js', target));
|
const webpackConfig = neutrino(config).webpack();
|
||||||
const webpackConfig = neutrino(config).webpack();
|
config.use.unshift(
|
||||||
config.use.unshift(
|
airbnb({
|
||||||
airbnb({
|
eslint: {
|
||||||
eslint: {
|
baseConfig: {
|
||||||
baseConfig: {
|
...baseConfig,
|
||||||
...baseConfig,
|
settings: {
|
||||||
settings: {
|
...(baseConfig.settings || {}),
|
||||||
...(baseConfig.settings || {}),
|
'import/resolver': {
|
||||||
'import/resolver': {
|
...(baseConfig.settings['import/resolver'] || {}),
|
||||||
...(baseConfig.settings['import/resolver'] || {}),
|
webpack: {
|
||||||
webpack: {
|
config: {
|
||||||
config: {
|
resolve: webpackConfig.resolve,
|
||||||
resolve: webpackConfig.resolve,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
);
|
}),
|
||||||
}
|
);
|
||||||
},
|
}
|
||||||
'@flecks/core.build.config': () => [
|
|
||||||
/**
|
|
||||||
* Babel configuration. See: https://babeljs.io/docs/en/config-files
|
|
||||||
*/
|
|
||||||
'babel.config.js',
|
|
||||||
/**
|
|
||||||
* ESLint defaults. The default .eslintrc.js just reads from this file so that the build
|
|
||||||
* process can dynamically configure parts of ESLint.
|
|
||||||
*/
|
|
||||||
['.eslint.defaults.js', {specifier: (specific) => `${specific}.eslint.defaults.js`}],
|
|
||||||
/**
|
|
||||||
* ESLint configuration. See: https://eslint.org/docs/user-guide/configuring/
|
|
||||||
*/
|
|
||||||
['.eslintrc.js', {specifier: (specific) => `${specific}.eslintrc.js`}],
|
|
||||||
/**
|
|
||||||
* Neutrino build configuration. See: https://neutrinojs.org/usage/
|
|
||||||
*/
|
|
||||||
['.neutrinorc.js', {specifier: (specific) => `${specific}.neutrinorc.js`}],
|
|
||||||
/**
|
|
||||||
* Webpack (v4) configuration. See: https://v4.webpack.js.org/configuration/
|
|
||||||
*/
|
|
||||||
'webpack.config.js',
|
|
||||||
],
|
|
||||||
'@flecks/core.commands': commands,
|
|
||||||
'@flecks/core.config': () => ({
|
|
||||||
/**
|
|
||||||
* Build targets to exclude from ESLint.
|
|
||||||
*/
|
|
||||||
'eslint.exclude': [],
|
|
||||||
/**
|
|
||||||
* Build targets to profile with `webpack.debug.ProfilingPlugin`.
|
|
||||||
*/
|
|
||||||
profile: [],
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
'@flecks/core.build.config': () => [
|
||||||
|
/**
|
||||||
|
* Babel configuration. See: https://babeljs.io/docs/en/config-files
|
||||||
|
*/
|
||||||
|
'babel.config.js',
|
||||||
|
/**
|
||||||
|
* ESLint defaults. The default .eslintrc.js just reads from this file so that the build
|
||||||
|
* process can dynamically configure parts of ESLint.
|
||||||
|
*/
|
||||||
|
['.eslint.defaults.js', {specifier: (specific) => `${specific}.eslint.defaults.js`}],
|
||||||
|
/**
|
||||||
|
* ESLint configuration. See: https://eslint.org/docs/user-guide/configuring/
|
||||||
|
*/
|
||||||
|
['.eslintrc.js', {specifier: (specific) => `${specific}.eslintrc.js`}],
|
||||||
|
/**
|
||||||
|
* Neutrino build configuration. See: https://neutrinojs.org/usage/
|
||||||
|
*/
|
||||||
|
['.neutrinorc.js', {specifier: (specific) => `${specific}.neutrinorc.js`}],
|
||||||
|
/**
|
||||||
|
* Webpack (v4) configuration. See: https://v4.webpack.js.org/configuration/
|
||||||
|
*/
|
||||||
|
'webpack.config.js',
|
||||||
|
],
|
||||||
|
'@flecks/core.commands': commands,
|
||||||
|
'@flecks/core.config': () => ({
|
||||||
|
/**
|
||||||
|
* Build targets to exclude from ESLint.
|
||||||
|
*/
|
||||||
|
'eslint.exclude': [],
|
||||||
|
/**
|
||||||
|
* Build targets to profile with `webpack.debug.ProfilingPlugin`.
|
||||||
|
*/
|
||||||
|
profile: [],
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import {expect} from 'chai';
|
import chai from 'chai';
|
||||||
|
import chaiAsPromised from 'chai-as-promised';
|
||||||
|
|
||||||
import {Flecks} from '@flecks/core';
|
import {Flecks} from '@flecks/core';
|
||||||
|
|
||||||
|
chai.use(chaiAsPromised);
|
||||||
|
|
||||||
|
const {expect} = chai;
|
||||||
|
|
||||||
const testOne = require('./one');
|
const testOne = require('./one');
|
||||||
const testTwo = require('./two');
|
const testTwo = require('./two');
|
||||||
|
|
||||||
|
@ -33,3 +38,13 @@ it('can invoke merge async', async () => {
|
||||||
expect(await flecks.invokeMergeAsync('@flecks/core/test/invoke-merge-async'))
|
expect(await flecks.invokeMergeAsync('@flecks/core/test/invoke-merge-async'))
|
||||||
.to.deep.equal({foo: 69, bar: 420});
|
.to.deep.equal({foo: 69, bar: 420});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can enforce uniqueness', () => {
|
||||||
|
expect(() => flecks.invokeMergeUnique('@flecks/core/test/invoke-merge-unique'))
|
||||||
|
.to.throw(ReferenceError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can enforce uniqueness async', async () => {
|
||||||
|
expect(flecks.invokeMergeUniqueAsync('@flecks/core/test/invoke-merge-unique-async'))
|
||||||
|
.to.be.rejectedWith(ReferenceError);
|
||||||
|
});
|
||||||
|
|
51
packages/core/test/middleware.js
Normal file
51
packages/core/test/middleware.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import {expect} from 'chai';
|
||||||
|
|
||||||
|
import {Flecks} from '@flecks/core';
|
||||||
|
|
||||||
|
const testOne = require('./one');
|
||||||
|
const testTwo = require('./two');
|
||||||
|
|
||||||
|
it('can make middleware', (done) => {
|
||||||
|
let flecks;
|
||||||
|
let foo;
|
||||||
|
let mw;
|
||||||
|
flecks = new Flecks({
|
||||||
|
config: {
|
||||||
|
'@flecks/core/test': {
|
||||||
|
middleware: [
|
||||||
|
'@flecks/core/one',
|
||||||
|
'@flecks/core/two',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
flecks: {
|
||||||
|
'@flecks/core/one': testOne,
|
||||||
|
'@flecks/core/two': testTwo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
foo = {bar: 1};
|
||||||
|
mw = flecks.makeMiddleware('@flecks/core/test.middleware');
|
||||||
|
mw(foo, () => {
|
||||||
|
expect(foo.bar).to.equal(4);
|
||||||
|
flecks = new Flecks({
|
||||||
|
config: {
|
||||||
|
'@flecks/core/test': {
|
||||||
|
middleware: [
|
||||||
|
'@flecks/core/two',
|
||||||
|
'@flecks/core/one',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
flecks: {
|
||||||
|
'@flecks/core/one': testOne,
|
||||||
|
'@flecks/core/two': testTwo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
foo = {bar: 1};
|
||||||
|
mw = flecks.makeMiddleware('@flecks/core/test.middleware');
|
||||||
|
mw(foo, () => {
|
||||||
|
expect(foo.bar).to.equal(3);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,5 @@
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
|
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
|
||||||
import {Flecks, Hooks} from '@flecks/core';
|
import {Flecks} from '@flecks/core';
|
||||||
|
|
||||||
export const testNodespace = () => [
|
export const testNodespace = () => [
|
||||||
/* eslint-disable no-eval */
|
/* eslint-disable no-eval */
|
||||||
|
@ -8,23 +8,28 @@ export const testNodespace = () => [
|
||||||
/* eslint-enable no-eval */
|
/* eslint-enable no-eval */
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
foo: 'bar',
|
||||||
foo: 'bar',
|
}),
|
||||||
}),
|
'@flecks/core/one/test-gather': (
|
||||||
'@flecks/core/one/test-gather': (
|
Flecks.provide(require.context('./things', false, /\.js$/))
|
||||||
Flecks.provide(require.context('./things', false, /\.js$/))
|
),
|
||||||
),
|
'@flecks/core/one/test-gather.decorate': (
|
||||||
'@flecks/core/one/test-gather.decorate': (
|
Flecks.decorate(require.context('./things/decorators', false, /\.js$/))
|
||||||
Flecks.decorate(require.context('./things/decorators', false, /\.js$/))
|
),
|
||||||
),
|
'@flecks/core/test/invoke': () => 69,
|
||||||
'@flecks/core/test/invoke': () => 69,
|
'@flecks/core/test/invoke-parallel': (O) => {
|
||||||
'@flecks/core/test/invoke-parallel': (O) => {
|
// eslint-disable-next-line no-param-reassign
|
||||||
// eslint-disable-next-line no-param-reassign
|
O.foo *= 2;
|
||||||
O.foo *= 2;
|
},
|
||||||
},
|
'@flecks/core/test/invoke-merge': () => ({foo: 69}),
|
||||||
'@flecks/core/test/invoke-merge': () => ({foo: 69}),
|
'@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => resolve({foo: 69})),
|
||||||
'@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => resolve({foo: 69})),
|
'@flecks/core/test/invoke-merge-unique': () => ({foo: 69}),
|
||||||
|
'@flecks/core/test/invoke-merge-unique-async': () => new Promise((resolve) => resolve({foo: 69})),
|
||||||
|
'@flecks/core/test.middleware': () => (foo, next) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
foo.bar += 1;
|
||||||
|
next();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import {Flecks, Hooks} from '@flecks/core';
|
import {Flecks} from '@flecks/core';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core/one/test-gather': (
|
||||||
'@flecks/core/one/test-gather': (
|
Flecks.provide(require.context('./things', false, /\.js$/))
|
||||||
Flecks.provide(require.context('./things', false, /\.js$/))
|
),
|
||||||
),
|
'@flecks/core/test/invoke': () => 420,
|
||||||
'@flecks/core/test/invoke': () => 420,
|
'@flecks/core/test/invoke-parallel': (O) => new Promise((resolve) => {
|
||||||
'@flecks/core/test/invoke-parallel': (O) => new Promise((resolve) => {
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
// eslint-disable-next-line no-param-reassign
|
||||||
// eslint-disable-next-line no-param-reassign
|
O.foo += 2;
|
||||||
O.foo += 2;
|
resolve();
|
||||||
resolve();
|
}, 0);
|
||||||
}, 0);
|
}),
|
||||||
}),
|
'@flecks/core/test/invoke-merge': () => ({bar: 420}),
|
||||||
'@flecks/core/test/invoke-merge': () => ({bar: 420}),
|
'@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => resolve({bar: 420})),
|
||||||
'@flecks/core/test/invoke-merge-async': () => new Promise((resolve) => resolve({bar: 420})),
|
'@flecks/core/test/invoke-merge-unique': () => ({foo: 69}),
|
||||||
|
'@flecks/core/test/invoke-merge-unique-async': () => new Promise((resolve) => resolve({foo: 69})),
|
||||||
|
'@flecks/core/test.middleware': () => (foo, next) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
foo.bar *= 2;
|
||||||
|
next();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,28 +1,24 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
|
* Gather database models.
|
||||||
|
*
|
||||||
|
* In the example below, your fleck would have a `models` subdirectory, and each model would be
|
||||||
|
* defined in its own file.
|
||||||
|
* See: https://github.com/cha0s/flecks/tree/master/packages/user/src/server/models
|
||||||
|
*/
|
||||||
|
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
|
||||||
|
|
||||||
export default {
|
/**
|
||||||
[Hooks]: {
|
* Decorate database models.
|
||||||
/**
|
*
|
||||||
* Gather database models.
|
* In the example below, your fleck would have a `models/decorators` subdirectory, and each
|
||||||
*
|
* decorator would be defined in its own file.
|
||||||
* In the example below, your fleck would have a `models` subdirectory, and each model would be
|
* See: https://github.com/cha0s/flecks/tree/master/packages/user/src/local/server/models/decorators
|
||||||
* defined in its own file.
|
*
|
||||||
* See: https://github.com/cha0s/flecks/tree/master/packages/user/src/server/models
|
* @param {constructor} Model The model to decorate.
|
||||||
*/
|
*/
|
||||||
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
|
'@flecks/db/server.models.decorate': (
|
||||||
|
Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
|
||||||
/**
|
),
|
||||||
* Decorate database models.
|
|
||||||
*
|
|
||||||
* In the example below, your fleck would have a `models/decorators` subdirectory, and each
|
|
||||||
* decorator would be defined in its own file.
|
|
||||||
* See: https://github.com/cha0s/flecks/tree/master/packages/user/src/local/server/models/decorators
|
|
||||||
*
|
|
||||||
* @param {constructor} Model The model to decorate.
|
|
||||||
*/
|
|
||||||
'@flecks/db/server.models.decorate': (
|
|
||||||
Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import {createDatabaseConnection} from './connection';
|
import {createDatabaseConnection} from './connection';
|
||||||
import containers from './containers';
|
import containers from './containers';
|
||||||
|
|
||||||
|
@ -9,49 +7,47 @@ export {default as Model} from './model';
|
||||||
|
|
||||||
export {createDatabaseConnection};
|
export {createDatabaseConnection};
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* The database to connect to.
|
||||||
* The database to connect to.
|
*/
|
||||||
*/
|
database: ':memory:',
|
||||||
database: ':memory:',
|
/**
|
||||||
/**
|
* SQL dialect.
|
||||||
* SQL dialect.
|
*
|
||||||
*
|
* See: https://sequelize.org/v5/manual/dialects.html
|
||||||
* See: https://sequelize.org/v5/manual/dialects.html
|
*/
|
||||||
*/
|
dialect: 'sqlite',
|
||||||
dialect: 'sqlite',
|
/**
|
||||||
/**
|
* Database server host.
|
||||||
* Database server host.
|
*/
|
||||||
*/
|
host: undefined,
|
||||||
host: undefined,
|
/**
|
||||||
/**
|
* Database server password.
|
||||||
* Database server password.
|
*/
|
||||||
*/
|
password: undefined,
|
||||||
password: undefined,
|
/**
|
||||||
/**
|
* Database server port.
|
||||||
* Database server port.
|
*/
|
||||||
*/
|
port: undefined,
|
||||||
port: undefined,
|
/**
|
||||||
/**
|
* Database server username.
|
||||||
* Database server username.
|
*/
|
||||||
*/
|
username: undefined,
|
||||||
username: undefined,
|
}),
|
||||||
}),
|
'@flecks/core.starting': (flecks) => {
|
||||||
'@flecks/core.starting': (flecks) => {
|
flecks.set('$flecks/db.models', flecks.gather(
|
||||||
flecks.set('$flecks/db.models', flecks.gather(
|
'@flecks/db/server.models',
|
||||||
'@flecks/db/server.models',
|
{typeProperty: 'name'},
|
||||||
{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'),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
'@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'),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,27 +1,23 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
export default {
|
* Define docker containers.
|
||||||
[Hooks]: {
|
*
|
||||||
/**
|
* Beware: the user running the server must have Docker privileges.
|
||||||
* Define docker containers.
|
* See: https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user
|
||||||
*
|
*/
|
||||||
* Beware: the user running the server must have Docker privileges.
|
'@flecks/docker.containers': () => ({
|
||||||
* See: https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user
|
someContainer: {
|
||||||
*/
|
// Environment variables.
|
||||||
'@flecks/docker.containers': () => ({
|
environment: {
|
||||||
someContainer: {
|
SOME_CONTAINER_VAR: 'hello',
|
||||||
// Environment variables.
|
|
||||||
environment: {
|
|
||||||
SOME_CONTAINER_VAR: 'hello',
|
|
||||||
},
|
|
||||||
// The docker image.
|
|
||||||
image: 'some-image:latest',
|
|
||||||
// Some container path you'd like to persist. Flecks handles the host path.
|
|
||||||
mount: '/some/container/path',
|
|
||||||
// Expose ports.
|
|
||||||
ports: {3000: 3000},
|
|
||||||
},
|
},
|
||||||
}),
|
// The docker image.
|
||||||
},
|
image: 'some-image:latest',
|
||||||
|
// Some container path you'd like to persist. Flecks handles the host path.
|
||||||
|
mount: '/some/container/path',
|
||||||
|
// Expose ports.
|
||||||
|
ports: {3000: 3000},
|
||||||
|
},
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import commands from './commands';
|
import commands from './commands';
|
||||||
import startContainer from './start-container';
|
import startContainer from './start-container';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* Whether to run docker containers.
|
||||||
* Whether to run docker containers.
|
*/
|
||||||
*/
|
enabled: true,
|
||||||
enabled: true,
|
}),
|
||||||
}),
|
'@flecks/core.commands': commands,
|
||||||
'@flecks/core.commands': commands,
|
'@flecks/server.up': async (flecks) => {
|
||||||
'@flecks/server.up': async (flecks) => {
|
if (!flecks.get('@flecks/docker/server.enabled')) {
|
||||||
if (!flecks.get('@flecks/docker/server.enabled')) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
const containers = await flecks.invokeMergeAsync('@flecks/docker.containers');
|
||||||
const containers = await flecks.invokeMergeAsync('@flecks/docker.containers');
|
await Promise.all(
|
||||||
await Promise.all(
|
Object.entries(containers)
|
||||||
Object.entries(containers)
|
.map(([key, config]) => startContainer(flecks, key, config)),
|
||||||
.map(([key, config]) => startContainer(flecks, key, config)),
|
);
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
isObjectExpression,
|
isObjectExpression,
|
||||||
isStringLiteral,
|
isStringLiteral,
|
||||||
isThisExpression,
|
isThisExpression,
|
||||||
|
isVariableDeclaration,
|
||||||
} from '@babel/types';
|
} from '@babel/types';
|
||||||
import {require as R} from '@flecks/core/server';
|
import {require as R} from '@flecks/core/server';
|
||||||
import {parse as parseComment} from 'comment-parser';
|
import {parse as parseComment} from 'comment-parser';
|
||||||
|
@ -75,15 +76,14 @@ class ParserState {
|
||||||
}
|
}
|
||||||
|
|
||||||
const implementationVisitor = (fn) => ({
|
const implementationVisitor = (fn) => ({
|
||||||
ExportDefaultDeclaration(path) {
|
ExportNamedDeclaration(path) {
|
||||||
const {declaration} = path.node;
|
const {declaration} = path.node;
|
||||||
if (isObjectExpression(declaration)) {
|
if (isVariableDeclaration(declaration)) {
|
||||||
const {properties} = declaration;
|
const {declarations} = declaration;
|
||||||
properties.forEach((property) => {
|
declarations.forEach((declarator) => {
|
||||||
const {key, value} = property;
|
if ('hooks' === declarator.id.name) {
|
||||||
if (isIdentifier(key) && key.name === 'Hooks') {
|
if (isObjectExpression(declarator.init)) {
|
||||||
if (isObjectExpression(value)) {
|
const {properties} = declarator.init;
|
||||||
const {properties} = value;
|
|
||||||
properties.forEach((property) => {
|
properties.forEach((property) => {
|
||||||
const {key} = property;
|
const {key} = property;
|
||||||
if (isLiteral(key)) {
|
if (isLiteral(key)) {
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import commands from './commands';
|
import commands from './commands';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.commands': commands,
|
||||||
'@flecks/core.commands': commands,
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* Rewrite the output filenames of source files.
|
||||||
* Rewrite the output filenames of source files.
|
*
|
||||||
*
|
* `filename.replace(new RegExp([key]), [value]);`
|
||||||
* `filename.replace(new RegExp([key]), [value]);`
|
*/
|
||||||
*/
|
filenameRewriters: {},
|
||||||
filenameRewriters: {},
|
}),
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,24 +1,20 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
|
* Invoked when electron is initializing.
|
||||||
|
* @param {Electron.App} app The electron app. See: https://www.electronjs.org/docs/latest/api/app
|
||||||
|
*/
|
||||||
|
'@flecks/electron/server.initialize': (app) => {
|
||||||
|
app.on('will-quit', () => {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
export default {
|
/**
|
||||||
[Hooks]: {
|
* Invoked when a window is created
|
||||||
/**
|
* @param {Electron.BrowserWindow} win The electron browser window. See: https://www.electronjs.org/docs/latest/api/browser-window
|
||||||
* Invoked when electron is initializing.
|
*/
|
||||||
* @param {Electron.App} app The electron app. See: https://www.electronjs.org/docs/latest/api/app
|
'@flecks/electron/server.window': (win) => {
|
||||||
*/
|
win.maximize();
|
||||||
'@flecks/electron/server.initialize': (app) => {
|
|
||||||
app.on('will-quit', () => {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when a window is created
|
|
||||||
* @param {Electron.BrowserWindow} win The electron browser window. See: https://www.electronjs.org/docs/latest/api/browser-window
|
|
||||||
*/
|
|
||||||
'@flecks/electron/server.window': (win) => {
|
|
||||||
win.maximize();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import cluster from 'cluster';
|
import cluster from 'cluster';
|
||||||
import {join} from 'path';
|
import {join} from 'path';
|
||||||
|
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
import {require as R} from '@flecks/core/server';
|
import {require as R} from '@flecks/core/server';
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
|
@ -21,119 +20,117 @@ async function createWindow(flecks) {
|
||||||
await flecks.invokeSequentialAsync('@flecks/electron/server.window', win);
|
await flecks.invokeSequentialAsync('@flecks/electron/server.window', win);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* Browser window options.
|
||||||
* Browser window options.
|
*
|
||||||
*
|
* See: https://www.electronjs.org/docs/latest/api/browser-window
|
||||||
* See: https://www.electronjs.org/docs/latest/api/browser-window
|
*/
|
||||||
*/
|
browserWindowOptions: {},
|
||||||
browserWindowOptions: {},
|
/**
|
||||||
/**
|
* Install devtools extensions (by default).
|
||||||
* Install devtools extensions (by default).
|
*
|
||||||
*
|
* If `true`, will install some devtools extensions based on which flecks are enabled.
|
||||||
* If `true`, will install some devtools extensions based on which flecks are enabled.
|
*
|
||||||
*
|
* You can pass an array of Chrome store IDs to install a list of custom extensions.
|
||||||
* You can pass an array of Chrome store IDs to install a list of custom extensions.
|
*
|
||||||
*
|
* Extensions will not be installed if `'production' === process.env.NODE_ENV`
|
||||||
* Extensions will not be installed if `'production' === process.env.NODE_ENV`
|
*/
|
||||||
*/
|
installExtensions: true,
|
||||||
installExtensions: true,
|
/**
|
||||||
/**
|
* Quit the app when all windows are closed.
|
||||||
* Quit the app when all windows are closed.
|
*/
|
||||||
*/
|
quitOnClosed: true,
|
||||||
quitOnClosed: true,
|
/**
|
||||||
/**
|
* The URL to load in electron by default.
|
||||||
* The URL to load in electron by default.
|
*
|
||||||
*
|
* Defaults to `http://${flecks.get('@flecks/web/server.public')}`.
|
||||||
* Defaults to `http://${flecks.get('@flecks/web/server.public')}`.
|
*/
|
||||||
*/
|
url: undefined,
|
||||||
url: undefined,
|
}),
|
||||||
}),
|
'@flecks/core.webpack': (target, config) => {
|
||||||
'@flecks/core.webpack': (target, config) => {
|
const StartServerWebpackPlugin = R('start-server-webpack-plugin');
|
||||||
const StartServerWebpackPlugin = R('start-server-webpack-plugin');
|
const plugin = config.plugins.find((plugin) => plugin instanceof StartServerWebpackPlugin);
|
||||||
const plugin = config.plugins.find((plugin) => plugin instanceof StartServerWebpackPlugin);
|
// Extremely hackish, c'est la vie.
|
||||||
// Extremely hackish, c'est la vie.
|
if (plugin) {
|
||||||
if (plugin) {
|
/* eslint-disable no-underscore-dangle */
|
||||||
/* eslint-disable no-underscore-dangle */
|
plugin._startServer = function _startServerHacked(callback) {
|
||||||
plugin._startServer = function _startServerHacked(callback) {
|
const execArgv = this._getArgs();
|
||||||
const execArgv = this._getArgs();
|
const inspectPort = this._getInspectPort(execArgv);
|
||||||
const inspectPort = this._getInspectPort(execArgv);
|
const clusterOptions = {
|
||||||
const clusterOptions = {
|
args: [this._entryPoint],
|
||||||
args: [this._entryPoint],
|
exec: join(FLECKS_CORE_ROOT, 'node_modules', '.bin', 'electron'),
|
||||||
exec: join(FLECKS_CORE_ROOT, 'node_modules', '.bin', 'electron'),
|
execArgv,
|
||||||
execArgv,
|
|
||||||
};
|
|
||||||
if (inspectPort) {
|
|
||||||
clusterOptions.inspectPort = inspectPort;
|
|
||||||
}
|
|
||||||
cluster.setupMaster(clusterOptions);
|
|
||||||
cluster.on('online', (worker) => {
|
|
||||||
callback(worker);
|
|
||||||
});
|
|
||||||
cluster.fork();
|
|
||||||
};
|
};
|
||||||
/* eslint-enable no-underscore-dangle */
|
if (inspectPort) {
|
||||||
}
|
clusterOptions.inspectPort = inspectPort;
|
||||||
},
|
|
||||||
'@flecks/electron/server.initialize': async (app, flecks) => {
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
const {quitOnClosed} = flecks.get('@flecks/electron/server');
|
|
||||||
if (!quitOnClosed) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Apple has to be *special*.
|
cluster.setupMaster(clusterOptions);
|
||||||
if (process.platform === 'darwin') {
|
cluster.on('online', (worker) => {
|
||||||
return;
|
callback(worker);
|
||||||
}
|
});
|
||||||
app.quit();
|
cluster.fork();
|
||||||
});
|
};
|
||||||
app.on('activate', async () => {
|
/* eslint-enable no-underscore-dangle */
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
}
|
||||||
createWindow();
|
},
|
||||||
}
|
'@flecks/electron/server.initialize': async (app, flecks) => {
|
||||||
});
|
app.on('window-all-closed', () => {
|
||||||
await app.whenReady();
|
const {quitOnClosed} = flecks.get('@flecks/electron/server');
|
||||||
await createWindow(flecks);
|
if (!quitOnClosed) {
|
||||||
},
|
|
||||||
'@flecks/electron/server.window': async (win, flecks) => {
|
|
||||||
const {public: $$public} = flecks.get('@flecks/web/server');
|
|
||||||
const {
|
|
||||||
installExtensions,
|
|
||||||
url = `http://${$$public}`,
|
|
||||||
} = flecks.get('@flecks/electron/server');
|
|
||||||
if (installExtensions && 'production' !== NODE_ENV) {
|
|
||||||
const {
|
|
||||||
default: installExtension,
|
|
||||||
REDUX_DEVTOOLS,
|
|
||||||
REACT_DEVELOPER_TOOLS,
|
|
||||||
} = __non_webpack_require__('electron-devtools-installer');
|
|
||||||
let extensions = installExtensions;
|
|
||||||
if (!Array.isArray(extensions)) {
|
|
||||||
extensions = [];
|
|
||||||
if (flecks.fleck('@flecks/react')) {
|
|
||||||
extensions.push(REACT_DEVELOPER_TOOLS);
|
|
||||||
}
|
|
||||||
if (flecks.fleck('@flecks/redux')) {
|
|
||||||
extensions.push(REDUX_DEVTOOLS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await installExtension(extensions);
|
|
||||||
}
|
|
||||||
await win.loadURL(url);
|
|
||||||
},
|
|
||||||
'@flecks/repl.context': (flecks) => ({
|
|
||||||
electron: {
|
|
||||||
createWindow: () => createWindow(flecks),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'@flecks/server.up': async (flecks) => {
|
|
||||||
// `app` will be undefined if we aren't running in an electron environment. Just bail.
|
|
||||||
if (!app) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await flecks.invokeSequentialAsync('@flecks/electron/server.initialize', app);
|
// Apple has to be *special*.
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
app.on('activate', async () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await app.whenReady();
|
||||||
|
await createWindow(flecks);
|
||||||
|
},
|
||||||
|
'@flecks/electron/server.window': async (win, flecks) => {
|
||||||
|
const {public: $$public} = flecks.get('@flecks/web/server');
|
||||||
|
const {
|
||||||
|
installExtensions,
|
||||||
|
url = `http://${$$public}`,
|
||||||
|
} = flecks.get('@flecks/electron/server');
|
||||||
|
if (installExtensions && 'production' !== NODE_ENV) {
|
||||||
|
const {
|
||||||
|
default: installExtension,
|
||||||
|
REDUX_DEVTOOLS,
|
||||||
|
REACT_DEVELOPER_TOOLS,
|
||||||
|
} = __non_webpack_require__('electron-devtools-installer');
|
||||||
|
let extensions = installExtensions;
|
||||||
|
if (!Array.isArray(extensions)) {
|
||||||
|
extensions = [];
|
||||||
|
if (flecks.fleck('@flecks/react')) {
|
||||||
|
extensions.push(REACT_DEVELOPER_TOOLS);
|
||||||
|
}
|
||||||
|
if (flecks.fleck('@flecks/redux')) {
|
||||||
|
extensions.push(REDUX_DEVTOOLS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await installExtension(extensions);
|
||||||
|
}
|
||||||
|
await win.loadURL(url);
|
||||||
|
},
|
||||||
|
'@flecks/repl.context': (flecks) => ({
|
||||||
|
electron: {
|
||||||
|
createWindow: () => createWindow(flecks),
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
'@flecks/server.up': async (flecks) => {
|
||||||
|
// `app` will be undefined if we aren't running in an electron environment. Just bail.
|
||||||
|
if (!app) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await flecks.invokeSequentialAsync('@flecks/electron/server.initialize', app);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import commands from './commands';
|
import commands from './commands';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.commands': commands,
|
||||||
'@flecks/core.commands': commands,
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* Webpack stats configuration when building fleck target.
|
||||||
* Webpack stats configuration when building fleck target.
|
*/
|
||||||
*/
|
stats: {
|
||||||
stats: {
|
children: false,
|
||||||
children: false,
|
chunks: false,
|
||||||
chunks: false,
|
colors: true,
|
||||||
colors: true,
|
modules: false,
|
||||||
modules: false,
|
},
|
||||||
},
|
}),
|
||||||
}),
|
'@flecks/core.targets': () => ['fleck'],
|
||||||
'@flecks/core.targets': () => ['fleck'],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,135 +1,133 @@
|
||||||
import {ByType, Flecks, Hooks} from '@flecks/core';
|
import {ByType, Flecks} from '@flecks/core';
|
||||||
|
|
||||||
import LimitedPacket from './limited-packet';
|
import LimitedPacket from './limited-packet';
|
||||||
import createLimiter from './limiter';
|
import createLimiter from './limiter';
|
||||||
|
|
||||||
export {default as createLimiter} from './limiter';
|
export {default as createLimiter} from './limiter';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* All keys used to determine fingerprint.
|
||||||
* All keys used to determine fingerprint.
|
*/
|
||||||
*/
|
keys: ['ip'],
|
||||||
|
web: {
|
||||||
keys: ['ip'],
|
keys: ['ip'],
|
||||||
web: {
|
points: 60,
|
||||||
keys: ['ip'],
|
duration: 30,
|
||||||
points: 60,
|
ttl: 30,
|
||||||
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/web/server.request.route': (flecks) => {
|
|
||||||
const {web} = flecks.get('@flecks/governor/server');
|
|
||||||
const limiter = flecks.get('$flecks/governor.web.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} = web;
|
|
||||||
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) => {
|
socket: {
|
||||||
if (flecks.fleck('@flecks/web/server')) {
|
keys: ['ip'],
|
||||||
const {web} = flecks.get('@flecks/governor/server');
|
points: 60,
|
||||||
const limiter = await createLimiter(
|
duration: 30,
|
||||||
flecks,
|
ttl: 30,
|
||||||
{
|
},
|
||||||
keyPrefix: '@flecks/governor.web.request.route',
|
}),
|
||||||
...web,
|
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
|
||||||
},
|
'@flecks/web/server.request.route': (flecks) => {
|
||||||
);
|
const {web} = flecks.get('@flecks/governor/server');
|
||||||
flecks.set('$flecks/governor.web.limiter', limiter);
|
const limiter = flecks.get('$flecks/governor.web.limiter');
|
||||||
|
return async (req, res, next) => {
|
||||||
|
const {Ban} = flecks.get('$flecks/db.models');
|
||||||
|
try {
|
||||||
|
await Ban.check(req);
|
||||||
}
|
}
|
||||||
if (flecks.fleck('@flecks/socket/server')) {
|
catch (error) {
|
||||||
const {[ByType]: Packets} = flecks.get('$flecks/socket.packets');
|
res.status(403).send(`<pre>${error.message}</pre>`);
|
||||||
const limiters = Object.fromEntries(
|
return;
|
||||||
await Promise.all(
|
|
||||||
Object.entries(Packets)
|
|
||||||
.filter(([, Packet]) => Packet.limit)
|
|
||||||
.map(async ([name, Packet]) => (
|
|
||||||
[
|
|
||||||
name,
|
|
||||||
await createLimiter(
|
|
||||||
flecks,
|
|
||||||
{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(
|
|
||||||
flecks,
|
|
||||||
{
|
|
||||||
keyPrefix: '@flecks/governor.socket.request.socket',
|
|
||||||
...socket,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
flecks.set('$flecks/governor.socket.limiter', limiter);
|
|
||||||
}
|
}
|
||||||
},
|
req.ban = async (keys, ttl = 0) => {
|
||||||
'@flecks/socket/server.request.socket': (flecks) => {
|
const ban = Ban.fromRequest(req, keys, ttl);
|
||||||
const limiter = flecks.get('$flecks/governor.socket.limiter');
|
await Ban.create({...ban});
|
||||||
return async (socket, next) => {
|
res.status(403).send(`<pre>${Ban.format([ban])}</pre>`);
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
try {
|
||||||
'@flecks/socket.packets.decorate': (Packets, flecks) => (
|
await limiter.consume(req.ip);
|
||||||
Object.fromEntries(
|
next();
|
||||||
Object.entries(Packets).map(([keyPrefix, Packet]) => [
|
}
|
||||||
keyPrefix,
|
catch (error) {
|
||||||
!Packet.limit ? Packet : LimitedPacket(flecks, [keyPrefix, Packet]),
|
const {ttl, keys} = web;
|
||||||
]),
|
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/web/server')) {
|
||||||
|
const {web} = flecks.get('@flecks/governor/server');
|
||||||
|
const limiter = await createLimiter(
|
||||||
|
flecks,
|
||||||
|
{
|
||||||
|
keyPrefix: '@flecks/governor.web.request.route',
|
||||||
|
...web,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
flecks.set('$flecks/governor.web.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(
|
||||||
|
flecks,
|
||||||
|
{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(
|
||||||
|
flecks,
|
||||||
|
{
|
||||||
|
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]),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,36 +1,32 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
export default {
|
* Define React Providers.
|
||||||
[Hooks]: {
|
*
|
||||||
/**
|
* Note: `req` will be only be defined when server-side rendering.
|
||||||
* Define React Providers.
|
* @param {http.ClientRequest} req The HTTP request object.
|
||||||
*
|
*/
|
||||||
* Note: `req` will be only be defined when server-side rendering.
|
'@flecks/react.providers': (req) => {
|
||||||
* @param {http.ClientRequest} req The HTTP request object.
|
// Generally it makes more sense to separate client and server concerns using platform
|
||||||
*/
|
// naming conventions, but this is just a small contrived example.
|
||||||
'@flecks/react.providers': (req) => {
|
return req ? serverSideProvider(req) : clientSideProvider();
|
||||||
// Generally it makes more sense to separate client and server concerns using platform
|
},
|
||||||
// naming conventions, but this is just a small contrived example.
|
/**
|
||||||
return req ? serverSideProvider(req) : clientSideProvider();
|
* Define root-level React components that are mounted as siblings on `#main`.
|
||||||
},
|
* Note: `req` will be only be defined when server-side rendering.
|
||||||
/**
|
*
|
||||||
* Define root-level React components that are mounted as siblings on `#main`.
|
* Return either a React component or an array whose elements must either be a React component
|
||||||
* Note: `req` will be only be defined when server-side rendering.
|
* or an array of two elements where the first element is the component and the second element
|
||||||
*
|
* is the props passed to the component.
|
||||||
* Return either a React component or an array whose elements must either be a React component
|
* @param {http.ClientRequest} req The HTTP request object.
|
||||||
* or an array of two elements where the first element is the component and the second element
|
*/
|
||||||
* is the props passed to the component.
|
'@flecks/react.roots': (req) => {
|
||||||
* @param {http.ClientRequest} req The HTTP request object.
|
// Note that we're not returning `<Component />`, but `Component`.
|
||||||
*/
|
return [
|
||||||
'@flecks/react.roots': (req) => {
|
Component,
|
||||||
// Note that we're not returning `<Component />`, but `Component`.
|
[SomeOtherComponent, {prop: 'value'}]
|
||||||
return [
|
];
|
||||||
Component,
|
// You can also just:
|
||||||
[SomeOtherComponent, {prop: 'value'}]
|
return Component;
|
||||||
];
|
|
||||||
// You can also just:
|
|
||||||
return Component;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {D, Hooks} from '@flecks/core';
|
import {D} from '@flecks/core';
|
||||||
import {hydrate, render} from '@hot-loader/react-dom';
|
import {hydrate, render} from '@hot-loader/react-dom';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
@ -10,20 +10,18 @@ const debug = D('@flecks/react/client');
|
||||||
|
|
||||||
export {FlecksContext};
|
export {FlecksContext};
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/web/client.up': async (flecks) => {
|
||||||
'@flecks/web/client.up': async (flecks) => {
|
const {ssr} = flecks.get('@flecks/react');
|
||||||
const {ssr} = flecks.get('@flecks/react');
|
debug('%sing...', ssr ? 'hydrat' : 'render');
|
||||||
debug('%sing...', ssr ? 'hydrat' : 'render');
|
(ssr ? hydrate : render)(
|
||||||
(ssr ? hydrate : render)(
|
React.createElement(
|
||||||
React.createElement(
|
React.StrictMode,
|
||||||
React.StrictMode,
|
{},
|
||||||
{},
|
[React.createElement(await root(flecks), {key: 'root'})],
|
||||||
[React.createElement(await root(flecks), {key: 'root'})],
|
),
|
||||||
),
|
window.document.getElementById('root'),
|
||||||
window.document.getElementById('root'),
|
);
|
||||||
);
|
debug('rendered');
|
||||||
debug('rendered');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
export {default as ReactDom} from '@hot-loader/react-dom';
|
export {default as ReactDom} from '@hot-loader/react-dom';
|
||||||
export {default as classnames} from 'classnames';
|
export {default as classnames} from 'classnames';
|
||||||
export {default as PropTypes} from 'prop-types';
|
export {default as PropTypes} from 'prop-types';
|
||||||
|
@ -14,13 +12,11 @@ export {default as useEvent} from './hooks/use-event';
|
||||||
export {default as useFlecks} from './hooks/use-flecks';
|
export {default as useFlecks} from './hooks/use-flecks';
|
||||||
export {default as usePrevious} from './hooks/use-previous';
|
export {default as usePrevious} from './hooks/use-previous';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* Whether to enable server-side rendering.
|
||||||
* Whether to enable server-side rendering.
|
*/
|
||||||
*/
|
ssr: true,
|
||||||
ssr: true,
|
}),
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import {createReduxHistory, history} from '@flecks/react/router/context';
|
import {createReduxHistory, history} from '@flecks/react/router/context';
|
||||||
import {unstable_HistoryRouter as HistoryRouter} from 'react-router-dom';
|
import {unstable_HistoryRouter as HistoryRouter} from 'react-router-dom';
|
||||||
import {HistoryRouter as ReduxHistoryRouter} from 'redux-first-history/rr6';
|
import {HistoryRouter as ReduxHistoryRouter} from 'redux-first-history/rr6';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/react.providers': (req, flecks) => (
|
||||||
'@flecks/react.providers': (req, flecks) => (
|
flecks.fleck('@flecks/redux')
|
||||||
flecks.fleck('@flecks/redux')
|
? [ReduxHistoryRouter, {history: createReduxHistory(flecks.get('$flecks/redux.store'))}]
|
||||||
? [ReduxHistoryRouter, {history: createReduxHistory(flecks.get('$flecks/redux.store'))}]
|
: [HistoryRouter, {history}]
|
||||||
: [HistoryRouter, {history}]
|
),
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import {routerMiddleware, routerReducer} from '@flecks/react/router/context';
|
import {routerMiddleware, routerReducer} from '@flecks/react/router/context';
|
||||||
|
|
||||||
export * from 'react-router-dom';
|
export * from 'react-router-dom';
|
||||||
export * from 'redux-first-history';
|
export * from 'redux-first-history';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/redux.slices': () => ({
|
||||||
'@flecks/redux.slices': () => ({
|
router: routerReducer,
|
||||||
router: routerReducer,
|
}),
|
||||||
}),
|
'@flecks/redux.store': (options) => {
|
||||||
'@flecks/redux.store': (options) => {
|
options.middleware.push(routerMiddleware);
|
||||||
options.middleware.push(routerMiddleware);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
import {StaticRouter} from 'react-router-dom/server';
|
import {StaticRouter} from 'react-router-dom/server';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/react.providers': (req, flecks) => (
|
||||||
'@flecks/react.providers': (req, flecks) => (
|
flecks.get('@flecks/react.ssr') ? [StaticRouter, {location: req.url}] : []
|
||||||
flecks.get('@flecks/react.ssr') ? [StaticRouter, {location: req.url}] : []
|
),
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
import {augmentBuild} from '@flecks/web/server';
|
import {augmentBuild} from '@flecks/web/server';
|
||||||
|
|
||||||
import ssr from './ssr';
|
import ssr from './ssr';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.build': (target, config, flecks) => {
|
||||||
'@flecks/core.build': (target, config, flecks) => {
|
// Resolution.
|
||||||
// Resolution.
|
config.use.push(({config}) => {
|
||||||
config.use.push(({config}) => {
|
config.resolve.alias
|
||||||
config.resolve.alias
|
.set('react-native', 'react-native-web');
|
||||||
.set('react-native', 'react-native-web');
|
config.resolve.extensions
|
||||||
config.resolve.extensions
|
.prepend('.web.js')
|
||||||
.prepend('.web.js')
|
.prepend('.web.jsx');
|
||||||
.prepend('.web.jsx');
|
});
|
||||||
});
|
// Augment the build on behalf of a missing `@flecks/web`.
|
||||||
// Augment the build on behalf of a missing `@flecks/web`.
|
if (!flecks.fleck('@flecks/web/server')) {
|
||||||
if (!flecks.fleck('@flecks/web/server')) {
|
flecks.registerBuildConfig('postcss.config.js', {fleck: '@flecks/web/server'});
|
||||||
flecks.registerBuildConfig('postcss.config.js', {fleck: '@flecks/web/server'});
|
flecks.registerResolver('@flecks/web');
|
||||||
flecks.registerResolver('@flecks/web');
|
augmentBuild(target, config, flecks);
|
||||||
augmentBuild(target, config, flecks);
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
'@flecks/web/server.stream.html': (stream, req, flecks) => (
|
|
||||||
flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
'@flecks/web/server.stream.html': (stream, req, flecks) => (
|
||||||
|
flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import containers from './containers';
|
import containers from './containers';
|
||||||
import createClient from './create-client';
|
import createClient from './create-client';
|
||||||
|
|
||||||
|
@ -27,21 +25,19 @@ const safeKeys = async (client, pattern, caret) => {
|
||||||
|
|
||||||
export const keys = (client, pattern) => safeKeys(client, pattern, 0);
|
export const keys = (client, pattern) => safeKeys(client, pattern, 0);
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* Redis server host.
|
||||||
* Redis server host.
|
*/
|
||||||
*/
|
host: 'localhost',
|
||||||
host: 'localhost',
|
/**
|
||||||
/**
|
* Redis server port.
|
||||||
* Redis server port.
|
*/
|
||||||
*/
|
port: 6379,
|
||||||
port: 6379,
|
}),
|
||||||
}),
|
'@flecks/docker.containers': containers,
|
||||||
'@flecks/docker.containers': containers,
|
'@flecks/repl.context': (flecks) => ({
|
||||||
'@flecks/repl.context': (flecks) => ({
|
redisClient: createClient(flecks),
|
||||||
redisClient: createClient(flecks),
|
}),
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {D, Hooks} from '@flecks/core';
|
import {D} from '@flecks/core';
|
||||||
import redisAdapter from '@socket.io/redis-adapter';
|
import redisAdapter from '@socket.io/redis-adapter';
|
||||||
import ConnectRedis from 'connect-redis';
|
import ConnectRedis from 'connect-redis';
|
||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
|
@ -10,23 +10,21 @@ const debugSilly = debug.extend('silly');
|
||||||
|
|
||||||
const RedisStore = ConnectRedis(session);
|
const RedisStore = ConnectRedis(session);
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/user.session': async (flecks) => {
|
||||||
'@flecks/user.session': async (flecks) => {
|
const client = createClient(flecks, {legacyMode: true});
|
||||||
const client = createClient(flecks, {legacyMode: true});
|
await client.connect();
|
||||||
await client.connect();
|
return {
|
||||||
return {
|
store: new RedisStore({client}),
|
||||||
store: new RedisStore({client}),
|
};
|
||||||
};
|
},
|
||||||
},
|
'@flecks/socket.server': async (flecks) => {
|
||||||
'@flecks/socket.server': async (flecks) => {
|
const pubClient = createClient(flecks);
|
||||||
const pubClient = createClient(flecks);
|
const subClient = createClient(flecks);
|
||||||
const subClient = createClient(flecks);
|
await Promise.all([pubClient.connect(), subClient.connect()]);
|
||||||
await Promise.all([pubClient.connect(), subClient.connect()]);
|
debugSilly('creating adapter');
|
||||||
debugSilly('creating adapter');
|
return {
|
||||||
return {
|
adapter: redisAdapter(pubClient, subClient),
|
||||||
adapter: redisAdapter(pubClient, subClient),
|
};
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,45 +1,41 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
export default {
|
* Define side-effects to run against Redux actions.
|
||||||
[Hooks]: {
|
*/
|
||||||
/**
|
'@flecks/redux.effects': () => ({
|
||||||
* Define side-effects to run against Redux actions.
|
someActionName: (store, action) => {
|
||||||
*/
|
// Runs when `someActionName` actions are dispatched.
|
||||||
'@flecks/redux.effects': () => ({
|
|
||||||
someActionName: (store, action) => {
|
|
||||||
// Runs when `someActionName` actions are dispatched.
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* Define root-level reducers for the Redux store.
|
|
||||||
*/
|
|
||||||
'@flecks/redux.reducers': () => {
|
|
||||||
return (state, action) => {
|
|
||||||
// Whatever you'd like.
|
|
||||||
return state;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Define Redux slices.
|
|
||||||
*
|
|
||||||
* See: https://redux-toolkit.js.org/api/createSlice
|
|
||||||
*/
|
|
||||||
'@flecks/redux.slices': () => {
|
|
||||||
const something = createSlice(
|
|
||||||
// ...
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
something: something.reducer,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Modify Redux store configuration.
|
|
||||||
* @param {Object} options A mutable object with keys for enhancers and middleware.
|
|
||||||
*/
|
|
||||||
'@flecks/redux.store': (options) => {
|
|
||||||
options.enhancers.splice(someIndex, 1);
|
|
||||||
options.middleware.push(mySpecialMiddleware);
|
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Define root-level reducers for the Redux store.
|
||||||
|
*/
|
||||||
|
'@flecks/redux.reducers': () => {
|
||||||
|
return (state, action) => {
|
||||||
|
// Whatever you'd like.
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Define Redux slices.
|
||||||
|
*
|
||||||
|
* See: https://redux-toolkit.js.org/api/createSlice
|
||||||
|
*/
|
||||||
|
'@flecks/redux.slices': () => {
|
||||||
|
const something = createSlice(
|
||||||
|
// ...
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
something: something.reducer,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Modify Redux store configuration.
|
||||||
|
* @param {Object} options A mutable object with keys for enhancers and middleware.
|
||||||
|
*/
|
||||||
|
'@flecks/redux.store': (options) => {
|
||||||
|
options.enhancers.splice(someIndex, 1);
|
||||||
|
options.middleware.push(mySpecialMiddleware);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
import {ensureUniqueReduction, Flecks, Hooks} from '@flecks/core';
|
import {Flecks} from '@flecks/core';
|
||||||
import {Provider} from 'react-redux';
|
import {Provider} from 'react-redux';
|
||||||
|
|
||||||
import configureStore, {createReducer} from '../store';
|
import configureStore, {createReducer} from '../store';
|
||||||
import localStorageEnhancer from './local-storage';
|
import localStorageEnhancer from './local-storage';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/react.providers': async (req, flecks) => {
|
||||||
'@flecks/react.providers': async (req, flecks) => {
|
const slices = await flecks.invokeMergeUnique('@flecks/redux.slices');
|
||||||
const slices = await ensureUniqueReduction(flecks, '@flecks/redux.slices');
|
const reducer = createReducer(flecks, slices);
|
||||||
const reducer = createReducer(flecks, slices);
|
// Hydrate from server.
|
||||||
// Hydrate from server.
|
const {preloadedState} = flecks.get('@flecks/redux/client');
|
||||||
const {preloadedState} = flecks.get('@flecks/redux/client');
|
const store = await configureStore(flecks, reducer, {preloadedState});
|
||||||
const store = await configureStore(flecks, reducer, {preloadedState});
|
flecks.set('$flecks/redux.store', store);
|
||||||
flecks.set('$flecks/redux.store', store);
|
return [Provider, {store}];
|
||||||
return [Provider, {store}];
|
|
||||||
},
|
|
||||||
'@flecks/redux.store': ({enhancers}) => {
|
|
||||||
// Hydrate from and subscribe to localStorage.
|
|
||||||
enhancers.push(localStorageEnhancer);
|
|
||||||
},
|
|
||||||
'@flecks/socket.packets.decorate': (
|
|
||||||
Flecks.decorate(require.context('./packets/decorators', false, /\.js$/))
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
'@flecks/redux.store': ({enhancers}) => {
|
||||||
|
// Hydrate from and subscribe to localStorage.
|
||||||
|
enhancers.push(localStorageEnhancer);
|
||||||
|
},
|
||||||
|
'@flecks/socket.packets.decorate': (
|
||||||
|
Flecks.decorate(require.context('./packets/decorators', false, /\.js$/))
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import {Flecks, Hooks} from '@flecks/core';
|
import {Flecks} from '@flecks/core';
|
||||||
|
|
||||||
export * from '@reduxjs/toolkit';
|
export * from '@reduxjs/toolkit';
|
||||||
export * from 'react-redux';
|
export * from 'react-redux';
|
||||||
|
|
||||||
export * from './actions';
|
export * from './actions';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)),
|
||||||
'@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {D, ensureUniqueReduction, Hooks} from '@flecks/core';
|
import {D} from '@flecks/core';
|
||||||
import {Provider} from 'react-redux';
|
import {Provider} from 'react-redux';
|
||||||
|
|
||||||
import {hydrateServer} from './actions';
|
import {hydrateServer} from './actions';
|
||||||
|
@ -8,29 +8,27 @@ import configureStore from './store';
|
||||||
const debug = D('@flecks/redux/server');
|
const debug = D('@flecks/redux/server');
|
||||||
const debugSilly = debug.extend('silly');
|
const debugSilly = debug.extend('silly');
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/web/server.request.route': (flecks) => async (req, res, next) => {
|
||||||
'@flecks/web/server.request.route': (flecks) => async (req, res, next) => {
|
const slices = await flecks.invokeMergeUnique('@flecks/redux.slices');
|
||||||
const slices = await ensureUniqueReduction(flecks, '@flecks/redux.slices');
|
const reducer = createReducer(flecks, slices);
|
||||||
const reducer = createReducer(flecks, slices);
|
// Let the slices have a(n async) chance to hydrate with server data.
|
||||||
// Let the slices have a(n async) chance to hydrate with server data.
|
await Promise.all(
|
||||||
await Promise.all(
|
Object.values(slices).map(({hydrateServer}) => hydrateServer?.(req, flecks)),
|
||||||
Object.values(slices).map(({hydrateServer}) => hydrateServer?.(req, flecks)),
|
);
|
||||||
);
|
const preloadedState = reducer(undefined, hydrateServer({flecks, req}));
|
||||||
const preloadedState = reducer(undefined, hydrateServer({flecks, req}));
|
debugSilly(
|
||||||
debugSilly(
|
'creating redux store with slices(%O) and state(%O)',
|
||||||
'creating redux store with slices(%O) and state(%O)',
|
Object.keys(slices),
|
||||||
Object.keys(slices),
|
preloadedState,
|
||||||
preloadedState,
|
);
|
||||||
);
|
req.redux = await configureStore(flecks, reducer, {preloadedState});
|
||||||
req.redux = await configureStore(flecks, reducer, {preloadedState});
|
next();
|
||||||
next();
|
|
||||||
},
|
|
||||||
'@flecks/web.config': async (req) => ({
|
|
||||||
'@flecks/redux/client': {
|
|
||||||
preloadedState: req.redux.getState(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'@flecks/react.providers': (req) => [Provider, {store: req.redux}],
|
|
||||||
},
|
},
|
||||||
|
'@flecks/web.config': async (req) => ({
|
||||||
|
'@flecks/redux/client': {
|
||||||
|
preloadedState: req.redux.getState(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'@flecks/react.providers': (req) => [Provider, {store: req.redux}],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,30 +1,26 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
export default {
|
* Define REPL commands.
|
||||||
[Hooks]: {
|
*
|
||||||
/**
|
* Note: commands will be prefixed with a period in the Node REPL.
|
||||||
* Define REPL commands.
|
*/
|
||||||
*
|
'@flecks/repl.commands': () => ({
|
||||||
* Note: commands will be prefixed with a period in the Node REPL.
|
someCommand: (...args) => {
|
||||||
*/
|
// args are passed from the Node REPL. So, you could invoke it like:
|
||||||
'@flecks/repl.commands': () => ({
|
// .someCommand foo bar
|
||||||
someCommand: (...args) => {
|
// and `args` would be `['foo', 'bar']`.
|
||||||
// args are passed from the Node REPL. So, you could invoke it like:
|
|
||||||
// .someCommand foo bar
|
|
||||||
// and `args` would be `['foo', 'bar']`.
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* Provide global context to the REPL.
|
|
||||||
*/
|
|
||||||
'@flecks/repl.context': () => {
|
|
||||||
// Now you'd be able to do like:
|
|
||||||
// `node> someValue;`
|
|
||||||
// and the REPL would evaluate it to `'foobar'`.
|
|
||||||
return {
|
|
||||||
someValue: 'foobar',
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Provide global context to the REPL.
|
||||||
|
*/
|
||||||
|
'@flecks/repl.context': () => {
|
||||||
|
// Now you'd be able to do like:
|
||||||
|
// `node> someValue;`
|
||||||
|
// and the REPL would evaluate it to `'foobar'`.
|
||||||
|
return {
|
||||||
|
someValue: 'foobar',
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import commands from './commands';
|
import commands from './commands';
|
||||||
import {createReplServer} from './repl';
|
import {createReplServer} from './repl';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.commands': commands,
|
||||||
'@flecks/core.commands': commands,
|
'@flecks/server.up': (flecks) => createReplServer(flecks),
|
||||||
'@flecks/server.up': (flecks) => createReplServer(flecks),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
export default {
|
* Define sequential actions to run when the server comes up.
|
||||||
[Hooks]: {
|
*/
|
||||||
/**
|
'@flecks/server.up': async () => {
|
||||||
* Define sequential actions to run when the server comes up.
|
await youCanDoAsyncThingsHere();
|
||||||
*/
|
|
||||||
'@flecks/server.up': async () => {
|
|
||||||
await youCanDoAsyncThingsHere();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -33,7 +33,8 @@ const {version} = require('../package.json');
|
||||||
rcs,
|
rcs,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await global.flecks.up('@flecks/server.up');
|
await Promise.all(global.flecks.invokeFlat('@flecks/core.starting'));
|
||||||
|
await global.flecks.invokeSequentialAsync('@flecks/server.up');
|
||||||
debug('up!');
|
debug('up!');
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|
|
@ -1,28 +1,24 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
'@flecks/core.config': () => ({
|
||||||
export default {
|
/**
|
||||||
[Hooks]: {
|
* Whether HMR is enabled.
|
||||||
'@flecks/core.config': () => ({
|
*/
|
||||||
/**
|
hot: false,
|
||||||
* Whether HMR is enabled.
|
/**
|
||||||
*/
|
* Arguments to pass along to node. See: https://nodejs.org/api/cli.html
|
||||||
hot: false,
|
*/
|
||||||
/**
|
nodeArgs: [],
|
||||||
* Arguments to pass along to node. See: https://nodejs.org/api/cli.html
|
/**
|
||||||
*/
|
* Whether to start the server after building.
|
||||||
nodeArgs: [],
|
*/
|
||||||
/**
|
start: true,
|
||||||
* Whether to start the server after building.
|
/**
|
||||||
*/
|
* Webpack stats configuration when building server target.
|
||||||
start: true,
|
*/
|
||||||
/**
|
stats: {
|
||||||
* Webpack stats configuration when building server target.
|
chunks: false,
|
||||||
*/
|
colors: true,
|
||||||
stats: {
|
modules: false,
|
||||||
chunks: false,
|
},
|
||||||
colors: true,
|
}),
|
||||||
modules: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
'@flecks/core.targets': () => ['server'],
|
||||||
export default {
|
|
||||||
[Hooks]: {
|
|
||||||
'@flecks/core.targets': () => ['server'],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,65 +1,61 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
export default {
|
* Modify Socket.io client configuration.
|
||||||
[Hooks]: {
|
*
|
||||||
/**
|
* See: https://socket.io/docs/v4/client-options/
|
||||||
* Modify Socket.io client configuration.
|
*/
|
||||||
*
|
'@flecks/socket.client': () => ({
|
||||||
* See: https://socket.io/docs/v4/client-options/
|
timeout: Infinity,
|
||||||
*/
|
}),
|
||||||
'@flecks/socket.client': () => ({
|
/**
|
||||||
timeout: Infinity,
|
* Define server-side intercom channels.
|
||||||
}),
|
*/
|
||||||
/**
|
'@flecks/socket.intercom': (req) => ({
|
||||||
* Define server-side intercom channels.
|
// This would have been called like:
|
||||||
*/
|
// `const result = await req.intercom('someChannel', payload)`.
|
||||||
'@flecks/socket.intercom': (req) => ({
|
// `result` will be an `n`-length array, where `n` is the number of server instances. Each
|
||||||
// This would have been called like:
|
// element in the array will be the result of `someServiceSpecificInformation()` running
|
||||||
// `const result = await req.intercom('someChannel', payload)`.
|
// against that server instance.
|
||||||
// `result` will be an `n`-length array, where `n` is the number of server instances. Each
|
someChannel: async (payload, server) => {
|
||||||
// element in the array will be the result of `someServiceSpecificInformation()` running
|
return someServiceSpecificInformation();
|
||||||
// against that server instance.
|
|
||||||
someChannel: async (payload, server) => {
|
|
||||||
return someServiceSpecificInformation();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* Define socket packets.
|
|
||||||
*
|
|
||||||
* In the example below, your fleck would have a `packets` subdirectory, and each
|
|
||||||
* decorator would be defined in its own file.
|
|
||||||
* See: https://github.com/cha0s/flecks/tree/master/packages/redux/src/packets
|
|
||||||
*
|
|
||||||
* See: https://github.com/cha0s/flecks/tree/master/packages/socket/src/packet/packet.js
|
|
||||||
* See: https://github.com/cha0s/flecks/tree/master/packages/socket/src/packet/redirect.js
|
|
||||||
*/
|
|
||||||
'@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)),
|
|
||||||
/**
|
|
||||||
* Decorate database models.
|
|
||||||
*
|
|
||||||
* In the example below, your fleck would have a `packets/decorators` subdirectory, and each
|
|
||||||
* decorator would be defined in its own file.
|
|
||||||
* @param {constructor} Packet The packet to decorate.
|
|
||||||
*/
|
|
||||||
'@flecks/socket.packets.decorate': (
|
|
||||||
Flecks.decorate(require.context('./packets/decorators', false, /\.js$/))
|
|
||||||
),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modify Socket.io server configuration.
|
|
||||||
*
|
|
||||||
* See: https://socket.io/docs/v4/server-options/
|
|
||||||
*/
|
|
||||||
'@flecks/socket.server': () => ({
|
|
||||||
pingTimeout: Infinity,
|
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* Define middleware to run when a socket connection is established.
|
|
||||||
*/
|
|
||||||
'@flecks/socket/server.request.socket': () => (socket, next) => {
|
|
||||||
// Express-style route middleware...
|
|
||||||
next();
|
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Define socket packets.
|
||||||
|
*
|
||||||
|
* In the example below, your fleck would have a `packets` subdirectory, and each
|
||||||
|
* decorator would be defined in its own file.
|
||||||
|
* See: https://github.com/cha0s/flecks/tree/master/packages/redux/src/packets
|
||||||
|
*
|
||||||
|
* See: https://github.com/cha0s/flecks/tree/master/packages/socket/src/packet/packet.js
|
||||||
|
* See: https://github.com/cha0s/flecks/tree/master/packages/socket/src/packet/redirect.js
|
||||||
|
*/
|
||||||
|
'@flecks/socket.packets': Flecks.provide(require.context('./packets', false, /\.js$/)),
|
||||||
|
/**
|
||||||
|
* Decorate database models.
|
||||||
|
*
|
||||||
|
* In the example below, your fleck would have a `packets/decorators` subdirectory, and each
|
||||||
|
* decorator would be defined in its own file.
|
||||||
|
* @param {constructor} Packet The packet to decorate.
|
||||||
|
*/
|
||||||
|
'@flecks/socket.packets.decorate': (
|
||||||
|
Flecks.decorate(require.context('./packets/decorators', false, /\.js$/))
|
||||||
|
),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify Socket.io server configuration.
|
||||||
|
*
|
||||||
|
* See: https://socket.io/docs/v4/server-options/
|
||||||
|
*/
|
||||||
|
'@flecks/socket.server': () => ({
|
||||||
|
pingTimeout: Infinity,
|
||||||
|
}),
|
||||||
|
/**
|
||||||
|
* Define middleware to run when a socket connection is established.
|
||||||
|
*/
|
||||||
|
'@flecks/socket/server.request.socket': () => (socket, next) => {
|
||||||
|
// Express-style route middleware...
|
||||||
|
next();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import SocketClient from './socket';
|
import SocketClient from './socket';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/web/client.up': (flecks) => {
|
||||||
'@flecks/web/client.up': (flecks) => {
|
const socket = new SocketClient(flecks);
|
||||||
const socket = new SocketClient(flecks);
|
flecks.set('$flecks/socket.socket', socket);
|
||||||
flecks.set('$flecks/socket.socket', socket);
|
socket.connect();
|
||||||
socket.connect();
|
socket.listen();
|
||||||
socket.listen();
|
|
||||||
},
|
|
||||||
'@flecks/socket.client': ({config: {'@flecks/core': {id}}}) => ({
|
|
||||||
cors: {
|
|
||||||
origin: false,
|
|
||||||
},
|
|
||||||
path: `/${id}/socket.io`,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
'@flecks/socket.client': ({config: {'@flecks/core': {id}}}) => ({
|
||||||
|
cors: {
|
||||||
|
origin: false,
|
||||||
|
},
|
||||||
|
path: `/${id}/socket.io`,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import badPacketsCheck from './packet/bad-packets-check';
|
import badPacketsCheck from './packet/bad-packets-check';
|
||||||
import Bundle from './packet/bundle';
|
import Bundle from './packet/bundle';
|
||||||
import Redirect from './packet/redirect';
|
import Redirect from './packet/redirect';
|
||||||
|
@ -9,28 +7,26 @@ export {default as normalize} from './normalize';
|
||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
export {default as Packet, Packer, ValidationError} from './packet';
|
export {default as Packet, Packer, ValidationError} from './packet';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.starting': (flecks) => {
|
||||||
'@flecks/core.starting': (flecks) => {
|
flecks.set('$flecks/socket.packets', flecks.gather(
|
||||||
flecks.set('$flecks/socket.packets', flecks.gather(
|
'@flecks/socket.packets',
|
||||||
'@flecks/socket.packets',
|
{check: badPacketsCheck},
|
||||||
{check: badPacketsCheck},
|
));
|
||||||
));
|
|
||||||
},
|
|
||||||
'@flecks/web.config': async (
|
|
||||||
req,
|
|
||||||
{config: {'@flecks/socket': {'packets.decorate': decorators = ['...']}}},
|
|
||||||
) => ({
|
|
||||||
'@flecks/socket': {
|
|
||||||
'packets.decorate': decorators.filter(
|
|
||||||
(decorator) => 'server' !== decorator.split('/').pop(),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'@flecks/socket.packets': (flecks) => ({
|
|
||||||
Bundle: Bundle(flecks),
|
|
||||||
Redirect,
|
|
||||||
Refresh,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
'@flecks/web.config': async (
|
||||||
|
req,
|
||||||
|
{config: {'@flecks/socket': {'packets.decorate': decorators = ['...']}}},
|
||||||
|
) => ({
|
||||||
|
'@flecks/socket': {
|
||||||
|
'packets.decorate': decorators.filter(
|
||||||
|
(decorator) => 'server' !== decorator.split('/').pop(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'@flecks/socket.packets': (flecks) => ({
|
||||||
|
Bundle: Bundle(flecks),
|
||||||
|
Redirect,
|
||||||
|
Refresh,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,25 +1,21 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import createIntercom from './create-intercom';
|
import createIntercom from './create-intercom';
|
||||||
import Sockets from './sockets';
|
import Sockets from './sockets';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/web/server.request.socket': ({config: {'$flecks/socket.sockets': sockets}}) => (req, res, next) => {
|
||||||
'@flecks/web/server.request.socket': ({config: {'$flecks/socket.sockets': sockets}}) => (req, res, next) => {
|
req.intercom = createIntercom(sockets, 'web');
|
||||||
req.intercom = createIntercom(sockets, 'web');
|
next();
|
||||||
next();
|
|
||||||
},
|
|
||||||
'@flecks/web/server.up': async (httpServer, flecks) => {
|
|
||||||
const sockets = new Sockets(httpServer, flecks);
|
|
||||||
await sockets.connect();
|
|
||||||
flecks.set('$flecks/socket.sockets', sockets);
|
|
||||||
},
|
|
||||||
'@flecks/repl.context': (flecks) => ({
|
|
||||||
Packets: flecks.get('$flecks/socket.packets'),
|
|
||||||
sockets: flecks.get('$flecks/socket.sockets'),
|
|
||||||
}),
|
|
||||||
'@flecks/socket.server': ({config: {'@flecks/core': {id}}}) => ({
|
|
||||||
path: `/${id}/socket.io`,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
'@flecks/web/server.up': async (httpServer, flecks) => {
|
||||||
|
const sockets = new Sockets(httpServer, flecks);
|
||||||
|
await sockets.connect();
|
||||||
|
flecks.set('$flecks/socket.sockets', sockets);
|
||||||
|
},
|
||||||
|
'@flecks/repl.context': (flecks) => ({
|
||||||
|
Packets: flecks.get('$flecks/socket.packets'),
|
||||||
|
sockets: flecks.get('$flecks/socket.sockets'),
|
||||||
|
}),
|
||||||
|
'@flecks/socket.server': ({config: {'@flecks/core': {id}}}) => ({
|
||||||
|
path: `/${id}/socket.io`,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
export default {
|
* Modify express-session configuration.
|
||||||
[Hooks]: {
|
*
|
||||||
/**
|
* See: https://www.npmjs.com/package/express-session
|
||||||
* Modify express-session configuration.
|
*/
|
||||||
*
|
'@flecks/user.session': () => ({
|
||||||
* See: https://www.npmjs.com/package/express-session
|
saveUninitialized: true,
|
||||||
*/
|
}),
|
||||||
'@flecks/user.session': () => ({
|
|
||||||
saveUninitialized: true,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
import {Hooks} from '@flecks/core';
|
|
||||||
|
|
||||||
import {Logout} from './packets';
|
import {Logout} from './packets';
|
||||||
|
|
||||||
import {user, users} from './state';
|
import {user, users} from './state';
|
||||||
|
|
||||||
export * from './state';
|
export * from './state';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/redux.slices': () => ({
|
||||||
'@flecks/redux.slices': () => ({
|
user,
|
||||||
user,
|
users,
|
||||||
users,
|
}),
|
||||||
}),
|
'@flecks/socket.packets': (flecks) => ({
|
||||||
'@flecks/socket.packets': (flecks) => ({
|
Logout: Logout(flecks),
|
||||||
Logout: Logout(flecks),
|
}),
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,70 +1,68 @@
|
||||||
import {randomBytes} from 'crypto';
|
import {randomBytes} from 'crypto';
|
||||||
|
|
||||||
import {Flecks, Hooks} from '@flecks/core';
|
import {Flecks} from '@flecks/core';
|
||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import LocalStrategy from 'passport-local';
|
import LocalStrategy from 'passport-local';
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* Path to redirect to after failed login.
|
||||||
* Path to redirect to after failed login.
|
*/
|
||||||
*/
|
failureRedirect: '/',
|
||||||
failureRedirect: '/',
|
/**
|
||||||
/**
|
* Path to redirect to after successful login.
|
||||||
* Path to redirect to after successful login.
|
*/
|
||||||
*/
|
successRedirect: '/',
|
||||||
successRedirect: '/',
|
}),
|
||||||
}),
|
'@flecks/db/server.models.decorate': (
|
||||||
'@flecks/db/server.models.decorate': (
|
Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
|
||||||
Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
|
),
|
||||||
),
|
'@flecks/web.routes': (flecks) => {
|
||||||
'@flecks/web.routes': (flecks) => {
|
const {failureRedirect, successRedirect} = flecks.get('@flecks/user/local/server');
|
||||||
const {failureRedirect, successRedirect} = flecks.get('@flecks/user/local/server');
|
return [
|
||||||
return [
|
{
|
||||||
{
|
method: 'post',
|
||||||
method: 'post',
|
path: '/auth/local',
|
||||||
path: '/auth/local',
|
middleware: passport.authenticate('local', {failureRedirect, successRedirect}),
|
||||||
middleware: passport.authenticate('local', {failureRedirect, successRedirect}),
|
},
|
||||||
},
|
];
|
||||||
];
|
},
|
||||||
},
|
'@flecks/repl.commands': (flecks) => {
|
||||||
'@flecks/repl.commands': (flecks) => {
|
const {User} = flecks.get('$flecks/db.models');
|
||||||
const {User} = flecks.get('$flecks/db.models');
|
return {
|
||||||
return {
|
createUser: async (spec) => {
|
||||||
createUser: async (spec) => {
|
const [email, maybePassword] = spec.split(' ', 2);
|
||||||
const [email, maybePassword] = spec.split(' ', 2);
|
const password = maybePassword || randomBytes(8).toString('hex');
|
||||||
const password = maybePassword || randomBytes(8).toString('hex');
|
const user = User.build({email});
|
||||||
const user = User.build({email});
|
await user.addHashedPassword(password);
|
||||||
|
await user.save();
|
||||||
|
},
|
||||||
|
resetPassword: async (email) => {
|
||||||
|
const password = randomBytes(8).toString('hex');
|
||||||
|
const user = await User.findOne({where: {email}});
|
||||||
|
if (user) {
|
||||||
await user.addHashedPassword(password);
|
await user.addHashedPassword(password);
|
||||||
await user.save();
|
await user.save();
|
||||||
},
|
return `\nNew password: ${password}\n\n`;
|
||||||
resetPassword: async (email) => {
|
}
|
||||||
const password = randomBytes(8).toString('hex');
|
return 'User not found.\n';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'@flecks/server.up': (flecks) => {
|
||||||
|
passport.use(new LocalStrategy(
|
||||||
|
{usernameField: 'email'},
|
||||||
|
async (email, password, fn) => {
|
||||||
|
const {User} = flecks.get('$flecks/db.models');
|
||||||
|
try {
|
||||||
const user = await User.findOne({where: {email}});
|
const user = await User.findOne({where: {email}});
|
||||||
if (user) {
|
fn(undefined, user && await user.validatePassword(password) && user);
|
||||||
await user.addHashedPassword(password);
|
}
|
||||||
await user.save();
|
catch (error) {
|
||||||
return `\nNew password: ${password}\n\n`;
|
fn(error);
|
||||||
}
|
}
|
||||||
return 'User not found.\n';
|
},
|
||||||
},
|
));
|
||||||
};
|
|
||||||
},
|
|
||||||
'@flecks/server.up': (flecks) => {
|
|
||||||
passport.use(new LocalStrategy(
|
|
||||||
{usernameField: 'email'},
|
|
||||||
async (email, password, fn) => {
|
|
||||||
const {User} = flecks.get('$flecks/db.models');
|
|
||||||
try {
|
|
||||||
const user = await User.findOne({where: {email}});
|
|
||||||
fn(undefined, user && await user.validatePassword(password) && user);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
fn(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
));
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,85 +1,83 @@
|
||||||
import {D, Flecks, Hooks} from '@flecks/core';
|
import {D, Flecks} from '@flecks/core';
|
||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import LogOps from 'passport/lib/http/request';
|
import LogOps from 'passport/lib/http/request';
|
||||||
|
|
||||||
const debug = D('@flecks/user/passport');
|
const debug = D('@flecks/user/passport');
|
||||||
const debugSilly = debug.extend('silly');
|
const debugSilly = debug.extend('silly');
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
|
||||||
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
|
'@flecks/web/server.request.route': (flecks) => (req, res, next) => {
|
||||||
'@flecks/web/server.request.route': (flecks) => (req, res, next) => {
|
debugSilly('@flecks/web/server.request.route: passport.initialize()');
|
||||||
debugSilly('@flecks/web/server.request.route: passport.initialize()');
|
passport.initialize()(req, res, () => {
|
||||||
passport.initialize()(req, res, () => {
|
debugSilly('@flecks/web/server.request.route: passport.session()');
|
||||||
debugSilly('@flecks/web/server.request.route: passport.session()');
|
passport.session()(req, res, () => {
|
||||||
passport.session()(req, res, () => {
|
if (!req.user) {
|
||||||
if (!req.user) {
|
const {User} = flecks.get('$flecks/db.models');
|
||||||
const {User} = flecks.get('$flecks/db.models');
|
req.user = new User();
|
||||||
req.user = new User();
|
req.user.id = 0;
|
||||||
req.user.id = 0;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
'@flecks/web.routes': () => [
|
|
||||||
{
|
|
||||||
method: 'get',
|
|
||||||
path: '/auth/logout',
|
|
||||||
middleware: (req, res) => {
|
|
||||||
req.logout();
|
|
||||||
res.redirect('/');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@flecks/server.up': (flecks) => {
|
|
||||||
passport.serializeUser((user, fn) => fn(null, user.id));
|
|
||||||
passport.deserializeUser(async (id, fn) => {
|
|
||||||
const {User} = flecks.get('$flecks/db.models');
|
|
||||||
try {
|
|
||||||
fn(undefined, await User.findByPk(id));
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
fn(error);
|
|
||||||
}
|
}
|
||||||
|
next();
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
'@flecks/socket.intercom': () => ({
|
},
|
||||||
'@flecks/user/users': async (sids, server) => {
|
'@flecks/web.routes': () => [
|
||||||
const sockets = await server.sockets();
|
{
|
||||||
return sids
|
method: 'get',
|
||||||
.filter((sid) => sockets.has(sid))
|
path: '/auth/logout',
|
||||||
.reduce(
|
middleware: (req, res) => {
|
||||||
(r, sid) => ({
|
req.logout();
|
||||||
...r,
|
res.redirect('/');
|
||||||
[sid]: sockets.get(sid).handshake.user.id,
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
'@flecks/socket/server.request.socket': (flecks) => (socket, next) => {
|
|
||||||
debugSilly('@flecks/socket/server.request.socket: passport.initialize()');
|
|
||||||
passport.initialize()(socket.handshake, undefined, () => {
|
|
||||||
debugSilly('@flecks/socket/server.request.socket: passport.session()');
|
|
||||||
passport.session()(socket.handshake, undefined, async () => {
|
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
if (!socket.handshake.user) {
|
|
||||||
const {User} = flecks.get('$flecks/db.models');
|
|
||||||
socket.handshake.user = new User();
|
|
||||||
socket.handshake.user.id = 0;
|
|
||||||
}
|
|
||||||
socket.handshake.login = LogOps.logIn;
|
|
||||||
socket.handshake.logIn = LogOps.logIn;
|
|
||||||
socket.handshake.logout = LogOps.logOut;
|
|
||||||
socket.handshake.logOut = LogOps.logOut;
|
|
||||||
socket.handshake.isAuthenticated = LogOps.isAuthenticated;
|
|
||||||
socket.handshake.isUnauthenticated = LogOps.isUnauthenticated;
|
|
||||||
/* eslint-enable no-param-reassign */
|
|
||||||
await socket.join(`/u/${socket.handshake.user.id}`);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
'@flecks/server.up': (flecks) => {
|
||||||
|
passport.serializeUser((user, fn) => fn(null, user.id));
|
||||||
|
passport.deserializeUser(async (id, fn) => {
|
||||||
|
const {User} = flecks.get('$flecks/db.models');
|
||||||
|
try {
|
||||||
|
fn(undefined, await User.findByPk(id));
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
fn(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'@flecks/socket.intercom': () => ({
|
||||||
|
'@flecks/user/users': async (sids, server) => {
|
||||||
|
const sockets = await server.sockets();
|
||||||
|
return sids
|
||||||
|
.filter((sid) => sockets.has(sid))
|
||||||
|
.reduce(
|
||||||
|
(r, sid) => ({
|
||||||
|
...r,
|
||||||
|
[sid]: sockets.get(sid).handshake.user.id,
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'@flecks/socket/server.request.socket': (flecks) => (socket, next) => {
|
||||||
|
debugSilly('@flecks/socket/server.request.socket: passport.initialize()');
|
||||||
|
passport.initialize()(socket.handshake, undefined, () => {
|
||||||
|
debugSilly('@flecks/socket/server.request.socket: passport.session()');
|
||||||
|
passport.session()(socket.handshake, undefined, async () => {
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
if (!socket.handshake.user) {
|
||||||
|
const {User} = flecks.get('$flecks/db.models');
|
||||||
|
socket.handshake.user = new User();
|
||||||
|
socket.handshake.user.id = 0;
|
||||||
|
}
|
||||||
|
socket.handshake.login = LogOps.logIn;
|
||||||
|
socket.handshake.logIn = LogOps.logIn;
|
||||||
|
socket.handshake.logout = LogOps.logOut;
|
||||||
|
socket.handshake.logOut = LogOps.logOut;
|
||||||
|
socket.handshake.isAuthenticated = LogOps.isAuthenticated;
|
||||||
|
socket.handshake.isUnauthenticated = LogOps.isUnauthenticated;
|
||||||
|
/* eslint-enable no-param-reassign */
|
||||||
|
await socket.join(`/u/${socket.handshake.user.id}`);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,59 +1,57 @@
|
||||||
import {D, Hooks} from '@flecks/core';
|
import {D} from '@flecks/core';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import expressSession from 'express-session';
|
import expressSession from 'express-session';
|
||||||
|
|
||||||
const debug = D('@flecks/user/session');
|
const debug = D('@flecks/user/session');
|
||||||
const debugSilly = debug.extend('silly');
|
const debugSilly = debug.extend('silly');
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.config': () => ({
|
||||||
'@flecks/core.config': () => ({
|
/**
|
||||||
/**
|
* Set the cookie secret for session encryption.
|
||||||
* Set the cookie secret for session encryption.
|
*
|
||||||
*
|
* See: http://expressjs.com/en/resources/middleware/cookie-parser.html
|
||||||
* See: http://expressjs.com/en/resources/middleware/cookie-parser.html
|
*/
|
||||||
*/
|
cookieSecret: (
|
||||||
cookieSecret: (
|
'Set the FLECKS_ENV_FLECKS_USER_SESSION_SERVER_cookieSecret environment variable!'
|
||||||
'Set the FLECKS_ENV_FLECKS_USER_SESSION_SERVER_cookieSecret environment variable!'
|
),
|
||||||
),
|
}),
|
||||||
}),
|
'@flecks/web/server.request.route': (flecks) => {
|
||||||
'@flecks/web/server.request.route': (flecks) => {
|
const urle = express.urlencoded({extended: true});
|
||||||
const urle = express.urlencoded({extended: true});
|
return (req, res, next) => {
|
||||||
return (req, res, next) => {
|
debugSilly('@flecks/web/server.request.route: express.urlencoded()');
|
||||||
debugSilly('@flecks/web/server.request.route: express.urlencoded()');
|
urle(req, res, (error) => {
|
||||||
urle(req, res, (error) => {
|
if (error) {
|
||||||
|
next(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugSilly('@flecks/web/server.request.route: session()');
|
||||||
|
flecks.get('$flecks/user.session')(req, res, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
next(error);
|
next(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debugSilly('@flecks/web/server.request.route: session()');
|
debugSilly('session ID: %s', req.session.id);
|
||||||
flecks.get('$flecks/user.session')(req, res, (error) => {
|
next();
|
||||||
if (error) {
|
|
||||||
next(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
debugSilly('session ID: %s', req.session.id);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
},
|
|
||||||
'@flecks/server.up': async (flecks) => {
|
|
||||||
flecks.set('$flecks/user.session', expressSession({
|
|
||||||
resave: false,
|
|
||||||
sameSite: true,
|
|
||||||
saveUninitialized: false,
|
|
||||||
secret: flecks.get('@flecks/user/session/server.cookieSecret'),
|
|
||||||
...await flecks.invokeMergeAsync('@flecks/user.session'),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
'@flecks/socket/server.request.socket': (flecks) => (socket, next) => {
|
|
||||||
debugSilly('@flecks/socket/server.request.socket: session()');
|
|
||||||
flecks.get('$flecks/user.session')(socket.handshake, {}, () => {
|
|
||||||
const id = socket.handshake.session?.id;
|
|
||||||
socket.join(id);
|
|
||||||
next();
|
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
|
},
|
||||||
|
'@flecks/server.up': async (flecks) => {
|
||||||
|
flecks.set('$flecks/user.session', expressSession({
|
||||||
|
resave: false,
|
||||||
|
sameSite: true,
|
||||||
|
saveUninitialized: false,
|
||||||
|
secret: flecks.get('@flecks/user/session/server.cookieSecret'),
|
||||||
|
...await flecks.invokeMergeAsync('@flecks/user.session'),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
'@flecks/socket/server.request.socket': (flecks) => (socket, next) => {
|
||||||
|
debugSilly('@flecks/socket/server.request.socket: session()');
|
||||||
|
flecks.get('$flecks/user.session')(socket.handshake, {}, () => {
|
||||||
|
const id = socket.handshake.session?.id;
|
||||||
|
socket.join(id);
|
||||||
|
next();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,62 +1,58 @@
|
||||||
import {Hooks} from '@flecks/core';
|
export const hooks = {
|
||||||
|
/**
|
||||||
export default {
|
* Define sequential actions to run when the client comes up.
|
||||||
[Hooks]: {
|
*/
|
||||||
/**
|
'@flecks/web/client.up': async () => {
|
||||||
* Define sequential actions to run when the client comes up.
|
await youCanDoAsyncThingsHere();
|
||||||
*/
|
},
|
||||||
'@flecks/web/client.up': async () => {
|
/**
|
||||||
await youCanDoAsyncThingsHere();
|
* Override flecks configuration sent to client flecks.
|
||||||
|
* @param {http.ClientRequest} req The HTTP request object.
|
||||||
|
*/
|
||||||
|
'@flecks/web.config': (req) => ({
|
||||||
|
someClientFleck: {
|
||||||
|
someConfig: req.someConfig,
|
||||||
},
|
},
|
||||||
/**
|
}),
|
||||||
* Override flecks configuration sent to client flecks.
|
/**
|
||||||
* @param {http.ClientRequest} req The HTTP request object.
|
* Define HTTP routes.
|
||||||
*/
|
*/
|
||||||
'@flecks/web.config': (req) => ({
|
'@flecks/web.routes': () => [
|
||||||
someClientFleck: {
|
{
|
||||||
someConfig: req.someConfig,
|
method: 'get',
|
||||||
|
path: '/some-path',
|
||||||
|
middleware: (req, res, next) => {
|
||||||
|
// Express-style route middleware...
|
||||||
|
next();
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* Define HTTP routes.
|
|
||||||
*/
|
|
||||||
'@flecks/web.routes': () => [
|
|
||||||
{
|
|
||||||
method: 'get',
|
|
||||||
path: '/some-path',
|
|
||||||
middleware: (req, res, next) => {
|
|
||||||
// Express-style route middleware...
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
/**
|
|
||||||
* Define middleware to run when a route is matched.
|
|
||||||
*/
|
|
||||||
'@flecks/web/server.request.route': () => (req, res, next) => {
|
|
||||||
// Express-style route middleware...
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Define middleware to run when an HTTP socket connection is established.
|
|
||||||
*/
|
|
||||||
'@flecks/web/server.request.socket': () => (req, res, next) => {
|
|
||||||
// Express-style route middleware...
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Define composition functions to run over the HTML stream prepared for the client.
|
|
||||||
* @param {stream.Readable} stream The HTML stream.
|
|
||||||
* @param {http.ClientRequest} req The HTTP request object.
|
|
||||||
*/
|
|
||||||
'@flecks/web/server.stream.html': (stream, req) => {
|
|
||||||
return stream.pipe(myTransformStream);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Define sequential actions to run when the HTTP server comes up.
|
|
||||||
*/
|
|
||||||
'@flecks/web/server.up': async () => {
|
|
||||||
await youCanDoAsyncThingsHere();
|
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
/**
|
||||||
|
* Define middleware to run when a route is matched.
|
||||||
|
*/
|
||||||
|
'@flecks/web/server.request.route': () => (req, res, next) => {
|
||||||
|
// Express-style route middleware...
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Define middleware to run when an HTTP socket connection is established.
|
||||||
|
*/
|
||||||
|
'@flecks/web/server.request.socket': () => (req, res, next) => {
|
||||||
|
// Express-style route middleware...
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Define composition functions to run over the HTML stream prepared for the client.
|
||||||
|
* @param {stream.Readable} stream The HTML stream.
|
||||||
|
* @param {http.ClientRequest} req The HTTP request object.
|
||||||
|
*/
|
||||||
|
'@flecks/web/server.stream.html': (stream, req) => {
|
||||||
|
return stream.pipe(myTransformStream);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Define sequential actions to run when the HTTP server comes up.
|
||||||
|
*/
|
||||||
|
'@flecks/web/server.up': async () => {
|
||||||
|
await youCanDoAsyncThingsHere();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {D, Flecks} from '@flecks/core';
|
import {D, Flecks} from '@flecks/core';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
|
||||||
const {version} = require('@flecks/web/package.json');
|
const {version} = require('@flecks/web/package.json');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -56,7 +56,8 @@ const {version} = require('@flecks/web/package.json');
|
||||||
const flecks = new Flecks(runtime);
|
const flecks = new Flecks(runtime);
|
||||||
window.flecks = flecks;
|
window.flecks = flecks;
|
||||||
try {
|
try {
|
||||||
await flecks.up('@flecks/web/client.up');
|
await Promise.all(flecks.invokeFlat('@flecks/core.starting'));
|
||||||
|
await flecks.invokeSequentialAsync('@flecks/web/client.up');
|
||||||
window.document.querySelector('#root').style.display = 'block';
|
window.document.querySelector('#root').style.display = 'block';
|
||||||
debug('up!');
|
debug('up!');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {stat, unlink} from 'fs/promises';
|
import {stat, unlink} from 'fs/promises';
|
||||||
import {join} from 'path';
|
import {join} from 'path';
|
||||||
|
|
||||||
import {D, Hooks} from '@flecks/core';
|
import {D} from '@flecks/core';
|
||||||
import {Flecks, spawnWith} from '@flecks/core/server';
|
import {Flecks, spawnWith} from '@flecks/core/server';
|
||||||
|
|
||||||
import augmentBuild from './augment-build';
|
import augmentBuild from './augment-build';
|
||||||
|
@ -16,199 +16,197 @@ const debug = D('@flecks/web/server');
|
||||||
|
|
||||||
export {augmentBuild};
|
export {augmentBuild};
|
||||||
|
|
||||||
export default {
|
export const hooks = {
|
||||||
[Hooks]: {
|
'@flecks/core.build': augmentBuild,
|
||||||
'@flecks/core.build': augmentBuild,
|
'@flecks/core.build.alter': async (neutrinoConfigs, flecks) => {
|
||||||
'@flecks/core.build.alter': async (neutrinoConfigs, flecks) => {
|
// Don't build if there's a fleck target.
|
||||||
// Don't build if there's a fleck target.
|
if (neutrinoConfigs.fleck && !flecks.get('@flecks/web/server.forceBuildWithFleck')) {
|
||||||
if (neutrinoConfigs.fleck && !flecks.get('@flecks/web/server.forceBuildWithFleck')) {
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
delete neutrinoConfigs.web;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only build vendor in dev.
|
||||||
|
if (neutrinoConfigs['web-vendor']) {
|
||||||
|
if (process.argv.find((arg) => 'production' === arg)) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
delete neutrinoConfigs.web;
|
delete neutrinoConfigs['web-vendor'];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Only build vendor in dev.
|
// Only build if something actually changed.
|
||||||
if (neutrinoConfigs['web-vendor']) {
|
const dll = flecks.get('@flecks/web/server.dll');
|
||||||
if (process.argv.find((arg) => 'production' === arg)) {
|
if (dll.length > 0) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
const manifest = join(
|
||||||
delete neutrinoConfigs['web-vendor'];
|
FLECKS_CORE_ROOT,
|
||||||
|
'node_modules',
|
||||||
|
'.cache',
|
||||||
|
'flecks',
|
||||||
|
'web-vendor.manifest.json',
|
||||||
|
);
|
||||||
|
let timestamp = 0;
|
||||||
|
try {
|
||||||
|
const stats = await stat(manifest);
|
||||||
|
timestamp = stats.mtime;
|
||||||
}
|
}
|
||||||
// Only build if something actually changed.
|
// eslint-disable-next-line no-empty
|
||||||
const dll = flecks.get('@flecks/web/server.dll');
|
catch (error) {}
|
||||||
if (dll.length > 0) {
|
let latest = 0;
|
||||||
const manifest = join(
|
for (let i = 0; i < dll.length; ++i) {
|
||||||
FLECKS_CORE_ROOT,
|
const path = dll[i];
|
||||||
'node_modules',
|
|
||||||
'.cache',
|
|
||||||
'flecks',
|
|
||||||
'web-vendor.manifest.json',
|
|
||||||
);
|
|
||||||
let timestamp = 0;
|
|
||||||
try {
|
try {
|
||||||
const stats = await stat(manifest);
|
// eslint-disable-next-line no-await-in-loop
|
||||||
timestamp = stats.mtime;
|
const stats = await stat(join(FLECKS_CORE_ROOT, 'node_modules', path));
|
||||||
|
if (stats.mtime > latest) {
|
||||||
|
latest = stats.mtime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-empty
|
// eslint-disable-next-line no-empty
|
||||||
catch (error) {}
|
catch (error) {}
|
||||||
let latest = 0;
|
}
|
||||||
for (let i = 0; i < dll.length; ++i) {
|
if (timestamp > latest) {
|
||||||
const path = dll[i];
|
// eslint-disable-next-line no-param-reassign
|
||||||
try {
|
delete neutrinoConfigs['web-vendor'];
|
||||||
// eslint-disable-next-line no-await-in-loop
|
}
|
||||||
const stats = await stat(join(FLECKS_CORE_ROOT, 'node_modules', path));
|
else if (timestamp > 0) {
|
||||||
if (stats.mtime > latest) {
|
await unlink(manifest);
|
||||||
latest = stats.mtime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-empty
|
|
||||||
catch (error) {}
|
|
||||||
}
|
|
||||||
if (timestamp > latest) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
delete neutrinoConfigs['web-vendor'];
|
|
||||||
}
|
|
||||||
else if (timestamp > 0) {
|
|
||||||
await unlink(manifest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bail if there's no web build.
|
}
|
||||||
if (!neutrinoConfigs.web) {
|
// Bail if there's no web build.
|
||||||
return;
|
if (!neutrinoConfigs.web) {
|
||||||
}
|
return;
|
||||||
// Bail if the build isn't watching.
|
}
|
||||||
if (!process.argv.find((arg) => '--watch' === arg)) {
|
// Bail if the build isn't watching.
|
||||||
return;
|
if (!process.argv.find((arg) => '--watch' === arg)) {
|
||||||
}
|
return;
|
||||||
// Otherwise, spawn `webpack-dev-server` (WDS).
|
}
|
||||||
const cmd = [
|
// Otherwise, spawn `webpack-dev-server` (WDS).
|
||||||
'npx', 'webpack-dev-server',
|
const cmd = [
|
||||||
'--mode', 'development',
|
'npx', 'webpack-dev-server',
|
||||||
'--hot',
|
'--mode', 'development',
|
||||||
'--config', flecks.buildConfig('webpack.config.js'),
|
'--hot',
|
||||||
];
|
'--config', flecks.buildConfig('webpack.config.js'),
|
||||||
spawnWith(
|
];
|
||||||
cmd,
|
spawnWith(
|
||||||
{
|
cmd,
|
||||||
env: {
|
|
||||||
FLECKS_CORE_BUILD_LIST: 'web',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
// Remove the build config since we're handing off to WDS.
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
delete neutrinoConfigs.web;
|
|
||||||
},
|
|
||||||
'@flecks/core.build.config': () => [
|
|
||||||
/**
|
|
||||||
* Template file used to generate the client HTML.
|
|
||||||
*
|
|
||||||
* See: https://github.com/jantimon/html-webpack-plugin/blob/main/docs/template-option.md
|
|
||||||
*/
|
|
||||||
'template.ejs',
|
|
||||||
/**
|
|
||||||
* PostCSS config file.
|
|
||||||
*
|
|
||||||
* See: https://github.com/postcss/postcss#usage
|
|
||||||
*/
|
|
||||||
'postcss.config.js',
|
|
||||||
],
|
|
||||||
'@flecks/core.config': () => ({
|
|
||||||
/**
|
|
||||||
* (webpack-dev-server) Disable the host check.
|
|
||||||
*
|
|
||||||
* See: https://github.com/webpack/webpack-dev-server/issues/887
|
|
||||||
*/
|
|
||||||
devDisableHostCheck: false,
|
|
||||||
/**
|
|
||||||
* (webpack-dev-server) Host to bind.
|
|
||||||
*/
|
|
||||||
devHost: 'localhost',
|
|
||||||
/**
|
|
||||||
* (webpack-dev-server) Port to bind.
|
|
||||||
*/
|
|
||||||
devPort: undefined,
|
|
||||||
/**
|
|
||||||
* (webpack-dev-server) Public path to serve.
|
|
||||||
*
|
|
||||||
* Defaults to `flecks.get('@flecks/web/server.public')`.
|
|
||||||
*/
|
|
||||||
devPublic: undefined,
|
|
||||||
/**
|
|
||||||
* (webpack-dev-server) Webpack stats output.
|
|
||||||
*/
|
|
||||||
devStats: {
|
|
||||||
assets: false,
|
|
||||||
chunks: false,
|
|
||||||
colors: true,
|
|
||||||
modules: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Modules to externalize using `webpack.DllPlugin`.
|
|
||||||
*/
|
|
||||||
dll: [],
|
|
||||||
/**
|
|
||||||
* Force building http target even if there's a fleck target.
|
|
||||||
*/
|
|
||||||
forceBuildWithFleck: false,
|
|
||||||
/**
|
|
||||||
* Host to bind.
|
|
||||||
*/
|
|
||||||
host: '0.0.0.0',
|
|
||||||
/**
|
|
||||||
* Build path.
|
|
||||||
*/
|
|
||||||
output: 'web',
|
|
||||||
/**
|
|
||||||
* Port to bind.
|
|
||||||
*/
|
|
||||||
port: 32340,
|
|
||||||
/**
|
|
||||||
* Public path to server.
|
|
||||||
*/
|
|
||||||
public: 'localhost:32340',
|
|
||||||
/**
|
|
||||||
* Webpack stats configuration when building HTTP target.
|
|
||||||
*/
|
|
||||||
stats: {
|
|
||||||
children: false,
|
|
||||||
chunks: false,
|
|
||||||
colors: true,
|
|
||||||
modules: false,
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Proxies to trust.
|
|
||||||
*
|
|
||||||
* See: https://www.npmjs.com/package/proxy-addr
|
|
||||||
*/
|
|
||||||
trust: false,
|
|
||||||
}),
|
|
||||||
'@flecks/core.starting': (flecks) => {
|
|
||||||
debug('bootstrapping flecks...');
|
|
||||||
const webFlecks = Flecks.bootstrap({
|
|
||||||
config: flecks.config,
|
|
||||||
platforms: ['client', '!server'],
|
|
||||||
});
|
|
||||||
debug('bootstrapped');
|
|
||||||
flecks.set('$flecks/web.flecks', webFlecks);
|
|
||||||
},
|
|
||||||
'@flecks/core.targets': (flecks) => [
|
|
||||||
'web',
|
|
||||||
...(flecks.get('@flecks/web/server.dll').length > 0 ? ['web-vendor'] : []),
|
|
||||||
],
|
|
||||||
'@flecks/web.routes': (flecks) => [
|
|
||||||
{
|
{
|
||||||
method: 'get',
|
env: {
|
||||||
path: '/flecks.config.js',
|
FLECKS_CORE_BUILD_LIST: 'web',
|
||||||
middleware: async (req, res) => {
|
|
||||||
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
|
|
||||||
res.send(await configSource(flecks, req));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
);
|
||||||
'@flecks/web/server.stream.html': inlineConfig,
|
// Remove the build config since we're handing off to WDS.
|
||||||
'@flecks/server.up': (flecks) => createHttpServer(flecks),
|
// eslint-disable-next-line no-param-reassign
|
||||||
'@flecks/repl.context': (flecks) => ({
|
delete neutrinoConfigs.web;
|
||||||
httpServer: flecks.get('$flecks/web/server.instance'),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
'@flecks/core.build.config': () => [
|
||||||
|
/**
|
||||||
|
* Template file used to generate the client HTML.
|
||||||
|
*
|
||||||
|
* See: https://github.com/jantimon/html-webpack-plugin/blob/main/docs/template-option.md
|
||||||
|
*/
|
||||||
|
'template.ejs',
|
||||||
|
/**
|
||||||
|
* PostCSS config file.
|
||||||
|
*
|
||||||
|
* See: https://github.com/postcss/postcss#usage
|
||||||
|
*/
|
||||||
|
'postcss.config.js',
|
||||||
|
],
|
||||||
|
'@flecks/core.config': () => ({
|
||||||
|
/**
|
||||||
|
* (webpack-dev-server) Disable the host check.
|
||||||
|
*
|
||||||
|
* See: https://github.com/webpack/webpack-dev-server/issues/887
|
||||||
|
*/
|
||||||
|
devDisableHostCheck: false,
|
||||||
|
/**
|
||||||
|
* (webpack-dev-server) Host to bind.
|
||||||
|
*/
|
||||||
|
devHost: 'localhost',
|
||||||
|
/**
|
||||||
|
* (webpack-dev-server) Port to bind.
|
||||||
|
*/
|
||||||
|
devPort: undefined,
|
||||||
|
/**
|
||||||
|
* (webpack-dev-server) Public path to serve.
|
||||||
|
*
|
||||||
|
* Defaults to `flecks.get('@flecks/web/server.public')`.
|
||||||
|
*/
|
||||||
|
devPublic: undefined,
|
||||||
|
/**
|
||||||
|
* (webpack-dev-server) Webpack stats output.
|
||||||
|
*/
|
||||||
|
devStats: {
|
||||||
|
assets: false,
|
||||||
|
chunks: false,
|
||||||
|
colors: true,
|
||||||
|
modules: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Modules to externalize using `webpack.DllPlugin`.
|
||||||
|
*/
|
||||||
|
dll: [],
|
||||||
|
/**
|
||||||
|
* Force building http target even if there's a fleck target.
|
||||||
|
*/
|
||||||
|
forceBuildWithFleck: false,
|
||||||
|
/**
|
||||||
|
* Host to bind.
|
||||||
|
*/
|
||||||
|
host: '0.0.0.0',
|
||||||
|
/**
|
||||||
|
* Build path.
|
||||||
|
*/
|
||||||
|
output: 'web',
|
||||||
|
/**
|
||||||
|
* Port to bind.
|
||||||
|
*/
|
||||||
|
port: 32340,
|
||||||
|
/**
|
||||||
|
* Public path to server.
|
||||||
|
*/
|
||||||
|
public: 'localhost:32340',
|
||||||
|
/**
|
||||||
|
* Webpack stats configuration when building HTTP target.
|
||||||
|
*/
|
||||||
|
stats: {
|
||||||
|
children: false,
|
||||||
|
chunks: false,
|
||||||
|
colors: true,
|
||||||
|
modules: false,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Proxies to trust.
|
||||||
|
*
|
||||||
|
* See: https://www.npmjs.com/package/proxy-addr
|
||||||
|
*/
|
||||||
|
trust: false,
|
||||||
|
}),
|
||||||
|
'@flecks/core.starting': (flecks) => {
|
||||||
|
debug('bootstrapping flecks...');
|
||||||
|
const webFlecks = Flecks.bootstrap({
|
||||||
|
config: flecks.config,
|
||||||
|
platforms: ['client', '!server'],
|
||||||
|
});
|
||||||
|
debug('bootstrapped');
|
||||||
|
flecks.set('$flecks/web.flecks', webFlecks);
|
||||||
|
},
|
||||||
|
'@flecks/core.targets': (flecks) => [
|
||||||
|
'web',
|
||||||
|
...(flecks.get('@flecks/web/server.dll').length > 0 ? ['web-vendor'] : []),
|
||||||
|
],
|
||||||
|
'@flecks/web.routes': (flecks) => [
|
||||||
|
{
|
||||||
|
method: 'get',
|
||||||
|
path: '/flecks.config.js',
|
||||||
|
middleware: async (req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
|
||||||
|
res.send(await configSource(flecks, req));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@flecks/web/server.stream.html': inlineConfig,
|
||||||
|
'@flecks/server.up': (flecks) => createHttpServer(flecks),
|
||||||
|
'@flecks/repl.context': (flecks) => ({
|
||||||
|
httpServer: flecks.get('$flecks/web/server.instance'),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user