@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.
- package/LICENSE +201 -0
- package/README.md +67 -0
- package/index.d.ts +8 -0
- package/index.js +10 -0
- package/lib/base-generator.d.ts +105 -0
- package/lib/base-generator.js +426 -0
- package/lib/create-gitignore.js +42 -0
- package/lib/create-plugin.d.ts +13 -0
- package/lib/create-plugin.js +252 -0
- package/lib/errors.js +11 -0
- package/lib/file-generator.d.ts +29 -0
- package/lib/file-generator.js +90 -0
- package/lib/utils.d.ts +12 -0
- package/lib/utils.js +69 -0
- package/package.json +27 -0
- package/test/base-generator.test.js +459 -0
- package/test/file-generator.test.js +106 -0
- package/test/helpers.js +43 -0
- package/test/runner.js +20 -0
- package/test/utils.test.js +89 -0
|
@@ -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>
|