feat: hook provision through context

This commit is contained in:
cha0s 2024-02-14 03:08:39 -06:00
parent ffe634dce8
commit f3e85ee8fb
5 changed files with 184 additions and 113 deletions

196
package-lock.json generated
View File

@ -16866,7 +16866,7 @@
},
"packages/build": {
"name": "@flecks/build",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.12.10",
@ -16877,7 +16877,7 @@
"@babel/preset-env": "^7.12.11",
"@babel/traverse": "^7.17.0",
"@babel/types": "^7.17.0",
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"babel-loader": "^9.1.3",
"babel-merge": "^3.0.0",
"chai": "4.2.0",
@ -16934,9 +16934,10 @@
},
"packages/core": {
"name": "@flecks/core",
"version": "4.0.5",
"version": "4.1.1",
"license": "MIT",
"dependencies": {
"callsites": "^3.1.0",
"debug": "4.3.1",
"glob": "^10.3.10",
"js-yaml": "4.1.0",
@ -16989,10 +16990,10 @@
},
"packages/create-app": {
"name": "@flecks/create-app",
"version": "4.0.5",
"version": "4.0.8",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"commander": "11.1.0",
"validate-npm-package-name": "^3.0.0"
},
@ -17000,8 +17001,8 @@
"create-app": "build/cli.js"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/create-app/node_modules/builtins": {
@ -17019,45 +17020,45 @@
},
"packages/create-fleck": {
"name": "@flecks/create-fleck",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"commander": "11.1.0"
},
"bin": {
"create-fleck": "build/cli.js"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/db": {
"name": "@flecks/db",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"sequelize": "^6.3.5",
"sqlite3": "^5.0.2"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/docker": {
"name": "@flecks/docker",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"debug": "^4.3.3"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/docker/node_modules/debug": {
@ -17078,47 +17079,47 @@
},
"packages/dox": {
"name": "@flecks/dox",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.17.2",
"@babel/traverse": "^7.17.0",
"@babel/types": "^7.17.0",
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"comment-parser": "^1.3.0",
"rimraf": "^5.0.5"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/electron": {
"name": "@flecks/electron",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"electron": "^28.1.4",
"electron-devtools-installer": "^3.2.0"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/fleck": {
"name": "@flecks/fleck",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"babel-merge": "^3.0.0",
"debug": "^4.3.3",
"mocha": "^10.2.0"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/build": "^4.0.7",
"chai": "4.2.0"
}
},
@ -17140,75 +17141,75 @@
},
"packages/passport": {
"name": "@flecks/passport",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/db": "^4.0.5",
"@flecks/redux": "^4.0.5",
"@flecks/session": "^4.0.5",
"@flecks/core": "^4.1.1",
"@flecks/db": "^4.0.7",
"@flecks/redux": "^4.0.7",
"@flecks/session": "^4.0.7",
"passport": "^0.7.0"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/passport-local": {
"name": "@flecks/passport-local",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/passport": "^4.0.5",
"@flecks/core": "^4.1.1",
"@flecks/passport": "^4.0.7",
"bcrypt": "^5.1.1",
"passport-local": "^1.0.0"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/passport-local-react": {
"name": "@flecks/passport-local-react",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/passport-local": "^4.0.5",
"@flecks/passport-react": "^4.0.5",
"@flecks/react": "^4.0.5"
"@flecks/core": "^4.1.1",
"@flecks/passport-local": "^4.0.7",
"@flecks/passport-react": "^4.0.7",
"@flecks/react": "^4.0.7"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/passport-react": {
"name": "@flecks/passport-react",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/passport": "^4.0.5",
"@flecks/react": "^4.0.5",
"@flecks/react-redux": "^4.0.5",
"@flecks/web": "^4.0.5"
"@flecks/core": "^4.1.1",
"@flecks/passport": "^4.0.7",
"@flecks/react": "^4.0.7",
"@flecks/react-redux": "^4.0.7",
"@flecks/web": "^4.0.7"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/react": {
"name": "@flecks/react",
"version": "4.0.5",
"version": "4.0.7",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@babel/preset-react": "^7.23.3",
"@flecks/core": "^4.0.5",
"@flecks/web": "^4.0.5",
"@flecks/core": "^4.1.1",
"@flecks/web": "^4.0.7",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"babel-merge": "^3.0.0",
"classnames": "^2.3.1",
@ -17224,55 +17225,55 @@
"redux-first-history": "5.1.1"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/react-redux": {
"name": "@flecks/react-redux",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/react": "^4.0.5",
"@flecks/redux": "^4.0.5",
"@flecks/core": "^4.1.1",
"@flecks/react": "^4.0.7",
"@flecks/redux": "^4.0.7",
"react-redux": "^7.2.2"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/redis": {
"name": "@flecks/redis",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"@socket.io/redis-adapter": "7.1.0",
"connect-redis": "^5.0.0",
"express-session": "^1.17.1",
"redis": "4.0.3"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/redux": {
"name": "@flecks/redux",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"@reduxjs/toolkit": "^1.5.0",
"debug": "^4.3.3",
"lodash.throttle": "^4.1.1",
"reduce-reducers": "^1.0.4"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/redux/node_modules/debug": {
@ -17293,51 +17294,51 @@
},
"packages/repl": {
"name": "@flecks/repl",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"command-exists": "^1.2.9",
"debug": "4.3.1"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/server": {
"name": "@flecks/server",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5"
"@flecks/core": "^4.1.1"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/session": {
"name": "@flecks/session",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"express": "^4.18.2",
"express-session": "^1.17.3"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/socket": {
"name": "@flecks/socket",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@flecks/core": "^4.0.5",
"@flecks/react": "^4.0.5",
"@flecks/core": "^4.1.1",
"@flecks/react": "^4.0.7",
"msgpack-lite": "^0.1.26",
"proxy-addr": "^2.0.6",
"schemapack": "^1.4.2",
@ -17345,18 +17346,19 @@
"socket.io-client": "^4.1.2"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5"
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7"
}
},
"packages/web": {
"name": "@flecks/web",
"version": "4.0.5",
"version": "4.0.7",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.17.0",
"@babel/types": "^7.17.0",
"@flecks/core": "^4.0.5",
"@flecks/core": "^4.1.1",
"@flecks/server": "^4.0.7",
"@webpack-cli/serve": "^2.0.5",
"add-asset-html-webpack-plugin": "^6.0.0",
"assert": "^2.1.0",
@ -17389,8 +17391,8 @@
"webpack-dev-server": "^4.15.1"
},
"devDependencies": {
"@flecks/build": "^4.0.5",
"@flecks/fleck": "^4.0.5",
"@flecks/build": "^4.0.7",
"@flecks/fleck": "^4.0.7",
"puppeteer": "^22.0.0"
}
}

