feat: require.context in bootstrap scripts

fix: test running
chore: task coloring
refactor: hooks
This commit is contained in:
cha0s 2024-02-14 06:34:29 -06:00
parent 389316195e
commit 66b4a95cf0
36 changed files with 639 additions and 804 deletions

View File

@ -2,6 +2,7 @@ const {join, relative} = require('path');
const {PassThrough} = require('stream');
const {pipesink, processCode, spawnWith} = require('@flecks/core/src/server');
const chalk = require('chalk');
const {glob} = require('glob');
const concurrent = require('./concurrent');
@ -27,10 +28,15 @@ const {workspaces} = require(join(FLECKS_CORE_ROOT, 'package.json'));
const stdio = new PassThrough();
const buffer = pipesink(child.stderr.pipe(child.stdout.pipe(stdio)));
const code = await processCode(child);
console.log(`::group::@flecks/${relative(join(FLECKS_CORE_ROOT, 'packages'), cwd)}`);
console.log(
`::group::@flecks/${
chalk.blue(relative(join(FLECKS_CORE_ROOT, 'packages'), cwd))
} ${0 === code ? chalk.green('passed') : chalk.red('failed')}`,
);
process.stdout.write(await buffer);
console.log('::endgroup::');
console.log('::endgroup::\n');
return code;
},
);
console.log(0 === process.exitCode ? chalk.green('success') : chalk.red('failure'), '\n');
})();

866
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,13 +13,14 @@
"gh-pages": "cd gh-pages && git add . && git commit -m $(git -C ../website rev-parse HEAD) && git push origin gh-pages",
"lint": "node build/tasks npm run -- lint",
"publish": "node build/publish --provenance",
"test": "node build/tasks npm run -- test",
"test": "MOCHA_COLORS=1 node build/tasks npm run -- test",
"verify": "act -j test --matrix node-version:20.x"
},
"workspaces": [
"packages/*"
],
"devDependencies": {
"@npmcli/arborist": "^7.3.1"
"@npmcli/arborist": "^7.3.1",
"chalk": "^4.1.2"
}
}

View File

@ -1,3 +1,16 @@
const {addHook} = require('pirates');
// Hack in `require.context`.
addHook(
(code) => (
code.replaceAll('require.context', "require('@flecks/core/build/flecks').Flecks.context")
),
{
exts: ['.js'],
ignoreNodeModules: false,
matcher: (request) => request.match(/flecks\.bootstrap\.js$/),
},
);
const {join} = require('path');
const D = require('@flecks/core/build/debug');

View File

@ -1,71 +1,3 @@
const {join} = require('path');
const {Flecks} = require('@flecks/core/build/flecks');
const webpack = require('webpack');
const {commands} = require('./commands');
const {ProcessAssets} = require('./process-assets');
const {externals} = require('./webpack');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
exports.hooks = {
'@flecks/build.extensions': () => ['.mjs', '.js', '.json', '.wasm'],
'@flecks/build.commands': commands,
'@flecks/build.config': async (target, config, env, argv, flecks) => {
if (flecks.get('@flecks/build.profile').includes(target)) {
config.plugins.push(
new webpack.debug.ProfilingPlugin({
outputPath: join(FLECKS_CORE_ROOT, `profile.build-${target}.json`),
}),
);
}
if (flecks.roots.some(([path, request]) => path !== request)) {
config.resolve.symlinks = false;
}
config.plugins.push(new ProcessAssets(target, flecks));
},
'@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)),
});
}
},
'@flecks/build.files': () => [
/**
* Babel configuration. See: https://babeljs.io/docs/en/config-files
*/
'babel.config.js',
/**
* ESLint defaults. The generated `eslint.config.js` just reads from this file so that the
* build can dynamically configure parts of ESLint.
*/
'default.eslint.config.js',
/**
* ESLint configuration managed by flecks to allow async.
*/
'eslint.config.js',
/**
* Flecks webpack configuration. See: https://webpack.js.org/configuration/
*/
'fleckspack.config.js',
/**
* Fleck source build configuration. See: https://webpack.js.org/configuration/
*/
'fleck.webpack.config.js',
/**
* Fleck test build configuration. See: https://webpack.js.org/configuration/
*/
'test.webpack.config.js',
],
'@flecks/core.config': () => ({
/**
* Build targets to profile with `webpack.debug.ProfilingPlugin`.
*/
profile: [],
}),
};
exports.hooks = Flecks.hooks(require.context('./hooks'));

View File

@ -96,7 +96,7 @@ async function rootsDependencies(roots, resolver) {
);
}
exports.commands = (program, flecks) => {
exports.hook = (program, flecks) => {
const commands = {
add: {
args: [

View File

@ -0,0 +1,11 @@
const {externals} = require('../../webpack');
exports.hook = 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)),
});
}
};

View File

