create-platformatic 2.63.3 → 2.64.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/create-platformatic.mjs +2 -1
- package/package.json +8 -10
- package/src/index.mjs +99 -68
- package/src/utils.mjs +31 -0
- package/src/colors.mjs +0 -4
- package/src/say.mjs +0 -29
package/create-platformatic.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-platformatic",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.64.0",
|
|
4
4
|
"description": "Create platformatic application interactive tool",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"license": "Apache-2.0",
|
|
16
16
|
"author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"chalk": "^5.3.0",
|
|
19
18
|
"columnify": "^1.6.0",
|
|
20
19
|
"commist": "^3.2.0",
|
|
21
20
|
"desm": "^1.3.1",
|
|
@@ -23,7 +22,6 @@
|
|
|
23
22
|
"execa": "^9.0.0",
|
|
24
23
|
"help-me": "^5.0.0",
|
|
25
24
|
"inquirer": "^9.2.16",
|
|
26
|
-
"log-update": "^6.0.0",
|
|
27
25
|
"minimist": "^1.2.8",
|
|
28
26
|
"ora": "^6.3.1",
|
|
29
27
|
"pino": "^9.0.0",
|
|
@@ -33,9 +31,9 @@
|
|
|
33
31
|
"strip-ansi": "^7.1.0",
|
|
34
32
|
"undici": "^7.0.0",
|
|
35
33
|
"which": "^3.0.1",
|
|
36
|
-
"@platformatic/
|
|
37
|
-
"@platformatic/
|
|
38
|
-
"@platformatic/
|
|
34
|
+
"@platformatic/generators": "2.64.0",
|
|
35
|
+
"@platformatic/utils": "2.64.0",
|
|
36
|
+
"@platformatic/config": "2.64.0"
|
|
39
37
|
},
|
|
40
38
|
"devDependencies": {
|
|
41
39
|
"@types/node": "^22.5.0",
|
|
@@ -50,10 +48,10 @@
|
|
|
50
48
|
"neostandard": "^0.12.0",
|
|
51
49
|
"typescript": "~5.8.0",
|
|
52
50
|
"yaml": "^2.4.1",
|
|
53
|
-
"@platformatic/composer": "2.
|
|
54
|
-
"@platformatic/db": "2.
|
|
55
|
-
"@platformatic/runtime": "2.
|
|
56
|
-
"@platformatic/service": "2.
|
|
51
|
+
"@platformatic/composer": "2.64.0",
|
|
52
|
+
"@platformatic/db": "2.64.0",
|
|
53
|
+
"@platformatic/runtime": "2.64.0",
|
|
54
|
+
"@platformatic/service": "2.64.0"
|
|
57
55
|
},
|
|
58
56
|
"scripts": {
|
|
59
57
|
"test:cli": "borp --pattern \"test/cli/*test.mjs\" --timeout=300000 --concurrency=1",
|
package/src/index.mjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { ConfigManager } from '@platformatic/config'
|
|
2
|
-
import { createDirectory, generateDashedName, getPkgManager } from '@platformatic/utils'
|
|
2
|
+
import { createDirectory, executeWithTimeout, generateDashedName, getPkgManager } from '@platformatic/utils'
|
|
3
3
|
import { execa } from 'execa'
|
|
4
|
-
import
|
|
4
|
+
import defaultInquirer from 'inquirer'
|
|
5
5
|
import parseArgs from 'minimist'
|
|
6
6
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
7
|
-
import
|
|
8
|
-
import { setTimeout } from 'node:timers/promises'
|
|
7
|
+
import { basename, join, resolve as pathResolve } from 'node:path'
|
|
9
8
|
import { pathToFileURL } from 'node:url'
|
|
10
9
|
import ora from 'ora'
|
|
11
10
|
import pino from 'pino'
|
|
@@ -13,40 +12,41 @@ import pretty from 'pino-pretty'
|
|
|
13
12
|
import resolve from 'resolve'
|
|
14
13
|
import { request } from 'undici'
|
|
15
14
|
import { createGitRepository } from './create-git-repository.mjs'
|
|
16
|
-
import { say } from './
|
|
17
|
-
import { getUsername, getVersion } from './utils.mjs'
|
|
18
|
-
|
|
15
|
+
import { getUsername, getVersion, say } from './utils.mjs'
|
|
19
16
|
const MARKETPLACE_HOST = 'https://marketplace.platformatic.dev'
|
|
20
|
-
const defaultStackables = ['@platformatic/
|
|
17
|
+
const defaultStackables = ['@platformatic/service', '@platformatic/composer', '@platformatic/db']
|
|
18
|
+
|
|
19
|
+
export async function fetchStackables (marketplaceHost, modules = []) {
|
|
20
|
+
const stackables = new Set([...modules, ...defaultStackables])
|
|
21
21
|
|
|
22
|
-
export async function fetchStackables (marketplaceHost) {
|
|
23
22
|
// Skip the remote network request if we are running tests
|
|
24
23
|
if (process.env.MARKETPLACE_TEST) {
|
|
25
|
-
return
|
|
24
|
+
return Array.from(stackables)
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const stackablesRequest = request(marketplaceHost + '/templates')
|
|
31
|
-
const stackablesRequestTimeout = setTimeout(5000, new Error('Request timed out'))
|
|
32
|
-
|
|
27
|
+
let response
|
|
33
28
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
response = await executeWithTimeout(request(new URL('/templates', marketplaceHost || MARKETPLACE_HOST)), 5000)
|
|
30
|
+
} catch (err) {
|
|
31
|
+
// No-op: we just use the default stackables
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (response && response.statusCode === 200) {
|
|
35
|
+
for (const stackable of await response.body.json()) {
|
|
36
|
+
stackables.add(stackable.name)
|
|
37
37
|
}
|
|
38
|
-
}
|
|
38
|
+
}
|
|
39
39
|
|
|
40
|
-
return
|
|
40
|
+
return Array.from(stackables)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export async function chooseStackable (stackables) {
|
|
43
|
+
export async function chooseStackable (inquirer, stackables) {
|
|
44
44
|
const options = await inquirer.prompt({
|
|
45
45
|
type: 'list',
|
|
46
46
|
name: 'type',
|
|
47
|
-
message: 'Which kind of
|
|
48
|
-
default: stackables
|
|
49
|
-
choices: stackables
|
|
47
|
+
message: 'Which kind of service do you want to create?',
|
|
48
|
+
default: stackables[0],
|
|
49
|
+
choices: stackables
|
|
50
50
|
})
|
|
51
51
|
|
|
52
52
|
return options.type
|
|
@@ -59,7 +59,9 @@ async function importOrLocal ({ pkgManager, name, projectDir, pkg }) {
|
|
|
59
59
|
try {
|
|
60
60
|
const fileToImport = resolve.sync(pkg, { basedir: projectDir })
|
|
61
61
|
return await import(pathToFileURL(fileToImport))
|
|
62
|
-
} catch {
|
|
62
|
+
} catch {
|
|
63
|
+
// No-op
|
|
64
|
+
}
|
|
63
65
|
|
|
64
66
|
let version = ''
|
|
65
67
|
|
|
@@ -84,13 +86,14 @@ async function importOrLocal ({ pkgManager, name, projectDir, pkg }) {
|
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
export
|
|
89
|
+
export async function createPlatformatic (argv) {
|
|
88
90
|
const args = parseArgs(argv, {
|
|
89
91
|
default: {
|
|
90
92
|
install: true,
|
|
93
|
+
module: []
|
|
91
94
|
},
|
|
92
95
|
boolean: ['install'],
|
|
93
|
-
string: ['global-config', 'marketplace-host']
|
|
96
|
+
string: ['global-config', 'marketplace-host', 'module']
|
|
94
97
|
})
|
|
95
98
|
|
|
96
99
|
const username = await getUsername()
|
|
@@ -101,65 +104,78 @@ export const createPlatformatic = async argv => {
|
|
|
101
104
|
const logger = pino(
|
|
102
105
|
pretty({
|
|
103
106
|
translateTime: 'SYS:HH:MM:ss',
|
|
104
|
-
ignore: 'hostname,pid'
|
|
107
|
+
ignore: 'hostname,pid'
|
|
105
108
|
})
|
|
106
109
|
)
|
|
107
110
|
|
|
108
111
|
const pkgManager = getPkgManager()
|
|
109
|
-
|
|
112
|
+
const modules = Array.isArray(args.module) ? args.module : [args.module]
|
|
113
|
+
await createApplication(logger, pkgManager, modules, args['marketplace-host'], args['install'])
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
async function createApplication (
|
|
116
|
+
export async function createApplication (
|
|
117
|
+
logger,
|
|
118
|
+
packageManager,
|
|
119
|
+
modules,
|
|
120
|
+
marketplaceHost,
|
|
121
|
+
install,
|
|
122
|
+
additionalGeneratorOptions = {}
|
|
123
|
+
) {
|
|
124
|
+
// This is only used for testing for now, but might be useful in the future
|
|
125
|
+
const inquirer = process.env.INQUIRER_PATH ? await import(process.env.INQUIRER_PATH) : defaultInquirer
|
|
126
|
+
|
|
113
127
|
let projectDir = process.cwd()
|
|
114
128
|
if (!(await ConfigManager.findConfigFile())) {
|
|
115
129
|
const optionsDir = await inquirer.prompt({
|
|
116
130
|
type: 'input',
|
|
117
131
|
name: 'dir',
|
|
118
132
|
message: 'Where would you like to create your project?',
|
|
119
|
-
default: 'platformatic'
|
|
133
|
+
default: 'platformatic'
|
|
120
134
|
})
|
|
121
135
|
|
|
122
|
-
projectDir =
|
|
136
|
+
projectDir = pathResolve(process.cwd(), optionsDir.dir)
|
|
123
137
|
}
|
|
124
138
|
const projectName = basename(projectDir)
|
|
125
139
|
|
|
126
140
|
await createDirectory(projectDir)
|
|
127
141
|
|
|
128
142
|
const runtime = await importOrLocal({
|
|
129
|
-
pkgManager,
|
|
143
|
+
pkgManager: packageManager,
|
|
130
144
|
name: projectName,
|
|
131
145
|
projectDir,
|
|
132
|
-
pkg: '@platformatic/runtime'
|
|
146
|
+
pkg: '@platformatic/runtime'
|
|
133
147
|
})
|
|
134
148
|
|
|
135
149
|
const generator = new runtime.Generator({
|
|
136
150
|
logger,
|
|
137
151
|
name: projectName,
|
|
138
152
|
inquirer,
|
|
153
|
+
...additionalGeneratorOptions
|
|
139
154
|
})
|
|
155
|
+
|
|
140
156
|
generator.setConfig({
|
|
141
157
|
...generator.config,
|
|
142
|
-
targetDirectory: projectDir
|
|
158
|
+
targetDirectory: projectDir
|
|
143
159
|
})
|
|
144
160
|
|
|
145
161
|
await generator.populateFromExistingConfig()
|
|
146
162
|
if (generator.existingConfig) {
|
|
147
|
-
await say('Using existing configuration')
|
|
163
|
+
await say('Using existing configuration ...')
|
|
148
164
|
}
|
|
149
165
|
|
|
150
|
-
const stackables = await fetchStackables(
|
|
166
|
+
const stackables = await fetchStackables(marketplaceHost, modules)
|
|
151
167
|
|
|
152
|
-
const names = []
|
|
168
|
+
const names = generator.existingServices ?? []
|
|
153
169
|
|
|
154
170
|
while (true) {
|
|
155
|
-
const stackableName = await chooseStackable(stackables)
|
|
171
|
+
const stackableName = await chooseStackable(inquirer, stackables)
|
|
156
172
|
// await say(`Creating a ${stackable} project in ${projectDir}...`)
|
|
157
173
|
|
|
158
174
|
const stackable = await importOrLocal({
|
|
159
|
-
pkgManager,
|
|
175
|
+
pkgManager: packageManager,
|
|
160
176
|
name: projectName,
|
|
161
177
|
projectDir,
|
|
162
|
-
pkg: stackableName
|
|
178
|
+
pkg: stackableName
|
|
163
179
|
})
|
|
164
180
|
|
|
165
181
|
const { serviceName } = await inquirer.prompt({
|
|
@@ -181,19 +197,19 @@ async function createApplication (args, logger, pkgManager) {
|
|
|
181
197
|
}
|
|
182
198
|
|
|
183
199
|
return true
|
|
184
|
-
}
|
|
200
|
+
}
|
|
185
201
|
})
|
|
186
202
|
|
|
187
203
|
names.push(serviceName)
|
|
188
204
|
|
|
189
205
|
const stackableGenerator = new stackable.Generator({
|
|
190
206
|
logger,
|
|
191
|
-
inquirer
|
|
207
|
+
inquirer
|
|
192
208
|
})
|
|
193
209
|
|
|
194
210
|
stackableGenerator.setConfig({
|
|
195
211
|
...stackableGenerator.config,
|
|
196
|
-
serviceName
|
|
212
|
+
serviceName
|
|
197
213
|
})
|
|
198
214
|
|
|
199
215
|
generator.addService(stackableGenerator, serviceName)
|
|
@@ -208,9 +224,9 @@ async function createApplication (args, logger, pkgManager) {
|
|
|
208
224
|
default: false,
|
|
209
225
|
choices: [
|
|
210
226
|
{ name: 'yes', value: false },
|
|
211
|
-
{ name: 'no', value: true }
|
|
212
|
-
]
|
|
213
|
-
}
|
|
227
|
+
{ name: 'no', value: true }
|
|
228
|
+
]
|
|
229
|
+
}
|
|
214
230
|
])
|
|
215
231
|
|
|
216
232
|
if (shouldBreak) {
|
|
@@ -219,15 +235,16 @@ async function createApplication (args, logger, pkgManager) {
|
|
|
219
235
|
}
|
|
220
236
|
|
|
221
237
|
let entrypoint = ''
|
|
238
|
+
const chooseEntrypoint = names.length > 1 && (!generator.existingConfigRaw || !generator.existingConfigRaw.entrypoint)
|
|
222
239
|
|
|
223
|
-
if (
|
|
240
|
+
if (chooseEntrypoint) {
|
|
224
241
|
const results = await inquirer.prompt([
|
|
225
242
|
{
|
|
226
243
|
type: 'list',
|
|
227
244
|
name: 'entrypoint',
|
|
228
245
|
message: 'Which service should be exposed?',
|
|
229
|
-
choices: names.map(name => ({ name, value: name }))
|
|
230
|
-
}
|
|
246
|
+
choices: names.map(name => ({ name, value: name }))
|
|
247
|
+
}
|
|
231
248
|
])
|
|
232
249
|
entrypoint = results.entrypoint
|
|
233
250
|
} else {
|
|
@@ -238,40 +255,54 @@ async function createApplication (args, logger, pkgManager) {
|
|
|
238
255
|
|
|
239
256
|
await generator.ask()
|
|
240
257
|
await generator.prepare()
|
|
258
|
+
|
|
259
|
+
if (chooseEntrypoint) {
|
|
260
|
+
// This can return null if the generator was not supposed to modify the config
|
|
261
|
+
const configObject = generator.getFileObject(generator.runtimeConfig)
|
|
262
|
+
const config = configObject ? JSON.parse(configObject.contents) : generator.existingConfigRaw
|
|
263
|
+
config.entrypoint = entrypoint
|
|
264
|
+
|
|
265
|
+
generator.addFile({ path: '', file: generator.runtimeConfig, contents: JSON.stringify(config, null, 2) })
|
|
266
|
+
}
|
|
267
|
+
|
|
241
268
|
await generator.writeFiles()
|
|
242
269
|
|
|
243
270
|
// Create project here
|
|
271
|
+
if (!generator.existingConfigRaw) {
|
|
272
|
+
const { initGitRepository } = await inquirer.prompt({
|
|
273
|
+
type: 'list',
|
|
274
|
+
name: 'initGitRepository',
|
|
275
|
+
message: 'Do you want to init the git repository?',
|
|
276
|
+
default: false,
|
|
277
|
+
choices: [
|
|
278
|
+
{ name: 'yes', value: true },
|
|
279
|
+
{ name: 'no', value: false }
|
|
280
|
+
]
|
|
281
|
+
})
|
|
244
282
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
message: 'Do you want to init the git repository?',
|
|
249
|
-
default: false,
|
|
250
|
-
choices: [
|
|
251
|
-
{ name: 'yes', value: true },
|
|
252
|
-
{ name: 'no', value: false },
|
|
253
|
-
],
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
if (initGitRepository) {
|
|
257
|
-
await createGitRepository(logger, projectDir)
|
|
283
|
+
if (initGitRepository) {
|
|
284
|
+
await createGitRepository(logger, projectDir)
|
|
285
|
+
}
|
|
258
286
|
}
|
|
259
287
|
|
|
260
|
-
if (
|
|
288
|
+
if (packageManager === 'pnpm') {
|
|
261
289
|
// add pnpm-workspace.yaml file if needed
|
|
262
290
|
const content = `packages:
|
|
263
291
|
# all packages in direct subdirs of packages/
|
|
264
|
-
- 'services/*'
|
|
292
|
+
- 'services/*'
|
|
293
|
+
- 'web/*'`
|
|
265
294
|
await writeFile(join(projectDir, 'pnpm-workspace.yaml'), content)
|
|
266
295
|
}
|
|
267
296
|
|
|
268
|
-
if (
|
|
297
|
+
if (typeof install === 'function') {
|
|
298
|
+
await install(projectDir, generator.runtimeConfig, packageManager)
|
|
299
|
+
} else if (install) {
|
|
269
300
|
const spinner = ora('Installing dependencies...').start()
|
|
270
|
-
await execa(
|
|
301
|
+
await execa(packageManager, ['install'], { cwd: projectDir })
|
|
271
302
|
spinner.succeed()
|
|
272
303
|
}
|
|
273
304
|
|
|
274
305
|
logger.info('Project created successfully, executing post-install actions...')
|
|
275
306
|
await generator.postInstallActions()
|
|
276
|
-
logger.info(
|
|
307
|
+
logger.info(`You are all set! Run \`${packageManager} start\` to start your project.`)
|
|
277
308
|
}
|
package/src/utils.mjs
CHANGED
|
@@ -11,6 +11,14 @@ export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
|
11
11
|
export const randomBetween = (min, max) => Math.floor(Math.random() * (max - min + 1) + min)
|
|
12
12
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
|
13
13
|
|
|
14
|
+
const ansiCodes = {
|
|
15
|
+
// Platformatic Green: #21FA90
|
|
16
|
+
pltGreen: '\u001B[38;2;33;250;144m',
|
|
17
|
+
bell: '\u0007',
|
|
18
|
+
reset: '\u001b[0m',
|
|
19
|
+
erasePreviousLine: '\u001b[1K',
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
export async function isFileAccessible (filename, directory) {
|
|
15
23
|
try {
|
|
16
24
|
const filePath = directory ? resolve(directory, filename) : filename
|
|
@@ -118,6 +126,7 @@ export const getDependencyVersion = async dependencyName => {
|
|
|
118
126
|
export function convertServiceNameToPrefix (serviceName) {
|
|
119
127
|
return serviceName.replace(/-/g, '_').toUpperCase()
|
|
120
128
|
}
|
|
129
|
+
|
|
121
130
|
export function addPrefixToEnv (env, prefix) {
|
|
122
131
|
const output = {}
|
|
123
132
|
Object.entries(env).forEach(([key, value]) => {
|
|
@@ -125,3 +134,25 @@ export function addPrefixToEnv (env, prefix) {
|
|
|
125
134
|
})
|
|
126
135
|
return output
|
|
127
136
|
}
|
|
137
|
+
|
|
138
|
+
export async function say (message) {
|
|
139
|
+
// Disable if not supporting colors
|
|
140
|
+
if (process.env.NO_COLOR) {
|
|
141
|
+
console.log(message)
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const words = message.split(' ')
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i <= words.length; i++) {
|
|
148
|
+
if (i > 0) {
|
|
149
|
+
process.stdout.write('\r' + ansiCodes.erasePreviousLine)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
process.stdout.write(ansiCodes.pltGreen + words.slice(0, i).join(' ') + ansiCodes.reset + ansiCodes.bell)
|
|
153
|
+
await sleep(randomBetween(75, 100))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
process.stdout.write('\n')
|
|
157
|
+
await sleep(randomBetween(75, 200))
|
|
158
|
+
}
|
package/src/colors.mjs
DELETED
package/src/say.mjs
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import logUpdate from 'log-update'
|
|
2
|
-
import { pltGreen } from './colors.mjs'
|
|
3
|
-
import { randomBetween, sleep } from './utils.mjs'
|
|
4
|
-
|
|
5
|
-
export async function say (messages) {
|
|
6
|
-
const _messages = Array.isArray(messages) ? messages : [messages]
|
|
7
|
-
|
|
8
|
-
if (process.env.NO_COLOR) {
|
|
9
|
-
for (const message of _messages) {
|
|
10
|
-
console.log(message)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
logUpdate.done()
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
for (const message of _messages) {
|
|
18
|
-
const _message = Array.isArray(message) ? message : message.split(' ')
|
|
19
|
-
const msg = []
|
|
20
|
-
for (const word of [''].concat(_message)) {
|
|
21
|
-
msg.push(word)
|
|
22
|
-
logUpdate(pltGreen(msg.join(' ')))
|
|
23
|
-
await sleep(randomBetween(75, 100))
|
|
24
|
-
process.stdout.write('\u0007') // Do we want to enable terminal bell?
|
|
25
|
-
}
|
|
26
|
-
await sleep(randomBetween(75, 200))
|
|
27
|
-
}
|
|
28
|
-
logUpdate.done()
|
|
29
|
-
}
|