View File

@ -4,6 +4,8 @@ const {
dirname,
extname,
join,
resolve,
sep,
} = require('path');
const get = require('lodash.get');
@ -127,6 +129,46 @@ class Flecks {
});
}
static context(
directory,
useSubdirectories = true,
regExp = /^\.\/.*$/,
) {
// eslint-disable-next-line no-eval
const R = eval('require');
if (!R) {
throw new Error('Flecks.context is only meant as an escape hatch for bootstrap scripts.');
}
const caller = dirname(R('callsites')()[1].getFileName());
const {glob} = R('glob');
const filenames = glob.sync(
useSubdirectories ? '**/*' : '*',
{cwd: resolve(caller, directory), nodir: true},
);
const map = {};
filenames.forEach((filename) => {
const normalized = ['.', '/'].some((char) => filename.startsWith(char))
? filename
: `.${sep}${filename}`;
if (normalized.match(regExp)) {
map[normalized] = filename;
map[normalized.slice(0, normalized.lastIndexOf('.'))] = filename;
}
});
const resolver = (key) => map[key];
const context = (request) => {
if (!resolver(request)) {
throw new Error(`Cannot find module '${request}'`);
}
return R(join(caller, directory, request));
};
const qualified = join(caller, directory);
context.id = `${qualified} sync${useSubdirectories ? ' recursive' : ''} ${regExp.source}`;
context.keys = () => Object.keys(map);
context.resolve = resolver;
return context;
}
/**
* [Dasherize]{@link https://en.wiktionary.org/wiki/dasherize} a fleck path.
*
@ -533,18 +575,32 @@ class Flecks {
return get(this.config, path, defaultValue);
}
/**
* Check whether an updated module changed its hook implementation.
*
* @param {*} fleck The fleck implementing the hook.
* @param {*} hook The hook.
* @param {*} M The updated module.
* @returns {boolean}
*/
implementationChanged(fleck, hook, M) {
return (
(this.fleckImplementation(fleck, hook) || M.hooks?.[hook])
&& this.fleckImplementation(fleck, hook)?.toString() !== M.hooks?.[hook]?.toString()
static hooks(context) {
const implementations = {};
context.keys()
.forEach((key) => {
const id = context.resolve(key);
if (!implementations[id]) {
implementations[id] = [];
}
implementations[id].push(key);
});
return Object.fromEntries(
Object.entries(implementations)
.map(([, keys]) => {
// Shortest is the one without extension.
const key = keys.reduce((l, r) => (r.length < l.length ? r : l));
const trimmed = key.startsWith('./') ? key.slice(2) : key;
const M = context(key);
if (!M.hook) {
const hasDefault = !!M.default;
throw new Error([
`'${context.id}${key}' must have implementation at named export 'hook'!`,
...(hasDefault ? ['Did you default export the implementation?'] : []),
].join(' '));
}
return [trimmed, context(key).hook];
}),
);
}
@ -908,6 +964,18 @@ class Flecks {
*/
refresh(fleck, M) {
this.constructor.debug('refreshing %s...', fleck);
// Notify about hook implementation updates.
const previousM = this.fleck(fleck);
[...new Set([
...(Object.keys(previousM.hooks) || []),
...(Object.keys(M.hooks) || []),
])]
.forEach((hook) => {
if (previousM.hooks?.[hook] !== M.hooks?.[hook]) {
debug("HMR update for hook '%s' implemented by '%s'", hook, fleck);
this.invokeSequential('@flecks/core.hmr.hook', hook, fleck);
}
});
// Remove old hook implementations.
this.unregisterFleckHooks(fleck);
// Replace the fleck.

View File

@ -22,6 +22,7 @@
"server.js"
],
"dependencies": {
"callsites": "^3.1.0",
"debug": "4.3.1",
"glob": "^10.3.10",
"js-yaml": "4.1.0",

View File

@ -7,8 +7,8 @@ const {
} = process.env;
export const hooks = {
'@flecks/core.hmr': (path, M, flecks) => {
if (flecks.implementationChanged(path, '@flecks/server.up', M)) {
'@flecks/core.hmr.hook': (hook) => {
if ('@flecks/server.up' === hook) {
if (cluster.isWorker) {
cluster.worker.disconnect();
const error = new Error('@flecks/server.up implementation changed!');

View File

@ -1,6 +1,6 @@
export const hooks = {
'@flecks/core.hmr': (path, M, flecks) => {
if (flecks.implementationChanged(path, '@flecks/web/client.up', M)) {
'@flecks/core.hmr.hook': (hook) => {
if ('@flecks/web/client.up' !== hook) {
throw new Error('@flecks/web/client.up implementation changed!');
}
},