Skip to content

Commit

Permalink
feat: configure static directory, default wwwroot (#3682) (#3683)
Browse files Browse the repository at this point in the history
Fixes #3681
  • Loading branch information
joshgummersall authored May 13, 2021
1 parent 372e498 commit 199aba1
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@ import type {
WebResponse,
} from 'botbuilder';

const TypedOptions = t.Record({
/**
* Log errors to stderr
*/
logErrors: t.Boolean,

/**
* Path inside applicationRoot that should be served as static files
*/
staticDirectory: t.String.withConstraint((str) => str.length > 0 || 'must be non-empty string'),
});

/**
* Options for runtime Azure Functions adapter
*/
export type Options = t.Static<typeof TypedOptions>;

const defaultOptions: Options = {
logErrors: true,
staticDirectory: 'wwwroot',
};

// helper function to memoize the result of `func`
function memoize<T>(func: () => T): () => T {
let result: T;
Expand All @@ -40,12 +62,16 @@ const extensionContentTypes: Record<string, string> = {
*
* @param runtimeServices result of calling `once(() => getRuntimeServices(...))`
* @param applicationRoot application root directory
* @param options options bag for configuring Azure Functions
* @returns azure function triggers for `module.exports`
*/
export function makeTriggers(
runtimeServices: () => Promise<[ServiceCollection, Configuration]>,
applicationRoot: string
applicationRoot: string,
options: Partial<Options> = {}
): Record<string, AzureFunction> {
const resolvedOptions = TypedOptions.check(Object.assign({}, defaultOptions, options));

const build = memoize(async () => {
const [services, configuration] = await runtimeServices();

Expand All @@ -59,7 +85,7 @@ export function makeTriggers(
return { configuration, instances };
});

const staticDirectory = path.join(applicationRoot, 'public');
const staticDirectory = path.join(applicationRoot, resolvedOptions.staticDirectory);

return {
messageTrigger: async (context: Context, req: HttpRequest) => {
Expand Down Expand Up @@ -112,7 +138,10 @@ export function makeTriggers(
await bot.run(turnContext);
});
} catch (err) {
context.log.error(err);
if (resolvedOptions.logErrors) {
context.log.error(err);
}

throw err;
}
},
Expand Down Expand Up @@ -144,7 +173,10 @@ export function makeTriggers(
res.send(result);
res.end();
} catch (err) {
context.log.error(err);
if (resolvedOptions.logErrors) {
context.log.error(err);
}

throw err;
}
},
Expand Down Expand Up @@ -180,7 +212,10 @@ export function makeTriggers(
return res.status(404).end();
}

context.log.error(err);
if (resolvedOptions.logErrors) {
context.log.error(err);
}

throw err;
}
},
Expand All @@ -192,11 +227,17 @@ export function makeTriggers(
*
* @param applicationRoot application root directory
* @param settingsDirectory settings directory
* @param options options bag for configuring Azure Functions
* @returns azure function triggers for `module.exports`
*/
export function triggers(applicationRoot: string, settingsDirectory: string): Record<string, AzureFunction> {
export function triggers(
applicationRoot: string,
settingsDirectory: string,
options: Partial<Options> = {}
): Record<string, AzureFunction> {
return makeTriggers(
memoize(() => getRuntimeServices(applicationRoot, settingsDirectory)),
applicationRoot
applicationRoot,
options
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,34 @@ import type { ActivityHandlerBase, BotFrameworkAdapter, ChannelServiceRoutes } f
import { Configuration, getRuntimeServices } from 'botbuilder-dialogs-adaptive-runtime';
import type { ServiceCollection } from 'botbuilder-dialogs-adaptive-runtime-core';

// Explicitly fails checks for `""`
const NonEmptyString = t.String.withConstraint((str) => str.length > 0 || 'must be non-empty string');

const TypedOptions = t.Record({
/**
* Path that the server will listen to for [Activities](xref:botframework-schema.Activity)
*/
messagingEndpointPath: t.String,
messagingEndpointPath: NonEmptyString,

/**
* Path that the server will listen to for skills requests
*/
skillsEndpointPrefix: t.String,
skillsEndpointPrefix: NonEmptyString,

/**
* Port that server should listen on
*/
port: t.Union(t.String, t.Number),
port: t.Union(NonEmptyString, t.Number),

/**
* Log errors to stderr
*/
logErrors: t.Boolean,

/**
* Path inside applicationRoot that should be served as static files
*/
staticDirectory: NonEmptyString,
});

/**
Expand All @@ -41,6 +49,7 @@ const defaultOptions: Options = {
messagingEndpointPath: '/api/messages',
skillsEndpointPrefix: '/api/skills',
port: 3978,
staticDirectory: 'wwwroot',
};

/**
Expand All @@ -56,7 +65,7 @@ export async function start(
options: Partial<Options> = {}
): Promise<void> {
const [services, configuration] = await getRuntimeServices(applicationRoot, settingsDirectory);
const [_, listen] = await makeApp(services, configuration, applicationRoot, options);
const [, listen] = await makeApp(services, configuration, applicationRoot, options);

listen();
}
Expand Down Expand Up @@ -111,7 +120,7 @@ export async function makeApp(
}>('adapter', 'bot', 'channelServiceRoutes', 'customAdapters');

app.use(
express.static(path.join(applicationRoot, 'public'), {
express.static(path.join(applicationRoot, resolvedOptions.staticDirectory), {
setHeaders: (res, filePath) => {
const contentType = extensionContentTypes[path.extname(filePath)];
if (contentType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,34 @@ import type { ActivityHandlerBase, BotFrameworkAdapter, ChannelServiceRoutes } f
import { Configuration, getRuntimeServices } from 'botbuilder-dialogs-adaptive-runtime';
import type { ServiceCollection } from 'botbuilder-dialogs-adaptive-runtime-core';

// Explicitly fails checks for `""`
const NonEmptyString = t.String.withConstraint((str) => str.length > 0 || 'must be non-empty string');

const TypedOptions = t.Record({
/**
* Path that the server will listen to for [Activities](xref:botframework-schema.Activity)
*/
messagingEndpointPath: t.String,
messagingEndpointPath: NonEmptyString,

/**
* Path that the server will listen to for skills requests
*/
skillsEndpointPrefix: t.String,
skillsEndpointPrefix: NonEmptyString,

/**
* Port that server should listen on
*/
port: t.Union(t.String, t.Number),
port: t.Union(NonEmptyString, t.Number),

/**
* Log errors to stderr
*/
logErrors: t.Boolean,

/**
* Path inside applicationRoot that should be served as static files
*/
staticDirectory: NonEmptyString,
});

/**
Expand All @@ -40,6 +48,7 @@ const defaultOptions: Options = {
messagingEndpointPath: '/api/messages',
skillsEndpointPrefix: '/api/skills',
port: 3978,
staticDirectory: 'wwwroot',
};

async function resolveOptions(options: Partial<Options>, configuration: Configuration): Promise<Options> {
Expand Down Expand Up @@ -162,7 +171,7 @@ export async function makeServer(

server.get(
'*',
restify.plugins.serveStaticFiles(path.join(applicationRoot, 'public'), {
restify.plugins.serveStaticFiles(path.join(applicationRoot, resolvedOptions.staticDirectory), {
setHeaders: (res, filePath) => {
const contentType = extensionContentTypes[path.extname(filePath)];
if (contentType) {
Expand Down

0 comments on commit 199aba1

Please sign in to comment.