refactor: testing
This commit is contained in:
parent
2a98363942
commit
27e3275a39
63
.github/workflows/ci.yml
vendored
63
.github/workflows/ci.yml
vendored
|
@ -8,15 +8,58 @@ on:
|
|||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
uses: ./.github/workflows/node.yml
|
||||
with:
|
||||
run: npm run lint
|
||||
test:
|
||||
uses: ./.github/workflows/node.yml
|
||||
with:
|
||||
run: npm run -- test -p 360000
|
||||
|
||||
build:
|
||||
uses: ./.github/workflows/node.yml
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
run: npm run build
|
||||
node-version: 20.x
|
||||
cache: 'npm'
|
||||
- run: npm config set registry ${{ vars.NPM_CI_REGISTRY }}
|
||||
if: ${{ vars.NPM_CI_REGISTRY }}
|
||||
- run: |
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: ${{ vars.CI_PARALLEL || 256 }}
|
||||
matrix:
|
||||
node-version: [16.x, 18.x, 20.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgconf-2-4 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm-dev libnss3-dev libxss-dev libasound2
|
||||
- uses: browser-actions/setup-chrome@latest
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm config set registry ${{ vars.NPM_CI_REGISTRY }}
|
||||
if: ${{ vars.NPM_CI_REGISTRY }}
|
||||
- run: |
|
||||
npm ci
|
||||
npm run -- test -t 360000
|
||||
npm run -- test -t 360000 -p e2e
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'npm'
|
||||
- run: npm config set registry ${{ vars.NPM_CI_REGISTRY }}
|
||||
if: ${{ vars.NPM_CI_REGISTRY }}
|
||||
- run: |
|
||||
npm ci
|
||||
npm run lint
|
||||
|
||||
|
|
12
.github/workflows/e2e.yml
vendored
12
.github/workflows/e2e.yml
vendored
|
@ -1,12 +0,0 @@
|
|||
name: End-to-end tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '20 4 * * *'
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
uses: ./.github/workflows/node.yml
|
||||
with:
|
||||
run: npm run -- test -t 360000 -p e2e
|
27
.github/workflows/node.yml
vendored
27
.github/workflows/node.yml
vendored
|
@ -1,27 +0,0 @@
|
|||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
node:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 20.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm config set registry ${{ vars.NPM_CI_REGISTRY }}
|
||||
if: ${{ vars.NPM_CI_REGISTRY }}
|
||||
- run: |
|
||||
npm ci
|
||||
${{ inputs.run }}
|
811
package-lock.json
generated
811
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -261,11 +261,12 @@ exports.commands = (program, flecks) => {
|
|||
const spawnWebpack = () => {
|
||||
webpack = spawnWith(cmd, options);
|
||||
webpack.on('message', (message) => {
|
||||
switch (message) {
|
||||
switch (message.type) {
|
||||
case 'kill':
|
||||
debug('killing...');
|
||||
webpack.kill();
|
||||
watcher.close();
|
||||
process.exitCode = message.payload;
|
||||
break;
|
||||
case 'restart':
|
||||
debug('restarting webpack...');
|
||||
|
|
|
@ -41,6 +41,7 @@ module.exports = async (flecks) => ({
|
|||
'test/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
'prefer-arrow-callback': ['error', {allowNamedFunctions: true}],
|
||||
'brace-style': 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
|
|
|
@ -26,9 +26,11 @@ exports.hooks = {
|
|||
}
|
||||
config.plugins.push(new ProcessAssets(target, flecks));
|
||||
},
|
||||
'@flecks/build.config.alter': async ({test}) => {
|
||||
'@flecks/build.config.alter': async ({test}, env, argv, flecks) => {
|
||||
if (test) {
|
||||
// Externalize the rest.
|
||||
test.externals = await externals({
|
||||
additionalModuleDirs: flecks.resolver.modules,
|
||||
allowlist: Object.keys(test.resolve.fallback).map((fallback) => new RegExp(fallback)),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {randomBytes} from 'crypto';
|
||||
import {mkdir} from 'fs/promises';
|
||||
import {tmpdir} from 'os';
|
||||
import {join} from 'path';
|
||||
import {basename, join} from 'path';
|
||||
|
||||
import {rimraf} from 'rimraf';
|
||||
|
||||
|
@ -12,8 +12,23 @@ export function id() {
|
|||
}
|
||||
|
||||
export async function createWorkspace() {
|
||||
const workspace = join(tmpdir(), '@flecks', 'core', 'testing', await id());
|
||||
let workspace = join(tmpdir(), '@flecks', 'core', 'testing', await id());
|
||||
try {
|
||||
throw new Error();
|
||||
}
|
||||
catch (error) {
|
||||
workspace += `-${basename(
|
||||
error.stack
|
||||
.split('\n').slice(-1)[0]
|
||||
.split('at ')[1]
|
||||
.match(/\((.*)\)$/)[1]
|
||||
.split(':').slice(-3, -2)[0],
|
||||
)}`;
|
||||
}
|
||||
await mkdir(workspace, {recursive: true});
|
||||
process.on('exit', () => {
|
||||
rimraf.sync(workspace);
|
||||
});
|
||||
// sheeeeesh
|
||||
process.prependListener('message', async (message) => {
|
||||
if ('__workerpool-terminate__' === message) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const {access} = require('fs/promises');
|
||||
const {join, relative} = require('path');
|
||||
const {join} = require('path');
|
||||
|
||||
const {commands: coreCommands} = require('@flecks/build/build/commands');
|
||||
const {rimraf} = require('@flecks/build/src/server');
|
||||
const {D} = require('@flecks/core/src');
|
||||
const {glob} = require('@flecks/core/src/server');
|
||||
const {glob, pipesink, processCode} = require('@flecks/core/src/server');
|
||||
const Mocha = require('mocha');
|
||||
const {watchParallelRun} = require('mocha/lib/cli/watch-run');
|
||||
|
||||
|
@ -12,6 +12,7 @@ const debug = D('@flecks/build.commands');
|
|||
|
||||
const {
|
||||
FLECKS_CORE_ROOT = process.cwd(),
|
||||
TERM,
|
||||
} = process.env;
|
||||
|
||||
module.exports = (program, flecks) => {
|
||||
|
@ -41,47 +42,36 @@ module.exports = (program, flecks) => {
|
|||
watch,
|
||||
} = opts;
|
||||
const {build} = coreCommands(program, flecks);
|
||||
let files = [];
|
||||
if (platforms.includes('default')) {
|
||||
files.push(...await glob(join(FLECKS_CORE_ROOT, 'test', '*.js')));
|
||||
}
|
||||
await Promise.all(
|
||||
platforms
|
||||
.filter((platform) => 'default' !== platform)
|
||||
.map(async (platform) => {
|
||||
files.push(...await glob(join(FLECKS_CORE_ROOT, 'test', platform, '*.js')));
|
||||
}),
|
||||
);
|
||||
if (0 === files.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('No tests found.');
|
||||
return undefined;
|
||||
}
|
||||
files = files.map((path) => relative(FLECKS_CORE_ROOT, path));
|
||||
if (only) {
|
||||
if (files.includes(only)) {
|
||||
files = [only];
|
||||
}
|
||||
else {
|
||||
throw new Error(`Test '${only}' does not exist!`);
|
||||
}
|
||||
}
|
||||
files = files.map((file) => join('dist', file));
|
||||
// Remove the previous test.
|
||||
// Remove the previous test(s).
|
||||
await rimraf(join(FLECKS_CORE_ROOT, 'dist', 'test'));
|
||||
// Kick off building the test and wait for the file to exist.
|
||||
await build.action(
|
||||
const child = await build.action(
|
||||
'test',
|
||||
{
|
||||
env: {FLECKS_CORE_TEST_PLATFORMS: JSON.stringify(platforms)},
|
||||
env: {
|
||||
FLECKS_CORE_TEST_PLATFORMS: JSON.stringify(platforms),
|
||||
FORCE_COLOR: 'dumb' !== TERM,
|
||||
},
|
||||
production,
|
||||
stdio: 'ignore',
|
||||
stdio: watch ? 'inherit' : 'pipe',
|
||||
watch,
|
||||
},
|
||||
);
|
||||
if (!watch) {
|
||||
const stdout = pipesink(child.stdout);
|
||||
if (0 !== await processCode(child)) {
|
||||
const buffer = await stdout;
|
||||
if (!process.stdout.write(buffer)) {
|
||||
await new Promise((resolve, reject) => {
|
||||
process.stdout.on('error', reject);
|
||||
process.stdout.on('drain', resolve);
|
||||
});
|
||||
}
|
||||
program.error('\nbuilding tests failed!\n');
|
||||
}
|
||||
}
|
||||
debug('Testing...', opts);
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
while (watch) {
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await access(join(FLECKS_CORE_ROOT, 'dist', 'test'));
|
||||
|
@ -94,6 +84,19 @@ module.exports = (program, flecks) => {
|
|||
});
|
||||
}
|
||||
}
|
||||
let files = await glob(join(FLECKS_CORE_ROOT, 'dist', 'test', '**', '*.js'));
|
||||
if (0 === files.length) {
|
||||
return undefined;
|
||||
}
|
||||
if (only) {
|
||||
const index = files.indexOf(join(FLECKS_CORE_ROOT, 'dist', only));
|
||||
if (-1 !== index) {
|
||||
files = [files[index]];
|
||||
}
|
||||
else {
|
||||
throw new Error(`Test '${only}' does not exist!`);
|
||||
}
|
||||
}
|
||||
// Magic.
|
||||
require('@flecks/core/build/resolve')(
|
||||
{
|
||||
|
|
|
@ -92,15 +92,17 @@ class StartServerPlugin {
|
|||
...(inspectPort && {inspectPort}),
|
||||
});
|
||||
this.worker = cluster.fork(env);
|
||||
this.worker.on('exit', (code) => {
|
||||
if (killOnExit) {
|
||||
process.send({type: 'kill', payload: code});
|
||||
process.exit(code);
|
||||
}
|
||||
});
|
||||
this.worker.on('disconnect', () => {
|
||||
if (this.worker.exitedAfterDisconnect) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[HMR] Restarting application...');
|
||||
process.send('restart');
|
||||
}
|
||||
else if (killOnExit) {
|
||||
process.send('kill');
|
||||
process.exit(0);
|
||||
process.send({type: 'restart'});
|
||||
}
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
},
|
||||
"files": [
|
||||
"entry.js",
|
||||
"runtime.js"
|
||||
"runtime.js",
|
||||
"server.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@flecks/core": "^4.0.5"
|
||||
|
|
|
@ -20,15 +20,9 @@ import {D, Flecks} from '@flecks/core';
|
|||
}
|
||||
const debug = D('@flecks/server/entry');
|
||||
debug('starting server...');
|
||||
try {
|
||||
global.flecks = await Flecks.from({...runtime, flecks: await loadFlecks()});
|
||||
await global.flecks.invokeSequentialAsync('@flecks/server.up');
|
||||
debug('up!');
|
||||
}
|
||||
catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
|
||||
if (module.hot) {
|
||||
|
|
49
packages/server/src/server.js
Normal file
49
packages/server/src/server.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import cluster from 'cluster';
|
||||
import {createConnection} from 'net';
|
||||
|
||||
const {
|
||||
FLECKS_SERVER_TEST_SOCKET,
|
||||
NODE_ENV,
|
||||
} = process.env;
|
||||
|
||||
export const hooks = {
|
||||
'@flecks/server.up': (flecks) => {
|
||||
if (!FLECKS_SERVER_TEST_SOCKET || 'test' !== NODE_ENV) {
|
||||
return;
|
||||
}
|
||||
const socket = createConnection(FLECKS_SERVER_TEST_SOCKET);
|
||||
if (cluster.isWorker) {
|
||||
cluster.worker.on('disconnect', () => {
|
||||
socket.end();
|
||||
});
|
||||
}
|
||||
flecks.server.socket = socket;
|
||||
socket.on('connect', () => {
|
||||
socket.on('data', (data) => {
|
||||
const {meta, payload, type} = JSON.parse(data);
|
||||
switch (type) {
|
||||
case 'config.get':
|
||||
socket.write(JSON.stringify({
|
||||
meta,
|
||||
payload: flecks.get(payload),
|
||||
}));
|
||||
break;
|
||||
case 'exit':
|
||||
socket.end();
|
||||
process.exit(payload);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const mixin = (Flecks) => class FlecksWithServer extends Flecks {
|
||||
|
||||
constructor(runtime) {
|
||||
super(runtime);
|
||||
this.server = {};
|
||||
}
|
||||
|
||||
};
|
|
@ -2,9 +2,14 @@ import {cp} from 'fs/promises';
|
|||
import {join} from 'path';
|
||||
|
||||
import {createWorkspace} from '@flecks/core/build/testing';
|
||||
import {binaryPath, processCode, spawnWith} from '@flecks/core/server';
|
||||
import {
|
||||
binaryPath,
|
||||
pipesink,
|
||||
processCode,
|
||||
spawnWith,
|
||||
} from '@flecks/core/server';
|
||||
|
||||
import {listen} from './listen';
|
||||
import {socketListener} from './listen';
|
||||
|
||||
const {
|
||||
FLECKS_CORE_ROOT = process.cwd(),
|
||||
|
@ -18,43 +23,85 @@ export async function createApplication() {
|
|||
return workspace;
|
||||
}
|
||||
|
||||
export async function buildChild(path, {args = [], opts = {}} = {}) {
|
||||
return spawnWith(
|
||||
class TestingServer {
|
||||
|
||||
constructor(path, child, socketServer) {
|
||||
this.path = path;
|
||||
this.child = child;
|
||||
this.socketServer = socketServer;
|
||||
}
|
||||
|
||||
async waitForSocket(options) {
|
||||
return this.socketServer.waitForSocket(options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function startServer({
|
||||
args = ['-h'],
|
||||
beforeBuild,
|
||||
failOnErrorCode = true,
|
||||
opts = {},
|
||||
path: request,
|
||||
task,
|
||||
} = {}) {
|
||||
let previousTimeout;
|
||||
const start = Date.now();
|
||||
if (task) {
|
||||
previousTimeout = task.timeout();
|
||||
task.timeout(0);
|
||||
}
|
||||
const {socketPath, socketServer} = await socketListener();
|
||||
const path = request || await createApplication();
|
||||
if (beforeBuild) {
|
||||
await beforeBuild({path, task});
|
||||
}
|
||||
const server = spawnWith(
|
||||
[await binaryPath('flecks', '@flecks/build'), 'build', ...args],
|
||||
{
|
||||
stdio: 'ignore',
|
||||
stdio: 'pipe',
|
||||
...opts,
|
||||
env: {
|
||||
FLECKS_ENV__flecks_server__stats: '{"preset": "none"}',
|
||||
FLECKS_ENV__flecks_server__start: 0,
|
||||
FLECKS_ENV__flecks_server__start: true,
|
||||
FLECKS_CORE_ROOT: path,
|
||||
FLECKS_SERVER_TEST_SOCKET: socketPath,
|
||||
NODE_ENV: 'test',
|
||||
NODE_PATH: join(FLECKS_CORE_ROOT, '..', '..', 'node_modules'),
|
||||
...opts.env,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function build(path, {args = [], opts = {}} = {}) {
|
||||
return processCode(await buildChild(path, {args, opts}));
|
||||
}
|
||||
|
||||
export async function serverActions(path, actions) {
|
||||
const {listening, path: socketPath, socketServer} = await listen();
|
||||
await listening;
|
||||
const server = spawnWith(
|
||||
['node', join(path, 'dist', 'server')],
|
||||
{
|
||||
env: {
|
||||
FLECKS_SERVER_TEST_SOCKET: socketPath,
|
||||
NODE_PATH: join(FLECKS_CORE_ROOT, '..', '..', 'node_modules'),
|
||||
},
|
||||
stdio: 'ignore',
|
||||
},
|
||||
if (failOnErrorCode) {
|
||||
const stderr = pipesink(server.stderr);
|
||||
server.on('exit', async (code) => {
|
||||
if (0 !== code) {
|
||||
const buffer = await stderr;
|
||||
if (!process.stderr.write(buffer)) {
|
||||
await new Promise((resolve, reject) => {
|
||||
process.stderr.on('error', reject);
|
||||
process.stderr.on('drain', resolve);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('\nserver process exited unexpectedly\n');
|
||||
process.exit(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
task?.timeout(previousTimeout + (Date.now() - start));
|
||||
return new TestingServer(
|
||||
path,
|
||||
server,
|
||||
socketServer,
|
||||
);
|
||||
const [code, results] = await Promise.all([
|
||||
processCode(server),
|
||||
socketServer.waitForSocket().then(async (socket) => {
|
||||
}
|
||||
|
||||
export function withServer(task, options) {
|
||||
return async function withServer() {
|
||||
const server = await startServer({...options, task: this});
|
||||
const socket = await server.waitForSocket({task: this});
|
||||
server.actions = async (actions) => {
|
||||
const results = [];
|
||||
await actions.reduce(
|
||||
(p, action) => (
|
||||
|
@ -68,7 +115,25 @@ export async function serverActions(path, actions) {
|
|||
Promise.resolve(),
|
||||
);
|
||||
return results;
|
||||
}),
|
||||
]);
|
||||
return {code, results};
|
||||
};
|
||||
return task({server, socket});
|
||||
};
|
||||
}
|
||||
|
||||
export async function build(path, {args = [], opts = {}} = {}) {
|
||||
return processCode(spawnWith(
|
||||
[await binaryPath('flecks', '@flecks/build'), 'build', ...args],
|
||||
{
|
||||
stdio: 'ignore',
|
||||
...opts,
|
||||
env: {
|
||||
FLECKS_ENV__flecks_server__stats: '{"preset": "none"}',
|
||||
FLECKS_ENV__flecks_server__start: 0,
|
||||
FLECKS_CORE_ROOT: path,
|
||||
NODE_ENV: 'test',
|
||||
NODE_PATH: join(FLECKS_CORE_ROOT, '..', '..', 'node_modules'),
|
||||
...opts.env,
|
||||
},
|
||||
},
|
||||
));
|
||||
}
|
||||
|
|
|
@ -33,12 +33,12 @@ class SocketWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
async waitForHmr() {
|
||||
async waitForAction(type) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket.on('error', reject);
|
||||
this.socket.on('data', (data) => {
|
||||
const action = JSON.parse(data.toString());
|
||||
if ('hmr' === action.type) {
|
||||
if (action.type === type) {
|
||||
resolve(action);
|
||||
}
|
||||
});
|
||||
|
@ -47,31 +47,39 @@ class SocketWrapper {
|
|||
|
||||
}
|
||||
|
||||
export async function listen() {
|
||||
export async function socketListener() {
|
||||
const path = join(tmpdir(), 'flecks', 'ci', await id());
|
||||
await mkdir(dirname(path), {recursive: true});
|
||||
const server = createServer();
|
||||
server.listen(path);
|
||||
server.waitForSocket = () => (
|
||||
server.waitForSocket = ({task, timeout = 30000} = {}) => (
|
||||
new Promise((resolve, reject) => {
|
||||
server.on('error', reject);
|
||||
let previousTimeout;
|
||||
const start = Date.now();
|
||||
if (task) {
|
||||
previousTimeout = task.timeout();
|
||||
task.timeout(0);
|
||||
}
|
||||
const handle = setTimeout(() => {
|
||||
reject(new Error('timeout waiting for IPC connection'));
|
||||
}, timeout);
|
||||
const finish = () => {
|
||||
clearTimeout(handle);
|
||||
task?.timeout(previousTimeout + (Date.now() - start));
|
||||
};
|
||||
server.on('error', (error) => {
|
||||
finish();
|
||||
reject(error);
|
||||
});
|
||||
server.on('connection', (socket) => {
|
||||
finish();
|
||||
resolve(new SocketWrapper(socket));
|
||||
});
|
||||
})
|
||||
);
|
||||
return {
|
||||
listening: new Promise((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
server.on('error', reject);
|
||||
server.on('listening', resolve);
|
||||
}),
|
||||
path,
|
||||
socketServer: server,
|
||||
};
|
||||
}
|
||||
|
||||
export async function socketListener() {
|
||||
const {listening, path: socketPath, socketServer} = await listen();
|
||||
await listening;
|
||||
return {socketServer, socketPath};
|
||||
});
|
||||
return {socketServer: server, socketPath: path};
|
||||
}
|
||||
|
|
|
@ -1,38 +1,15 @@
|
|||
import {join} from 'path';
|
||||
|
||||
import {heavySetup} from '@flecks/core/build/testing';
|
||||
import {writeFile} from '@flecks/core/server';
|
||||
import {expect} from 'chai';
|
||||
|
||||
import {build, createApplication} from './build/build';
|
||||
import {socketListener} from './build/listen';
|
||||
import {withServer} from './build/build';
|
||||
|
||||
let path;
|
||||
let socket;
|
||||
|
||||
before(heavySetup(async () => {
|
||||
path = await createApplication();
|
||||
const {socketPath, socketServer} = await socketListener();
|
||||
build(
|
||||
path,
|
||||
{
|
||||
args: ['-h'],
|
||||
opts: {
|
||||
env: {
|
||||
FLECKS_ENV__flecks_server__start: true,
|
||||
FLECKS_SERVER_TEST_SOCKET: socketPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
socket = await socketServer.waitForSocket();
|
||||
}));
|
||||
|
||||
it('updates config', async () => {
|
||||
it('allows updates to fail', withServer(async ({server, socket}) => {
|
||||
expect((await socket.send({type: 'config.get', payload: 'comm.foo'})).payload)
|
||||
.to.equal('bar');
|
||||
await writeFile(
|
||||
join(path, 'build', 'flecks.yml'),
|
||||
join(server.path, 'build', 'flecks.yml'),
|
||||
`
|
||||
'@flecks/build': {}
|
||||
'@flecks/core': {}
|
||||
|
@ -40,12 +17,12 @@ it('updates config', async () => {
|
|||
'comm:./comm': {foo: 'baz'}
|
||||
`,
|
||||
);
|
||||
await socket.waitForHmr();
|
||||
await socket.waitForAction('hmr');
|
||||
expect((await socket.send({type: 'config.get', payload: 'comm.foo'})).payload)
|
||||
.to.equal('baz');
|
||||
let restarted;
|
||||
const whatHappened = Promise.race([
|
||||
socket.waitForHmr()
|
||||
socket.waitForAction('hmr')
|
||||
.then(() => {
|
||||
restarted = false;
|
||||
})
|
||||
|
@ -58,7 +35,7 @@ it('updates config', async () => {
|
|||
}),
|
||||
]);
|
||||
await writeFile(
|
||||
join(path, 'build', 'flecks.yml'),
|
||||
join(server.path, 'build', 'flecks.yml'),
|
||||
`
|
||||
'@flecks/build': {}
|
||||
'@flecks/core': {}
|
||||
|
@ -69,4 +46,4 @@ it('updates config', async () => {
|
|||
await whatHappened;
|
||||
expect(restarted)
|
||||
.to.be.true;
|
||||
});
|
||||
}));
|
||||
|
|
|
@ -1,40 +1,14 @@
|
|||
import {join} from 'path';
|
||||
|
||||
import {heavySetup} from '@flecks/core/build/testing';
|
||||
import {writeFile} from '@flecks/core/server';
|
||||
import {expect} from 'chai';
|
||||
|
||||
import {buildChild, createApplication} from './build/build';
|
||||
import {socketListener} from './build/listen';
|
||||
import {withServer} from './build/build';
|
||||
|
||||
let path;
|
||||
let listener;
|
||||
let socket;
|
||||
|
||||
before(heavySetup(async () => {
|
||||
path = await createApplication();
|
||||
listener = await socketListener();
|
||||
const {socketPath, socketServer} = listener;
|
||||
await buildChild(
|
||||
path,
|
||||
{
|
||||
args: ['-w'],
|
||||
opts: {
|
||||
env: {
|
||||
FLECKS_ENV__flecks_server__start: true,
|
||||
FLECKS_SERVER_TEST_SOCKET: socketPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
socket = await socketServer.waitForSocket();
|
||||
}));
|
||||
|
||||
async function restart() {
|
||||
this.timeout(0);
|
||||
it('restarts when config keys change', withServer(async ({server, socket}) => {
|
||||
let restarted;
|
||||
const whatHappened = Promise.race([
|
||||
socket.waitForHmr()
|
||||
socket.waitForAction('hmr')
|
||||
.then(() => {
|
||||
restarted = false;
|
||||
})
|
||||
|
@ -47,7 +21,7 @@ async function restart() {
|
|||
}),
|
||||
]);
|
||||
await writeFile(
|
||||
join(path, 'build', 'flecks.yml'),
|
||||
join(server.path, 'build', 'flecks.yml'),
|
||||
`
|
||||
'@flecks/build': {}
|
||||
'@flecks/core': {}
|
||||
|
@ -60,15 +34,10 @@ async function restart() {
|
|||
expect(restarted)
|
||||
.to.be.true;
|
||||
let config;
|
||||
const before = Date.now();
|
||||
await listener.socketServer.waitForSocket()
|
||||
await server.socketServer.waitForSocket({task: this})
|
||||
.then(async (socket) => {
|
||||
({payload: config} = await socket.send({type: 'config.get', payload: '@flecks/repl/server'}));
|
||||
});
|
||||
// Had to rebuild...
|
||||
this.timeout(2000 + (Date.now() - before));
|
||||
expect(config)
|
||||
.to.not.be.undefined;
|
||||
}
|
||||
|
||||
it('restarts when config keys change', restart);
|
||||
}));
|
||||
|
|
|
@ -1,38 +1,15 @@
|
|||
import {join} from 'path';
|
||||
|
||||
import {heavySetup} from '@flecks/core/build/testing';
|
||||
import {writeFile} from '@flecks/core/server';
|
||||
import {expect} from 'chai';
|
||||
|
||||
import {build, createApplication} from './build/build';
|
||||
import {socketListener} from './build/listen';
|
||||
import {withServer} from './build/build';
|
||||
|
||||
let path;
|
||||
let socket;
|
||||
|
||||
before(heavySetup(async () => {
|
||||
path = await createApplication();
|
||||
const {socketPath, socketServer} = await socketListener();
|
||||
build(
|
||||
path,
|
||||
{
|
||||
args: ['-h'],
|
||||
opts: {
|
||||
env: {
|
||||
FLECKS_ENV__flecks_server__start: true,
|
||||
FLECKS_SERVER_TEST_SOCKET: socketPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
socket = await socketServer.waitForSocket();
|
||||
}));
|
||||
|
||||
it('updates config', async () => {
|
||||
it('updates config', withServer(async ({server, socket}) => {
|
||||
expect((await socket.send({type: 'config.get', payload: '@flecks/core.id'})).payload)
|
||||
.to.equal('flecks');
|
||||
await writeFile(
|
||||
join(path, 'build', 'flecks.yml'),
|
||||
join(server.path, 'build', 'flecks.yml'),
|
||||
`
|
||||
'@flecks/build': {}
|
||||
'@flecks/core': {id: 'testing'}
|
||||
|
@ -40,8 +17,8 @@ it('updates config', async () => {
|
|||
'comm:./comm': {}
|
||||
`,
|
||||
);
|
||||
await socket.waitForHmr();
|
||||
await socket.waitForAction('hmr');
|
||||
expect((await socket.send({type: 'config.get', payload: '@flecks/core.id'})).payload)
|
||||
.to.equal('testing');
|
||||
await socket.send({type: 'exit'});
|
||||
});
|
||||
}));
|
||||
|
|
|
@ -1,16 +1,25 @@
|
|||
import {mkdir} from 'fs/promises';
|
||||
import {join} from 'path';
|
||||
|
||||
import {heavySetup} from '@flecks/core/build/testing';
|
||||
import {writeFile} from '@flecks/core/server';
|
||||
import {expect} from 'chai';
|
||||
|
||||
import {build, createApplication, serverActions} from './build/build';
|
||||
import {withServer} from './build/build';
|
||||
|
||||
let path;
|
||||
|
||||
before(heavySetup(async () => {
|
||||
path = await createApplication();
|
||||
it('propagates bootstrap config', withServer(
|
||||
async ({server}) => {
|
||||
const [{payload: foo}, {payload: blah}] = await server.actions([
|
||||
{type: 'config.get', payload: 'server-only.foo'},
|
||||
{type: 'config.get', payload: 'server-only.blah'},
|
||||
{type: 'exit'},
|
||||
]);
|
||||
expect(foo)
|
||||
.to.equal('baz');
|
||||
expect(blah)
|
||||
.to.deep.equal({one: 2, three: 4});
|
||||
},
|
||||
{
|
||||
beforeBuild: async ({path}) => {
|
||||
await mkdir(join(path, 'server-only', 'build'), {recursive: true});
|
||||
await writeFile(join(path, 'server-only', 'package.json'), '{}');
|
||||
const config = `
|
||||
|
@ -32,17 +41,6 @@ before(heavySetup(async () => {
|
|||
'server-only:./server-only': {foo: 'baz'}
|
||||
`,
|
||||
);
|
||||
await build(path, {args: ['-d']});
|
||||
}));
|
||||
|
||||
it('propagates bootstrap config', async () => {
|
||||
const {results: [{payload: foo}, {payload: blah}]} = await serverActions(path, [
|
||||
{type: 'config.get', payload: 'server-only.foo'},
|
||||
{type: 'config.get', payload: 'server-only.blah'},
|
||||
{type: 'exit'},
|
||||
]);
|
||||
expect(foo)
|
||||
.to.equal('baz');
|
||||
expect(blah)
|
||||
.to.deep.equal({one: 2, three: 4});
|
||||
});
|
||||
},
|
||||
},
|
||||
));
|
||||
|
|
|
@ -1,15 +1,24 @@
|
|||
import {join} from 'path';
|
||||
|
||||
import {heavySetup} from '@flecks/core/build/testing';
|
||||
import {writeFile} from '@flecks/core/server';
|
||||
import {expect} from 'chai';
|
||||
|
||||
import {build, createApplication, serverActions} from './build/build';
|
||||
import {withServer} from './build/build';
|
||||
|
||||
let path;
|
||||
|
||||
before(heavySetup(async () => {
|
||||
path = await createApplication();
|
||||
it('propagates bootstrap config', withServer(
|
||||
async ({server}) => {
|
||||
const [{payload: id}, {payload: foo}] = await server.actions([
|
||||
{type: 'config.get', payload: '@flecks/core.id'},
|
||||
{type: 'config.get', payload: 'comm.foo'},
|
||||
{type: 'exit'},
|
||||
]);
|
||||
expect(id)
|
||||
.to.equal('testing');
|
||||
expect(foo)
|
||||
.to.equal('baz');
|
||||
},
|
||||
{
|
||||
beforeBuild: async ({path}) => {
|
||||
await writeFile(
|
||||
join(path, 'build', 'flecks.yml'),
|
||||
`
|
||||
|
@ -19,17 +28,6 @@ before(heavySetup(async () => {
|
|||
'comm:./comm': {foo: 'baz'}
|
||||
`,
|
||||
);
|
||||
await build(path, {args: ['-d']});
|
||||
}));
|
||||
|
||||
it('propagates bootstrap config', async () => {
|
||||
const {results: [{payload: id}, {payload: foo}]} = await serverActions(path, [
|
||||
{type: 'config.get', payload: '@flecks/core.id'},
|
||||
{type: 'config.get', payload: 'comm.foo'},
|
||||
{type: 'exit'},
|
||||
]);
|
||||
expect(id)
|
||||
.to.equal('testing');
|
||||
expect(foo)
|
||||
.to.equal('baz');
|
||||
});
|
||||
},
|
||||
},
|
||||
));
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
import {heavySetup} from '@flecks/core/build/testing';
|
||||
import {expect} from 'chai';
|
||||
|
||||
import {build, createApplication, serverActions} from './build/build';
|
||||
import {withServer} from './build/build';
|
||||
|
||||
let path;
|
||||
|
||||
before(heavySetup(async () => {
|
||||
path = await createApplication();
|
||||
await build(path, {args: ['-d']});
|
||||
}));
|
||||
|
||||
it('propagates runtime config', async () => {
|
||||
const {results: [{payload: foo}]} = await serverActions(path, [
|
||||
it('propagates runtime config', withServer(async ({server}) => {
|
||||
const [{payload: foo}] = await server.actions([
|
||||
{type: 'config.get', payload: 'comm.foo'},
|
||||
{type: 'exit'},
|
||||
]);
|
||||
expect(foo)
|
||||
.to.equal('bar');
|
||||
});
|
||||
}));
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import {heavySetup} from '@flecks/core/build/testing';
|
||||
import {processCode} from '@flecks/core/src/server';
|
||||
import {expect} from 'chai';
|
||||
|
||||
import {build, createApplication, serverActions} from './build/build';
|
||||
import {withServer} from './build/build';
|
||||
|
||||
let path;
|
||||
|
||||
before(heavySetup(async () => {
|
||||
path = await createApplication();
|
||||
await build(path, {args: ['-d']});
|
||||
}));
|
||||
|
||||
it('connects', async () => {
|
||||
const {code} = await serverActions(path, [
|
||||
it('connects', withServer(
|
||||
async ({server}) => {
|
||||
const code = processCode(server.child);
|
||||
await server.actions([
|
||||
{type: 'exit', payload: 42},
|
||||
]);
|
||||
expect(code)
|
||||
expect(await code)
|
||||
.to.equal(42);
|
||||
});
|
||||
},
|
||||
{failOnErrorCode: false},
|
||||
));
|
||||
|
|
|
@ -1,37 +1,14 @@
|
|||
import {join} from 'path';
|
||||
|
||||
import {heavySetup} from '@flecks/core/build/testing';
|
||||
import {writeFile} from '@flecks/core/server';
|
||||
import {expect} from 'chai';
|
||||
|
||||
import {buildChild, createApplication} from './build/build';
|
||||
import {socketListener} from './build/listen';
|
||||
import {withServer} from './build/build';
|
||||
|
||||
let path;
|
||||
let socket;
|
||||
|
||||
before(heavySetup(async () => {
|
||||
path = await createApplication();
|
||||
const {socketPath, socketServer} = await socketListener();
|
||||
await buildChild(
|
||||
path,
|
||||
{
|
||||
args: ['-w'],
|
||||
opts: {
|
||||
env: {
|
||||
FLECKS_ENV__flecks_server__start: true,
|
||||
FLECKS_SERVER_TEST_SOCKET: socketPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
socket = await socketServer.waitForSocket();
|
||||
}));
|
||||
|
||||
it('restarts when root sources change', async () => {
|
||||
it('restarts when root sources change', withServer(async ({server, socket}) => {
|
||||
let restarted;
|
||||
const whatHappened = Promise.race([
|
||||
socket.waitForHmr()
|
||||
socket.waitForAction('hmr')
|
||||
.then(() => {
|
||||
restarted = false;
|
||||
})
|
||||
|
@ -43,8 +20,8 @@ it('restarts when root sources change', async () => {
|
|||
});
|
||||
}),
|
||||
]);
|
||||
await writeFile(join(path, 'comm', 'package.json'), '{}');
|
||||
await writeFile(join(server.path, 'comm', 'package.json'), '{}');
|
||||
await whatHappened;
|
||||
expect(restarted)
|
||||
.to.be.true;
|
||||
});
|
||||
}));
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
import cluster from 'cluster';
|
||||
import {createConnection} from 'net';
|
||||
|
||||
const {
|
||||
FLECKS_SERVER_TEST_SOCKET,
|
||||
} = process.env;
|
||||
|
||||
export const hooks = {
|
||||
'@flecks/core.reload': (fleck, config) => {
|
||||
if ('comm' === fleck && 'fail' === config.foo) {
|
||||
|
@ -12,42 +5,10 @@ export const hooks = {
|
|||
}
|
||||
},
|
||||
'@flecks/core.hmr': async (path, M, flecks) => {
|
||||
if (!flecks.socket) {
|
||||
return;
|
||||
}
|
||||
flecks.socket.write(JSON.stringify({
|
||||
const {socket} = flecks.server;
|
||||
socket.write(JSON.stringify({
|
||||
type: 'hmr',
|
||||
payload: path,
|
||||
}));
|
||||
},
|
||||
'@flecks/server.up': async (flecks) => {
|
||||
if (!FLECKS_SERVER_TEST_SOCKET) {
|
||||
return;
|
||||
}
|
||||
const socket = createConnection(FLECKS_SERVER_TEST_SOCKET);
|
||||
if (cluster.isWorker) {
|
||||
cluster.worker.on('disconnect', () => {
|
||||
socket.end();
|
||||
});
|
||||
}
|
||||
flecks.socket = socket;
|
||||
socket.on('connect', () => {
|
||||
socket.on('data', (data) => {
|
||||
const {meta, payload, type} = JSON.parse(data);
|
||||
switch (type) {
|
||||
case 'config.get':
|
||||
socket.write(JSON.stringify({
|
||||
meta,
|
||||
payload: flecks.get(payload),
|
||||
}));
|
||||
break;
|
||||
case 'exit':
|
||||
socket.end();
|
||||
process.exit(payload);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
const {stat, unlink} = require('fs/promises');
|
||||
const {join} = require('path');
|
||||
const {
|
||||
basename,
|
||||
dirname,
|
||||
extname,
|
||||
join,
|
||||
relative,
|
||||
} = require('path');
|
||||
|
||||
const Build = require('@flecks/build/build/build');
|
||||
const {regexFromExtensions} = require('@flecks/build/src/server');
|
||||
const {binaryPath, spawnWith} = require('@flecks/core/src/server');
|
||||
const {binaryPath, glob, spawnWith} = require('@flecks/core/src/server');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
const {
|
||||
FLECKS_CORE_ROOT = process.cwd(),
|
||||
} = process.env;
|
||||
|
||||
const tests = join(FLECKS_CORE_ROOT, 'test');
|
||||
|
||||
exports.hooks = {
|
||||
'@flecks/build.config': async (target, config, env, argv, flecks) => {
|
||||
const isProduction = 'production' === argv.mode;
|
||||
|
@ -25,6 +33,19 @@ exports.hooks = {
|
|||
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
|
||||
break;
|
||||
}
|
||||
case 'test': {
|
||||
finalLoader = {loader: MiniCssExtractPlugin.loader};
|
||||
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
|
||||
(await glob(join(tests, 'client', '*.js')))
|
||||
.forEach((path) => {
|
||||
const entry = relative(tests, path);
|
||||
config.entry[join(dirname(entry), basename(entry, extname(entry)))] = [
|
||||
'source-map-support/register',
|
||||
path,
|
||||
];
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'web': {
|
||||
if (isProduction) {
|
||||
finalLoader = {loader: MiniCssExtractPlugin.loader};
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
const {access, readFile} = require('fs/promises');
|
||||
const {access} = require('fs/promises');
|
||||
const {
|
||||
basename,
|
||||
dirname,
|
||||
extname,
|
||||
join,
|
||||
} = require('path');
|
||||
|
||||
const Build = require('@flecks/build/build/build');
|
||||
const {glob} = require('@flecks/core/server');
|
||||
|
||||
module.exports = async (config, env, argv, flecks) => {
|
||||
const buildFlecks = await Build.from({
|
||||
config: flecks.realiasedConfig,
|
||||
platforms: ['client', '!server'],
|
||||
});
|
||||
const {resolver, flecks: webFlecks} = buildFlecks;
|
||||
const {flecks: webFlecks} = buildFlecks;
|
||||
const paths = Object.keys(webFlecks)
|
||||
.filter((fleck) => !['@flecks/server'].includes(fleck));
|
||||
const styles = (
|
||||
|
@ -44,7 +42,6 @@ module.exports = async (config, env, argv, flecks) => {
|
|||
)
|
||||
.filter((filename) => !!filename);
|
||||
const runtime = await flecks.resolver.resolve(join('@flecks/web/runtime'));
|
||||
const isProduction = 'production' === argv.mode;
|
||||
const resolvedPaths = (await Promise.all(
|
||||
paths.map(async (path) => [path, await flecks.resolver.resolve(path)]),
|
||||
))
|
||||
|
@ -98,56 +95,4 @@ module.exports = async (config, env, argv, flecks) => {
|
|||
await buildFlecks.runtimeCompiler('web', config, env, argv);
|
||||
// Styles.
|
||||
config.entry.index.push(...styles);
|
||||
// Tests.
|
||||
if (!isProduction) {
|
||||
const testEntries = (await Promise.all(
|
||||
buildFlecks.roots
|
||||
.map(async ([root, request]) => {
|
||||
const tests = [];
|
||||
const resolved = dirname(
|
||||
await buildFlecks.resolver.resolve(join(request, 'package.json')),
|
||||
);
|
||||
const rootTests = await glob(join(resolved, 'test', '*.js'));
|
||||
tests.push(...rootTests.map((test) => test.replace(resolved, root)));
|
||||
const platformTests = await Promise.all(
|
||||
buildFlecks.platforms.map((platform) => (
|
||||
glob(join(resolved, 'test', platform, '*.js'))
|
||||
)),
|
||||
);
|
||||
tests.push(...platformTests.flat().map((test) => test.replace(resolved, root)));
|
||||
return [root, tests];
|
||||
}),
|
||||
))
|
||||
.filter(([, tests]) => tests.length > 0);
|
||||
const tests = await resolver.resolve(
|
||||
join('@flecks/web', 'server', 'build', 'tests'),
|
||||
);
|
||||
const testsSource = (await readFile(tests)).toString();
|
||||
config.module.rules.push({
|
||||
test: tests,
|
||||
use: [
|
||||
{
|
||||
loader: runtime,
|
||||
options: {
|
||||
source: testsSource.replace(
|
||||
" await import('@flecks/web/tests');",
|
||||
testEntries
|
||||
.map(([root, tests]) => (
|
||||
[
|
||||
` describe('${root}', () => {`,
|
||||
` ${tests.map((test) => `require('${test}');`).join('\n ')}`,
|
||||
' });',
|
||||
].join('\n')
|
||||
)).join('\n\n'),
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
// Fix a little derp in mocha 10.2.0.
|
||||
config.module.rules.push({
|
||||
test: /mocha\/mocha\.js$/,
|
||||
use: await flecks.resolver.resolve('@flecks/web/build/fix-mocha-critical-dependency'),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ const WaitForManifestPlugin = require('./wait-for-manifest');
|
|||
|
||||
const {
|
||||
FLECKS_CORE_ROOT = process.cwd(),
|
||||
NODE_ENV,
|
||||
} = process.env;
|
||||
|
||||
module.exports = async (env, argv, flecks) => {
|
||||
|
@ -70,12 +71,6 @@ module.exports = async (env, argv, flecks) => {
|
|||
entry: '@flecks/web/server/build/entry',
|
||||
}],
|
||||
];
|
||||
if (!isProduction) {
|
||||
entries.push(['tests', {
|
||||
entry: '@flecks/web/server/build/tests',
|
||||
title: 'Testbed',
|
||||
}]);
|
||||
}
|
||||
await Promise.all(
|
||||
entries
|
||||
.map(async ([name, mainsConfig]) => {
|
||||
|
@ -195,6 +190,7 @@ module.exports = async (env, argv, flecks) => {
|
|||
},
|
||||
optimization: {
|
||||
minimize: isProduction,
|
||||
nodeEnv: NODE_ENV,
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"client.js",
|
||||
"entry.js",
|
||||
"index.js",
|
||||
"runtime.js",
|
||||
|
@ -29,11 +30,13 @@
|
|||
"@babel/parser": "^7.17.0",
|
||||
"@babel/types": "^7.17.0",
|
||||
"@flecks/core": "^4.0.5",
|
||||
"@flecks/server": "^4.0.5",
|
||||
"@webpack-cli/serve": "^2.0.5",
|
||||
"add-asset-html-webpack-plugin": "^6.0.0",
|
||||
"assert": "^2.1.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"before-build-webpack": "^0.2.13",
|
||||
"body-parser": "^1.20.2",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"buffer": "^6.0.3",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
|
@ -42,8 +45,8 @@
|
|||
"express": "^4.17.1",
|
||||
"html-loader": "^4.2.0",
|
||||
"html-webpack-plugin": "^5.5.3",
|
||||
"husky": "^9.0.7",
|
||||
"http-proxy": "^1.17.0",
|
||||
"husky": "^9.0.7",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"mocha": "^10.2.0",
|
||||
"null-loader": "^4.0.1",
|
||||
|
@ -61,6 +64,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@flecks/build": "^4.0.5",
|
||||
"@flecks/fleck": "^4.0.5"
|
||||
"@flecks/fleck": "^4.0.5",
|
||||
"puppeteer": "^22.0.0"
|
||||
}
|
||||
}
|
||||
|
|
24
packages/web/src/client.js
Normal file
24
packages/web/src/client.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
export const mixin = (Flecks) => class FlecksWithWebClient extends Flecks {
|
||||
|
||||
constructor(runtime) {
|
||||
super(runtime);
|
||||
if ('test' !== process.env.NODE_ENV) {
|
||||
return;
|
||||
}
|
||||
this.web = {
|
||||
test: ({payload, type}) => (
|
||||
fetch(
|
||||
`/@flecks/web/testing?type=${type}`,
|
||||
{
|
||||
body: JSON.stringify(payload),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
},
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
};
|
|
@ -3,6 +3,7 @@ import {createServer, ServerResponse} from 'http';
|
|||
import {join} from 'path';
|
||||
|
||||
import {D} from '@flecks/core';
|
||||
import bodyParser from 'body-parser';
|
||||
import compression from 'compression';
|
||||
import express from 'express';
|
||||
import httpProxy from 'http-proxy';
|
||||
|
@ -31,6 +32,8 @@ export const createHttpServer = async (flecks) => {
|
|||
const httpServer = createServer(app);
|
||||
httpServer.app = app;
|
||||
flecks.web.server = httpServer;
|
||||
// Body parser.
|
||||
app.use(bodyParser.json());
|
||||
// Compression. heheh
|
||||
app.use(compression({level: 'production' === NODE_ENV ? 6 : 9}));
|
||||
// Socket connection.
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import {configSource, inlineConfig} from './config';
|
||||
import {createHttpServer} from './http';
|
||||
|
||||
const {
|
||||
NODE_ENV,
|
||||
} = process.env;
|
||||
|
||||
export {configSource};
|
||||
|
||||
export const hooks = {
|
||||
'@flecks/web.routes': (flecks) => [
|
||||
'@flecks/web.routes': (flecks) => {
|
||||
const routes = [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/flecks.config.js',
|
||||
|
@ -13,7 +18,19 @@ export const hooks = {
|
|||
res.send(await configSource(flecks, req));
|
||||
},
|
||||
},
|
||||
],
|
||||
];
|
||||
if ('test' === NODE_ENV) {
|
||||
routes.push({
|
||||
method: 'post',
|
||||
path: '/@flecks/web/testing',
|
||||
middleware: (req, res, next) => {
|
||||
flecks.server.socket.write(JSON.stringify({payload: req.body, type: req.query.type}));
|
||||
next();
|
||||
},
|
||||
});
|
||||
}
|
||||
return routes;
|
||||
},
|
||||
'@flecks/web/server.stream.html': inlineConfig,
|
||||
'@flecks/server.up': (flecks) => createHttpServer(flecks),
|
||||
};
|
||||
|
|
41
packages/web/test/client/up.js
Normal file
41
packages/web/test/client/up.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import {expect} from 'chai';
|
||||
|
||||
import {withWeb} from '../helpers/with-web';
|
||||
|
||||
let report;
|
||||
|
||||
const options = {
|
||||
beforeConnect: ({socket}) => {
|
||||
report = socket.waitForAction('report');
|
||||
},
|
||||
};
|
||||
|
||||
it('brings a client up', withWeb(
|
||||
async function test({
|
||||
browser,
|
||||
page,
|
||||
response,
|
||||
}) {
|
||||
expect(response)
|
||||
.to.not.be.null;
|
||||
const {
|
||||
payload: {
|
||||
config,
|
||||
id,
|
||||
request,
|
||||
},
|
||||
} = await report;
|
||||
const appMountSelector = await page.waitForSelector(`#${id}`);
|
||||
expect(await appMountSelector?.evaluate((el) => el.textContent))
|
||||
.to.equal('hello world');
|
||||
const yepSelector = await page.waitForSelector(`.${request}`);
|
||||
expect(await yepSelector?.evaluate((el) => el.textContent))
|
||||
.to.equal('YEP');
|
||||
expect(config)
|
||||
.to.deep.equal({why: 'hello there'});
|
||||
expect(request)
|
||||
.to.equal('testing-value-value');
|
||||
await browser.close();
|
||||
},
|
||||
options,
|
||||
));
|
69
packages/web/test/helpers/with-web.js
Normal file
69
packages/web/test/helpers/with-web.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {startServer} from '@flecks/server/test/server/build/build';
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
export async function connectBrowser(url, options = {}) {
|
||||
let previousTimeout;
|
||||
const start = Date.now();
|
||||
if (options.task) {
|
||||
previousTimeout = options.task.timeout();
|
||||
options.task.timeout(0);
|
||||
}
|
||||
const {timeout = 30000} = options;
|
||||
const browser = await puppeteer.launch({
|
||||
// For CI.
|
||||
args: ['--no-sandbox'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
let response;
|
||||
const handle = setTimeout(() => {
|
||||
throw new Error(`timed out trying to connect browser to '${url}'!`);
|
||||
}, timeout);
|
||||
/* eslint-disable no-await-in-loop */
|
||||
while (!response) {
|
||||
try {
|
||||
response = await page.goto(url, {...options, timeout: timeout - (Date.now() - start)});
|
||||
if (response) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 250);
|
||||
});
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-await-in-loop */
|
||||
clearTimeout(handle);
|
||||
options.task?.timeout(previousTimeout + (Date.now() - start));
|
||||
return {browser, page, response};
|
||||
}
|
||||
|
||||
export function withWeb(task, options) {
|
||||
return async function withWeb() {
|
||||
const server = await startServer({...options, task: this});
|
||||
const socket = await server.waitForSocket({...options, task: this});
|
||||
if (options.beforeConnect) {
|
||||
await options.beforeConnect({server, socket});
|
||||
}
|
||||
const start = Date.now();
|
||||
const previousTimeout = this.timeout();
|
||||
this.timeout(0);
|
||||
const {payload: config} = await socket.send({type: 'config.get', payload: '@flecks/web'});
|
||||
this.timeout(previousTimeout + (Date.now() - start));
|
||||
const {browser, page, response} = await connectBrowser(
|
||||
// @todo schema
|
||||
`http://${config.public}`,
|
||||
{
|
||||
...options,
|
||||
task: this,
|
||||
},
|
||||
);
|
||||
return task({
|
||||
browser,
|
||||
page,
|
||||
response,
|
||||
server,
|
||||
socket,
|
||||
});
|
||||
};
|
||||
}
|
4
packages/web/test/server/template/build/flecks.yml
Normal file
4
packages/web/test/server/template/build/flecks.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
'@flecks/core': {}
|
||||
'@flecks/server': {}
|
||||
'@flecks/web': {}
|
||||
'test:./test': {}
|
1
packages/web/test/server/template/package.json
Normal file
1
packages/web/test/server/template/package.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
1
packages/web/test/server/template/test/package.json
Normal file
1
packages/web/test/server/template/test/package.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
13
packages/web/test/server/template/test/src/client.js
Normal file
13
packages/web/test/server/template/test/src/client.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export const hooks = {
|
||||
'@flecks/web/client.up': async (container, flecks) => {
|
||||
container.innerHTML = 'hello world';
|
||||
await flecks.web.test({
|
||||
type: 'report',
|
||||
payload: {
|
||||
id: flecks.get('@flecks/web.appMountId'),
|
||||
config: flecks.get('test'),
|
||||
env: process.env.NODE_ENV,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
5
packages/web/test/server/template/test/src/index.js
Normal file
5
packages/web/test/server/template/test/src/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export const hooks = {
|
||||
'@flecks/web.config': () => ({
|
||||
why: 'hello there',
|
||||
}),
|
||||
};
|
18
packages/web/test/server/template/test/src/server.js
Normal file
18
packages/web/test/server/template/test/src/server.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {Readable} from 'stream';
|
||||
|
||||
import {pipesink} from '@flecks/core/server';
|
||||
|
||||
export const hooks = {
|
||||
'@flecks/web/server.request.route': () => (req, res, next) => {
|
||||
req.body.request += '-value';
|
||||
next();
|
||||
},
|
||||
'@flecks/web/server.request.socket': () => (req, res, next) => {
|
||||
req.body.request = 'testing-value';
|
||||
next();
|
||||
},
|
||||
'@flecks/web/server.stream.html': async (stream, req) => {
|
||||
const html = (await pipesink(stream)).toString();
|
||||
return Readable.from(html.replace('<body>', `<body><p class="${req.body.request}">YEP</p>`));
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue
Block a user