Skip to content

Commit

Permalink
Merge pull request #56 from hapipal/esm-and-ts-support
Browse files Browse the repository at this point in the history
Support and test TypeScript and ESM
  • Loading branch information
devinivy committed Dec 7, 2021
2 parents a1a642d + 2e26e88 commit acb5c66
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 44 deletions.
1 change: 1 addition & 0 deletions .eslintignore
@@ -1,2 +1,3 @@
test/closet/haute-couture-broken/node_modules/haute-couture.js
test/closet/new/**
test/closet/run-command-ts/**
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2017-2020, Devin Ivy and collaborators
Copyright (c) 2017-2021, Devin Ivy and collaborators

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
8 changes: 8 additions & 0 deletions bin/_hpal
@@ -1,11 +1,19 @@
#!/usr/bin/env node
'use strict';

const Bounce = require('@hapi/bounce');
const Hpal = require('..');
const DisplayError = require('../lib/display-error');

(async () => {

try {
require('ts-node').register(); // Support .hc.ts files, etc.
}
catch (err) {
Bounce.ignore(err, { code: 'MODULE_NOT_FOUND' });
}

try {

await Hpal.start({
Expand Down
26 changes: 8 additions & 18 deletions lib/commands/run.js
@@ -1,6 +1,7 @@
'use strict';

const Path = require('path');
const Mo = require('mo-walk');
const PkgDir = require('pkg-dir');
const Helpers = require('../helpers');
const DisplayError = require('../display-error');
Expand Down Expand Up @@ -92,28 +93,17 @@ internals.getServer = async (root, ctx) => {

const path = Path.join(root, 'server');

try {

const srv = require(path);

if (typeof srv.deployment !== 'function') {
throw new DisplayError(ctx.colors.red(`No server found! To run commands the current project must export { deployment: async () => server } from ${root}/server.`));
}

const server = await srv.deployment();

await server.initialize();
const [srv] = await Mo.tryToResolve(path) || [];

return server;
if (!srv || typeof srv.deployment !== 'function') {
throw new DisplayError(ctx.colors.red(`No server found! To run commands the current project must export { deployment: async () => server } from ${root}/server.`));
}
catch (err) {

if (err.code === 'MODULE_NOT_FOUND' && err.message.includes(`'${path}'`)) {
throw new DisplayError(ctx.colors.red(`No server found! To run commands the current project must export { deployment: async () => server } from ${root}/server.`));
}
const server = await srv.deployment();

throw err;
}
await server.initialize();

return server;
};

internals.kebabize = (str) => str.replace(/[A-Z]/g, (m) => (`-${m}`).toLowerCase());
Expand Down
38 changes: 18 additions & 20 deletions lib/helpers.js
Expand Up @@ -6,6 +6,7 @@ const Util = require('util');
const AsyncHooks = require('async_hooks');
const ChildProcess = require('child_process');
const Mkdirp = require('mkdirp');
const Mo = require('mo-walk');
const Toys = require('@hapipal/toys');
const Bounce = require('@hapi/bounce');
const Glob = require('glob');
Expand All @@ -27,29 +28,26 @@ exports.mkdirp = Mkdirp;

exports.getAmendments = async (root, ctx, { amendmentsRequired = true } = {}) => {

// Fail early to avoid getAmendmentFile(), which can be expensive (globbing)
// Fail early to avoid getHcDirectory(), which can be expensive (globbing)
const hc = internals.getHauteCouture(root, ctx);

let amendmentFile;
let hcDirectory;

try {
amendmentFile = await internals.getAmendmentFile(root, ctx);
hcDirectory = await internals.getHcDirectory(root, ctx);
}
catch (err) {

Bounce.ignore(err, DisplayError);

if (amendmentsRequired) {
throw err;
}

amendmentFile = null;
}

const overrides = (amendmentFile !== null) ? require(amendmentFile) : {};
const resolved = hcDirectory && await Mo.tryToResolve(Path.join(hcDirectory, '.hc'));
const overrides = resolved ? Mo.getDefaultExport(...resolved) : {};
const amendments = hc.amendments(overrides);

return { amendments, file: amendmentFile };
return { amendments, file: resolved ? resolved[1] : null };
};

exports.withAsyncStorage = (command, run) => {
Expand Down Expand Up @@ -91,7 +89,7 @@ internals.getHauteCouture = (root, { colors }) => {

internals.glob = Util.promisify((pattern, opts, cb) => new Glob.Glob(pattern, opts, cb));

internals.getAmendmentFile = async (root, ctx) => {
internals.getHcDirectory = async (root, ctx) => {

const { colors, options: { cwd } } = ctx;

Expand All @@ -113,8 +111,6 @@ internals.getAmendmentFile = async (root, ctx) => {

const isAncestorPathOrNoRelation = (pathA, pathB) => isAncestorPath(pathA, pathB) || !isChildPath(pathA, pathB);

const makeFilename = (path) => Path.resolve(path, '.hc.js');

const cull = (paths, predicate) => {

let i = 0;
Expand All @@ -127,42 +123,44 @@ internals.getAmendmentFile = async (root, ctx) => {

if (paths.length > 1) {
const relativize = (path) => Path.relative(cwd, path);
const filenames = paths.map(makeFilename).map(relativize);
throw new DisplayError(colors.red(`It's ambiguous which directory containing a .hc.js file to use: ${filenames.join(', ')}`));
const pathnames = paths.map(relativize);
throw new DisplayError(colors.red(`It's ambiguous which directory containing a .hc.* file to use: ${pathnames.join(', ')}`));
}

return paths[0];
};

const amendmentFiles = await internals.glob('**/.hc.js', {
const unique = (arr) => [...new Set(arr)];

