@platformatic/generators 1.18.0 → 1.20.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/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const { BaseGenerator } = require('./lib/base-generator')
4
+ const { StackableGenerator } = require('./lib/stackable-generator')
4
5
  const { generateTests } = require('./lib/create-plugin')
5
6
  const { addPrefixToEnv } = require('./lib/utils')
6
7
  module.exports = {
7
8
  addPrefixToEnv,
8
9
  BaseGenerator,
10
+ StackableGenerator,
9
11
  generateTests
10
12
  }
@@ -54,7 +54,7 @@ function testHelperJS (mod, customization = { pre: '', post: '', config: '' }) {
54
54
 
55
55
  const { join } = require('node:path')
56
56
  const { readFile } = require('node:fs/promises')
57
- const { buildServer } = require('@platformatic/${mod}')
57
+ const { buildServer } = require('${mod}')
58
58
  ${customization.requires || ''}
59
59
 
60
60
  async function getServer (t) {
@@ -0,0 +1,167 @@
1
+ 'use strict'
2
+
3
+ const { kebabCase } = require('change-case-all')
4
+
5
+ function getJsStackableStartCli () {
6
+ return `\
7
+ #!/usr/bin/env node
8
+ 'use strict'
9
+
10
+ const stackable = require('../index')
11
+ const { start } = require('@platformatic/service')
12
+ const { printAndExitLoadConfigError } = require('@platformatic/config')
13
+
14
+ start(stackable, process.argv.splice(2)).catch(printAndExitLoadConfigError)
15
+ `
16
+ }
17
+
18
+ function getTsStackableStartCli () {
19
+ return `\
20
+ #!/usr/bin/env node
21
+ import stackable from '../index'
22
+ import { start } from '@platformatic/service'
23
+ import { printAndExitLoadConfigError } from '@platformatic/config'
24
+
25
+ start(stackable, process.argv.splice(2)).catch(printAndExitLoadConfigError)
26
+ `
27
+ }
28
+
29
+ function getJsStackableCreateCli (stackableName) {
30
+ return `\
31
+ #!/usr/bin/env node
32
+ 'use strict'
33
+
34
+ const { join } = require('node:path')
35
+ const pino = require('pino')
36
+ const pretty = require('pino-pretty')
37
+ const minimist = require('minimist')
38
+ const { Generator } = require('../lib/generator')
39
+
40
+ async function execute () {
41
+ const logger = pino(pretty({
42
+ translateTime: 'SYS:HH:MM:ss',
43
+ ignore: 'hostname,pid'
44
+ }))
45
+
46
+ const args = minimist(process.argv.slice(2), {
47
+ string: ['dir', 'port', 'hostname'],
48
+ boolean: ['typescript', 'install', 'plugin', 'git'],
49
+ default: {
50
+ dir: join(process.cwd(), '${kebabCase(stackableName + '-app')}'),
51
+ port: 3042,
52
+ hostname: '0.0.0.0',
53
+ plugin: true,
54
+ typescript: false,
55
+ git: false,
56
+ install: true
57
+ }
58
+ })
59
+
60
+ const generator = new Generator({ logger })
61
+
62
+ generator.setConfig({
63
+ port: args.port,
64
+ hostname: args.hostname,
65
+ plugin: args.plugin,
66
+ tests: args.plugin,
67
+ typescript: args.typescript,
68
+ initGitRepository: args.git,
69
+ targetDirectory: args.dir
70
+ })
71
+
72
+ await generator.run()
73
+
74
+ logger.info('Application created successfully! Run \`npm run start\` to start an application.')
75
+ }
76
+
77
+ execute()
78
+ `
79
+ }
80
+
81
+ function getTsStackableCreateCli (stackableName) {
82
+ return `\
83
+ #!/usr/bin/env node
84
+ import { join } from 'node:path'
85
+ import pino from 'pino'
86
+ import pretty from 'pino-pretty'
87
+ import minimist from 'minimist'
88
+ import { Generator } from '../lib/generator'
89
+
90
+ async function execute () {
91
+ const logger = pino(pretty({
92
+ translateTime: 'SYS:HH:MM:ss',
93
+ ignore: 'hostname,pid'
94
+ }))
95
+
96
+ const args = minimist(process.argv.slice(2), {
97
+ string: ['dir', 'port', 'hostname'],
98
+ boolean: ['typescript', 'install', 'plugin', 'git'],
99
+ default: {
100
+ dir: join(process.cwd(), '${kebabCase(stackableName + '-app')}'),
101
+ port: 3042,
102
+ hostname: '0.0.0.0',
103
+ plugin: true,
104
+ typescript: false,
105
+ git: false,
106
+ install: true
107
+ }
108
+ })
109
+
110
+ const generator = new Generator({ logger })
111
+
112
+ generator.setConfig({
113
+ port: args.port,
114
+ hostname: args.hostname,
115
+ plugin: args.plugin,
116
+ tests: args.plugin,
117
+ typescript: args.typescript,
118
+ initGitRepository: args.git,
119
+ targetDirectory: args.dir
120
+ })
121
+
122
+ await generator.run()
123
+
124
+ logger.info('Application created successfully! Run \`npm run start\` to start an application.')
125
+ }
126
+
127
+ execute()
128
+ `
129
+ }
130
+
131
+ function generateStackableCli (typescript, stackableName) {
132
+ if (typescript) {
133
+ return [
134
+ {
135
+ path: 'cli',
136
+ file: 'start.ts',
137
+ contents: getTsStackableStartCli(),
138
+ options: { mode: 0o755 }
139
+ },
140
+ {
141
+ path: 'cli',
142
+ file: 'create.ts',
143
+ contents: getTsStackableCreateCli(stackableName),
144
+ options: { mode: 0o755 }
145
+ }
146
+ ]
147
+ }
148
+
149
+ return [
150
+ {
151
+ path: 'cli',
152
+ file: 'start.js',
153
+ contents: getJsStackableStartCli(),
154
+ options: { mode: 0o755 }
155
+ },
156
+ {
157
+ path: 'cli',
158
+ file: 'create.js',
159
+ contents: getJsStackableCreateCli(stackableName),
160
+ options: { mode: 0o755 }
161
+ }
162
+ ]
163
+ }
164
+
165
+ module.exports = {
166
+ generateStackableCli
167
+ }
@@ -0,0 +1,344 @@
1
+ 'use strict'
2
+
3
+ const { pascalCase, camelCase, capitalCase, kebabCase } = require('change-case-all')
4
+
5
+ function getJsStackableIndexFile (stackableName) {
6
+ return `\
7
+ 'use strict'
8
+
9
+ const { platformaticService } = require('@platformatic/service')
10
+ const { schema } = require('./lib/schema')
11
+ const { Generator } = require('./lib/generator')
12
+
13
+ async function stackable (fastify, opts) {
14
+ await fastify.register(platformaticService, opts)
15
+ await fastify.register(require('./plugins/example'), opts)
16
+ }
17
+
18
+ stackable.configType = '${kebabCase(stackableName + '-app')}'
19
+ stackable.schema = schema
20
+ stackable.Generator = Generator
21
+ stackable.configManagerConfig = {
22
+ schema,
23
+ envWhitelist: ['PORT', 'HOSTNAME'],
24
+ allowToWatch: ['.env'],
25
+ schemaOptions: {
26
+ useDefaults: true,
27
+ coerceTypes: true,
28
+ allErrors: true,
29
+ strict: false
30
+ },
31
+ transformConfig: async () => {}
32
+ }
33
+
34
+ // break Fastify encapsulation
35
+ stackable[Symbol.for('skip-override')] = true
36
+
37
+ module.exports = stackable
38
+ `
39
+ }
40
+
41
+ function getTsStackableIndexFile (stackableName) {
42
+ const stackableConfigType = pascalCase(stackableName + 'Config')
43
+
44
+ return `\
45
+ import { platformaticService, Stackable } from '@platformatic/service'
46
+ import { schema } from './lib/schema'
47
+ import { Generator } from './lib/generator'
48
+ import { ${stackableConfigType} } from './config'
49
+
50
+ const stackable: Stackable<${stackableConfigType}> = async function (fastify, opts) {
51
+ await fastify.register(platformaticService, opts)
52
+ await fastify.register(require('./plugins/example'), opts)
53
+ }
54
+
55
+ stackable.configType = '${kebabCase(stackableName + '-app')}'
56
+ stackable.schema = schema
57
+ stackable.Generator = Generator
58
+ stackable.configManagerConfig = {
59
+ schema,
60
+ envWhitelist: ['PORT', 'HOSTNAME'],
61
+ allowToWatch: ['.env'],
62
+ schemaOptions: {
63
+ useDefaults: true,
64
+ coerceTypes: true,
65
+ allErrors: true,
66
+ strict: false
67
+ },
68
+ transformConfig: async () => {}
69
+ }
70
+
71
+ // break Fastify encapsulation
72
+ // @ts-ignore
73
+ stackable[Symbol.for('skip-override')] = true
74
+
75
+ export default stackable
76
+ `
77
+ }
78
+
79
+ function getStackableIndexTypesFile (stackableName) {
80
+ const stackableConfigType = pascalCase(stackableName + 'Config')
81
+
82
+ return `\
83
+ import { FastifyInstance } from 'fastify'
84
+ import { PlatformaticApp } from '@platformatic/service'
85
+ import { ${stackableConfigType} } from './config'
86
+
87
+ declare module 'fastify' {
88
+ interface FastifyInstance {
89
+ platformatic: PlatformaticApp<${stackableConfigType}>
90
+ }
91
+ }
92
+ `
93
+ }
94
+
95
+ function getJsStackableGeneratorFile (stackableName) {
96
+ const stackableGeneratorType = pascalCase(stackableName + 'Generator')
97
+
98
+ return `\
99
+ 'use strict'
100
+
101
+ const { Generator: ServiceGenerator } = require('@platformatic/service')
102
+ const { schema } = require('./schema')
103
+
104
+ class ${stackableGeneratorType} extends ServiceGenerator {
105
+ getDefaultConfig () {
106
+ const defaultBaseConfig = super.getDefaultConfig()
107
+ const defaultConfig = {
108
+ greeting: 'Hello world!'
109
+ }
110
+ return Object.assign({}, defaultBaseConfig, defaultConfig)
111
+ }
112
+
113
+ async _getConfigFileContents () {
114
+ const baseConfig = await super._getConfigFileContents()
115
+ const config = {
116
+ $schema: './stackable.schema.json',
117
+ greeting: {
118
+ text: '{PLT_GREETING_TEXT}'
119
+ }
120
+ }
121
+ return Object.assign({}, baseConfig, config)
122
+ }
123
+
124
+ async _beforePrepare () {
125
+ super._beforePrepare()
126
+
127
+ this.config.env = {
128
+ PLT_GREETING_TEXT: this.config.greeting ?? 'Hello world!',
129
+ ...this.config.env
130
+ }
131
+ }
132
+
133
+ async _afterPrepare () {
134
+ this.addFile({
135
+ path: '',
136
+ file: 'stackable.schema.json',
137
+ contents: JSON.stringify(schema, null, 2)
138
+ })
139
+ }
140
+ }
141
+
142
+ module.exports = ${stackableGeneratorType}
143
+ module.exports.Generator = ${stackableGeneratorType}
144
+ `
145
+ }
146
+
147
+ function getTsStackableGeneratorFile (stackableName) {
148
+ const stackableGeneratorType = pascalCase(stackableName + 'Generator')
149
+
150
+ return `\
151
+ import { Generator as ServiceGenerator } from '@platformatic/service'
152
+ import { BaseGenerator } from '@platformatic/generators'
153
+ import { schema } from './schema'
154
+
155
+ class ${stackableGeneratorType} extends ServiceGenerator {
156
+ getDefaultConfig (): BaseGenerator.JSONValue {
157
+ const defaultBaseConfig = super.getDefaultConfig()
158
+ const defaultConfig = {
159
+ greeting: 'Hello world!'
160
+ }
161
+ return Object.assign({}, defaultBaseConfig, defaultConfig)
162
+ }
163
+
164
+ async _getConfigFileContents (): Promise<BaseGenerator.JSONValue> {
165
+ const baseConfig = await super._getConfigFileContents()
166
+ const config = {
167
+ $schema: './stackable.schema.json',
168
+ greeting: {
169
+ text: '{PLT_GREETING_TEXT}'
170
+ }
171
+ }
172
+ return Object.assign({}, baseConfig, config)
173
+ }
174
+
175
+ async _beforePrepare () {
176
+ super._beforePrepare()
177
+
178
+ this.config.env = {
179
+ PLT_GREETING_TEXT: this.config.greeting ?? 'Hello world!',
180
+ ...this.config.env
181
+ }
182
+ }
183
+
184
+ async _afterPrepare () {
185
+ this.addFile({
186
+ path: '',
187
+ file: 'stackable.schema.json',
188
+ contents: JSON.stringify(schema, null, 2)
189
+ })
190
+ }
191
+ }
192
+
193
+ export default ${stackableGeneratorType}
194
+ export { ${stackableGeneratorType} as Generator }
195
+ `
196
+ }
197
+
198
+ function getJsStackableSchemaFile (stackableName) {
199
+ const schemaId = kebabCase(stackableName)
200
+ const schemaTitle = capitalCase(stackableName + 'Config')
201
+ const schemaVarName = camelCase(stackableName + 'Schema')
202
+
203
+ return `\
204
+ 'use strict'
205
+
206
+ const { schema } = require('@platformatic/service')
207
+
208
+ const ${schemaVarName} = {
209
+ ...schema.schema,
210
+ $id: '${schemaId}',
211
+ title: '${schemaTitle}',
212
+ properties: {
213
+ ...schema.schema.properties,
214
+ greeting: {
215
+ type: 'object',
216
+ properties: {
217
+ text: {
218
+ type: 'string'
219
+ }
220
+ },
221
+ required: ['text'],
222
+ additionalProperties: false
223
+ }
224
+ }
225
+ }
226
+
227
+ module.exports.schema = ${schemaVarName}
228
+
229
+ if (require.main === module) {
230
+ console.log(JSON.stringify(${schemaVarName}, null, 2))
231
+ }
232
+ `
233
+ }
234
+
235
+ function getTsStackableSchemaFile (stackableName) {
236
+ const schemaId = kebabCase(stackableName)
237
+ const schemaTitle = capitalCase(stackableName + 'Config')
238
+ const schemaVarName = camelCase(stackableName + 'Schema')
239
+
240
+ return `\
241
+ import { schema } from '@platformatic/service'
242
+
243
+ const ${schemaVarName} = {
244
+ ...schema.schema,
245
+ $id: '${schemaId}',
246
+ title: '${schemaTitle}',
247
+ properties: {
248
+ ...schema.schema.properties,
249
+ greeting: {
250
+ type: 'object',
251
+ properties: {
252
+ text: {
253
+ type: 'string'
254
+ }
255
+ },
256
+ required: ['text'],
257
+ additionalProperties: false
258
+ }
259
+ }
260
+ }
261
+
262
+ export { ${schemaVarName} as schema }
263
+
264
+ if (require.main === module) {
265
+ console.log(JSON.stringify(${schemaVarName}, null, 2))
266
+ }
267
+ `
268
+ }
269
+
270
+ function getStackableConfigTypesFile (stackableName) {
271
+ const stackableConfigType = pascalCase(stackableName + 'Config')
272
+
273
+ return `\
274
+ // Use npm run build:config to generate this file from the Stackable schema
275
+ export interface ${stackableConfigType} {
276
+ greeting?: {
277
+ text: string;
278
+ };
279
+ }
280
+ `
281
+ }
282
+
283
+ function generateStackableFiles (typescript, stackableName) {
284
+ if (typescript) {
285
+ return [
286
+ {
287
+ path: '',
288
+ file: 'index.ts',
289
+ contents: getTsStackableIndexFile(stackableName)
290
+ },
291
+ {
292
+ path: '',
293
+ file: 'index.d.ts',
294
+ contents: getStackableIndexTypesFile(stackableName)
295
+ },
296
+ {
297
+ path: '',
298
+ file: 'config.d.ts',
299
+ contents: getStackableConfigTypesFile(stackableName)
300
+ },
301
+ {
302
+ path: 'lib',
303
+ file: 'generator.ts',
304
+ contents: getTsStackableGeneratorFile(stackableName)
305
+ },
306
+ {
307
+ path: 'lib',
308
+ file: 'schema.ts',
309
+ contents: getTsStackableSchemaFile(stackableName)
310
+ }
311
+ ]
312
+ }
313
+ return [
314
+ {
315
+ path: '',
316
+ file: 'index.js',
317
+ contents: getJsStackableIndexFile(stackableName)
318
+ },
319
+ {
320
+ path: '',
321
+ file: 'index.d.ts',
322
+ contents: getStackableIndexTypesFile(stackableName)
323
+ },
324
+ {
325
+ path: '',
326
+ file: 'config.d.ts',
327
+ contents: getStackableConfigTypesFile(stackableName)
328
+ },
329
+ {
330
+ path: 'lib',
331
+ file: 'generator.js',
332
+ contents: getJsStackableGeneratorFile(stackableName)
333
+ },
334
+ {
335
+ path: 'lib',
336
+ file: 'schema.js',
337
+ contents: getJsStackableSchemaFile(stackableName)
338
+ }
339
+ ]
340
+ }
341
+
342
+ module.exports = {
343
+ generateStackableFiles
344
+ }
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ function getJsStackablePluginFile () {
4
+ return `\
5
+ /// <reference path="../index.d.ts" />
6
+ 'use strict'
7
+ /** @param {import('fastify').FastifyInstance} fastify */
8
+ module.exports = async function (fastify, opts) {
9
+ const config = fastify.platformatic.config
10
+ const greeting = config.greeting
11
+ fastify.log.info({ greeting }, 'Loading stackable greeting plugin.')
12
+ fastify.decorate('greeting', greeting)
13
+ }
14
+ `
15
+ }
16
+
17
+ function getTsStackablePluginFile () {
18
+ return `\
19
+ /// <reference path="../index.d.ts" />
20
+ import { FastifyInstance, FastifyPluginOptions } from 'fastify'
21
+
22
+ export default async function (fastify: FastifyInstance, opts: FastifyPluginOptions) {
23
+ const config = fastify.platformatic.config
24
+ const greeting = config.greeting
25
+ fastify.log.info({ greeting }, 'Loading stackable greeting plugin.')
26
+ fastify.decorate('greeting', greeting)
27
+ }
28
+ `
29
+ }
30
+
31
+ function generateStackablePlugins (typescript) {
32
+ if (typescript) {
33
+ return [{
34
+ path: 'plugins',
35
+ file: 'example.ts',
36
+ contents: getTsStackablePluginFile()
37
+ }]
38
+ }
39
+ return [{
40
+ path: 'plugins',
41
+ file: 'example.js',
42
+ contents: getJsStackablePluginFile()
43
+ }]
44
+ }
45
+
46
+ module.exports = {
47
+ generateStackablePlugins
48
+ }
@@ -24,7 +24,7 @@ class FileGenerator {
24
24
  this.targetDirectory = dir
25
25
  }
26
26
 
27
- addFile ({ path, file, contents }) {
27
+ addFile ({ path, file, contents, options = {} }) {
28
28
  const fileObject = this.getFileObject(file, path)
29
29
  if (path.startsWith('/')) {
30
30
  path = path.substring(1)
@@ -32,7 +32,7 @@ class FileGenerator {
32
32
  if (fileObject) {
33
33
  fileObject.contents = contents
34
34
  } else {
35
- this.files.push({ path, file, contents })
35
+ this.files.push({ path, file, contents, options })
36
36
  }
37
37
  }
38
38
 
@@ -62,7 +62,7 @@ class FileGenerator {
62
62
  await safeMkdir(baseDir)
63
63
  }
64
64
  const fullFilePath = join(baseDir, fileToWrite.file)
65
- await writeFile(fullFilePath, fileToWrite.contents)
65
+ await writeFile(fullFilePath, fileToWrite.contents, fileToWrite.options)
66
66
  this.logger.info(`${fullFilePath} written!`)
67
67
  }
68
68
  }
@@ -0,0 +1,309 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('node:path')
4
+ const { readFile } = require('node:fs/promises')
5
+ const { kebabCase } = require('change-case-all')
6
+ const { stripVersion, getLatestNpmVersion } = require('./utils')
7
+ const { FileGenerator } = require('./file-generator')
8
+ const { PrepareError } = require('./errors')
9
+ const { generateGitignore } = require('./create-gitignore')
10
+ const { generateStackableCli } = require('./create-stackable-cli')
11
+ const { generateStackableFiles } = require('./create-stackable-files')
12
+ const { generateStackablePlugins } = require('./create-stackable-plugin')
13
+
14
+ /* c8 ignore start */
15
+ const fakeLogger = {
16
+ info: () => {},
17
+ warn: () => {},
18
+ debug: () => {},
19
+ trace: () => {},
20
+ error: () => {}
21
+ }
22
+ /* c8 ignore start */
23
+
24
+ class StackableGenerator extends FileGenerator {
25
+ constructor (opts = {}) {
26
+ super(opts)
27
+ this.files = []
28
+ this.logger = opts.logger || fakeLogger
29
+ this.questions = []
30
+ this.pkgData = null
31
+ this.inquirer = opts.inquirer || null
32
+ this.targetDirectory = opts.targetDirectory || null
33
+ this.config = this.getDefaultConfig()
34
+ this.packages = []
35
+ }
36
+
37
+ getDefaultConfig () {
38
+ return {
39
+ stackableName: 'my-stackable',
40
+ typescript: false,
41
+ initGitRepository: false,
42
+ dependencies: {},
43
+ devDependencies: {}
44
+ }
45
+ }
46
+
47
+ setConfigFields (fields) {
48
+ for (const field of fields) {
49
+ this.config[field.configValue] = field.value
50
+ }
51
+ }
52
+
53
+ setConfig (config) {
54
+ if (!config) {
55
+ this.config = this.getDefaultConfig()
56
+ }
57
+ const oldConfig = this.config
58
+ this.config = {
59
+ ...this.getDefaultConfig(),
60
+ ...oldConfig,
61
+ ...config
62
+ }
63
+
64
+ if (this.config.targetDirectory) {
65
+ this.targetDirectory = this.config.targetDirectory
66
+ }
67
+ }
68
+
69
+ /* c8 ignore start */
70
+ async ask () {
71
+ if (this.inquirer) {
72
+ await this.prepareQuestions()
73
+ const newConfig = await this.inquirer.prompt(this.questions)
74
+ this.setConfig({
75
+ ...this.config,
76
+ ...newConfig
77
+ })
78
+ }
79
+ }
80
+
81
+ async prepare () {
82
+ try {
83
+ this.reset()
84
+ await this.getFastifyVersion()
85
+ await this.getPlatformaticVersion()
86
+
87
+ await this._beforePrepare()
88
+
89
+ // generate package.json
90
+ const template = await this.generatePackageJson()
91
+ this.addFile({
92
+ path: '',
93
+ file: 'package.json',
94
+ contents: JSON.stringify(template, null, 2)
95
+ })
96
+
97
+ if (this.config.typescript) {
98
+ // create tsconfig.json
99
+ this.addFile({
100
+ path: '',
101
+ file: 'tsconfig.json',
102
+ contents: JSON.stringify(this.getTsConfig(), null, 2)
103
+ })
104
+ }
105
+
106
+ const typescript = this.config.typescript
107
+ const stackableName = this.config.stackableName
108
+
109
+ this.files.push(...generateStackableFiles(typescript, stackableName))
110
+ this.files.push(...generateStackableCli(typescript, stackableName))
111
+ this.files.push(...generateStackablePlugins(typescript))
112
+ this.files.push(generateGitignore())
113
+
114
+ await this._afterPrepare()
115
+
116
+ return {
117
+ targetDirectory: this.targetDirectory
118
+ }
119
+ } catch (err) {
120
+ if (err.code?.startsWith('PLT_GEN')) {
121
+ // throw the same error
122
+ throw err
123
+ }
124
+ const _err = new PrepareError(err.message)
125
+ _err.cause = err
126
+ throw _err
127
+ }
128
+ }
129
+
130
+ getTsConfig () {
131
+ return {
132
+ compilerOptions: {
133
+ module: 'commonjs',
134
+ esModuleInterop: true,
135
+ target: 'es2020',
136
+ sourceMap: true,
137
+ pretty: true,
138
+ noEmitOnError: true,
139
+ incremental: true,
140
+ strict: true,
141
+ outDir: 'dist'
142
+ },
143
+ watchOptions: {
144
+ watchFile: 'fixedPollingInterval',
145
+ watchDirectory: 'fixedPollingInterval',
146
+ fallbackPolling: 'dynamicPriority',
147
+ synchronousWatchDirectory: true,
148
+ excludeDirectories: ['**/node_modules', 'dist']
149
+ }
150
+ }
151
+ }
152
+
153
+ async prepareQuestions () {
154
+ if (!this.config.targetDirectory) {
155
+ // directory
156
+ this.questions.push({
157
+ type: 'input',
158
+ name: 'targetDirectory',
159
+ message: 'Where would you like to create your project?',
160
+ default: 'platformatic'
161
+ })
162
+ }
163
+
164
+ this.questions.push({
165
+ type: 'input',
166
+ name: 'stackableName',
167
+ message: 'What is the name of the stackable?',
168
+ default: 'my-stackable'
169
+ })
170
+
171
+ // typescript
172
+ this.questions.push({
173
+ type: 'list',
174
+ name: 'typescript',
175
+ message: 'Do you want to use TypeScript?',
176
+ default: false,
177
+ choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
178
+ })
179
+ }
180
+
181
+ /**
182
+ * Reads the content of package.json and returns it as an object
183
+ * @returns Object
184
+ */
185
+ async readPackageJsonFile () {
186
+ if (this.pkgData) {
187
+ return this.pkgData
188
+ }
189
+ const currentPackageJsonPath = join(__dirname, '..', 'package.json')
190
+ this.pkgData = JSON.parse(await readFile(currentPackageJsonPath, 'utf8'))
191
+ return this.pkgData
192
+ }
193
+
194
+ async getFastifyVersion () {
195
+ const pkgData = await this.readPackageJsonFile()
196
+ this.fastifyVersion = stripVersion(pkgData.dependencies.fastify)
197
+ }
198
+
199
+ async getPlatformaticVersion () {
200
+ const pkgData = await this.readPackageJsonFile()
201
+ this.platformaticVersion = stripVersion(pkgData.version)
202
+ }
203
+
204
+ async generatePackageJson () {
205
+ const dependencies = {
206
+ '@platformatic/config': `^${this.platformaticVersion}`,
207
+ '@platformatic/service': `^${this.platformaticVersion}`,
208
+ 'json-schema-to-typescript': '^13.0.0',
209
+ pino: '^8.0.0',
210
+ 'pino-pretty': '^10.0.0',
211
+ minimist: '^1.2.0',
212
+ platformatic: `^${this.platformaticVersion}`
213
+ }
214
+
215
+ const devDependencies = {
216
+ fastify: `^${this.fastifyVersion}`
217
+ }
218
+
219
+ const npmPackageName = kebabCase(this.config.stackableName)
220
+ const createStackableCommand = kebabCase('create-' + this.config.stackableName)
221
+ const startStackableCommand = kebabCase('start-' + this.config.stackableName)
222
+
223
+ if (this.config.typescript) {
224
+ const packageJsonFile = await readFile(join(__dirname, '..', 'package.json'), 'utf-8')
225
+ const typescriptVersion = JSON.parse(packageJsonFile).devDependencies.typescript
226
+
227
+ return {
228
+ name: npmPackageName,
229
+ main: 'dist/index.js',
230
+ bin: {
231
+ [createStackableCommand]: './dist/cli/create.js',
232
+ [startStackableCommand]: './dist/cli/start.js'
233
+ },
234
+ scripts: {
235
+ build: 'tsc --build',
236
+ 'build:config': 'node ./dist/lib/schema.js | json2ts > config.d.ts',
237
+ clean: 'rm -fr ./dist'
238
+ },
239
+ devDependencies: {
240
+ ...devDependencies,
241
+ '@types/minimist': '^1.2.5',
242
+ typescript: typescriptVersion,
243
+ ...this.config.devDependencies
244
+ },
245
+ dependencies: {
246
+ ...dependencies,
247
+ '@platformatic/generators': `^${this.platformaticVersion}`,
248
+ ...this.config.dependencies
249
+ },
250
+ engines: {
251
+ node: '^18.8.0 || >=20.6.0'
252
+ }
253
+ }
254
+ }
255
+
256
+ return {
257
+ name: npmPackageName,
258
+ main: 'index.js',
259
+ bin: {
260
+ [createStackableCommand]: './cli/create.js',
261
+ [startStackableCommand]: './cli/start.js'
262
+ },
263
+ scripts: {
264
+ 'build:config': 'node lib/schema.js | json2ts > config.d.ts'
265
+ },
266
+ devDependencies: {
267
+ ...devDependencies,
268
+ ...this.config.devDependencies
269
+ },
270
+ dependencies: {
271
+ ...dependencies,
272
+ ...this.config.dependencies
273
+ },
274
+ engines: {
275
+ node: '^18.8.0 || >=20.6.0'
276
+ }
277
+ }
278
+ }
279
+
280
+ async run () {
281
+ const metadata = await this.prepare()
282
+ await this.writeFiles()
283
+ return metadata
284
+ }
285
+
286
+ async addPackage (pkg) {
287
+ this.config.dependencies[pkg.name] = 'latest'
288
+ try {
289
+ const version = await getLatestNpmVersion(pkg.name)
290
+ if (version) {
291
+ this.config.dependencies[pkg.name] = version
292
+ }
293
+ } catch (err) {
294
+ this.logger.warn(`Could not get latest version for ${pkg.name}, setting it to latest`)
295
+ }
296
+ this.packages.push(pkg)
297
+ }
298
+
299
+ // implement in the subclass
300
+ /* c8 ignore next 1 */
301
+ async postInstallActions () {}
302
+ async _beforePrepare () {}
303
+ async _afterPrepare () {}
304
+ async _getConfigFileContents () { return {} }
305
+ async _generateEnv () {}
306
+ }
307
+
308
+ module.exports = StackableGenerator
309
+ module.exports.StackableGenerator = StackableGenerator
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/generators",
3
- "version": "1.18.0",
3
+ "version": "1.20.0",
4
4
  "description": "Main classes and utils for generators.",
5
5
  "main": "index.js",
6
6
  "keywords": [],
@@ -9,20 +9,22 @@
9
9
  "dependencies": {
10
10
  "@fastify/error": "^3.4.1",
11
11
  "boring-name-generator": "^1.0.3",
12
- "fastify": "^4.24.3",
13
- "pino": "^8.16.1",
14
- "undici": "^6.3.0"
12
+ "change-case-all": "^2.1.0",
13
+ "fastify": "^4.26.0",
14
+ "pino": "^8.17.2",
15
+ "undici": "^6.6.0"
15
16
  },
16
17
  "devDependencies": {
17
18
  "@types/inquirer": "^9.0.7",
18
- "c8": "^9.0.0",
19
+ "borp": "^0.9.0",
20
+ "c8": "^9.1.0",
19
21
  "glob": "^10.3.10",
20
22
  "snazzy": "^9.0.0",
21
23
  "standard": "^17.1.0",
22
- "typescript": "^5.2.2"
24
+ "typescript": "^5.3.3"
23
25
  },
24
26
  "scripts": {
25
27
  "lint": "standard | snazzy",
26
- "test": "pnpm run lint && c8 --100 -x fixtures -x test node ./test/runner.js"
28
+ "test": "pnpm run lint && borp -C -X fixtures -X test --concurrency=1"
27
29
  }
28
30
  }
@@ -74,7 +74,8 @@ test('extended class should generate config', async (t) => {
74
74
  assert.deepEqual(configFile, {
75
75
  path: '',
76
76
  file: 'platformatic.json',
77
- contents: JSON.stringify({ foo: 'bar' }, null, 2)
77
+ contents: JSON.stringify({ foo: 'bar' }, null, 2),
78
+ options: {}
78
79
  })
79
80
  })
80
81
 
@@ -0,0 +1,96 @@
1
+ 'use strict'
2
+
3
+ const { readFile, rm } = require('node:fs/promises')
4
+ const { test, afterEach } = require('node:test')
5
+ const assert = require('node:assert')
6
+ const { join } = require('node:path')
7
+
8
+ const { fakeLogger, getTempDir } = require('./helpers')
9
+ const { StackableGenerator } = require('../lib/stackable-generator')
10
+
11
+ afterEach(async () => {
12
+ try {
13
+ await rm(join(__dirname, 'tmp'), { recursive: true })
14
+ } catch (err) {
15
+ // do nothing
16
+ }
17
+ })
18
+
19
+ test('should create a stackable project without typescript', async (t) => {
20
+ const dir = await getTempDir()
21
+ const gen = new StackableGenerator({
22
+ logger: fakeLogger
23
+ })
24
+
25
+ gen.setConfig({
26
+ targetDirectory: dir
27
+ })
28
+
29
+ await gen.run()
30
+ // check files are created
31
+ const packageJson = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8'))
32
+ assert.ok(packageJson.scripts)
33
+ assert.ok(packageJson.dependencies)
34
+ assert.ok(packageJson.engines)
35
+
36
+ const indexFile = await readFile(join(dir, 'index.js'), 'utf8')
37
+ assert.ok(indexFile.length > 0)
38
+
39
+ const indexTypesFile = await readFile(join(dir, 'index.d.ts'), 'utf8')
40
+ assert.ok(indexTypesFile.length > 0)
41
+
42
+ const schemaFile = await readFile(join(dir, 'lib', 'schema.js'), 'utf8')
43
+ assert.ok(schemaFile.length > 0)
44
+
45
+ const generatorFile = await readFile(join(dir, 'lib', 'generator.js'), 'utf8')
46
+ assert.ok(generatorFile.length > 0)
47
+
48
+ const startCommandFile = await readFile(join(dir, 'cli', 'start.js'), 'utf8')
49
+ assert.ok(startCommandFile.length > 0)
50
+
51
+ const createCommandFile = await readFile(join(dir, 'cli', 'create.js'), 'utf8')
52
+ assert.ok(createCommandFile.length > 0)
53
+
54
+ const gitignore = await readFile(join(dir, '.gitignore'), 'utf8')
55
+ assert.ok(gitignore.length > 0)
56
+ })
57
+
58
+ test('should create a stackable project with typescript', async (t) => {
59
+ const dir = await getTempDir()
60
+ const gen = new StackableGenerator({
61
+ logger: fakeLogger
62
+ })
63
+
64
+ gen.setConfig({
65
+ targetDirectory: dir,
66
+ typescript: true
67
+ })
68
+
69
+ await gen.run()
70
+ // check files are created
71
+ const packageJson = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8'))
72
+ assert.ok(packageJson.scripts)
73
+ assert.ok(packageJson.dependencies)
74
+ assert.ok(packageJson.engines)
75
+
76
+ const indexFile = await readFile(join(dir, 'index.ts'), 'utf8')
77
+ assert.ok(indexFile.length > 0)
78
+
79
+ const indexTypesFile = await readFile(join(dir, 'index.d.ts'), 'utf8')
80
+ assert.ok(indexTypesFile.length > 0)
81
+
82
+ const schemaFile = await readFile(join(dir, 'lib', 'schema.ts'), 'utf8')
83
+ assert.ok(schemaFile.length > 0)
84
+
85
+ const generatorFile = await readFile(join(dir, 'lib', 'generator.ts'), 'utf8')
86
+ assert.ok(generatorFile.length > 0)
87
+
88
+ const startCommandFile = await readFile(join(dir, 'cli', 'start.ts'), 'utf8')
89
+ assert.ok(startCommandFile.length > 0)
90
+
91
+ const createCommandFile = await readFile(join(dir, 'cli', 'create.ts'), 'utf8')
92
+ assert.ok(createCommandFile.length > 0)
93
+
94
+ const gitignore = await readFile(join(dir, '.gitignore'), 'utf8')
95
+ assert.ok(gitignore.length > 0)
96
+ })