@volcanicminds/backend 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.ts ADDED
@@ -0,0 +1,253 @@
1
+ 'use strict'
2
+
3
+ import dotenv from 'dotenv'
4
+ dotenv.config()
5
+
6
+ import yn from './util/yn'
7
+ import logger from './util/logger'
8
+ import * as mark from './util/mark'
9
+ import * as loaderRoles from './loader/roles'
10
+ import * as loaderRouter from './loader/router'
11
+
12
+ import Fastify, { FastifyInstance } from 'fastify'
13
+ import swagger from '@fastify/swagger'
14
+ import swaggerUI from '@fastify/swagger-ui'
15
+
16
+ import cors from '@fastify/cors'
17
+ import helmet from '@fastify/helmet'
18
+ import compress from '@fastify/compress'
19
+ import rateLimit from '@fastify/rate-limit'
20
+
21
+ import { ApolloServer } from '@apollo/server'
22
+ import fastifyApollo, { fastifyApolloHandler, fastifyApolloDrainPlugin } from '@as-integrations/fastify'
23
+ import { myContextFunction, MyContext } from './apollo/context'
24
+ import resolvers from './apollo/resolvers'
25
+ import typeDefs from './apollo/type-defs'
26
+
27
+ const begin = new Date().getTime()
28
+ mark.print(logger)
29
+
30
+ export interface global {}
31
+ declare global {
32
+ var log: any
33
+ var roles: Roles
34
+ }
35
+
36
+ global.log = logger
37
+ global.roles = loaderRoles.load()
38
+
39
+ declare module 'fastify' {
40
+ export interface FastifyRequest {
41
+ user?: AuthenticatedUser
42
+ }
43
+ }
44
+
45
+ async function attachApollo(fastify: FastifyInstance) {
46
+ log.info('Attach ApolloServer to Fastify')
47
+ const apollo = new ApolloServer<MyContext>({
48
+ typeDefs,
49
+ resolvers,
50
+ plugins: [fastifyApolloDrainPlugin(fastify)]
51
+ })
52
+
53
+ await apollo.start()
54
+
55
+ return apollo
56
+ }
57
+
58
+ async function addApolloRouting(fastify: FastifyInstance, apollo: ApolloServer<MyContext> | null) {
59
+ if (apollo) {
60
+ log.info('Add graphql routes')
61
+ await fastify.register(fastifyApollo(apollo), {
62
+ context: myContextFunction
63
+ })
64
+
65
+ // // OR
66
+ // fastify.post(
67
+ // '/graphql-alt',
68
+ // fastifyApolloHandler(apollo, {
69
+ // context: myContextFunction
70
+ // })
71
+ // )
72
+ }
73
+ }
74
+
75
+ async function addFastifyRouting(fastify: FastifyInstance) {
76
+ log.info('Add fastify routes')
77
+
78
+ fastify.addHook('onSend', async (req, reply) => {
79
+ log.debug('onSend')
80
+ })
81
+
82
+ fastify.addHook('onResponse', async (req, reply) => {
83
+ log.debug('onResponse')
84
+ })
85
+
86
+ fastify.addHook('onTimeout', async (req, reply) => {
87
+ log.debug('onTimeout')
88
+ })
89
+
90
+ fastify.addHook('onReady', async () => {
91
+ log.debug('onReady')
92
+ })
93
+
94
+ fastify.addHook('onClose', async (instance) => {
95
+ log.debug('onClose')
96
+ })
97
+
98
+ fastify.addHook('onError', async (req, reply, error) => {
99
+ log.debug(`onError ${error}`)
100
+ })
101
+
102
+ fastify.addHook('onRequest', async (req, reply) => {
103
+ log.debug(`onRequest ${req.method} ${req.url}`)
104
+ req.user = {
105
+ id: 306,
106
+ name: 'Huseyin',
107
+ roles: ['admin', 'public']
108
+ }
109
+ })
110
+
111
+ fastify.addHook('preParsing', async (req) => {
112
+ log.debug(`preParsing ${req.method} ${req.url}`)
113
+ req.user = {
114
+ id: 42,
115
+ name: 'Jane Doe',
116
+ roles: ['admin', 'public']
117
+ }
118
+ })
119
+
120
+ const routes = loaderRouter.load()
121
+ routes && loaderRouter.apply(fastify, routes)
122
+ }
123
+
124
+ async function addFastifySwagger(fastify: FastifyInstance) {
125
+ const { NODE_ENV, SWAGGER, SWAGGER_TITLE, SWAGGER_DESCRIPTION, SWAGGER_VERSION } = process.env
126
+ const loadSwagger = yn(SWAGGER, false)
127
+
128
+ if (loadSwagger && NODE_ENV !== 'production') {
129
+ log.info('Add swagger plugin')
130
+
131
+ await fastify.register(swagger, {
132
+ swagger: {
133
+ info: {
134
+ title: SWAGGER_TITLE || 'API Documentation',
135
+ description: SWAGGER_DESCRIPTION || 'List of available APIs and schemes to use',
136
+ version: SWAGGER_VERSION || '0.1.0'
137
+ },
138
+ consumes: ['application/json'],
139
+ produces: ['application/json']
140
+ }
141
+ })
142
+
143
+ await fastify.register(swaggerUI, {
144
+ routePrefix: '/documentation',
145
+ uiConfig: {
146
+ docExpansion: 'list',
147
+ deepLinking: true,
148
+ defaultModelsExpandDepth: 1
149
+ },
150
+ uiHooks: {
151
+ onRequest: function (request, reply, next) {
152
+ next()
153
+ },
154
+ preHandler: function (request, reply, next) {
155
+ next()
156
+ }
157
+ },
158
+ staticCSP: true,
159
+ transformStaticCSP: (header) => header
160
+ })
161
+
162
+ await fastify.put(
163
+ '/some-route/:id',
164
+ {
165
+ schema: {
166
+ description: 'post some data',
167
+ tags: ['user', 'code'],
168
+ deprecated: true,
169
+ summary: 'qwerty',
170
+ params: {
171
+ type: 'object',
172
+ properties: {
173
+ id: {
174
+ type: 'string',
175
+ description: 'user id'
176
+ }
177
+ }
178
+ },
179
+ body: {
180
+ type: 'object',
181
+ properties: {
182
+ hello: { type: 'string' },
183
+ obj: {
184
+ type: 'object',
185
+ properties: {
186
+ some: { type: 'string' }
187
+ }
188
+ }
189
+ }
190
+ },
191
+ response: {
192
+ 201: {
193
+ description: 'Successful response',
194
+ type: 'object',
195
+ properties: {
196
+ hello: { type: 'string' }
197
+ }
198
+ },
199
+ default: {
200
+ description: 'Default response',
201
+ type: 'object',
202
+ properties: {
203
+ foo: { type: 'string' }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ },
209
+ (req, reply) => {}
210
+ )
211
+ }
212
+ }
213
+ const opts = yn(process.env.LOG_FASTIFY, false) ? { logger: logger } : {}
214
+ Fastify(opts).then(async (fastify) => {
215
+ const { HOST: host = '0.0.0.0', PORT: port = '2230', GRAPHQL } = process.env
216
+ const { SRV_CORS, SRV_HELMET, SRV_RATELIMIT, SRV_COMPRESS } = process.env
217
+
218
+ const loadApollo = yn(GRAPHQL, true)
219
+ const addPluginCors = yn(SRV_CORS, true)
220
+ const addPluginHelmet = yn(SRV_HELMET, true)
221
+ const addPluginRateLimit = yn(SRV_RATELIMIT, true)
222
+ const addPluginCompress = yn(SRV_COMPRESS, true)
223
+
224
+ log.t && log.trace(`Attach Apollo Server ${loadApollo}`)
225
+ log.t && log.trace(`Add plugin CORS: ${addPluginCors}`)
226
+ log.t && log.trace(`Add plugin HELMET: ${!loadApollo ? addPluginHelmet : 'Not usable with Apollo'}`)
227
+ log.t && log.trace(`Add plugin COMPRESS: ${addPluginCompress}`)
228
+ log.t && log.trace(`Add plugin RATELIMIT: ${addPluginRateLimit}`)
229
+
230
+ const apollo = loadApollo ? await attachApollo(fastify) : null
231
+ // Helmet is not usable with Apollo Server
232
+ !loadApollo && addPluginHelmet && (await fastify.register(helmet))
233
+
234
+ // Usable with Apollo Server
235
+ addPluginRateLimit && (await fastify.register(rateLimit))
236
+ addPluginCors && (await fastify.register(cors))
237
+ addPluginCompress && (await fastify.register(compress))
238
+
239
+ await addFastifySwagger(fastify)
240
+ await addApolloRouting(fastify, apollo)
241
+ await addFastifyRouting(fastify)
242
+
243
+ fastify
244
+ .listen({
245
+ port: Number(port),
246
+ host: host
247
+ })
248
+ .then((address) => {
249
+ const elapsed = (new Date().getTime() - begin) / 100
250
+ log.info(`All stuff loaded in ${elapsed} sec`)
251
+ log.info(`🚀 Server ready at ${address}`)
252
+ })
253
+ })
@@ -0,0 +1,13 @@
1
+ import { roles as configRoles } from '../config/roles'
2
+
3
+ export function load() {
4
+ const roles: {
5
+ [key in RoleKey]?: Role
6
+ } = {}
7
+
8
+ configRoles.forEach((role) => {
9
+ roles[role.code as RoleKey] = role
10
+ })
11
+
12
+ return { ...roles } as Roles
13
+ }
@@ -0,0 +1,179 @@
1
+ import { FastifyReply, FastifyRequest } from 'fastify'
2
+
3
+ const glob = require('glob')
4
+ const path = require('path')
5
+ const methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS']
6
+
7
+ export function load(): ConfiguredRoute[] {
8
+ const check = true,
9
+ print = true,
10
+ load = true
11
+
12
+ const validRoutes: ConfiguredRoute[] = []
13
+ const patterns = [`${__dirname}/../api/**/routes.{ts,js}`, `${process.cwd()}/src/api/**/routes.{ts,js}`]
14
+
15
+ patterns.forEach((pattern) => {
16
+ check && log.i && log.info('Looking for ' + pattern)
17
+ glob.sync(pattern).forEach((f: string, index: number, values: string[]) => {
18
+ const base = path.dirname(f)
19
+ const dir = path.basename(base)
20
+ const file = path.join(dir, path.basename(f))
21
+
22
+ // allow array or structure
23
+ const routesjs = require(f)
24
+ const { routes = [], config: defaultConfig = {} } = routesjs?.default
25
+
26
+ // adjust default config
27
+ if (!defaultConfig.enable) defaultConfig.enable = true
28
+ if (defaultConfig.deprecated == null) defaultConfig.deprecated = false
29
+ if (defaultConfig.controller == null) defaultConfig.controller = 'controller'
30
+
31
+ check && log.i && log.info(`Load ${file} with ${routes.length} routes defined`)
32
+ print && log.d && log.debug(`Valid routes loaded from ${file}`)
33
+
34
+ routes.forEach((route: Route, index: number) => {
35
+ const errors = []
36
+ const { method: methodCase, path: pathName = '/', handler, config, middlewares = [], roles = [] } = route
37
+
38
+ // specific route config
39
+ const {
40
+ title = '',
41
+ description = '',
42
+ enable = defaultConfig.enable || true,
43
+ deprecated = defaultConfig.deprecated || false,
44
+ version = defaultConfig.version || '',
45
+ params,
46
+ body,
47
+ response
48
+ } = config || {}
49
+
50
+ // adjust something
51
+ const endpoint = `${dir}${pathName.replace(/\/+$/, '')}`
52
+ const method = methodCase.toUpperCase()
53
+ const num = index + 1
54
+ const handlerParts = handler.split('.')
55
+
56
+ if (enable) {
57
+ if (!pathName.startsWith('/')) {
58
+ errors.push(`Error in [${file}] bad path [${pathName}] at route n. ${num}`)
59
+ }
60
+
61
+ if (!methods.includes(method)) {
62
+ errors.push(`Error in [${file}] bad method [${method}] at route n. ${num}`)
63
+ }
64
+
65
+ if (handlerParts.length !== 2) {
66
+ errors.push(`Error in [${file}] bad handler [${handler}] at route n. ${num}`)
67
+ }
68
+
69
+ const key = method + endpoint + version
70
+ if (validRoutes.some((r) => `${r.method}${r.path}${r.doc?.version}` === key)) {
71
+ errors.push(`Error in [${file}] duplicated path [${pathName}] at route n. ${num}`)
72
+ }
73
+
74
+ if (errors.length > 0) {
75
+ check && log.e && errors.forEach((error) => log.error(error))
76
+ }
77
+ }
78
+
79
+ if (errors.length == 0) {
80
+ enable && print
81
+ ? log.d &&
82
+ log.debug(
83
+ `* Method [${method}] path ${endpoint} handler ${handler}, has middleware? ${
84
+ (middlewares && middlewares.length) || 'no'
85
+ }`
86
+ )
87
+ : !enable
88
+ ? log.w && log.warn(`* Method [${method}] path ${endpoint} handler ${handler} disabled. Skip.`)
89
+ : log.i && log.info(`* Method [${method}] path ${endpoint} handler ${handler} enabled.`)
90
+
91
+ validRoutes.push({
92
+ handler,
93
+ method,
94
+ path: '/' + endpoint,
95
+ middlewares,
96
+ roles,
97
+ enable,
98
+ base,
99
+ file: path.join(base, defaultConfig.controller, handlerParts[0]),
100
+ func: handlerParts[1],
101
+ // swagger
102
+ doc: {
103
+ summary: title,
104
+ description,
105
+ deprecated,
106
+ version,
107
+ params,
108
+ body,
109
+ response
110
+ }
111
+ })
112
+ }
113
+ })
114
+ })
115
+ })
116
+
117
+ return validRoutes
118
+ }
119
+
120
+ function normalizeMiddlewarePath(base: string, middleware: string = '') {
121
+ const key = 'global.'
122
+ const idx = middleware.indexOf(key)
123
+ return idx == 0
124
+ ? path.resolve(__dirname + '/../middleware/' + middleware.substring(key.length))
125
+ : path.resolve(base + '/middleware/' + middleware)
126
+ }
127
+
128
+ export function apply(server: any, routes: ConfiguredRoute[]): void {
129
+ log.t && log.trace(`Apply ${routes.length} routes to server with pid ${process.pid}`)
130
+
131
+ routes.forEach(async ({ handler, method, path, middlewares, roles, enable, base, file, func, doc }) => {
132
+ if (enable) {
133
+ log.t && log.trace(`Add path ${method} ${path} on handle ${handler}`)
134
+
135
+ const allMiddlewares =
136
+ middlewares?.length > 0 ? middlewares.map((m) => require(normalizeMiddlewarePath(base, m))) : []
137
+
138
+ server.route({
139
+ method: method,
140
+ path: path,
141
+ schema: doc,
142
+ // preHandler: allMiddlewares,
143
+ handler: (request: FastifyRequest, reply: FastifyReply) => {
144
+ try {
145
+ if (roles?.length > 0) {
146
+ const userRoles = request.user?.roles || []
147
+ const resolvedRole = roles.filter((r) => userRoles.includes(r.code))
148
+ if (!resolvedRole || resolvedRole.length === 0) {
149
+ log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${path}`)
150
+ return reply.code(403).send()
151
+ }
152
+ }
153
+ return require(file + '.ts')[func](request, reply)
154
+ } catch (err) {
155
+ log.e && log.error(`Cannot find ${file}.js or method ${func}: ${err}`)
156
+ return reply.code(500).send(`Invalid handler ${handler}`)
157
+ }
158
+ }
159
+ })
160
+
161
+ // server[method](path, (request: FastifyRequest, reply: FastifyReply) => {
162
+ // try {
163
+ // if (roles?.length > 0) {
164
+ // const userRoles = request.user?.roles || []
165
+ // const resolvedRole = roles.filter((r) => userRoles.includes(r.code))
166
+ // if (!resolvedRole || resolvedRole.length === 0) {
167
+ // log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${path}`)
168
+ // return reply.code(403).send()
169
+ // }
170
+ // }
171
+ // return require(file + '.ts')[func](request, reply)
172
+ // } catch (err) {
173
+ // log.e && log.error(`Cannot find ${file}.js or method ${func}: ${err}`)
174
+ // return reply.code(500).send(`Invalid handler ${handler}`)
175
+ // }
176
+ // })
177
+ }
178
+ })
179
+ }
@@ -0,0 +1,12 @@
1
+ import { FastifyReply, FastifyRequest } from 'fastify'
2
+
3
+ const log = global.log
4
+ module.exports = (req: FastifyRequest, res: FastifyReply, next: any) => {
5
+ try {
6
+ // TODO: do something and then you can throw an exception or call next()..
7
+ return next()
8
+ } catch (err) {
9
+ log.e && log.error(`Upps, something just happened ${err}`)
10
+ res.code(403).send(err)
11
+ }
12
+ }
@@ -0,0 +1,15 @@
1
+ import { FastifyReply, FastifyRequest } from 'fastify'
2
+
3
+ const log = global.log
4
+ module.exports = (req: FastifyRequest, res: FastifyReply, next: any) => {
5
+ try {
6
+ if (!!req.user?.id && (req.user?.roles || []).includes(roles.admin.code)) {
7
+ log.d && log.trace('isAdmin - user id ' + req.user?.id)
8
+ return next()
9
+ }
10
+ throw new Error('User not valid')
11
+ } catch (err) {
12
+ log.e && log.error(`Upps, something just happened ${err}`)
13
+ res.code(403).send(err)
14
+ }
15
+ }
@@ -0,0 +1,16 @@
1
+ import { FastifyReply, FastifyRequest } from 'fastify'
2
+
3
+ const log = global.log
4
+ module.exports = (req: FastifyRequest, res: FastifyReply, next: any) => {
5
+ try {
6
+ // TODO: do something and then you can throw an exception or call next()..
7
+ if (!!req.user?.id) {
8
+ log.d && log.trace('isAuthenticated - user id ' + req.user?.id)
9
+ return next()
10
+ }
11
+ throw new Error('User not authenticated')
12
+ } catch (err) {
13
+ log.e && log.error(`Upps, something just happened ${err}`)
14
+ res.code(403).send(err)
15
+ }
16
+ }
@@ -0,0 +1,66 @@
1
+ export {}
2
+
3
+ declare global {
4
+ interface AuthenticatedUser {
5
+ id: number
6
+ name: string
7
+ roles: string[]
8
+ }
9
+
10
+ interface Role {
11
+ code: string
12
+ name: string
13
+ description: string
14
+ }
15
+
16
+ declare enum RoleKey {
17
+ public = 'public',
18
+ admin = 'admin',
19
+ backoffice = 'backoffice'
20
+ }
21
+
22
+ declare type Roles = {
23
+ [key in RoleKey]: Role
24
+ }
25
+
26
+ interface RouteConfig {
27
+ title: string
28
+ description: string
29
+ enable: boolean
30
+ deprecated: boolean
31
+ version: string
32
+ params?: any
33
+ body?: any
34
+ response?: any
35
+ }
36
+
37
+ interface Route {
38
+ method: string
39
+ path: string
40
+ handler: string
41
+ roles: Role[]
42
+ config?: RouteConfig
43
+ middlewares: string[]
44
+ }
45
+
46
+ interface ConfiguredRoute {
47
+ enable: boolean
48
+ method: any
49
+ path: string
50
+ handler: any
51
+ file: string
52
+ func: any
53
+ base: string
54
+ middlewares: string[]
55
+ roles: Role[]
56
+ doc: {
57
+ summary?: string
58
+ description?: string
59
+ deprecated?: boolean
60
+ version?: string
61
+ params?: any
62
+ body?: any
63
+ response?: any
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,71 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Minimal logger thanks to Pino
5
+ */
6
+
7
+ // log.debug('test log test log test log')
8
+ // log.error('test log test log test log')
9
+ // log.warn('test log test log test log')
10
+ // log.info('test log test log test log')
11
+ // log.fatal('test log test log test log')
12
+ // log.trace('test log test log test log')
13
+
14
+ import pino from 'pino'
15
+ import yn from './yn'
16
+
17
+ const logLevels = ['fatal', 'error', 'warn', 'info', 'debug', 'trace']
18
+
19
+ const { LOG_LEVEL, LOG_COLORIZE, LOG_TIMESTAMP, LOG_TIMESTAMP_READABLE } = process.env
20
+
21
+ function getLogLevel(): string {
22
+ const lvl = LOG_LEVEL?.toLowerCase()
23
+ return LOG_LEVEL && logLevels.includes(lvl!) ? lvl! : 'debug'
24
+ }
25
+
26
+ const logColorize = yn(LOG_COLORIZE, true)
27
+ const logTimestamp = yn(LOG_TIMESTAMP, true)
28
+ const logTimestampReadable = yn(LOG_TIMESTAMP_READABLE, true)
29
+
30
+ const loggerConfig = {
31
+ level: getLogLevel(),
32
+ timestamp: logTimestamp,
33
+ transport: {
34
+ target: 'pino-pretty',
35
+ options: {
36
+ translateTime: logTimestampReadable ? 'yyyymmdd HH:MM:ss.l' : false,
37
+ colorize: logColorize
38
+ }
39
+ }
40
+ }
41
+
42
+ let logger = pino(loggerConfig)
43
+ const logLevel = logger.levels.values[loggerConfig.level]
44
+
45
+ // Level: trace debug info warn error fatal silent
46
+ // Value: 10 20 30 40 50 60 Infinity
47
+
48
+ const loggerExt = Object.assign(logger, {
49
+ t: logLevel < 11,
50
+ d: logLevel < 21,
51
+ i: logLevel < 31,
52
+ w: logLevel < 41,
53
+ e: logLevel < 51,
54
+ f: logLevel < 61,
55
+ getLogLevel: getLogLevel,
56
+ loggerConfig: loggerConfig,
57
+ updateLevel: () => {
58
+ loggerExt.t = loggerExt.levelVal < 11
59
+ loggerExt.d = loggerExt.levelVal < 21
60
+ loggerExt.i = loggerExt.levelVal < 31
61
+ loggerExt.w = loggerExt.levelVal < 41
62
+ loggerExt.e = loggerExt.levelVal < 51
63
+ loggerExt.f = loggerExt.levelVal < 61
64
+ }
65
+ })
66
+
67
+ loggerExt.on('level-change', () => {
68
+ log.trace('Log level changed')
69
+ })
70
+
71
+ export default loggerExt
@@ -0,0 +1,25 @@
1
+ const pkg = require('../../package.json')
2
+
3
+ /**
4
+ * Minimal mark printed at startup
5
+ */
6
+
7
+ export function print(logg: any = log): void {
8
+ logg.i && logg.info('Ciao')
9
+ logg.i && logg.info(` ,--. ,--. `)
10
+ logg.i && logg.info(`.--. ,--,---.| |,---.,--,--,--,--\\\`--',---. `)
11
+ logg.i && logg.info(` \\ '' | .-. | / .--' ,-. | ,--/ .--' `)
12
+ logg.i && logg.info(` \\ /' '-' | \\ \`--\\ '-' | || | \\ \`--. `)
13
+ logg.i && logg.info(` \`--' \`---'\`--'\`---'\`--\`--\`--''--\`--'\`---' `)
14
+ logg.i && logg.info('')
15
+ logg.t && logg.trace(`Package ${pkg.name}`)
16
+ logg.i && logg.info(`License ${pkg.license}`)
17
+ logg.i && logg.info(`Version ${pkg.version}`)
18
+ logg.i && logg.info(`Codename ${pkg.codename}`)
19
+ logg.i && logg.info(`Environment ${process.env.NODE_ENV}`)
20
+ logg.t && logg.trace(`Platform ${process.platform} ${process.arch}`)
21
+ logg.t && logg.trace(`Root path ${process.cwd()}`)
22
+ logg.t && logg.trace(`Node ${process.version}`)
23
+ logg.t && logg.trace(`Release ${JSON.stringify(process.release)}`)
24
+ logg.t && logg.trace(`Versions ${JSON.stringify(process.versions)}`)
25
+ }
package/lib/util/yn.ts ADDED
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ export default function yn(value: any, defaultValue: boolean): boolean {
4
+ if (value === undefined || value === null) {
5
+ return defaultValue
6
+ }
7
+
8
+ const val = String(value).trim()
9
+
10
+ if (/^(?:y|yes|true|1|on)$/i.test(val)) {
11
+ return true
12
+ }
13
+
14
+ if (/^(?:n|no|false|0|off)$/i.test(val)) {
15
+ return false
16
+ }
17
+
18
+ return defaultValue || false
19
+ }
package/nodemon.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "watch": [
3
+ "lib"
4
+ ],
5
+ "verbose": true,
6
+ "ext": "js,ts,json,jsonc",
7
+ "ignore": [
8
+ ".git",
9
+ "coverage",
10
+ "dist",
11
+ "lib/**/*.spec.ts",
12
+ "node_modules"
13
+ ],
14
+ "exec": "ts-node ./lib/index.ts"
15
+ }