refactor: bootstrap

This commit is contained in:
cha0s 2024-01-16 00:28:20 -06:00
parent 5d2bc3ee07
commit 757cd89e99
157 changed files with 2116 additions and 2509 deletions

View File

@ -20,6 +20,10 @@
"@flecks/electron": "*",
"@flecks/fleck": "*",
"@flecks/governor": "*",
"@flecks/passport": "*",
"@flecks/passport-local": "*",
"@flecks/passport-local-react": "*",
"@flecks/passport-react": "*",
"@flecks/react": "*",
"@flecks/redis": "*",
"@flecks/redux": "*",
@ -27,10 +31,6 @@
"@flecks/server": "*",
"@flecks/session": "*",
"@flecks/socket": "*",
"@flecks/passport": "*",
"@flecks/passport-local": "*",
"@flecks/passport-local-react": "*",
"@flecks/passport-react": "*",
"@flecks/web": "*"
},
"dependencies": {

View File

@ -0,0 +1,19 @@
const {readFile, writeFile} = require('fs/promises');
const {
join,
sep,
} = require('path');
const {dump: dumpYml, load: loadYml} = require('js-yaml');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
module.exports = async (fleck, path) => {
const key = [fleck].concat(path ? `.${sep}${join('packages', path, 'src')}` : []).join(':');
const ymlPath = join(FLECKS_CORE_ROOT, 'build', 'flecks.yml');
let yml = loadYml(await readFile(ymlPath));
yml = Object.fromEntries(Object.entries(yml).concat([[key, {}]]));
await writeFile(ymlPath, dumpYml(yml, {sortKeys: true}));
};

View File

@ -0,0 +1 @@
module.exports = class {};

67
packages/core/build/cli.js Executable file
View File

@ -0,0 +1,67 @@
#!/usr/bin/env node
const {Command} = require('commander');
const {processCode} = require('./commands');
const D = require('./debug');
const Server = require('./server');
const debug = D('@flecks/core/cli');
const debugSilly = debug.extend('silly');
// Asynchronous command process code forwarding.
const forwardProcessCode = (fn) => async (...args) => {
const child = await fn(...args);
if ('object' !== typeof child) {
const code = 'undefined' !== typeof child ? child : 0;
debugSilly('action returned code %d', code);
process.exitCode = code;
return;
}
try {
const code = await processCode(child);
debugSilly('action exited with code %d', code);
process.exitCode = code;
}
catch (error) {
// eslint-disable-next-line no-console
console.error(error);
process.exitCode = child.exitCode || 1;
}
};
// Initialize Commander.
const program = new Command();
program
.enablePositionalOptions()
.name('flecks')
.usage('[command] [...]');
// Bootstrap.
(async () => {
debugSilly('bootstrapping flecks...');
const flecks = await Server.from();
debugSilly('bootstrapped');
// Register commands.
const commands = flecks.invokeMerge('@flecks/core.commands', program);
const keys = Object.keys(commands).sort();
for (let i = 0; i < keys.length; ++i) {
const {
action,
args = [],
description,
name = keys[i],
options = [],
} = commands[keys[i]];
debugSilly('adding command %s...', name);
const cmd = program.command(name);
cmd.description(description);
for (let i = 0; i < args.length; ++i) {
cmd.addArgument(args[i]);
}
for (let i = 0; i < options.length; ++i) {
cmd.option(...options[i]);
}
cmd.action(forwardProcessCode(action));
}
// Parse commandline.
program.parse(process.argv);
})();

View File

@ -1,13 +1,12 @@
import {spawn} from 'child_process';
import {join, normalize} from 'path';
const {spawn} = require('child_process');
const {join, normalize} = require('path');
import {Argument} from 'commander';
import {glob} from 'glob';
import flatten from 'lodash.flatten';
import rimraf from 'rimraf';
const {Argument, Option, program} = require('commander');
const {glob} = require('glob');
const rimraf = require('rimraf');
import D from '../debug';
import Flecks from './flecks';
const D = require('./debug');
const addFleckToYml = require('./add-fleck-to-yml');
const {
FLECKS_CORE_ROOT = process.cwd(),
@ -17,32 +16,8 @@ const debug = D('@flecks/core/commands');
const debugSilly = debug.extend('silly');
const flecksRoot = normalize(FLECKS_CORE_ROOT);
export {Argument, Option, program} from 'commander';
export const processCode = (child) => new Promise((resolve, reject) => {
child.on('error', reject);
child.on('exit', (code) => {
child.off('error', reject);
resolve(code);
});
});
export const spawnWith = (cmd, opts = {}) => {
debug("spawning: '%s'", cmd.join(' '));
debugSilly('with options: %O', opts);
const child = spawn(cmd[0], cmd.slice(1), {
stdio: 'inherit',
...opts,
env: {
...process.env,
...opts.env,
},
});
return child;
};
export default (program, flecks) => {
const {packageManager} = flecks.get('@flecks/core/server');
exports.commands = (program, flecks) => {
const {packageManager} = flecks.get('@flecks/core');
const commands = {
add: {
args: [
@ -58,8 +33,8 @@ export default (program, flecks) => {
args.push(packageManager, ['install', fleck]);
}
args.push({stdio: 'inherit'});
await processCode(spawn(...args));
await Flecks.addFleckToYml(fleck);
await module.exports.processCode(spawn(...args));
await addFleckToYml(fleck);
},
},
clean: {
@ -83,11 +58,11 @@ export default (program, flecks) => {
},
},
};
const targets = flatten(flecks.invokeFlat('@flecks/core.targets'));
const {targets} = flecks;
if (targets.length > 0) {
commands.build = {
args: [
new Argument('[target]', 'build target').choices(targets),
new Argument('[target]', 'build target').choices(targets.map(([, target]) => target)),
],
options: [
['-d, --no-production', 'dev build'],
@ -95,21 +70,21 @@ export default (program, flecks) => {
['-w, --watch', 'watch for changes'],
],
description: 'build a target in your application',
action: (target, opts) => {
action: async (target, opts) => {
const {
hot,
production,
watch,
} = opts;
debug('Building...', opts);
const webpackConfig = flecks.buildConfig('fleckspack.config.js');
const webpackConfig = await flecks.resolveBuildConfig('fleckspack.config.js');
const cmd = [
'npx', 'webpack',
'--config', webpackConfig,
'--mode', (production && !hot) ? 'production' : 'development',
...((watch || hot) ? ['--watch'] : []),
];
return spawnWith(
return module.exports.spawnWith(
cmd,
{
env: {
@ -131,28 +106,30 @@ export default (program, flecks) => {
if (0 === packages.length) {
packages.push('.');
}
packages
.map((pkg) => join(process.cwd(), pkg))
.forEach((cwd) => {
const cmd = [
'npx', 'eslint',
'--config', flecks.buildConfig('eslint.config.js'),
'.',
];
promises.push(new Promise((resolve, reject) => {
const child = spawnWith(
cmd,
{
cwd,
},
);
child.on('error', reject);
child.on('exit', (code) => {
child.off('error', reject);
resolve(code);
});
}));
});
await Promise.all(
packages
.map((pkg) => join(process.cwd(), pkg))
.map(async (cwd) => {
const cmd = [
'npx', 'eslint',
'--config', await flecks.resolveBuildConfig('eslint.config.js'),
'.',
];
promises.push(new Promise((resolve, reject) => {
const child = module.exports.spawnWith(
cmd,
{
cwd,
},
);
child.on('error', reject);
child.on('exit', (code) => {
child.off('error', reject);
resolve(code);
});
}));
}),
);
const promise = Promise.all(promises)
.then(
(codes) => (
@ -176,3 +153,29 @@ export default (program, flecks) => {
};
return commands;
};
exports.processCode = (child) => new Promise((resolve, reject) => {
child.on('error', reject);
child.on('exit', (code) => {
child.off('error', reject);
resolve(code);
});
});
exports.spawnWith = (cmd, opts = {}) => {
debug("spawning: '%s'", cmd.join(' '));
debugSilly('with options: %O', opts);
const child = spawn(cmd[0], cmd.slice(1), {
stdio: 'inherit',
...opts,
env: {
...process.env,
...opts.env,
},
});
return child;
};
exports.Argument = Argument;
exports.Option = Option;
exports.program = program;

View File

@ -1,4 +1,4 @@
export default function compose(...funcs) {
module.exports = function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg;
}
@ -6,4 +6,4 @@ export default function compose(...funcs) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
};

View File

@ -0,0 +1,4 @@
const defaultConfigFn = require('./default.eslint.config');
const Server = require('./server');
module.exports = defaultConfigFn(Server.from());

View File

@ -0,0 +1,10 @@
const Server = require('./server');
const configFn = require('./fleck.webpack.config');
const {executable} = require('./webpack');
module.exports = async (env, argv) => {
const flecks = await Server.from();
const config = await configFn(env, argv, flecks);
config.plugins.push(...executable());
return config;
};

View File

@ -0,0 +1,74 @@
const globals = require('globals');
module.exports = async (flecks) => ({
extends: [
require.resolve('eslint-config-airbnb'),
require.resolve('eslint-config-airbnb/hooks'),
],
globals: {
...globals.browser,
...globals.es2021,
...globals.mocha,
...globals.node,
__non_webpack_require__: true,
},
ignorePatterns: [
'dist/**',
// Not even gonna try.
'build/dox/hooks.js',
],
overrides: [
{
files: [
'build/**/*.js',
],
rules: {
'import/no-dynamic-require': 'off',
'global-require': 'off',
},
},
{
files: [
'test/**/*.js',
],
rules: {
'brace-style': 'off',
'class-methods-use-this': 'off',
'import/no-extraneous-dependencies': 'off',
'import/no-unresolved': 'off',
'max-classes-per-file': 'off',
'no-new': 'off',
'no-unused-expressions': 'off',
'padded-blocks': 'off',
},
},
],
parser: require.resolve('@babel/eslint-parser'),
parserOptions: {
requireConfigFile: false,
babelOptions: await flecks.babel(),
},
plugins: ['@babel'],
rules: {
'brace-style': ['error', 'stroustrup'],
// Bug: https://github.com/import-js/eslint-plugin-import/issues/2181
'import/no-import-module-exports': 'off',
'import/prefer-default-export': 'off',
'jsx-a11y/control-has-associated-label': ['error', {assert: 'either'}],
'jsx-a11y/label-has-associated-control': ['error', {assert: 'either'}],
'no-param-reassign': ['error', {props: false}],
'no-plusplus': 'off',
'no-shadow': 'off',
'object-curly-spacing': 'off',
'padded-blocks': ['error', {classes: 'always'}],
yoda: 'off',
},
settings: {
'import/resolver': {
node: {},
},
react: {
version: '18.2.0',
},
},
});

View File

@ -87,4 +87,4 @@ class Digraph {
}
export default Digraph;
module.exports = Digraph;

View File

@ -68,7 +68,7 @@ Have fun!
## Resolution order 🤔
The flecks server provides an interface (`flecks.buildConfig()`) for gathering configuration files
The flecks server provides an interface (`flecks.resolveBuildConfig()`) for gathering configuration files
from the `build` directory. The resolution order is determined by a few variables:
- `filename` specifies the name of the configuration file, e.g. `server.webpack.config.js`.

View File

@ -1,3 +1,71 @@
const defaultConfigFn = require('../src/server/build/default.eslint.config');
const {spawnSync} = require('child_process');
const {
mkdirSync,
readFileSync,
statSync,
writeFileSync,
} = require('fs');
const {join} = require('path');
module.exports = defaultConfigFn();
const D = require('./debug');
const Server = require('./server');
const debug = D('@flecks/core/build/eslint.config.js');
const {
FLECKS_CORE_ROOT = process.cwd(),
FLECKS_CORE_SYNC_FOR_ESLINT = false,
} = process.env;
// This is kinda nuts, but ESLint doesn't support its configuration files returning a promise!
if (FLECKS_CORE_SYNC_FOR_ESLINT) {
(async () => {
debug('bootstrapping flecks...');
const flecks = await Server.from();
debug('bootstrapped');
// Load and finalize ESLint configuration.
const eslintConfig = await require(
await flecks.resolveBuildConfig('default.eslint.config.js'),
)(flecks);
const {resolve} = await require(
await flecks.resolveBuildConfig('fleck.webpack.config.js'),
)({}, {mode: 'development'}, flecks);
eslintConfig.settings['import/resolver'].webpack = {config: {resolve}};
// Write it out to stdout.
process.stdout.write(JSON.stringify(eslintConfig, null, 2));
})();
}
else {
// Check cache first.
const cacheDirectory = join(FLECKS_CORE_ROOT, 'node_modules', '.cache', '@flecks', 'core');
try {
statSync(join(cacheDirectory, 'eslint.config.json'));
module.exports = JSON.parse(readFileSync(join(cacheDirectory, 'eslint.config.json')).toString());
}
catch (error) {
// Just silly. By synchronously spawning... ourselves, the child can use async.
const {stderr, stdout} = spawnSync('node', [__filename], {
env: {
FLECKS_CORE_SYNC_FOR_ESLINT: true,
NODE_PATH: join(FLECKS_CORE_ROOT, 'node_modules'),
...process.env,
},
});
// eslint-disable-next-line no-console
console.error(stderr.toString());
// Read the JSON written out to stdout.
const json = stdout.toString();
try {
const parsed = JSON.parse(json);
statSync(join(FLECKS_CORE_ROOT, 'node_modules'));
mkdirSync(cacheDirectory, {recursive: true});
// Cache.
writeFileSync(join(cacheDirectory, 'eslint.config.json'), json);
module.exports = parsed;
}
catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}
}

View File

@ -6,7 +6,7 @@ const createListener = (fn, that, type, once) => ({
bound: that ? fn.bind(that) : fn,
});
export default function EventEmitterDecorator(Superclass) {
module.exports = function EventEmitterDecorator(Superclass) {
return class EventEmitter extends Superclass {
@ -123,4 +123,4 @@ export default function EventEmitterDecorator(Superclass) {
};
}
};

View File

@ -0,0 +1,135 @@
const {
join,
resolve,
} = require('path');
module.exports = async function explicate(
maybeAliasedPaths,
{
importer,
platforms = ['server'],
resolver,
root,
},
) {
const descriptors = {};
const seen = {};
const roots = {};
function createDescriptor(maybeAliasedPath) {
const index = maybeAliasedPath.indexOf(':');
return -1 === index
? {
path: maybeAliasedPath,
request: maybeAliasedPath,
}
: {
path: maybeAliasedPath.slice(0, index),
request: resolve(root, maybeAliasedPath.slice(index + 1)),
};
}
async function doExplication(descriptor) {
const {path, request} = descriptor;
if (
platforms
.filter((platform) => platform.startsWith('!'))
.map((platform) => platform.slice(1))
.includes(path.split('/').pop())
) {
return;
}
if (path !== request) {
resolver.addAlias(path, request);
}
descriptors[request] = descriptor;
}
async function getRootDescriptor(descriptor) {
const {path, request} = descriptor;
// Walk up and find the root, if any.
const pathParts = path.split('/');
const requestParts = request.split('/');
let rootDescriptor;
while (pathParts.length > 0 && requestParts.length > 0) {
const candidate = requestParts.join('/');
// eslint-disable-next-line no-await-in-loop
if (await resolver.resolve(join(candidate, 'package.json'))) {
rootDescriptor = {
path: pathParts.join('/'),
request: requestParts.join('/'),
};
break;
}
pathParts.pop();
requestParts.pop();
}
return rootDescriptor;
}
function descriptorsAreTheSame(l, r) {
return (l && !r) || (!l && r) ? false : l.request === r.request;
}
async function explicateDescriptor(descriptor) {
if (descriptors[descriptor.request] || seen[descriptor.request]) {
return;
}
seen[descriptor.request] = true;
const areDescriptorsTheSame = descriptorsAreTheSame(
descriptor,
await getRootDescriptor(descriptor),
);
const resolved = await resolver.resolve(descriptor.request);
if (resolved || areDescriptorsTheSame) {
// eslint-disable-next-line no-use-before-define
await explicateRoot(descriptor);
}
if (!resolved && areDescriptorsTheSame) {
descriptors[descriptor.request] = descriptor;
}
if (resolved) {
await doExplication(descriptor);
}
await Promise.all(
platforms
.filter((platform) => !platform.startsWith('!'))
.map(async (platform) => {
if (await resolver.resolve(join(descriptor.request, platform))) {
return doExplication({
path: join(descriptor.path, platform),
request: join(descriptor.request, platform),
});
}
return undefined;
}),
);
}
async function explicateRoot(descriptor) {
// Walk up and find the root, if any.
const rootDescriptor = await getRootDescriptor(descriptor);
if (!rootDescriptor || roots[rootDescriptor.request]) {
return;
}
const {request} = rootDescriptor;
roots[request] = true;
// Import bootstrap script.
const bootstrapPath = await resolver.resolve(join(request, 'build', 'flecks.bootstrap'));
const bootstrap = bootstrapPath ? importer(bootstrapPath) : {};
roots[request] = bootstrap;
// Explicate dependcies.
const {dependencies = []} = bootstrap;
if (dependencies.length > 0) {
await Promise.all(
dependencies
.map(createDescriptor)
.map(explicateDescriptor),
);
}
await explicateDescriptor(rootDescriptor);
}
await Promise.all(
maybeAliasedPaths
.map(createDescriptor)
.map(explicateDescriptor),
);
return {
descriptors,
roots,
};
};

View File

@ -5,34 +5,28 @@ const {
join,
} = require('path');
const babelmerge = require('babel-merge');
const CopyPlugin = require('copy-webpack-plugin');
const glob = require('glob');
const D = require('../../debug');
const R = require('../../require');
const {defaultConfig, externals, regexFromExtensions} = require('../webpack');
const {defaultConfig, externals, regexFromExtensions} = require('./webpack');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
const debug = D('@flecks/core/server/build/fleck.webpack.config.js');
const debugSilly = debug.extend('silly');
const source = join(FLECKS_CORE_ROOT, 'src');
const tests = join(FLECKS_CORE_ROOT, 'test');
const resolveValidModulePath = (source) => (path) => {
// Does the file resolve as source?
try {
R.resolve(`${source}/${path}`);
require.resolve(`${source}/${path}`);
}
catch (error) {
const ext = extname(path);
// Try the implicit [path]/index[.ext] variation.
try {
R.resolve(`${source}/${dirname(path)}/${basename(path, ext)}/index${ext}`);
require.resolve(`${source}/${dirname(path)}/${basename(path, ext)}/index${ext}`);
}
catch (error) {
return false;
@ -41,8 +35,8 @@ const resolveValidModulePath = (source) => (path) => {
return true;
};
module.exports = (env, argv, flecks) => {
const {name, files = []} = R(join(FLECKS_CORE_ROOT, 'package.json'));
module.exports = async (env, argv, flecks) => {
const {name, files = []} = require(join(FLECKS_CORE_ROOT, 'package.json'));
const config = defaultConfig(flecks, {
externals: externals({importType: 'umd'}),
node: {
@ -86,6 +80,9 @@ module.exports = (env, argv, flecks) => {
alias: {
[name]: source,
},
fallback: {
[name]: FLECKS_CORE_ROOT,
},
},
stats: {
colors: true,
@ -93,30 +90,7 @@ module.exports = (env, argv, flecks) => {
},
target: 'node',
});
const merging = [
{
plugins: ['@babel/plugin-syntax-dynamic-import'],
presets: [
[
'@babel/preset-env',
{
shippedProposals: true,
targets: {
esmodules: true,
node: 'current',
},
},
],
],
},
];
if (flecks) {
merging.push({configFile: flecks.buildConfig('babel.config.js')});
const flecksBabelConfig = flecks.babel();
debugSilly('flecks.config.js: babel: %j', flecksBabelConfig);
merging.push(...flecksBabelConfig.map(([, babel]) => babel));
}
const babelConfig = babelmerge.all(merging);
const babelConfig = await flecks.babel();
const extensionsRegex = regexFromExtensions(config.resolve.extensions);
config.module.rules.push(
{
@ -144,14 +118,12 @@ module.exports = (env, argv, flecks) => {
});
// Test entry.
const testPaths = glob.sync(join(tests, '*.js'));
const platforms = flecks
? flecks.platforms
: ['server'];
const {platforms} = flecks;
for (let i = 0; i < platforms.length; ++i) {
testPaths.push(...glob.sync(join(tests, `platforms/${platforms[i]}/*.js`)));
testPaths.push(...glob.sync(join(tests, platforms[i], '*.js')));
}
if (testPaths.length > 0) {
config.entry.test = testPaths;
config.entry.test = ['source-map-support/register', ...testPaths];
}
return config;
};

View File

@ -0,0 +1,60 @@
const {join} = require('path');
const webpack = require('webpack');
const {commands} = require('./commands');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
exports.hooks = {
'@flecks/core.exts': () => ['.mjs', '.js', '.json', '.wasm'],
'@flecks/core.build': async (target, config, env, argv, flecks) => {
if (flecks.get('@flecks/core.profile').includes(target)) {
config.plugins.push(
new webpack.debug.ProfilingPlugin({
outputPath: join(FLECKS_CORE_ROOT, `profile.build-${target}.json`),
}),
);
}
},
'@flecks/core.build.config': () => [
/**
* 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 build configuration. See: https://webpack.js.org/configuration/
*/
'fleck.webpack.config.js',
],
'@flecks/core.commands': commands,
'@flecks/core.config': () => ({
/**
* The ID of your application.
*/
id: 'flecks',
/**
* The package manager used for tasks.
*/
packageManager: 'npm',
/**
* Build targets to profile with `webpack.debug.ProfilingPlugin`.
*/
profile: [],
}),
};

View File

@ -1,3 +0,0 @@
module.exports = {
exts: ['.mjs', '.js'],
};

View File

@ -1,18 +1,18 @@
// eslint-disable-next-line max-classes-per-file
import {
const {
basename,
dirname,
extname,
join,
} from 'path';
} = require('path');
import get from 'lodash.get';
import set from 'lodash.set';
const get = require('lodash.get');
const set = require('lodash.set');
import compose from './compose';
import D from './debug';
import Digraph from './digraph';
import Middleware from './middleware';
const compose = require('./compose');
const D = require('./debug');
const Digraph = require('./digraph');
const Middleware = require('./middleware');
const debug = D('@flecks/core/flecks');
const debugSilly = debug.extend('silly');
@ -21,8 +21,8 @@ const debugSilly = debug.extend('silly');
const HookPriority = Symbol.for('@flecks/core.hookPriority');
// Symbols for Gathered classes.
export const ById = Symbol.for('@flecks/core.byId');
export const ByType = Symbol.for('@flecks/core.byType');
exports.ById = Symbol.for('@flecks/core.byId');
exports.ByType = Symbol.for('@flecks/core.byType');
/**
* Capitalize a string.
@ -59,7 +59,7 @@ const wrapGathered = (Class, id, idProperty, type, typeProperty) => {
return Subclass;
};
export default class Flecks {
exports.Flecks = class Flecks {
config = {};
@ -69,23 +69,19 @@ export default class 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.
* @param {object} runtime
* @param {object} runtime.config configuration (e.g. loaded from `flecks.yml`).
* @param {object} runtime.flecks fleck modules.
*/
constructor({
config = {},
flecks = {},
platforms = [],
} = {}) {
const emptyConfigForAllFlecks = Object.fromEntries(
Object.keys(flecks).map((path) => [path, {}]),
);
this.config = {...emptyConfigForAllFlecks, ...config};
this.platforms = platforms;
const entries = Object.entries(flecks);
debugSilly('paths: %O', entries.map(([fleck]) => fleck));
for (let i = 0; i < entries.length; i++) {
@ -191,7 +187,6 @@ export default class Flecks {
this.config = {};
this.hooks = {};
this.flecks = {};
this.platforms = [];
}
/**
@ -206,17 +201,9 @@ export default class Flecks {
}
const flecks = this.lookupFlecks(hook);
let expanded = [];
// Expand configured flecks.
for (let i = 0; i < flecks.length; ++i) {
const fleck = flecks[i];
expanded.push(fleck);
for (let j = 0; j < this.platforms.length; ++j) {
const platform = this.platforms[j];
const variant = join(fleck, platform);
if (this.fleck(variant)) {
expanded.push(variant);
}
}
}
// Handle elision.
const index = expanded.findIndex((fleck) => '...' === fleck);
@ -238,13 +225,6 @@ export default class Flecks {
if (!expanded.includes(fleck)) {
elided.push(fleck);
}
for (let j = 0; j < this.platforms.length; ++j) {
const platform = this.platforms[j];
const variant = join(fleck, platform);
if (this.fleck(variant) && !expanded.includes(variant)) {
elided.push(variant);
}
}
}
// Map the fleck implementations to vertices in a dependency graph.
const graph = this.flecksHookGraph([...before, ...elided, ...after], hook);
@ -380,13 +360,13 @@ export default class Flecks {
* @param {Object} config Configuration.
* @returns {Flecks} A flecks instance.
*/
static from(config) {
const {flecks} = config;
static from(runtime) {
const {flecks} = runtime;
const mixins = Object.entries(flecks)
.map(([, M]) => M.hooks?.['@flecks/core.mixin'])
.filter((e) => e);
const Flecks = compose(...mixins)(this);
return new Flecks(config);
return new Flecks(runtime);
}
/**
@ -431,8 +411,8 @@ export default class Flecks {
const gathered = {
...ids,
...types,
[ById]: ids,
[ByType]: types,
[exports.ById]: ids,
[exports.ByType]: types,
};
// Register for HMR?
hotGathered.set(
@ -444,7 +424,7 @@ export default class Flecks {
gathered,
},
);
debug("gathered '%s': %O", hook, Object.keys(gathered[ByType]));
debug("gathered '%s': %O", hook, Object.keys(gathered[exports.ByType]));
return gathered;
}
@ -846,7 +826,10 @@ export default class Flecks {
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;
gathered[type] = Subclass;
gathered[id] = Subclass;
gathered[exports.ById][id] = Subclass;
gathered[exports.ByType][type] = Subclass;
this.invoke('@flecks/core.hmr.gathered.class', Subclass, hook);
});
this.invoke('@flecks/core.hmr.gathered', gathered, hook);
@ -905,7 +888,7 @@ export default class Flecks {
}
}
}
};
Flecks.get = get;
Flecks.set = set;
exports.Flecks.get = get;
exports.Flecks.set = set;

View File

@ -1,2 +0,0 @@
# This isn't a "real" `flecks.yml`. It only exists for testing purposes.
'@flecks/core:./src': {}

View File

@ -0,0 +1,65 @@
require('source-map-support/register');
const D = require('./debug');
const Server = require('./server');
const debug = D('@flecks/core/build/fleckspack.config.js');
const {
FLECKS_CORE_BUILD_LIST = '',
} = process.env;
const buildList = FLECKS_CORE_BUILD_LIST
.split(',')
.map((name) => name.trim())
.filter((e) => e);
module.exports = async (env, argv) => {
debug('bootstrapping flecks...');
const flecks = await Server.from();
debug('bootstrapped');
debug('gathering configs');
const {targets} = flecks;
const building = targets
.filter(([, target]) => 0 === buildList.length || buildList.includes(target));
debug('building: %O', building.map(([target]) => target));
if (0 === building.length) {
debug('no build configuration found! aborting...');
await new Promise(() => {});
}
const entries = await Promise.all(building.map(
async ([fleck, target]) => {
const configFn = require(await flecks.resolveBuildConfig(`${target}.webpack.config.js`, fleck));
if ('function' !== typeof configFn) {
debug(`'${
target
}' build configuration expected function got ${
typeof configFn
}! aborting...`);
return undefined;
}
return [target, await configFn(env, argv, flecks)];
},
));
await Promise.all(
entries.map(async ([target, config]) => (
Promise.all(flecks.invokeFlat('@flecks/core.build', target, config, env, argv))
)),
);
const webpackConfigs = Object.fromEntries(entries);
await Promise.all(flecks.invokeFlat('@flecks/core.build.alter', webpackConfigs, env, argv));
const enterableWebpackConfigs = Object.values(webpackConfigs)
.filter((webpackConfig) => {
if (!webpackConfig.entry) {
debug('webpack configurations %O had no entry... discarding', webpackConfig);
return false;
}
return true;
});
if (0 === enterableWebpackConfigs.length) {
debug('no webpack configuration found! aborting...');
await new Promise(() => {});
}
debug('webpack configurations %O', enterableWebpackConfigs);
return enterableWebpackConfigs;
};

View File

@ -0,0 +1,32 @@
const {readFile} = require('fs/promises');
const {join} = require('path');
const D = require('./debug');
const debug = D('@flecks/core:load-config');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
module.exports = async function loadConfig() {
try {
const {load} = require('js-yaml');
const filename = join(FLECKS_CORE_ROOT, 'build', 'flecks.yml');
const buffer = await readFile(filename, 'utf8');
debug('parsing configuration from YML...');
return ['YML', load(buffer, {filename})];
}
catch (error) {
if ('ENOENT' !== error.code) {
throw error;
}
const {name} = require(join(FLECKS_CORE_ROOT, 'package.json'));
const barebones = {'@flecks/core': {}, '@flecks/fleck': {}};
if (barebones[name]) {
delete barebones[name];
}
barebones[`${name}:${FLECKS_CORE_ROOT}`] = {};
return ['barebones', barebones];
}
};

View File

@ -1,4 +1,4 @@
export default class Middleware {
module.exports = class Middleware {
constructor(middleware = []) {
this.middleware = [];
@ -61,4 +61,4 @@ export default class Middleware {
this.middleware.push(this.constructor.check(fn));
}
}
};

View File

@ -0,0 +1,88 @@
const {join} = require('path');
const {CachedInputFileSystem, ResolverFactory} = require('enhanced-resolve');
const AppendPlugin = require('enhanced-resolve/lib/AppendPlugin');
const AliasPlugin = require('enhanced-resolve/lib/AliasPlugin');
const fs = require('graceful-fs');
const D = require('./debug');
const debug = D('@flecks/core/build/resolver');
const debugSilly = debug.extend('silly');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
const nodeContext = {
environments: ['node+es3+es5+process+native'],
};
const nodeFileSystem = new CachedInputFileSystem(fs, 4000);
module.exports = class Resolver {
constructor(options) {
this.resolver = ResolverFactory.createResolver({
conditionNames: ['node'],
extensions: ['.js', '.json', '.node'],
fileSystem: nodeFileSystem,
symlinks: false,
...{
modules: [join(FLECKS_CORE_ROOT, 'node_modules')],
...options,
},
});
}
addAlias(name, alias) {
debugSilly("adding alias: '%s' -> '%s'", name, alias);
new AliasPlugin(
'raw-resolve',
{name, onlyModule: false, alias},
'internal-resolve',
).apply(this.resolver);
}
addExtensions(extensions) {
debugSilly("adding extensions: '%O'", extensions);
extensions.forEach((extension) => {
new AppendPlugin('raw-file', extension, 'file').apply(this);
});
}
addFallback(name, alias) {
debugSilly("adding fallback: '%s' -> '%s'", name, alias);
new AliasPlugin(
'described-resolve',
{name, onlyModule: false, alias},
'internal-resolve',
).apply(this.resolver);
}
static isResolutionError(error) {
return error.message.startsWith("Can't resolve");
}
async resolve(request) {
try {
return await new Promise((resolve, reject) => {
this.resolver.resolve(nodeContext, FLECKS_CORE_ROOT, request, {}, (error, path) => {
if (error) {
reject(error);
}
else {
resolve(path);
}
});
});
}
catch (error) {
if (!this.constructor.isResolutionError(error)) {
throw error;
}
return undefined;
}
}
};

View File

@ -0,0 +1,318 @@
const {realpath} = require('fs/promises');
const {join} = require('path');
const babelmerge = require('babel-merge');
const set = require('lodash.set');
const D = require('./debug');
const explicate = require('./explicate');
const {Flecks} = require('./flecks');
const loadConfig = require('./load-config');
const Resolver = require('./resolver');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
const debug = D('@flecks/core/build/bootstrap');
const debugSilly = debug.extend('silly');
function environmentalize(path) {
return path
// - `@flecks/core` -> `flecks_core`
.replace(/[^a-zA-Z0-9]/g, '_')
.replace(/_*(.*)_*/, '$1');
}
function environmentConfiguration(config) {
const keys = Object.keys(process.env);
Object.keys(config)
.sort((l, r) => (l < r ? 1 : -1))
.forEach((fleck) => {
const prefix = `FLECKS_ENV__${environmentalize(fleck)}`;
keys
.filter((key) => key.startsWith(`${prefix}__`))
.map((key) => {
debug('reading environment from %s...', key);
return [key.slice(prefix.length + 2), process.env[key]];
})
.map(([subkey, value]) => [subkey.split('_'), value])
.forEach(([path, jsonOrString]) => {
try {
set(config, [fleck, ...path], JSON.parse(jsonOrString));
debug('read (%s) as JSON', jsonOrString);
}
catch (error) {
set(config, [fleck, ...path], jsonOrString);
debug('read (%s) as string', jsonOrString);
}
});
});
return config;
}
module.exports = class Server extends Flecks {
buildConfigs = {};
platforms = ['server'];
resolved = {};
resolver = new Resolver();
roots = {};
async babel() {
const merging = [
{
plugins: ['@babel/plugin-syntax-dynamic-import'],
presets: [
[
'@babel/preset-env',
{
shippedProposals: true,
targets: {
esmodules: true,
node: 'current',
},
},
],
],
},
];
merging.push({configFile: await this.resolveBuildConfig('babel.config.js')});
merging.push(...this.invokeFlat('@flecks/core.babel'));
return babelmerge.all(merging);
}
static async buildRuntime(originalConfig, platforms, flecks = {}) {
const dealiasedConfig = Object.fromEntries(
Object.entries(originalConfig)
.map(([maybeAliasedPath, config]) => {
const index = maybeAliasedPath.indexOf(':');
return [
-1 === index ? maybeAliasedPath : maybeAliasedPath.slice(0, index),
config,
];
}),
);
const resolver = new Resolver();
const explication = await explicate(
Object.keys(originalConfig),
{
platforms,
resolver,
root: FLECKS_CORE_ROOT,
importer: (request) => require(request),
},
);
const runtime = {
config: environmentConfiguration(
Object.fromEntries(
Object.values(explication.descriptors)
.map(({path}) => [path, dealiasedConfig[path] || {}]),
),
),
flecks: Object.fromEntries(
Object.values(explication.descriptors)
.map(({path, request}) => [path, flecks[path] || explication.roots[request] || {}]),
),
};
const resolved = {};
await Promise.all(
Object.entries(explication.descriptors)
.map(async ([, {path, request}]) => {
try {
if (path !== request || request !== await realpath(request)) {
resolved[path] = request;
}
}
// eslint-disable-next-line no-empty
catch (error) {}
}),
);
const reverseRequest = Object.fromEntries(
Object.entries(explication.descriptors)
.map(([, {path, request}]) => [request, path]),
);
return {
resolved,
resolver,
roots: Object.fromEntries(
Object.entries(explication.roots)
.map(([request, bootstrap]) => [reverseRequest[request], {bootstrap, request}]),
),
runtime,
};
}
get extensions() {
return this.invokeFlat('@flecks/core.exts').flat();
}
static async from(
{
config: configParameter,
flecks: configFlecks,
platforms = ['server'],
} = {},
) {
// Load or use parameterized configuration.
let originalConfig;
let configType = 'parameter';
if (!configParameter) {
// eslint-disable-next-line no-param-reassign
[configType, originalConfig] = await loadConfig();
}
else {
originalConfig = JSON.parse(JSON.stringify(configParameter));
}
debug('bootstrap configuration (%s)', configType);
debugSilly(originalConfig);
const {
resolved,
resolver,
roots,
runtime,
} = await this.buildRuntime(originalConfig, platforms, configFlecks);
const flecks = super.from(runtime);
flecks.roots = roots;
flecks.platforms = platforms;
flecks.resolved = resolved;
flecks.resolver = resolver;
flecks.loadBuildConfigs();
return flecks;
}
loadBuildConfigs() {
Object.entries(this.invoke('@flecks/core.build.config'))
.forEach(([fleck, configs]) => {
configs.forEach((config) => {
this.buildConfigs[config] = fleck;
});
});
debugSilly('build configs loaded: %O', this.buildConfigs);
}
async resolveBuildConfig(config, override) {
const fleck = this.buildConfigs[config];
if (!fleck) {
throw new Error(`Unknown build config: '${config}'`);
}
const rootConfig = await this.resolver.resolve(join(FLECKS_CORE_ROOT, 'build', config));
if (rootConfig) {
return rootConfig;
}
if (override) {
const overrideConfig = await this.resolver.resolve(join(override, 'build', config));
if (overrideConfig) {
return overrideConfig;
}
}
return this.resolver.resolve(join(fleck, 'build', config));
}
async runtimeCompiler(runtime, config, {allowlist = []} = {}) {
// Compile.
const needCompilation = Object.entries(this.resolved);
if (needCompilation.length > 0) {
const babelConfig = await this.babel();
// const flecksBabelConfig = this.babel();
// Alias and de-externalize.
await Promise.all(
needCompilation
.map(async ([fleck, resolved]) => {
allowlist.push(fleck);
// Create alias.
config.resolve.alias[fleck] = resolved;
debugSilly('%s runtime de-externalized %s, alias: %s', runtime, fleck, resolved);
// Alias this compiled fleck's `node_modules` to the root `node_modules`.
config.resolve.alias[
join(resolved, 'node_modules')
] = join(FLECKS_CORE_ROOT, 'node_modules');
config.module.rules.push(
{
test: /\.(m?jsx?)?$/,
include: [resolved],
use: [
{
loader: require.resolve('babel-loader'),
options: {
cacheDirectory: true,
babelrc: false,
configFile: false,
...babelConfig,
},
},
],
},
);
}),
);
// Our very own lil' chunk.
set(config, 'optimization.splitChunks.cacheGroups.flecks-compiled', {
chunks: 'all',
enforce: true,
priority: 100,
test: new RegExp(
`(?:${
Object.keys(this.resolved)
.map((path) => path.replace(/[\\/]/g, '[\\/]')).join('|')
})`,
),
});
}
}
get stubs() {
return Object.values(this.flecks)
.reduce(
(r, {stubs = {}}) => (
r.concat(
Object.entries(stubs)
.reduce(
(r, [platform, stubs]) => (
r.concat(this.platforms.includes(platform) ? stubs : [])
),
[],
),
)
),
[],
).flat();
}
get targets() {
const targets = this.invoke('@flecks/core.targets');
const duplicates = {};
const entries = Object.entries(targets);
const set = new Set();
entries
.forEach(([fleck, targets]) => {
targets.forEach((target) => {
if (set.has(target)) {
if (!duplicates[target]) {
duplicates[target] = [];
}
duplicates[target].push(fleck);
}
set.add(target);
});
});
const errorMessage = Object.entries(duplicates).map(([target, flecks]) => (
`Multiple flecks ('${flecks.join("', '")})' tried to build target '${target}'`
)).join('\n');
if (errorMessage) {
throw new Error(`@flecks/core.targets:\n${errorMessage}`);
}
this.invoke('@flecks/core.targets.alter', set);
return entries
.map(([fleck, targets]) => (
targets
.filter((target) => set.has(target))
.map((target) => [fleck, target])
)).flat();
}
};

View File

@ -1,8 +1,8 @@
// eslint-disable-next-line max-classes-per-file
import JsonParse from 'jsonparse';
import {Transform} from 'stream';
const JsonParse = require('jsonparse');
const {Transform} = require('stream');
export class JsonStream extends Transform {
exports.JsonStream = class JsonStream extends Transform {
constructor() {
super();
@ -23,9 +23,9 @@ export class JsonStream extends Transform {
this.parser.write(chunk);
}
}
};
JsonStream.PrettyPrint = class extends Transform {
exports.JsonStream.PrettyPrint = class extends Transform {
constructor(indent = 2) {
super();
@ -40,7 +40,7 @@ JsonStream.PrettyPrint = class extends Transform {
};
export const transform = (fn, opts = {}) => {
exports.transform = (fn, opts = {}) => {
class EasyTransform extends Transform {
constructor() {

View File

@ -0,0 +1,15 @@
module.exports = function stub(stubs) {
if (0 === stubs.length) {
return;
}
const {Module} = require('module');
const {require: Mr} = Module.prototype;
Module.prototype.require = function hackedRequire(request, options) {
for (let i = 0; i < stubs.length; ++i) {
if (request.match(stubs[i])) {
return undefined;
}
}
return Mr.call(this, request, options);
};
};

View File

@ -1,15 +0,0 @@
const configFn = require('../src/server/build/webpack.config');
const {executable} = require('../src/server/webpack');
const eslintConfigFn = require('../src/server/build/default.eslint.config');
module.exports = async (env, argv) => {
const config = await configFn(env, argv);
config.plugins.push(...executable());
const eslint = await eslintConfigFn();
eslint.settings['import/resolver'].webpack = {
config: {
resolve: config.resolve,
},
};
return config;
};

View File

@ -20,10 +20,7 @@ exports.banner = (options) => (
exports.copy = (options) => (new CopyPlugin(options));
exports.defaultConfig = (flecks, specializedConfig) => {
const extensions = ['.mjs', '.js', '.json', '.wasm'];
if (flecks) {
extensions.push(...flecks.exts());
}
const {extensions} = flecks;
const extensionsRegex = exports.regexFromExtensions(extensions);
const defaults = {
context: FLECKS_CORE_ROOT,
@ -47,10 +44,10 @@ exports.defaultConfig = (flecks, specializedConfig) => {
alias: {},
extensions,
fallback: {},
modules: [
'node_modules',
join(FLECKS_CORE_ROOT, 'node_modules'),
],
// modules: [
// 'node_modules',
// join(FLECKS_CORE_ROOT, 'node_modules'),
// ],
},
stats: {
colors: true,
@ -90,10 +87,6 @@ exports.defaultConfig = (flecks, specializedConfig) => {
// Include a shebang and set the executable bit..
exports.executable = () => ([
exports.banner({
banner: '#!/usr/bin/env node',
include: /^cli\.js$/,
}),
new class Executable {
// eslint-disable-next-line class-methods-use-this
@ -101,7 +94,7 @@ exports.executable = () => ([
compiler.hooks.afterEmit.tapAsync(
'Executable',
(compilation, callback) => {
chmod(join(FLECKS_CORE_ROOT, 'dist', 'cli.js'), 0o755, callback);
chmod(join(FLECKS_CORE_ROOT, 'dist', 'build', 'cli.js'), 0o755, callback);
},
);
}

View File

@ -8,38 +8,26 @@
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"version": "3.0.0",
"main": "index.js",
"author": "cha0s",
"license": "MIT",
"bin": {
"flecks": "./cli.js"
"flecks": "./build/cli.js"
},
"scripts": {
"build": "NODE_PATH=./node_modules webpack --config ./build/webpack.config.js --mode production",
"build": "NODE_PATH=./node_modules webpack --config ./build/core.webpack.config.js --mode production",
"clean": "rm -rf dist bun.lockb && bun install",
"lint": "NODE_PATH=./node_modules eslint --config ./build/eslint.config.js .",
"postversion": "cp package.json dist",
"test": "npm run build && mocha -t 10000 --colors ./dist/test.js"
"test": "npm run build -d && mocha -t 10000 --colors ./dist/test.js"
},
"files": [
"build",
"cli.js",
"cli.js.map",
"index.js",
"index.js.map",
"server.js",
"server.js.map",
"server/build/babel.config.js",
"server/build/babel.config.js.map",
"server/build/default.eslint.config.js",
"server/build/default.eslint.config.js.map",
"server/build/eslint.config.js",
"server/build/eslint.config.js.map",
"server/build/webpack.config.js",
"server/build/webpack.config.js.map",
"server/build/fleckspack.config.js",
"server/build/fleckspack.config.js.map",
"src",
"start.js",
"start.js.map",
@ -51,13 +39,10 @@
"@babel/core": "^7.12.10",
"@babel/eslint-parser": "^7.23.3",
"@babel/eslint-plugin": "^7.22.10",
"@babel/plugin-proposal-optional-chaining": "^7.12.16",
"@babel/plugin-transform-regenerator": "^7.16.7",
"@babel/preset-env": "^7.12.11",
"@babel/register": "7.13.0",
"babel-loader": "^9.1.3",
"babel-merge": "^3.0.0",
"babel-plugin-prepend": "^1.0.2",
"chai": "4.2.0",
"chai-as-promised": "7.1.1",
"commander": "11.1.0",
@ -72,17 +57,16 @@
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-webpack-plugin": "^4.0.1",
"eslint-webpack-plugin": "^3.2.0",
"glob": "^10.3.10",
"globals": "^13.23.0",
"graceful-fs": "^4.2.11",
"js-yaml": "4.1.0",
"jsonparse": "^1.3.1",
"lodash.flatten": "^4.4.0",
"lodash.get": "^4.4.2",
"lodash.intersectionby": "4.7.0",
"lodash.set": "^4.3.2",
"mocha": "^8.3.2",
"pirates": "^4.0.5",
"null-loader": "^4.0.1",
"rimraf": "^3.0.2",
"source-map-loader": "4.0.1",
"source-map-support": "0.5.19",

View File

@ -1 +0,0 @@
export default class {}

View File

@ -1,116 +0,0 @@
import {fork} from 'child_process';
import {join, resolve, sep} from 'path';
import {Command} from 'commander';
import D from './debug';
import {processCode} from './server/commands';
import Flecks from './server/flecks';
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
const debug = D('@flecks/core/cli');
const debugSilly = debug.extend('silly');
// Guarantee local node_modules path.
const defaultNodeModules = resolve(join(FLECKS_CORE_ROOT, 'node_modules'));
const nodePathSeparator = '/' === sep ? ':' : ';';
let updatedNodePath;
if (!process.env.NODE_PATH) {
updatedNodePath = defaultNodeModules;
}
else {
const parts = process.env.NODE_PATH.split(nodePathSeparator);
if (!parts.some((part) => resolve(part) === defaultNodeModules)) {
parts.push(defaultNodeModules);
updatedNodePath = parts.join(nodePathSeparator);
}
}
// Guarantee symlink preservation for linked flecks.
const updateSymlinkPreservation = !process.env.NODE_PRESERVE_SYMLINKS;
const environmentUpdates = (
updatedNodePath
|| updateSymlinkPreservation
)
? {
NODE_PATH: updatedNodePath,
NODE_PRESERVE_SYMLINKS: 1,
}
: undefined;
if (environmentUpdates) {
debugSilly('updating environment, forking with %O...', environmentUpdates);
const forkOptions = {
env: {
...process.env,
...environmentUpdates,
DEBUG_COLORS: 'true',
},
stdio: 'inherit',
};
fork(__filename, process.argv.slice(2), forkOptions)
.on('exit', (code, signal) => {
process.exitCode = code;
process.exit(signal);
});
}
else {
// Asynchronous command process code forwarding.
const forwardProcessCode = (fn) => async (...args) => {
const child = await fn(...args);
if ('object' !== typeof child) {
const code = 'undefined' !== typeof child ? child : 0;
debugSilly('action returned code %d', code);
process.exitCode = code;
return;
}
try {
const code = await processCode(child);
debugSilly('action exited with code %d', code);
process.exitCode = code;
}
catch (error) {
// eslint-disable-next-line no-console
console.error(error);
process.exitCode = child.exitCode || 1;
}
};
// Initialize Commander.
const program = new Command();
program
.enablePositionalOptions()
.name('flecks')
.usage('[command] [...]');
// Bootstrap.
(async () => {
debugSilly('bootstrapping flecks...');
const flecks = Flecks.bootstrap();
debugSilly('bootstrapped');
// Register commands.
const commands = flecks.invokeMerge('@flecks/core.commands', program);
const keys = Object.keys(commands).sort();
for (let i = 0; i < keys.length; ++i) {
const {
action,
args = [],
description,
name = keys[i],
options = [],
} = commands[keys[i]];
debugSilly('adding command %s...', name);
const cmd = program.command(name);
cmd.description(description);
for (let i = 0; i < args.length; ++i) {
cmd.addArgument(args[i]);
}
for (let i = 0; i < options.length; ++i) {
cmd.option(...options[i]);
}
cmd.action(forwardProcessCode(action));
}
// Parse commandline.
program.parse(process.argv);
})();
}

View File

@ -1,18 +1,9 @@
export {default as Class} from './class';
export {default as compose} from './compose';
export {default as D} from './debug';
export {default as EventEmitter} from './event-emitter';
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 {
default as Flecks,
ById,
ByType,
} from './flecks';
export const hooks = {
'@flecks/core.config': () => ({
/**
* The ID of your application.
*/
id: 'flecks',
}),
};
Flecks,
} from '../build/flecks';

View File

@ -1,4 +0,0 @@
// Get a runtime require function by hook or by crook. :)
// eslint-disable-next-line no-eval
module.exports = eval('"undefined" !== typeof require ? require : undefined');

View File

@ -1,93 +0,0 @@
const babelmerge = require('babel-merge');
const globals = require('globals');
const R = require('../../require');
module.exports = (flecks) => {
const merging = [
{
plugins: [R.resolve('@babel/plugin-syntax-dynamic-import')],
presets: [
[
R.resolve('@babel/preset-env'),
{
shippedProposals: true,
targets: {
esmodules: true,
node: 'current',
},
},
],
],
},
];
if (flecks) {
merging.push({configFile: flecks.buildConfig('babel.config.js')});
const flecksBabelConfig = flecks.babel();
merging.push(...flecksBabelConfig.map(([, babel]) => babel));
}
const babelConfig = babelmerge.all(merging);
return {
extends: [
R.resolve('eslint-config-airbnb'),
R.resolve('eslint-config-airbnb/hooks'),
],
globals: {
...globals.browser,
...globals.es2021,
...globals.mocha,
...globals.node,
__non_webpack_require__: true,
},
ignorePatterns: [
'dist/**',
// Not even gonna try.
'build/dox/hooks.js',
],
overrides: [
{
files: [
'test/**/*.js',
],
rules: {
'brace-style': 'off',
'class-methods-use-this': 'off',
'import/no-extraneous-dependencies': 'off',
'import/no-unresolved': 'off',
'max-classes-per-file': 'off',
'no-new': 'off',
'no-unused-expressions': 'off',
'padded-blocks': 'off',
},
},
],
parser: R.resolve('@babel/eslint-parser'),
parserOptions: {
requireConfigFile: false,
babelOptions: babelConfig,
},
plugins: ['@babel'],
rules: {
'brace-style': ['error', 'stroustrup'],
// Bug: https://github.com/import-js/eslint-plugin-import/issues/2181
'import/no-import-module-exports': 'off',
'import/prefer-default-export': 'off',
'jsx-a11y/control-has-associated-label': ['error', {assert: 'either'}],
'jsx-a11y/label-has-associated-control': ['error', {assert: 'either'}],
'no-param-reassign': ['error', {props: false}],
'no-plusplus': 'off',
'no-shadow': 'off',
'object-curly-spacing': 'off',
'padded-blocks': ['error', {classes: 'always'}],
yoda: 'off',
},
settings: {
'import/resolver': {
node: {},
},
react: {
version: '18.2.0',
},
},
};
};

View File

@ -1,65 +0,0 @@
const {spawnSync} = require('child_process');
const {
mkdirSync,
readFileSync,
statSync,
writeFileSync,
} = require('fs');
const {join} = require('path');
const D = require('../../debug');
const {default: Flecks} = require('../flecks');
const debug = D('@flecks/core/server/build/eslint.config.js');
const {
FLECKS_CORE_ROOT = process.cwd(),
FLECKS_CORE_SYNC_FOR_ESLINT = false,
} = process.env;
// This is kinda nuts, but ESLint doesn't support its configuration files returning a promise!
if (FLECKS_CORE_SYNC_FOR_ESLINT) {
(async () => {
debug('bootstrapping flecks...');
const flecks = Flecks.bootstrap();
debug('bootstrapped');
const eslintConfigFn = __non_webpack_require__(flecks.buildConfig('default.eslint.config.js'));
const eslintConfig = await eslintConfigFn(flecks);
const webpackConfigFn = __non_webpack_require__(flecks.buildConfig('webpack.config.js', 'fleck'));
const webpackConfig = await webpackConfigFn({}, {mode: 'development'}, flecks);
eslintConfig.settings['import/resolver'].webpack = {
config: {
resolve: webpackConfig.resolve,
},
};
process.stdout.write(JSON.stringify(eslintConfig, null, 2));
})();
}
else {
const cacheDirectory = join(FLECKS_CORE_ROOT, 'node_modules', '.cache', '@flecks', 'core');
try {
statSync(join(cacheDirectory, 'eslint.config.json'));
module.exports = JSON.parse(readFileSync(join(cacheDirectory, 'eslint.config.json')).toString());
}
catch (error) {
// Just silly. By synchronously spawning... ourselves, the spawned copy can use async.
const {stderr, stdout} = spawnSync('node', [__filename], {
env: {
FLECKS_CORE_SYNC_FOR_ESLINT: true,
NODE_PATH: join(FLECKS_CORE_ROOT, 'node_modules'),
...process.env,
},
});
// eslint-disable-next-line no-console
console.error(stderr.toString());
const json = stdout.toString();
try {
statSync(join(FLECKS_CORE_ROOT, 'node_modules'));
mkdirSync(cacheDirectory, {recursive: true});
writeFileSync(join(cacheDirectory, 'eslint.config.json'), json);
}
// eslint-disable-next-line no-empty
catch (error) {}
module.exports = JSON.parse(json);
}
}

View File

@ -1,94 +0,0 @@
/* eslint-disable import/first */
import 'source-map-support/register';
if ('production' !== process.env.NODE_ENV) {
try {
// eslint-disable-next-line global-require, import/no-unresolved
__non_webpack_require__('dotenv/config');
}
// eslint-disable-next-line no-empty
catch (error) {}
}
import intersectionBy from 'lodash.intersectionby';
import D from '../../debug';
import Flecks from '../flecks';
const debug = D('@flecks/core/server/build/fleckspack.config.js');
const {
FLECKS_CORE_BUILD_LIST = '',
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
const buildList = FLECKS_CORE_BUILD_LIST
.split(',')
.map((name) => name.trim())
.filter((e) => e);
export default async (env, argv) => {
debug('bootstrapping flecks...');
const flecks = Flecks.bootstrap();
debug('bootstrapped');
debug('gathering configs');
const targets = [];
Object.entries(flecks.invoke('@flecks/core.targets'))
.forEach(([fleck, fleckTargets]) => {
intersectionBy(fleckTargets, buildList.length ? buildList : fleckTargets)
.forEach((target) => {
targets.push([target, fleck]);
});
});
debug('building: %O', targets.map(([target]) => target));
if (0 === targets.length) {
debug('no build configuration found! aborting...');
await new Promise(() => {});
}
const entries = await Promise.all(targets.map(
async ([target, fleck]) => {
const buildConfig = flecks.resolveBuildConfig(
[
FLECKS_CORE_ROOT,
flecks.resolvePath(fleck),
],
[
`${target}.webpack.config.js`,
'webpack.config.js',
],
);
const configFn = __non_webpack_require__(buildConfig);
if ('function' !== typeof configFn) {
debug(`'${
target
}' build configuration expected function got ${
typeof configFn
}! aborting...`);
return undefined;
}
return [target, await configFn(env, argv, flecks)];
},
));
await Promise.all(
entries.map(async ([target, config]) => (
flecks.invokeFlat('@flecks/core.build', target, config, env, argv)
)),
);
const webpackConfigs = Object.fromEntries(entries);
await Promise.all(flecks.invokeFlat('@flecks/core.build.alter', webpackConfigs, env, argv));
const enterableWebpackConfigs = Object.values(webpackConfigs)
.filter((webpackConfig) => {
if (!webpackConfig.entry) {
debug('webpack configurations %O had no entry... discarding', webpackConfig);
return false;
}
return true;
});
if (0 === enterableWebpackConfigs.length) {
debug('no webpack configuration found! aborting...');
await new Promise(() => {});
}
debug('webpack configurations %O', enterableWebpackConfigs);
return enterableWebpackConfigs;
};

View File

@ -1,163 +0,0 @@
import {statSync} from 'fs';
import {dirname, sep} from 'path';
import {
getEnv,
OptionManager,
transformSync,
version,
} from '@babel/core';
import sourceMapSupport from 'source-map-support';
sourceMapSupport.install();
const cache = require('@babel/register/lib/cache');
const identity = (i) => i;
// This is basically what @babel/register does, but better in several ways.
class Compiler {
constructor(options) {
// Make some goodies exist in nodespace.
this.options = {
...options,
plugins: [
...(options.plugins || []),
...this.constructor.nodespaceBabelPlugins(),
],
};
this.constructor.warmUpCache();
}
compile(input, request) {
const options = new OptionManager()
.init({
sourceRoot: `${dirname(request)}${sep}`,
...this.options,
filename: request,
});
if (null === options) {
return null;
}
const {cached, store} = this.lookup(options, request);
if (cached) {
return cached;
}
const {code, map} = transformSync(input, {
...options,
sourceMaps: 'both',
ast: false,
});
this.constructor.maps[request] = map;
return store({code, map});
}
static installSourceMapSupport() {
this.maps = Object.create(null);
sourceMapSupport.install({
handleUncaughtExceptions: false,
environment: 'node',
retrieveSourceMap: (request) => {
const map = this.maps[request];
if (map) {
return {url: null, map};
}
return null;
},
});
}
lookup(options, request) {
if (!this.constructor.cache) {
return {
cached: null,
store: identity,
};
}
let cacheKey = `${JSON.stringify(options)}:${version}`;
const env = getEnv();
if (env) {
cacheKey += `:${env}`;
}
const cached = this.constructor.cache[cacheKey];
const mtime = +statSync(request).mtime;
if (cached && cached.mtime === mtime) {
return {
cached: cached.value,
store: identity,
};
}
return {
cached: null,
store: (value) => {
this.constructor.cache[cacheKey] = {mtime, value};
cache.setDirty();
return value;
},
};
}
static nodespaceBabelPlugins() {
return [
[
'prepend',
{
prepend: [
'require.context = (',
' directory,',
' useSubdirectories = true,',
' regExp = /^\\.\\/.*$/,',
' mode = "sync",',
') => {',
' const glob = require("glob");',
' const {join} = require("path");',
' const {resolve, sep} = require("path");',
' const keys = glob.sync(',
' useSubdirectories ? "**/*" : "*",',
' {cwd: resolve(__dirname, directory)},',
' )',
' .filter((key) => key.match(regExp))',
' .map(',
' (key) => (',
' -1 !== [".".charCodeAt(0), "/".charCodeAt(0)].indexOf(key.charCodeAt(0))',
' ? key',
' : ("." + sep + key)',
' ),',
' );',
' const R = (request) => {',
' if (-1 === keys.indexOf(request)) {',
// eslint-disable-next-line no-template-curly-in-string
' throw new Error(`Cannot find module \'${request}\'`);',
' }',
' return require(join(__dirname, directory, request));',
' };',
' R.id = __filename',
' R.keys = () => keys;',
' return R;',
'};',
].join('\n'),
},
'require.context',
],
[
'prepend',
{
prepend: '"undefined" === typeof global.__non_webpack_require__ && (global.__non_webpack_require__ = require);',
},
'__non_webpack_require__',
],
];
}
static warmUpCache() {
if ('undefined' === typeof this.cache) {
cache.load();
this.cache = cache.get();
this.installSourceMapSupport();
}
}
}
export default Compiler;

View File

@ -1,733 +0,0 @@
import {
readFileSync,
realpathSync,
statSync,
} from 'fs';
import {readFile, writeFile} from 'fs/promises';
import {
basename,
dirname,
extname,
isAbsolute,
join,
resolve,
sep,
} from 'path';
import babelmerge from 'babel-merge';
import enhancedResolve from 'enhanced-resolve';
import {dump as dumpYml, load as loadYml} from 'js-yaml';
import {addHook} from 'pirates';
import D from '../debug';
import Flecks from '../flecks';
import R from '../require';
import Compiler from './compiler';
const {
FLECKS_CORE_ROOT = process.cwd(),
FLECKS_YML = 'flecks.yml',
} = process.env;
const debug = D('@flecks/core/flecks/server');
const debugSilly = debug.extend('silly');
export default class ServerFlecks extends Flecks {
constructor(options = {}) {
super(options);
this.overrideConfigFromEnvironment();
this.buildConfigs = {};
this.loadBuildConfigs();
this.flecksConfig = options.flecksConfig || {};
this.resolver = options.resolver || {};
}
static async addFleckToYml(fleck, path) {
const key = [fleck].concat(path ? `.${sep}${join('packages', path, 'src')}` : []).join(':');
const ymlPath = join(FLECKS_CORE_ROOT, 'build', 'flecks.yml');
let yml = loadYml(await readFile(ymlPath));
yml = Object.fromEntries(Object.entries(yml).concat([[key, {}]]));
await writeFile(ymlPath, dumpYml(yml, {sortKeys: true}));
}
get aliasedConfig() {
const aliases = this.aliases();
return Object.fromEntries(
Object.entries(
this.config,
)
.map(([path, config]) => [
this.fleckIsAliased(path) ? `${path}:${aliases[path]}` : path,
config,
]),
);
}
aliases() {
return this.constructor.aliases(this.flecksConfig);
}
static aliases(flecksConfig) {
const keys = Object.keys(flecksConfig);
let aliases = {};
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
const config = flecksConfig[key];
if (config.aliases && Object.keys(config.aliases).length > 0) {
aliases = {...aliases, ...config.aliases};
}
}
return aliases;
}
babel() {
return this.constructor.babel(this.flecksConfig);
}
static babel(flecksConfig) {
return Object.entries(flecksConfig)
.filter(([, {babel}]) => babel)
.map(([key, {babel}]) => [key, babel]);
}
static bootstrap(
{
config,
platforms = ['server'],
root = FLECKS_CORE_ROOT,
} = {},
) {
// Load or use parameterized configuration.
let configType;
if (!config) {
// eslint-disable-next-line no-param-reassign
[configType, config] = this.loadConfig(root);
}
else {
configType = 'parameter';
}
debug('bootstrap configuration (%s)', configType);
debugSilly(config);
// Make resolver and load flecksConfig.
const {flecksConfig, resolver} = this.makeResolverAndLoadRcs(
Object.keys(config),
platforms,
root,
);
// Rewrite aliased config keys.
// eslint-disable-next-line no-param-reassign
config = Object.fromEntries(
Object.entries(config)
.map(([key, value]) => {
const index = key.indexOf(':');
return [-1 !== index ? key.slice(0, index) : key, value];
}),
);
this.installCompilers(flecksConfig, resolver);
// Instantiate with mixins.
return ServerFlecks.from({
config,
flecks: Object.fromEntries(
Object.keys(resolver)
.map((path) => [path, R(this.resolve(resolver, path))]),
),
platforms,
flecksConfig,
resolver,
});
}
buildConfig(path, specific) {
const config = this.buildConfigs[path];
if (!config) {
throw new Error(`Unknown build config '${path}'`);
}
const paths = [];
if (specific) {
if ('specifier' in config) {
if (false === config.specifier) {
paths.shift();
}
else {
paths.push(config.specifier(specific));
}
}
else {
paths.push(`${specific}.${path}`);
}
}
paths.push(path);
const roots = [config.root];
if (config.root !== FLECKS_CORE_ROOT) {
roots.push(FLECKS_CORE_ROOT);
}
roots.push(this.resolvePath(this.resolve(config.fleck)));
return this.constructor.resolveBuildConfig(this.resolver, roots, paths);
}
static environmentalize(path) {
return path
// - `@flecks/core` -> `flecks_core`
.replace(/[^a-zA-Z0-9]/g, '_')
.replace(/_*(.*)_*/, '$1');
}
exts() {
return this.constructor.exts(this.flecksConfig);
}
static exts(flecksConfig) {
const keys = Object.keys(flecksConfig);
const exts = [];
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
const config = flecksConfig[key];
if (config.exts) {
exts.push(...config.exts);
}
}
return exts;
}
fleckIsAliased(fleck) {
return this.constructor.fleckIsAliased(this.resolver, fleck);
}
static fleckIsAliased(resolver, fleck) {
return fleck !== this.resolve(resolver, fleck);
}
fleckIsCompiled(fleck) {
return this.constructor.fleckIsCompiled(this.resolver, fleck);
}
static fleckIsCompiled(resolver, fleck) {
return this.fleckIsAliased(resolver, fleck) || this.fleckIsSymlinked(resolver, fleck);
}
fleckIsSymlinked(fleck) {
return this.constructor.fleckIsSymlinked(this.resolver, fleck);
}
static fleckIsSymlinked(resolver, fleck) {
const resolved = R.resolve(this.resolve(resolver, fleck));
const realpath = realpathSync(resolved);
return realpath !== resolved;
}
static installCompilers(flecksConfig, resolver) {
const paths = Object.keys(resolver);
debugSilly('flecksConfig: %O', flecksConfig);
// Merge aliases;
const aliases = Object.fromEntries(
Object.entries({
// from fleck configuration above,
...Object.fromEntries(Object.entries(resolver).filter(([from, to]) => from !== to)),
// from symlinks,
...(
Object.fromEntries(
paths.filter((path) => this.fleckIsSymlinked(resolver, path))
.map((path) => [path, this.sourcepath(R.resolve(this.resolve(resolver, path)))]),
)
),
// and from RCs.
...this.aliases(flecksConfig),
})
.map(([from, to]) => [from, to.endsWith('/index') ? to.slice(0, -6) : to]),
);
if (Object.keys(aliases).length > 0) {
debugSilly('aliases: %O', aliases);
}
const exts = this.exts(flecksConfig);
const enhancedResolver = enhancedResolve.create.sync({
extensions: exts,
alias: aliases,
});
// Stub server-unfriendly modules.
const stubs = this.stubs(['server'], flecksConfig);
if (stubs.length > 0) {
debugSilly('stubbing: %O', stubs);
}
// Do we need to get up in `require()`'s guts?
if (
Object.keys(aliases).length > 0
|| stubs.length > 0
) {
const {Module} = R('module');
const {require: Mr} = Module.prototype;
const aliasKeys = Object.keys(aliases);
Module.prototype.require = function hackedRequire(request, options) {
for (let i = 0; i < stubs.length; ++i) {
if (request.match(stubs[i])) {
return undefined;
}
}
if (aliasKeys.find((aliasKey) => request.startsWith(aliasKey))) {
try {
const resolved = enhancedResolver(FLECKS_CORE_ROOT, request);
if (resolved) {
return Mr.call(this, resolved, options);
}
}
// eslint-disable-next-line no-empty
catch (error) {}
}
return Mr.call(this, request, options);
};
}
// Compile.
const compilations = [];
const needCompilation = paths
.filter((path) => this.fleckIsCompiled(resolver, path));
if (needCompilation.length > 0) {
// Augment the compilations with babel config from flecksrc.
const flecksBabelConfig = babelmerge.all(this.babel(flecksConfig).map(([, babel]) => babel));
debugSilly('.flecksrc: babel: %O', flecksBabelConfig);
// Key flecks needing compilation by their roots, so we can compile all common roots with a
// single invocation of `@babel/register`.
const compilationRootMap = {};
needCompilation.forEach((fleck) => {
const root = this.root(resolver, fleck);
if (!compilationRootMap[root]) {
compilationRootMap[root] = [];
}
compilationRootMap[root].push(fleck);
});
// Register a compilation for each root.
Object.entries(compilationRootMap).forEach(([root, compiling]) => {
const resolved = dirname(R.resolve(join(root, 'package.json')));
const sourcepath = this.sourcepath(resolved);
const sourceroot = join(sourcepath, '..');
// Load babel config from whichever we find first:
// - The fleck being compiled's build directory
// - The root build directory
// - Finally, the built-in babel config
let builtInPath;
try {
builtInPath = this.resolvePath(resolver, '@flecks/core/server');
}
catch (error) {
// This file won't be resolved during testing.
builtInPath = join(__dirname, '..', 'src', 'server');
}
const configFile = this.resolveBuildConfig(
resolver,
[
resolved,
FLECKS_CORE_ROOT,
builtInPath,
],
[
'babel.config.js',
],
);
const ignore = `${resolve(join(sourceroot, 'node_modules'))}/`;
const only = `${resolve(sourceroot)}/`;
const config = {
// Augment the selected config with the babel config from RCs.
configFile,
// Target the compiler to avoid unnecessary work.
ignore: [ignore],
only: [only],
};
debugSilly('compiling %O with %j at %s', compiling, config, only);
compilations.push({
ignore,
only,
compiler: new Compiler(babelmerge(config, flecksBabelConfig)),
});
});
}
const findCompiler = (request) => {
for (let i = 0; i < compilations.length; ++i) {
const {compiler, ignore, only} = compilations[i];
if (request.startsWith(only) && !request.startsWith(ignore)) {
return compiler;
}
}
return undefined;
};
debugSilly('pirating exts: %O', exts);
addHook(
(code, request) => {
const compilation = findCompiler(request).compile(code, request);
return null === compilation ? code : compilation.code;
},
{exts, matcher: (request) => !!findCompiler(request)},
);
}
loadBuildConfigs() {
Object.entries(this.invoke('@flecks/core.build.config'))
.forEach(([fleck, configs]) => {
configs.forEach((config) => {
const defaults = {
fleck,
};
if (Array.isArray(config)) {
this.registerBuildConfig(config[0], {...defaults, ...config[1]});
}
this.registerBuildConfig(config, defaults);
});
});
}
static loadConfig(root) {
const resolvedRoot = resolve(FLECKS_CORE_ROOT, root);
try {
const {load} = R('js-yaml');
const filename = join(resolvedRoot, 'build', FLECKS_YML);
const buffer = readFileSync(filename, 'utf8');
debugSilly('parsing configuration from YML...');
return ['YML', load(buffer, {filename}) || {}];
}
catch (error) {
if ('ENOENT' !== error.code) {
throw error;
}
return ['barebones', {'@flecks/core': {}, '@flecks/fleck': {}}];
}
}
static makeResolver(maybeAliasedPath, platforms, root) {
const resolvedRoot = resolve(FLECKS_CORE_ROOT, root);
const resolver = {};
// `!platform` excludes that platform.
const without = platforms
.filter((platform) => '!'.charCodeAt(0) === platform.charCodeAt(0))
.map((platform) => platform.slice(1));
// Parse the alias (if any).
const index = maybeAliasedPath.indexOf(':');
const [path, alias] = -1 === index
? [maybeAliasedPath, undefined]
: [maybeAliasedPath.slice(0, index), maybeAliasedPath.slice(index + 1)];
// Run it by the exception list.
if (-1 !== without.indexOf(path.split('/').pop())) {
// eslint-disable-next-line no-continue
return {};
}
// Resolve the path (if necessary).
let resolvedPath;
if (alias) {
resolvedPath = isAbsolute(alias) ? alias : join(resolvedRoot, alias);
}
else {
if (path.startsWith('.')) {
throw new Error(`non-aliased relative path '${path}' in configuration`);
}
resolvedPath = path;
}
try {
R.resolve(resolvedPath);
resolver[path] = resolvedPath;
}
// eslint-disable-next-line no-empty
catch (error) {}
// Discover platform-specific variants.
if (platforms) {
platforms.forEach((platform) => {
try {
const resolvedPlatformPath = join(resolvedPath, platform);
R.resolve(resolvedPlatformPath);
resolver[join(path, platform)] = resolvedPlatformPath;
}
// eslint-disable-next-line no-empty
catch (error) {}
});
}
return resolver;
}
static makeResolverAndLoadRcs(
maybeAliasedPaths,
platforms = ['server'],
root = FLECKS_CORE_ROOT,
) {
const resolver = {};
const rootsFrom = (paths) => (
Array.from(new Set(
paths
.map((path) => this.root(resolver, path))
.filter((e) => !!e),
))
);
for (let i = 0; i < maybeAliasedPaths.length; ++i) {
const maybeAliasedPath = maybeAliasedPaths[i];
Object.entries(this.makeResolver(maybeAliasedPath, platforms, root))
.forEach(([path, alias]) => {
resolver[path] = alias;
});
}
const flecksConfig = {};
const roots = Array.from(new Set(
rootsFrom(Object.keys(resolver))
.concat(FLECKS_CORE_ROOT),
));
let rootsToScan = roots;
while (rootsToScan.length > 0) {
const dependencies = [];
for (let i = 0; i < rootsToScan.length; ++i) {
const root = rootsToScan[i];
try {
flecksConfig[root] = R(join(root, 'build', 'flecks.config'));
const {dependencies: flecksConfigDependencies = []} = flecksConfig[root];
dependencies.push(...flecksConfigDependencies);
flecksConfigDependencies.forEach((dependency) => {
Object.entries(this.makeResolver(dependency, platforms, root))
.forEach(([path, alias]) => {
resolver[path] = alias;
});
});
}
catch (error) {
if ('MODULE_NOT_FOUND' !== error.code) {
throw error;
}
}
}
rootsToScan = rootsFrom(dependencies)
.filter((root) => !flecksConfig[root]);
}
return {flecksConfig, resolver};
}
overrideConfigFromEnvironment() {
const keys = Object.keys(process.env);
const seen = [];
Object.keys(this.flecks)
.sort((l, r) => (l < r ? 1 : -1))
.forEach((fleck) => {
const prefix = `FLECKS_ENV__${this.constructor.environmentalize(fleck)}`;
keys
.filter((key) => key.startsWith(`${prefix}__`) && -1 === seen.indexOf(key))
.map((key) => {
seen.push(key);
debug('reading environment from %s...', key);
return [key, process.env[key]];
})
.map(([key, value]) => [key.slice(prefix.length + 2), value])
.map(([subkey, value]) => [subkey.split('_'), value])
.forEach(([path, jsonOrString]) => {
try {
this.set([fleck, ...path], JSON.parse(jsonOrString));
debug('read (%s) as JSON', jsonOrString);
}
catch (error) {
this.set([fleck, ...path], jsonOrString);
debug('read (%s) as string', jsonOrString);
}
});
});
}
flecksConfig() {
return this.flecksConfig;
}
registerBuildConfig(filename, config) {
const defaults = {
root: FLECKS_CORE_ROOT,
};
this.buildConfigs[filename] = {...defaults, ...config};
}
registerResolver(from, to = from) {
this.resolver[from] = to;
}
resolve(path) {
return this.constructor.resolve(this.resolver, path);
}
static resolve(resolver, fleck) {
return resolver[fleck] || fleck;
}
resolveBuildConfig(roots, paths) {
return this.constructor.resolveBuildConfig(this.resolver, roots, paths);
}
static resolveBuildConfig(resolver, roots, paths) {
const tried = [];
for (let i = 0; i < roots.length; ++i) {
const root = roots[i];
for (let j = 0; j < paths.length; ++j) {
const path = paths[j];
const resolved = join(root, 'build', path);
try {
tried.push(resolved);
statSync(resolved);
return resolved;
}
// eslint-disable-next-line no-empty
catch (error) {}
}
}
throw new Error(`Couldn't resolve build file '${paths.pop()}', tried: ${tried.join(', ')}`);
}
resolvePath(path) {
return this.constructor.resolvePath(this.resolver, path);
}
static resolvePath(resolver, path) {
const resolved = R.resolve(this.resolve(resolver, path));
const ext = extname(resolved);
const base = basename(resolved, ext);
return join(dirname(resolved), 'index' === base ? '' : base);
}
root(fleck) {
return this.constructor.root(this.resolver, fleck);
}
static root(resolver, fleck) {
const parts = this.resolve(resolver, fleck).split('/');
try {
R.resolve(parts.join('/'));
}
catch (error) {
try {
R.resolve(join(parts.join('/'), 'build', 'flecks.config'));
}
catch (error) {
return undefined;
}
}
while (parts.length > 0) {
try {
R.resolve(join(parts.join('/'), 'package.json'));
return parts.join('/');
}
catch (error) {
parts.pop();
}
}
return undefined;
}
runtimeCompiler(resolver, runtime, config, {allowlist = []} = {}) {
// Compile.
const needCompilation = Object.entries(resolver)
.filter(([fleck]) => this.constructor.fleckIsCompiled(resolver, fleck));
if (needCompilation.length > 0) {
const flecksBabelConfig = this.babel();
debugSilly('flecks.config.js: babel: %O', flecksBabelConfig);
// Alias and de-externalize.
needCompilation
.sort(([l], [r]) => (l < r ? 1 : -1))
.forEach(([fleck, resolved]) => {
let alias = this.constructor.fleckIsAliased(resolver, fleck)
? resolved
: this.constructor.sourcepath(R.resolve(this.constructor.resolve(resolver, fleck)));
alias = alias.endsWith('/index') ? alias.slice(0, -6) : alias;
allowlist.push(fleck);
config.resolve.alias[fleck] = alias;
debugSilly('%s runtime de-externalized %s, alias: %s', runtime, fleck, alias);
});
// Set up compilation at each root.
const compiledPaths = [];
Array.from(new Set(
needCompilation
.map(([fleck]) => fleck)
.map((fleck) => this.constructor.root(resolver, fleck)),
))
.forEach((root) => {
const resolved = dirname(R.resolve(join(root, 'package.json')));
const sourcepath = this.constructor.sourcepath(resolved);
const sourceroot = join(sourcepath, '..');
compiledPaths.push(sourceroot);
// @todo Ideally the fleck's 3rd party modules would be externalized.
// Alias this compiled fleck's `node_modules` to the root `node_modules`.
config.resolve.alias[join(sourceroot, 'node_modules')] = join(FLECKS_CORE_ROOT, 'node_modules');
const configFile = this.buildConfig('babel.config.js');
debugSilly('compiling: %s with %s', root, configFile);
const babel = {
configFile,
// Augment the compiler with babel config from `flecks.config.js`.
...babelmerge.all(flecksBabelConfig.map(([, babel]) => babel)),
};
config.module.rules.push(
{
test: /\.(m?jsx?)?$/,
include: [sourceroot],
use: [
{
loader: R.resolve('babel-loader'),
options: {
cacheDirectory: true,
babelrc: false,
configFile: false,
...babel,
},
},
],
},
);
});
const compiledPathsRegex = new RegExp(
`(?:${compiledPaths.map((path) => path.replace(/[\\/]/g, '[\\/]')).join('|')})`,
);
if (!config.optimization) {
config.optimization = {};
}
if (!config.optimization.splitChunks) {
config.optimization.splitChunks = {};
}
if (!config.optimization.splitChunks.cacheGroups) {
config.optimization.splitChunks.cacheGroups = {};
}
config.optimization.splitChunks.cacheGroups.flecksCompiled = {
chunks: 'all',
enforce: true,
priority: 100,
test: compiledPathsRegex,
};
}
}
sourcepath(fleck) {
return this.constructor.sourcepath(fleck);
}
static sourcepath(path) {
let sourcepath = realpathSync(path);
const parts = sourcepath.split('/');
const indexOf = parts.lastIndexOf('dist');
if (-1 !== indexOf) {
parts.splice(indexOf, 1, 'src');
sourcepath = parts.join('/');
sourcepath = join(dirname(sourcepath), basename(sourcepath, extname(sourcepath)));
}
else {
sourcepath = join(sourcepath, 'src');
}
return sourcepath;
}
stubs() {
return this.constructor.stubs(this.platforms, this.flecksConfig);
}
static stubs(platforms, flecksConfig) {
const keys = Object.keys(flecksConfig);
const stubs = [];
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
const config = flecksConfig[key];
if (config.stubs) {
Object.entries(config.stubs)
.forEach(([platform, patterns]) => {
if (-1 !== platforms.indexOf(platform)) {
patterns.forEach((pattern) => {
stubs.push(pattern);
});
}
});
}
}
return stubs;
}
}

View File

@ -1,14 +1,7 @@
import {join} from 'path';
import {inspect} from 'util';
import webpack from 'webpack';
import commands from './commands';
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
const {defaultOptions} = inspect;
defaultOptions.breakLength = 160;
defaultOptions.compact = 6;
@ -17,66 +10,18 @@ defaultOptions.sorted = true;
export {glob} from 'glob';
export {dump as dumpYml, load as loadYml} from 'js-yaml';
export {
Argument,
default as commands,
Option,
processCode,
program,
spawnWith,
} from './commands';
export {default as Flecks} from './flecks';
export {default as require} from '../require';
export {JsonStream, transform} from './stream';
export * from './webpack';
export {commands, processCode, spawnWith} from '../../build/commands';
export {JsonStream, transform} from '../../build/stream';
export * from '../../build/webpack';
export {webpack};
export const hooks = {
'@flecks/core.build': async (target, config, env, argv, flecks) => {
const {
profile,
} = flecks.get('@flecks/core/server');
if (profile.includes(target)) {
config.plugins.push(
new webpack.debug.ProfilingPlugin({
outputPath: join(FLECKS_CORE_ROOT, `profile.build-${target}.json`),
}),
);
}
},
'@flecks/core.build.config': () => [
/**
* 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', {specifier: false}],
/**
* ESLint configuration. See: https://eslint.org/docs/user-guide/configuring/
*/
['eslint.config.js', {specifier: false}],
/**
* Flecks webpack configuration. See: https://webpack.js.org/configuration/
*/
['fleckspack.config.js', {specifier: false}],
/**
* Webpack configuration. See: https://webpack.js.org/configuration/
*/
'webpack.config.js',
],
'@flecks/core.commands': commands,
'@flecks/core.config': () => ({
/**
* The package manager used for tasks.
*/
packageManager: 'npm',
/**
* Build targets to profile with `webpack.debug.ProfilingPlugin`.
*/
profile: [],
'@flecks/web.config': async (req, flecks) => ({
'@flecks/core': {
id: flecks.get('@flecks/core.id'),
packageManager: undefined,
profile: undefined,
},
}),
};

View File

@ -1,4 +0,0 @@
'@flecks/core:../src': {}
'@flecks/core/one:./one': {}
'@flecks/core/one/server:./one/server': {}
'@flecks/core/two:./two': {}

View File

@ -2,8 +2,8 @@ import {expect} from 'chai';
import {Flecks, ById, ByType} from '@flecks/core';
const testOne = require('./one');
const testTwo = require('./two');
const testOne = require('./packages/one');
const testTwo = require('./packages/two');
it('can gather', () => {
const flecks = Flecks.from({

View File

@ -2,7 +2,7 @@ import {expect} from 'chai';
import {Flecks} from '@flecks/core';
const testOne = require('./one');
const testOne = require('./packages/one');
it('can create an empty instance', () => {
const flecks = new Flecks();

View File

@ -7,8 +7,8 @@ chai.use(chaiAsPromised);
const {expect} = chai;
const testOne = require('./one');
const testTwo = require('./two');
const testOne = require('./packages/one');
const testTwo = require('./packages/two');
let flecks;

View File

@ -2,9 +2,9 @@ import {expect} from 'chai';
import {Flecks} from '@flecks/core';
const testOne = require('./one');
const testTwo = require('./two');
const testThree = require('./three');
const testOne = require('./packages/one');
const testTwo = require('./packages/two');
const testThree = require('./packages/three');
it('can make middleware', (done) => {
const flecks = Flecks.from({

View File

@ -1,51 +0,0 @@
import {expect} from 'chai';
// eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies
import {Flecks} from '../../../src/server';
it('bootstraps FLECKS_CORE_ROOT by default', () => {
const flecks = Flecks.bootstrap();
expect(flecks.fleck('@flecks/core')).to.not.equal(undefined);
});
it('bootstraps server platform by default', () => {
const flecks = Flecks.bootstrap();
expect(flecks.fleck('@flecks/core/server')).to.not.equal(undefined);
});
it('can bootstrap from a foreign root', () => {
const flecks = Flecks.bootstrap({
root: './test',
});
expect(flecks.fleck('@flecks/core/one')).to.not.equal(undefined);
expect(flecks.fleck('@flecks/core/two')).to.not.equal(undefined);
});
it('can bootstrap other platforms', () => {
const flecks = Flecks.bootstrap({
platforms: ['client'],
root: './test',
});
expect(flecks.fleck('@flecks/core/one')).to.not.equal(undefined);
expect(flecks.fleck('@flecks/core/one/client')).to.not.equal(undefined);
expect(flecks.fleck('@flecks/core/one/server')).to.not.equal(undefined);
});
it('can exclude platforms', () => {
const flecks = Flecks.bootstrap({
platforms: ['client', '!server'],
root: './test',
});
expect(flecks.fleck('@flecks/core/one')).to.not.equal(undefined);
expect(flecks.fleck('@flecks/core/one/client')).to.not.equal(undefined);
expect(flecks.fleck('@flecks/core/one/server')).to.equal(undefined);
});
it('provides webpack goodies in nodespace', () => {
const flecks = Flecks.bootstrap({
root: './test',
});
flecks.fleck('@flecks/core/one').testNodespace().forEach((result) => {
expect(result).to.not.equal('undefined');
});
});

View File

@ -0,0 +1,80 @@
import {join} from 'path';
import {expect} from 'chai';
import explicate from '@flecks/core/build/explicate';
import Resolver from '@flecks/core/build/resolver';
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
const root = join(FLECKS_CORE_ROOT, 'test', 'server', 'explicate');
function createExplication(paths, platforms) {
const resolver = new Resolver({modules: [join(root, 'fake_node_modules')]});
return explicate(
paths,
{
platforms,
resolver,
root,
importer: (request) => __non_webpack_require__(request),
},
);
}
describe('explication', () => {
it('derives platforms', async () => {
expect(Object.keys((await createExplication(['platformed'])).descriptors))
.to.deep.equal([
'platformed', 'platformed/server',
]);
expect(Object.keys((await createExplication(['server-only'])).descriptors))
.to.deep.equal([
'server-only/server',
]);
});
it('derives through bootstrap', async () => {
expect(Object.keys((await createExplication(['real-root'])).descriptors))
.to.deep.equal([
'dependency', 'dependency/server',
'real-root', 'real-root/server',
]);
});
it('excludes platforms', async () => {
expect(Object.keys(
(await createExplication(
['platformed/client', 'dependency'],
['server', '!client'],
)).descriptors,
))
.to.deep.equal([
'dependency', 'dependency/server',
]);
});
it('explicates parents first', async () => {
expect(Object.keys((await createExplication(['real-root/server'])).descriptors))
.to.deep.equal([
'dependency', 'dependency/server',
'real-root', 'real-root/server',
]);
});
it('explicates only bootstrapped', async () => {
expect(Object.keys((await createExplication(['only-bootstrapped'])).descriptors))
.to.deep.equal([
'only-bootstrapped',
]);
});
it('skips nonexistent', async () => {
expect(await createExplication(['real-root/nonexistent']))
.to.deep.equal({descriptors: {}, roots: {}});
});
});

View File

@ -0,0 +1 @@
exports.dependencies = ['dependency'];

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,24 @@
import {join} from 'path';
import {expect} from 'chai';
import Resolver from '@flecks/core/build/resolver';
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
it('can resolve', async () => {
const resolver = new Resolver();
expect(await resolver.resolve('./test/server/resolve'))
.to.equal(join(FLECKS_CORE_ROOT, 'test', 'server', 'resolve.js'));
});
it('can create aliases at runtime', async () => {
const resolver = new Resolver();
expect(await resolver.resolve('./test/server/foobar'))
.to.be.undefined;
resolver.addAlias('./test/server/foobar', './test/server/resolve');
expect(await resolver.resolve('./test/server/foobar'))
.to.not.be.undefined;
});

View File

@ -1,3 +1,5 @@
#!/usr/bin/env node
import {join} from 'path';
import {
@ -19,7 +21,7 @@ const {
(async () => {
program.argument('<app>', 'name of the app to create');
program.addOption(
new Option('--package-manager <binary>', 'package manager binary')
new Option('-pm,--package-manager <binary>', 'package manager binary')
.choices(['npm', 'bun', 'yarn'])
.default('npm'),
);

View File

@ -1,6 +1,6 @@
const {copy, executable} = require('@flecks/core/server');
// eslint-disable-next-line import/no-extraneous-dependencies
const configFn = require('@flecks/fleck/server/build/fleck.webpack.config');
const configFn = require('@flecks/fleck/build/fleck.webpack.config');
module.exports = async (env, argv, flecks) => {
const config = await configFn(env, argv, flecks);

View File

@ -1,12 +1,7 @@
import {
stat,
} from 'fs/promises';
import {stat} from 'fs/promises';
import {basename, dirname, join} from 'path';
import {
JsonStream,
transform,
} from '@flecks/core/server';
import {JsonStream, transform} from '@flecks/core/server';
import FileTree from './tree';

View File

@ -8,7 +8,7 @@
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"version": "3.0.0",
"bin": {
"create-app": "./cli.js"
},
@ -22,15 +22,14 @@
"files": [
"cli.js",
"server.js",
"src",
"template"
],
"dependencies": {
"@flecks/core": "^2.0.3",
"@flecks/core": "^3.0.0",
"minimatch": "^5.0.1",
"validate-npm-package-name": "^3.0.0"
},
"devDependencies": {
"@flecks/fleck": "^2.0.3"
"@flecks/fleck": "^3.0.0"
}
}

View File

@ -1,5 +1 @@
export {default as validate} from 'validate-npm-package-name';
export {default as build} from './build';
export {default as move, testDestination} from './move';
export {default as FileTree} from './tree';

View File

@ -9,11 +9,11 @@
"start": "DEBUG=@flecks/*,-*:silly npm run dev"
},
"dependencies": {
"@flecks/core": "^2.0.0",
"@flecks/server": "^2.0.0"
"@flecks/core": "3.0.0",
"@flecks/server": "3.0.0"
},
"devDependencies": {
"@flecks/create-fleck": "^2.0.0",
"@flecks/create-fleck": "3.0.0",
"lerna": "^3.22.1"
}
}

View File

@ -1,13 +1,14 @@
import {stat} from 'fs/promises';
import {join} from 'path';
#!/usr/bin/env node
import {
build,
move,
testDestination,
validate,
} from '@flecks/create-app/server';
import {Flecks, program} from '@flecks/core/server';
const {stat} = require('fs/promises');
const {join} = require('path');
const addFleckToYml = require('@flecks/core/build/add-fleck-to-yml');
const {Server, program} = require('@flecks/core/server');
const build = require('@flecks/create-app/build/build');
const move = require('@flecks/create-app/build/move');
const testDestination = require('@flecks/create-app/build/testDestination');
const {validate} = require('@flecks/create-app/server');
const {
FLECKS_CORE_ROOT = process.cwd(),
@ -65,8 +66,8 @@ const target = async (fleck) => {
program.action(async (fleck, o) => {
const {alias, add} = o;
try {
const flecks = await Flecks.bootstrap();
const {packageManager} = flecks.get('@flecks/core/server');
const flecks = await Server.from();
const {packageManager} = flecks.get('@flecks/core');
const isMonorepo = await checkIsMonorepo();
const [scope, pkg] = await target(fleck);
const name = [scope, pkg].filter((e) => !!e).join('/');
@ -86,7 +87,7 @@ const target = async (fleck) => {
await fileTree.writeTo(destination);
await build(packageManager, destination);
if (isMonorepo && add) {
await Flecks.addFleckToYml(...[name].concat(alias ? pkg : []));
await addFleckToYml(...[name].concat(alias ? pkg : []));
}
}
catch (error) {

View File

@ -1,6 +1,6 @@
const {copy, executable} = require('@flecks/core/server');
// eslint-disable-next-line import/no-extraneous-dependencies
const configFn = require('@flecks/fleck/server/build/fleck.webpack.config');
const configFn = require('@flecks/fleck/build/fleck.webpack.config');
module.exports = async (env, argv, flecks) => {
const config = await configFn(env, argv, flecks);

View File

@ -8,7 +8,7 @@
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"version": "3.0.0",
"bin": {
"create-fleck": "./cli.js"
},
@ -20,15 +20,13 @@
"test": "flecks test"
},
"files": [
"cli.js",
"src",
"template"
],
"dependencies": {
"@flecks/core": "^2.0.3",
"@flecks/create-app": "^2.0.3"
"@flecks/core": "^3.0.0",
"@flecks/create-app": "^3.0.0"
},
"devDependencies": {
"@flecks/fleck": "^2.0.3"
"@flecks/fleck": "^3.0.0"
}
}

View File

@ -11,9 +11,9 @@
"index.js"
],
"dependencies": {
"@flecks/core": "^2.0.0"
"@flecks/core": "3.0.0"
},
"devDependencies": {
"@flecks/fleck": "^2.0.0"
"@flecks/fleck": "3.0.0"
}
}

View File

@ -8,7 +8,7 @@
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"version": "3.0.0",
"scripts": {
"build": "flecks build",
"clean": "flecks clean",
@ -20,11 +20,11 @@
"server.js"
],
"dependencies": {
"@flecks/core": "^2.0.3",
"@flecks/core": "^3.0.0",
"sequelize": "^6.3.5",
"sqlite3": "^5.0.2"
},
"devDependencies": {
"@flecks/fleck": "^2.0.3"
"@flecks/fleck": "^3.0.0"
}
}

View File

@ -8,7 +8,7 @@
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"version": "3.0.0",
"main": "index.js",
"scripts": {
"build": "flecks build",
@ -21,10 +21,10 @@
"server.js"
],
"dependencies": {
"@flecks/core": "^2.0.3",
"@flecks/core": "^3.0.0",
"debug": "^4.3.3"
},
"devDependencies": {
"@flecks/fleck": "^2.0.3"
"@flecks/fleck": "^3.0.0"
}
}

View File

@ -1,24 +1,25 @@
import {
const {
access,
cp,
mkdir,
rename,
rmdir,
} from 'fs/promises';
import {dirname, join} from 'path';
} = require('fs/promises');
const {dirname, join} = require('path');
import {
const {Argument} = require('@flecks/core/build/commands');
const {
generate,
resolveSiteDir,
spawn,
} from './docusaurus';
} = require('./docusaurus');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
export default (program, flecks) => {
const {Argument} = flecks.fleck('@flecks/core/server');
module.exports = (program, flecks) => {
const commands = {};
const siteDirArgument = new Argument('[siteDir]', 'Docusaurus directory');
siteDirArgument.defaultValue = 'website';

View File

@ -1,23 +1,23 @@
import {mkdir, writeFile} from 'fs/promises';
import {isAbsolute, join, resolve} from 'path';
const {mkdir, writeFile} = require('fs/promises');
const {isAbsolute, join, resolve} = require('path');
import {spawnWith} from '@flecks/core/server';
import {themes as prismThemes} from 'prism-react-renderer';
import {rimraf} from 'rimraf';
const {spawnWith} = require('@flecks/core/server');
const {themes: prismThemes} = require('prism-react-renderer');
const {rimraf} = require('rimraf');
import {
const {
generateBuildConfigsPage,
generateConfigPage,
generateHookPage,
generateTodoPage,
} from './generate';
import {parseFlecks} from './parser';
} = require('./generate');
const {parseFlecks} = require('./parser');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
export function configDefaults() {
exports.configDefaults = function configDefaults() {
/** @type {import('@docusaurus/types').Config} */
const config = {
tagline: 'built with flecks',
@ -49,15 +49,15 @@ export function configDefaults() {
},
};
return config;
}
};
export function resolveSiteDir(siteDir) {
exports.resolveSiteDir = function resolveSiteDir(siteDir) {
return isAbsolute(siteDir)
? siteDir
: resolve(FLECKS_CORE_ROOT, siteDir);
}
};
export async function generate(flecks, siteDir) {
exports.generate = async function generate(flecks, siteDir) {
// Generate "docs".
const docsDirectory = join(siteDir, 'docs', 'flecks');
await rimraf(docsDirectory);
@ -72,9 +72,9 @@ export async function generate(flecks, siteDir) {
await writeFile(join(generatedDirectory, 'TODO.md'), todoPage);
await writeFile(join(generatedDirectory, 'build-configs.md'), buildConfigsPage);
await writeFile(join(generatedDirectory, 'config.mdx'), configPage);
}
};
export function spawn(subcommand, siteDir) {
exports.spawn = function spawn(subcommand, siteDir) {
const args = [];
switch (subcommand) {
case 'start':
@ -112,4 +112,4 @@ export function spawn(subcommand, siteDir) {
child.kill();
});
return child;
}
};

View File

@ -1,8 +1,6 @@
import commands from './commands';
const commands = require('./commands');
export {configDefaults} from './docusaurus';
export const hooks = {
exports.hooks = {
'@flecks/core.commands': commands,
'@flecks/core.config': () => ({
/**

View File

@ -1,2 +0,0 @@
'@flecks/core': {}
'@flecks/fleck': {}

View File

@ -6,7 +6,7 @@ const makeFilenameRewriter = (filenameRewriters) => (filename, line, column) =>
)
);
export const generateBuildConfigsPage = (buildConfigs) => {
exports.generateBuildConfigsPage = (buildConfigs) => {
const source = [];
source.push('# Build configuration');
source.push('');
@ -25,9 +25,9 @@ export const generateBuildConfigsPage = (buildConfigs) => {
return source.join('\n');
};
export const generateConfigPage = (configs) => {
exports.generateConfigPage = (configs) => {
const source = [];
source.push("import CodeBlock from '@theme/CodeBlock';");
source.push("const CodeBlock = require('@theme/CodeBlock');");
source.push('');
source.push('# Fleck configuration');
source.push('');
@ -61,8 +61,8 @@ export const generateConfigPage = (configs) => {
return source.join('\n');
};
export const generateHookPage = (hooks, flecks) => {
const {filenameRewriters} = flecks.get('@flecks/dox/server');
exports.generateHookPage = (hooks, flecks) => {
const {filenameRewriters} = flecks.get('@flecks/dox');
const rewriteFilename = makeFilenameRewriter(filenameRewriters);
const source = [];
source.push('# Hooks');
@ -126,8 +126,8 @@ export const generateHookPage = (hooks, flecks) => {
return source.join('\n');
};
export const generateTodoPage = (todos, flecks) => {
const {filenameRewriters} = flecks.get('@flecks/dox/server');
exports.generateTodoPage = (todos, flecks) => {
const {filenameRewriters} = flecks.get('@flecks/dox');
const rewriteFilename = makeFilenameRewriter(filenameRewriters);
const source = [];
source.push('# TODO list');

View File

@ -1,14 +1,14 @@
import {readFile} from 'fs/promises';
import {
const {readFile} = require('fs/promises');
const {
basename,
dirname,
extname,
join,
} from 'path';
} = require('path');
import {transformAsync} from '@babel/core';
import traverse from '@babel/traverse';
import {
const {transformAsync} = require('@babel/core');
const traverse = require('@babel/traverse');
const {
isArrayExpression,
isArrowFunctionExpression,
isIdentifier,
@ -18,9 +18,9 @@ import {
isStringLiteral,
isThisExpression,
isVariableDeclaration,
} from '@babel/types';
import {glob, require as R} from '@flecks/core/server';
import {parse as parseComment} from 'comment-parser';
} = require('@babel/types');
const {glob} = require('@flecks/core/server');
const {parse: parseComment} = require('comment-parser');
class ParserState {
@ -272,7 +272,7 @@ const FlecksTodos = (state, filename) => ({
},
});
export const parseCode = async (code) => {
exports.parseCode = async (code) => {
const config = {
ast: true,
code: false,
@ -281,10 +281,10 @@ export const parseCode = async (code) => {
return ast;
};
export const parseFile = async (filename, resolved, state) => {
exports.parseFile = async (filename, resolved, state) => {
const buffer = await readFile(filename);
const source = buffer.toString('utf8');
const ast = await parseCode(source);
const ast = await exports.parseCode(source);
traverse(ast, FlecksBuildConfigs(state, resolved));
traverse(ast, FlecksConfigs(state, resolved, source));
traverse(ast, FlecksInvocations(state, resolved));
@ -294,19 +294,19 @@ export const parseFile = async (filename, resolved, state) => {
const fleckSources = async (path) => glob(join(path, 'src', '**', '*.js'));
export const parseFleckRoot = async (root, state) => {
const resolved = dirname(R.resolve(join(root, 'package.json')));
exports.parseFleckRoot = async (root, state) => {
const resolved = dirname(require.resolve(join(root, 'package.json')));
const sources = await fleckSources(resolved);
await Promise.all(
sources.map(async (source) => {
// @todo Aliased fleck paths are gonna be bad.
await parseFile(source, join(root, source.slice(resolved.length)), state);
await exports.parseFile(source, join(root, source.slice(resolved.length)), state);
}),
);
try {
const buffer = await readFile(join(resolved, 'build', 'dox', 'hooks.js'));
const source = buffer.toString('utf8');
const ast = await parseCode(source);
const ast = await exports.parseCode(source);
traverse(ast, FlecksSpecifications(state, source));
}
catch (error) {
@ -316,7 +316,7 @@ export const parseFleckRoot = async (root, state) => {
}
};
export const parseFlecks = async (flecks) => {
exports.parseFlecks = async (flecks) => {
const state = new ParserState();
const paths = Object.keys(flecks.resolver);
const roots = Array.from(new Set(
@ -327,7 +327,7 @@ export const parseFlecks = async (flecks) => {
await Promise.all(
roots
.map(async (root) => {
await parseFleckRoot(root, state);
await exports.parseFleckRoot(root, state);
}),
);
return state;

View File

@ -8,7 +8,7 @@
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"version": "3.0.0",
"main": "index.js",
"scripts": {
"build": "flecks build",
@ -18,7 +18,6 @@
"test": "flecks test"
},
"files": [
"server.js",
"website"
],
"dependencies": {
@ -29,7 +28,7 @@
"@docusaurus/module-type-aliases": "3.0.1",
"@docusaurus/preset-classic": "3.0.1",
"@docusaurus/types": "3.0.1",
"@flecks/core": "^2.0.3",
"@flecks/core": "^3.0.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"comment-parser": "^1.3.0",
@ -40,6 +39,6 @@
"rimraf": "^5.0.5"
},
"devDependencies": {
"@flecks/fleck": "^2.0.3"
"@flecks/fleck": "^3.0.0"
}
}

View File

@ -4,10 +4,9 @@
// There are various equivalent ways to declare your Docusaurus config.
// See: https://docusaurus.io/docs/api/docusaurus-config
// For some reason we get a webpack warning if we use import here...
const {configDefaults} = require('@flecks/dox/server'); // eslint-disable-line import/no-extraneous-dependencies
const {configDefaults} = require('@flecks/dox/build/docusaurus');
export default async function flecksDocusaurus() {
module.exports = async function flecksDocusaurus() {
const defaults = configDefaults();
/** @type {import('@docusaurus/types').Config} */
const config = {
@ -18,4 +17,4 @@ export default async function flecksDocusaurus() {
baseUrl: '/',
};
return config;
}
};

View File

@ -1,9 +1,9 @@
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
const clsx = require('clsx');
const Link = require('@docusaurus/Link');
const useDocusaurusContext = require('@docusaurus/useDocusaurusContext');
const Layout = require('@theme/Layout');
import Heading from '@theme/Heading';
const Heading = require('@theme/Heading');
import styles from './index.module.css';
function HomepageHeader() {

View File

@ -0,0 +1,59 @@
const {join} = require('path');
const {banner} = require('@flecks/core/server');
exports.hooks = {
'@flecks/core.build': (target, config) => {
if ('server' === target) {
config.plugins.push(
banner({
// Bootstrap our `require()` magic.
banner: "require('module').Module._initPaths();",
include: 'index.js',
}),
);
}
},
'@flecks/core.config': () => ({
/**
* Browser window options.
*
* See: https://www.electronjs.org/docs/latest/api/browser-window
*/
browserWindowOptions: {},
/**
* Install devtools extensions (by default).
*
* 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.
*
* Extensions will not be installed if `'production' === process.env.NODE_ENV`
*/
installExtensions: true,
/**
* Quit the app when all windows are closed.
*/
quitOnClosed: true,
/**
* The URL to load in electron by default.
*
* Defaults to `http://${flecks.get('@flecks/web.public')}`.
*/
url: undefined,
}),
'@flecks/core.build.alter': (configs) => {
const {server: config} = configs;
if (config) {
const plugin = config.plugins.find(({pluginName}) => pluginName === 'StartServerPlugin');
// Extremely hackish, c'est la vie.
if (plugin) {
const {exec} = plugin.options;
plugin.options.exec = (compilation) => {
plugin.options.args = [join(config.output.path, compilation.getPath(exec))];
return join('..', '..', 'node_modules', '.bin', 'electron');
};
}
}
},
};

View File

@ -8,7 +8,7 @@
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"version": "3.0.0",
"scripts": {
"build": "flecks build",
"clean": "flecks clean",
@ -20,11 +20,11 @@
"server.js"
],
"dependencies": {
"@flecks/core": "^2.0.3",
"@flecks/core": "^3.0.0",
"electron": "^18.0.1",
"electron-devtools-installer": "^3.2.0"
},
"devDependencies": {
"@flecks/fleck": "^2.0.3"
"@flecks/fleck": "^3.0.0"
}
}

View File

@ -1,7 +1,4 @@
import {join} from 'path';
import {Flecks} from '@flecks/core';
import {banner} from '@flecks/core/server';
const electron = __non_webpack_require__('electron');
@ -13,66 +10,13 @@ let win;
async function createWindow(flecks) {
const {BrowserWindow} = flecks.electron;
const {browserWindowOptions} = flecks.get('@flecks/electron/server');
flecks.invoke('@flecks/electron/server.browserWindowOptions.alter', browserWindowOptions)
const {browserWindowOptions} = flecks.get('@flecks/electron');
flecks.invoke('@flecks/electron/server.browserWindowOptions.alter', browserWindowOptions);
win = new BrowserWindow(browserWindowOptions);
await flecks.invokeSequentialAsync('@flecks/electron/server.window', win);
}
export const hooks = {
'@flecks/core.build': (target, config) => {
if ('server' === target) {
config.plugins.push(
banner({
// Bootstrap our `require()` magic.
banner: "require('module').Module._initPaths();",
include: 'index.js',
}),
);
}
},
'@flecks/core.config': () => ({
/**
* Browser window options.
*
* See: https://www.electronjs.org/docs/latest/api/browser-window
*/
browserWindowOptions: {},
/**
* Install devtools extensions (by default).
*
* 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.
*
* Extensions will not be installed if `'production' === process.env.NODE_ENV`
*/
installExtensions: true,
/**
* Quit the app when all windows are closed.
*/
quitOnClosed: true,
/**
* The URL to load in electron by default.
*
* Defaults to `http://${flecks.get('@flecks/web/server.public')}`.
*/
url: undefined,
}),
'@flecks/core.build.alter': (configs) => {
const {server: config} = configs;
if (config) {
const plugin = config.plugins.find(({pluginName}) => pluginName === 'StartServerPlugin');
// Extremely hackish, c'est la vie.
if (plugin) {
const {exec} = plugin.options;
plugin.options.exec = (compilation) => {
plugin.options.args = [join(config.output.path, compilation.getPath(exec))];
return join('..', '..', 'node_modules', '.bin', 'electron');
};
}
}
},
'@flecks/core.mixin': (Flecks) => (
class FlecksWithElectron extends Flecks {
@ -82,7 +26,7 @@ export const hooks = {
),
'@flecks/electron/server.initialize': async (electron, flecks) => {
electron.app.on('window-all-closed', () => {
const {quitOnClosed} = flecks.get('@flecks/electron/server');
const {quitOnClosed} = flecks.get('@flecks/electron');
if (!quitOnClosed) {
return;
}
@ -101,11 +45,11 @@ export const hooks = {
await createWindow(flecks);
},
'@flecks/electron/server.window': async (win, flecks) => {
const {public: $$public} = flecks.get('@flecks/web/server');
const {public: $$public} = flecks.get('@flecks/web');
const {
installExtensions,
url = `http://${$$public}`,
} = flecks.get('@flecks/electron/server');
} = flecks.get('@flecks/electron');
if (installExtensions && 'production' !== NODE_ENV) {
const {
default: installExtension,

View File

@ -1,11 +1,11 @@
import {stat, unlink} from 'fs/promises';
import {join} from 'path';
const {stat, unlink} = require('fs/promises');
const {join} = require('path');
import {D} from '@flecks/core';
import {commands as coreCommands, glob} from '@flecks/core/server';
import chokidar from 'chokidar';
import clearModule from 'clear-module';
import Mocha from 'mocha';
const {D} = require('@flecks/core');
const {commands: coreCommands, glob} = require('@flecks/core/server');
const chokidar = require('chokidar');
const clearModule = require('clear-module');
const Mocha = require('mocha');
const debug = D('@flecks/core.commands');
@ -13,7 +13,7 @@ const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
export default (program, flecks) => {
module.exports = (program, flecks) => {
const commands = {};
commands.test = {
options: [
@ -27,7 +27,7 @@ export default (program, flecks) => {
watch,
} = opts;
const {build} = coreCommands(program, flecks);
const child = build.action(undefined, opts);
const child = await build.action(undefined, opts);
const testPaths = await glob(join(FLECKS_CORE_ROOT, 'test/*.js'));
if (0 === testPaths.length) {
// eslint-disable-next-line no-console
@ -71,6 +71,7 @@ export default (program, flecks) => {
});
});
};
require('@flecks/core/build/stub')(flecks.stubs);
if (!watch) {
await new Promise((resolve, reject) => {
child.on('exit', (code) => {

View File

@ -1,10 +1,10 @@
const flecksConfigFn = require('@flecks/core/server/build/webpack.config');
const flecksConfigFn = require('@flecks/core/build/fleck.webpack.config');
const ProcessAssets = require('./process-assets');
module.exports = async (env, argv, flecks) => {
const config = await flecksConfigFn(env, argv, flecks);
config.plugins.push(new ProcessAssets(flecks));
config.stats = flecks.get('@flecks/fleck/server.stats');
config.stats = flecks.get('@flecks/fleck.stats');
return config;
};

View File

@ -1,14 +1,14 @@
import {join} from 'path';
const {join} = require('path');
import {glob} from '@flecks/core/server';
const {glob} = require('@flecks/core/server');
import commands from './commands';
const commands = require('./commands');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
export const hooks = {
exports.hooks = {
'@flecks/core.commands': commands,
'@flecks/core.config': () => ({
/**
@ -20,20 +20,24 @@ export const hooks = {
},
}),
'@flecks/core.targets': () => ['fleck'],
'@flecks/fleck/server.processAssets': async (assets, compilation, flecks) => {
'@flecks/fleck.processAssets': async (assets, compilation, flecks) => {
const {RawSource} = compilation.compiler.webpack.sources;
const packageJson = assets['package.json'];
const json = JSON.parse(packageJson.source().toString());
const {files} = json;
// Add defaults.
files.push('build', 'src');
files.push('build');
// Add source if it exists.
if ((await glob(join(FLECKS_CORE_ROOT, 'src/**/*.js'))).length > 0) {
files.push('src');
}
// Add tests if they exist.
const testFiles = await glob(join(FLECKS_CORE_ROOT, 'test/*.js'));
const testFiles = await glob(join(FLECKS_CORE_ROOT, 'test/**/*.js'));
if (testFiles.length > 0) {
files.push('test', 'test.js');
}
// Let others have a say.
await flecks.invokeSequentialAsync('@flecks/fleck/server.packageJson', json, compilation);
await flecks.invokeSequentialAsync('@flecks/fleck.packageJson', json, compilation);
// Add any sourcemaps.
json.files = json.files
.map((filename) => {

View File

@ -1,2 +1,2 @@
'@flecks/core': {}
'@flecks/fleck:./src': {}
'@flecks/fleck:.': {}

Some files were not shown because too many files have changed in this diff Show More