Skip to content

Commit

Permalink
Merge pull request #84 from hapipal/esm-stop-at-indexes
Browse files Browse the repository at this point in the history
Support ESM and stopping at index files while recursing
  • Loading branch information
devinivy committed Dec 7, 2021
2 parents 19ab76d + d2a864c commit e11e233
Show file tree
Hide file tree
Showing 16 changed files with 137 additions and 19 deletions.
2 changes: 2 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ When `composeOptions` is specified then it may define the following properties:
There are many amendments that haute-couture uses to provide its default behavior, as described in [Files and directories](#files-and-directories). In the case that `amendments` defines an amendment at a place for which haute-couture has a default, the contents of `amendment` will override the default. If `amendments` contains a key whose value is `false`, that place specified by that key will be ignored by haute-couture. You may also specify the key [`HauteCouture.default` or `'$default'`](#hautecouturedefault) to define defaults for all items. The following are amendment settings that can be changed through defaults:

- `recursive` - this option causes files to be picked-up recursively within their directory rather than just files that live directly under `place`. Flip this to `false` if you would prefer not to have a nested folder structure, e.g. `routes/users/login.js` versus `routes/users-login.js`.
- `stopAtIndexes` - when used with the `recursive` option, setting this option to `true` causes files adjacent to an index file (e.g. `index.js`) to not be recursed. For example, of the files `routes/users/index.js` and `routes/users/schema.js`, the first would be seen by haute-couture and the second would be excluded.
- `include` - may be a function `(filename, path) => Boolean` or a RegExp where `filename` (a filename without extension) and `path` (a file's path relative to `place`) are particular to files under `place`. When this option is used, a file will only be used as a call when the function returns `true` or the RegExp matches `path`.
- `exclude` - takes a function or RegExp, identically to `include`. When this option is used, a file will only be used as a call when the function returns `false` or the RegExp does not match `path`. This option defaults to exclude any file that lives under a directory named `helpers/`.
- `meta` - an object containing any meta information not required by haute-couture or haute, primarily for integration with other tools.
Expand Down Expand Up @@ -352,6 +353,7 @@ A haute manifest item describes the mapping of a file/directory's place and cont
- each value exported by the files within `place` when `place` is a directory without an index file (e.g. `plugins/vision.js`, `plugins/inert.js`).
- `useFilename` - (optional) when `list` is `true` and `place` is a directory without an index file, then this option allows one to use the name of the each file within `place` to modify its contents. Should be a function with signature `function(filename, value, path)` that receives the file's `filename` (without file extension); its contents at `value`; and the file's path relative to `place`. The function should return a new value to be used as arguments for hapi plugin API call.
- `recursive` - when `true` and `list` is in effect, this option causes files to be picked-up recursively within `place` rather than just files that live directly under `place`.
- `stopAtIndexes` - when used with the `recursive` option, setting this option to `true` causes files adjacent to an index file (e.g. `index.js`) to not be recursed.
- `include` - may be a function `(filename, path) => Boolean` or a RegExp where `filename` (a filename without extension) and `path` (a file's path relative to `place`) are particular to files under `place`. When this option is used, a file will only be used as a call when the function returns `true` or the RegExp matches `path`.
- `exclude` - takes a function or RegExp, identically to `include`. When this option is used, a file will only be used as a call when the function returns `false` or the RegExp does not match `path`. This option defaults to exclude any file that lives under a directory named `helpers/`.
- `meta` - an object containing any meta information not required by haute-couture or haute, primarily for integration with other tools.
22 changes: 7 additions & 15 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const Path = require('path');
const Hoek = require('@hapi/hoek');
const ParentModule = require('parent-module');
const Mo = require('mo-walk');
const Haute = require('haute');
const Manifest = require('./manifest');

Expand All @@ -21,13 +22,13 @@ exports.manifest = Manifest.manifest;
exports.compose = async (server, options, { dirname, amendments } = {}) => {

dirname = dirname || Path.dirname(ParentModule());
amendments = amendments || internals.maybeGetHcFile(dirname);
amendments = amendments || await internals.maybeGetHcFile(dirname);

// Protect from usage such as { name, register: HauteCouture.compose }
Hoek.assert(Path.relative(Path.resolve(dirname, '..', '..', '..'), dirname) !== Path.join('@hapi', 'hapi', 'lib'), 'You may not have called HauteCouture.compose(), which is necessary to determine the correct dirname. Consider using HauteCouture.composeWith() for your purposes.');

const manifest = Manifest.manifest(amendments, dirname);
const calls = Haute.calls('server', manifest);
const calls = await Haute.calls('server', manifest);

return await Haute.run(calls, server, options);
};
Expand All @@ -39,20 +40,11 @@ exports.composeWith = ({ dirname, amendments } = {}) => {
return (server, options) => exports.compose(server, options, { dirname, amendments });
};

internals.maybeGetHcFile = (dirname) => {
internals.maybeGetHcFile = async (dirname) => {

const path = Path.join(dirname, '.hc');
const resolved = await Mo.tryToResolve(Path.join(dirname, '.hc'));

try {
const resolved = require.resolve(path);
return exports.getDefaultExport(require(resolved), resolved);
}
catch (err) {
// Must be an error specifically from trying to require the passed (normalized) path
// Note that these error messages are of the form "Cannot find module '${path}'".
// In node v12 this is followed by a "Require stack", which is why we're testing for
// the module path specifically wrapped in single quotes.
Hoek.assert(err.code === 'MODULE_NOT_FOUND' && err.message.includes(`'${path}'`), err);
return;
if (resolved) {
return exports.getDefaultExport(...resolved);
}
};
4 changes: 3 additions & 1 deletion lib/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,11 @@ internals.extractFromPath = (path, values) => {
// E.g. a/b/c.d.js -> a-b-c.d
internals.normalizePath = (p) => {

const basename = Path.basename(p, Path.extname(p));

return Path.join(
Path.dirname(p),
Path.basename(p, Path.extname(p))
basename === 'index' ? '' : basename
)
.split(Path.sep)
.join('-');
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"dependencies": {
"@hapi/hoek": "9.x.x",
"@hapi/topo": "5.x.x",
"haute": ">=3.2.x <4",
"haute": "4.x.x",
"mo-walk": ">=1.1.0 <2",
"parent-module": "2.x.x"
},
"peerDependencies": {
Expand Down
6 changes: 6 additions & 0 deletions test/closet/decorations-deep/decorations/y/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

module.exports = {
type: 'toolkit',
method: () => null
};
7 changes: 7 additions & 0 deletions test/closet/hc-file-esm/.hc.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
methods: false,
controllers: {
method: 'method',
list: true
}
};
12 changes: 12 additions & 0 deletions test/closet/hc-file-esm/controllers.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default [
{
name: 'controllerOne',
method: () => 'controller-one',
options: {}
},
{
name: 'controllerTwo',
method: () => 'controller-two',
options: {}
}
];
12 changes: 12 additions & 0 deletions test/closet/hc-file-esm/methods.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default [
{
name: 'methodOne',
method: () => 'method-one',
options: {}
},
{
name: 'methodTwo',
method: () => 'method-two',
options: {}
}
];
7 changes: 7 additions & 0 deletions test/closet/stop-at-indexes/routes/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

exports.createRoute = (name) => ({
method: 'get',
path: `/${name}`,
handler: () => name
});
5 changes: 5 additions & 0 deletions test/closet/stop-at-indexes/routes/item-one.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const Helpers = require('./helpers');

module.exports = Helpers.createRoute('item-one');
5 changes: 5 additions & 0 deletions test/closet/stop-at-indexes/routes/one/b/item-one.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const Helpers = require('../../helpers');

module.exports = Helpers.createRoute('one/b/item-one');
5 changes: 5 additions & 0 deletions test/closet/stop-at-indexes/routes/one/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const Helpers = require('../helpers');

module.exports = Helpers.createRoute('one/index');
5 changes: 5 additions & 0 deletions test/closet/stop-at-indexes/routes/one/item-one.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const Helpers = require('../helpers');

module.exports = Helpers.createRoute('one/item-one');
5 changes: 5 additions & 0 deletions test/closet/stop-at-indexes/routes/two/e/item-one.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const Helpers = require('../../helpers');

module.exports = Helpers.createRoute('two/e/item-one');
5 changes: 5 additions & 0 deletions test/closet/stop-at-indexes/routes/two/item-one.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const Helpers = require('../helpers');

module.exports = Helpers.createRoute('two/item-one');
51 changes: 49 additions & 2 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,9 @@ describe('HauteCouture', () => {

expect(server.decorations).to.equal({
handler: [],
toolkit: [],
toolkit: [
'y' // y/index ({ type })
],
request: [
'serverY', // server/request.y
'bySelfHasConfiguredFull', // x/server/has-configured-full ({ type, property })
Expand All @@ -569,8 +571,8 @@ describe('HauteCouture', () => {
'xW' // x/response.w
],
server: [
'xU', // server/x/u
'x', // server/x
'xU', // server/x/u
'xZ' // x/server/z
]
});
Expand Down Expand Up @@ -723,6 +725,32 @@ describe('HauteCouture', () => {
]);
});

it('recurses with stopAtIndexes.', async () => {

const server = Hapi.server();

const plugin = {
name: 'my-recursive-plugin',
register: HauteCouture.composeWith({
dirname: `${__dirname}/closet/stop-at-indexes`,
amendments: {
$default: {
stopAtIndexes: true
}
}
})
};

await server.register(plugin);

expect(server.table().map((r) => r.settings.id)).to.equal([
'item-one',
'one',
'two-item-one',
'two-e-item-one'
]);
});

describe('amendment()', () => {

it('fetches default amendment by place.', () => {
Expand Down Expand Up @@ -1187,6 +1215,25 @@ describe('HauteCouture', () => {
expect(server.methods.methodTwo).to.not.exist();
});

it('supports .hc.mjs files.', async (flags) => {

const server = Hapi.server();

const plugin = {
name: 'my-hc-plugin',
register: HauteCouture.composeWith({
dirname: `${__dirname}/closet/hc-file-esm`
})
};

await server.register(plugin);

expect(server.methods.controllerOne()).to.equal('controller-one');
expect(server.methods.controllerTwo()).to.equal('controller-two');
expect(server.methods.methodOne).to.not.exist();
expect(server.methods.methodTwo).to.not.exist();
});

it('supports .hc.ts files.', async (flags) => {

// Emulate ts-node
Expand Down

0 comments on commit e11e233

Please sign in to comment.