@ -0,0 +1,23 @@
const {join} = require('path');
const webpack = require('webpack');
const {ProcessAssets} = require('../../process-assets');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
exports.hook = async (target, config, env, argv, flecks) => {
if (flecks.get('@flecks/build.profile').includes(target)) {
config.plugins.push(
new webpack.debug.ProfilingPlugin({
outputPath: join(FLECKS_CORE_ROOT, `profile.build-${target}.json`),
}),
);
}
if (flecks.roots.some(([path, request]) => path !== request)) {
config.resolve.symlinks = false;
}
config.plugins.push(new ProcessAssets(target, flecks));
};

View File

@ -0,0 +1 @@
exports.hook = () => ['.mjs', '.js', '.json', '.wasm'];

View File

@ -0,0 +1,27 @@
exports.hook = () => [
/**
* Babel configuration. See: https://babeljs.io/docs/en/config-files
*/
'babel.config.js',
/**
* ESLint defaults. The generated `eslint.config.js` just reads from this file so that the
* build can dynamically configure parts of ESLint.
*/
'default.eslint.config.js',
/**
* ESLint configuration managed by flecks to allow async.
*/
'eslint.config.js',
/**
* Flecks webpack configuration. See: https://webpack.js.org/configuration/
*/
'fleckspack.config.js',
/**
* Fleck source build configuration. See: https://webpack.js.org/configuration/
*/
'fleck.webpack.config.js',
/**
* Fleck test build configuration. See: https://webpack.js.org/configuration/
*/
'test.webpack.config.js',
];

View File

@ -0,0 +1,6 @@
exports.hook = () => ({
/**
* Build targets to profile with `webpack.debug.ProfilingPlugin`.
*/
profile: [],
});

View File

@ -54,6 +54,7 @@
"globals": "^13.23.0",
"graceful-fs": "^4.2.11",
"mocha": "^10.2.0",
"pirates": "^4.0.6",
"precinct": "^11.0.5",
"rimraf": "^5.0.5",
"source-map-loader": "4.0.1",

View File

@ -0,0 +1,34 @@
const {join} = require('path');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
exports.hook = (path, config, flecks) => {
if (path !== join(FLECKS_CORE_ROOT, 'build', 'flecks.yml')) {
return;
}
const dealiasedConfig = flecks.constructor.dealiasedConfig(config);
if (
JSON.stringify(Object.keys(flecks.originalConfig).sort())
!== JSON.stringify(Object.keys(dealiasedConfig).sort())
) {
throw new Error('build manifest keys changed!');
}
Object.entries(dealiasedConfig)
.forEach(([fleck, value]) => {
if (JSON.stringify(flecks.originalConfig[fleck]) !== JSON.stringify(value)) {
const fleckList = flecks.flecksImplementing('@flecks/core.reload');
for (let i = 0; i < fleckList.length; ++i) {
try {
flecks.invokeFleck('@flecks/core.reload', fleckList[i], fleck, value);
}
catch (error) {
throw new Error(`'${fleck}' aborted reload: ${error.name}: ${error.message}`);
}
}
flecks.originalConfig[fleck] = value;
flecks.configureFleckDefaults(fleck);
}
});
};

View File

@ -0,0 +1,3 @@
exports.hook = async (req, flecks) => ({
id: flecks.get('@flecks/core.id'),
});

View File

@ -1,48 +1,11 @@
/* eslint-disable global-require */
const {join} = require('path');
const {Flecks} = require('../build/flecks');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
export {default as Class} from '../build/class';
export {default as compose} from '../build/compose';
export {default as D} from '../build/debug';
export {default as EventEmitter} from '../build/event-emitter';
export * from '../build/flecks';
module.exports = {
Class: require('../build/class'),
compose: require('../build/compose'),
D: require('../build/debug'),
EventEmitter: require('../build/event-emitter'),
...require('../build/flecks'),
hooks: {
'@flecks/core.hmr': (path, config, flecks) => {
if (path !== join(FLECKS_CORE_ROOT, 'build', 'flecks.yml')) {
return;
}
const dealiasedConfig = flecks.constructor.dealiasedConfig(config);
if (
JSON.stringify(Object.keys(flecks.originalConfig).sort())
!== JSON.stringify(Object.keys(dealiasedConfig).sort())
) {
throw new Error('build manifest keys changed!');
}
Object.entries(dealiasedConfig)
.forEach(([fleck, value]) => {
if (JSON.stringify(flecks.originalConfig[fleck]) !== JSON.stringify(value)) {
const fleckList = flecks.flecksImplementing('@flecks/core.reload');
for (let i = 0; i < fleckList.length; ++i) {
try {
flecks.invokeFleck('@flecks/core.reload', fleckList[i], fleck, value);
}
catch (error) {
throw new Error(`'${fleck}' aborted reload: ${error.name}: ${error.message}`);
}
}
flecks.originalConfig[fleck] = value;
flecks.configureFleckDefaults(fleck);
}
});
},
'@flecks/web.config': async (req, flecks) => ({
id: flecks.get('@flecks/core.id'),
}),
},
};
export const hooks = Flecks.hooks(require.context('./hooks'));

