create-platformatic 2.65.1 → 2.66.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.
Files changed (2) hide show
  1. package/package.json +9 -8
  2. package/src/index.mjs +158 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-platformatic",
3
- "version": "2.65.1",
3
+ "version": "2.66.0",
4
4
  "description": "Create platformatic application interactive tool",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,6 +20,7 @@
20
20
  "desm": "^1.3.1",
21
21
  "es-main": "^1.3.0",
22
22
  "execa": "^9.0.0",
23
+ "glob": "^11.0.2",
23
24
  "help-me": "^5.0.0",
24
25
  "inquirer": "^9.2.16",
25
26
  "minimist": "^1.2.8",
@@ -31,9 +32,9 @@
31
32
  "strip-ansi": "^7.1.0",
32
33
  "undici": "^7.0.0",
33
34
  "which": "^3.0.1",
34
- "@platformatic/generators": "2.65.1",
35
- "@platformatic/utils": "2.65.1",
36
- "@platformatic/config": "2.65.1"
35
+ "@platformatic/config": "2.66.0",
36
+ "@platformatic/generators": "2.66.0",
37
+ "@platformatic/utils": "2.66.0"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@types/node": "^22.5.0",
@@ -48,10 +49,10 @@
48
49
  "neostandard": "^0.12.0",
49
50
  "typescript": "~5.8.0",
50
51
  "yaml": "^2.4.1",
51
- "@platformatic/composer": "2.65.1",
52
- "@platformatic/db": "2.65.1",
53
- "@platformatic/runtime": "2.65.1",
54
- "@platformatic/service": "2.65.1"
52
+ "@platformatic/composer": "2.66.0",
53
+ "@platformatic/db": "2.66.0",
54
+ "@platformatic/runtime": "2.66.0",
55
+ "@platformatic/service": "2.66.0"
55
56
  },
