@platformatic/runtime 3.72.0 → 4.0.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.d.ts CHANGED
@@ -37,6 +37,8 @@ export interface LoopbackMessagingOptions {
37
37
  }
38
38
 
39
39
  export namespace errors {
40
+ export const AddressInUseError: (port: number, firstApplication: string, secondApplication: string) => FastifyError
41
+ export const WorkerAddressInUseError: (port: number, application: string) => FastifyError
40
42
  export const RuntimeExitedError: () => FastifyError
41
43
  export const UnknownRuntimeAPICommandError: (command: string) => FastifyError
42
44
  export const ApplicationNotFoundError: (id: string) => FastifyError
@@ -46,8 +48,6 @@ export namespace errors {
46
48
  export const RuntimeNotStartedError: () => FastifyError
47
49
  export const ConfigPathMustBeStringError: () => FastifyError
48
50
  export const NoConfigFileFoundError: (id: string) => FastifyError
49
- export const InvalidEntrypointError: (entrypoint: string) => FastifyError
50
- export const MissingEntrypointError: () => FastifyError
51
51
  export const MissingDependencyError: (dependency: string) => FastifyError
52
52
  export const InspectAndInspectBrkError: () => FastifyError
53
53
  export const InspectorPortError: () => FastifyError
@@ -86,9 +86,9 @@ export namespace symbols {
86
86
  export const kHealthCheckTimer: unique symbol
87
87
  export const kHealthMetricsTimer: unique symbol
88
88
  export const kLastHealthCheckELU: unique symbol
89
- export const kLastWorkerScalerELU: unique symbol
90
89
  export const kWorkerStatus: unique symbol
91
90
  export const kWorkerHealthSignals: unique symbol
91
+ export const kWorkerUrl: unique symbol
92
92
  export const kStderrMarker: string
93
93
  export const kWorkersBroadcast: unique symbol
94
94
  }
@@ -110,23 +110,29 @@ export interface InjectResponse {
110
110
  rawPayload: ArrayBuffer
111
111
  }
112
112
 
113
+ export type ServingState = 'listening' | 'mesh-only' | 'background' | 'inactive'
114
+
113
115
  export interface ApplicationDetails {
114
116
  id: string
115
117
  type?: string
118
+ /** v3 only: the path to the application's configuration file. v4 reports configPath. */
116
119
  config?: string
120
+ /** v4 only: the configuration file the loader decided on, absent for an inline definition. */
121
+ configPath?: string
117
122
  path?: string
118
123
  status?: string
124
+ /** How the application serves, as opposed to whether it runs. Absent when it is not started. */
125
+ servingState?: ServingState
119
126
  dependencies?: string[]
120
127
  version?: string
121
128
  localUrl?: string
122
- entrypoint?: boolean
123
129
  sourceMaps?: boolean
124
130
  workers?: number
125
131
  url?: string | null
132
+ urls?: string[]
126
133
  }
127
134
 
128
135
  export interface ApplicationsTopology {
129
- entrypoint: string
130
136
  production: boolean
131
137
  applications: ApplicationDetails[]
132
138
  }
@@ -157,14 +163,6 @@ export interface SchedulerJobBase {
157
163
  nextRunAt?: string | null
158
164
  }
159
165
 
160
- export interface ConfiguredSchedulerJob extends SchedulerJobBase {
161
- source: 'config'
162
- callbackUrl: string
163
- method: string
164
- headers?: Record<string, string>
165
- body?: string | Record<string, unknown>
166
- }
167
-
168
166
  export interface ApplicationSchedulerJob extends SchedulerJobBase {
169
167
  source: 'application'
170
168
  applicationId: string
@@ -172,7 +170,7 @@ export interface ApplicationSchedulerJob extends SchedulerJobBase {
172
170
  tasks: string[]
173
171
  }
174
172
 
175
- export type SchedulerJob = ConfiguredSchedulerJob | ApplicationSchedulerJob
173
+ export type SchedulerJob = ApplicationSchedulerJob
176
174
 
177
175
  export interface SchedulerRunResult {
178
176
  name: string
@@ -194,10 +192,14 @@ export interface RuntimeMetadata {
194
192
  execPath: string
195
193
  nodeVersion: string
196
194
  projectDir: string
195
+ /** The configuration file the loader decided on. Null when the runtime was built from an object. */
196
+ configPath: string | null
197
+ /** The autoload declaration, with its path resolved. Null when the project does not autoload. */
198
+ autoload: { path: string, exclude?: string[], mappings?: Record<string, unknown> } | null
197
199
  packageName: string | null
198
200
  packageVersion: string | null
199
- url: string | null
200
201
  platformaticVersion: string
202
+ urls: Record<string, string>
201
203
  }
202
204
 
203
205
  export interface WorkerLifecycleEvent {
@@ -234,9 +236,7 @@ export interface HealthMetricsEvent extends WorkerLifecycleEvent {
234
236
  healthSignals: RuntimeHealthSignal[]
235
237
  }
236
238
 
237
- export declare class ManagementClient {
238
- constructor (allowedOperations?: string[])
239
-
239
+ export interface ManagementClient {
240
240
  getRuntimeStatus (): Promise<string>
241
241
  getRuntimeMetadata (): Promise<RuntimeMetadata>
242
242
  getRuntimeConfig (): Promise<Record<string, unknown>>
@@ -258,7 +258,7 @@ export declare class ManagementClient {
258
258
  startApplication (id: string): Promise<void>
259
259
  stopApplication (id: string): Promise<void>
260
260
  restartApplication (id: string): Promise<void>
261
- restart (applications?: string[]): Promise<string>
261
+ restart (applications?: string[]): Promise<void>
262
262
  addApplications (applications: unknown[], start?: boolean): Promise<ApplicationDetails[]>
263
263
  removeApplications (ids: string[]): Promise<ApplicationDetails[]>
264
264
  inject (id: string, injectParams: InjectParams): Promise<InjectResponse>
@@ -370,12 +370,11 @@ export type RuntimeExtension = (
370
370
 
371
371
  export declare class Runtime extends EventEmitter {
372
372
  init (): Promise<void>
373
- start (silent?: boolean): Promise<string | undefined>
373
+ start (silent?: boolean): Promise<Record<string, string>>
374
374
  stop (silent?: boolean): Promise<void>
375
375
  close (silent?: boolean): Promise<void>
376
- restart (applications?: string[]): Promise<string | undefined>
376
+ restart (applications?: string[]): Promise<void>
377
377
  inject (id: string, injectParams: InjectParams): Promise<InjectResponse>
378
- getUrl (): string | undefined
379
378
  getRuntimeStatus (): string
380
379
  getRuntimeMetadata (): Promise<RuntimeMetadata>
381
380
  getRuntimeEnv (): Record<string, string>
@@ -386,6 +385,7 @@ export declare class Runtime extends EventEmitter {
386
385
  getRuntimeConfig (includeMeta: true): RuntimeConfiguration
387
386
  getRuntimeConfig (includeMeta?: boolean): Record<string, unknown>
388
387
  getApplicationsIds (): string[]
388
+ getUrls (applicationId?: string): Record<string, string>
389
389
  /**
390
390
  * Returns topology for every configured application.
391
391
  * When `allowUnloaded` is `true`, applications without running workers are
@@ -472,11 +472,6 @@ export declare class Runtime extends EventEmitter {
472
472
  runApplicationScheduledTasks (id: string, scheduleId: string, scheduledTime: number): Promise<unknown>
473
473
  }
474
474
 
475
- export function wrapInRuntimeConfig (
476
- config: Configuration<unknown>,
477
- context?: ConfigurationOptions
478
- ): Promise<RuntimeConfiguration>
479
-
480
475
  export declare const version: string
481
476
 
482
477
  export declare function loadConfiguration (
package/index.js CHANGED
@@ -1,20 +1,27 @@
1
- import { resolve, validationOptions } from '@platformatic/basic'
1
+ import { importCapabilityPackage, resolve } from '@platformatic/basic'
2
2
  import {
3
3
  abstractLogger,
4
4
  ensureLoggableError,
5
- extractModuleFromSchemaUrl,
6
5
  findRuntimeConfigurationFile,
7
6
  kMetadata,
8
- loadConfigurationModule,
9
- loadConfiguration as utilsLoadConfiguration
7
+ loadConfigurationModule
10
8
  } from '@platformatic/foundation'
9
+ import {
10
+ findDecidingFile,
11
+ isProductionCommand,
12
+ LegacyConfigurationFileError,
13
+ loadConfiguration as loadFileConfiguration,
14
+ loadObjectConfiguration,
15
+ validateCapabilityConfiguration
16
+ } from '@platformatic/foundation/loader'
11
17
  import closeWithGrace from 'close-with-grace'
18
+ import { stat } from 'node:fs/promises'
12
19
  import inspector from 'node:inspector'
13
- import { transform, wrapInRuntimeConfig } from './lib/config.js'
20
+ import { dirname, resolve as resolvePath } from 'node:path'
21
+ import { transformConfiguration } from './lib/config.js'
14
22
  import { NodeInspectorFlagsNotSupportedError } from './lib/errors.js'
15
23
  import { Runtime } from './lib/runtime.js'
16
- import { schema } from './lib/schema.js'
17
- import { upgrade } from './lib/upgrade.js'
24
+ import { configurationSchema } from './lib/schema.js'
18
25
 
19
26
  async function restartRuntime (runtime) {
20
27
  runtime.logger.info('Received SIGUSR2, restarting all applications ...')
@@ -59,34 +66,264 @@ function handleSignal (runtime, config) {
59
66
  })
60
67
  }
61
68
 
69
+ /*
70
+ The configuration is code, evaluated by the loader in foundation: it walks to the deciding file,
71
+ resolves both views of the env ladder, evaluates the root in a worker and fans out one worker per
72
+ per-app file. Everything the runtime transform used to discover — the application list, each
73
+ application's capability, its schema and its worker environment — arrives already resolved.
74
+
75
+ Routing by filename keeps the legacy path intact while the in-tree JSON fixtures are converted; the
76
+ legacy half is what a later commit deletes, not something this one has to keep working around.
77
+ */
78
+ /*
79
+ `--config` names a configuration; it does not widen what a configuration may be called. The four
80
+ names are the format, and `resolveNamedConfigurationFile` is what refuses anything else -- it
81
+ separates a legacy name, which gets the migrate hint, from an unrecognised one, which gets told
82
+ what the four names are. Deciding here instead produced one of those answers for both.
83
+
84
+ What `--config` does change is *where*: a named file need not sit where discovery would look.
85
+ */
86
+ function isNamedConfigurationPath (path) {
87
+ return /\.(json|json5|ya?ml|to?ml|js|mjs|ts|mts)$/.test(path)
88
+ }
89
+
90
+ async function findConfigurationForSource (configOrRoot, sourceOrConfig) {
91
+ // A programmatic object source is not this path: the object entry point is
92
+ // loadObjectConfiguration, which skips the root eval worker entirely.
93
+ if (sourceOrConfig && typeof sourceOrConfig !== 'string') {
94
+ return null
95
+ }
96
+
97
+ if (typeof sourceOrConfig === 'string') {
98
+ const named = resolvePath(configOrRoot, sourceOrConfig)
99
+
100
+ // Named outright: the loader decides whether the name is one it accepts, and says which it is.
101
+ return isNamedConfigurationPath(named) ? named : null
102
+ }
103
+
104
+ if (typeof configOrRoot !== 'string') {
105
+ return null
106
+ }
107
+
108
+ if (isNamedConfigurationPath(configOrRoot)) {
109
+ return configOrRoot
110
+ }
111
+
112
+ /*
113
+ Only an actual directory is a place to search from. findDecidingFile walks toward the env root,
114
+ so handing it a path that does not exist would answer with whatever project sits above it: a
115
+ typo'd --config would boot the parent rather than failing. Discovery looks in the named directory
116
+ and nowhere else, and a wrong path has to stay an error.
117
+ */
118
+ const stats = await stat(configOrRoot).catch(() => null)
119
+
120
+ if (!stats?.isDirectory()) {
121
+ return null
122
+ }
123
+
124
+ /*
125
+ A legacy configuration found by the walk is refused, with the error that names migrate. It used to
126
+ be swallowed so the legacy loader below could answer instead; that loader is gone, and the refusal
127
+ is the correct answer again.
128
+ */
129
+ const deciding = await findDecidingFile(configOrRoot, { throwOnMissing: false })
130
+
131
+ return deciding?.path ?? null
132
+ }
133
+
62
134
  export async function loadConfiguration (configOrRoot, sourceOrConfig, context) {
135
+ // Checked before the legacy resolver, which throws when it finds no legacy file — a project has
136
+ // none by construction.
137
+ const decidingConfigurationFile = await findConfigurationForSource(configOrRoot, sourceOrConfig)
138
+
139
+ if (decidingConfigurationFile) {
140
+ return loadRuntimeConfiguration(decidingConfigurationFile, context)
141
+ }
142
+
143
+ /*
144
+ Level 0. A directory with no configuration file of any kind is not an error: the loader
145
+ synthesizes one in memory from what the directory contains. Reaching the legacy resolver instead
146
+ made a configless project fail to load, which is what the removed temporary-file fallback --
147
+ detect the type, write a watt.json into the user's tree, then load that -- existed to paper
148
+ over. Nothing is written to disk now.
149
+ */
150
+ if (!sourceOrConfig && typeof configOrRoot === 'string' && !isNamedConfigurationPath(configOrRoot)) {
151
+ const stats = await stat(configOrRoot).catch(() => null)
152
+
153
+ if (stats?.isDirectory()) {
154
+ return loadRuntimeConfiguration({ cwd: configOrRoot }, context)
155
+ }
156
+ }
157
+
63
158
  const { root, source } = await resolve(configOrRoot, sourceOrConfig, 'runtime')
64
159
 
65
- // First of all, load the configuration without any validation
66
- const config = await utilsLoadConfiguration(source, null, {
67
- root,
68
- envFile: context?.envFile
69
- })
160
+ /*
161
+ A configuration handed over as an object rather than named as a file: what an embedder builds in
162
+ memory, and what the ICC generates. There is nothing to evaluate, so it skips the root eval
163
+ worker and joins the pipeline at validation.
164
+ */
165
+ if (source && typeof source !== 'string') {
166
+ return loadRuntimeConfiguration({ root, source }, context)
167
+ }
168
+
169
+ /*
170
+ A path that reaches here named a file the walk did not recognise as configuration, so the walk's
171
+ own refusal is the answer -- it names the file and tells the reader to run migrate, which is more
172
+ use than loading a legacy configuration into a runtime that no longer implements what it says.
173
+ */
174
+ throw new LegacyConfigurationFileError(source)
175
+ }
176
+
177
+ /*
178
+ `target` is either a configuration file to evaluate or `{ root, source }` for a configuration
179
+ handed over as an object -- what an embedder builds in memory and what the ICC generates. The two
180
+ differ only in where the configuration comes from: an object skips the root eval worker, since
181
+ there is nothing to evaluate, and everything after that is the same pipeline.
182
+ */
183
+ async function loadRuntimeConfiguration (target, context) {
184
+ /*
185
+ The command decides `production` when the caller did not, rather than the other way round:
186
+ `build` produces production artifacts, so a build that evaluated as a development boot read the
187
+ development env files and gave every callback `production: false` while writing what `start`
188
+ would later run.
189
+ */
190
+ const diagnostics = context?.logger ?? abstractLogger
191
+ const explicitProduction = context?.isProduction ?? context?.production
192
+ const command = context?.command ?? (explicitProduction ? 'start' : 'dev')
193
+ const production = explicitProduction ?? isProductionCommand(command)
194
+
195
+ /*
196
+ The environment the loader treats as real. An embedder can add to it, and can ask for a
197
+ hermetic runtime that sees none of this process's -- the loader honours both through loadEnv, and
198
+ a runtime that ignored them would quietly hand every worker the parent's environment.
199
+ */
200
+ const realEnv = context?.ignoreProcessEnv ? { ...context?.env } : { ...process.env, ...context?.env }
70
201
 
71
- if (config.envfile) {
72
- // The context is optional, so it might not have been provided at all
73
- context ??= {}
74
- context.envFile = config.envfile
202
+ const objectSource = typeof target !== 'string' && target.source !== undefined
203
+ const shared = {
204
+ realEnv,
205
+ command,
206
+ mode: context?.mode,
207
+ production,
208
+ customEnvFile: context?.envFile,
209
+ /*
210
+ Step 3 of the pipeline validates orchestration in the eval worker, before autoload expansion
211
+ acts on it -- without the schema that step is skipped, and a malformed `autoload` reaches the
212
+ filesystem walk as a raw TypeError instead of a validation error naming the property.
213
+ */
214
+ schema: context?.schema ?? configurationSchema,
215
+ /*
216
+ The evaluation deadline. A configuration that never resolves — an awaited fetch to a dead
217
+ host, a forgotten promise — otherwise hangs the boot rather than failing it, and the default
218
+ is a guess about how long a reasonable one takes. A deployment that knows better says so.
219
+ */
220
+ timeout: context?.configTimeout,
221
+ // The one file --debug-config evaluates in this process when an inspector is attached, so a
222
+ // breakpoint in it is reachable at all: a throwaway worker dies before one can attach.
223
+ inProcessTarget: context?.inProcessTarget,
224
+ /*
225
+ Validation imports each capability's schema, so it needs the capability installed — not
226
+ merely declared, which is all the detector needs. That moves the moment a capability must be
227
+ resolvable earlier than it once had to be: loading used to succeed and fail later, in the worker.
228
+
229
+ A caller that is loading only to discover the topology — a tool about to install those
230
+ dependencies, for instance — can say so rather than being made to install first.
231
+ */
232
+ validateCapabilities: context?.validateCapabilities ?? true,
233
+ // The runtime is where the bundled capability copies live, so it is the fallback scope for
234
+ // both the schema import and the version stamp — the application's own dependencies still
235
+ // come first.
236
+ runtimeScope: import.meta.filename,
237
+ /*
238
+ These are the loader's user-facing diagnostics — the standalone-boot warning, the zero-config
239
+ inference notice, the capability the detector chose — and they were being handed to
240
+ abstractLogger, which is a no-op. Every one of them was discarded, including the warning the
241
+ format relies on to tell someone that nothing the root says is being applied.
242
+
243
+ A caller with a real logger passes it. The default stays silent because an embedder loading a
244
+ configuration has not asked for anything on its stderr.
245
+ */
246
+ onWarning: warning => diagnostics.warn(warning.message),
247
+ onInfo: info => diagnostics.info(info.message)
75
248
  }
76
249
 
77
- const mod = extractModuleFromSchemaUrl(config)
78
- if (mod?.module !== '@platformatic/runtime') {
79
- return wrapInRuntimeConfig(config, context)
250
+ let loaded
251
+ if (objectSource) {
252
+ loaded = await loadObjectConfiguration({ root: target.root, source: target.source, ...shared })
253
+ } else if (typeof target === 'string') {
254
+ loaded = await loadFileConfiguration({ cwd: dirname(target), configPath: target, ...shared })
255
+ } else {
256
+ // No file was named and none was found: the loader searches from this directory and, finding
257
+ // nothing, synthesizes.
258
+ loaded = await loadFileConfiguration({ cwd: target.cwd, ...shared })
80
259
  }
81
260
 
82
- return utilsLoadConfiguration(source, context?.schema ?? schema, {
83
- validationOptions,
84
- transform,
85
- upgrade,
86
- replaceEnv: true,
87
- root,
88
- ...context
261
+ const config = loaded.config
262
+
263
+ /*
264
+ The documented pipeline is validation with useDefaults, then kMetadata, then transform. The eval
265
+ worker's own check is a shape check that injects nothing — deliberately, so the resolve
266
+ projection carries authored values — so this is where the runtime schema's defaults arrive, and
267
+ without it the runtime reaches for settings like gracefulShutdown that nothing supplied.
268
+ */
269
+ validateCapabilityConfiguration(config, context?.schema ?? configurationSchema, {
270
+ id: 'runtime',
271
+ module: '@platformatic/runtime',
272
+ root: loaded.root
89
273
  })
274
+
275
+ // kMetadata is symbol-keyed and cannot cross a worker boundary, so each worker rebuilds it; this
276
+ // is the main process's copy. env is the runtime's own view — the real environment under the root
277
+ // env block and the root's env files — and every application worker gets the per-application
278
+ // environment the loader resolved instead, which this one is not a substitute for.
279
+ config[kMetadata] = {
280
+ root: loaded.root,
281
+ path: loaded.configPath,
282
+ env: loaded.env ?? process.env,
283
+ module: '@platformatic/runtime',
284
+ /*
285
+ The evaluation context, kept beside the envelope rather than inside it: applications added
286
+ after boot are evaluated with the same command, mode and production flags the boot pass used,
287
+ and its presence is also what tells the add path this runtime resolves configuration main-side.
288
+ A legacy runtime has no such context and keeps resolving configuration files worker-side.
289
+ */
290
+ loader: {
291
+ command,
292
+ mode: loaded.mode,
293
+ production: loaded.production,
294
+ /*
295
+ The environment contract of the boot, carried so an application added while the runtime
296
+ runs is enveloped under the same one: the realEnv the boot resolved ladders against
297
+ (hermetic under ignoreProcessEnv, augmented by context.env) and the --env file that
298
+ replaces every application's env-file rung. Without them the add path defaulted to
299
+ process.env and the conventional .env chain -- an environment the boot says cannot exist.
300
+ */
301
+ realEnv,
302
+ customEnvFile: context?.envFile,
303
+ /*
304
+ Everything the evaluation actually read: the configuration files, their import graph minus
305
+ node_modules, the env files consulted, and the directories whose membership decides the
306
+ application list. `wattpm dev` reloads on any of it -- watching only the deciding file meant
307
+ a config split across a helper module reloaded for none of its own edits.
308
+ */
309
+ watchTargets: loaded.watchTargets,
310
+ /*
311
+ Every entry carrying a `url`, recorded on the way past -- before the enabled filter, which
312
+ is the point. `enabled` is the supported way to keep an application out of a boot, and an
313
+ entry it hides from `resolve` is one whose clone never arrives.
314
+ */
315
+ resolveCandidates: loaded.resolveCandidates
316
+ }
317
+ }
318
+
319
+ /*
320
+ A caller-supplied transform replaces the built-in one, exactly as it does on the legacy path where
321
+ it arrives as part of ...context. Ignoring it here meant a test helper or an embedder that
322
+ customized the configuration was silently not consulted.
323
+ */
324
+ const apply = context?.transform ?? transformConfiguration
325
+
326
+ return apply(config, null, context)
90
327
  }
91
328
 
92
329
  export async function loadApplicationsCommands (executableName = '', configurationFile = null) {
@@ -111,7 +348,13 @@ export async function loadApplicationsCommands (executableName = '', configurati
111
348
  throw new Error('No runtime configuration file found.')
112
349
  }
113
350
 
114
- config = await loadConfiguration(file)
351
+ /*
352
+ 'exec' is every non-boot evaluation, and enumerating a capability's commands is one: nothing
353
+ starts. Without it this evaluated as a development boot — development env files, and
354
+ `production: false` handed to every callback — which is a context no invocation of these
355
+ commands actually runs in.
356
+ */
357
+ config = await loadConfiguration(file, null, { command: 'exec' })
115
358
 
116
359
  /* c8 ignore next 3 - Hard to test */
117
360
  if (!config) {
@@ -123,8 +366,12 @@ export async function loadApplicationsCommands (executableName = '', configurati
123
366
 
124
367
  for (const application of config.applications) {
125
368
  try {
126
- const applicationConfig = await utilsLoadConfiguration(application.config)
127
- const pkg = await loadConfigurationModule(application.path, applicationConfig)
369
+ // An application arrives with its configuration already evaluated and its capability
370
+ // already named, so there is nothing to read and nothing to infer.
371
+ const applicationConfig = application.resolvedConfig
372
+ const pkg = application.module
373
+ ? await importCapabilityPackage(application.path, application.module)
374
+ : await loadConfigurationModule(application.path, applicationConfig)
128
375
 
129
376
  if (pkg.createCommands) {
130
377
  const definition = await pkg.createCommands(application.id)
@@ -175,7 +422,20 @@ export async function create (configOrRoot, sourceOrConfig, context) {
175
422
  return runtime
176
423
  }
177
424
 
178
- export { prepareApplication, transform, wrapInRuntimeConfig } from './lib/config.js'
425
+ /*
426
+ The one entry point for an application added while the runtime is running -- what an extension
427
+ calls before `addApplications`. It replaces the exported `prepareApplication`, which is the legacy
428
+ half of the same job: an added application has to be evaluated the way boot evaluates
429
+ one, and an entry that skips that arrives without `resolvedConfig` and fails as an unhelpful
430
+ "unable to initialize the worker".
431
+ */
432
+ export { prepareAddedApplications } from './lib/config.js'
433
+
434
+ // The one transform there is: the legacy one died with its last caller, the wizard's legacy-JSON
435
+ // branch, and the dispatch that told the two apart went with it.
436
+ export async function transform (config, schema, context) {
437
+ return transformConfiguration(config, schema, context)
438
+ }
179
439
  export * as errors from './lib/errors.js'
180
440
  export { RuntimeGenerator as Generator, WrappedGenerator } from './lib/generator.js'
181
441
  export { setupLoopbackMessaging } from './lib/loopback-messaging.js'