const amendmentFiles = await internals.glob(`**/.hc.{${Mo.defaultExtensions.join(',')}}`, {
cwd: root,
absolute: true,
ignore: 'node_modules/**'
});

const amendmentPaths = amendmentFiles.map(Path.dirname);
const amendmentPaths = unique(amendmentFiles.map(Path.dirname));

// Prefer nearest ancestor...

const ancestorPaths = amendmentPaths.filter((path) => isAncestorPath(path, cwd));

if (ancestorPaths.length) {
return makeFilename(cull(ancestorPaths, isChildPath));
return cull(ancestorPaths, isChildPath);
}

// ... then nearest (unambiguous) child...

const childPaths = amendmentPaths.filter((path) => isChildPath(path, cwd));

if (childPaths.length) {
return makeFilename(cull(childPaths, isAncestorPathOrNoRelation));
return cull(childPaths, isAncestorPathOrNoRelation);
}

// ... then any (unambiguous) side-paths!

if (amendmentPaths.length) {
return makeFilename(cull(amendmentPaths, isAncestorPathOrNoRelation));
return cull(amendmentPaths, isAncestorPathOrNoRelation);
}

throw new DisplayError(colors.red('There\'s no directory in this project containing a .hc.js file.'));
throw new DisplayError(colors.red('There\'s no directory in this project containing a .hc.* file.'));
};
18 changes: 17 additions & 1 deletion package.json
Expand Up @@ -47,18 +47,34 @@
"marked": "1.x.x",
"marked-terminal": "4.x.x",
"mkdirp": "1.x.x",
"mo-walk": ">=1.1.0 <2",
"pkg-dir": "5.x.x",
"pluralize": "8.x.x",
"supports-color": "7.x.x"
},
"peerDependencies": {
"ts-node": "10.x.x",
"typescript": "*"
},
"peerDependenciesMeta": {
"ts-node": {
"optional": true
},
"typescript": {
"optional": true
}
},
"devDependencies": {
"@hapi/boom": "9.x.x",
"@hapi/code": "8.x.x",
"@hapi/hapi": "20.x.x",
"@hapi/lab": "24.x.x",
"@hapipal/haute-couture": "4.x.x",
"@types/hapi__hapi": "20.x.x",
"coveralls": "3.x.x",
"rimraf": "3.x.x",
"strip-ansi": "6.x.x"
"strip-ansi": "6.x.x",
"ts-node": "10.x.x",
"typescript": "4.x.x"
}
}
3 changes: 3 additions & 0 deletions test/closet/non-ambiguous-hc-file-cwd-esm/package.json
@@ -0,0 +1,3 @@
{
"name": "non-ambiguous-hc-file-cwd-esm"
}
Empty file.
5 changes: 5 additions & 0 deletions test/closet/non-ambiguous-hc-file-cwd-esm/project-b/.hc.mjs
@@ -0,0 +1,5 @@
import * as HauteCouture from '@hapipal/haute-couture';

export default {
pluggums: HauteCouture.amendment('plugins')
};
3 changes: 3 additions & 0 deletions test/closet/run-command-esm/package.json
@@ -0,0 +1,3 @@
{
"name": "run-command-esm"
}
35 changes: 35 additions & 0 deletions test/closet/run-command-esm/server.mjs
@@ -0,0 +1,35 @@
import * as Hapi from '@hapi/hapi';

export const deployment = async () => {

const server = Hapi.server();

const register = (srv) => {

srv.expose('commands', {
someCommand: (rootServer, args, root, ctx) => {

ctx.options.cmd = [rootServer, args, root, ctx];

const stop = rootServer.stop;
rootServer.stop = async () => {

rootServer.stop = stop;

await rootServer.stop();

rootServer.stopped = true;
};
}
});
};

const plugin = {
name: 'x',
register
};

await server.register(plugin);

return server;
};
3 changes: 3 additions & 0 deletions test/closet/run-command-ts/package.json
@@ -0,0 +1,3 @@
{
"name": "run-command-ts"
}
25 changes: 25 additions & 0 deletions test/closet/run-command-ts/server.ts
@@ -0,0 +1,25 @@
import * as Hapi from '@hapi/hapi';

export const deployment = async () => {

const server = Hapi.server();

const register = (srv: Hapi.Server) => {

srv.expose('commands', {
someCommand: () => {

console.log('some-command was run!');
}
});
};

const plugin = {
name: 'x',
register
};

await server.register(plugin);

return server;
};

0 comments on commit acb5c66

Please sign in to comment.