@anthonylzq/simba.js 5.1.0 → 6.0.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/README.md +4 -1
- package/lib/index.js +42 -4
- package/lib/src/functions/api/database.js +139 -15
- package/lib/src/functions/api/index.js +4 -2
- package/lib/src/functions/api/network.js +164 -256
- package/lib/src/functions/api/schemas.js +18 -29
- package/lib/src/functions/api/services.js +7 -9
- package/lib/src/functions/api/types.js +0 -1
- package/lib/src/functions/docker.js +20 -4
- package/lib/src/functions/eslint.js +40 -12
- package/lib/src/functions/ghat.js +65 -0
- package/lib/src/functions/gitignore.js +1 -0
- package/lib/src/functions/packageJson.js +11 -2
- package/lib/src/functions/tests.js +615 -0
- package/lib/src/index.js +24 -13
- package/lib/src/utils/constants.js +3 -0
- package/package.json +38 -27
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<h1 align="center">
|
|
2
2
|
<!-- <p align="center">Simba.js</p> -->
|
|
3
|
-
<a href="https://simbajs.notion.site/783092dc7d444067b4c56a25d671f658?v=31060f3d17524ca58870e86c2960a6df"><img src="https://i.ibb.co/
|
|
3
|
+
<a href="https://simbajs.notion.site/783092dc7d444067b4c56a25d671f658?v=31060f3d17524ca58870e86c2960a6df"><img src="https://i.ibb.co/QFX0WnH/simba-pink.png" alt="Simba.js"></a>
|
|
4
4
|
</h1>
|
|
5
5
|
|
|
6
6
|
[](https://www.npmjs.com/package/@anthonylzq/simba.js)
|
|
7
7
|
[](https://github.com/AnthonyLzq/simba.js/blob/master/LICENSE)
|
|
8
8
|
[](https://standardjs.com)
|
|
9
9
|
[](https://reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
|
|
10
|
+
[](https://github.com/AnthonyLzq/simba.js/actions/workflows/lint.yml)
|
|
11
|
+
[](https://github.com/AnthonyLzq/simba.js/actions/workflows/test.yml)
|
|
10
12
|
|
|
11
13
|
Set up a modern backend app by running one command. This project has the goal to create a complete setup for a backend application using `TypeScript` and `Express` or `Fastify`. It will create many files that are usually created manually. Think about Simba.js like a [CRA](https://create-react-app.dev/), but for backend development. Check the [**project structure**](#project-structure) for more information.
|
|
12
14
|
|
|
@@ -442,6 +444,7 @@ If you want to check the content of the files, please check the [example](https:
|
|
|
442
444
|
- If you provide a project name that contains spaces, something like 'My awesome Project', every space will be replaced with a hyphen. So at the end your project name will be 'My-awesome-project', but in its README.md file, the hyphens will be removed and the project name will be parsed to title case (My Awesome Project).
|
|
443
445
|
- Finally, `git` will be initialized and a list of libraries will be installed. Check the [**notes**](#notes).
|
|
444
446
|
- Relative imports is already configured, you do not need to import a file using `../../../some/very/nested/stuff/in/other/folder`, you can use `some/very/nested/stuff/in/other/folder` assuming that your folder is under the `src` folder.
|
|
447
|
+
- The Fastify version is set to v3 because Apollo Server has not yet provided support for Fastify v4 yet, and it is difficult to have support for two different major versions of Fastify, so until Apollo Server supports Fastify v4, this package will use Fastify v3.
|
|
445
448
|
|
|
446
449
|
## What is new?
|
|
447
450
|
|
package/lib/index.js
CHANGED
|
@@ -12,7 +12,7 @@ const argv = yargs(hideBin(process.argv))
|
|
|
12
12
|
'"simba [options]" (if you it installed globally) or only "simba -q" if you want to be asked for the options one by one'
|
|
13
13
|
)
|
|
14
14
|
.example(
|
|
15
|
-
"simba -N 'Project Name' -D 'Project description' -a Anthony -e sluzquinosa@uni.pe"
|
|
15
|
+
"simba -N 'Project Name' -D 'Project description' -a Anthony -e sluzquinosa@uni.pe -l mit -F --tests --ghat"
|
|
16
16
|
)
|
|
17
17
|
.alias('N', 'projectName')
|
|
18
18
|
.nargs('N', 1)
|
|
@@ -54,6 +54,16 @@ const argv = yargs(hideBin(process.argv))
|
|
|
54
54
|
.describe('F', 'Whether or not you want to use Fastify for your project')
|
|
55
55
|
.alias('g', 'graphql')
|
|
56
56
|
.describe('g', 'Whether or not you want to use GraphQL for your project')
|
|
57
|
+
.alias('t', 'tests')
|
|
58
|
+
.describe(
|
|
59
|
+
't',
|
|
60
|
+
'Whether or not you want to have a basic suit of unit tests with Jest'
|
|
61
|
+
)
|
|
62
|
+
.alias('ghat', 'gh-action-tests')
|
|
63
|
+
.describe(
|
|
64
|
+
'ghat',
|
|
65
|
+
'Whether or not you want to have a GitHub Action with a CI for your tests. If this option is set to true, the tests flag must be set to true.'
|
|
66
|
+
)
|
|
57
67
|
.default({
|
|
58
68
|
H: false,
|
|
59
69
|
n: false,
|
|
@@ -63,9 +73,11 @@ const argv = yargs(hideBin(process.argv))
|
|
|
63
73
|
f: 'src/index.ts',
|
|
64
74
|
q: false,
|
|
65
75
|
F: false,
|
|
66
|
-
g: false
|
|
76
|
+
g: false,
|
|
77
|
+
t: false,
|
|
78
|
+
ghat: false
|
|
67
79
|
})
|
|
68
|
-
.boolean(['H', 'n', 'q', 'F', 'g'])
|
|
80
|
+
.boolean(['H', 'n', 'q', 'F', 'g', 't'])
|
|
69
81
|
.help('h')
|
|
70
82
|
.alias('h', 'help')
|
|
71
83
|
.epilog('Developed by AnthonyLzq').argv
|
|
@@ -84,7 +96,9 @@ const config = {
|
|
|
84
96
|
manager: 'yarn add',
|
|
85
97
|
mainFile: 'src/index.ts',
|
|
86
98
|
fastify: false,
|
|
87
|
-
graphql: false
|
|
99
|
+
graphql: false,
|
|
100
|
+
tests: true,
|
|
101
|
+
ghat: true
|
|
88
102
|
}
|
|
89
103
|
const UNLICENSED = 'unlicensed'
|
|
90
104
|
const LICENSES = [
|
|
@@ -216,6 +230,21 @@ const main = async () => {
|
|
|
216
230
|
}
|
|
217
231
|
)
|
|
218
232
|
config.mainFile = readLineSync.question('> Main file (src/index.ts): ')
|
|
233
|
+
config.tests = readLineSync.keyInYNStrict(
|
|
234
|
+
'> Would you want to have a basic suit of tests with Jest? ',
|
|
235
|
+
{
|
|
236
|
+
caseSensitive: false
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if (config.tests)
|
|
241
|
+
config.ghat = readLineSync.keyInYNStrict(
|
|
242
|
+
'> Would you want to have a basic GitHub Action for the suit of tests?',
|
|
243
|
+
{
|
|
244
|
+
caseSensitive: false
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
else config.ghat = false
|
|
219
248
|
} else {
|
|
220
249
|
if (!argv.author) return console.log('Error! An author is required!')
|
|
221
250
|
else config.author = argv.author
|
|
@@ -289,6 +318,15 @@ const main = async () => {
|
|
|
289
318
|
|
|
290
319
|
if (!argv.mainFile) console.log('Using src/index.ts as default main file')
|
|
291
320
|
else config.mainFile = argv.mainFile
|
|
321
|
+
|
|
322
|
+
if (argv.tests) config.tests = true
|
|
323
|
+
|
|
324
|
+
if (argv.ghat) config.ghat = true
|
|
325
|
+
|
|
326
|
+
if (!config.tests && argv.ghat)
|
|
327
|
+
return console.log(
|
|
328
|
+
'GitHub Action for tests can not be set to true if the tests flag is set to false'
|
|
329
|
+
)
|
|
292
330
|
}
|
|
293
331
|
|
|
294
332
|
await installation(config)
|
|
@@ -5,9 +5,10 @@ const writeFile = require('../../utils/writeFile')
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @param {Object} args
|
|
8
|
-
* @param {String} projectName
|
|
8
|
+
* @param {String} args.projectName
|
|
9
|
+
* @param {Boolean} args.fastify
|
|
9
10
|
*/
|
|
10
|
-
const mongoF = async ({ projectName }) => {
|
|
11
|
+
const mongoF = async ({ projectName, fastify }) => {
|
|
11
12
|
const createFoldersCommand = `mkdir ${projectName}/src/database/mongo \
|
|
12
13
|
${projectName}/src/database/mongo/models \
|
|
13
14
|
${projectName}/src/database/mongo/queries`
|
|
@@ -22,9 +23,126 @@ ${projectName}/src/database/mongo/queries`
|
|
|
22
23
|
file: `${projectName}/src/database/index.ts`
|
|
23
24
|
},
|
|
24
25
|
mongo: {
|
|
26
|
+
connection: {
|
|
27
|
+
content: fastify
|
|
28
|
+
? `import { connect, connection } from 'mongoose'
|
|
29
|
+
import { FastifyLoggerInstance } from 'fastify'
|
|
30
|
+
|
|
31
|
+
const ENVIRONMENTS_WITHOUT_RECONNECTION = ['ci', 'local']
|
|
32
|
+
const dbConnection = async (
|
|
33
|
+
logger: FastifyLoggerInstance
|
|
34
|
+
): Promise<{
|
|
35
|
+
connect: () => Promise<typeof import('mongoose')>
|
|
36
|
+
disconnect: () => Promise<void>
|
|
37
|
+
}> => {
|
|
38
|
+
const connectionConfig = {
|
|
39
|
+
keepAlive: true,
|
|
40
|
+
useNewUrlParser: true,
|
|
41
|
+
useUnifiedTopology: true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
connection.on('connected', () => {
|
|
45
|
+
logger.info('Mongo connection established.')
|
|
46
|
+
})
|
|
47
|
+
connection.on('reconnected', () => {
|
|
48
|
+
logger.info('Mongo connection reestablished')
|
|
49
|
+
})
|
|
50
|
+
connection.on('disconnected', () => {
|
|
51
|
+
if (
|
|
52
|
+
!ENVIRONMENTS_WITHOUT_RECONNECTION.includes(
|
|
53
|
+
process.env.NODE_ENV as string
|
|
54
|
+
)
|
|
55
|
+
) {
|
|
56
|
+
logger.info(
|
|
57
|
+
'Mongo connection disconnected. Trying to reconnected to Mongo...'
|
|
58
|
+
)
|
|
59
|
+
setTimeout(() => {
|
|
60
|
+
connect(process.env.MONGO_URI as string, {
|
|
61
|
+
...connection,
|
|
62
|
+
connectTimeoutMS: 3000,
|
|
63
|
+
socketTimeoutMS: 3000
|
|
64
|
+
})
|
|
65
|
+
}, 3000)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
connection.on('close', () => {
|
|
69
|
+
logger.info('Mongo connection closed')
|
|
70
|
+
})
|
|
71
|
+
connection.on('error', (e: Error) => {
|
|
72
|
+
logger.info('Mongo connection error:')
|
|
73
|
+
logger.error(e)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
connect: () => connect(process.env.MONGO_URI as string, connectionConfig),
|
|
78
|
+
disconnect: () => connection.close()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { dbConnection }
|
|
83
|
+
`
|
|
84
|
+
: `import { connect, connection } from 'mongoose'
|
|
85
|
+
import { HttpLogger } from 'express-pino-logger'
|
|
86
|
+
|
|
87
|
+
const ENVIRONMENTS_WITHOUT_RECONNECTION = ['ci', 'local']
|
|
88
|
+
const dbConnection = async (
|
|
89
|
+
logger: HttpLogger['logger']
|
|
90
|
+
): Promise<{
|
|
91
|
+
connect: () => Promise<typeof import('mongoose')>
|
|
92
|
+
disconnect: () => Promise<void>
|
|
93
|
+
}> => {
|
|
94
|
+
const connectionConfig = {
|
|
95
|
+
keepAlive: true,
|
|
96
|
+
useNewUrlParser: true,
|
|
97
|
+
useUnifiedTopology: true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
connection.on('connected', () => {
|
|
101
|
+
logger.info('Mongo connection established.')
|
|
102
|
+
})
|
|
103
|
+
connection.on('reconnected', () => {
|
|
104
|
+
logger.info('Mongo connection reestablished')
|
|
105
|
+
})
|
|
106
|
+
connection.on('disconnected', () => {
|
|
107
|
+
if (
|
|
108
|
+
!ENVIRONMENTS_WITHOUT_RECONNECTION.includes(
|
|
109
|
+
process.env.NODE_ENV as string
|
|
110
|
+
)
|
|
111
|
+
) {
|
|
112
|
+
logger.info(
|
|
113
|
+
'Mongo connection disconnected. Trying to reconnected to Mongo...'
|
|
114
|
+
)
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
connect(process.env.MONGO_URI as string, {
|
|
117
|
+
...connection,
|
|
118
|
+
connectTimeoutMS: 3000,
|
|
119
|
+
socketTimeoutMS: 3000
|
|
120
|
+
})
|
|
121
|
+
}, 3000)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
connection.on('close', () => {
|
|
125
|
+
logger.info('Mongo connection closed')
|
|
126
|
+
})
|
|
127
|
+
connection.on('error', (e: Error) => {
|
|
128
|
+
logger.info('Mongo connection error:')
|
|
129
|
+
logger.error(e)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
connect: () => connect(process.env.MONGO_URI as string, connectionConfig),
|
|
134
|
+
disconnect: () => connection.close()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { dbConnection }
|
|
139
|
+
`,
|
|
140
|
+
file: `${projectName}/src/database/mongo/connection.ts`
|
|
141
|
+
},
|
|
25
142
|
index: {
|
|
26
143
|
content: `export * from './models'
|
|
27
144
|
export * from './queries'
|
|
145
|
+
export * from './connection'
|
|
28
146
|
`,
|
|
29
147
|
file: `${projectName}/src/database/mongo/index.ts`
|
|
30
148
|
},
|
|
@@ -72,13 +190,14 @@ export { UserModel }
|
|
|
72
190
|
file: `${projectName}/src/database/mongo/queries/index.ts`
|
|
73
191
|
},
|
|
74
192
|
user: {
|
|
75
|
-
content: `import { Document, Types } from 'mongoose'
|
|
193
|
+
content: `import { Document, MergeType, Types } from 'mongoose'
|
|
76
194
|
|
|
77
195
|
import { UserModel } from '..'
|
|
78
|
-
import { UserDTO } from 'schemas'
|
|
196
|
+
import { User, UserDTO, UserWithId } from 'schemas'
|
|
79
197
|
|
|
80
198
|
const userDBOtoDTO = (
|
|
81
|
-
userDBO: Document<unknown, unknown, UserDBO
|
|
199
|
+
userDBO: Document<unknown, unknown, MergeType<UserDBO, UserDBO>> &
|
|
200
|
+
Omit<UserDBO, keyof UserDBO> &
|
|
82
201
|
UserDBO & {
|
|
83
202
|
_id: Types.ObjectId
|
|
84
203
|
}
|
|
@@ -88,7 +207,7 @@ const userDBOtoDTO = (
|
|
|
88
207
|
updatedAt: userDBO.updatedAt.toISOString()
|
|
89
208
|
})
|
|
90
209
|
|
|
91
|
-
const store = async (userData:
|
|
210
|
+
const store = async (userData: User): Promise<UserDTO> => {
|
|
92
211
|
const user = new UserModel(userData)
|
|
93
212
|
|
|
94
213
|
await user.save()
|
|
@@ -124,7 +243,7 @@ const get = async (
|
|
|
124
243
|
return users.map(u => userDBOtoDTO(u))
|
|
125
244
|
}
|
|
126
245
|
|
|
127
|
-
const update = async (userData:
|
|
246
|
+
const update = async (userData: UserWithId): Promise<UserDTO | null> => {
|
|
128
247
|
const { id, ...rest } = userData
|
|
129
248
|
const user = await UserModel.findByIdAndUpdate(id, rest, { new: true })
|
|
130
249
|
|
|
@@ -140,21 +259,25 @@ export { store, remove, get, update }
|
|
|
140
259
|
}
|
|
141
260
|
|
|
142
261
|
await Promise.all([
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
262
|
+
writeFile(database.index.file, database.index.content),
|
|
263
|
+
writeFile(
|
|
264
|
+
database.mongo.connection.file,
|
|
265
|
+
database.mongo.connection.content
|
|
266
|
+
),
|
|
267
|
+
writeFile(database.mongo.index.file, database.mongo.index.content),
|
|
268
|
+
writeFile(
|
|
146
269
|
database.mongo.models.index.file,
|
|
147
270
|
database.mongo.models.index.content
|
|
148
271
|
),
|
|
149
|
-
|
|
272
|
+
writeFile(
|
|
150
273
|
database.mongo.models.user.file,
|
|
151
274
|
database.mongo.models.user.content
|
|
152
275
|
),
|
|
153
|
-
|
|
276
|
+
writeFile(
|
|
154
277
|
database.mongo.queries.index.file,
|
|
155
278
|
database.mongo.queries.index.content
|
|
156
279
|
),
|
|
157
|
-
|
|
280
|
+
writeFile(
|
|
158
281
|
database.mongo.queries.user.file,
|
|
159
282
|
database.mongo.queries.user.content
|
|
160
283
|
)
|
|
@@ -165,13 +288,14 @@ export { store, remove, get, update }
|
|
|
165
288
|
* @param {Object} args
|
|
166
289
|
* @param {Boolean|undefined} args.mongo
|
|
167
290
|
* @param {String} args.projectName
|
|
291
|
+
* @param {Boolean} args.fastify
|
|
168
292
|
*/
|
|
169
|
-
module.exports = async ({ mongo = true, projectName }) => {
|
|
293
|
+
module.exports = async ({ mongo = true, projectName, fastify }) => {
|
|
170
294
|
const createFoldersCommand = `mkdir ${projectName}/src/database`
|
|
171
295
|
|
|
172
296
|
if (platform() === 'win32')
|
|
173
297
|
await exec(createFoldersCommand.replaceAll('/', '\\'))
|
|
174
298
|
else await exec(createFoldersCommand)
|
|
175
299
|
|
|
176
|
-
if (mongo) await mongoF({ projectName })
|
|
300
|
+
if (mongo) await mongoF({ projectName, fastify })
|
|
177
301
|
}
|
|
@@ -9,6 +9,7 @@ const types = require('./types')
|
|
|
9
9
|
const network = require('./network')
|
|
10
10
|
const utils = require('./utils')
|
|
11
11
|
const writeFile = require('../../utils/writeFile')
|
|
12
|
+
const { ENVIRONMENTS_WITH_MONGO_URI } = require('../../utils/constants')
|
|
12
13
|
|
|
13
14
|
/*
|
|
14
15
|
* Express api:
|
|
@@ -163,7 +164,7 @@ DELETE http://localhost:1996/api/user/60e7e3b93b01c1a7aa74cd6b
|
|
|
163
164
|
},
|
|
164
165
|
'.env': {
|
|
165
166
|
content: `MONGO_URI = ${
|
|
166
|
-
process.env.
|
|
167
|
+
ENVIRONMENTS_WITH_MONGO_URI.includes(process.env.NODE_ENV)
|
|
167
168
|
? process.env.MONGO_URI
|
|
168
169
|
: `mongodb://mongo:mongo@mongo:27017/${projectName}`
|
|
169
170
|
}`,
|
|
@@ -194,7 +195,8 @@ Server.start()
|
|
|
194
195
|
// /database
|
|
195
196
|
database({
|
|
196
197
|
mongo,
|
|
197
|
-
projectName
|
|
198
|
+
projectName,
|
|
199
|
+
fastify
|
|
198
200
|
}),
|
|
199
201
|
// /network
|
|
200
202
|
network({
|