create-platformatic 0.25.0 → 0.26.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/package.json +6 -5
- package/src/{ask-project-dir.mjs → ask-dir.mjs} +2 -2
- package/src/cli-options.mjs +14 -0
- package/src/composer/README.md +30 -0
- package/src/composer/create-composer-cli.mjs +107 -0
- package/src/composer/create-composer.mjs +76 -0
- package/src/db/create-db-cli.mjs +43 -22
- package/src/ghaction.mjs +3 -4
- package/src/index.mjs +58 -14
- package/src/runtime/README.md +32 -0
- package/src/runtime/create-runtime-cli.mjs +155 -0
- package/src/runtime/create-runtime.mjs +40 -0
- package/src/service/create-service-cli.mjs +28 -22
- package/src/service/create-service.mjs +3 -0
- package/src/utils.mjs +2 -0
- package/test/cli-options.test.mjs +43 -1
- package/test/composer/create-composer.test.mjs +75 -0
- package/test/ghaction-dynamic.test.mjs +3 -4
- package/test/ghaction-static.test.mjs +3 -4
- package/test/runtime/create-runtime.test.mjs +70 -0
- package/test/utils.test.mjs +23 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-platformatic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
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
|
-
"
|
|
35
|
+
"which": "^3.0.1",
|
|
36
|
+
"@platformatic/config": "0.26.0"
|
|
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.
|
|
48
|
-
"@platformatic/service": "0.
|
|
48
|
+
"@platformatic/db": "0.26.0",
|
|
49
|
+
"@platformatic/service": "0.26.0"
|
|
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
|
|
9
|
+
message,
|
|
10
10
|
default: defaultName,
|
|
11
11
|
validate: validatePath
|
|
12
12
|
})
|
package/src/cli-options.mjs
CHANGED
|
@@ -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
|
package/src/db/create-db-cli.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
123
|
-
|
|
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 (
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
spinner.succeed('...done!')
|
|
145
|
+
if (!hasPlatformaticInstalled) {
|
|
146
|
+
const exe = await which('platformatic', { noThrow: true })
|
|
147
|
+
hasPlatformaticInstalled = !!exe
|
|
130
148
|
}
|
|
131
149
|
|
|
132
|
-
if (
|
|
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
|
-
|
|
170
|
-
|
|
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
|
|
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
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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
|
|
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
|
|
74
|
+
await mkdir(projectDir, { recursive: true })
|
|
75
75
|
|
|
76
76
|
const params = {
|
|
77
77
|
hostname: args.hostname,
|
|
78
|
-
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
})
|
package/test/utils.test.mjs
CHANGED
|
@@ -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')
|