56
57
  "scripts": {
57
58
  "test:cli": "borp --pattern \"test/cli/*test.mjs\" --timeout=300000 --concurrency=1",
package/src/index.mjs CHANGED
@@ -1,15 +1,17 @@
1
- import { ConfigManager } from '@platformatic/config'
1
+ import ConfigManager, { findConfigurationFile, loadConfigurationFile } from '@platformatic/config'
2
2
  import { createDirectory, executeWithTimeout, generateDashedName, getPkgManager } from '@platformatic/utils'
3
3
  import { execa } from 'execa'
4
+ import { glob } from 'glob'
4
5
  import defaultInquirer from 'inquirer'
5
6
  import parseArgs from 'minimist'
7
+ import { existsSync } from 'node:fs'
6
8
  import { readFile, writeFile } from 'node:fs/promises'
7
- import { basename, join, resolve as pathResolve } from 'node:path'
9
+ import { basename, dirname, join, resolve } from 'node:path'
8
10
  import { pathToFileURL } from 'node:url'
9
11
  import ora from 'ora'
10
12
  import pino from 'pino'
11
13
  import pretty from 'pino-pretty'
12
- import resolve from 'resolve'
14
+ import * as resolveModule from 'resolve'
13
15
  import { request } from 'undici'
14
16
  import { createGitRepository } from './create-git-repository.mjs'
15
17
  import { getUsername, getVersion, say } from './utils.mjs'
@@ -57,7 +59,7 @@ async function importOrLocal ({ pkgManager, name, projectDir, pkg }) {
57
59
  return await import(pkg)
58
60
  } catch (err) {
59
61
  try {
60
- const fileToImport = resolve.sync(pkg, { basedir: projectDir })
62
+ const fileToImport = resolveModule.sync(pkg, { basedir: projectDir })
61
63
  return await import(pathToFileURL(fileToImport))
62
64
  } catch {
63
65
  // No-op
@@ -81,11 +83,93 @@ async function importOrLocal ({ pkgManager, name, projectDir, pkg }) {
81
83
  await execa(pkgManager, ['install', pkg + version], { cwd: projectDir })
82
84
  spinner.succeed()
83
85
 
84
- const fileToImport = resolve.sync(pkg, { basedir: projectDir })
86
+ const fileToImport = resolveModule.sync(pkg, { basedir: projectDir })
85
87
  return await import(pathToFileURL(fileToImport))
86
88
  }
87
89
  }
88
90
 
91
+ async function findApplicationRoot (projectDir) {
92
+ if (existsSync(resolve(projectDir, 'package.json'))) {
93
+ return projectDir
94
+ }
95
+
96
+ const files = await glob('**/*.{js,mjs,cjs,ts,mts,cts}', { cwd: projectDir })
97
+
98
+ if (files.length > 0) {
99
+ return dirname(resolve(projectDir, files[0]))
100
+ }
101
+
102
+ return null
103
+ }
104
+
105
+ export async function determineApplicationType (projectDir) {
106
+ let rootPackageJson
107
+ try {
108
+ rootPackageJson = JSON.parse(await readFile(resolve(projectDir, 'package.json'), 'utf-8'))
109
+ } catch {
110
+ rootPackageJson = {}
111
+ }
112
+
113
+ const { dependencies, devDependencies } = rootPackageJson
114
+
115
+ if (dependencies?.next || devDependencies?.next) {
116
+ return ['@platformatic/next', 'Next.js']
117
+ } else if (dependencies?.['@remix-run/dev'] || devDependencies?.['@remix-run/dev']) {
118
+ return ['@platformatic/remix', 'Remix']
119
+ } else if (dependencies?.astro || devDependencies?.astro) {
120
+ return ['@platformatic/astro', 'Astro']
121
+ } else if (dependencies?.vite || devDependencies?.vite) {
122
+ return ['@platformatic/vite', 'Vite']
123
+ }
124
+
125
+ return ['@platformatic/node', 'Node.js']
126
+ }
127
+
128
+ export async function wrapApplication (
129
+ logger,
130
+ inquirer,
131
+ packageManager,
132
+ module,
133
+ install,
134
+ projectDir,
135
+ additionalGeneratorOptions = {},
136
+ additionalGeneratorConfig = {}
137
+ ) {
138
+ const projectName = basename(projectDir)
139
+
140
+ const runtime = await importOrLocal({
141
+ pkgManager: packageManager,
142
+ name: projectName,
143
+ projectDir,
144
+ pkg: '@platformatic/runtime'
145
+ })
146
+
147
+ const generator = new runtime.WrappedGenerator({
148
+ logger,
149
+ module,
150
+ name: projectName,
151
+ inquirer,
152
+ ...additionalGeneratorOptions
153
+ })
154
+ generator.setConfig({
155
+ ...generator.config,
156
+ ...additionalGeneratorConfig,
157
+ targetDirectory: projectDir,
158
+ typescript: false
159
+ })
160
+
161
+ await generator.ask()
162
+ await generator.prepare()
163
+ await generator.writeFiles()
164
+
165
+ if (install) {
166
+ logger.info(`Installing dependencies for the application using ${packageManager} ...`)
167
+ await execa(packageManager, ['install'], { cwd: projectDir, stdio: 'inherit' })
168
+ }
169
+
170
+ logger.info(`You are all set! Run \`${packageManager} start\` to start your project.`)
171
+ }
172
+
89
173
  export async function createPlatformatic (argv) {
90
174
  const args = parseArgs(argv, {
91
175
  default: {
@@ -119,13 +203,75 @@ export async function createApplication (
119
203
  modules,
120
204
  marketplaceHost,
121
205
  install,
122
- additionalGeneratorOptions = {}
206
+ additionalGeneratorOptions = {},
207
+ additionalGeneratorConfig = {}
123
208
  ) {
124
209
  // 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
210
+ const inquirer = process.env.USER_INPUT_HANDLER ? await import(process.env.USER_INPUT_HANDLER) : defaultInquirer
126
211
 
212
+ // Check in the directory and its parents if there is a config file
213
+ let shouldChooseProjectDir = true
127
214
  let projectDir = process.cwd()
128
- if (!(await ConfigManager.findConfigFile())) {
215
+ const runtimeConfigFile = await findConfigurationFile(projectDir, null, 'runtime')
216
+
217
+ if (runtimeConfigFile) {
218
+ shouldChooseProjectDir = false
219
+ projectDir = dirname(runtimeConfigFile)
220
+ } else {
221
+ // Check the current directory for suitable config files
222
+ const applicationRoot = await findApplicationRoot(projectDir)
223
+
224
+ if (applicationRoot) {
225
+ const [module, label] = await determineApplicationType(projectDir)
226
+
227
+ // Check if the file belongs to a Watt application, this can happen for instance if we executed watt create
228
+ // in the services folder
229
+ const existingRuntime = await findConfigurationFile(applicationRoot, null, 'runtime')
230
+
231
+ if (!existingRuntime) {
232
+ // If there is a watt.json file with a runtime property, we assume we already executed watt create and we exit.
233
+ const existingService = await ConfigManager.findConfigFile(projectDir)
234
+
235
+ if (existingService) {
236
+ const serviceConfig = await loadConfigurationFile(existingService)
237
+
238
+ if (serviceConfig.runtime) {
239
+ await say(`The ${label} application has already been wrapped into Watt.`)
240
+ return
241
+ }
242
+ }
243
+
244
+ const { shouldWrap } = await inquirer.prompt({
245
+ type: 'list',
246
+ name: 'shouldWrap',
247
+ message: `This folder seems to already contain a ${label} application. Do you want to wrap into Watt?`,
248
+ // default: 'yes',
249
+ choices: [
250
+ { name: 'yes', value: true },
251
+ { name: 'no', value: false }
252
+ ]
253
+ })
254
+
255
+ if (shouldWrap) {
256
+ return wrapApplication(
257
+ logger,
258
+ inquirer,
259
+ packageManager,
260
+ module,
261
+ install,
262
+ process.cwd(),
263
+ additionalGeneratorOptions,
264
+ { ...additionalGeneratorConfig, skipTypescript: true }
265
+ )
266
+ }
267
+ } else {
268
+ projectDir = dirname(existingRuntime)
269
+ shouldChooseProjectDir = false
270
+ }
271
+ }
272
+ }
273
+
274
+ if (shouldChooseProjectDir) {
129
275
  const optionsDir = await inquirer.prompt({
130
276
  type: 'input',
131
277
  name: 'dir',
@@ -133,11 +279,11 @@ export async function createApplication (
133
279
  default: 'platformatic'
134
280
  })
135
281
 
136
- projectDir = pathResolve(process.cwd(), optionsDir.dir)
282
+ projectDir = resolve(process.cwd(), optionsDir.dir)
283
+ await createDirectory(projectDir)
137
284
  }
138
- const projectName = basename(projectDir)
139
285
 
140
- await createDirectory(projectDir)
286
+ const projectName = basename(projectDir)
141
287
 
142
288
  const runtime = await importOrLocal({
143
289
  pkgManager: packageManager,
@@ -155,6 +301,7 @@ export async function createApplication (
155
301
 
156
302
  generator.setConfig({
157
303
  ...generator.config,
304
+ ...additionalGeneratorConfig,
158
305
  targetDirectory: projectDir
159
306
  })
160
307