View File

@ -0,0 +1,7 @@
import register from '@flecks/db/server/register';
export const hook = (gathered, hook, flecks) => {
if ('@flecks/db.models' === hook) {
register(gathered, flecks.db.sequelize);
}
};

View File

@ -0,0 +1,3 @@
import containers from '@flecks/db/build/containers';
export const hook = containers;

View File

@ -0,0 +1,10 @@
import {Flecks} from '@flecks/core';
import {createDatabaseConnection} from '@flecks/db/server/connection';
export const hook = Flecks.priority(
async (flecks) => {
flecks.db.sequelize = await createDatabaseConnection(flecks);
},
{after: '@flecks/docker/server'},
);

View File

@ -1,9 +1,5 @@
import {Flecks} from '@flecks/core';
import containers from '../build/containers';
import {createDatabaseConnection} from './connection';
import register from './register';
export {
DataTypes as Types,
Op,
@ -13,22 +9,7 @@ export {
export {default as Model} from './model';
export {createDatabaseConnection};
export const hooks = {
'@flecks/core.hmr.gathered': (gathered, hook, flecks) => {
if ('@flecks/db.models' === hook) {
register(gathered, flecks.db.sequelize);
}
},
'@flecks/docker.containers': containers,
'@flecks/server.up': Flecks.priority(
async (flecks) => {
flecks.db.sequelize = await createDatabaseConnection(flecks);
},
{after: '@flecks/docker/server'},
),
};
export const hooks = Flecks.hooks(require.context('./hooks'));
export const mixin = (Flecks) => class FlecksWithDb extends Flecks {

View File

@ -1,4 +1,4 @@
import Model from '../../../src/model';
import Model from '@flecks/db/server/model';
export const hooks = {
'@flecks/db.models': () => ({

View File

@ -1,9 +1,9 @@
const {access} = require('fs/promises');
const {join} = require('path');
const {commands: coreCommands} = require('@flecks/build/build/commands');
const {hook: coreCommands} = require('@flecks/build/build/hooks/@flecks/build.commands');
const {rimraf} = require('@flecks/build/src/server');
const {D} = require('@flecks/core/src');
const D = require('@flecks/core/build/debug');
const {glob, pipesink, processCode} = require('@flecks/core/src/server');
const Mocha = require('mocha');
const {watchParallelRun} = require('mocha/lib/cli/watch-run');
@ -23,8 +23,7 @@ module.exports = (program, flecks) => {
],
options: [
program.createOption('-d, --no-production', 'dev build'),
program.createOption('-p, --platform [platforms...]', 'platforms to test')
.default(['default', 'server']),
program.createOption('-p, --platform [platforms...]', 'platforms to test'),
program.createOption('-t, --timeout <ms>', 'timeout').default(2000),
program.createOption('-v, --verbose', 'verbose output'),
program.createOption('-w, --watch', 'watch for changes'),
@ -44,6 +43,9 @@ module.exports = (program, flecks) => {
const {build} = coreCommands(program, flecks);
// Check for work.
const [env, argv] = [{}, {mode: production ? 'production' : 'development'}];
if (platforms) {
process.env.FLECKS_CORE_TEST_PLATFORMS = JSON.stringify(platforms);
}
const filename = await flecks.resolveBuildConfig('test.webpack.config.js', '@flecks/build');
const config = {test: await require(filename)(env, argv, flecks)};
await flecks.configureBuilds(config, env, argv);
@ -57,7 +59,7 @@ module.exports = (program, flecks) => {
'test',
{
env: {
FLECKS_CORE_TEST_PLATFORMS: JSON.stringify(platforms),
FLECKS_CORE_TEST_PLATFORMS: platforms && JSON.stringify(platforms),
FORCE_COLOR: 'dumb' !== TERM,
},
production,

View File

@ -3,7 +3,7 @@ const {readdir} = require('fs/promises');
const {tmpdir} = require('os');
const {join} = require('path');
const {D} = require('@flecks/core/src');
const D = require('@flecks/core/build/debug');
const commandExists = require('command-exists');
const debug = D('@flecks/repl/commands');

View File

@ -1,61 +1,5 @@
const {banner} = require('@flecks/build/src/server');
const {Flecks} = require('@flecks/core/build/flecks');
exports.dependencies = ['@flecks/build'];
exports.hooks = {
'@flecks/build.config.alter': ({server}, env, argv, flecks) => {
if (server) {
const resolver = JSON.stringify({
alias: server.resolve.alias,
fallback: server.resolve.fallback,
});
const stubs = JSON.stringify(flecks.stubs);
if ('{"alias":{},"fallback":{}}' !== resolver || '[]' !== stubs) {
server.plugins.push(
banner({
// `require()` magic.
banner: `require('@flecks/core/build/resolve')(${resolver}, ${stubs})`,
include: 'index.js',
}),
);
}
}
},
'@flecks/build.files': () => [
/**
* Server build configuration. See: https://webpack.js.org/configuration/
*/
'server.webpack.config.js',
],
'@flecks/build.targets': () => ['server'],
'@flecks/build.targets.alter': (targets) => {
// Don't build if there's a fleck target.
if (targets.has('fleck')) {
targets.delete('server');
}
},
'@flecks/core.config': () => ({
/**
* Whether HMR is enabled.
*/
hot: false,
/**
* Arguments to pass along to node. See: https://nodejs.org/api/cli.html
*/
nodeArgs: [],
/**
* Environment to pass along to node. See: https://nodejs.org/api/cli.html#environment-variables
*/
nodeEnv: {},
/**
* Whether to start the server after building.
*/
start: true,
/**
* Webpack stats configuration.
*/
stats: {
preset: 'minimal',
},
}),
};
exports.hooks = Flecks.hooks(require.context('./hooks'));

View File

@ -0,0 +1,20 @@
const {banner} = require('@flecks/build/src/server');
exports.hook = ({server}, env, argv, flecks) => {
if (server) {
const resolver = JSON.stringify({
alias: server.resolve.alias,
fallback: server.resolve.fallback,
});
const stubs = JSON.stringify(flecks.stubs);
if ('{"alias":{},"fallback":{}}' !== resolver || '[]' !== stubs) {
server.plugins.push(
banner({
// `require()` magic.
banner: `require('@flecks/core/build/resolve')(${resolver}, ${stubs})`,
include: 'index.js',
}),
);
}
}
};

View File

@ -0,0 +1,6 @@
exports.hook = () => [
/**
* Server build configuration. See: https://webpack.js.org/configuration/
*/
'server.webpack.config.js',
];

View File

@ -0,0 +1,6 @@
exports.hook = (targets) => {
// Don't build if there's a fleck target.
if (targets.has('fleck')) {
targets.delete('server');
}
};

View File

@ -0,0 +1 @@
exports.hook = () => ['server'];

View File

@ -0,0 +1,24 @@
exports.hook = () => ({
/**
* Whether HMR is enabled.
*/
hot: false,
/**
* Arguments to pass along to node. See: https://nodejs.org/api/cli.html
*/
nodeArgs: [],
/**
* Environment to pass along to node. See: https://nodejs.org/api/cli.html#environment-variables
*/
nodeEnv: {},
/**
* Whether to start the server after building.
*/
start: true,
/**
* Webpack stats configuration.
*/
stats: {
preset: 'minimal',
},
});

View File

@ -1,59 +0,0 @@
import cluster from 'cluster';
import {createConnection} from 'net';
const {
FLECKS_SERVER_TEST_SOCKET,
NODE_ENV,
} = process.env;
export const hooks = {
'@flecks/core.hmr.hook': (hook) => {
if ('@flecks/server.up' === hook) {
if (cluster.isWorker) {
cluster.worker.disconnect();
const error = new Error('@flecks/server.up implementation changed!');
error.stack = '';
throw error;
}
}
},
'@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 = {};
}
};

View File

@ -0,0 +1,12 @@
import cluster from 'cluster';
export const hook = (hook) => {
if ('@flecks/server.up' === hook) {
if (cluster.isWorker) {
cluster.worker.disconnect();
const error = new Error('@flecks/server.up implementation changed!');
error.stack = '';
throw error;
}
}
};

View File

@ -0,0 +1,38 @@
import cluster from 'cluster';
import {createConnection} from 'net';
const {
FLECKS_SERVER_TEST_SOCKET,
NODE_ENV,
} = process.env;
export const hook = (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:
}
});
});
};

View File

@ -0,0 +1,12 @@
import {Flecks} from '@flecks/core';
export const hooks = Flecks.hooks(require.context('./hooks'));
export const mixin = (Flecks) => class FlecksWithServer extends Flecks {
constructor(runtime) {
super(runtime);
this.server = {};
}
};

View File

@ -14,6 +14,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const {
FLECKS_CORE_ROOT = process.cwd(),
FLECKS_CORE_TEST_PLATFORMS,
} = process.env;
const tests = join(FLECKS_CORE_ROOT, 'test');
@ -36,14 +37,20 @@ exports.hooks = {
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,
];
});
if (
FLECKS_CORE_TEST_PLATFORMS
? JSON.parse(FLECKS_CORE_TEST_PLATFORMS).includes('client')
: true
) {
(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': {