@platformatic/runtime 3.4.1 → 3.5.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/README.md +1 -1
- package/config.d.ts +224 -77
- package/eslint.config.js +3 -5
- package/index.d.ts +73 -24
- package/index.js +173 -29
- package/lib/config.js +279 -197
- package/lib/errors.js +126 -34
- package/lib/generator.js +640 -0
- package/lib/logger.js +43 -41
- package/lib/management-api.js +109 -118
- package/lib/prom-server.js +202 -16
- package/lib/runtime.js +1963 -585
- package/lib/scheduler.js +119 -0
- package/lib/schema.js +22 -234
- package/lib/shared-http-cache.js +43 -0
- package/lib/upgrade.js +6 -8
- package/lib/utils.js +6 -61
- package/lib/version.js +7 -0
- package/lib/versions/v1.36.0.js +2 -4
- package/lib/versions/v1.5.0.js +2 -4
- package/lib/versions/v2.0.0.js +3 -5
- package/lib/versions/v3.0.0.js +16 -0
- package/lib/worker/controller.js +302 -0
- package/lib/worker/http-cache.js +171 -0
- package/lib/worker/interceptors.js +190 -10
- package/lib/worker/itc.js +146 -59
- package/lib/worker/main.js +220 -81
- package/lib/worker/messaging.js +182 -0
- package/lib/worker/round-robin-map.js +62 -0
- package/lib/worker/shared-context.js +22 -0
- package/lib/worker/symbols.js +14 -5
- package/package.json +47 -38
- package/schema.json +1383 -55
- package/help/compile.txt +0 -8
- package/help/help.txt +0 -5
- package/help/start.txt +0 -21
- package/index.test-d.ts +0 -41
- package/lib/build-server.js +0 -69
- package/lib/compile.js +0 -98
- package/lib/dependencies.js +0 -59
- package/lib/generator/README.md +0 -32
- package/lib/generator/errors.js +0 -10
- package/lib/generator/runtime-generator.d.ts +0 -37
- package/lib/generator/runtime-generator.js +0 -498
- package/lib/start.js +0 -190
- package/lib/worker/app.js +0 -278
- package/lib/worker/default-stackable.js +0 -33
- package/lib/worker/metrics.js +0 -122
- package/runtime.mjs +0 -54
package/lib/generator.js
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import createError from '@fastify/error'
|
|
2
|
+
import {
|
|
3
|
+
defaultPackageManager,
|
|
4
|
+
findConfigurationFile,
|
|
5
|
+
generateDashedName,
|
|
6
|
+
kMetadata,
|
|
7
|
+
loadConfiguration,
|
|
8
|
+
loadConfigurationFile,
|
|
9
|
+
safeRemove
|
|
10
|
+
} from '@platformatic/foundation'
|
|
11
|
+
import { BaseGenerator, envObjectToString, getApplicationTemplateFromSchemaUrl } from '@platformatic/generators'
|
|
12
|
+
import { existsSync } from 'node:fs'
|
|
13
|
+
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
14
|
+
import { createRequire } from 'node:module'
|
|
15
|
+
import { basename, join } from 'node:path'
|
|
16
|
+
import { pathToFileURL } from 'node:url'
|
|
17
|
+
import { transform } from './config.js'
|
|
18
|
+
import { schema } from './schema.js'
|
|
19
|
+
import { getArrayDifference } from './utils.js'
|
|
20
|
+
|
|
21
|
+
const wrappableProperties = {
|
|
22
|
+
logger: {
|
|
23
|
+
level: '{PLT_SERVER_LOGGER_LEVEL}'
|
|
24
|
+
},
|
|
25
|
+
server: {
|
|
26
|
+
hostname: '{PLT_SERVER_HOSTNAME}',
|
|
27
|
+
port: '{PORT}'
|
|
28
|
+
},
|
|
29
|
+
managementApi: '{PLT_MANAGEMENT_API}'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const engines = {
|
|
33
|
+
node: '>=22.19.0'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const ERROR_PREFIX = 'PLT_RUNTIME_GEN'
|
|
37
|
+
|
|
38
|
+
const NoApplicationNamedError = createError(
|
|
39
|
+
`${ERROR_PREFIX}_NO_APPLICATION_FOUND`,
|
|
40
|
+
"No application named '%s' has been added to this runtime."
|
|
41
|
+
)
|
|
42
|
+
const NoEntryPointError = createError(`${ERROR_PREFIX}_NO_ENTRYPOINT`, 'No entrypoint had been defined.')
|
|
43
|
+
|
|
44
|
+
function getRuntimeBaseEnvVars (config) {
|
|
45
|
+
return {
|
|
46
|
+
PLT_SERVER_HOSTNAME: '127.0.0.1',
|
|
47
|
+
PORT: config.port || 3042,
|
|
48
|
+
PLT_SERVER_LOGGER_LEVEL: config.logLevel || 'info',
|
|
49
|
+
PLT_MANAGEMENT_API: true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// This is needed as dotenv-tool is not loading with ESM currently
|
|
54
|
+
export function createDotenvTool (...args) {
|
|
55
|
+
const { DotEnvTool } = createRequire(import.meta.url)('dotenv-tool')
|
|
56
|
+
return new DotEnvTool(...args)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class RuntimeGenerator extends BaseGenerator {
|
|
60
|
+
constructor (opts) {
|
|
61
|
+
super({
|
|
62
|
+
...opts,
|
|
63
|
+
module: '@platformatic/runtime'
|
|
64
|
+
})
|
|
65
|
+
this.runtimeName = opts.name
|
|
66
|
+
this.applicationsFolder = opts.applicationsFolder ?? 'applications'
|
|
67
|
+
this.applications = []
|
|
68
|
+
this.existingApplications = []
|
|
69
|
+
this.entryPoint = null
|
|
70
|
+
this.packageManager = opts.packageManager ?? defaultPackageManager
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async addApplication (application, name) {
|
|
74
|
+
// ensure application config is correct
|
|
75
|
+
const originalConfig = application.config
|
|
76
|
+
const applicationName = name || generateDashedName()
|
|
77
|
+
const newConfig = {
|
|
78
|
+
...originalConfig,
|
|
79
|
+
isRuntimeContext: true,
|
|
80
|
+
applicationName
|
|
81
|
+
}
|
|
82
|
+
// reset all files previously generated by the application
|
|
83
|
+
application.reset()
|
|
84
|
+
application.setConfig(newConfig)
|
|
85
|
+
this.applications.push({
|
|
86
|
+
name: applicationName,
|
|
87
|
+
application
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
application.setRuntime(this)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setEntryPoint (entryPoint) {
|
|
94
|
+
const application =
|
|
95
|
+
this.existingApplications.includes(entryPoint) || this.applications.find(svc => svc.name === entryPoint)
|
|
96
|
+
if (!application) {
|
|
97
|
+
throw new NoApplicationNamedError(entryPoint)
|
|
98
|
+
}
|
|
99
|
+
this.entryPoint = application
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async generatePackageJson () {
|
|
103
|
+
const template = {
|
|
104
|
+
name: `${this.runtimeName}`,
|
|
105
|
+
scripts: {
|
|
106
|
+
dev: this.config.devCommand,
|
|
107
|
+
build: this.config.buildCommand,
|
|
108
|
+
start: this.config.startCommand ?? 'platformatic start'
|
|
109
|
+
},
|
|
110
|
+
devDependencies: {
|
|
111
|
+
fastify: `^${this.fastifyVersion}`
|
|
112
|
+
},
|
|
113
|
+
dependencies: {
|
|
114
|
+
'@platformatic/runtime': `^${this.platformaticVersion}`,
|
|
115
|
+
platformatic: `^${this.platformaticVersion}`,
|
|
116
|
+
wattpm: `^${this.platformaticVersion}`,
|
|
117
|
+
...this.config.dependencies
|
|
118
|
+
},
|
|
119
|
+
engines
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (this.packageManager === 'npm' || this.packageManager === 'yarn') {
|
|
123
|
+
template.workspaces = [this.applicationsFolder + '/*']
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return template
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async _beforePrepare () {
|
|
130
|
+
this.setApplicationsDirectory()
|
|
131
|
+
this.setApplicationsConfigValues()
|
|
132
|
+
this.addApplicationsDependencies()
|
|
133
|
+
|
|
134
|
+
this.addEnvVars(getRuntimeBaseEnvVars(this.config), { overwrite: false, default: true })
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
addApplicationsDependencies () {
|
|
138
|
+
this.applications.forEach(({ application }) => {
|
|
139
|
+
if (application.config.dependencies) {
|
|
140
|
+
Object.entries(application.config.dependencies).forEach(kv => {
|
|
141
|
+
this.config.dependencies[kv[0]] = kv[1]
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async populateFromExistingConfig () {
|
|
148
|
+
if (this._hasCheckedForExistingConfig) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
this._hasCheckedForExistingConfig = true
|
|
152
|
+
const existingConfigFile = this.runtimeConfig ?? (await findConfigurationFile(this.targetDirectory, 'runtime'))
|
|
153
|
+
if (existingConfigFile && existsSync(join(this.targetDirectory, existingConfigFile))) {
|
|
154
|
+
this.existingConfigRaw = await loadConfigurationFile(join(this.targetDirectory, existingConfigFile))
|
|
155
|
+
this.existingConfig = await loadConfiguration(join(this.targetDirectory, existingConfigFile), schema, {
|
|
156
|
+
transform,
|
|
157
|
+
ignoreProcessEnv: true
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const { PLT_ROOT, ...existingEnvironment } = this.existingConfig[kMetadata].env
|
|
161
|
+
this.config.env = existingEnvironment
|
|
162
|
+
this.config.port = this.config.env.PORT
|
|
163
|
+
this.entryPoint = this.existingConfig.applications.find(svc => svc.entrypoint)
|
|
164
|
+
this.existingApplications = this.existingConfig.applications.map(s => s.id)
|
|
165
|
+
|
|
166
|
+
this.updateRuntimeConfig(this.existingConfigRaw)
|
|
167
|
+
this.updateRuntimeEnv(await readFile(join(this.targetDirectory, '.env'), 'utf-8'))
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async prepare () {
|
|
172
|
+
await this.populateFromExistingConfig()
|
|
173
|
+
if (this.existingConfig) {
|
|
174
|
+
this.setApplicationsDirectory()
|
|
175
|
+
this.setApplicationsConfigValues()
|
|
176
|
+
await this._afterPrepare()
|
|
177
|
+
return {
|
|
178
|
+
env: this.config.env,
|
|
179
|
+
targetDirectory: this.targetDirectory
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
return await super.prepare()
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
setApplicationsConfigValues () {
|
|
187
|
+
this.applications.forEach(({ application }) => {
|
|
188
|
+
if (!application.config) {
|
|
189
|
+
// set default config
|
|
190
|
+
application.setConfig()
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async _getConfigFileContents () {
|
|
196
|
+
const config = {
|
|
197
|
+
$schema: `https://schemas.platformatic.dev/@platformatic/runtime/${this.platformaticVersion}.json`,
|
|
198
|
+
entrypoint: this.entryPoint.name,
|
|
199
|
+
watch: true,
|
|
200
|
+
autoload: {
|
|
201
|
+
path: this.config.autoload || this.applicationsFolder,
|
|
202
|
+
exclude: ['docs']
|
|
203
|
+
},
|
|
204
|
+
...wrappableProperties
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return config
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async _afterPrepare () {
|
|
211
|
+
if (!this.entryPoint) {
|
|
212
|
+
throw new NoEntryPointError()
|
|
213
|
+
}
|
|
214
|
+
const applicationsEnv = await this.prepareApplicationFiles()
|
|
215
|
+
this.addEnvVars({
|
|
216
|
+
...this.config.env,
|
|
217
|
+
...this.getRuntimeEnv(),
|
|
218
|
+
...applicationsEnv
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
this.updateRuntimeEnv(envObjectToString(this.config.env))
|
|
222
|
+
|
|
223
|
+
this.addFile({
|
|
224
|
+
path: '',
|
|
225
|
+
file: '.env.sample',
|
|
226
|
+
contents: envObjectToString(this.config.defaultEnv)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
targetDirectory: this.targetDirectory,
|
|
231
|
+
env: applicationsEnv
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async writeFiles () {
|
|
236
|
+
for (const { application } of this.applications) {
|
|
237
|
+
await application._beforeWriteFiles?.(this)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
await super.writeFiles()
|
|
241
|
+
|
|
242
|
+
if (!this.config.isUpdating) {
|
|
243
|
+
for (const { application } of this.applications) {
|
|
244
|
+
await application.writeFiles()
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (const { application } of this.applications) {
|
|
249
|
+
await application._afterWriteFiles?.(this)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async prepareQuestions () {
|
|
254
|
+
await this.populateFromExistingConfig()
|
|
255
|
+
|
|
256
|
+
if (this.existingConfig) {
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// port
|
|
261
|
+
this.questions.push({
|
|
262
|
+
type: 'input',
|
|
263
|
+
name: 'port',
|
|
264
|
+
default: 3042,
|
|
265
|
+
message: 'What port do you want to use?'
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
setApplicationsDirectory () {
|
|
270
|
+
this.applications.forEach(({ application }) => {
|
|
271
|
+
if (!application.config) {
|
|
272
|
+
// set default config
|
|
273
|
+
application.setConfig()
|
|
274
|
+
}
|
|
275
|
+
let basePath
|
|
276
|
+
if (this.existingConfig) {
|
|
277
|
+
basePath = this.existingConfig.autoload.path
|
|
278
|
+
} else {
|
|
279
|
+
basePath = join(this.targetDirectory, this.config.autoload || this.applicationsFolder)
|
|
280
|
+
}
|
|
281
|
+
this.applicationsBasePath = basePath
|
|
282
|
+
application.setTargetDirectory(join(basePath, application.config.applicationName))
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
setApplicationsConfig (configToOverride) {
|
|
287
|
+
this.applications.forEach(application => {
|
|
288
|
+
const originalConfig = application.config
|
|
289
|
+
application.setConfig({
|
|
290
|
+
...originalConfig,
|
|
291
|
+
...configToOverride
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async prepareApplicationFiles () {
|
|
297
|
+
let applicationsEnv = {}
|
|
298
|
+
for (const svc of this.applications) {
|
|
299
|
+
const svcEnv = await svc.application.prepare()
|
|
300
|
+
applicationsEnv = {
|
|
301
|
+
...applicationsEnv,
|
|
302
|
+
...svcEnv.env
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return applicationsEnv
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
getConfigFieldsDefinitions () {
|
|
309
|
+
return []
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
setConfigFields () {
|
|
313
|
+
// do nothing, makes no sense
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
getRuntimeEnv () {
|
|
317
|
+
return {
|
|
318
|
+
PORT: this.config.port
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async postInstallActions () {
|
|
323
|
+
for (const { application } of this.applications) {
|
|
324
|
+
await application.postInstallActions()
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async _getGeneratorForTemplate (dir, pkg) {
|
|
329
|
+
const _require = createRequire(dir)
|
|
330
|
+
const fileToImport = _require.resolve(pkg)
|
|
331
|
+
return (await import(pathToFileURL(fileToImport))).Generator
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async loadFromDir () {
|
|
335
|
+
const output = {
|
|
336
|
+
applications: []
|
|
337
|
+
}
|
|
338
|
+
const runtimePkgConfigFileData = JSON.parse(await readFile(join(this.targetDirectory, this.runtimeConfig), 'utf-8'))
|
|
339
|
+
const applicationsPath = join(this.targetDirectory, runtimePkgConfigFileData.autoload.path)
|
|
340
|
+
|
|
341
|
+
// load all applications
|
|
342
|
+
const allApplications = await readdir(applicationsPath)
|
|
343
|
+
for (const s of allApplications) {
|
|
344
|
+
// check is a directory
|
|
345
|
+
const currentApplicationPath = join(applicationsPath, s)
|
|
346
|
+
const dirStat = await stat(currentApplicationPath)
|
|
347
|
+
if (dirStat.isDirectory()) {
|
|
348
|
+
// load the application config
|
|
349
|
+
const configFile = await findConfigurationFile(currentApplicationPath)
|
|
350
|
+
const applicationPltJson = JSON.parse(await readFile(join(currentApplicationPath, configFile), 'utf-8'))
|
|
351
|
+
// get module to load
|
|
352
|
+
const template = applicationPltJson.module || getApplicationTemplateFromSchemaUrl(applicationPltJson.$schema)
|
|
353
|
+
const Generator = await this._getGeneratorForTemplate(currentApplicationPath, template)
|
|
354
|
+
const instance = new Generator({
|
|
355
|
+
logger: this.logger
|
|
356
|
+
})
|
|
357
|
+
this.addApplication(instance, s)
|
|
358
|
+
output.applications.push(await instance.loadFromDir(s, this.targetDirectory))
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return output
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async update (newConfig) {
|
|
365
|
+
let allApplicationsDependencies = {}
|
|
366
|
+
const runtimeAddedEnvKeys = []
|
|
367
|
+
|
|
368
|
+
this.config.isUpdating = true
|
|
369
|
+
const currrentPackageJson = JSON.parse(await readFile(join(this.targetDirectory, 'package.json'), 'utf-8'))
|
|
370
|
+
const currentRuntimeDependencies = currrentPackageJson.dependencies
|
|
371
|
+
// check all applications are present with the same template
|
|
372
|
+
const allCurrentApplicationsNames = this.applications.map(s => s.name)
|
|
373
|
+
const allNewApplicationsNames = newConfig.applications.map(s => s.name)
|
|
374
|
+
// load dotenv tool
|
|
375
|
+
const envTool = createDotenvTool({
|
|
376
|
+
path: join(this.targetDirectory, '.env')
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
await envTool.load()
|
|
380
|
+
|
|
381
|
+
const removedApplications = getArrayDifference(allCurrentApplicationsNames, allNewApplicationsNames)
|
|
382
|
+
if (removedApplications.length > 0) {
|
|
383
|
+
for (const removedApplication of removedApplications) {
|
|
384
|
+
// handle application delete
|
|
385
|
+
|
|
386
|
+
// delete env variables
|
|
387
|
+
const s = this.applications.find(f => f.name === removedApplication)
|
|
388
|
+
const allKeys = envTool.getKeys()
|
|
389
|
+
allKeys.forEach(k => {
|
|
390
|
+
if (k.startsWith(`PLT_${s.application.config.envPrefix}`)) {
|
|
391
|
+
envTool.deleteKey(k)
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// delete dependencies
|
|
396
|
+
const applicationPath = join(this.targetDirectory, this.applicationsFolder, s.name)
|
|
397
|
+
const configFile = await findConfigurationFile(applicationPath)
|
|
398
|
+
const applicationPackageJson = JSON.parse(await readFile(join(applicationPath, configFile), 'utf-8'))
|
|
399
|
+
if (applicationPackageJson.plugins && applicationPackageJson.plugins.packages) {
|
|
400
|
+
applicationPackageJson.plugins.packages.forEach(p => {
|
|
401
|
+
delete currrentPackageJson.dependencies[p.name]
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
// delete directory
|
|
405
|
+
await safeRemove(join(this.targetDirectory, this.applicationsFolder, s.name))
|
|
406
|
+
}
|
|
407
|
+
// throw new CannotRemoveApplicationOnUpdateError(removedApplications.join(', '))
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// handle new applications
|
|
411
|
+
for (const newApplication of newConfig.applications) {
|
|
412
|
+
// create generator for the application
|
|
413
|
+
const ApplicationGenerator = await this._getGeneratorForTemplate(
|
|
414
|
+
join(this.targetDirectory, 'package.json'),
|
|
415
|
+
newApplication.template
|
|
416
|
+
)
|
|
417
|
+
const applicationInstance = new ApplicationGenerator({
|
|
418
|
+
logger: this.logger
|
|
419
|
+
})
|
|
420
|
+
const baseConfig = {
|
|
421
|
+
isRuntimeContext: true,
|
|
422
|
+
targetDirectory: join(this.targetDirectory, this.applicationsFolder, newApplication.name),
|
|
423
|
+
applicationName: newApplication.name,
|
|
424
|
+
plugin: true
|
|
425
|
+
}
|
|
426
|
+
if (allCurrentApplicationsNames.includes(newApplication.name)) {
|
|
427
|
+
// update existing applications env values
|
|
428
|
+
// otherwise, is a new application
|
|
429
|
+
baseConfig.isUpdating = true
|
|
430
|
+
|
|
431
|
+
// handle application's plugin differences
|
|
432
|
+
const oldApplicationMetadata = await applicationInstance.loadFromDir(newApplication.name, this.targetDirectory)
|
|
433
|
+
const oldApplicationPackages = oldApplicationMetadata.plugins.map(meta => meta.name)
|
|
434
|
+
const newApplicationPackages = newApplication.plugins.map(meta => meta.name)
|
|
435
|
+
const pluginsToRemove = getArrayDifference(oldApplicationPackages, newApplicationPackages)
|
|
436
|
+
pluginsToRemove.forEach(p => delete currentRuntimeDependencies[p])
|
|
437
|
+
} else {
|
|
438
|
+
// add application to the generator
|
|
439
|
+
this.applications.push({
|
|
440
|
+
name: newApplication.name,
|
|
441
|
+
application: applicationInstance
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
applicationInstance.setConfig(baseConfig)
|
|
445
|
+
applicationInstance.setConfigFields(newApplication.fields)
|
|
446
|
+
|
|
447
|
+
const applicationEnvPrefix = `PLT_${applicationInstance.config.envPrefix}`
|
|
448
|
+
for (const plug of newApplication.plugins) {
|
|
449
|
+
await applicationInstance.addPackage(plug)
|
|
450
|
+
for (const opt of plug.options) {
|
|
451
|
+
const key = `${applicationEnvPrefix}_${opt.name}`
|
|
452
|
+
runtimeAddedEnvKeys.push(key)
|
|
453
|
+
const value = opt.value
|
|
454
|
+
if (envTool.hasKey(key)) {
|
|
455
|
+
envTool.updateKey(key, value)
|
|
456
|
+
} else {
|
|
457
|
+
envTool.addKey(key, value)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
allApplicationsDependencies = { ...allApplicationsDependencies, ...applicationInstance.config.dependencies }
|
|
462
|
+
const afterPrepareMetadata = await applicationInstance.prepare()
|
|
463
|
+
await applicationInstance.writeFiles()
|
|
464
|
+
// cleanup runtime env removing keys not present anymore in application plugins
|
|
465
|
+
const allKeys = envTool.getKeys()
|
|
466
|
+
allKeys.forEach(k => {
|
|
467
|
+
if (k.startsWith(`${applicationEnvPrefix}_FST_PLUGIN`) && !runtimeAddedEnvKeys.includes(k)) {
|
|
468
|
+
envTool.deleteKey(k)
|
|
469
|
+
}
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
// add application env variables to runtime env
|
|
473
|
+
Object.entries(afterPrepareMetadata.env).forEach(([key, value]) => {
|
|
474
|
+
envTool.addKey(key, value)
|
|
475
|
+
})
|
|
476
|
+
}
|
|
477
|
+
// update runtime package.json dependencies
|
|
478
|
+
currrentPackageJson.dependencies = {
|
|
479
|
+
...currrentPackageJson.dependencies,
|
|
480
|
+
...allApplicationsDependencies
|
|
481
|
+
}
|
|
482
|
+
this.addFile({
|
|
483
|
+
path: '',
|
|
484
|
+
file: 'package.json',
|
|
485
|
+
contents: JSON.stringify(currrentPackageJson, null, 2)
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
// set new entrypoint if specified
|
|
489
|
+
const newEntrypoint = newConfig.entrypoint
|
|
490
|
+
if (newEntrypoint) {
|
|
491
|
+
// load platformatic.json runtime config
|
|
492
|
+
const runtimePkgConfigFileData = JSON.parse(
|
|
493
|
+
await readFile(join(this.targetDirectory, this.runtimeConfig), 'utf-8')
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
this.setEntryPoint(newEntrypoint)
|
|
497
|
+
runtimePkgConfigFileData.entrypoint = newEntrypoint
|
|
498
|
+
this.updateRuntimeConfig(runtimePkgConfigFileData)
|
|
499
|
+
}
|
|
500
|
+
await this.writeFiles()
|
|
501
|
+
// save new env
|
|
502
|
+
await envTool.save()
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async generateConfigFile () {
|
|
506
|
+
this.updateRuntimeConfig(await super.generateConfigFile())
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async generateEnv () {
|
|
510
|
+
const serialized = await super.generateEnv()
|
|
511
|
+
|
|
512
|
+
if (serialized) {
|
|
513
|
+
this.updateRuntimeEnv(serialized)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
getRuntimeConfigFileObject () {
|
|
518
|
+
return this.files.find(file => file.tags?.includes('runtime-config')) ?? null
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
getRuntimeEnvFileObject () {
|
|
522
|
+
return this.files.find(file => file.tags?.includes('runtime-env')) ?? null
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
updateRuntimeConfig (config) {
|
|
526
|
+
this.addFile({
|
|
527
|
+
path: '',
|
|
528
|
+
file: this.runtimeConfig,
|
|
529
|
+
contents: JSON.stringify(config, null, 2),
|
|
530
|
+
tags: ['runtime-config']
|
|
531
|
+
})
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
updateRuntimeEnv (contents) {
|
|
535
|
+
this.addFile({
|
|
536
|
+
path: '',
|
|
537
|
+
file: '.env',
|
|
538
|
+
contents,
|
|
539
|
+
tags: ['runtime-env']
|
|
540
|
+
})
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
updateConfigEntryPoint (entrypoint) {
|
|
544
|
+
// This can return null if the generator was not supposed to modify the config
|
|
545
|
+
const configObject = this.getRuntimeConfigFileObject()
|
|
546
|
+
const config = JSON.parse(configObject.contents)
|
|
547
|
+
config.entrypoint = entrypoint
|
|
548
|
+
|
|
549
|
+
this.updateRuntimeConfig(config)
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export class WrappedGenerator extends BaseGenerator {
|
|
554
|
+
async prepare () {
|
|
555
|
+
await this.getPlatformaticVersion()
|
|
556
|
+
await this.#updateEnvironment()
|
|
557
|
+
await this.#updatePackageJson()
|
|
558
|
+
await this.#createConfigFile()
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async #updateEnvironment () {
|
|
562
|
+
this.addEnvVars(getRuntimeBaseEnvVars(this.config), { overwrite: false, default: true })
|
|
563
|
+
|
|
564
|
+
this.addFile({
|
|
565
|
+
path: '',
|
|
566
|
+
file: '.env',
|
|
567
|
+
contents: (await this.#readExistingFile('.env', '', '\n')) + envObjectToString(this.config.env)
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
this.addFile({
|
|
571
|
+
path: '',
|
|
572
|
+
file: '.env.sample',
|
|
573
|
+
contents: (await this.#readExistingFile('.env.sample', '', '\n')) + envObjectToString(this.config.defaultEnv)
|
|
574
|
+
})
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async #updatePackageJson () {
|
|
578
|
+
// Manipulate the package.json, if any
|
|
579
|
+
const packageJson = JSON.parse(await this.#readExistingFile('package.json', '{}'))
|
|
580
|
+
let { name, dependencies, devDependencies, scripts, engines: packageJsonEngines, ...rest } = packageJson
|
|
581
|
+
|
|
582
|
+
// Add the dependencies
|
|
583
|
+
dependencies = {
|
|
584
|
+
...dependencies,
|
|
585
|
+
[this.module]: `^${this.platformaticVersion}`,
|
|
586
|
+
platformatic: `^${this.platformaticVersion}`,
|
|
587
|
+
wattpm: `^${this.platformaticVersion}`
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// For easier readbility, sort dependencies and devDependencies by name
|
|
591
|
+
dependencies = Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b)))
|
|
592
|
+
devDependencies = Object.fromEntries(Object.entries(devDependencies ?? {}).sort(([a], [b]) => a.localeCompare(b)))
|
|
593
|
+
|
|
594
|
+
scripts ??= {}
|
|
595
|
+
scripts.dev ??= this.config.devCommand
|
|
596
|
+
scripts.build ??= this.config.buildCommand
|
|
597
|
+
scripts.start ??= this.config.startCommand ?? 'platformatic start'
|
|
598
|
+
|
|
599
|
+
this.addFile({
|
|
600
|
+
path: '',
|
|
601
|
+
file: 'package.json',
|
|
602
|
+
contents: JSON.stringify(
|
|
603
|
+
{
|
|
604
|
+
name: name ?? this.projectName ?? this.runtimeName ?? basename(this.targetDirectory),
|
|
605
|
+
scripts,
|
|
606
|
+
dependencies,
|
|
607
|
+
devDependencies,
|
|
608
|
+
...rest,
|
|
609
|
+
engines: { ...packageJsonEngines, ...engines }
|
|
610
|
+
},
|
|
611
|
+
null,
|
|
612
|
+
2
|
|
613
|
+
)
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async #createConfigFile () {
|
|
618
|
+
const config = {
|
|
619
|
+
$schema: `https://schemas.platformatic.dev/${this.module}/${this.platformaticVersion}.json`,
|
|
620
|
+
runtime: wrappableProperties
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
this.addFile({
|
|
624
|
+
path: '',
|
|
625
|
+
file: 'watt.json',
|
|
626
|
+
contents: JSON.stringify(config, null, 2)
|
|
627
|
+
})
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async #readExistingFile (path, emptyContents = '', suffix = '') {
|
|
631
|
+
const filePath = join(this.targetDirectory, path)
|
|
632
|
+
|
|
633
|
+
if (!existsSync(filePath)) {
|
|
634
|
+
return emptyContents
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const contents = await readFile(filePath, 'utf-8')
|
|
638
|
+
return contents + suffix
|
|
639
|
+
}
|
|
640
|
+
}
|