wattpm 2.14.0 → 2.16.0-alpha.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/index.js +4 -0
- package/lib/commands/external.js +230 -156
- package/lib/commands/init.js +17 -11
- package/lib/gitignore.js +3 -0
- package/lib/utils.js +57 -6
- package/package.json +8 -7
- package/schema.json +7 -1
package/index.js
CHANGED
|
@@ -11,6 +11,8 @@ import { version } from './lib/schema.js'
|
|
|
11
11
|
import { createLogger, overrideFatal, parseArgs, setVerbose } from './lib/utils.js'
|
|
12
12
|
|
|
13
13
|
export async function main () {
|
|
14
|
+
globalThis.platformatic = { executable: 'watt' }
|
|
15
|
+
|
|
14
16
|
const logger = createLogger('info')
|
|
15
17
|
|
|
16
18
|
overrideFatal(logger)
|
|
@@ -106,3 +108,5 @@ export async function main () {
|
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
export * from './lib/schema.js'
|
|
111
|
+
|
|
112
|
+
export { resolveServices } from './lib/commands/external.js'
|
package/lib/commands/external.js
CHANGED
|
@@ -1,56 +1,25 @@
|
|
|
1
1
|
import { configCandidates } from '@platformatic/basic'
|
|
2
|
-
import { Store } from '@platformatic/config'
|
|
3
|
-
import { platformaticRuntime } from '@platformatic/runtime'
|
|
4
2
|
import { ensureLoggableError } from '@platformatic/utils'
|
|
5
3
|
import { bold } from 'colorette'
|
|
4
|
+
import { parse } from 'dotenv'
|
|
6
5
|
import { execa } from 'execa'
|
|
7
|
-
import { existsSync
|
|
8
|
-
import {
|
|
9
|
-
import { basename, dirname, isAbsolute, join, relative, resolve
|
|
6
|
+
import { existsSync } from 'node:fs'
|
|
7
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
8
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
10
9
|
import { defaultServiceJson } from '../defaults.js'
|
|
11
10
|
import { version } from '../schema.js'
|
|
12
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
findConfigurationFile,
|
|
13
|
+
loadConfigurationFile,
|
|
14
|
+
loadRawConfigurationFile,
|
|
15
|
+
overrideFatal,
|
|
16
|
+
parseArgs,
|
|
17
|
+
saveConfigurationFile,
|
|
18
|
+
serviceToEnvVariable
|
|
19
|
+
} from '../utils.js'
|
|
13
20
|
|
|
14
21
|
const originCandidates = ['origin', 'upstream']
|
|
15
22
|
|
|
16
|
-
export async function checkEmptyDirectory (logger, path, relativePath) {
|
|
17
|
-
if (existsSync(path)) {
|
|
18
|
-
const statObject = await stat(path)
|
|
19
|
-
|
|
20
|
-
if (!statObject.isDirectory()) {
|
|
21
|
-
logger.fatal(`Path ${bold(relativePath)} exists but it is not a directory.`)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const entries = await readdir(path)
|
|
25
|
-
|
|
26
|
-
if (entries.filter(e => !e.startsWith('.')).length) {
|
|
27
|
-
logger.fatal(`Directory ${bold(relativePath)} is not empty.`)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function parseConfiguration (logger, configurationFile) {
|
|
33
|
-
const store = new Store({
|
|
34
|
-
cwd: process.cwd(),
|
|
35
|
-
logger
|
|
36
|
-
})
|
|
37
|
-
store.add(platformaticRuntime)
|
|
38
|
-
|
|
39
|
-
const { configManager } = await store.loadConfig({
|
|
40
|
-
config: configurationFile,
|
|
41
|
-
overrides: {
|
|
42
|
-
/* c8 ignore next 3 */
|
|
43
|
-
onMissingEnv (key) {
|
|
44
|
-
return ''
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
await configManager.parse()
|
|
50
|
-
|
|
51
|
-
return configManager.current
|
|
52
|
-
}
|
|
53
|
-
|
|
54
23
|
async function parseLocalFolder (path) {
|
|
55
24
|
// Read the package.json, if any
|
|
56
25
|
const packageJsonPath = resolve(path, 'package.json')
|
|
@@ -103,54 +72,36 @@ async function findExistingConfiguration (root, path) {
|
|
|
103
72
|
}
|
|
104
73
|
}
|
|
105
74
|
|
|
106
|
-
async function
|
|
107
|
-
|
|
108
|
-
const root = dirname(configurationFile)
|
|
75
|
+
export async function appendEnvVariable (envFile, key, value) {
|
|
76
|
+
let contents = ''
|
|
109
77
|
|
|
110
|
-
|
|
78
|
+
if (existsSync(envFile)) {
|
|
79
|
+
contents = await readFile(envFile, 'utf-8')
|
|
111
80
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (path.startsWith(autoloadPath)) {
|
|
115
|
-
return
|
|
81
|
+
if (contents.length && !contents.endsWith('\n')) {
|
|
82
|
+
contents += '\n'
|
|
116
83
|
}
|
|
117
84
|
}
|
|
118
85
|
|
|
119
|
-
|
|
120
|
-
config.web ??= []
|
|
121
|
-
config.web.push({ id, path, url })
|
|
86
|
+
contents += `${key}=${value}\n`
|
|
122
87
|
|
|
123
|
-
|
|
88
|
+
return writeFile(envFile, contents, 'utf-8')
|
|
124
89
|
}
|
|
125
90
|
|
|
126
91
|
async function fixConfiguration (logger, root) {
|
|
127
92
|
const configurationFile = await findConfigurationFile(logger, root)
|
|
128
|
-
const config =
|
|
129
|
-
|
|
130
|
-
// Load all services in the autoload and the one manually specified
|
|
131
|
-
const services = []
|
|
132
|
-
const autoLoadPath = config.autoload?.path
|
|
133
|
-
if (autoLoadPath) {
|
|
134
|
-
for (const path of await readdir(resolve(root, autoLoadPath))) {
|
|
135
|
-
services.push(join(autoLoadPath, path))
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/* c8 ignore next */
|
|
140
|
-
for (const service of config.services ?? []) {
|
|
141
|
-
services.push(service.path)
|
|
142
|
-
}
|
|
93
|
+
const config = await loadConfigurationFile(logger, configurationFile)
|
|
143
94
|
|
|
144
95
|
// For each service, if there is no watt.json, create one and fix package dependencies
|
|
145
|
-
for (const
|
|
146
|
-
const wattConfiguration = await findExistingConfiguration(root,
|
|
96
|
+
for (const { path } of config.services) {
|
|
97
|
+
const wattConfiguration = await findExistingConfiguration(root, path)
|
|
147
98
|
|
|
148
99
|
/* c8 ignore next 3 */
|
|
149
100
|
if (wattConfiguration) {
|
|
150
101
|
continue
|
|
151
102
|
}
|
|
152
103
|
|
|
153
|
-
const { id, packageJson, stackable } = await parseLocalFolder(resolve(root,
|
|
104
|
+
const { id, packageJson, stackable } = await parseLocalFolder(resolve(root, path))
|
|
154
105
|
|
|
155
106
|
packageJson.dependencies ??= {}
|
|
156
107
|
packageJson.dependencies[stackable] = `^${version}`
|
|
@@ -161,28 +112,102 @@ async function fixConfiguration (logger, root) {
|
|
|
161
112
|
}
|
|
162
113
|
|
|
163
114
|
logger.info(`Detected stackable ${bold(stackable)} for service ${bold(id)}, adding to the service dependencies.`)
|
|
164
|
-
|
|
165
|
-
await
|
|
115
|
+
|
|
116
|
+
await saveConfigurationFile(logger, resolve(path, 'package.json'), packageJson)
|
|
117
|
+
await saveConfigurationFile(logger, resolve(path, 'watt.json'), wattJson)
|
|
166
118
|
}
|
|
167
119
|
}
|
|
168
120
|
|
|
169
|
-
async function
|
|
170
|
-
const
|
|
121
|
+
async function importService (logger, configurationFile, id, path, url) {
|
|
122
|
+
const config = await loadConfigurationFile(logger, configurationFile)
|
|
123
|
+
const rawConfig = await loadRawConfigurationFile(logger, configurationFile)
|
|
124
|
+
const root = dirname(configurationFile)
|
|
125
|
+
const envFile = resolve(root, '.env')
|
|
126
|
+
const envSampleFile = resolve(root, '.env.sample')
|
|
127
|
+
const envVariable = serviceToEnvVariable(id)
|
|
128
|
+
|
|
129
|
+
let useEnv = true
|
|
130
|
+
|
|
131
|
+
// If there is a locale path
|
|
132
|
+
if (path) {
|
|
133
|
+
let autoloadPath = config.autoload?.path
|
|
134
|
+
|
|
135
|
+
// If we already autoload this path, there is nothing to do
|
|
136
|
+
if (autoloadPath) {
|
|
137
|
+
autoloadPath = resolve(root, autoloadPath)
|
|
138
|
+
if (path.startsWith(autoloadPath)) {
|
|
139
|
+
logger.warn('The path is already autoloaded as a service.')
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// If the path is within the application repository
|
|
145
|
+
if (path.startsWith(root)) {
|
|
146
|
+
// If the path is already defined as a service, there is nothing to do
|
|
147
|
+
if (config.services.some(s => s.path === path)) {
|
|
148
|
+
logger.warn('The path is already defined as a service.')
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Do not use env variables
|
|
153
|
+
useEnv = false
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!url) {
|
|
157
|
+
logger.warn(`The service ${bold(id)} does not define a Git repository.`)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Make sure the service is not already defined
|
|
162
|
+
if (config.serviceMap.has(id)) {
|
|
163
|
+
logger.fatal(`There is already a service ${bold(id)} defined, please choose a different service ID.`)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* c8 ignore next */
|
|
167
|
+
rawConfig.web ??= []
|
|
171
168
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
let isAutoloaded = false
|
|
175
|
-
const autoLoadPath = config.autoload?.path
|
|
176
|
-
if (autoLoadPath) {
|
|
177
|
-
const relativePath = relative(root, path)
|
|
169
|
+
if (useEnv) {
|
|
170
|
+
rawConfig.web.push({ id, path: `{${envVariable}}`, url })
|
|
178
171
|
|
|
179
|
-
|
|
172
|
+
// Make sure the environment variable is not already defined
|
|
173
|
+
if (existsSync(envFile)) {
|
|
174
|
+
const env = parse(await readFile(envFile, 'utf-8'))
|
|
175
|
+
|
|
176
|
+
if (env[envVariable]) {
|
|
177
|
+
logger.fatal(
|
|
178
|
+
`There is already an environment variable ${bold(envVariable)} defined, please choose a different service ID.`
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Copy the .env file to .env.sample if it does not exist
|
|
184
|
+
if (!existsSync(envSampleFile)) {
|
|
185
|
+
await writeFile(envSampleFile, '', 'utf-8')
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await appendEnvVariable(envFile, envVariable, path ?? '')
|
|
189
|
+
await appendEnvVariable(envSampleFile, envVariable, '')
|
|
190
|
+
} else {
|
|
191
|
+
rawConfig.web.push({ id, path: relative(root, path) })
|
|
180
192
|
}
|
|
181
193
|
|
|
182
|
-
|
|
183
|
-
|
|
194
|
+
await saveConfigurationFile(logger, configurationFile, rawConfig)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function importURL (logger, _, configurationFile, rawUrl, id, http) {
|
|
198
|
+
let url = rawUrl
|
|
199
|
+
if (rawUrl.match(/^[a-z0-9-]+\/[a-z0-9-]+$/i)) {
|
|
200
|
+
url = http ? `https://github.com/${rawUrl}.git` : `git@github.com:${rawUrl}.git`
|
|
184
201
|
}
|
|
185
202
|
|
|
203
|
+
await importService(logger, configurationFile, id ?? basename(rawUrl, '.git'), null, url)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function importLocal (logger, root, configurationFile, path, overridenId) {
|
|
207
|
+
const { id, url, packageJson, stackable } = await parseLocalFolder(path)
|
|
208
|
+
|
|
209
|
+
await importService(logger, configurationFile, overridenId ?? id, path, url)
|
|
210
|
+
|
|
186
211
|
// Check if there is any configuration file we recognize. If so, don't do anything
|
|
187
212
|
const wattConfiguration = await findExistingConfiguration(root, path)
|
|
188
213
|
if (wattConfiguration) {
|
|
@@ -204,34 +229,126 @@ async function importLocal (logger, root, configurationFile, path) {
|
|
|
204
229
|
}
|
|
205
230
|
|
|
206
231
|
logger.info(`Detected stackable ${bold(stackable)} for service ${bold(id)}, adding to the service dependencies.`)
|
|
207
|
-
|
|
208
|
-
await
|
|
232
|
+
|
|
233
|
+
await saveConfigurationFile(logger, resolve(path, 'package.json'), packageJson)
|
|
234
|
+
await saveConfigurationFile(logger, resolve(path, 'watt.json'), wattJson)
|
|
209
235
|
}
|
|
210
236
|
|
|
211
|
-
async function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
237
|
+
export async function resolveServices (
|
|
238
|
+
logger,
|
|
239
|
+
root,
|
|
240
|
+
configurationFile,
|
|
241
|
+
username,
|
|
242
|
+
password,
|
|
243
|
+
skipDependencies,
|
|
244
|
+
packageManager
|
|
245
|
+
) {
|
|
246
|
+
const config = await loadConfigurationFile(logger, configurationFile)
|
|
247
|
+
|
|
248
|
+
/* c8 ignore next 8 */
|
|
249
|
+
if (!packageManager) {
|
|
250
|
+
if (existsSync(resolve(root, 'pnpm-lock.yaml'))) {
|
|
251
|
+
packageManager = 'pnpm'
|
|
252
|
+
} else {
|
|
253
|
+
packageManager = 'npm'
|
|
254
|
+
}
|
|
215
255
|
}
|
|
216
256
|
|
|
217
|
-
|
|
218
|
-
|
|
257
|
+
// The services which might be to be resolved are the one that have a URL and either
|
|
258
|
+
// no path defined (which means no environment variable set) or a non-existing path (which means not resolved yet)
|
|
259
|
+
const resolvableServices = config.services.filter(service => {
|
|
260
|
+
if (!service.url) {
|
|
261
|
+
return false
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (service.path && existsSync(service.path)) {
|
|
265
|
+
logger.warn(`Skipping service ${bold(service.id)} as the path already exists.`)
|
|
266
|
+
return false
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return true
|
|
270
|
+
})
|
|
219
271
|
|
|
220
|
-
|
|
272
|
+
// Iterate the services a first time to verify the environment files configuration and which services must be resolved
|
|
273
|
+
const toResolve = []
|
|
274
|
+
|
|
275
|
+
// Simply use service.path here
|
|
276
|
+
for (const service of resolvableServices) {
|
|
277
|
+
if (!service.path) {
|
|
278
|
+
service.path = resolve(root, `${config.resolvedServicesBasePath}/${service.id}`)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const directory = resolve(root, service.path)
|
|
282
|
+
|
|
283
|
+
// If the directory already exists, it's either external or already resolved, nothing to do in both cases
|
|
284
|
+
if (!existsSync(directory)) {
|
|
285
|
+
if (!directory.startsWith(root)) {
|
|
286
|
+
logger.warn(
|
|
287
|
+
`Skipping service ${bold(service.id)} as the non existent directory ${bold(service.path)} is outside the project directory.`
|
|
288
|
+
)
|
|
289
|
+
} else {
|
|
290
|
+
// This repository must be resolved
|
|
291
|
+
toResolve.push(service)
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
logger.warn(
|
|
295
|
+
`Skipping service ${bold(service.id)} as the generated path ${bold(join(config.resolvedServicesBasePath, service.id))} already exists.`
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Resolve the services
|
|
301
|
+
for (const service of toResolve) {
|
|
302
|
+
let operation
|
|
303
|
+
const childLogger = logger.child({ name: service.id })
|
|
304
|
+
overrideFatal(childLogger)
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const absolutePath = service.path
|
|
308
|
+
const relativePath = relative(root, absolutePath)
|
|
309
|
+
|
|
310
|
+
// Clone and install dependencies
|
|
311
|
+
operation = 'clone repository'
|
|
312
|
+
childLogger.info(`Resolving service ${bold(service.id)} ...`)
|
|
313
|
+
|
|
314
|
+
let url = service.url
|
|
315
|
+
if (url.startsWith('http') && username && password) {
|
|
316
|
+
const parsed = new URL(url)
|
|
317
|
+
parsed.username ||= username
|
|
318
|
+
parsed.password ||= password
|
|
319
|
+
url = parsed.toString()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (username) {
|
|
323
|
+
childLogger.info(`Cloning ${bold(service.url)} as user ${bold(username)} into ${bold(relativePath)} ...`)
|
|
324
|
+
} else {
|
|
325
|
+
childLogger.info(`Cloning ${bold(service.url)} into ${bold(relativePath)} ...`)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
await execa('git', ['clone', url, absolutePath])
|
|
329
|
+
|
|
330
|
+
if (!skipDependencies) {
|
|
331
|
+
operation = 'installing dependencies'
|
|
332
|
+
childLogger.info(`Installing dependencies for service ${bold(service.id)} ...`)
|
|
333
|
+
await execa(packageManager, ['install'], { cwd: absolutePath })
|
|
334
|
+
}
|
|
335
|
+
} catch (error) {
|
|
336
|
+
childLogger.fatal({ error: ensureLoggableError(error) }, `Unable to ${operation} of service ${bold(service.id)}`)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
221
339
|
}
|
|
222
340
|
|
|
223
341
|
export async function importCommand (logger, args) {
|
|
224
|
-
const {
|
|
342
|
+
const {
|
|
343
|
+
values: { id, http },
|
|
344
|
+
positionals
|
|
345
|
+
} = parseArgs(
|
|
225
346
|
args,
|
|
226
347
|
{
|
|
227
348
|
id: {
|
|
228
349
|
type: 'string',
|
|
229
350
|
short: 'i'
|
|
230
351
|
},
|
|
231
|
-
path: {
|
|
232
|
-
type: 'string',
|
|
233
|
-
short: 'p'
|
|
234
|
-
},
|
|
235
352
|
http: {
|
|
236
353
|
type: 'boolean',
|
|
237
354
|
short: 'h'
|
|
@@ -271,15 +388,15 @@ export async function importCommand (logger, args) {
|
|
|
271
388
|
})
|
|
272
389
|
|
|
273
390
|
if (local) {
|
|
274
|
-
return importLocal(logger, root, configurationFile, local)
|
|
391
|
+
return importLocal(logger, root, configurationFile, local, id)
|
|
275
392
|
}
|
|
276
393
|
|
|
277
|
-
return importURL(logger, root, configurationFile,
|
|
394
|
+
return importURL(logger, root, configurationFile, rawUrl, id, http)
|
|
278
395
|
}
|
|
279
396
|
|
|
280
397
|
export async function resolveCommand (logger, args) {
|
|
281
398
|
const {
|
|
282
|
-
values: { username, password, 'skip-dependencies': skipDependencies },
|
|
399
|
+
values: { username, password, 'skip-dependencies': skipDependencies, 'package-manager': packageManager },
|
|
283
400
|
positionals
|
|
284
401
|
} = parseArgs(
|
|
285
402
|
args,
|
|
@@ -298,6 +415,10 @@ export async function resolveCommand (logger, args) {
|
|
|
298
415
|
type: 'boolean',
|
|
299
416
|
short: 's',
|
|
300
417
|
default: false
|
|
418
|
+
},
|
|
419
|
+
'package-manager': {
|
|
420
|
+
type: 'string',
|
|
421
|
+
short: 'p'
|
|
301
422
|
}
|
|
302
423
|
},
|
|
303
424
|
false
|
|
@@ -305,56 +426,9 @@ export async function resolveCommand (logger, args) {
|
|
|
305
426
|
|
|
306
427
|
/* c8 ignore next */
|
|
307
428
|
const root = resolve(process.cwd(), positionals[0] ?? '')
|
|
308
|
-
|
|
309
429
|
const configurationFile = await findConfigurationFile(logger, root)
|
|
310
|
-
const config = await parseConfiguration(logger, configurationFile)
|
|
311
|
-
|
|
312
|
-
for (const service of config.services) {
|
|
313
|
-
let operation
|
|
314
|
-
const childLogger = logger.child({ name: service.id })
|
|
315
|
-
overrideFatal(childLogger)
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
if (!service.url) {
|
|
319
|
-
continue
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
childLogger.info(`Resolving service ${bold(service.id)} ...`)
|
|
323
|
-
|
|
324
|
-
const relativePath = relative(root, service.path)
|
|
325
|
-
|
|
326
|
-
// Check that the target directory is empty, otherwise cloning will likely fail
|
|
327
|
-
await checkEmptyDirectory(childLogger, service.path, relativePath)
|
|
328
|
-
|
|
329
|
-
operation = 'clone repository'
|
|
330
|
-
|
|
331
|
-
let url = service.url
|
|
332
|
-
|
|
333
|
-
if (url.startsWith('http') && username && password) {
|
|
334
|
-
const parsed = new URL(url)
|
|
335
|
-
parsed.username ||= username
|
|
336
|
-
parsed.password ||= password
|
|
337
|
-
url = parsed.toString()
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (username) {
|
|
341
|
-
childLogger.info(`Cloning ${bold(service.url)} as user ${bold(username)} into ${bold(relativePath)} ...`)
|
|
342
|
-
} else {
|
|
343
|
-
childLogger.info(`Cloning ${bold(service.url)} into ${bold(relativePath)} ...`)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
await execa('git', ['clone', url, service.path])
|
|
347
|
-
|
|
348
|
-
if (!skipDependencies) {
|
|
349
|
-
operation = 'installing dependencies'
|
|
350
|
-
childLogger.info('Installing dependencies ...')
|
|
351
|
-
await execa('npm', ['i'], { cwd: service.path })
|
|
352
|
-
}
|
|
353
|
-
} catch (error) {
|
|
354
|
-
childLogger.fatal({ error: ensureLoggableError(error) }, `Unable to ${operation} of service ${bold(service.id)}`)
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
430
|
|
|
431
|
+
await resolveServices(logger, root, configurationFile, username, password, skipDependencies, packageManager)
|
|
358
432
|
logger.done('All services have been resolved.')
|
|
359
433
|
}
|
|
360
434
|
|
|
@@ -377,10 +451,6 @@ export const help = {
|
|
|
377
451
|
usage: '-i, --id <value>',
|
|
378
452
|
description: 'The id of the service (the default is the basename of the URL)'
|
|
379
453
|
},
|
|
380
|
-
{
|
|
381
|
-
usage: '-p, --path <value>',
|
|
382
|
-
description: 'The path where to import the service (the default is the service id)'
|
|
383
|
-
},
|
|
384
454
|
{
|
|
385
455
|
usage: '-h, --http',
|
|
386
456
|
description: 'Use HTTP URL when expanding GitHub repositories'
|
|
@@ -408,6 +478,10 @@ export const help = {
|
|
|
408
478
|
{
|
|
409
479
|
usage: '-s, --skip-dependencies',
|
|
410
480
|
description: 'Do not install services dependencies'
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
usage: 'P, --package-manager',
|
|
484
|
+
description: 'Use an alternative package manager (the default is to autodetect it)'
|
|
411
485
|
}
|
|
412
486
|
],
|
|
413
487
|
footer: `
|
package/lib/commands/init.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ConfigManager } from '@platformatic/config'
|
|
2
2
|
import { ensureLoggableError } from '@platformatic/utils'
|
|
3
3
|
import { bold } from 'colorette'
|
|
4
|
-
import { existsSync
|
|
4
|
+
import { existsSync } from 'node:fs'
|
|
5
5
|
import { mkdir, stat, writeFile } from 'node:fs/promises'
|
|
6
6
|
import { basename, resolve } from 'node:path'
|
|
7
7
|
import { defaultConfiguration, defaultPackageJson } from '../defaults.js'
|
|
8
8
|
import { gitignore } from '../gitignore.js'
|
|
9
9
|
import { schema, version } from '../schema.js'
|
|
10
|
-
import { parseArgs, verbose } from '../utils.js'
|
|
10
|
+
import { parseArgs, saveConfigurationFile, verbose } from '../utils.js'
|
|
11
11
|
|
|
12
12
|
export async function initCommand (logger, args) {
|
|
13
13
|
const {
|
|
@@ -57,7 +57,7 @@ export async function initCommand (logger, args) {
|
|
|
57
57
|
|
|
58
58
|
// Create the web folder, will implicitly create the root
|
|
59
59
|
try {
|
|
60
|
-
await mkdir(web, { recursive: true
|
|
60
|
+
await mkdir(web, { recursive: true })
|
|
61
61
|
/* c8 ignore next 6 */
|
|
62
62
|
} catch (error) {
|
|
63
63
|
logger.fatal(
|
|
@@ -76,11 +76,11 @@ export async function initCommand (logger, args) {
|
|
|
76
76
|
|
|
77
77
|
await configManager.parse()
|
|
78
78
|
|
|
79
|
-
await
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
79
|
+
await saveConfigurationFile(logger, configurationFile, {
|
|
80
|
+
$schema: schema.$id,
|
|
81
|
+
...configManager.current,
|
|
82
|
+
entrypoint: positionals[1] ?? undefined
|
|
83
|
+
})
|
|
84
84
|
|
|
85
85
|
const packageJson = {
|
|
86
86
|
name: basename(root),
|
|
@@ -89,13 +89,13 @@ export async function initCommand (logger, args) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
if (packageManager === 'npm') {
|
|
92
|
-
packageJson.workspaces = ['web/*']
|
|
92
|
+
packageJson.workspaces = ['web/*', 'external/*']
|
|
93
93
|
} else if (packageManager === 'pnpm') {
|
|
94
|
-
await
|
|
94
|
+
await saveConfigurationFile(logger, resolve(root, 'pnpm-workspace.yaml'), { packages: ['web/*', 'external/*'] })
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// Write the package.json file
|
|
98
|
-
await
|
|
98
|
+
await saveConfigurationFile(logger, resolve(root, 'package.json'), packageJson)
|
|
99
99
|
|
|
100
100
|
// Write the .gitignore file
|
|
101
101
|
await writeFile(resolve(root, '.gitignore'), gitignore, 'utf-8')
|
|
@@ -116,6 +116,12 @@ export const help = {
|
|
|
116
116
|
name: 'entrypoint',
|
|
117
117
|
description: 'The name of the entrypoint service'
|
|
118
118
|
}
|
|
119
|
+
],
|
|
120
|
+
options: [
|
|
121
|
+
{
|
|
122
|
+
usage: 'p, --package-manager',
|
|
123
|
+
description: 'Use an alternative package manager'
|
|
124
|
+
}
|
|
119
125
|
]
|
|
120
126
|
}
|
|
121
127
|
}
|
package/lib/gitignore.js
CHANGED
package/lib/utils.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ConfigManager,
|
|
3
|
+
getParser,
|
|
4
|
+
getStringifier,
|
|
5
|
+
loadConfig as pltConfigLoadConfig,
|
|
6
|
+
Store
|
|
7
|
+
} from '@platformatic/config'
|
|
2
8
|
import { platformaticRuntime, buildRuntime as pltBuildRuntime } from '@platformatic/runtime'
|
|
3
9
|
import { bgGreen, black, bold } from 'colorette'
|
|
10
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
4
11
|
import { dirname, resolve } from 'node:path'
|
|
5
12
|
import { parseArgs as nodeParseArgs } from 'node:util'
|
|
6
|
-
import pino from 'pino'
|
|
13
|
+
import { pino } from 'pino'
|
|
7
14
|
import pinoPretty from 'pino-pretty'
|
|
8
15
|
|
|
9
16
|
export let verbose = false
|
|
@@ -70,7 +77,12 @@ export function parseArgs (args, options, stopAtFirstPositional = true) {
|
|
|
70
77
|
tokens: true
|
|
71
78
|
})
|
|
72
79
|
|
|
73
|
-
return {
|
|
80
|
+
return {
|
|
81
|
+
values,
|
|
82
|
+
positionals,
|
|
83
|
+
unparsed,
|
|
84
|
+
tokens
|
|
85
|
+
}
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
export function getMatchingRuntimeArgs (logger, positional) {
|
|
@@ -85,12 +97,16 @@ export function getMatchingRuntimeArgs (logger, positional) {
|
|
|
85
97
|
return args
|
|
86
98
|
}
|
|
87
99
|
|
|
100
|
+
export function serviceToEnvVariable (service) {
|
|
101
|
+
return `PLT_SERVICE_${service.toUpperCase().replaceAll(/[^A-Z0-9_]/g, '_')}_PATH`
|
|
102
|
+
}
|
|
103
|
+
|
|
88
104
|
export async function findConfigurationFile (logger, root) {
|
|
89
105
|
let current = root
|
|
90
106
|
let configurationFile
|
|
91
107
|
while (configurationFile === undefined) {
|
|
92
108
|
// Find a wattpm.json or watt.json file
|
|
93
|
-
configurationFile = await ConfigManager.findConfigFile(current,
|
|
109
|
+
configurationFile = await ConfigManager.findConfigFile(current, true)
|
|
94
110
|
if (!configurationFile) {
|
|
95
111
|
const newCurrent = dirname(current)
|
|
96
112
|
|
|
@@ -104,7 +120,9 @@ export async function findConfigurationFile (logger, root) {
|
|
|
104
120
|
|
|
105
121
|
if (typeof configurationFile !== 'string') {
|
|
106
122
|
logger.fatal(
|
|
107
|
-
`Cannot find a
|
|
123
|
+
`Cannot find a supported Watt configuration file (like ${bold(
|
|
124
|
+
'watt.json'
|
|
125
|
+
)}, a ${bold('wattpm.json')} or a ${bold('platformatic.json')}) in ${bold(root)}.`
|
|
108
126
|
)
|
|
109
127
|
}
|
|
110
128
|
|
|
@@ -112,11 +130,44 @@ export async function findConfigurationFile (logger, root) {
|
|
|
112
130
|
return resolved
|
|
113
131
|
}
|
|
114
132
|
|
|
133
|
+
export async function loadConfigurationFile (logger, configurationFile) {
|
|
134
|
+
const store = new Store({
|
|
135
|
+
cwd: process.cwd(),
|
|
136
|
+
logger
|
|
137
|
+
})
|
|
138
|
+
store.add(platformaticRuntime)
|
|
139
|
+
|
|
140
|
+
const { configManager } = await store.loadConfig({
|
|
141
|
+
config: configurationFile,
|
|
142
|
+
overrides: {
|
|
143
|
+
/* c8 ignore next 3 */
|
|
144
|
+
onMissingEnv (key) {
|
|
145
|
+
return ''
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
await configManager.parse(true, [])
|
|
151
|
+
return configManager.current
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function loadRawConfigurationFile (_, configurationFile) {
|
|
155
|
+
const parseConfig = getParser(configurationFile)
|
|
156
|
+
|
|
157
|
+
return parseConfig(await readFile(configurationFile, 'utf-8'))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function saveConfigurationFile (logger, configurationFile, config) {
|
|
161
|
+
const stringifyConfig = getStringifier(configurationFile)
|
|
162
|
+
|
|
163
|
+
return writeFile(configurationFile, stringifyConfig(config), 'utf-8')
|
|
164
|
+
}
|
|
165
|
+
|
|
115
166
|
export async function buildRuntime (logger, configurationFile) {
|
|
116
167
|
const store = new Store()
|
|
117
168
|
store.add(platformaticRuntime)
|
|
118
169
|
|
|
119
|
-
const config = await pltConfigLoadConfig({}, ['-c', configurationFile], store, {}, true)
|
|
170
|
+
const config = await pltConfigLoadConfig({}, ['-c', configurationFile], store, {}, true, logger)
|
|
120
171
|
config.configManager.args = config.args
|
|
121
172
|
|
|
122
173
|
const runtimeConfig = config.configManager
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wattpm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0-alpha.1",
|
|
4
4
|
"description": "The Node.js Application Server",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"colorette": "^2.0.20",
|
|
29
29
|
"commist": "^3.2.0",
|
|
30
|
+
"dotenv": "^16.4.5",
|
|
30
31
|
"execa": "^9.4.0",
|
|
31
32
|
"help-me": "^5.0.0",
|
|
32
33
|
"minimist": "^1.2.8",
|
|
@@ -34,11 +35,11 @@
|
|
|
34
35
|
"pino-pretty": "^13.0.0",
|
|
35
36
|
"split2": "^4.2.0",
|
|
36
37
|
"table": "^6.8.2",
|
|
37
|
-
"@platformatic/basic": "2.
|
|
38
|
-
"@platformatic/
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/runtime": "2.
|
|
41
|
-
"@platformatic/utils": "2.
|
|
38
|
+
"@platformatic/basic": "2.16.0-alpha.1",
|
|
39
|
+
"@platformatic/control": "2.16.0-alpha.1",
|
|
40
|
+
"@platformatic/config": "2.16.0-alpha.1",
|
|
41
|
+
"@platformatic/runtime": "2.16.0-alpha.1",
|
|
42
|
+
"@platformatic/utils": "2.16.0-alpha.1"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"borp": "^0.18.0",
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"neostandard": "^0.11.1",
|
|
49
50
|
"typescript": "^5.5.4",
|
|
50
51
|
"undici": "^6.19.8",
|
|
51
|
-
"@platformatic/node": "2.
|
|
52
|
+
"@platformatic/node": "2.16.0-alpha.1"
|
|
52
53
|
},
|
|
53
54
|
"scripts": {
|
|
54
55
|
"test": "npm run lint && borp --concurrency=1 --timeout=300000",
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/wattpm/2.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/wattpm/2.16.0-alpha.1.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -183,6 +183,7 @@
|
|
|
183
183
|
},
|
|
184
184
|
"path": {
|
|
185
185
|
"type": "string",
|
|
186
|
+
"allowEmptyPaths": true,
|
|
186
187
|
"resolvePath": true
|
|
187
188
|
},
|
|
188
189
|
"config": {
|
|
@@ -335,6 +336,7 @@
|
|
|
335
336
|
},
|
|
336
337
|
"path": {
|
|
337
338
|
"type": "string",
|
|
339
|
+
"allowEmptyPaths": true,
|
|
338
340
|
"resolvePath": true
|
|
339
341
|
},
|
|
340
342
|
"config": {
|
|
@@ -1078,6 +1080,10 @@
|
|
|
1078
1080
|
}
|
|
1079
1081
|
],
|
|
1080
1082
|
"default": 300000
|
|
1083
|
+
},
|
|
1084
|
+
"resolvedServicesBasePath": {
|
|
1085
|
+
"type": "string",
|
|
1086
|
+
"default": "external"
|
|
1081
1087
|
}
|
|
1082
1088
|
},
|
|
1083
1089
|
"anyOf": [
|