create-platformatic 0.25.0 → 0.26.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-platformatic",
3
- "version": "0.25.0",
3
+ "version": "0.26.1",
4
4
  "description": "Create platformatic-db interactive tool",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,6 +15,7 @@
15
15
  "license": "Apache-2.0",
16
16
  "author": "Marco Piraccini <marco.piraccini@gmail.com>",
17
17
  "dependencies": {
18
+ "boring-name-generator": "^1.0.3",
18
19
  "chalk": "^5.2.0",
19
20
  "commist": "^3.2.0",
20
21
  "desm": "^1.3.0",
@@ -25,14 +26,14 @@
25
26
  "inquirer": "^9.2.6",
26
27
  "log-update": "^5.0.1",
27
28
  "minimist": "^1.2.8",
28
- "mkdirp": "^2.1.6",
29
29
  "ora": "^6.3.1",
30
30
  "pino": "^8.14.1",
31
31
  "pino-pretty": "^10.0.0",
32
32
  "pupa": "^3.1.0",
33
33
  "semver": "^7.5.1",
34
34
  "undici": "^5.22.1",
35
- "@platformatic/config": "0.25.0"
35
+ "which": "^3.0.1",
36
+ "@platformatic/config": "0.26.1"
36
37
  },
37
38
  "devDependencies": {
38
39
  "ajv": "^8.12.0",
@@ -44,8 +45,8 @@
44
45
  "standard": "^17.0.0",
45
46
  "tap": "^16.3.4",
46
47
  "yaml": "^2.2.2",
47
- "@platformatic/db": "0.25.0",
48
- "@platformatic/service": "0.25.0"
48
+ "@platformatic/db": "0.26.1",
49
+ "@platformatic/service": "0.26.1"
49
50
  },
