@platformatic/generators 1.13.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.
@@ -0,0 +1,426 @@
1
+ 'use strict'
2
+
3
+ const { readFile } = require('node:fs/promises')
4
+ const { stripVersion, convertServiceNameToPrefix, addPrefixToEnv, extractEnvVariablesFromText } = require('./utils')
5
+ const { join } = require('node:path')
6
+ const { FileGenerator } = require('./file-generator')
7
+ const { generateTests, generatePlugins } = require('./create-plugin')
8
+ const { generateGitignore } = require('./create-gitignore')
9
+ const { NoQuestionsError, PrepareError, MissingEnvVariable } = require('./errors')
10
+ const generateName = require('boring-name-generator')
11
+ /* c8 ignore start */
12
+ const fakeLogger = {
13
+ info: () => {},
14
+ warn: () => {},
15
+ debug: () => {},
16
+ trace: () => {},
17
+ error: () => {}
18
+ }
19
+ /* c8 ignore start */
20
+
21
+ class BaseGenerator extends FileGenerator {
22
+ constructor (opts = {}) {
23
+ super(opts)
24
+ this.type = opts.type
25
+ this.files = []
26
+ this.logger = opts.logger || fakeLogger
27
+ this.questions = []
28
+ this.pkgData = null
29
+ this.inquirer = null
30
+ this.targetDirectory = opts.targetDirectory || null
31
+ this.config = this.getDefaultConfig()
32
+ }
33
+
34
+ getDefaultConfig () {
35
+ return {
36
+ port: 3042,
37
+ hostname: '0.0.0.0',
38
+ plugin: false,
39
+ tests: false,
40
+ typescript: false,
41
+ initGitRepository: false,
42
+ dependencies: {},
43
+ devDependencies: {},
44
+ staticWorkspaceGitHubActions: false,
45
+ dynamicWorkspaceGitHubActions: false,
46
+ isRuntimeContext: false,
47
+ serviceName: '',
48
+ envPrefix: '',
49
+ env: {}
50
+ }
51
+ }
52
+
53
+ getConfigFieldsDefinitions () {
54
+ return []
55
+ }
56
+
57
+ setConfigFields (fields) {
58
+ const availableConfigFields = this.getConfigFieldsDefinitions()
59
+ function shouldHandleConfigField (field) {
60
+ return availableConfigFields.filter((f) => {
61
+ return f.configValue === field.configValue && f.var === field.var
62
+ }).length > 0
63
+ }
64
+ for (const field of fields) {
65
+ if (shouldHandleConfigField(field)) {
66
+ if (field.var) {
67
+ this.config.env[field.var] = field.value
68
+ }
69
+ if (field.configValue) {
70
+ this.config[field.configValue] = field.value
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ getDefaultEnv () {
77
+ return {}
78
+ }
79
+
80
+ setEnv (env) {
81
+ if (this.config.isRuntimeContext) {
82
+ this.config.env = addPrefixToEnv(this.config.env, this.config.envPrefix)
83
+ } else {
84
+ this.config.env = env
85
+ }
86
+ }
87
+
88
+ setConfig (config) {
89
+ if (!config) {
90
+ this.config = this.getDefaultConfig()
91
+ }
92
+ this.config = {
93
+ ...this.getDefaultConfig(),
94
+ ...config
95
+ }
96
+
97
+ if (this.config.isRuntimeContext) {
98
+ if (!this.config.serviceName) {
99
+ this.config.serviceName = generateName().dashed
100
+ }
101
+ // set envPrefix
102
+ if (this.config.serviceName && !this.config.envPrefix) {
103
+ this.config.envPrefix = convertServiceNameToPrefix(this.config.serviceName)
104
+ }
105
+
106
+ // modify env
107
+ this.config.env = addPrefixToEnv(this.config.env, this.config.envPrefix)
108
+ }
109
+
110
+ if (this.config.targetDirectory) {
111
+ this.targetDirectory = this.config.targetDirectory
112
+ }
113
+ }
114
+
115
+ /* c8 ignore start */
116
+ async ask () {
117
+ if (!this.questions.length) {
118
+ throw new NoQuestionsError()
119
+ }
120
+ if (this.inquirer) {
121
+ this.config = await this.inquirer.prompt(this.questions)
122
+ }
123
+ }
124
+
125
+ /* c8 ignore stop */
126
+ appendConfigEnv () {
127
+ const dotEnvFile = this.getFileObject('.env')
128
+ let dotEnvFileContents = dotEnvFile.contents
129
+
130
+ if (this.config.env) {
131
+ Object.entries(this.config.env).forEach((kv) => {
132
+ dotEnvFileContents += `${kv[0]}=${kv[1]}\n`
133
+ })
134
+ dotEnvFile.contents = dotEnvFileContents
135
+ }
136
+ }
137
+
138
+ async prepare () {
139
+ try {
140
+ this.reset()
141
+ await this.getFastifyVersion()
142
+ await this.getPlatformaticVersion()
143
+
144
+ await this._beforePrepare()
145
+
146
+ // generate package.json
147
+ const template = await this.generatePackageJson()
148
+ this.addFile({
149
+ path: '',
150
+ file: 'package.json',
151
+ contents: JSON.stringify(template, null, 2)
152
+ })
153
+
154
+ await this.generateConfigFile()
155
+
156
+ await this.prepareQuestions()
157
+
158
+ await this.generateEnv()
159
+
160
+ if (this.config.typescript) {
161
+ // create tsconfig.json
162
+ this.addFile({
163
+ path: '',
164
+ file: 'tsconfig.json',
165
+ contents: JSON.stringify(this.getTsConfig(), null, 2)
166
+ })
167
+ }
168
+
169
+ if (this.config.plugin) {
170
+ // create plugin
171
+ this.files.push(...generatePlugins(this.config.typescript))
172
+ if (this.config.tests) {
173
+ // create tests
174
+ this.files.push(...generateTests(this.config.typescript, this.type))
175
+ }
176
+ }
177
+ this.files.push(generateGitignore())
178
+ await this._afterPrepare()
179
+ this.checkEnvVariablesInConfigFile()
180
+ return {
181
+ targetDirectory: this.targetDirectory,
182
+ env: this.config.env
183
+ }
184
+ } catch (err) {
185
+ if (err.code?.startsWith('PLT_GEN')) {
186
+ // throw the same error
187
+ throw err
188
+ }
189
+ throw new PrepareError(err.message)
190
+ }
191
+ }
192
+
193
+ checkEnvVariablesInConfigFile () {
194
+ const configFileName = this.getConfigFileName()
195
+ const fileOjbect = this.getFileObject(configFileName)
196
+ const envVars = extractEnvVariablesFromText(fileOjbect.contents)
197
+ const envKeys = Object.keys(this.config.env)
198
+ if (envVars.length > 0) {
199
+ envVars.forEach((ev) => {
200
+ if (!envKeys.includes(ev)) {
201
+ throw new MissingEnvVariable(ev, configFileName)
202
+ }
203
+ })
204
+ }
205
+
206
+ return true
207
+ }
208
+
209
+ getTsConfig () {
210
+ return {
211
+ compilerOptions: {
212
+ module: 'commonjs',
213
+ esModuleInterop: true,
214
+ target: 'es2020',
215
+ sourceMap: true,
216
+ pretty: true,
217
+ noEmitOnError: true,
218
+ incremental: true,
219
+ strict: true,
220
+ outDir: 'dist'
221
+ },
222
+ watchOptions: {
223
+ watchFile: 'fixedPollingInterval',
224
+ watchDirectory: 'fixedPollingInterval',
225
+ fallbackPolling: 'dynamicPriority',
226
+ synchronousWatchDirectory: true,
227
+ excludeDirectories: ['**/node_modules', 'dist']
228
+ }
229
+ }
230
+ }
231
+
232
+ async prepareQuestions () {
233
+ // directory
234
+ this.questions.push({
235
+ type: 'input',
236
+ name: 'targetDirectory',
237
+ message: 'Where would you like to create your project?'
238
+ })
239
+
240
+ // typescript
241
+ this.questions.push({
242
+ type: 'list',
243
+ name: 'typescript',
244
+ message: 'Do you want to use TypeScript?',
245
+ default: false,
246
+ choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
247
+ })
248
+
249
+ // init git repository
250
+ this.questions.push({
251
+ type: 'list',
252
+ name: 'initGitRepository',
253
+ message: 'Do you want to init the git repository?',
254
+ default: false,
255
+ choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
256
+ })
257
+
258
+ // port
259
+ this.questions.push({
260
+ type: 'input',
261
+ name: 'port',
262
+ message: 'What port do you want to use?'
263
+ })
264
+
265
+ // github actions
266
+ this.questions.push({
267
+ type: 'list',
268
+ name: 'staticWorkspaceGitHubAction',
269
+ message: 'Do you want to create the github action to deploy this application to Platformatic Cloud?',
270
+ default: true,
271
+ choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
272
+ },
273
+ {
274
+ type: 'list',
275
+ name: 'dynamicWorkspaceGitHubAction',
276
+ message: 'Do you want to enable PR Previews in your application?',
277
+ default: true,
278
+ choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
279
+ })
280
+ }
281
+
282
+ async addQuestion (question, where) {
283
+ if (where) {
284
+ if (where.before) {
285
+ const position = this.questions.reduce((acc, element, idx) => {
286
+ if (acc === null) {
287
+ if (element.name === where.before) {
288
+ acc = idx
289
+ }
290
+ }
291
+ return acc
292
+ }, null)
293
+
294
+ if (position) {
295
+ this.questions.splice(position, 0, question)
296
+ }
297
+ } else if (where.after) {
298
+ const position = this.questions.reduce((acc, element, idx) => {
299
+ if (acc === null) {
300
+ if (element.name === where.after) {
301
+ acc = idx + 1
302
+ }
303
+ }
304
+ return acc
305
+ }, null)
306
+
307
+ if (position) {
308
+ this.questions.splice(position, 0, question)
309
+ }
310
+ }
311
+ } else {
312
+ this.questions.push(question)
313
+ }
314
+ }
315
+
316
+ removeQuestion (variableName) {
317
+ const position = this.questions.reduce((acc, element, idx) => {
318
+ if (acc === null) {
319
+ if (element.name === variableName) {
320
+ acc = idx
321
+ }
322
+ }
323
+ return acc
324
+ }, null)
325
+ if (position) {
326
+ this.questions.splice(position, 1)
327
+ }
328
+ }
329
+
330
+ getConfigFileName () {
331
+ if (!this.type) {
332
+ return 'platformatic.json'
333
+ } else {
334
+ return `platformatic.${this.type}.json`
335
+ }
336
+ }
337
+
338
+ async generateConfigFile () {
339
+ const configFileName = this.getConfigFileName()
340
+ const contents = await this._getConfigFileContents()
341
+ this.addFile({
342
+ path: '',
343
+ file: configFileName,
344
+ contents: JSON.stringify(contents, null, 2)
345
+ })
346
+ }
347
+
348
+ /**
349
+ * Reads the content of package.json and returns it as an object
350
+ * @returns Object
351
+ */
352
+ async readPackageJsonFile () {
353
+ if (this.pkgData) {
354
+ return this.pkgData
355
+ }
356
+ const currentPackageJsonPath = join(__dirname, '..', 'package.json')
357
+ this.pkgData = JSON.parse(await readFile(currentPackageJsonPath, 'utf8'))
358
+ return this.pkgData
359
+ }
360
+
361
+ async getFastifyVersion () {
362
+ const pkgData = await this.readPackageJsonFile()
363
+ this.fastifyVersion = stripVersion(pkgData.dependencies.fastify)
364
+ }
365
+
366
+ async getPlatformaticVersion () {
367
+ const pkgData = await this.readPackageJsonFile()
368
+ this.platformaticVersion = stripVersion(pkgData.version)
369
+ }
370
+
371
+ async generatePackageJson () {
372
+ const template = {
373
+ scripts: {
374
+ start: 'platformatic start',
375
+ test: 'node --test test/**'
376
+ },
377
+ devDependencies: {
378
+ fastify: `^${this.fastifyVersion}`,
379
+ ...this.config.devDependencies
380
+ },
381
+ dependencies: {
382
+ platformatic: `^${this.platformaticVersion}`,
383
+ ...this.config.dependencies
384
+ },
385
+ engines: {
386
+ node: '^18.8.0 || >=20.6.0'
387
+ }
388
+ }
389
+
390
+ if (this.config.typescript) {
391
+ const typescriptVersion = JSON.parse(await readFile(join(__dirname, '..', 'package.json'), 'utf-8')).devDependencies.typescript
392
+ template.scripts.clean = 'rm -fr ./dist'
393
+ template.scripts.build = 'platformatic compile'
394
+ template.devDependencies.typescript = typescriptVersion
395
+ }
396
+ return template
397
+ }
398
+
399
+ async generateEnv () {
400
+ if (!this.config.isRuntimeContext) {
401
+ // generate an empty .env file
402
+ this.addFile({
403
+ path: '',
404
+ file: '.env',
405
+ contents: ''
406
+ })
407
+ await this._generateEnv()
408
+ this.appendConfigEnv()
409
+ }
410
+ }
411
+
412
+ async run () {
413
+ const metadata = await this.prepare()
414
+ await this.writeFiles()
415
+ return metadata
416
+ }
417
+
418
+ // implement in the subclass
419
+ async _beforePrepare () {}
420
+ async _afterPrepare () {}
421
+ async _getConfigFileContents () { return {} }
422
+ async _generateEnv () {}
423
+ }
424
+
425
+ module.exports = BaseGenerator
426
+ module.exports.BaseGenerator = BaseGenerator
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ const gitignore = `\
4
+ dist
5
+ .DS_Store
6
+
7
+ # dotenv environment variable files
8
+ .env
9
+
10
+ # database files
11
+ *.sqlite
12
+ *.sqlite3
13
+
14
+ # Logs
15
+ logs
16
+ *.log
17
+ npm-debug.log*
18
+ yarn-debug.log*
19
+ yarn-error.log*
20
+ lerna-debug.log*
21
+ .pnpm-debug.log*
22
+
23
+ # Dependency directories
24
+ node_modules/
25
+
26
+ # ctags
27
+ tags
28
+
29
+ # clinicjs
30
+ .clinic/
31
+ `
32
+ function generateGitignore () {
33
+ return {
34
+ path: '',
35
+ file: '.gitignore',
36
+ contents: gitignore
37
+ }
38
+ }
39
+
40
+ module.exports = {
41
+ generateGitignore
42
+ }
@@ -0,0 +1,13 @@
1
+ import { FileGenerator } from './file-generator'
2
+
3
+ type HelperCustomization = {
4
+ pre: string
5
+ post: string
6
+ config: string
7
+ requires: string
8
+ }
9
+
10
+ export function generatePluginWithTypesSupport(typescript: boolean): FileGenerator.FileObject
11
+ export function generateRouteWithTypesSupport(typescript: boolean): FileGenerator.FileObject
12
+ export function generateTests(typescript: boolean, type: string, customization?: HelperCustomization): Array<FileGenerator.FileObject>
13
+ export function generatePlugins(typescript: boolean): Array<FileGenerator.FileObject>