diff --git a/TODO.md b/TODO.md index f176e7b..284e0bf 100644 --- a/TODO.md +++ b/TODO.md @@ -19,7 +19,7 @@ - [x] renamed to 'build/config'? - [x] automatically generated list of build config - [ ] static documentation site generator -- [ ] autogenerated config dox page +- [x] autogenerated config dox page - [x] remove `invokeParallel()` - [x] Specialize `invokeReduce()` with `invokeMerge()`. - [x] Rename all hooks to dot-first notation; rewrite `lookupFlecks()`. \ No newline at end of file diff --git a/packages/core/build/dox/hooks.js b/packages/core/build/dox/hooks.js index 7ae4d15..3f34046 100644 --- a/packages/core/build/dox/hooks.js +++ b/packages/core/build/dox/hooks.js @@ -69,6 +69,9 @@ export default { whatever: 'configuration', your: 1337, fleck: 'needs', + /** + * Also, comments like this will be used to automatically generate documentation. + */ though: 'you should keep the values serializable', }), diff --git a/packages/core/src/index.js b/packages/core/src/index.js index a60e9f1..625594d 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -16,6 +16,9 @@ export { export default { [Hooks]: { '@flecks/core.config': () => ({ + /** + * The ID of your application. + */ id: 'flecks', }), }, diff --git a/packages/core/src/server/index.js b/packages/core/src/server/index.js index 48a3414..b9b7616 100644 --- a/packages/core/src/server/index.js +++ b/packages/core/src/server/index.js @@ -63,6 +63,9 @@ export default { ], '@flecks/core.commands': commands, '@flecks/core.config': () => ({ + /** + * Build targets to exclude from ESLint. + */ 'eslint.exclude': [], }), }, diff --git a/packages/db/src/server.js b/packages/db/src/server.js index 7ff9c09..45eed1d 100644 --- a/packages/db/src/server.js +++ b/packages/db/src/server.js @@ -12,11 +12,31 @@ export {createDatabaseConnection}; export default { [Hooks]: { '@flecks/core.config': () => ({ + /** + * The database to connect to. + */ database: ':memory:', + /** + * SQL dialect. + * + * See: https://sequelize.org/v5/manual/dialects.html + */ dialect: 'sqlite', + /** + * Database server host. + */ host: undefined, + /** + * Database server password. + */ password: undefined, + /** + * Database server port. + */ port: undefined, + /** + * Database server username. + */ username: undefined, }), '@flecks/core.starting': (flecks) => { diff --git a/packages/docker/src/server.js b/packages/docker/src/server.js index b8bf429..413df06 100644 --- a/packages/docker/src/server.js +++ b/packages/docker/src/server.js @@ -6,6 +6,9 @@ import startContainer from './start-container'; export default { [Hooks]: { '@flecks/core.config': () => ({ + /** + * Whether to run docker containers. + */ enabled: true, }), '@flecks/core.commands': commands, diff --git a/packages/dox/src/commands.js b/packages/dox/src/commands.js index eaca010..1bf9f32 100644 --- a/packages/dox/src/commands.js +++ b/packages/dox/src/commands.js @@ -5,6 +5,7 @@ import {D} from '@flecks/core'; import { generateBuildConfigsPage, + generateConfigPage, generateHookPage, generateTodoPage, } from './generate'; @@ -33,6 +34,9 @@ export default (program, flecks) => { debug('Generating build configs page...'); const buildConfigsPage = generateBuildConfigsPage(state.buildConfigs); debug('generated'); + debug('Generating config page...'); + const configPage = generateConfigPage(state.configs); + debug('generated'); const output = join(FLECKS_CORE_ROOT, 'dox'); await mkdir(output, {recursive: true}); /* eslint-disable no-console */ @@ -47,6 +51,9 @@ export default (program, flecks) => { debug('Writing build configs page...'); await writeFile(join(output, 'build-configs.md'), buildConfigsPage); console.log('build-configs.md'); + debug('Writing config page...'); + await writeFile(join(output, 'config.md'), configPage); + console.log('config.md'); console.groupEnd(); console.log(''); /* eslint-enable no-console */ diff --git a/packages/dox/src/generate.js b/packages/dox/src/generate.js index 341cf3b..cb0b092 100644 --- a/packages/dox/src/generate.js +++ b/packages/dox/src/generate.js @@ -25,6 +25,31 @@ export const generateBuildConfigsPage = (buildConfigs) => { return source.join('\n'); }; +export const generateConfigPage = (configs) => { + const source = []; + source.push('# Configuration'); + source.push(''); + source.push('This page documents all the configuration in this project.'); + source.push(''); + Object.entries(configs) + .sort(([l], [r]) => (l < r ? -1 : 1)) + .forEach(([fleck, configs]) => { + source.push(`## \`${fleck}\``); + source.push(''); + configs.forEach(({comment, config, defaultValue}) => { + comment.split('\n').forEach((line) => { + source.push(`> ${line}`); + }); + source.push(''); + source.push('```javascript'); + source.push(`${config}: ${defaultValue}`); + source.push('```'); + source.push(''); + }); + }); + return source.join('\n'); +}; + export const generateHookPage = (hooks, flecks) => { const {filenameRewriters} = flecks.get('@flecks/dox/server'); const rewriteFilename = makeFilenameRewriter(filenameRewriters); diff --git a/packages/dox/src/parser.js b/packages/dox/src/parser.js index bac88c1..ff70fc1 100644 --- a/packages/dox/src/parser.js +++ b/packages/dox/src/parser.js @@ -1,5 +1,10 @@ import {readFile} from 'fs/promises'; -import {dirname, join} from 'path'; +import { + basename, + dirname, + extname, + join, +} from 'path'; import {transformAsync} from '@babel/core'; import traverse from '@babel/traverse'; @@ -23,6 +28,7 @@ class ParserState { constructor() { this.buildConfigs = []; + this.configs = {}; this.hooks = {}; this.todos = []; } @@ -31,6 +37,20 @@ class ParserState { this.buildConfigs.push({comment, config}); } + addConfig(config, comment, filename, defaultValue) { + const ext = extname(filename); + const trimmed = join(dirname(filename), basename(filename, ext)).replace('/src', ''); + const fleck = 'index' === basename(trimmed) ? dirname(trimmed) : trimmed; + if (!this.configs[fleck]) { + this.configs[fleck] = []; + } + this.configs[fleck].push({ + comment, + config, + defaultValue, + }); + } + addImplementation(hook, filename, loc) { this.hooks[hook] = this.hooks[hook] || {}; this.hooks[hook].implementations = this.hooks[hook].implementations || []; @@ -92,16 +112,47 @@ const FlecksBuildConfigs = (state) => ( config = element.elements[0].value; } } - if (config && element.leadingComments.length > 0) { + if (config) { state.addBuildConfig( config, - element.leadingComments.pop().value.split('\n') - .map((line) => line.trim()) - .map((line) => line.replace(/^\*/, '')) - .map((line) => line.trim()) - .filter((line) => !!line) - .join(' ') - .trim(), + (element.leadingComments?.length > 0) + ? element.leadingComments.pop().value.split('\n') + .map((line) => line.trim()) + .map((line) => line.replace(/^\*/, '')) + .map((line) => line.trim()) + .filter((line) => !!line) + .join(' ') + .trim() + : '*No description provided.*', + ); + } + }); + } + } + } + }) +); + +const FlecksConfigs = (state, filename, source) => ( + implementationVisitor((property) => { + if ('@flecks/core.config' === property.key.value) { + if (isArrowFunctionExpression(property.value)) { + if (isObjectExpression(property.value.body)) { + property.value.body.properties.forEach((property) => { + if (isIdentifier(property.key) || isStringLiteral(property.key)) { + state.addConfig( + property.key.name || property.key.value, + (property.leadingComments?.length > 0) + ? property.leadingComments.pop().value.split('\n') + .map((line) => line.trim()) + .map((line) => line.replace(/^\*/, '')) + .map((line) => line.trim()) + .filter((line) => !!line) + .join(' ') + .trim() + : '*No description provided.*', + filename, + source.slice(property.value.start, property.value.end), ); } }); @@ -232,8 +283,10 @@ export const parseCode = async (code) => { export const parseFile = async (filename, resolved, state) => { const buffer = await readFile(filename); - const ast = await parseCode(buffer.toString('utf8')); + const source = buffer.toString('utf8'); + const ast = await parseCode(source); traverse(ast, FlecksBuildConfigs(state, resolved)); + traverse(ast, FlecksConfigs(state, resolved, source)); traverse(ast, FlecksInvocations(state, resolved)); traverse(ast, FlecksImplementations(state, resolved)); traverse(ast, FlecksTodos(state, resolved)); diff --git a/packages/dox/src/server.js b/packages/dox/src/server.js index aa24671..be9d656 100644 --- a/packages/dox/src/server.js +++ b/packages/dox/src/server.js @@ -6,6 +6,11 @@ export default { [Hooks]: { '@flecks/core.commands': commands, '@flecks/core.config': () => ({ + /** + * Rewrite the output filenames of source files. + * + * `filename.replace(new RegExp([key]), [value]);` + */ filenameRewriters: {}, }), }, diff --git a/packages/governor/src/server.js b/packages/governor/src/server.js index 447ee0a..4989dea 100644 --- a/packages/governor/src/server.js +++ b/packages/governor/src/server.js @@ -8,6 +8,9 @@ export {default as createLimiter} from './limiter'; export default { [Hooks]: { '@flecks/core.config': () => ({ + /** + * All keys used to determine fingerprint. + */ keys: ['ip'], http: { keys: ['ip'], diff --git a/packages/http/src/server/index.js b/packages/http/src/server/index.js index 402e315..4d53c13 100644 --- a/packages/http/src/server/index.js +++ b/packages/http/src/server/index.js @@ -45,15 +45,39 @@ export default { 'template.ejs', ], '@flecks/core.config': () => ({ + /** + * (webpack-dev-server) Host to bind. + */ devHost: 'localhost', + /** + * (webpack-dev-server) Port to bind. + */ devPort: undefined, + /** + * (webpack-dev-server) Public path to serve. + */ devPublic: undefined, + /** + * (webpack-dev-server) Webpack stats output. + */ devStats: 'minimal', + /** + * Host to bind. + */ host: '0.0.0.0', + /** + * Build path. + */ output: 'http', + /** + * Port to bind. + */ port: 32340, - 'request.route': [], - 'request.socket': [], + /** + * Proxies to trust. + * + * See: https://www.npmjs.com/package/proxy-addr + */ trust: false, }), '@flecks/core.starting': (flecks) => { diff --git a/packages/react/src/index.js b/packages/react/src/index.js index 6eadd39..eb3efd8 100644 --- a/packages/react/src/index.js +++ b/packages/react/src/index.js @@ -16,6 +16,9 @@ export {default as usePrevious} from './hooks/use-previous'; export default { [Hooks]: { '@flecks/core.config': () => ({ + /** + * Whether to enable server-side rendering. + */ ssr: true, }), }, diff --git a/packages/redis/src/server.js b/packages/redis/src/server.js index 6749956..29ade25 100644 --- a/packages/redis/src/server.js +++ b/packages/redis/src/server.js @@ -30,7 +30,13 @@ export const keys = (client, pattern) => safeKeys(client, pattern, 0); export default { [Hooks]: { '@flecks/core.config': () => ({ + /** + * Redis server host. + */ host: 'localhost', + /** + * Redis server port. + */ port: 6379, }), '@flecks/docker.containers': containers, diff --git a/packages/server/src/index.js b/packages/server/src/index.js index ff40697..91dee9a 100644 --- a/packages/server/src/index.js +++ b/packages/server/src/index.js @@ -3,9 +3,21 @@ import {Hooks} from '@flecks/core'; export default { [Hooks]: { '@flecks/core.config': () => ({ + /** + * Whether HMR is enabled. + */ hot: false, + /** + * Whether the Node inspector is enabled. + */ inspect: false, + /** + * Whether node profiling is enabled. + */ profile: false, + /** + * Whether to start the server after building. + */ start: false, }), }, diff --git a/packages/socket/src/server/index.js b/packages/socket/src/server/index.js index 90f5ba1..95773bc 100644 --- a/packages/socket/src/server/index.js +++ b/packages/socket/src/server/index.js @@ -5,10 +5,6 @@ import Sockets from './sockets'; export default { [Hooks]: { - '@flecks/core.config': () => ({ - connect: [], - 'request.socket': [], - }), '@flecks/http/server.request.socket': ({config: {'$flecks/socket.sockets': sockets}}) => (req, res, next) => { req.intercom = createIntercom(sockets, 'http'); next(); diff --git a/packages/user/src/local/server/index.js b/packages/user/src/local/server/index.js index 5d8ab9c..d54a354 100644 --- a/packages/user/src/local/server/index.js +++ b/packages/user/src/local/server/index.js @@ -7,7 +7,13 @@ import LocalStrategy from 'passport-local'; export default { [Hooks]: { '@flecks/core.config': () => ({ + /** + * Path to redirect to after failed login. + */ failureRedirect: '/', + /** + * Path to redirect to after successful login. + */ successRedirect: '/', }), '@flecks/db/server.models.decorate': ( diff --git a/packages/user/src/session/server.js b/packages/user/src/session/server.js index 6cd8597..4054551 100644 --- a/packages/user/src/session/server.js +++ b/packages/user/src/session/server.js @@ -7,6 +7,11 @@ const debug = D('@flecks/user/session'); export default { [Hooks]: { '@flecks/core.config': () => ({ + /** + * Set the cookie secret for session encryption. + * + * See: http://expressjs.com/en/resources/middleware/cookie-parser.html + */ cookieSecret: ( 'Set the FLECKS_ENV_FLECKS_USER_SESSION_SERVER_cookieSecret environment variable!' ),