50
51
  "scripts": {
51
52
  "test": "standard | snazzy && cross-env NODE_OPTIONS=\"--loader=esmock --no-warnings\" c8 --100 tap --no-coverage test/*test.mjs test/*/*test.mjs",
@@ -2,11 +2,11 @@ import { validatePath } from './utils.mjs'
2
2
  import inquirer from 'inquirer'
3
3
  import { resolve } from 'path'
4
4
 
5
- const askProjectDir = async (logger, defaultName) => {
5
+ const askProjectDir = async (logger, defaultName, message = 'Where would you like to create your project?') => {
6
6
  const options = await inquirer.prompt({
7
7
  type: 'input',
8
8
  name: 'dir',
9
- message: 'Where would you like to create your project?',
9
+ message,
10
10
  default: defaultName,
11
11
  validate: validatePath
12
12
  })
@@ -18,3 +18,17 @@ export const getUseTypescript = typescript => {
18
18
  choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
19
19
  }
20
20
  }
21
+
22
+ let port = 3042
23
+ export const getPort = (nextPort) => {
24
+ if (nextPort === undefined) {
25
+ nextPort = port++
26
+ }
27
+
28
+ return {
29
+ type: 'input',
30
+ name: 'port',
31
+ message: 'What port do you want to use?',
32
+ default: nextPort
33
+ }
34
+ }
@@ -0,0 +1,30 @@
1
+ # Platformatic Composer API
2
+
3
+ This is a generated [Platformatic Composer](https://oss.platformatic.dev/docs/reference/composer/introduction) application.
4
+
5
+ ## Requirements
6
+
7
+ Platformatic supports macOS, Linux and Windows ([WSL](https://docs.microsoft.com/windows/wsl/) recommended).
8
+ You'll need to have [Node.js](https://nodejs.org/) >= v18.8.0
9
+
10
+ ## Setup
11
+
12
+ 1. Install dependencies:
13
+
14
+ ```bash
15
+ npm install
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Run the API with:
21
+
22
+ ```bash
23
+ npm start
24
+ ```
25
+
26
+ ### Explore
27
+ - ⚡ The Platformatic Composer server is running at http://localhost:3042/
28
+ - 📔 View the REST API's Swagger documentation at http://localhost:3042/documentation/
29
+
30
+
@@ -0,0 +1,107 @@
1
+ import { getVersion, getDependencyVersion, isFileAccessible } from '../utils.mjs'
2
+ import { createPackageJson } from '../create-package-json.mjs'
3
+ import { createGitignore } from '../create-gitignore.mjs'
4
+ import { getPkgManager } from '../get-pkg-manager.mjs'
5
+ import parseArgs from 'minimist'
6
+ import { join } from 'path'
7
+ import inquirer from 'inquirer'
8
+ import { readFile, writeFile, mkdir } from 'fs/promises'
9
+ import pino from 'pino'
10
+ import pretty from 'pino-pretty'
11
+ import { execa } from 'execa'
12
+ import ora from 'ora'
13
+ import createComposer from './create-composer.mjs'
14
+ import askDir from '../ask-dir.mjs'
15
+ import { askDynamicWorkspaceCreateGHAction, askStaticWorkspaceGHAction } from '../ghaction.mjs'
16
+ import { getRunPackageManagerInstall, getPort } from '../cli-options.mjs'
17
+
18
+ export const createReadme = async (logger, dir = '.') => {
19
+ const readmeFileName = join(dir, 'README.md')
20
+ let isReadmeExists = await isFileAccessible(readmeFileName)
21
+ if (isReadmeExists) {
22
+ logger.debug(`${readmeFileName} found, asking to overwrite it.`)
23
+ const { shouldReplace } = await inquirer.prompt([{
24
+ type: 'list',
25
+ name: 'shouldReplace',
26
+ message: 'Do you want to overwrite the existing README.md?',
27
+ default: true,
28
+ choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
29
+ }])
30
+ isReadmeExists = !shouldReplace
31
+ }
32
+
33
+ if (isReadmeExists) {
34
+ logger.debug(`${readmeFileName} found, skipping creation of README.md file.`)
35
+ return
36
+ }
37
+
38
+ const readmeFile = new URL('README.md', import.meta.url)
39
+ const readme = await readFile(readmeFile, 'utf-8')
40
+ await writeFile(readmeFileName, readme)
41
+ logger.debug(`${readmeFileName} successfully created.`)
42
+ }
43
+
44
+ const createPlatformaticComposer = async (_args, opts) => {
45
+ const logger = opts.logger || pino(pretty({
46
+ translateTime: 'SYS:HH:MM:ss',
47
+ ignore: 'hostname,pid'
48
+ }))
49
+
50
+ const args = parseArgs(_args, {
51
+ default: {
52
+ hostname: '127.0.0.1'
53
+ },
54
+ alias: {
55
+ h: 'hostname',
56
+ p: 'port'
57
+ }
58
+ })
59
+
60
+ const version = await getVersion()
61
+ const pkgManager = getPkgManager()
62
+
63
+ const projectDir = opts.dir || await askDir(logger, '.')
64
+
65
+ const toAsk = [getPort(args.port)]
66
+
67
+ if (!opts.skipPackageJson) {
68
+ toAsk.push(getRunPackageManagerInstall(pkgManager))
69
+ }
70
+
71
+ const { runPackageManagerInstall, port } = await inquirer.prompt(toAsk)
72
+
73
+ // Create the project directory
74
+ await mkdir(projectDir, { recursive: true })
75
+
76
+ const params = {
77
+ hostname: args.hostname,
78
+ port
79
+ }
80
+
81
+ const env = await createComposer(params, logger, projectDir, version)
82
+
83
+ const fastifyVersion = await getDependencyVersion('fastify')
84
+
85
+ // Create the package.json, notes that we don't have the option for TS (yet) so we don't generate
86
+ // the package.json with the TS build
87
+ if (!opts.skipPackageJson) {
88
+ await createPackageJson('composer', version, fastifyVersion, logger, projectDir, false)
89
+ }
90
+ if (!opts.skipGitignore) {
91
+ await createGitignore(logger, projectDir)
92
+ }
93
+ await createReadme(logger, projectDir)
94
+
95
+ if (runPackageManagerInstall) {
96
+ const spinner = ora('Installing dependencies...').start()
97
+ await execa(pkgManager, ['install'], { cwd: projectDir })
98
+ spinner.succeed('...done!')
99
+ }
100
+
101
+ if (!opts.skipGitHubActions) {
102
+ await askDynamicWorkspaceCreateGHAction(logger, env, 'composer', false, projectDir)
103
+ await askStaticWorkspaceGHAction(logger, env, 'composer', false, projectDir)
104
+ }
105
+ }
106
+
107
+ export default createPlatformaticComposer
@@ -0,0 +1,76 @@
1
+ import { readFile, writeFile, appendFile } from 'fs/promises'
2
+ import { findComposerConfigFile, isFileAccessible } from '../utils.mjs'
3
+ import { join } from 'path'
4
+ import * as desm from 'desm'
5
+
6
+ function generateConfig (version) {
7
+ const config = {
8
+ $schema: `https://platformatic.dev/schemas/v${version}/composer`,
9
+ server: {
10
+ hostname: '{PLT_SERVER_HOSTNAME}',
11
+ port: '{PORT}',
12
+ logger: {
13
+ level: '{PLT_SERVER_LOGGER_LEVEL}'
14
+ }
15
+ },
16
+ composer: {
17
+ services: [{
18
+ id: 'example',
19
+ origin: '{PLT_EXAMPLE_ORIGIN}',
20
+ openapi: {
21
+ url: '/documentation/json'
22
+ }
23
+ }],
24
+ refreshTimeout: 1000
25
+ },
26
+ watch: true
27
+ }
28
+
29
+ return config
30
+ }
31
+
32
+ function generateEnv (hostname, port) {
33
+ const env = `\
34
+ PLT_SERVER_HOSTNAME=${hostname}
35
+ PORT=${port}
36
+ PLT_SERVER_LOGGER_LEVEL=info
37
+ PLT_EXAMPLE_ORIGIN=
38
+ `
39
+
40
+ return env
41
+ }
42
+
43
+ async function createComposer ({ hostname, port }, logger, currentDir = process.cwd(), version) {
44
+ if (!version) {
45
+ const pkg = await readFile(desm.join(import.meta.url, '..', '..', 'package.json'))
46
+ version = JSON.parse(pkg).version
47
+ }
48
+ const accessibleConfigFilename = await findComposerConfigFile(currentDir)
49
+
50
+ if (accessibleConfigFilename === undefined) {
51
+ const config = generateConfig(version)
52
+ await writeFile(join(currentDir, 'platformatic.composer.json'), JSON.stringify(config, null, 2))
53
+ logger.info('Configuration file platformatic.composer.json successfully created.')
54
+
55
+ const env = generateEnv(hostname, port)
56
+ const envFileExists = await isFileAccessible('.env', currentDir)
57
+ await appendFile(join(currentDir, '.env'), env)
58
+ await writeFile(join(currentDir, '.env.sample'), env)
59
+ /* c8 ignore next 5 */
60
+ if (envFileExists) {
61
+ logger.info('Environment file .env found, appending new environment variables to existing .env file.')
62
+ } else {
63
+ logger.info('Environment file .env successfully created.')
64
+ }
65
+ } else {
66
+ logger.info(`Configuration file ${accessibleConfigFilename} found, skipping creation of configuration file.`)
67
+ }
68
+
69
+ return {
70
+ PLT_SERVER_LOGGER_LEVEL: 'info',
71
+ PORT: port,
72
+ PLT_SERVER_HOSTNAME: hostname
73
+ }
74
+ }
75
+
76
+ export default createComposer
@@ -3,7 +3,7 @@ import { writeFile } from 'fs/promises'
3
3
  import { join } from 'node:path'
4
4
 
5
5
  const gitignore = `\
6
- dist
6
+ dist
7
7
  .DS_Store
8
8
 
9
9
  # dotenv environment variable files
@@ -5,16 +5,16 @@ import { getPkgManager } from '../get-pkg-manager.mjs'
5
5
  import parseArgs from 'minimist'
6
6
  import { join } from 'path'
7
7
  import inquirer from 'inquirer'
8
- import { readFile, writeFile } from 'fs/promises'
8
+ import which from 'which'
9
+ import { readFile, writeFile, mkdir } from 'fs/promises'
9
10
  import pino from 'pino'
10
11
  import pretty from 'pino-pretty'
11
12
  import { execa } from 'execa'
12
13
  import ora from 'ora'
13
14
  import createDB from './create-db.mjs'
14
- import askProjectDir from '../ask-project-dir.mjs'
15
+ import askDir from '../ask-dir.mjs'
15
16
  import { askDynamicWorkspaceCreateGHAction, askStaticWorkspaceGHAction } from '../ghaction.mjs'
16
- import { getRunPackageManagerInstall, getUseTypescript } from '../cli-options.mjs'
17
- import mkdirp from 'mkdirp'
17
+ import { getRunPackageManagerInstall, getUseTypescript, getPort } from '../cli-options.mjs'
18
18
 
19
19
  export const createReadme = async (logger, dir = '.') => {
20
20
  const readmeFileName = join(dir, 'README.md')
@@ -42,11 +42,10 @@ export const createReadme = async (logger, dir = '.') => {
42
42
  logger.debug(`${readmeFileName} successfully created.`)
43
43
  }
44
44
 
45
- export const parseDBArgs = (_args) => {
45
+ export function parseDBArgs (_args) {
46
46
  return parseArgs(_args, {
47
47
  default: {
48
48
  hostname: '127.0.0.1',
49
- port: 3042,
50
49
  database: 'sqlite',
51
50
  migrations: 'migrations',
52
51
  plugin: true,
@@ -66,8 +65,8 @@ export const parseDBArgs = (_args) => {
66
65
  })
67
66
  }
68
67
 
69
- const createPlatformaticDB = async (_args) => {
70
- const logger = pino(pretty({
68
+ const createPlatformaticDB = async (_args, opts) => {
69
+ const logger = opts.logger || pino(pretty({
71
70
  translateTime: 'SYS:HH:MM:ss',
72
71
  ignore: 'hostname,pid'
73
72
  }))
@@ -75,7 +74,7 @@ const createPlatformaticDB = async (_args) => {
75
74
  const args = parseDBArgs(_args)
76
75
  const version = await getVersion()
77
76
  const pkgManager = getPkgManager()
78
- const projectDir = await askProjectDir(logger, '.')
77
+ const projectDir = opts.dir || await askDir(logger, '.')
79
78
 
80
79
  const wizardOptions = await inquirer.prompt([{
81
80
  type: 'list',
@@ -90,11 +89,12 @@ const createPlatformaticDB = async (_args) => {
90
89
  default: args.plugin,
91
90
  choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
92
91
  },
93
- getUseTypescript(args.typescript)
92
+ getUseTypescript(args.typescript),
93
+ getPort(args.port)
94
94
  ])
95
95
 
96
96
  // Create the project directory
97
- await mkdirp(projectDir)
97
+ await mkdir(projectDir, { recursive: true })
98
98
 
99
99
  const generatePlugin = args.plugin || wizardOptions.generatePlugin
100
100
  const useTypescript = args.typescript || wizardOptions.useTypescript
@@ -102,7 +102,7 @@ const createPlatformaticDB = async (_args) => {
102
102
 
103
103
  const params = {
104
104
  hostname: args.hostname,
105
- port: args.port,
105
+ port: wizardOptions.port,
106
106
  database: args.database,
107
107
  migrations: wizardOptions.defaultMigrations ? args.migrations : '',
108
108
  plugin: generatePlugin,
@@ -119,17 +119,35 @@ const createPlatformaticDB = async (_args) => {
119
119
  await createGitignore(logger, projectDir)
120
120
  await createReadme(logger, projectDir)
121
121
 
122
- const { runPackageManagerInstall } = await inquirer.prompt([
123
- getRunPackageManagerInstall(pkgManager)
124
- ])
122
+ let hasPlatformaticInstalled = false
123
+ if (!opts.skipPackageJson) {
124
+ const { runPackageManagerInstall } = await inquirer.prompt([
125
+ getRunPackageManagerInstall(pkgManager)
126
+ ])
127
+
128
+ if (runPackageManagerInstall) {
129
+ const spinner = ora('Installing dependencies...').start()
130
+ await execa(pkgManager, ['install'], { cwd: projectDir })
131
+ spinner.succeed('...done!')
132
+ hasPlatformaticInstalled = true
133
+ }
134
+ }
135
+
136
+ if (!hasPlatformaticInstalled) {
137
+ try {
138
+ const npmLs = JSON.parse(await execa('npm', ['ls', '--json']))
139
+ hasPlatformaticInstalled = !!npmLs.dependencies.platformatic
140
+ } catch {
141
+ // Ignore all errors, this can fail
142
+ }
143
+ }
125
144
 
126
- if (runPackageManagerInstall) {
127
- const spinner = ora('Installing dependencies...').start()
128
- await execa(pkgManager, ['install'], { cwd: projectDir })
129
- spinner.succeed('...done!')
145
+ if (!hasPlatformaticInstalled) {
146
+ const exe = await which('platformatic', { nothrow: true })
147
+ hasPlatformaticInstalled = !!exe
130
148
  }
131
149
 
132
- if (runPackageManagerInstall) {
150
+ if (hasPlatformaticInstalled) {
133
151
  // We applied package manager install, so we can:
134
152
  // - run the migrations
135
153
  // - generate types
@@ -166,8 +184,11 @@ const createPlatformaticDB = async (_args) => {
166
184
  }
167
185
  }
168
186
  }
169
- await askDynamicWorkspaceCreateGHAction(logger, env, 'db', useTypescript, projectDir)
170
- await askStaticWorkspaceGHAction(logger, env, 'db', useTypescript, projectDir)
187
+
188
+ if (!opts.skipGitHubActions) {
189
+ await askDynamicWorkspaceCreateGHAction(logger, env, 'db', useTypescript, projectDir)
190
+ await askStaticWorkspaceGHAction(logger, env, 'db', useTypescript, projectDir)
191
+ }
171
192
  }
172
193
 
173
194
  export default createPlatformaticDB
package/src/ghaction.mjs CHANGED
@@ -1,8 +1,7 @@
1
- import mkdirp from 'mkdirp'
2
1
  import { join } from 'path'
3
2
  import inquirer from 'inquirer'
4
3
  import { isFileAccessible } from './utils.mjs'
5
- import { writeFile } from 'fs/promises'
4
+ import { writeFile, mkdir } from 'fs/promises'
6
5
 
7
6
  export const dynamicWorkspaceGHTemplate = (env, config, buildTS = false) => {
8
7
  const envAsStr = Object.keys(env).reduce((acc, key) => {
@@ -90,7 +89,7 @@ export const createDynamicWorkspaceGHAction = async (logger, env, config, projec
90
89
  const ghActionFilePath = join(projectDir, '.github', 'workflows', ghActionFileName)
91
90
  const isGithubActionExists = await isFileAccessible(ghActionFilePath)
92
91
  if (!isGithubActionExists) {
93
- await mkdirp(join(projectDir, '.github', 'workflows'))
92
+ await mkdir(join(projectDir, '.github', 'workflows'), { recursive: true })
94
93
  await writeFile(ghActionFilePath, dynamicWorkspaceGHTemplate(env, config, buildTS))
95
94
  logger.info('Github action successfully created, please add PLATFORMATIC_DYNAMIC_WORKSPACE_ID and PLATFORMATIC_DYNAMIC_WORKSPACE_API_KEY as repository secrets.')
96
95
  const isGitDir = await isFileAccessible('.git', projectDir)
@@ -125,7 +124,7 @@ export const createStaticWorkspaceGHAction = async (logger, env, config, project
125
124
  const ghActionFilePath = join(projectDir, '.github', 'workflows', ghActionFileName)
126
125
  const isGithubActionExists = await isFileAccessible(ghActionFilePath)
127
126
  if (!isGithubActionExists) {
128
- await mkdirp(join(projectDir, '.github', 'workflows'))
127
+ await mkdir(join(projectDir, '.github', 'workflows'), { recursive: true })
129
128
  await writeFile(ghActionFilePath, staticWorkspaceGHTemplate(env, config, buildTS))
130
129
  logger.info('Github action successfully created, please add PLATFORMATIC_STATIC_WORKSPACE_ID and PLATFORMATIC_STATIC_WORKSPACE_API_KEY as repository secret.')
131
130
  const isGitDir = await isFileAccessible('.git', projectDir)
package/src/index.mjs CHANGED
@@ -2,10 +2,45 @@ import { say } from './say.mjs'
2
2
  import helpMe from 'help-me'
3
3
  import { join } from 'desm'
4
4
  import inquirer from 'inquirer'
5
+ import { readdir, readFile } from 'fs/promises'
5
6
  import createPlatformaticDB from './db/create-db-cli.mjs'
6
7
  import createPlatformaticService from './service/create-service-cli.mjs'
8
+ import createPlatformaticComposer from './composer/create-composer-cli.mjs'
9
+ import { createPlatformaticRuntime, createRuntimeService } from './runtime/create-runtime-cli.mjs'
7
10
  import commist from 'commist'
8
- import { getUsername, getVersion, minimumSupportedNodeVersions, isCurrentVersionSupported } from './utils.mjs'
11
+ import { getUsername, getVersion, minimumSupportedNodeVersions, isCurrentVersionSupported, findRuntimeConfigFile } from './utils.mjs'
12
+
13
+ export async function chooseKind (argv, opts = {}) {
14
+ const skip = opts.skip
15
+ const choices = [
16
+ { name: 'DB', value: 'db' },
17
+ { name: 'Service', value: 'service' },
18
+ { name: 'Composer', value: 'composer' },
19
+ { name: 'Runtime', value: 'runtime' }
20
+ ].filter((choice) => !skip || choice.value !== skip)
21
+
22
+ const options = await inquirer.prompt({
23
+ type: 'list',
24
+ name: 'type',
25
+ message: 'Which kind of project do you want to create?',
26
+ choices
27
+ })
28
+
29
+ switch (options.type) {
30
+ case 'db':
31
+ await createPlatformaticDB(argv, opts)
32
+ break
33
+ case 'service':
34
+ await createPlatformaticService(argv, opts)
35
+ break
36
+ case 'composer':
37
+ await createPlatformaticComposer(argv, opts)
38
+ break
39
+ case 'runtime':
40
+ await createPlatformaticRuntime(argv, opts)
41
+ break
42
+ }
43
+ }
9
44
 
10
45
  const createPlatformatic = async (argv) => {
11
46
  const help = helpMe({
@@ -20,6 +55,8 @@ const createPlatformatic = async (argv) => {
20
55
  program.register('service help', help.toStdout.bind(null, ['service']))
21
56
  program.register('db', createPlatformaticDB)
22
57
  program.register('service', createPlatformaticService)
58
+ program.register('composer', createPlatformaticComposer)
59
+ program.register('runtime', createPlatformaticRuntime)
23
60
 
24
61
  const result = program.parse(argv)
25
62
 
@@ -28,7 +65,6 @@ const createPlatformatic = async (argv) => {
28
65
  const version = await getVersion()
29
66
  const greeting = username ? `Hello, ${username}` : 'Hello,'
30
67
  await say(`${greeting} welcome to ${version ? `Platformatic ${version}!` : 'Platformatic!'}`)
31
- await say('Let\'s start by creating a new project.')
32
68
 
33
69
  const currentVersion = process.versions.node
34
70
  const supported = isCurrentVersionSupported(currentVersion)
@@ -38,18 +74,26 @@ const createPlatformatic = async (argv) => {
38
74
  await say(`Please use one of the following Node.js versions >= ${supportedVersions}.`)
39
75
  }
40
76
 
41
- const options = await inquirer.prompt({
42
- type: 'list',
43
- name: 'type',
44
- message: 'Which kind of project do you want to create?',
45
- default: 'db',
46
- choices: [{ name: 'DB', value: 'db' }, { name: 'Service', value: 'service' }]
47
- })
48
-
49
- if (options.type === 'db') {
50
- await createPlatformaticDB(argv)
51
- } else if (options.type === 'service') {
52
- await createPlatformaticService(argv)
77
+ const runtimeConfig = await findRuntimeConfigFile(process.cwd())
78
+ if (runtimeConfig) {
79
+ await say(`Found a ${runtimeConfig} file in the current directory.`)
80
+ const config = JSON.parse(await readFile(runtimeConfig, 'utf8'))
81
+ if (config.autoload?.path) {
82
+ const servicesDir = config.autoload.path
83
+ const names = []
84
+ for (const entry of await readdir(servicesDir)) {
85
+ names.push(entry)
86
+ }
87
+ if (!await createRuntimeService({ servicesDir, names })) {
88
+ process.exit(1)
89
+ }
90
+ } else {
91
+ await say('The current project does not have a services directory.')
92
+ process.exit(1)
93
+ }
94
+ } else {
95
+ await say('Let\'s start by creating a new project.')
96
+ await chooseKind(argv)
53
97
  }
54
98
 
55
99
  await say('\nAll done! Please open the project directory and check the README.')
@@ -0,0 +1,32 @@
1
+ # Platformatic Runtime API
2
+
3
+ This is a generated [Platformatic Runtime](https://oss.platformatic.dev/docs/reference/runtime/introduction) application.
4
+
5
+ ## Requirements
6
+
7
+ Platformatic supports macOS, Linux and Windows ([WSL](https://docs.microsoft.com/windows/wsl/) recommended).
8
+ You'll need to have [Node.js](https://nodejs.org/) >= v18.8.0
9
+
10
+ ## Setup
11
+
12
+ 1. Install dependencies:
13
+
14
+ ```bash
15
+ npm install
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Run the API with:
21
+
22
+ ```bash
23
+ npm start
24
+ ```
25
+
26
+ ## Adding a Service
27
+
28
+ Adding a new service to this project is as simple as running `create-platformatic` again, like so:
29
+
30
+ ```
31
+ npx create-platformatic
32
+ ```
@@ -0,0 +1,155 @@
1
+ import { getVersion, getDependencyVersion, isFileAccessible } from '../utils.mjs'
2
+ import { createPackageJson } from '../create-package-json.mjs'
3
+ import { createGitignore } from '../create-gitignore.mjs'
4
+ import { getPkgManager } from '../get-pkg-manager.mjs'
5
+ import { join } from 'path'
6
+ import inquirer from 'inquirer'
7
+ import { readFile, writeFile, mkdir } from 'fs/promises'
8
+ import pino from 'pino'
9
+ import pretty from 'pino-pretty'
10
+ import { execa } from 'execa'
11
+ import ora from 'ora'
12
+ import createRuntime from './create-runtime.mjs'
13
+ import askDir from '../ask-dir.mjs'
14
+ import { askDynamicWorkspaceCreateGHAction, askStaticWorkspaceGHAction } from '../ghaction.mjs'
15
+ import { getRunPackageManagerInstall } from '../cli-options.mjs'
16
+ import generateName from 'boring-name-generator'
17
+ import { chooseKind } from '../index.mjs'
18
+
19
+ export const createReadme = async (logger, dir = '.') => {
20
+ const readmeFileName = join(dir, 'README.md')
21
+ let isReadmeExists = await isFileAccessible(readmeFileName)
22
+ if (isReadmeExists) {
23
+ logger.debug(`${readmeFileName} found, asking to overwrite it.`)
24
+ const { shouldReplace } = await inquirer.prompt([{
25
+ type: 'list',
26
+ name: 'shouldReplace',
27
+ message: 'Do you want to overwrite the existing README.md?',
28
+ default: true,
29
+ choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
30
+ }])
31
+ isReadmeExists = !shouldReplace
32
+ }
33
+
34
+ if (isReadmeExists) {
35
+ logger.debug(`${readmeFileName} found, skipping creation of README.md file.`)
36
+ return
37
+ }
38
+
39
+ const readmeFile = new URL('README.md', import.meta.url)
40
+ const readme = await readFile(readmeFile, 'utf-8')
41
+ await writeFile(readmeFileName, readme)
42
+ logger.debug(`${readmeFileName} successfully created.`)
43
+ }
44
+
45
+ export async function createPlatformaticRuntime (_args) {
46
+ const logger = pino(pretty({
47
+ translateTime: 'SYS:HH:MM:ss',
48
+ ignore: 'hostname,pid'
49
+ }))
50
+
51
+ const version = await getVersion()
52
+ const pkgManager = getPkgManager()
53
+
54
+ const projectDir = await askDir(logger, '.')
55
+ const servicesDir = await askDir(logger, 'services', 'Where would you like to load your services from?')
56
+
57
+ const { runPackageManagerInstall } = await inquirer.prompt([
58
+ getRunPackageManagerInstall(pkgManager)
59
+ ])
60
+
61
+ // Create the project directory
62
+ await mkdir(projectDir, { recursive: true })
63
+ await mkdir(servicesDir, { recursive: true })
64
+
65
+ const fastifyVersion = await getDependencyVersion('fastify')
66
+
67
+ // Create the package.json, notes that we don't have the option for TS (yet) so we don't generate
68
+ // the package.json with the TS build
69
+ await createPackageJson('runtime', version, fastifyVersion, logger, projectDir, false)
70
+ await createGitignore(logger, projectDir)
71
+ await createReadme(logger, projectDir)
72
+
73
+ if (runPackageManagerInstall) {
74
+ const spinner = ora('Installing dependencies...').start()
75
+ await execa(pkgManager, ['install'], { cwd: projectDir })
76
+ spinner.succeed('...done!')
77
+ }
78
+
79
+ logger.info('Let\'s create a first service!')
80
+
81
+ const names = []
82
+ while (true) {
83
+ if (!await createRuntimeService({ servicesDir, names, logger })) {
84
+ continue
85
+ }
86
+
87
+ const { shouldBreak } = await inquirer.prompt([
88
+ {
89
+ type: 'list',
90
+ name: 'shouldBreak',
91
+ message: 'Do you want to create another service?',
92
+ default: false,
93
+ choices: [{ name: 'yes', value: false }, { name: 'no', value: true }]
94
+ }
95
+ ])
96
+
97
+ if (shouldBreak) {
98
+ break
99
+ }
100
+ }
101
+
102
+ let entrypoint = ''
103
+
104
+ if (names.length > 1) {
105
+ const results = await inquirer.prompt([
106
+ {
107
+ type: 'list',
108
+ name: 'entrypoint',
109
+ message: 'Which service should be exposed?',
110
+ choices: names.map(name => ({ name, value: name }))
111
+ }
112
+ ])
113
+ entrypoint = results.entrypoint
114
+ } else {
115
+ entrypoint = names[0]
116
+ }
117
+
118
+ const env = await createRuntime(logger, projectDir, version, servicesDir, entrypoint)
119
+
120
+ await askDynamicWorkspaceCreateGHAction(logger, env, 'service', false, projectDir)
121
+ await askStaticWorkspaceGHAction(logger, env, 'service', false, projectDir)
122
+ }
123
+
124
+ export async function createRuntimeService ({ servicesDir, names, logger }) {
125
+ logger ||= pino(pretty({
126
+ translateTime: 'SYS:HH:MM:ss',
127
+ ignore: 'hostname,pid'
128
+ }))
129
+ const { name } = await inquirer.prompt({
130
+ type: 'input',
131
+ name: 'name',
132
+ message: 'What is the name of the service?',
133
+ default: generateName().dashed
134
+ })
135
+
136
+ if (names.includes(name)) {
137
+ logger.warn('This name is already used, please choose another one.')
138
+ return false
139
+ }
140
+ names.push(name)
141
+
142
+ const serviceDir = join(servicesDir, name)
143
+
144
+ await chooseKind([], {
145
+ skip: 'runtime',
146
+ dir: serviceDir,
147
+ logger,
148
+ skipGitHubActions: true,
149
+ skipPackageJson: true,
150
+ skipGitignore: true,
151
+ port: '0'
152
+ })
153
+
154
+ return true
155
+ }
@@ -0,0 +1,40 @@
1
+ import { readFile, writeFile } from 'fs/promises'
2
+ import { findRuntimeConfigFile } from '../utils.mjs'
3
+ import { join, relative, isAbsolute } from 'path'
4
+ import * as desm from 'desm'
5
+
6
+ function generateConfig (version, path, entrypoint) {
7
+ const config = {
8
+ $schema: `https://platformatic.dev/schemas/v${version}/runtime`,
9
+ entrypoint,
10
+ allowCycles: false,
11
+ hotReload: true,
12
+ autoload: {
13
+ path,
14
+ exclude: ['docs']
15
+ }
16
+ }
17
+
18
+ return config
19
+ }
20
+
21
+ async function createRuntime (logger, currentDir = process.cwd(), version, servicesDir, entrypoint) {
22
+ if (!version) {
23
+ const pkg = await readFile(desm.join(import.meta.url, '..', '..', 'package.json'))
24
+ version = JSON.parse(pkg).version
25
+ }
26
+ const accessibleConfigFilename = await findRuntimeConfigFile(currentDir)
27
+
28
+ if (accessibleConfigFilename === undefined) {
29
+ const path = isAbsolute(servicesDir) ? relative(currentDir, servicesDir) : servicesDir
30
+ const config = generateConfig(version, path, entrypoint)
31
+ await writeFile(join(currentDir, 'platformatic.runtime.json'), JSON.stringify(config, null, 2))
32
+ logger.info('Configuration file platformatic.runtime.json successfully created.')
33
+ } else {
34
+ logger.info(`Configuration file ${accessibleConfigFilename} found, skipping creation of configuration file.`)
35
+ }
36
+
37
+ return {}
38
+ }
39
+
40
+ export default createRuntime
@@ -1,4 +1,3 @@
1
-
2
1
  import { getVersion, getDependencyVersion, isFileAccessible } from '../utils.mjs'
3
2
  import { createPackageJson } from '../create-package-json.mjs'
4
3
  import { createGitignore } from '../create-gitignore.mjs'
@@ -6,16 +5,15 @@ import { getPkgManager } from '../get-pkg-manager.mjs'
6
5
  import parseArgs from 'minimist'
7
6
  import { join } from 'path'
8
7
  import inquirer from 'inquirer'
9
- import { readFile, writeFile } from 'fs/promises'
8
+ import { readFile, writeFile, mkdir } from 'fs/promises'
10
9
  import pino from 'pino'
11
10
  import pretty from 'pino-pretty'
12
11
  import { execa } from 'execa'
13
12
  import ora from 'ora'
14
13
  import createService from './create-service.mjs'
15
- import askProjectDir from '../ask-project-dir.mjs'
14
+ import askDir from '../ask-dir.mjs'
16
15
  import { askDynamicWorkspaceCreateGHAction, askStaticWorkspaceGHAction } from '../ghaction.mjs'
17
- import { getRunPackageManagerInstall, getUseTypescript } from '../cli-options.mjs'
18
- import mkdirp from 'mkdirp'
16
+ import { getRunPackageManagerInstall, getUseTypescript, getPort } from '../cli-options.mjs'
19
17
 
20
18
  export const createReadme = async (logger, dir = '.') => {
21
19
  const readmeFileName = join(dir, 'README.md')
@@ -43,16 +41,15 @@ export const createReadme = async (logger, dir = '.') => {
43
41
  logger.debug(`${readmeFileName} successfully created.`)
44
42
  }
45
43
 
46
- const createPlatformaticService = async (_args) => {
47
- const logger = pino(pretty({
44
+ const createPlatformaticService = async (_args, opts = {}) => {
45
+ const logger = opts.logger || pino(pretty({
48
46
  translateTime: 'SYS:HH:MM:ss',
49
47
  ignore: 'hostname,pid'
50
48
  }))
51
49
 
52
50
  const args = parseArgs(_args, {
53
51
  default: {
54
- hostname: '127.0.0.1',
55
- port: 3042
52
+ hostname: '127.0.0.1'
56
53
  },
57
54
  alias: {
58
55
  h: 'hostname',
@@ -63,19 +60,22 @@ const createPlatformaticService = async (_args) => {
63
60
  const version = await getVersion()
64
61
  const pkgManager = getPkgManager()
65
62
 
66
- const projectDir = await askProjectDir(logger, '.')
63
+ const projectDir = opts.dir || await askDir(logger, '.')
64
+
65
+ const toAsk = [getUseTypescript(args.typescript), getPort(args.port)]
66
+
67
+ if (!opts.skipPackageJson) {
68
+ toAsk.unshift(getRunPackageManagerInstall(pkgManager))
69
+ }
67
70
 
68
- const { runPackageManagerInstall, useTypescript } = await inquirer.prompt([
69
- getRunPackageManagerInstall(pkgManager),
70
- getUseTypescript(args.typescript)
71
- ])
71
+ const { runPackageManagerInstall, useTypescript, port } = await inquirer.prompt(toAsk)
72
72
 
73
73
  // Create the project directory
74
- await mkdirp(projectDir)
74
+ await mkdir(projectDir, { recursive: true })
75
75
 
76
76
  const params = {
77
77
  hostname: args.hostname,
78
- port: args.port,
78
+ port,
79
79
  typescript: useTypescript
80
80
  }
81
81
 
@@ -83,10 +83,14 @@ const createPlatformaticService = async (_args) => {
83
83
 
84
84
  const fastifyVersion = await getDependencyVersion('fastify')
85
85
 
86
- // Create the package.json, notes that we don't have the option for TS (yet) so we don't generate
87
- // the package.json with the TS build
88
- await createPackageJson('service', version, fastifyVersion, logger, projectDir, false)
89
- await createGitignore(logger, projectDir)
86
+ if (!opts.skipPackageJson) {
87
+ // Create the package.json, notes that we don't have the option for TS (yet) so we don't generate
88
+ // the package.json with the TS build
89
+ await createPackageJson('service', version, fastifyVersion, logger, projectDir, false)
90
+ }
91
+ if (!opts.skipGitignore) {
92
+ await createGitignore(logger, projectDir)
93
+ }
90
94
  await createReadme(logger, projectDir)
91
95
 
92
96
  if (runPackageManagerInstall) {
@@ -95,8 +99,10 @@ const createPlatformaticService = async (_args) => {
95
99
  spinner.succeed('...done!')
96
100
  }
97
101
 
98
- await askDynamicWorkspaceCreateGHAction(logger, env, 'service', useTypescript, projectDir)
99
- await askStaticWorkspaceGHAction(logger, env, 'service', useTypescript, projectDir)
102
+ if (!opts.skipGitHubActions) {
103
+ await askDynamicWorkspaceCreateGHAction(logger, env, 'service', useTypescript, projectDir)
104
+ await askStaticWorkspaceGHAction(logger, env, 'service', useTypescript, projectDir)
105
+ }
100
106
  }
101
107
 
102
108
  export default createPlatformaticService
@@ -22,6 +22,9 @@ function generateConfig (version, typescript) {
22
22
  level: '{PLT_SERVER_LOGGER_LEVEL}'
23
23
  }
24
24
  },
25
+ service: {
26
+ openapi: true
27
+ },
25
28
  plugins
26
29
  }
27
30
 
package/src/utils.mjs CHANGED
@@ -70,6 +70,8 @@ export const validatePath = async projectPath => {
70
70
 
71
71
  export const findDBConfigFile = async (directory) => (ConfigManager.findConfigFile(directory, 'db'))
72
72
  export const findServiceConfigFile = async (directory) => (ConfigManager.findConfigFile(directory, 'service'))
73
+ export const findComposerConfigFile = async (directory) => (ConfigManager.findConfigFile(directory, 'composer'))
74
+ export const findRuntimeConfigFile = async (directory) => (ConfigManager.findConfigFile(directory, 'runtime'))
73
75
 
74
76
  export const getDependencyVersion = async (dependencyName) => {
75
77
  const require = createRequire(import.meta.url)
@@ -1,5 +1,5 @@
1
1
  import { test } from 'tap'
2
- import { getRunPackageManagerInstall, getUseTypescript } from '../src/cli-options.mjs'
2
+ import { getRunPackageManagerInstall, getUseTypescript, getPort } from '../src/cli-options.mjs'
3
3
 
4
4
  test('getRunPackageManagerInstall', async ({ same }) => {
5
5
  same(
@@ -27,3 +27,45 @@ test('getUseTypescript', async ({ same }) => {
27
27
  }
28
28
  )
29
29
  })
30
+
31
+ test('getPort', async ({ same }) => {
32
+ same(
33
+ getPort(undefined),
34
+ {
35
+ type: 'input',
36
+ name: 'port',
37
+ message: 'What port do you want to use?',
38
+ default: 3042
39
+ }
40
+ )
41
+
42
+ same(
43
+ getPort(undefined),
44
+ {
45
+ type: 'input',
46
+ name: 'port',
47
+ message: 'What port do you want to use?',
48
+ default: 3043
49
+ }
50
+ )
51
+
52
+ same(
53
+ getPort(1234),
54
+ {
55
+ type: 'input',
56
+ name: 'port',
57
+ message: 'What port do you want to use?',
58
+ default: 1234
59
+ }
60
+ )
61
+
62
+ same(
63
+ getPort(undefined),
64
+ {
65
+ type: 'input',
66
+ name: 'port',
67
+ message: 'What port do you want to use?',
68
+ default: 3044
69
+ }
70
+ )
71
+ })
@@ -0,0 +1,75 @@
1
+ import createComposer from '../../src/composer/create-composer.mjs'
2
+ import { test, beforeEach, afterEach } from 'tap'
3
+ import { tmpdir } from 'os'
4
+ import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'fs'
5
+ import { join } from 'path'
6
+ import dotenv from 'dotenv'
7
+
8
+ const base = tmpdir()
9
+ let tmpDir
10
+ let log = []
11
+ beforeEach(() => {
12
+ tmpDir = mkdtempSync(join(base, 'test-create-platformatic-'))
13
+ })
14
+
15
+ afterEach(() => {
16
+ log = []
17
+ rmSync(tmpDir, { recursive: true, force: true })
18
+ process.env = {}
19
+ })
20
+
21
+ const fakeLogger = {
22
+ debug: msg => log.push(msg),
23
+ info: msg => log.push(msg)
24
+ }
25
+
26
+ test('creates composer', async ({ equal, same, ok }) => {
27
+ const params = {
28
+ hostname: 'myhost',
29
+ port: 6666,
30
+ typescript: false
31
+ }
32
+
33
+ await createComposer(params, fakeLogger, tmpDir)
34
+
35
+ const pathToComposerConfigFile = join(tmpDir, 'platformatic.composer.json')
36
+ const composerConfigFile = readFileSync(pathToComposerConfigFile, 'utf8')
37
+ const composerConfig = JSON.parse(composerConfigFile)
38
+ const { server, composer } = composerConfig
39
+
40
+ equal(server.hostname, '{PLT_SERVER_HOSTNAME}')
41
+ equal(server.port, '{PORT}')
42
+
43
+ const pathToDbEnvFile = join(tmpDir, '.env')
44
+ dotenv.config({ path: pathToDbEnvFile })
45
+ equal(process.env.PLT_SERVER_HOSTNAME, 'myhost')
46
+ equal(process.env.PORT, '6666')
47
+ process.env = {}
48
+
49
+ const pathToDbEnvSampleFile = join(tmpDir, '.env.sample')
50
+ dotenv.config({ path: pathToDbEnvSampleFile })
51
+ equal(process.env.PLT_SERVER_HOSTNAME, 'myhost')
52
+ equal(process.env.PORT, '6666')
53
+
54
+ same(composer, {
55
+ services: [{
56
+ id: 'example',
57
+ origin: '{PLT_EXAMPLE_ORIGIN}',
58
+ openapi: {
59
+ url: '/documentation/json'
60
+ }
61
+ }],
62
+ refreshTimeout: 1000
63
+ })
64
+ })
65
+
66
+ test('creates project with configuration already present', async ({ ok }) => {
67
+ const pathToComposerConfigFileOld = join(tmpDir, 'platformatic.composer.json')
68
+ writeFileSync(pathToComposerConfigFileOld, JSON.stringify({ test: 'test' }))
69
+ const params = {
70
+ hostname: 'myhost',
71
+ port: 6666
72
+ }
73
+ await createComposer(params, fakeLogger, tmpDir)
74
+ ok(log.includes('Configuration file platformatic.composer.json found, skipping creation of configuration file.'))
75
+ })
@@ -1,8 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  import { test, beforeEach, afterEach } from 'tap'
4
- import { mkdtemp, rmdir, writeFile, readFile } from 'fs/promises'
5
- import mkdirp from 'mkdirp'
4
+ import { mkdtemp, rmdir, writeFile, readFile, mkdir } from 'fs/promises'
6
5
  import { join } from 'path'
7
6
  import { tmpdir } from 'os'
8
7
  import { isFileAccessible } from '../src/utils.mjs'
@@ -70,7 +69,7 @@ test('creates gh action with TS build step', async ({ end, equal }) => {
70
69
  })
71
70
 
72
71
  test('do not create gitignore file because already present', async ({ end, equal }) => {
73
- await mkdirp(join(tmpDir, '.github', 'workflows'))
72
+ await mkdir(join(tmpDir, '.github', 'workflows'), { recursive: true })
74
73
  const ghaction = join(tmpDir, '.github', 'workflows', 'platformatic-dynamic-workspace-deploy.yml')
75
74
  await writeFile(ghaction, 'TEST')
76
75
  await createDynamicWorkspaceGHAction(fakeLogger, env, 'db', tmpDir)
@@ -86,7 +85,7 @@ test('creates gh action with a warn if a .git folder is not present', async ({ e
86
85
  })
87
86
 
88
87
  test('creates gh action without a warn if a .git folder is present', async ({ end, equal }) => {
89
- await mkdirp(join(tmpDir, '.git'))
88
+ await mkdir(join(tmpDir, '.git'), { recursive: true })
90
89
  await createDynamicWorkspaceGHAction(fakeLogger, env, 'db', tmpDir)
91
90
  equal(log[0], 'Github action successfully created, please add PLATFORMATIC_DYNAMIC_WORKSPACE_ID and PLATFORMATIC_DYNAMIC_WORKSPACE_API_KEY as repository secrets.')
92
91
  const accessible = await isFileAccessible(join(tmpDir, '.github/workflows/platformatic-dynamic-workspace-deploy.yml'))
@@ -1,8 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  import { test, beforeEach, afterEach } from 'tap'
4
- import { mkdtemp, rmdir, writeFile, readFile } from 'fs/promises'
5
- import mkdirp from 'mkdirp'
4
+ import { mkdtemp, rmdir, writeFile, readFile, mkdir } from 'fs/promises'
6
5
  import { join } from 'path'
7
6
  import { tmpdir } from 'os'
8
7
  import { isFileAccessible } from '../src/utils.mjs'
@@ -68,7 +67,7 @@ test('creates gh action with TS build step', async ({ end, equal }) => {
68
67
  })
69
68
 
70
69
  test('do not create gitignore file because already present', async ({ end, equal }) => {
71
- await mkdirp(join(tmpDir, '.github', 'workflows'))
70
+ await mkdir(join(tmpDir, '.github', 'workflows'), { recursive: true })
72
71
  const ghaction = join(tmpDir, '.github', 'workflows', 'platformatic-static-workspace-deploy.yml')
73
72
  await writeFile(ghaction, 'TEST')
74
73
  await createStaticWorkspaceGHAction(fakeLogger, env, 'db', tmpDir)
@@ -84,7 +83,7 @@ test('creates gh action with a warn if a .git folder is not present', async ({ e
84
83
  })
85
84
 
86
85
  test('creates gh action without a warn if a .git folder is present', async ({ end, equal }) => {
87
- await mkdirp(join(tmpDir, '.git'))
86
+ await mkdir(join(tmpDir, '.git'), { recursive: true })
88
87
  await createStaticWorkspaceGHAction(fakeLogger, env, 'db', tmpDir)
89
88
  equal(log[0], 'Github action successfully created, please add PLATFORMATIC_STATIC_WORKSPACE_ID and PLATFORMATIC_STATIC_WORKSPACE_API_KEY as repository secret.')
90
89
  const accessible = await isFileAccessible(join(tmpDir, '.github/workflows/platformatic-static-workspace-deploy.yml'))
@@ -0,0 +1,70 @@
1
+ import createRuntime from '../../src/runtime/create-runtime.mjs'
2
+ import { test, beforeEach, afterEach } from 'tap'
3
+ import { tmpdir } from 'os'
4
+ import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'fs'
5
+ import { join } from 'path'
6
+
7
+ const base = tmpdir()
8
+ let tmpDir
9
+ let log = []
10
+ beforeEach(() => {
11
+ tmpDir = mkdtempSync(join(base, 'test-create-platformatic-'))
12
+ })
13
+
14
+ afterEach(() => {
15
+ log = []
16
+ rmSync(tmpDir, { recursive: true, force: true })
17
+ process.env = {}
18
+ })
19
+
20
+ const fakeLogger = {
21
+ debug: msg => log.push(msg),
22
+ info: msg => log.push(msg)
23
+ }
24
+
25
+ test('creates runtime', async ({ equal, same, ok }) => {
26
+ await createRuntime(fakeLogger, tmpDir, undefined, 'services', 'foobar')
27
+
28
+ const pathToRuntimeConfigFile = join(tmpDir, 'platformatic.runtime.json')
29
+ const runtimeConfigFile = readFileSync(pathToRuntimeConfigFile, 'utf8')
30
+ const runtimeConfig = JSON.parse(runtimeConfigFile)
31
+
32
+ delete runtimeConfig.$schema
33
+
34
+ same(runtimeConfig, {
35
+ entrypoint: 'foobar',
36
+ allowCycles: false,
37
+ hotReload: true,
38
+ autoload: {
39
+ path: 'services',
40
+ exclude: ['docs']
41
+ }
42
+ })
43
+ })
44
+
45
+ test('with a full path for autoload', async ({ equal, same, ok }) => {
46
+ await createRuntime(fakeLogger, tmpDir, undefined, join(tmpDir, 'services'), 'foobar')
47
+
48
+ const pathToRuntimeConfigFile = join(tmpDir, 'platformatic.runtime.json')
49
+ const runtimeConfigFile = readFileSync(pathToRuntimeConfigFile, 'utf8')
50
+ const runtimeConfig = JSON.parse(runtimeConfigFile)
51
+
52
+ delete runtimeConfig.$schema
53
+
54
+ same(runtimeConfig, {
55
+ entrypoint: 'foobar',
56
+ allowCycles: false,
57
+ hotReload: true,
58
+ autoload: {
59
+ path: 'services',
60
+ exclude: ['docs']
61
+ }
62
+ })
63
+ })
64
+
65
+ test('creates project with configuration already present', async ({ ok }) => {
66
+ const pathToRuntimeConfigFileOld = join(tmpDir, 'platformatic.runtime.json')
67
+ writeFileSync(pathToRuntimeConfigFileOld, JSON.stringify({ test: 'test' }))
68
+ await createRuntime(fakeLogger, tmpDir, 'foobar')
69
+ ok(log.includes('Configuration file platformatic.runtime.json found, skipping creation of configuration file.'))
70
+ })
@@ -1,5 +1,5 @@
1
1
  import { test } from 'tap'
2
- import { randomBetween, sleep, validatePath, getDependencyVersion, findDBConfigFile, findServiceConfigFile, isFileAccessible, isCurrentVersionSupported, minimumSupportedNodeVersions } from '../src/utils.mjs'
2
+ import { randomBetween, sleep, validatePath, getDependencyVersion, findDBConfigFile, findServiceConfigFile, isFileAccessible, isCurrentVersionSupported, minimumSupportedNodeVersions, findRuntimeConfigFile, findComposerConfigFile } from '../src/utils.mjs'
3
3
  import { mkdtempSync, rmSync, writeFileSync } from 'fs'
4
4
  import { tmpdir, platform } from 'os'
5
5
  import { join } from 'path'
@@ -158,6 +158,28 @@ test('findServiceConfigFile', async ({ end, equal, mock }) => {
158
158
  rmSync(tmpDir2, { recursive: true, force: true })
159
159
  })
160
160
 
161
+ test('findComposerConfigFile', async ({ end, equal, mock }) => {
162
+ const tmpDir1 = mkdtempSync(join(tmpdir(), 'test-create-platformatic-'))
163
+ const tmpDir2 = mkdtempSync(join(tmpdir(), 'test-create-platformatic-'))
164
+ const config = join(tmpDir1, 'platformatic.composer.yml')
165
+ writeFileSync(config, 'TEST')
166
+ equal(await findComposerConfigFile(tmpDir1), 'platformatic.composer.yml')
167
+ equal(await findComposerConfigFile(tmpDir2), undefined)
168
+ rmSync(tmpDir1, { recursive: true, force: true })
169
+ rmSync(tmpDir2, { recursive: true, force: true })
170
+ })
171
+
172
+ test('findRuntimeConfigFile', async ({ end, equal, mock }) => {
173
+ const tmpDir1 = mkdtempSync(join(tmpdir(), 'test-create-platformatic-'))
174
+ const tmpDir2 = mkdtempSync(join(tmpdir(), 'test-create-platformatic-'))
175
+ const config = join(tmpDir1, 'platformatic.runtime.yml')
176
+ writeFileSync(config, 'TEST')
177
+ equal(await findRuntimeConfigFile(tmpDir1), 'platformatic.runtime.yml')
178
+ equal(await findRuntimeConfigFile(tmpDir2), undefined)
179
+ rmSync(tmpDir1, { recursive: true, force: true })
180
+ rmSync(tmpDir2, { recursive: true, force: true })
181
+ })
182
+
161
183
  test('isFileAccessible', async ({ end, equal, mock }) => {
162
184
  const tmpDir1 = mkdtempSync(join(tmpdir(), 'test-create-platformatic-'))
163
185
  const config = join(tmpDir1, 'platformatic.db.yml')