create-platformatic 0.45.1 → 0.46.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 +4 -4
- package/src/composer/create-composer-cli.mjs +1 -1
- package/src/create-plugins.mjs +232 -0
- package/src/db/create-db-cli.mjs +3 -2
- package/src/db/create-db.mjs +246 -36
- package/src/ghaction.mjs +1 -1
- package/src/runtime/create-runtime-cli.mjs +1 -1
- package/src/service/create-service-cli.mjs +6 -3
- package/src/service/create-service.mjs +3 -84
- package/test/db/create-db.test.mjs +13 -5
- package/test/service/create-service.test.mjs +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-platformatic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.1",
|
|
4
4
|
"description": "Create platformatic-db interactive tool",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"semver": "^7.5.1",
|
|
35
35
|
"undici": "^5.22.1",
|
|
36
36
|
"which": "^3.0.1",
|
|
37
|
-
"@platformatic/config": "0.
|
|
37
|
+
"@platformatic/config": "0.46.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"ajv": "^8.12.0",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"tap": "^16.3.6",
|
|
48
48
|
"typescript": "~5.2.0",
|
|
49
49
|
"yaml": "^2.3.1",
|
|
50
|
-
"@platformatic/db": "0.
|
|
51
|
-
"@platformatic/service": "0.
|
|
50
|
+
"@platformatic/db": "0.46.1",
|
|
51
|
+
"@platformatic/service": "0.46.1"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"test": "standard | snazzy && cross-env NODE_OPTIONS=\"--loader=esmock --no-warnings\" c8 tap --no-coverage test/*test.mjs test/*/*test.mjs",
|
|
@@ -93,8 +93,8 @@ const createPlatformaticComposer = async (_args, opts) => {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
if (!opts.skipGitHubActions) {
|
|
96
|
-
await askDynamicWorkspaceCreateGHAction(logger, env, 'composer', false, projectDir)
|
|
97
96
|
await askStaticWorkspaceGHAction(logger, env, 'composer', false, projectDir)
|
|
97
|
+
await askDynamicWorkspaceCreateGHAction(logger, env, 'composer', false, projectDir)
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { join } from 'path'
|
|
2
|
+
import { writeFile, mkdir } from 'fs/promises'
|
|
3
|
+
import { isFileAccessible } from './utils.mjs'
|
|
4
|
+
|
|
5
|
+
const JS_PLUGIN_WITH_TYPES_SUPPORT = `\
|
|
6
|
+
/// <reference path="../global.d.ts" />
|
|
7
|
+
'use strict'
|
|
8
|
+
/** @param {import('fastify').FastifyInstance} fastify */
|
|
9
|
+
module.exports = async function (fastify, opts) {
|
|
10
|
+
fastify.decorate('example', 'foobar')
|
|
11
|
+
}
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
const TS_PLUGIN_WITH_TYPES_SUPPORT = `\
|
|
15
|
+
/// <reference path="../global.d.ts" />
|
|
16
|
+
import { FastifyInstance, FastifyPluginOptions } from 'fastify'
|
|
17
|
+
|
|
18
|
+
export default async function (fastify: FastifyInstance, opts: FastifyPluginOptions) {
|
|
19
|
+
fastify.decorate('example', 'foobar')
|
|
20
|
+
}
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
const JS_ROUTES_WITH_TYPES_SUPPORT = `\
|
|
24
|
+
/// <reference path="../global.d.ts" />
|
|
25
|
+
'use strict'
|
|
26
|
+
/** @param {import('fastify').FastifyInstance} fastify */
|
|
27
|
+
module.exports = async function (fastify, opts) {
|
|
28
|
+
fastify.get('/example', async (request, reply) => {
|
|
29
|
+
return { hello: fastify.example }
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
`
|
|
33
|
+
|
|
34
|
+
const TS_ROUTES_WITH_TYPES_SUPPORT = `\
|
|
35
|
+
/// <reference path="../global.d.ts" />
|
|
36
|
+
import { FastifyInstance, FastifyPluginOptions } from 'fastify'
|
|
37
|
+
|
|
38
|
+
declare module 'fastify' {
|
|
39
|
+
interface FastifyInstance {
|
|
40
|
+
example: string
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default async function (fastify: FastifyInstance, opts: FastifyPluginOptions) {
|
|
45
|
+
fastify.get('/example', async (request, reply) => {
|
|
46
|
+
return { hello: fastify.example }
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
`
|
|
50
|
+
|
|
51
|
+
function testHelperJS (mod, customization = { pre: '', post: '', config: '' }) {
|
|
52
|
+
return `\
|
|
53
|
+
'use strict'
|
|
54
|
+
|
|
55
|
+
const { join } = require('node:path')
|
|
56
|
+
const { readFile } = require('node:fs/promises')
|
|
57
|
+
const { buildServer } = require('@platformatic/${mod}')
|
|
58
|
+
${customization.requires || ''}
|
|
59
|
+
|
|
60
|
+
async function getServer (t) {
|
|
61
|
+
${customization.pre || ''}
|
|
62
|
+
const config = JSON.parse(await readFile(join(__dirname, '..', 'platformatic.${mod}.json'), 'utf8'))
|
|
63
|
+
// Add your config customizations here. For example you want to set
|
|
64
|
+
// all things that are set in the config file to read from an env variable
|
|
65
|
+
config.server.logger.level = 'warn'
|
|
66
|
+
config.watch = false
|
|
67
|
+
${customization.config || ''}
|
|
68
|
+
// Add your config customizations here
|
|
69
|
+
const server = await buildServer(config)
|
|
70
|
+
t.after(() => server.close())
|
|
71
|
+
${customization.post || ''}
|
|
72
|
+
return server
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports.getServer = getServer
|
|
76
|
+
`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const TEST_ROUTES_JS = `\
|
|
80
|
+
'use strict'
|
|
81
|
+
|
|
82
|
+
const test = require('node:test')
|
|
83
|
+
const assert = require('node:assert')
|
|
84
|
+
const { getServer } = require('../helper')
|
|
85
|
+
|
|
86
|
+
test('example', async (t) => {
|
|
87
|
+
const server = await getServer(t)
|
|
88
|
+
const res = await server.inject({
|
|
89
|
+
method: 'GET',
|
|
90
|
+
url: '/example'
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
assert.strictEqual(res.statusCode, 200)
|
|
94
|
+
assert.deepStrictEqual(res.json(), {
|
|
95
|
+
hello: 'foobar'
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
`
|
|
99
|
+
|
|
100
|
+
const TEST_PLUGIN_JS = `\
|
|
101
|
+
'use strict'
|
|
102
|
+
|
|
103
|
+
const test = require('node:test')
|
|
104
|
+
const assert = require('node:assert')
|
|
105
|
+
const { getServer } = require('../helper')
|
|
106
|
+
|
|
107
|
+
test('example decorator', async (t) => {
|
|
108
|
+
const server = await getServer(t)
|
|
109
|
+
|
|
110
|
+
assert.strictEqual(server.example, 'foobar')
|
|
111
|
+
})
|
|
112
|
+
`
|
|
113
|
+
|
|
114
|
+
function testHelperTS (mod, customizations = { pre: '', post: '', config: '', requires: '' }) {
|
|
115
|
+
return `\
|
|
116
|
+
import { join } from 'node:path'
|
|
117
|
+
import { readFile } from 'node:fs/promises'
|
|
118
|
+
import { buildServer } from '@platformatic/${mod}'
|
|
119
|
+
${customizations.requires}
|
|
120
|
+
|
|
121
|
+
export async function getServer (t) {
|
|
122
|
+
${customizations.pre}
|
|
123
|
+
// We go up two folder because this files executes in the dist folder
|
|
124
|
+
const config = JSON.parse(await readFile(join(__dirname, '..', '..', 'platformatic.${mod}.json'), 'utf8'))
|
|
125
|
+
// Add your config customizations here. For example you want to set
|
|
126
|
+
// all things that are set in the config file to read from an env variable
|
|
127
|
+
config.server.logger.level = 'warn'
|
|
128
|
+
config.watch = false
|
|
129
|
+
${customizations.config}
|
|
130
|
+
// Add your config customizations here
|
|
131
|
+
const server = await buildServer(config)
|
|
132
|
+
t.after(() => server.close())
|
|
133
|
+
${customizations.post}
|
|
134
|
+
return server
|
|
135
|
+
}
|
|
136
|
+
`
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const TEST_ROUTES_TS = `\
|
|
140
|
+
import test from 'node:test'
|
|
141
|
+
import assert from 'node:assert'
|
|
142
|
+
import { getServer } from '../helper'
|
|
143
|
+
|
|
144
|
+
test('root', async (t) => {
|
|
145
|
+
const server = await getServer(t)
|
|
146
|
+
const res = await server.inject({
|
|
147
|
+
method: 'GET',
|
|
148
|
+
url: '/example'
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
assert.strictEqual(res.statusCode, 200)
|
|
152
|
+
assert.deepStrictEqual(res.json(), {
|
|
153
|
+
hello: 'foobar'
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
`
|
|
157
|
+
|
|
158
|
+
const TEST_PLUGIN_TS = `\
|
|
159
|
+
import test from 'node:test'
|
|
160
|
+
import assert from 'node:assert'
|
|
161
|
+
import { getServer } from '../helper'
|
|
162
|
+
|
|
163
|
+
test('example decorator', async (t) => {
|
|
164
|
+
const server = await getServer(t)
|
|
165
|
+
|
|
166
|
+
assert.strictEqual(server.example, 'foobar')
|
|
167
|
+
})
|
|
168
|
+
`
|
|
169
|
+
|
|
170
|
+
export async function generatePluginWithTypesSupport (logger, currentDir, isTypescript) {
|
|
171
|
+
const accessible = await isFileAccessible('plugins', currentDir)
|
|
172
|
+
if (accessible) {
|
|
173
|
+
logger.info('Plugins folder "plugins" found, skipping creation of plugins folder.')
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
await mkdir(join(currentDir, 'plugins'))
|
|
177
|
+
const pluginTemplate = isTypescript
|
|
178
|
+
? TS_PLUGIN_WITH_TYPES_SUPPORT
|
|
179
|
+
: JS_PLUGIN_WITH_TYPES_SUPPORT
|
|
180
|
+
const pluginName = isTypescript
|
|
181
|
+
? 'example.ts'
|
|
182
|
+
: 'example.js'
|
|
183
|
+
await writeFile(join(currentDir, 'plugins', pluginName), pluginTemplate)
|
|
184
|
+
logger.info('Plugins folder "plugins" successfully created.')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function generateRouteWithTypesSupport (logger, currentDir, isTypescript) {
|
|
188
|
+
const accessible = await isFileAccessible('routes', currentDir)
|
|
189
|
+
if (accessible) {
|
|
190
|
+
logger.info('Routes folder "routes" found, skipping creation of routes folder.')
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
await mkdir(join(currentDir, 'routes'))
|
|
194
|
+
const routesTemplate = isTypescript
|
|
195
|
+
? TS_ROUTES_WITH_TYPES_SUPPORT
|
|
196
|
+
: JS_ROUTES_WITH_TYPES_SUPPORT
|
|
197
|
+
const routesName = isTypescript
|
|
198
|
+
? 'root.ts'
|
|
199
|
+
: 'root.js'
|
|
200
|
+
await writeFile(join(currentDir, 'routes', routesName), routesTemplate)
|
|
201
|
+
logger.info('Routes folder "routes" successfully created.')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function generateTests (logger, currentDir, isTypescript, mod, customizations) {
|
|
205
|
+
const accessible = await isFileAccessible('tests', currentDir)
|
|
206
|
+
if (accessible) {
|
|
207
|
+
logger.info('Test folder found, skipping creation of tests.')
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await mkdir(join(currentDir, 'test'))
|
|
212
|
+
await mkdir(join(currentDir, 'test', 'plugins'))
|
|
213
|
+
await mkdir(join(currentDir, 'test', 'routes'))
|
|
214
|
+
|
|
215
|
+
if (isTypescript) {
|
|
216
|
+
await writeFile(join(currentDir, 'test', 'helper.ts'), testHelperTS(mod, customizations))
|
|
217
|
+
await writeFile(join(currentDir, 'test', 'plugins', 'example.test.ts'), TEST_PLUGIN_TS)
|
|
218
|
+
await writeFile(join(currentDir, 'test', 'routes', 'root.test.ts'), TEST_ROUTES_TS)
|
|
219
|
+
} else {
|
|
220
|
+
await writeFile(join(currentDir, 'test', 'helper.js'), testHelperJS(mod, customizations))
|
|
221
|
+
await writeFile(join(currentDir, 'test', 'plugins', 'example.test.js'), TEST_PLUGIN_JS)
|
|
222
|
+
await writeFile(join(currentDir, 'test', 'routes', 'root.test.js'), TEST_ROUTES_JS)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
logger.info('Test folder "tests" successfully created.')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export async function generatePlugins (logger, currentDir, isTypescript, mod, helperCustomization) {
|
|
229
|
+
await generatePluginWithTypesSupport(logger, currentDir, isTypescript)
|
|
230
|
+
await generateRouteWithTypesSupport(logger, currentDir, isTypescript)
|
|
231
|
+
await generateTests(logger, currentDir, isTypescript, mod, helperCustomization)
|
|
232
|
+
}
|
package/src/db/create-db-cli.mjs
CHANGED
|
@@ -166,7 +166,8 @@ const createPlatformaticDB = async (_args, opts) => {
|
|
|
166
166
|
const fastifyVersion = await getDependencyVersion('fastify')
|
|
167
167
|
|
|
168
168
|
const scripts = {
|
|
169
|
-
migrate: 'platformatic db migrations apply'
|
|
169
|
+
migrate: 'platformatic db migrations apply',
|
|
170
|
+
test: useTypescript ? 'tsc && node --test dist/test/*/*.test.js' : 'node --test test/*/*.test.js'
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
const dependencies = {
|
|
@@ -245,8 +246,8 @@ const createPlatformaticDB = async (_args, opts) => {
|
|
|
245
246
|
}
|
|
246
247
|
|
|
247
248
|
if (!opts.skipGitHubActions) {
|
|
248
|
-
await askDynamicWorkspaceCreateGHAction(logger, env, 'db', useTypescript, projectDir)
|
|
249
249
|
await askStaticWorkspaceGHAction(logger, env, 'db', useTypescript, projectDir)
|
|
250
|
+
await askDynamicWorkspaceCreateGHAction(logger, env, 'db', useTypescript, projectDir)
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
|
package/src/db/create-db.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { writeFile, mkdir, appendFile } from 'fs/promises'
|
|
2
|
-
import { join
|
|
2
|
+
import { join } from 'path'
|
|
3
3
|
import { findDBConfigFile, isFileAccessible } from '../utils.mjs'
|
|
4
4
|
import { getTsConfig } from '../get-tsconfig.mjs'
|
|
5
|
+
import { generatePlugins } from '../create-plugins.mjs'
|
|
5
6
|
|
|
6
7
|
const connectionStrings = {
|
|
7
8
|
postgres: 'postgres://postgres:postgres@127.0.0.1:5432/postgres',
|
|
@@ -32,9 +33,222 @@ const moviesMigrationUndo = `
|
|
|
32
33
|
DROP TABLE movies;
|
|
33
34
|
`
|
|
34
35
|
|
|
35
|
-
const getPluginName = (isTypescript) => isTypescript === true ? 'plugin.ts' : 'plugin.js'
|
|
36
36
|
const TS_OUT_DIR = 'dist'
|
|
37
37
|
|
|
38
|
+
const jsHelperSqlite = {
|
|
39
|
+
requires: `
|
|
40
|
+
const os = require('node:os')
|
|
41
|
+
const path = require('node:path')
|
|
42
|
+
const fs = require('node:fs/promises')
|
|
43
|
+
|
|
44
|
+
let counter = 0
|
|
45
|
+
`,
|
|
46
|
+
pre: `
|
|
47
|
+
const dbPath = join(os.tmpdir(), 'db-' + process.pid + '-' + counter++ + '.sqlite')
|
|
48
|
+
const connectionString = 'sqlite://' + dbPath
|
|
49
|
+
`,
|
|
50
|
+
config: `
|
|
51
|
+
config.migrations.autoApply = true
|
|
52
|
+
config.types.autogenerate = false
|
|
53
|
+
config.db.connectionString = connectionString
|
|
54
|
+
`,
|
|
55
|
+
post: `
|
|
56
|
+
t.after(async () => {
|
|
57
|
+
await fs.unlink(dbPath)
|
|
58
|
+
})
|
|
59
|
+
`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function jsHelperPostgres (connectionString) {
|
|
63
|
+
return {
|
|
64
|
+
// TODO(mcollina): replace sql-mapper
|
|
65
|
+
requires: `
|
|
66
|
+
const { createConnectionPool } = require('@platformatic/sql-mapper')
|
|
67
|
+
const connectionString = '${connectionString}'
|
|
68
|
+
let counter = 0
|
|
69
|
+
`,
|
|
70
|
+
pre: `
|
|
71
|
+
const { db, sql } = await createConnectionPool({
|
|
72
|
+
log: {
|
|
73
|
+
debug: () => {},
|
|
74
|
+
info: () => {},
|
|
75
|
+
trace: () => {}
|
|
76
|
+
},
|
|
77
|
+
connectionString,
|
|
78
|
+
poolSize: 1
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const newDB = \`t-\${process.pid}-\${counter++}\`
|
|
82
|
+
t.diagnostic('Creating database ' + newDB)
|
|
83
|
+
|
|
84
|
+
await db.query(sql\`
|
|
85
|
+
CREATE DATABASE \${sql.ident(newDB)}
|
|
86
|
+
\`)
|
|
87
|
+
`,
|
|
88
|
+
config: `
|
|
89
|
+
config.migrations.autoApply = true
|
|
90
|
+
config.types.autogenerate = false
|
|
91
|
+
config.db.connectionString = connectionString.replace(/\\/[a-zA-Z0-9\\-_]+$/, '/' + newDB)
|
|
92
|
+
config.db.schemalock = false
|
|
93
|
+
`,
|
|
94
|
+
post: `
|
|
95
|
+
t.after(async () => {
|
|
96
|
+
t.diagnostic('Disposing test database ' + newDB)
|
|
97
|
+
await db.query(sql\`
|
|
98
|
+
DROP DATABASE \${sql.ident(newDB)}
|
|
99
|
+
\`)
|
|
100
|
+
await db.dispose()
|
|
101
|
+
})
|
|
102
|
+
`
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function jsHelperMySQL (connectionString) {
|
|
107
|
+
return {
|
|
108
|
+
// TODO(mcollina): replace sql-mapper
|
|
109
|
+
requires: `
|
|
110
|
+
const { createConnectionPool } = require('@platformatic/sql-mapper')
|
|
111
|
+
const connectionString = '${connectionString}'
|
|
112
|
+
let counter = 0
|
|
113
|
+
`,
|
|
114
|
+
pre: `
|
|
115
|
+
const { db, sql } = await createConnectionPool({
|
|
116
|
+
log: {
|
|
117
|
+
debug: () => {},
|
|
118
|
+
info: () => {},
|
|
119
|
+
trace: () => {}
|
|
120
|
+
},
|
|
121
|
+
connectionString,
|
|
122
|
+
poolSize: 1
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const newDB = \`t-\${process.pid}-\${counter++}\`
|
|
126
|
+
t.diagnostic('Creating database ' + newDB)
|
|
127
|
+
|
|
128
|
+
await db.query(sql\`
|
|
129
|
+
CREATE DATABASE \${sql.ident(newDB)}
|
|
130
|
+
\`)
|
|
131
|
+
`,
|
|
132
|
+
config: `
|
|
133
|
+
config.migrations.autoApply = true
|
|
134
|
+
config.types.autogenerate = false
|
|
135
|
+
config.db.connectionString = connectionString.replace(/\\/[a-zA-Z0-9\\-_]+$/, '/' + newDB)
|
|
136
|
+
config.db.schemalock = false
|
|
137
|
+
`,
|
|
138
|
+
post: `
|
|
139
|
+
t.after(async () => {
|
|
140
|
+
t.diagnostic('Disposing test database ' + newDB)
|
|
141
|
+
await db.query(sql\`
|
|
142
|
+
DROP DATABASE \${sql.ident(newDB)}
|
|
143
|
+
\`)
|
|
144
|
+
await db.dispose()
|
|
145
|
+
})
|
|
146
|
+
`
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const moviesTestJS = `\
|
|
151
|
+
'use strict'
|
|
152
|
+
|
|
153
|
+
const test = require('node:test')
|
|
154
|
+
const assert = require('node:assert')
|
|
155
|
+
const { getServer } = require('../helper')
|
|
156
|
+
|
|
157
|
+
test('movies', async (t) => {
|
|
158
|
+
const server = await getServer(t)
|
|
159
|
+
|
|
160
|
+
{
|
|
161
|
+
const res = await server.inject({
|
|
162
|
+
method: 'GET',
|
|
163
|
+
url: '/movies'
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
assert.strictEqual(res.statusCode, 200)
|
|
167
|
+
assert.deepStrictEqual(res.json(), [])
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let id
|
|
171
|
+
{
|
|
172
|
+
const res = await server.inject({
|
|
173
|
+
method: 'POST',
|
|
174
|
+
url: '/movies',
|
|
175
|
+
body: {
|
|
176
|
+
title: 'The Matrix'
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
assert.strictEqual(res.statusCode, 200)
|
|
181
|
+
const body = res.json()
|
|
182
|
+
assert.strictEqual(body.title, 'The Matrix')
|
|
183
|
+
assert.strictEqual(body.id !== undefined, true)
|
|
184
|
+
id = body.id
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
{
|
|
188
|
+
const res = await server.inject({
|
|
189
|
+
method: 'GET',
|
|
190
|
+
url: '/movies'
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
assert.strictEqual(res.statusCode, 200)
|
|
194
|
+
assert.deepStrictEqual(res.json(), [{
|
|
195
|
+
id,
|
|
196
|
+
title: 'The Matrix'
|
|
197
|
+
}])
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
`
|
|
201
|
+
|
|
202
|
+
const moviesTestTS = `\
|
|
203
|
+
import test from 'node:test'
|
|
204
|
+
import assert from 'node:assert'
|
|
205
|
+
import { getServer } from '../helper'
|
|
206
|
+
|
|
207
|
+
test('movies', async (t) => {
|
|
208
|
+
const server = await getServer(t)
|
|
209
|
+
|
|
210
|
+
{
|
|
211
|
+
const res = await server.inject({
|
|
212
|
+
method: 'GET',
|
|
213
|
+
url: '/movies'
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
assert.strictEqual(res.statusCode, 200)
|
|
217
|
+
assert.deepStrictEqual(res.json(), [])
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let id : Number
|
|
221
|
+
{
|
|
222
|
+
const res = await server.inject({
|
|
223
|
+
method: 'POST',
|
|
224
|
+
url: '/movies',
|
|
225
|
+
body: {
|
|
226
|
+
title: 'The Matrix'
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
assert.strictEqual(res.statusCode, 200)
|
|
231
|
+
const body = res.json()
|
|
232
|
+
assert.strictEqual(body.title, 'The Matrix')
|
|
233
|
+
assert.strictEqual(body.id !== undefined, true)
|
|
234
|
+
id = body.id as Number
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
{
|
|
238
|
+
const res = await server.inject({
|
|
239
|
+
method: 'GET',
|
|
240
|
+
url: '/movies'
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
assert.strictEqual(res.statusCode, 200)
|
|
244
|
+
assert.deepStrictEqual(res.json(), [{
|
|
245
|
+
id,
|
|
246
|
+
title: 'The Matrix'
|
|
247
|
+
}])
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
`
|
|
251
|
+
|
|
38
252
|
function generateConfig (migrations, plugin, types, typescript, version) {
|
|
39
253
|
const config = {
|
|
40
254
|
$schema: `https://platformatic.dev/schemas/v${version}/db`,
|
|
@@ -64,7 +278,12 @@ function generateConfig (migrations, plugin, types, typescript, version) {
|
|
|
64
278
|
|
|
65
279
|
if (plugin === true) {
|
|
66
280
|
config.plugins = {
|
|
67
|
-
paths: [
|
|
281
|
+
paths: [{
|
|
282
|
+
path: './plugins',
|
|
283
|
+
encapsulate: false
|
|
284
|
+
}, {
|
|
285
|
+
path: './routes'
|
|
286
|
+
}]
|
|
68
287
|
}
|
|
69
288
|
}
|
|
70
289
|
|
|
@@ -101,38 +320,6 @@ PLT_TYPESCRIPT=true
|
|
|
101
320
|
return env
|
|
102
321
|
}
|
|
103
322
|
|
|
104
|
-
const JS_PLUGIN_WITH_TYPES_SUPPORT = `\
|
|
105
|
-
/// <reference path="./global.d.ts" />
|
|
106
|
-
'use strict'
|
|
107
|
-
|
|
108
|
-
/** @param {import('fastify').FastifyInstance} app */
|
|
109
|
-
module.exports = async function (app) {}
|
|
110
|
-
`
|
|
111
|
-
|
|
112
|
-
const TS_PLUGIN_WITH_TYPES_SUPPORT = `\
|
|
113
|
-
/// <reference path="./global.d.ts" />
|
|
114
|
-
import { FastifyInstance } from 'fastify'
|
|
115
|
-
|
|
116
|
-
export default async function (app: FastifyInstance) {}
|
|
117
|
-
`
|
|
118
|
-
|
|
119
|
-
async function generatePluginWithTypesSupport (logger, currentDir, isTypescript) {
|
|
120
|
-
const pluginPath = resolve(currentDir, getPluginName(isTypescript))
|
|
121
|
-
|
|
122
|
-
const isPluginExists = await isFileAccessible(pluginPath)
|
|
123
|
-
if (isPluginExists) {
|
|
124
|
-
logger.info(`Plugin file ${pluginPath} found, skipping creation of plugin file.`)
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const pluginTemplate = isTypescript
|
|
129
|
-
? TS_PLUGIN_WITH_TYPES_SUPPORT
|
|
130
|
-
: JS_PLUGIN_WITH_TYPES_SUPPORT
|
|
131
|
-
|
|
132
|
-
await writeFile(pluginPath, pluginTemplate)
|
|
133
|
-
logger.info(`Plugin file created at ${relative(currentDir, pluginPath)}`)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
323
|
export function getConnectionString (database) {
|
|
137
324
|
return connectionStrings[database]
|
|
138
325
|
}
|
|
@@ -201,7 +388,30 @@ export async function createDB ({ hostname, database = 'sqlite', port, migration
|
|
|
201
388
|
}
|
|
202
389
|
|
|
203
390
|
if (plugin) {
|
|
204
|
-
|
|
391
|
+
let jsHelper = { pre: '', config: '', post: '' }
|
|
392
|
+
switch (database) {
|
|
393
|
+
case 'sqlite':
|
|
394
|
+
jsHelper = jsHelperSqlite
|
|
395
|
+
break
|
|
396
|
+
case 'mysql':
|
|
397
|
+
jsHelper = jsHelperMySQL(connectionString)
|
|
398
|
+
break
|
|
399
|
+
case 'postgres':
|
|
400
|
+
jsHelper = jsHelperPostgres(connectionString)
|
|
401
|
+
break
|
|
402
|
+
case 'mariadb':
|
|
403
|
+
jsHelper = jsHelperMySQL(connectionString)
|
|
404
|
+
break
|
|
405
|
+
}
|
|
406
|
+
await generatePlugins(logger, currentDir, typescript, 'db', jsHelper)
|
|
407
|
+
|
|
408
|
+
if (createMigrations) {
|
|
409
|
+
if (typescript) {
|
|
410
|
+
await writeFile(join(currentDir, 'test', 'routes', 'movies.test.ts'), moviesTestTS)
|
|
411
|
+
} else {
|
|
412
|
+
await writeFile(join(currentDir, 'test', 'routes', 'movies.test.js'), moviesTestJS)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
205
415
|
}
|
|
206
416
|
|
|
207
417
|
const output = {
|
package/src/ghaction.mjs
CHANGED
|
@@ -176,7 +176,7 @@ export const askStaticWorkspaceGHAction = async (logger, env, type, buildTS, pro
|
|
|
176
176
|
{
|
|
177
177
|
type: 'list',
|
|
178
178
|
name: 'githubAction',
|
|
179
|
-
message: 'Do you want to create the github action to deploy this application to Platformatic Cloud
|
|
179
|
+
message: 'Do you want to create the github action to deploy this application to Platformatic Cloud?',
|
|
180
180
|
default: true,
|
|
181
181
|
choices: [{ name: 'yes', value: true }, { name: 'no', value: false }]
|
|
182
182
|
}
|
|
@@ -114,8 +114,8 @@ export async function createPlatformaticRuntime (_args) {
|
|
|
114
114
|
|
|
115
115
|
const env = await createRuntime(logger, projectDir, version, servicesDir, entrypoint)
|
|
116
116
|
|
|
117
|
-
await askDynamicWorkspaceCreateGHAction(logger, env, 'service', false, projectDir)
|
|
118
117
|
await askStaticWorkspaceGHAction(logger, env, 'service', false, projectDir)
|
|
118
|
+
await askDynamicWorkspaceCreateGHAction(logger, env, 'service', false, projectDir)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
export async function createRuntimeService ({ servicesDir, names, logger }) {
|
|
@@ -78,8 +78,11 @@ const createPlatformaticService = async (_args, opts = {}) => {
|
|
|
78
78
|
const fastifyVersion = await getDependencyVersion('fastify')
|
|
79
79
|
|
|
80
80
|
if (!opts.skipPackageJson) {
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
const test = useTypescript ? 'tsc && node --test dist/test/*/*.test.js' : 'node --test test/*/*.test.js'
|
|
82
|
+
await createPackageJson(version, fastifyVersion, logger, projectDir, useTypescript, {
|
|
83
|
+
test
|
|
84
|
+
}, {
|
|
85
|
+
'@platformatic/service': `^${version}`
|
|
83
86
|
})
|
|
84
87
|
}
|
|
85
88
|
if (!opts.skipGitignore) {
|
|
@@ -103,8 +106,8 @@ const createPlatformaticService = async (_args, opts = {}) => {
|
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
if (!opts.skipGitHubActions) {
|
|
106
|
-
await askDynamicWorkspaceCreateGHAction(logger, env, 'service', useTypescript, projectDir)
|
|
107
109
|
await askStaticWorkspaceGHAction(logger, env, 'service', useTypescript, projectDir)
|
|
110
|
+
await askDynamicWorkspaceCreateGHAction(logger, env, 'service', useTypescript, projectDir)
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { writeFile,
|
|
1
|
+
import { writeFile, readFile, appendFile } from 'fs/promises'
|
|
2
2
|
import { join } from 'path'
|
|
3
3
|
import * as desm from 'desm'
|
|
4
4
|
import { findServiceConfigFile, isFileAccessible } from '../utils.mjs'
|
|
5
5
|
import { getTsConfig } from '../get-tsconfig.mjs'
|
|
6
|
+
import { generatePlugins } from '../create-plugins.mjs'
|
|
6
7
|
|
|
7
8
|
const TS_OUT_DIR = 'dist'
|
|
8
9
|
|
|
@@ -55,76 +56,6 @@ PLT_TYPESCRIPT=true
|
|
|
55
56
|
return env
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
const JS_PLUGIN_WITH_TYPES_SUPPORT = `\
|
|
59
|
-
/// <reference path="../global.d.ts" />
|
|
60
|
-
'use strict'
|
|
61
|
-
/** @param {import('fastify').FastifyInstance} fastify */
|
|
62
|
-
module.exports = async function (fastify, opts) {
|
|
63
|
-
fastify.decorate('example', 'foobar')
|
|
64
|
-
}
|
|
65
|
-
`
|
|
66
|
-
|
|
67
|
-
const TS_PLUGIN_WITH_TYPES_SUPPORT = `\
|
|
68
|
-
/// <reference path="../global.d.ts" />
|
|
69
|
-
import { FastifyInstance, FastifyPluginOptions } from 'fastify'
|
|
70
|
-
|
|
71
|
-
export default async function (fastify: FastifyInstance, opts: FastifyPluginOptions) {
|
|
72
|
-
fastify.decorate('example', 'foobar')
|
|
73
|
-
}
|
|
74
|
-
`
|
|
75
|
-
|
|
76
|
-
const JS_ROUTES_WITH_TYPES_SUPPORT = `\
|
|
77
|
-
/// <reference path="../global.d.ts" />
|
|
78
|
-
'use strict'
|
|
79
|
-
/** @param {import('fastify').FastifyInstance} fastify */
|
|
80
|
-
module.exports = async function (fastify, opts) {
|
|
81
|
-
fastify.get('/', async (request, reply) => {
|
|
82
|
-
return { hello: fastify.example }
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
`
|
|
86
|
-
|
|
87
|
-
const TS_ROUTES_WITH_TYPES_SUPPORT = `\
|
|
88
|
-
/// <reference path="../global.d.ts" />
|
|
89
|
-
import { FastifyInstance, FastifyPluginOptions } from 'fastify'
|
|
90
|
-
|
|
91
|
-
declare module 'fastify' {
|
|
92
|
-
interface FastifyInstance {
|
|
93
|
-
example: string
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export default async function (fastify: FastifyInstance, opts: FastifyPluginOptions) {
|
|
98
|
-
fastify.get('/', async (request, reply) => {
|
|
99
|
-
return { hello: fastify.example }
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
`
|
|
103
|
-
|
|
104
|
-
async function generatePluginWithTypesSupport (logger, currentDir, isTypescript) {
|
|
105
|
-
await mkdir(join(currentDir, 'plugins'))
|
|
106
|
-
const pluginTemplate = isTypescript
|
|
107
|
-
? TS_PLUGIN_WITH_TYPES_SUPPORT
|
|
108
|
-
: JS_PLUGIN_WITH_TYPES_SUPPORT
|
|
109
|
-
const pluginName = isTypescript
|
|
110
|
-
? 'example.ts'
|
|
111
|
-
: 'example.js'
|
|
112
|
-
await writeFile(join(currentDir, 'plugins', pluginName), pluginTemplate)
|
|
113
|
-
logger.info('Plugins folder "plugins" successfully created.')
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function generateRouteWithTypesSupport (logger, currentDir, isTypescript) {
|
|
117
|
-
await mkdir(join(currentDir, 'routes'))
|
|
118
|
-
const routesTemplate = isTypescript
|
|
119
|
-
? TS_ROUTES_WITH_TYPES_SUPPORT
|
|
120
|
-
: JS_ROUTES_WITH_TYPES_SUPPORT
|
|
121
|
-
const routesName = isTypescript
|
|
122
|
-
? 'root.ts'
|
|
123
|
-
: 'root.js'
|
|
124
|
-
await writeFile(join(currentDir, 'routes', routesName), routesTemplate)
|
|
125
|
-
logger.info('Routes folder "routes" successfully created.')
|
|
126
|
-
}
|
|
127
|
-
|
|
128
59
|
async function createService ({ hostname, port, typescript = false }, logger, currentDir = process.cwd(), version) {
|
|
129
60
|
if (!version) {
|
|
130
61
|
const pkg = await readFile(desm.join(import.meta.url, '..', '..', 'package.json'))
|
|
@@ -167,19 +98,7 @@ async function createService ({ hostname, port, typescript = false }, logger, cu
|
|
|
167
98
|
}
|
|
168
99
|
}
|
|
169
100
|
|
|
170
|
-
|
|
171
|
-
if (!pluginFolderExists) {
|
|
172
|
-
await generatePluginWithTypesSupport(logger, currentDir, typescript)
|
|
173
|
-
} else {
|
|
174
|
-
logger.info('Plugins folder "plugins" found, skipping creation of plugins folder.')
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const routeFolderExists = await isFileAccessible('routes', currentDir)
|
|
178
|
-
if (!routeFolderExists) {
|
|
179
|
-
await generateRouteWithTypesSupport(logger, currentDir, typescript)
|
|
180
|
-
} else {
|
|
181
|
-
logger.info('Routes folder "routes" found, skipping creation of routes folder.')
|
|
182
|
-
}
|
|
101
|
+
await generatePlugins(logger, currentDir, typescript, 'service')
|
|
183
102
|
|
|
184
103
|
const output = {
|
|
185
104
|
PLT_SERVER_LOGGER_LEVEL: 'info',
|
|
@@ -88,7 +88,8 @@ test('creates project with no typescript', async ({ equal }) => {
|
|
|
88
88
|
const migrationFileUndo = readFileSync(pathToMigrationFileUndo, 'utf8')
|
|
89
89
|
equal(migrationFileUndo, moviesMigrationUndo)
|
|
90
90
|
|
|
91
|
-
equal(await isFileAccessible(join(tmpDir, '
|
|
91
|
+
equal(await isFileAccessible(join(tmpDir, 'routes', 'root.js')), true)
|
|
92
|
+
equal(await isFileAccessible(join(tmpDir, 'plugins', 'example.js')), true)
|
|
92
93
|
})
|
|
93
94
|
|
|
94
95
|
test('creates project with no typescript and no plugin', async ({ equal }) => {
|
|
@@ -202,9 +203,15 @@ test('creates project with typescript', async ({ equal, same }) => {
|
|
|
202
203
|
const migrationFileUndo = readFileSync(pathToMigrationFileUndo, 'utf8')
|
|
203
204
|
equal(migrationFileUndo, moviesMigrationUndo)
|
|
204
205
|
|
|
205
|
-
same(plugins.paths, [
|
|
206
|
+
same(plugins.paths, [{
|
|
207
|
+
path: './plugins',
|
|
208
|
+
encapsulate: false
|
|
209
|
+
}, {
|
|
210
|
+
path: './routes'
|
|
211
|
+
}])
|
|
206
212
|
equal(plugins.typescript, '{PLT_TYPESCRIPT}')
|
|
207
|
-
equal(await isFileAccessible(join(tmpDir, '
|
|
213
|
+
equal(await isFileAccessible(join(tmpDir, 'plugins', 'example.ts')), true)
|
|
214
|
+
equal(await isFileAccessible(join(tmpDir, 'routes', 'root.ts')), true)
|
|
208
215
|
equal(await isFileAccessible(join(tmpDir, 'tsconfig.json')), true)
|
|
209
216
|
})
|
|
210
217
|
|
|
@@ -256,7 +263,8 @@ test('creates project with tsconfig already present', async ({ ok }) => {
|
|
|
256
263
|
})
|
|
257
264
|
|
|
258
265
|
test('creates project with plugin already present', async ({ ok }) => {
|
|
259
|
-
const pathToPlugin = join(tmpDir, '
|
|
266
|
+
const pathToPlugin = join(tmpDir, 'routes', 'root.js')
|
|
267
|
+
mkdirSync(join(tmpDir, 'routes'))
|
|
260
268
|
writeFileSync(pathToPlugin, 'test')
|
|
261
269
|
const params = {
|
|
262
270
|
hostname: 'myhost',
|
|
@@ -265,7 +273,7 @@ test('creates project with plugin already present', async ({ ok }) => {
|
|
|
265
273
|
types: true
|
|
266
274
|
}
|
|
267
275
|
await createDB(params, fakeLogger, tmpDir)
|
|
268
|
-
ok(log.includes(
|
|
276
|
+
ok(log.includes('Routes folder "routes" found, skipping creation of routes folder.'))
|
|
269
277
|
})
|
|
270
278
|
|
|
271
279
|
test('creates project with no default migrations', async ({ notOk }) => {
|
|
@@ -68,6 +68,10 @@ test('creates service with typescript', async ({ equal, same, ok }) => {
|
|
|
68
68
|
ok(await isFileAccessible(join(tmpDir, 'tsconfig.json')))
|
|
69
69
|
ok(await isFileAccessible(join(tmpDir, 'plugins', 'example.ts')))
|
|
70
70
|
ok(await isFileAccessible(join(tmpDir, 'routes', 'root.ts')))
|
|
71
|
+
|
|
72
|
+
ok(await isFileAccessible(join(tmpDir, 'test', 'plugins', 'example.test.ts')))
|
|
73
|
+
ok(await isFileAccessible(join(tmpDir, 'test', 'routes', 'root.test.ts')))
|
|
74
|
+
ok(await isFileAccessible(join(tmpDir, 'test', 'helper.ts')))
|
|
71
75
|
})
|
|
72
76
|
|
|
73
77
|
test('creates service with javascript', async ({ equal, same, ok }) => {
|
|
@@ -101,6 +105,10 @@ test('creates service with javascript', async ({ equal, same, ok }) => {
|
|
|
101
105
|
same(plugins, { paths: [{ path: './plugins', encapsulate: false }, './routes'] })
|
|
102
106
|
ok(await isFileAccessible(join(tmpDir, 'plugins', 'example.js')))
|
|
103
107
|
ok(await isFileAccessible(join(tmpDir, 'routes', 'root.js')))
|
|
108
|
+
|
|
109
|
+
ok(await isFileAccessible(join(tmpDir, 'test', 'plugins', 'example.test.js')))
|
|
110
|
+
ok(await isFileAccessible(join(tmpDir, 'test', 'routes', 'root.test.js')))
|
|
111
|
+
ok(await isFileAccessible(join(tmpDir, 'test', 'helper.js')))
|
|
104
112
|
})
|
|
105
113
|
|
|
106
114
|
test('creates project with configuration already present', async ({ ok }) => {
|