Bringing local and remote repository in line with one another and adding Github Actions to publish package
This commit is contained in:
parent
28db152b87
commit
74997cb676
8 changed files with 479 additions and 0 deletions
53
.github/workflows/publish.yml
vendored
Normal file
53
.github/workflows/publish.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Publish to Private NPM Registry
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Checkout the repository
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Set up NPM Auth Token
|
||||
- name: Set up NPM Auth Token
|
||||
run: echo "NODE_AUTH_TOKEN=${{ secrets.NPM_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
# Set up Node.js
|
||||
- name: Set up Node.js version
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
# Taken from [Repo README](https://github.com/actions/setup-node#readme)
|
||||
#
|
||||
# > Version Spec of the version to use in SemVer notation.
|
||||
# > It also admits such aliases as lts/*, latest, nightly and canary builds
|
||||
# > Examples: 12.x, 10.15.1, >=10.15.0, lts/Hydrogen, 16-nightly, latest, node
|
||||
node-version: 'latest'
|
||||
|
||||
# Taken from [Repo README](https://github.com/actions/setup-node#readme)
|
||||
#
|
||||
# > Optional registry to set up for auth. Will set the registry in a project level .npmrc and .yarnrc file,
|
||||
# > and set up auth to read in from env.NODE_AUTH_TOKEN.
|
||||
# > Default: ''
|
||||
registry-url: 'https://npm.pkg.bridgemanaccessible.ca'
|
||||
|
||||
# Taken from [Repo README](https://github.com/actions/setup-node#readme)
|
||||
#
|
||||
# > Optional scope for authenticating against scoped registries.
|
||||
# > Will fall back to the repository owner when using the GitHub Packages registry (https://npm.pkg.github.com/).
|
||||
scope: '@BridgemanAccessible'
|
||||
|
||||
# Install dependencies
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn install
|
||||
yarn build
|
||||
|
||||
# Publish to private NPM registry
|
||||
- name: Publish package
|
||||
run: npm publish
|
||||
198
src/OAuthApp.ts
Normal file
198
src/OAuthApp.ts
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
import { Application } from 'express';
|
||||
import { Scopes } from '@BridgemanAccessible/ba-auth';
|
||||
import Client, { OnAuthCallback } from '@BridgemanAccessible/ba-auth/client';
|
||||
|
||||
import { App } from './App';
|
||||
import { Initializer } from './Initializer';
|
||||
|
||||
import { getValueFromEnvironmentVariable } from './utils/env-vars';
|
||||
|
||||
type OAuthAppOptions = {
|
||||
/** The base URL of the app */
|
||||
baseAppUrl?: URL,
|
||||
/** The abbreviation of the app */
|
||||
appAbbrv?: string,
|
||||
/** The name of the app */
|
||||
appName?: string | {
|
||||
/** Localized versions of the app name */
|
||||
[language: string]: string
|
||||
},
|
||||
/** The email addresses of the contacts for the app */
|
||||
contacts?: string[],
|
||||
/** The scopes an app token COULD ask for (token scopes would have to ask for this or a subset of this list) */
|
||||
scopes?: Scopes[],
|
||||
/** The URL of the app's logo */
|
||||
logo_url?: URL,
|
||||
/** The URL of the app's terms of service */
|
||||
tos_url?: URL,
|
||||
/** The URL of the app's privacy policy */
|
||||
policy_url?: URL,
|
||||
/** The type of vault to use for the keystore */
|
||||
vault_type?: "azure" | "hashicorp" | "file",
|
||||
/** The default method for authentication */
|
||||
auth_default_method?: "query" | "form_post" | "PAR",
|
||||
/** Whether to use JWT as the default authentication method */
|
||||
auth_default_use_JWT?: boolean,
|
||||
/** The default response mode for authentication */
|
||||
auth_default_response_mode?: 'query' | 'fragment' | 'form_post'
|
||||
/** The client secret for the app (if this IS set registration WON'T be done. Because re-registering isn't supported) */
|
||||
client_secret?: string
|
||||
};
|
||||
|
||||
export class OAuthApp extends App {
|
||||
private onAuth: OnAuthCallback;
|
||||
private saveSecret: (secret: string) => void | Promise<void>;
|
||||
|
||||
private baseAppUrl?: URL;
|
||||
private appAbbrv?: string;
|
||||
private appName?: string | { [language: string]: string };
|
||||
private contacts?: string[];
|
||||
private scopes?: Scopes[];
|
||||
private logo_url?: URL;
|
||||
private tos_url?: URL;
|
||||
private policy_url?: URL;
|
||||
private vault_type?: "azure" | "hashicorp" | "file";
|
||||
private auth_default_method?: "query" | "form_post" | "PAR";
|
||||
private auth_default_use_JWT?: boolean;
|
||||
private auth_default_response_mode?: 'query' | 'fragment' | 'form_post';
|
||||
private client_secret?: string;
|
||||
|
||||
/**
|
||||
* Create a new OAuth app
|
||||
*
|
||||
* This is a more specialized version of the `App` class that is designed to work with the Bridgeman Accessible OAuth system.
|
||||
* The only required parameters are the `onAuth` and `saveSecret` callbacks which are called when a user logs in.
|
||||
* And when the client secret is generated (and is to be saved) respectively.
|
||||
* The `options` parameter allows for tweaking the OAuth details for the app.
|
||||
*
|
||||
* Note, the following table of environment variables that are used to set the OAuth details if they aren't provided in the `options` parameter:
|
||||
*
|
||||
* | Environment Variable | Is Required | Description |
|
||||
* | -------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
* | BASE_APP_URL | Required if `options.baseAppUrl` isn't provided | The base URL of the client (app) |
|
||||
* | APP_ABBRV | Required if `options.appAbbrv` isn't provided | The abbreviation of the client (app) |
|
||||
* | APP_NAME | Required if `options.appName` isn't provided | The name of the client (only a single non-localized name is supported) |
|
||||
* | CONTACT_EMAIL | Required if `options.contacts` isn't provided | A comma-separated list of email addresses to list as contacts for the client |
|
||||
* | LOGO_URL | Optional (used if `options.logo_url` isn't specified) | The URL of the client's (app's) logo |
|
||||
* | TOS_URL | Optional (used if `options.tos_url` isn't specified) | The URL of the client's terms of service |
|
||||
* | POLICY_URL | Optional (used if `options.policy_url` isn't specified) | The URL of the client's privacy policy |
|
||||
* | SCOPES | Required if `options.scopes` isn't provided | A comma-separated list of scopes available to the client |
|
||||
* | VAULT_TYPE | Required if in production (`NODE_ENV` is `production`) | The type of vault to use for the keystore (one of azure, hashicorp, or file) |
|
||||
* | APP_SECRET | Optional (Determines if app should register or not) | The client secret for the app (if this IS set registration WON'T be done) |
|
||||
*
|
||||
* @param onAuth The callback to call when a user logs in
|
||||
* @param saveSecret The callback to call to save the secret
|
||||
* @param options The options for the OAuth app
|
||||
* @param options.baseAppUrl The base URL of the app
|
||||
* @param options.appAbbrv The abbreviation of the app (used for user properties associated with the app)
|
||||
* @param options.appName The name of the app
|
||||
* @param options.contacts The email addresses of the contacts for the app
|
||||
* @param options.scopes The scopes an app token COULD ask for (token scopes would have to ask for this or a subset of this list)
|
||||
* @param options.logo_url The URL of the app's logo
|
||||
* @param options.tos_url The URL of the app's terms of service
|
||||
* @param options.policy_url The URL of the app's privacy policy
|
||||
* @param options.vault_type The type of vault to use for the keystore
|
||||
* @param options.auth_default_method The default method for authentication
|
||||
* @param options.auth_default_use_JWT Whether to use JWT as the default authentication method
|
||||
* @param options.auth_default_response_mode The default response mode for authentication
|
||||
* @param options.client_secret The client secret for the app (if this IS set registration WON'T be done. Because re-registering isn't supported)
|
||||
*/
|
||||
constructor(onAuth: OnAuthCallback, saveSecret: (secret: string) => void | Promise<void>, options?: OAuthAppOptions) {
|
||||
super();
|
||||
this.onAuth = onAuth;
|
||||
this.saveSecret = saveSecret;
|
||||
|
||||
if(typeof options !== 'undefined') {
|
||||
this.baseAppUrl = options.baseAppUrl;
|
||||
this.appAbbrv = options.appAbbrv;
|
||||
this.appName = options.appName;
|
||||
this.contacts = options.contacts;
|
||||
this.scopes = options.scopes;
|
||||
this.logo_url = options.logo_url;
|
||||
this.tos_url = options.tos_url;
|
||||
this.policy_url = options.policy_url;
|
||||
this.vault_type = options.vault_type;
|
||||
this.auth_default_method = options.auth_default_method;
|
||||
this.auth_default_use_JWT = options.auth_default_use_JWT;
|
||||
this.auth_default_response_mode = options.auth_default_response_mode;
|
||||
this.client_secret = options.client_secret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the OAuth client
|
||||
*
|
||||
* This is done here because we need the client to be serving/listening for request in order for the auth library stuff to work properly
|
||||
* (mostly, the server needs to be able to get the client's keys which the client needs to be able to serve)
|
||||
*
|
||||
* @param app The app to setup the OAuth client for
|
||||
*/
|
||||
private async setupOAuthClient(app: Application) {
|
||||
// If the base URL of the app isn't provided, get it from the environment variable
|
||||
let baseAppUrl = this.baseAppUrl;
|
||||
if(typeof baseAppUrl === 'undefined') {
|
||||
baseAppUrl = new URL(getValueFromEnvironmentVariable('BASE_APP_URL', { description: 'app\'s base URL', blank_allowed: false }));
|
||||
}
|
||||
|
||||
// The app abbreviation (used for user properties associated with the app)
|
||||
let appAbbrv = this.appAbbrv;
|
||||
if(typeof appAbbrv === 'undefined') {
|
||||
appAbbrv = getValueFromEnvironmentVariable('APP_ABBRV', { description: 'app abbreviation', blank_allowed: false });
|
||||
}
|
||||
|
||||
console.log(`Attempting to create/register the app:\n\tApp Base URL: ${baseAppUrl}\n\tApp Abbreviation: ${appAbbrv}\n\tApp Name: ${typeof this.appName === 'undefined' ? 'uses APP_NAME environment variable (' + process.env.APP_NAME + ')' : this.appName}\n\tScopes: ${typeof this.scopes === 'undefined' ? 'uses SCOPES environment variable (' + process.env.SCOPES + ')' : this.scopes.join(', ')}`);
|
||||
|
||||
await Client.setup(app, baseAppUrl, this.onAuth, this.saveSecret, appAbbrv, this.appName, this.scopes, {
|
||||
contacts: this.contacts,
|
||||
logo_url: this.logo_url,
|
||||
tos_url: this.tos_url,
|
||||
policy_url: this.policy_url,
|
||||
vault_type: this.vault_type,
|
||||
auth_default_method: this.auth_default_method,
|
||||
auth_default_use_JWT: this.auth_default_use_JWT,
|
||||
auth_default_response_mode: this.auth_default_response_mode,
|
||||
client_secret: this.client_secret
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrapper method around the `onStart` callback that differentiates the `OAuthApp` from the `App` class.
|
||||
* That is, this (the `OAuthApp` class) sets up the OAuth client before calling the client's `onStart` callback.
|
||||
* As opposed to the `App` class which just calls the `onStart` callback.
|
||||
*
|
||||
* It's done this way so that it's almost entirely transparent the difference between apps that have OAuth and those that don't.
|
||||
*
|
||||
* @param app The Express app object (that is now listening)
|
||||
*/
|
||||
async onStart(app: Application, callback?: (app: Application) => void | Promise<void>) {
|
||||
try {
|
||||
// Setup the OAuth client.
|
||||
// This is done here because we need the client to be serving/listening for requests for the auth library stuff to work
|
||||
// (mostly because the server needs to be able to get the client's keys which it needs to be able to serve)
|
||||
await this.setupOAuthClient(app);
|
||||
|
||||
if(typeof callback !== 'undefined') {
|
||||
await callback.bind(this)(app);
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
console.error('Error setting up the BA User Auth');
|
||||
console.error('---------------------------------');
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main entry point for the app
|
||||
*
|
||||
* This largely just wraps the `run` method from the parent class (`App`).
|
||||
* However, instead of invoking the provided callback immediately when the client starts.
|
||||
* This initializes the app as an OAuth app first and then calls the callback.
|
||||
* This lets the app just worry about the `onAuth` callback (what happens when a user logs in) and `saveSecret` callback (that saves the generated client secret, if applicable)
|
||||
* And doesn't have to worry about the OAuth details to make this work.
|
||||
* Though it does provide tweaking the OAuth details via options provided to the constructor.
|
||||
*/
|
||||
async run<T extends Initializer>(initializer?: T, callback?: (app: Application) => void | Promise<void>) {
|
||||
await super.run(initializer, async (app: Application) => this.onStart(app, callback));
|
||||
}
|
||||
}
|
||||
9
src/controllers/ErrorController.ts
Normal file
9
src/controllers/ErrorController.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export abstract class ErrorController {
|
||||
/** A human readable string to describe the error this controller handles. */
|
||||
public handlesError?: string;
|
||||
|
||||
|
||||
abstract handle(error: unknown, req: Request, res: Response, next: NextFunction): any | Promise<any>;
|
||||
}
|
||||
21
src/decorators/DELETE.ts
Normal file
21
src/decorators/DELETE.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { NextFunction } from 'express';
|
||||
import { NextHandleFunction } from 'connect';
|
||||
|
||||
export const DELETE_METADATA_KEY = 'Delete';
|
||||
|
||||
/**
|
||||
* Method decorator intended to be used on methods of a class decorated with the `@Controller` decorator.
|
||||
*
|
||||
* This in conjunction with the `@Controller` decorator will automatically setup a DELETE route that executes the decorated method.
|
||||
* The specific path for the route is defined by the path parameter.
|
||||
* Controller authors can also specify middleware to be used with the DELETE route (because a middleware is commonly required to parse the body of a DELETE request).
|
||||
*
|
||||
* @param path The path for the DELETE route.
|
||||
* @param middleware The middleware to use with the DELETE route.
|
||||
*/
|
||||
export function DELETE(path: string, ...middleware: (NextHandleFunction | NextFunction)[]) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Define a metadata key with the path and middleware as the value on the target's propertyKey
|
||||
Reflect.defineMetadata(DELETE_METADATA_KEY, { path, middleware }, target, propertyKey);
|
||||
};
|
||||
}
|
||||
62
src/decorators/ErrorHandler.ts
Normal file
62
src/decorators/ErrorHandler.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
import { ErrorController } from '../controllers/ErrorController';
|
||||
|
||||
/**
|
||||
* Class decorator to create custom error handling for the app.
|
||||
*
|
||||
* That is, so that custom error pages can easily be created and used in the app.
|
||||
*
|
||||
* @example
|
||||
* The following example show how to use the Controller decorator to setup a path `/path` with a GET, POST, PUT and DELETE method.
|
||||
* ```ts
|
||||
* import { Request, Response, NextFunction } from 'express';
|
||||
*
|
||||
* import { ErrorController, ErrorHandler } from '@BridgemanAccessible/ba-web-framework';
|
||||
*
|
||||
* @ErrorHandler(404)
|
||||
* export class Custom404PageController extends ErrorController {
|
||||
* handle(error: unknown, req: Request, res: Response, next: NextFunction) {
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param errorCode The error code to handle. This is the status code that will be returned by the server.
|
||||
* @param description A human readable string to describe the error this controller handles. This is used for debugging purposes only and is not used in the app itself.
|
||||
*/
|
||||
export function ErrorHandler<T extends { new(...args: any): ErrorController }>(errorCode: number, description?: string) {
|
||||
// Technically the function we return here with the target parameter is the actual decorator itself but we wrap it in case we ever want to add parameters to the decorator
|
||||
return function(target: T){
|
||||
Reflect.defineMetadata('errorHandler', target, target);
|
||||
|
||||
if(typeof target.prototype.handlesError === 'undefined') {
|
||||
// If the handlesError property is not set, we set it to the description passed to the decorator
|
||||
target.prototype.handlesError = description || `${errorCode}`;
|
||||
}
|
||||
|
||||
// We extend the class that is decorated and override the setup method to automatically setup the routes
|
||||
return class extends target {
|
||||
async handle(error: unknown, req: Request, res: Response, next: NextFunction) {
|
||||
// Because the headers have already been sent, we cannot send a response again.
|
||||
// So we check if the headers have been sent and if so, we call the next middleware (default) in the error chain.
|
||||
if(res.headersSent) {
|
||||
return next(error);
|
||||
}
|
||||
|
||||
// Create an instance of the decorated (original) class
|
||||
const controller: ErrorController = new (Reflect.getMetadata('errorHandler', target))();
|
||||
|
||||
// We only want to call the handle method if the error code matches the one denoted.
|
||||
if(res.statusCode === errorCode) {
|
||||
// Call the handle method of the controller and return the result
|
||||
return await controller.handle.bind(controller)(error, req, res, next);
|
||||
}
|
||||
else {
|
||||
// If the error code does not match, we call the next middleware in the error chain
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/decorators/PUT.ts
Normal file
21
src/decorators/PUT.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { NextFunction } from 'express';
|
||||
import { NextHandleFunction } from 'connect';
|
||||
|
||||
export const PUT_METADATA_KEY = 'Put';
|
||||
|
||||
/**
|
||||
* Method decorator intended to be used on methods of a class decorated with the `@Controller` decorator.
|
||||
*
|
||||
* This in conjunction with the `@Controller` decorator will automatically setup a PUT route that executes the decorated method.
|
||||
* The specific path for the route is defined by the path parameter.
|
||||
* Controller authors can also specify middleware to be used with the PUT route (because a middleware is commonly required to parse the body of a PUT request).
|
||||
*
|
||||
* @param path The path for the PUT route.
|
||||
* @param middleware The middleware to use with the PUT route.
|
||||
*/
|
||||
export function PUT(path: string, ...middleware: (NextHandleFunction | NextFunction)[]) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
// Define a metadata key with the path and middleware as the value on the target's propertyKey
|
||||
Reflect.defineMetadata(PUT_METADATA_KEY, { path, middleware }, target, propertyKey);
|
||||
};
|
||||
}
|
||||
49
src/middlewares/HealthCheckMiddleware.ts
Normal file
49
src/middlewares/HealthCheckMiddleware.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
||||
|
||||
export enum HealthCheckStatus {
|
||||
OK = 'ok',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
export class HealthCheckMiddleware {
|
||||
public static readonly HEALTH_CHECK_ENDPOINT = '/.well-known/health-check';
|
||||
|
||||
private static status: HealthCheckStatus;
|
||||
|
||||
constructor(initialStatus: HealthCheckStatus = HealthCheckStatus.OK) {
|
||||
HealthCheckMiddleware.status = initialStatus;
|
||||
}
|
||||
|
||||
static setStatus(status: HealthCheckStatus) {
|
||||
HealthCheckMiddleware.status = status;
|
||||
}
|
||||
|
||||
middleware(req: Request, res: Response, next: NextFunction) {
|
||||
if(req.path === HealthCheckMiddleware.HEALTH_CHECK_ENDPOINT) {
|
||||
switch(HealthCheckMiddleware.status) {
|
||||
case HealthCheckStatus.OK:
|
||||
return res.status(200).setHeader('Content-Type', 'application/health+json').json({ status: 'ok' });
|
||||
break;
|
||||
case HealthCheckStatus.ERROR:
|
||||
return res.status(500).setHeader('Content-Type', 'application/health+json').json({ status: 'error' });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware function to create a health check endpoint.
|
||||
*
|
||||
* This attempts to comply with the [Health Check Response Format for HTTP APIs](https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check-06) proposed standard.
|
||||
*
|
||||
* @param initialStatus The initial status of the health check.
|
||||
* @returns The middleware function.
|
||||
*/
|
||||
export function healthCheckMiddleware(initialStatus: HealthCheckStatus = HealthCheckStatus.OK) {
|
||||
const instance = new HealthCheckMiddleware(initialStatus);
|
||||
|
||||
return instance.middleware.bind(instance);
|
||||
}
|
||||
66
src/utils/env-vars.ts
Normal file
66
src/utils/env-vars.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Strip quotes from a string (if they are present)
|
||||
*
|
||||
* This is useful for environment variables because otherwise there can be unexpected behaviors between quoted and unquoted values
|
||||
*
|
||||
* @param value The value to strip quotes from
|
||||
* @returns The value without quotes
|
||||
*/
|
||||
function stripQuotes(value: string): string {
|
||||
// Note we check BOTH the start and end of the string for quotes
|
||||
// This is because we don't want to strip quotes from the middle of the string
|
||||
// Or if for one reason or another there are quotes at one end but not the other
|
||||
//
|
||||
// For instance, `abc"value"abc` should NOT be stripped to `abcvalueabc`
|
||||
// Further, `"value"abc` should NOT be stripped to `valueabc`
|
||||
// Similarly, `value"abc"` should NOT be stripped to `valueabc`
|
||||
// But `"value"` SHOULD BE stripped to `value`
|
||||
//
|
||||
// In theory, the quotes should be used to avoid issues with spaces and other special characters and the encapsulated value should be treated as a single value and quotes removed
|
||||
// But, for now, we'll just strip the quotes as described above
|
||||
if(value.startsWith('"') && value.endsWith('"')) {
|
||||
return value.slice(1, -1);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value from an environment variable in a safe way
|
||||
*
|
||||
* @param varname The name of the environment variable to get the value from
|
||||
* @param options Options for getting the value from the environment variable. Optional.
|
||||
* @param options.description A human readable description of the environment variable (used for error messages). Optional.
|
||||
* @param options.default The default value to use if the environment variable is not defined. Optional.
|
||||
* @param options.blank_allowed Whether a blank value is allowed. Optional. Default is true (blanks are allowed).
|
||||
* @throws If the environment variable is not defined
|
||||
* @returns The value of the environment variable
|
||||
*/
|
||||
export function getValueFromEnvironmentVariable(varname: string, options?: { description?: string, default?: string, blank_allowed?: boolean }): string {
|
||||
// Verify if the environment variable is defined
|
||||
if(typeof process.env[varname] === 'undefined' || process.env[varname] === null) {
|
||||
if(typeof options !== 'undefined' && typeof options.default !== 'undefined') {
|
||||
// Because the variable is not defined, but a default value is provided, return the default value
|
||||
return options.default;
|
||||
}
|
||||
else {
|
||||
// Because the variable is not defined AND no default value is provided, throw an error
|
||||
throw new Error(`The ${typeof options !== 'undefined' && typeof options.description !== 'undefined' ? options.description : ''} (${varname} environment variable) is not defined`);
|
||||
}
|
||||
}
|
||||
|
||||
// If the value is blank and blanks are EXPLICITLY not allowed, return the default or throw an error
|
||||
if(typeof options !== 'undefined' && typeof options.blank_allowed !== 'undefined' && !options.blank_allowed && process.env[varname] === '') {
|
||||
if(typeof options.default !== 'undefined') {
|
||||
// Because the variable is blank, but blanks aren't allowed AND a default value is provided, return the default value
|
||||
return options.default;
|
||||
}
|
||||
else {
|
||||
// Because the variable is blank, but blanks aren't allowed AND no default value is provided, throw an error
|
||||
throw new Error(`The ${typeof options.description !== 'undefined' ? options.description : ''} (${varname} environment variable) cannot be blank`);
|
||||
}
|
||||
}
|
||||
|
||||
// Strip quotes from the value before returning it
|
||||
return stripQuotes(process.env[varname]);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue