create-platformatic 2.74.3 → 3.0.0-alpha.4
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/README.md +1 -1
- package/bin/cli.js +23 -0
- package/eslint.config.js +2 -2
- package/package.json +13 -42
- package/test/index.test.js +15 -0
- package/test/loader.js +13 -0
- package/create-platformatic.mjs +0 -27
- package/src/create-git-repository.mjs +0 -108
- package/src/index.mjs +0 -513
- package/src/utils.mjs +0 -158
package/README.md
CHANGED
package/bin/cli.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { checkNodeVersionForServices } from '@platformatic/foundation'
|
|
4
|
+
import { createPlatformatic } from 'create-wattpm'
|
|
5
|
+
import parseArgs from 'minimist'
|
|
6
|
+
import { readFile } from 'node:fs/promises'
|
|
7
|
+
import { join } from 'node:path'
|
|
8
|
+
|
|
9
|
+
checkNodeVersionForServices()
|
|
10
|
+
|
|
11
|
+
const _args = process.argv.slice(2)
|
|
12
|
+
const args = parseArgs(_args, {
|
|
13
|
+
alias: {
|
|
14
|
+
v: 'version'
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
if (args.version) {
|
|
19
|
+
console.log('v' + JSON.parse(await readFile(join(import.meta.dirname, 'package.json'), 'utf8')).version)
|
|
20
|
+
process.exit(0)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await createPlatformatic(_args)
|
package/eslint.config.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import neostandard from 'neostandard'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export default neostandard()
|
package/package.json
CHANGED
|
@@ -1,62 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-platformatic",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-alpha.4",
|
|
4
4
|
"description": "Create platformatic application interactive tool",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/platformatic/platformatic.git"
|
|
8
8
|
},
|
|
9
|
-
"exports": {
|
|
10
|
-
".": "./create-platformatic.mjs"
|
|
11
|
-
},
|
|
12
9
|
"bin": {
|
|
13
|
-
"create-platformatic": "./
|
|
10
|
+
"create-platformatic": "./bin/cli.js"
|
|
14
11
|
},
|
|
12
|
+
"type": "module",
|
|
15
13
|
"license": "Apache-2.0",
|
|
16
14
|
"author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
|
|
17
15
|
"dependencies": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"desm": "^1.3.1",
|
|
21
|
-
"es-main": "^1.3.0",
|
|
22
|
-
"execa": "^9.0.0",
|
|
23
|
-
"help-me": "^5.0.0",
|
|
24
|
-
"inquirer": "^9.2.16",
|
|
25
|
-
"minimist": "^1.2.8",
|
|
26
|
-
"ora": "^6.3.1",
|
|
27
|
-
"pino": "^9.0.0",
|
|
28
|
-
"pino-pretty": "^13.0.0",
|
|
29
|
-
"resolve": "^1.22.8",
|
|
30
|
-
"semver": "^7.6.0",
|
|
31
|
-
"strip-ansi": "^7.1.0",
|
|
32
|
-
"undici": "^7.0.0",
|
|
33
|
-
"which": "^3.0.1",
|
|
34
|
-
"@platformatic/generators": "2.74.3",
|
|
35
|
-
"@platformatic/config": "2.74.3",
|
|
36
|
-
"@platformatic/utils": "2.74.3"
|
|
16
|
+
"@platformatic/foundation": "3.0.0-alpha.4",
|
|
17
|
+
"create-wattpm": "3.0.0-alpha.4"
|
|
37
18
|
},
|
|
38
19
|
"devDependencies": {
|
|
39
|
-
"@types/node": "^22.5.0",
|
|
40
|
-
"ajv": "^8.12.0",
|
|
41
20
|
"borp": "^0.20.0",
|
|
42
|
-
"c8": "^10.0.0",
|
|
43
|
-
"cross-env": "^7.0.3",
|
|
44
|
-
"dotenv": "^16.4.5",
|
|
45
21
|
"eslint": "9",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
"@platformatic/composer": "2.74.3",
|
|
52
|
-
"@platformatic/runtime": "2.74.3",
|
|
53
|
-
"@platformatic/service": "2.74.3",
|
|
54
|
-
"@platformatic/db": "2.74.3"
|
|
22
|
+
"import-in-the-middle": "^1.14.2",
|
|
23
|
+
"neostandard": "^0.12.0"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22.18.0"
|
|
55
27
|
},
|
|
56
28
|
"scripts": {
|
|
57
|
-
"test
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"lint": "eslint"
|
|
29
|
+
"test": "pnpm run lint && borp --timeout 1200000 --concurrency 1",
|
|
30
|
+
"lint": "eslint",
|
|
31
|
+
"license": "license-checker --production --onlyAllow 'Apache-2.0;MIT;ISC;BSD-2-Clause;BSD-3-Clause;CC-BY-4.0;BlueOak-1.0.0'"
|
|
61
32
|
}
|
|
62
33
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
import { deepStrictEqual } from 'node:assert'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
import { test } from 'node:test'
|
|
5
|
+
import { pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
test('should be an alias for create-wattpm', async t => {
|
|
8
|
+
const { stdout } = await execa(process.argv[0], [
|
|
9
|
+
'--import',
|
|
10
|
+
pathToFileURL(resolve(import.meta.dirname, './loader.js')).toString(),
|
|
11
|
+
resolve(import.meta.dirname, '../bin/cli.js')
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
deepStrictEqual(stdout.trim(), 'OK')
|
|
15
|
+
})
|
package/test/loader.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Hook } from 'import-in-the-middle'
|
|
2
|
+
import { register } from 'node:module'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
register('import-in-the-middle/hook.mjs', import.meta.url)
|
|
6
|
+
|
|
7
|
+
// The package name will not work as we are in a pnpm workspace
|
|
8
|
+
// eslint-disable-next-line no-new
|
|
9
|
+
new Hook([fileURLToPath(new URL('../../create-wattpm/lib/index.js', import.meta.url))], function (exported) {
|
|
10
|
+
exported.createPlatformatic = async function () {
|
|
11
|
+
console.log('OK')
|
|
12
|
+
}
|
|
13
|
+
})
|
package/create-platformatic.mjs
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { checkNodeVersionForServices } from '@platformatic/utils'
|
|
3
|
-
import { join } from 'desm'
|
|
4
|
-
import isMain from 'es-main'
|
|
5
|
-
import { readFile } from 'fs/promises'
|
|
6
|
-
import parseArgs from 'minimist'
|
|
7
|
-
import { createPlatformatic } from './src/index.mjs'
|
|
8
|
-
|
|
9
|
-
if (isMain(import.meta)) {
|
|
10
|
-
checkNodeVersionForServices()
|
|
11
|
-
|
|
12
|
-
const _args = process.argv.slice(2)
|
|
13
|
-
const args = parseArgs(_args, {
|
|
14
|
-
alias: {
|
|
15
|
-
v: 'version'
|
|
16
|
-
}
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
if (args.version) {
|
|
20
|
-
console.log('v' + JSON.parse(await readFile(join(import.meta.url, 'package.json'), 'utf8')).version)
|
|
21
|
-
process.exit(0)
|
|
22
|
-
}
|
|
23
|
-
await createPlatformatic(_args)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export * from './src/index.mjs'
|
|
27
|
-
export * from './src/utils.mjs'
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { execa } from 'execa'
|
|
2
|
-
|
|
3
|
-
export const GIT_FIRST_COMMIT_MESSAGE = 'Platformatic project started! 🚀'
|
|
4
|
-
export const GIT_MAIN_BRANCH = 'main'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Creates a Git repository and performs the initial commit if it doesn't already exist.
|
|
8
|
-
*
|
|
9
|
-
* This function checks if Git is installed, initializes a Git repository in the specified
|
|
10
|
-
* directory if it's not already a Git repository, and performs the initial commit.
|
|
11
|
-
*
|
|
12
|
-
* @param {import('pino.').BaseLogger} logger - The logger interface for logging messages.
|
|
13
|
-
* @param {string} [dir='.'] - The target directory where the Git repository should be created.
|
|
14
|
-
*/
|
|
15
|
-
export async function createGitRepository (logger, dir = '.') {
|
|
16
|
-
if (!await isGitInstalled()) {
|
|
17
|
-
logger.error('Git is not installed')
|
|
18
|
-
return
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (!await gitInit(logger, dir)) {
|
|
22
|
-
return
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (!await gitCommit(logger, dir)) {
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
logger.info('Git repository initialized.')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Checks if Git is installed on the system.
|
|
34
|
-
*
|
|
35
|
-
* @async
|
|
36
|
-
* @returns {Promise<boolean>} A Promise that resolves to true if Git is installed, false otherwise.
|
|
37
|
-
*/
|
|
38
|
-
async function isGitInstalled () {
|
|
39
|
-
try {
|
|
40
|
-
await execa('git', ['--version'])
|
|
41
|
-
return true
|
|
42
|
-
} catch (err) {
|
|
43
|
-
return false
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Checks if a Git repository exists in the specified directory.
|
|
49
|
-
*
|
|
50
|
-
* @async
|
|
51
|
-
* @param {string} dir - The directory to check for a Git repository.
|
|
52
|
-
* @returns {Promise<boolean>} A Promise that resolves to true if a Git repository exists in the directory, false otherwise.
|
|
53
|
-
*/
|
|
54
|
-
async function doesGitRepositoryExist (dir) {
|
|
55
|
-
try {
|
|
56
|
-
await execa('git', ['rev-parse', '--is-inside-work-tree'], { cwd: dir })
|
|
57
|
-
return true
|
|
58
|
-
} catch (e) {
|
|
59
|
-
return false
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Initializes a Git repository in the specified directory if it doesn't already exist.
|
|
65
|
-
*
|
|
66
|
-
* @async
|
|
67
|
-
* @param {import('pino.').BaseLogger} - The logger object for logging messages.
|
|
68
|
-
* @param {string} dir - The directory where the Git repository should be initialized.
|
|
69
|
-
* @returns {Promise<boolean>} A Promise that resolves to true if the Git repository is successfully initialized, false otherwise.
|
|
70
|
-
*/
|
|
71
|
-
async function gitInit (logger, dir) {
|
|
72
|
-
try {
|
|
73
|
-
if (await doesGitRepositoryExist(dir)) {
|
|
74
|
-
logger.info('Git repository already exists.')
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
await execa('git', ['init', '-b', GIT_MAIN_BRANCH], { cwd: dir })
|
|
79
|
-
logger.debug('Git repository initialized.')
|
|
80
|
-
return true
|
|
81
|
-
} catch (err) {
|
|
82
|
-
logger.error('Git repository init failed.')
|
|
83
|
-
logger.debug({ err })
|
|
84
|
-
return false
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Commits changes in a Git repository located in the specified directory.
|
|
90
|
-
*
|
|
91
|
-
* @async
|
|
92
|
-
* @param {import('pino.').BaseLogger} - The logger object for logging messages.
|
|
93
|
-
* @param {string} dir - The directory of the Git repository where changes should be committed.
|
|
94
|
-
* @returns {Promise<boolean>} A Promise that resolves to true if the Git commit is successful, false otherwise.
|
|
95
|
-
*/
|
|
96
|
-
async function gitCommit (logger, dir) {
|
|
97
|
-
try {
|
|
98
|
-
await execa('git', ['add', '-A'], { cwd: dir })
|
|
99
|
-
await execa('git', ['commit', '-n', '-m', GIT_FIRST_COMMIT_MESSAGE], { cwd: dir })
|
|
100
|
-
logger.debug('Git commit done.')
|
|
101
|
-
return true
|
|
102
|
-
} catch (err) {
|
|
103
|
-
console.log(err)
|
|
104
|
-
logger.error('Git commit failed.')
|
|
105
|
-
logger.debug({ err })
|
|
106
|
-
return false
|
|
107
|
-
}
|
|
108
|
-
}
|
package/src/index.mjs
DELETED
|
@@ -1,513 +0,0 @@
|
|
|
1
|
-
import ConfigManager, { findConfigurationFile, loadConfigurationFile } from '@platformatic/config'
|
|
2
|
-
import { ImportGenerator } from '@platformatic/generators'
|
|
3
|
-
import {
|
|
4
|
-
getPackageManager, DEFAULT_PACKAGE_MANAGER,
|
|
5
|
-
createDirectory,
|
|
6
|
-
detectApplicationType,
|
|
7
|
-
executeWithTimeout,
|
|
8
|
-
generateDashedName,
|
|
9
|
-
getPkgManager,
|
|
10
|
-
searchJavascriptFiles
|
|
11
|
-
} from '@platformatic/utils'
|
|
12
|
-
|
|
13
|
-
import { execa } from 'execa'
|
|
14
|
-
import defaultInquirer from 'inquirer'
|
|
15
|
-
import parseArgs from 'minimist'
|
|
16
|
-
import { existsSync } from 'node:fs'
|
|
17
|
-
import { readFile, writeFile } from 'node:fs/promises'
|
|
18
|
-
import { basename, dirname, join, resolve } from 'node:path'
|
|
19
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
20
|
-
import ora from 'ora'
|
|
21
|
-
import pino from 'pino'
|
|
22
|
-
import pretty from 'pino-pretty'
|
|
23
|
-
import resolveModule from 'resolve'
|
|
24
|
-
import { request } from 'undici'
|
|
25
|
-
import { createGitRepository } from './create-git-repository.mjs'
|
|
26
|
-
import { getUsername, getVersion, say } from './utils.mjs'
|
|
27
|
-
|
|
28
|
-
const MARKETPLACE_HOST = 'https://marketplace.platformatic.dev'
|
|
29
|
-
const defaultStackables = ['@platformatic/service', '@platformatic/composer', '@platformatic/db']
|
|
30
|
-
|
|
31
|
-
export async function fetchStackables (marketplaceHost, modules = []) {
|
|
32
|
-
const stackables = new Set([...modules, ...defaultStackables])
|
|
33
|
-
|
|
34
|
-
// Skip the remote network request if we are running tests
|
|
35
|
-
if (process.env.MARKETPLACE_TEST) {
|
|
36
|
-
return Array.from(stackables)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let response
|
|
40
|
-
try {
|
|
41
|
-
response = await executeWithTimeout(request(new URL('/templates', marketplaceHost || MARKETPLACE_HOST)), 5000)
|
|
42
|
-
} catch (err) {
|
|
43
|
-
// No-op: we just use the default stackables
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (response && response.statusCode === 200) {
|
|
47
|
-
for (const stackable of await response.body.json()) {
|
|
48
|
-
stackables.add(stackable.name)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return Array.from(stackables)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function chooseStackable (inquirer, stackables) {
|
|
56
|
-
const options = await inquirer.prompt({
|
|
57
|
-
type: 'list',
|
|
58
|
-
name: 'type',
|
|
59
|
-
message: 'Which kind of service do you want to create?',
|
|
60
|
-
default: stackables[0],
|
|
61
|
-
choices: stackables
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
return options.type
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function getPackageVersion (pkg, projectDir) {
|
|
68
|
-
let main
|
|
69
|
-
try {
|
|
70
|
-
main = import.meta.resolve(pkg)
|
|
71
|
-
} catch {
|
|
72
|
-
main = resolveModule.sync(pkg, { basedir: projectDir })
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (main.startsWith('file:')) {
|
|
76
|
-
main = fileURLToPath(main)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let root = dirname(main)
|
|
80
|
-
|
|
81
|
-
while (!existsSync(join(root, 'package.json'))) {
|
|
82
|
-
const parent = dirname(root)
|
|
83
|
-
|
|
84
|
-
if (parent === root) {
|
|
85
|
-
// We reached the root of the filesystem
|
|
86
|
-
throw new Error(`Could not find package.json for ${pkg}.`)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
root = parent
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const packageJsonPath = JSON.parse(await readFile(join(root, 'package.json'), 'utf-8'))
|
|
93
|
-
return packageJsonPath.version
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function importOrLocal ({ pkgManager, name, projectDir, pkg }) {
|
|
97
|
-
try {
|
|
98
|
-
return await import(pkg)
|
|
99
|
-
} catch (err) {
|
|
100
|
-
try {
|
|
101
|
-
const fileToImport = resolveModule.sync(pkg, { basedir: projectDir })
|
|
102
|
-
return await import(pathToFileURL(fileToImport))
|
|
103
|
-
} catch {
|
|
104
|
-
// No-op
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
let version = ''
|
|
108
|
-
|
|
109
|
-
if (defaultStackables.includes(pkg) || pkg === '@platformatic/runtime') {
|
|
110
|
-
// Let's find if we are using one of the default stackables
|
|
111
|
-
// If we are, we have to use the "local" version of the package
|
|
112
|
-
|
|
113
|
-
const meta = await JSON.parse(await readFile(join(import.meta.dirname, '..', 'package.json'), 'utf-8'))
|
|
114
|
-
if (meta.version.includes('-')) {
|
|
115
|
-
version = `@${meta.version}`
|
|
116
|
-
} else {
|
|
117
|
-
version = `@^${meta.version}`
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const spinner = ora(`Installing ${pkg + version} using ${pkgManager} ...`).start()
|
|
122
|
-
const args = []
|
|
123
|
-
|
|
124
|
-
if (pkgManager === 'pnpm' && existsSync(resolve(projectDir, 'pnpm-workspace.yaml'))) {
|
|
125
|
-
args.push('-w')
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
await execa(pkgManager, ['install', ...args, pkg + version], { cwd: projectDir })
|
|
129
|
-
spinner.succeed()
|
|
130
|
-
|
|
131
|
-
const fileToImport = resolveModule.sync(pkg, { basedir: projectDir })
|
|
132
|
-
return await import(pathToFileURL(fileToImport))
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function findApplicationRoot (projectDir) {
|
|
137
|
-
if (existsSync(resolve(projectDir, 'package.json'))) {
|
|
138
|
-
return projectDir
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const files = await searchJavascriptFiles(projectDir)
|
|
142
|
-
|
|
143
|
-
if (files.length > 0) {
|
|
144
|
-
return dirname(resolve(projectDir, files[0]))
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return null
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export async function wrapApplication (
|
|
151
|
-
logger,
|
|
152
|
-
inquirer,
|
|
153
|
-
packageManager,
|
|
154
|
-
module,
|
|
155
|
-
install,
|
|
156
|
-
projectDir,
|
|
157
|
-
additionalGeneratorOptions = {},
|
|
158
|
-
additionalGeneratorConfig = {}
|
|
159
|
-
) {
|
|
160
|
-
const projectName = basename(projectDir)
|
|
161
|
-
|
|
162
|
-
const runtime = await importOrLocal({
|
|
163
|
-
pkgManager: packageManager,
|
|
164
|
-
name: projectName,
|
|
165
|
-
projectDir,
|
|
166
|
-
pkg: '@platformatic/runtime'
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
const generator = new runtime.WrappedGenerator({
|
|
170
|
-
logger,
|
|
171
|
-
module,
|
|
172
|
-
name: projectName,
|
|
173
|
-
inquirer,
|
|
174
|
-
...additionalGeneratorOptions
|
|
175
|
-
})
|
|
176
|
-
generator.setConfig({
|
|
177
|
-
...generator.config,
|
|
178
|
-
...additionalGeneratorConfig,
|
|
179
|
-
targetDirectory: projectDir,
|
|
180
|
-
typescript: false
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
await generator.ask()
|
|
184
|
-
await generator.prepare()
|
|
185
|
-
await generator.writeFiles()
|
|
186
|
-
|
|
187
|
-
if (install) {
|
|
188
|
-
logger.info(`Installing dependencies for the application using ${packageManager} ...`)
|
|
189
|
-
await execa(packageManager, ['install'], { cwd: projectDir, stdio: 'inherit' })
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
logger.info(`You are all set! Run \`${packageManager} start\` to start your project.`)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export async function createPlatformatic (argv) {
|
|
196
|
-
const args = parseArgs(argv, {
|
|
197
|
-
default: {
|
|
198
|
-
install: true,
|
|
199
|
-
module: []
|
|
200
|
-
},
|
|
201
|
-
boolean: ['install'],
|
|
202
|
-
string: ['global-config', 'marketplace-host', 'module']
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
const username = await getUsername()
|
|
206
|
-
const version = await getVersion()
|
|
207
|
-
const greeting = username ? `Hello ${username},` : 'Hello,'
|
|
208
|
-
await say(`${greeting} welcome to ${version ? `Platformatic ${version}!` : 'Platformatic!'}`)
|
|
209
|
-
|
|
210
|
-
const logger = pino(
|
|
211
|
-
pretty({
|
|
212
|
-
translateTime: 'SYS:HH:MM:ss',
|
|
213
|
-
ignore: 'hostname,pid'
|
|
214
|
-
})
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
const pkgManager = getPkgManager()
|
|
218
|
-
const modules = Array.isArray(args.module) ? args.module : [args.module]
|
|
219
|
-
await createApplication(logger, pkgManager, modules, args['marketplace-host'], args['install'])
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export async function createApplication (
|
|
223
|
-
logger,
|
|
224
|
-
packageManager,
|
|
225
|
-
modules,
|
|
226
|
-
marketplaceHost,
|
|
227
|
-
install,
|
|
228
|
-
additionalGeneratorOptions = {},
|
|
229
|
-
additionalGeneratorConfig = {}
|
|
230
|
-
) {
|
|
231
|
-
// This is only used for testing for now, but might be useful in the future
|
|
232
|
-
const inquirer = process.env.USER_INPUT_HANDLER ? await import(process.env.USER_INPUT_HANDLER) : defaultInquirer
|
|
233
|
-
|
|
234
|
-
// Check in the directory and its parents if there is a config file
|
|
235
|
-
let shouldChooseProjectDir = true
|
|
236
|
-
let projectDir = process.cwd()
|
|
237
|
-
const runtimeConfigFile = await findConfigurationFile(projectDir, null, 'runtime')
|
|
238
|
-
|
|
239
|
-
if (runtimeConfigFile) {
|
|
240
|
-
shouldChooseProjectDir = false
|
|
241
|
-
projectDir = dirname(runtimeConfigFile)
|
|
242
|
-
} else {
|
|
243
|
-
// Check the current directory for suitable config files
|
|
244
|
-
const applicationRoot = await findApplicationRoot(projectDir)
|
|
245
|
-
|
|
246
|
-
if (applicationRoot) {
|
|
247
|
-
// detectApplicationType cannot throw here as findApplicationRoot already checks for the existence of Javascript files
|
|
248
|
-
const { name: module, label } = await detectApplicationType(projectDir)
|
|
249
|
-
|
|
250
|
-
// Check if the file belongs to a Watt application, this can happen for instance if we executed watt create
|
|
251
|
-
// in the services folder
|
|
252
|
-
const existingRuntime = await findConfigurationFile(applicationRoot, null, 'runtime')
|
|
253
|
-
|
|
254
|
-
if (!existingRuntime) {
|
|
255
|
-
// If there is a watt.json file with a runtime property, we assume we already executed watt create and we exit.
|
|
256
|
-
const existingService = await ConfigManager.findConfigFile(projectDir)
|
|
257
|
-
|
|
258
|
-
if (existingService) {
|
|
259
|
-
const serviceConfig = await loadConfigurationFile(existingService)
|
|
260
|
-
|
|
261
|
-
if (serviceConfig.runtime) {
|
|
262
|
-
await say(`The ${label} application has already been wrapped into Watt.`)
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const { shouldWrap } = await inquirer.prompt({
|
|
268
|
-
type: 'list',
|
|
269
|
-
name: 'shouldWrap',
|
|
270
|
-
message: `This folder seems to already contain a ${label} application. Do you want to wrap into Watt?`,
|
|
271
|
-
// default: 'yes',
|
|
272
|
-
choices: [
|
|
273
|
-
{ name: 'yes', value: true },
|
|
274
|
-
{ name: 'no', value: false }
|
|
275
|
-
]
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
if (shouldWrap) {
|
|
279
|
-
if (!packageManager) {
|
|
280
|
-
packageManager = getPackageManager(projectDir, DEFAULT_PACKAGE_MANAGER)
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return wrapApplication(
|
|
284
|
-
logger,
|
|
285
|
-
inquirer,
|
|
286
|
-
packageManager,
|
|
287
|
-
module,
|
|
288
|
-
install,
|
|
289
|
-
process.cwd(),
|
|
290
|
-
additionalGeneratorOptions,
|
|
291
|
-
{ ...additionalGeneratorConfig, skipTypescript: true }
|
|
292
|
-
)
|
|
293
|
-
}
|
|
294
|
-
} else {
|
|
295
|
-
projectDir = dirname(existingRuntime)
|
|
296
|
-
shouldChooseProjectDir = false
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (shouldChooseProjectDir) {
|
|
302
|
-
const optionsDir = await inquirer.prompt({
|
|
303
|
-
type: 'input',
|
|
304
|
-
name: 'dir',
|
|
305
|
-
message: 'Where would you like to create your project?',
|
|
306
|
-
default: 'platformatic'
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
projectDir = resolve(process.cwd(), optionsDir.dir)
|
|
310
|
-
await createDirectory(projectDir)
|
|
311
|
-
process.chdir(projectDir)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const projectName = basename(projectDir)
|
|
315
|
-
|
|
316
|
-
if (!packageManager) {
|
|
317
|
-
packageManager = getPackageManager(projectDir, null, true)
|
|
318
|
-
|
|
319
|
-
if (!packageManager) {
|
|
320
|
-
const p = await inquirer.prompt({
|
|
321
|
-
type: 'list',
|
|
322
|
-
name: 'packageManager',
|
|
323
|
-
message: 'Which package manager do you want to use?',
|
|
324
|
-
default: DEFAULT_PACKAGE_MANAGER,
|
|
325
|
-
choices: [
|
|
326
|
-
{ name: 'npm', value: 'npm' },
|
|
327
|
-
{ name: 'pnpm', value: 'pnpm' },
|
|
328
|
-
{ name: 'yarn', value: 'yarn' }
|
|
329
|
-
]
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
packageManager = p.packageManager
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const runtime = await importOrLocal({
|
|
337
|
-
pkgManager: packageManager,
|
|
338
|
-
name: projectName,
|
|
339
|
-
projectDir,
|
|
340
|
-
pkg: '@platformatic/runtime'
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
const generator = new runtime.Generator({
|
|
344
|
-
logger,
|
|
345
|
-
name: projectName,
|
|
346
|
-
inquirer,
|
|
347
|
-
packageManager,
|
|
348
|
-
...additionalGeneratorOptions
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
generator.setConfig({
|
|
352
|
-
...generator.config,
|
|
353
|
-
...additionalGeneratorConfig,
|
|
354
|
-
targetDirectory: projectDir
|
|
355
|
-
})
|
|
356
|
-
|
|
357
|
-
await generator.populateFromExistingConfig()
|
|
358
|
-
if (generator.existingConfig) {
|
|
359
|
-
await say('Using existing configuration ...')
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const stackables = await fetchStackables(marketplaceHost, modules)
|
|
363
|
-
|
|
364
|
-
const names = generator.existingServices ?? []
|
|
365
|
-
|
|
366
|
-
while (true) {
|
|
367
|
-
const stackableName = await chooseStackable(inquirer, stackables)
|
|
368
|
-
// await say(`Creating a ${stackable} project in ${projectDir}...`)
|
|
369
|
-
|
|
370
|
-
const stackable = await importOrLocal({
|
|
371
|
-
pkgManager: packageManager,
|
|
372
|
-
name: projectName,
|
|
373
|
-
projectDir,
|
|
374
|
-
pkg: stackableName
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
const { serviceName } = await inquirer.prompt({
|
|
378
|
-
type: 'input',
|
|
379
|
-
name: 'serviceName',
|
|
380
|
-
message: 'What is the name of the service?',
|
|
381
|
-
default: generateDashedName(),
|
|
382
|
-
validate: value => {
|
|
383
|
-
if (value.length === 0) {
|
|
384
|
-
return 'Please enter a name'
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (value.includes(' ')) {
|
|
388
|
-
return 'Please enter a name without spaces'
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (names.includes(value)) {
|
|
392
|
-
return 'This name is already used, please choose another one.'
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return true
|
|
396
|
-
}
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
names.push(serviceName)
|
|
400
|
-
|
|
401
|
-
const stackableGenerator = stackable.Generator
|
|
402
|
-
? new stackable.Generator({
|
|
403
|
-
logger,
|
|
404
|
-
inquirer,
|
|
405
|
-
serviceName,
|
|
406
|
-
parent: generator,
|
|
407
|
-
...additionalGeneratorOptions
|
|
408
|
-
})
|
|
409
|
-
: new ImportGenerator({
|
|
410
|
-
logger,
|
|
411
|
-
inquirer,
|
|
412
|
-
serviceName,
|
|
413
|
-
module: stackableName,
|
|
414
|
-
version: await getPackageVersion(stackableName, projectDir),
|
|
415
|
-
parent: generator,
|
|
416
|
-
...additionalGeneratorOptions
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
stackableGenerator.setConfig({
|
|
420
|
-
...stackableGenerator.config,
|
|
421
|
-
...additionalGeneratorConfig,
|
|
422
|
-
serviceName
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
generator.addService(stackableGenerator, serviceName)
|
|
426
|
-
|
|
427
|
-
await stackableGenerator.ask()
|
|
428
|
-
|
|
429
|
-
const { shouldBreak } = await inquirer.prompt([
|
|
430
|
-
{
|
|
431
|
-
type: 'list',
|
|
432
|
-
name: 'shouldBreak',
|
|
433
|
-
message: 'Do you want to create another service?',
|
|
434
|
-
default: false,
|
|
435
|
-
choices: [
|
|
436
|
-
{ name: 'yes', value: false },
|
|
437
|
-
{ name: 'no', value: true }
|
|
438
|
-
]
|
|
439
|
-
}
|
|
440
|
-
])
|
|
441
|
-
|
|
442
|
-
if (shouldBreak) {
|
|
443
|
-
break
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
let entrypoint = ''
|
|
448
|
-
const chooseEntrypoint = names.length > 1 && (!generator.existingConfigRaw || !generator.existingConfigRaw.entrypoint)
|
|
449
|
-
|
|
450
|
-
if (chooseEntrypoint) {
|
|
451
|
-
const results = await inquirer.prompt([
|
|
452
|
-
{
|
|
453
|
-
type: 'list',
|
|
454
|
-
name: 'entrypoint',
|
|
455
|
-
message: 'Which service should be exposed?',
|
|
456
|
-
choices: names.map(name => ({ name, value: name }))
|
|
457
|
-
}
|
|
458
|
-
])
|
|
459
|
-
entrypoint = results.entrypoint
|
|
460
|
-
} else {
|
|
461
|
-
entrypoint = names[0]
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
generator.setEntryPoint(entrypoint)
|
|
465
|
-
|
|
466
|
-
await generator.ask()
|
|
467
|
-
await generator.prepare()
|
|
468
|
-
|
|
469
|
-
if (chooseEntrypoint) {
|
|
470
|
-
await generator.updateConfigEntryPoint(entrypoint)
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
await generator.writeFiles()
|
|
474
|
-
|
|
475
|
-
// Create project here
|
|
476
|
-
if (!generator.existingConfigRaw) {
|
|
477
|
-
const { initGitRepository } = await inquirer.prompt({
|
|
478
|
-
type: 'list',
|
|
479
|
-
name: 'initGitRepository',
|
|
480
|
-
message: 'Do you want to init the git repository?',
|
|
481
|
-
default: false,
|
|
482
|
-
choices: [
|
|
483
|
-
{ name: 'yes', value: true },
|
|
484
|
-
{ name: 'no', value: false }
|
|
485
|
-
]
|
|
486
|
-
})
|
|
487
|
-
|
|
488
|
-
if (initGitRepository) {
|
|
489
|
-
await createGitRepository(logger, projectDir)
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if (packageManager === 'pnpm') {
|
|
494
|
-
// add pnpm-workspace.yaml file if needed
|
|
495
|
-
const content = `packages:
|
|
496
|
-
# all packages in direct subdirs of packages/
|
|
497
|
-
- 'services/*'
|
|
498
|
-
- 'web/*'`
|
|
499
|
-
await writeFile(join(projectDir, 'pnpm-workspace.yaml'), content)
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (typeof install === 'function') {
|
|
503
|
-
await install(projectDir, generator.runtimeConfig, packageManager)
|
|
504
|
-
} else if (install) {
|
|
505
|
-
const spinner = ora('Installing dependencies...').start()
|
|
506
|
-
await execa(packageManager, ['install'], { cwd: projectDir })
|
|
507
|
-
spinner.succeed()
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
logger.info('Project created successfully, executing post-install actions...')
|
|
511
|
-
await generator.postInstallActions()
|
|
512
|
-
logger.info(`You are all set! Run \`${packageManager} start\` to start your project.`)
|
|
513
|
-
}
|
package/src/utils.mjs
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import * as desm from 'desm'
|
|
2
|
-
import { execa } from 'execa'
|
|
3
|
-
import { access, constants, readFile } from 'fs/promises'
|
|
4
|
-
import { createRequire } from 'module'
|
|
5
|
-
import { dirname, join, resolve } from 'path'
|
|
6
|
-
import * as url from 'url'
|
|
7
|
-
|
|
8
|
-
import ConfigManager from '@platformatic/config'
|
|
9
|
-
|
|
10
|
-
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
11
|
-
export const randomBetween = (min, max) => Math.floor(Math.random() * (max - min + 1) + min)
|
|
12
|
-
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
|
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
|
-
|
|
22
|
-
export async function isFileAccessible (filename, directory) {
|
|
23
|
-
try {
|
|
24
|
-
const filePath = directory ? resolve(directory, filename) : filename
|
|
25
|
-
await access(filePath)
|
|
26
|
-
return true
|
|
27
|
-
} catch (err) {
|
|
28
|
-
return false
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Gets the username from git config or `whoami` command
|
|
33
|
-
* @returns string | null
|
|
34
|
-
*/
|
|
35
|
-
export const getUsername = async () => {
|
|
36
|
-
try {
|
|
37
|
-
const { stdout } = await execa('git', ['config', 'user.name'])
|
|
38
|
-
if (stdout?.trim()) {
|
|
39
|
-
return stdout.trim()
|
|
40
|
-
}
|
|
41
|
-
} catch (err) {
|
|
42
|
-
// ignore: git failed
|
|
43
|
-
}
|
|
44
|
-
try {
|
|
45
|
-
const { stdout } = await execa('whoami')
|
|
46
|
-
if (stdout?.trim()) {
|
|
47
|
-
return stdout.trim()
|
|
48
|
-
}
|
|
49
|
-
} catch (err) {
|
|
50
|
-
// ignore: whoami failed
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return null
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Get the platformatic package version from package.json
|
|
57
|
-
* @returns string
|
|
58
|
-
*/
|
|
59
|
-
/* c8 ignore next 4 */
|
|
60
|
-
export const getVersion = async () => {
|
|
61
|
-
const data = await readFile(desm.join(import.meta.url, '..', 'package.json'), 'utf8')
|
|
62
|
-
return JSON.parse(data).version
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function isDirectoryWriteable (directory) {
|
|
66
|
-
try {
|
|
67
|
-
await access(directory, constants.R_OK | constants.W_OK)
|
|
68
|
-
return true
|
|
69
|
-
} catch (err) {
|
|
70
|
-
return false
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export const findConfigFile = async directory => ConfigManager.findConfigFile(directory)
|
|
75
|
-
export const findDBConfigFile = async directory => ConfigManager.findConfigFile(directory, 'db')
|
|
76
|
-
export const findServiceConfigFile = async directory => ConfigManager.findConfigFile(directory, 'service')
|
|
77
|
-
export const findComposerConfigFile = async directory => ConfigManager.findConfigFile(directory, 'composer')
|
|
78
|
-
export const findRuntimeConfigFile = async directory => ConfigManager.findConfigFile(directory, 'runtime')
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Gets the version of the specified dependency package from package.json
|
|
82
|
-
* @param {string} dependencyName
|
|
83
|
-
* @returns string
|
|
84
|
-
*/
|
|
85
|
-
export const getDependencyVersion = async dependencyName => {
|
|
86
|
-
const rootPackageJson = join(__dirname, '..', 'package.json')
|
|
87
|
-
const packageJsonContents = JSON.parse(await readFile(rootPackageJson, 'utf8'))
|
|
88
|
-
const dependencies = packageJsonContents.dependencies
|
|
89
|
-
const devDependencies = packageJsonContents.devDependencies
|
|
90
|
-
const regexp = /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)/
|
|
91
|
-
if (dependencies[dependencyName]) {
|
|
92
|
-
const match = dependencies[dependencyName].match(regexp)
|
|
93
|
-
if (!match) {
|
|
94
|
-
return await resolveWorkspaceDependency(dependencyName)
|
|
95
|
-
}
|
|
96
|
-
return match[0]
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (devDependencies[dependencyName]) {
|
|
100
|
-
const match = devDependencies[dependencyName].match(regexp)
|
|
101
|
-
if (!match) {
|
|
102
|
-
return await resolveWorkspaceDependency(dependencyName)
|
|
103
|
-
}
|
|
104
|
-
return match[0]
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function resolveWorkspaceDependency (dependencyName) {
|
|
108
|
-
const require = createRequire(import.meta.url)
|
|
109
|
-
let dependencyPath = dirname(require.resolve(dependencyName))
|
|
110
|
-
// some deps are resolved not at their root level
|
|
111
|
-
// for instance 'typescript' will be risolved in its own ./lib directory
|
|
112
|
-
// next loop is to find the nearest parent directory that contains a package.json file
|
|
113
|
-
while (!(await isFileAccessible(join(dependencyPath, 'package.json')))) {
|
|
114
|
-
dependencyPath = join(dependencyPath, '..')
|
|
115
|
-
if (dependencyPath === '/') {
|
|
116
|
-
throw new Error(`Cannot find package.json for ${dependencyName}`)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
const pathToPackageJson = join(dependencyPath, 'package.json')
|
|
120
|
-
const packageJsonFile = await readFile(pathToPackageJson, 'utf-8')
|
|
121
|
-
const packageJson = JSON.parse(packageJsonFile)
|
|
122
|
-
return packageJson.version
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function convertServiceNameToPrefix (serviceName) {
|
|
127
|
-
return serviceName.replace(/-/g, '_').toUpperCase()
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function addPrefixToEnv (env, prefix) {
|
|
131
|
-
const output = {}
|
|
132
|
-
Object.entries(env).forEach(([key, value]) => {
|
|
133
|
-
output[`${prefix}_${key}`] = value
|
|
134
|
-
})
|
|
135
|
-
return output
|
|
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
|
-
}
|