@platformatic/foundation 4.0.0-new-config.2 → 4.0.0-new-config.3
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 +1 -21
- package/lib/cli.js +24 -49
- package/lib/configuration.js +12 -212
- package/lib/file-system.js +12 -1
- package/lib/schema.js +68 -8
- package/lib/symbols.js +0 -1
- package/lib/v4/application.js +99 -0
- package/lib/v4/canonicalize.js +18 -19
- package/lib/v4/capability-resolution.js +4 -1
- package/lib/v4/classify.js +0 -4
- package/lib/v4/env.js +8 -5
- package/lib/v4/errors.js +46 -1
- package/lib/v4/filenames.js +33 -2
- package/lib/v4/index.js +3 -0
- package/lib/v4/load.js +174 -12
- package/lib/v4/pipeline.js +178 -15
- package/lib/v4/project.js +271 -0
- package/lib/v4/scope.js +11 -1
- package/lib/v4/stamp.js +50 -0
- package/lib/v4/topology.js +156 -11
- package/lib/v4/validate.js +23 -4
- package/lib/v4/write.js +135 -0
- package/package.json +2 -2
package/index.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ import type debug from 'debug';
|
|
|
7
7
|
|
|
8
8
|
// Symbols
|
|
9
9
|
export declare const kCanceled: unique symbol
|
|
10
|
-
export declare const kEnvFileFallbackKeys: unique symbol
|
|
11
10
|
export declare const kFailedImport: unique symbol
|
|
12
11
|
export declare const kHandledError: unique symbol
|
|
13
12
|
export declare const kMetadata: unique symbol
|
|
@@ -59,14 +58,7 @@ export function findRuntimeConfigurationFile(
|
|
|
59
58
|
executableName?: string
|
|
60
59
|
): Promise<string | false | undefined>
|
|
61
60
|
|
|
62
|
-
export function fallbackToTemporaryConfigFile(
|
|
63
|
-
logger: Logger,
|
|
64
|
-
root: string,
|
|
65
|
-
verifyPackages: boolean
|
|
66
|
-
): Promise<string | false | undefined>
|
|
67
|
-
|
|
68
61
|
// Configuration types
|
|
69
|
-
export declare const envVariablePattern: RegExp
|
|
70
62
|
export declare const knownConfigurationFilesExtensions: string[]
|
|
71
63
|
export declare const knownConfigurationFilesSchemas: RegExp[]
|
|
72
64
|
|
|
@@ -93,11 +85,6 @@ export type ConfigurationOptions<T = {}> = Partial<{
|
|
|
93
85
|
) => Promise<Configuration<T>> | Configuration<T>
|
|
94
86
|
upgrade: (logger: Logger, config: RawConfiguration, version: string) => Promise<RawConfiguration> | RawConfiguration
|
|
95
87
|
env: Record<string, string>
|
|
96
|
-
ignoreProcessEnv: boolean
|
|
97
|
-
replaceEnv: boolean
|
|
98
|
-
replaceEnvIgnore: string[]
|
|
99
|
-
onMissingEnv: (key: string) => string | undefined
|
|
100
|
-
strictEnv: boolean | 'warn'
|
|
101
88
|
fixPaths: boolean
|
|
102
89
|
logger: Logger
|
|
103
90
|
root: string
|
|
@@ -160,13 +147,6 @@ export declare function createValidator (
|
|
|
160
147
|
validationOptions?: object,
|
|
161
148
|
context?: ConfigurationOptions
|
|
162
149
|
): (data: any) => boolean
|
|
163
|
-
export declare function loadEnv (root: string): Promise<Record<string, string>>
|
|
164
|
-
export declare function replaceEnv (
|
|
165
|
-
config: RawConfiguration,
|
|
166
|
-
env: Record<string, string>,
|
|
167
|
-
onMissingEnv?: (key: string) => string | undefined,
|
|
168
|
-
ignore?: string[]
|
|
169
|
-
): RawConfiguration
|
|
170
150
|
// `config` also accepts an array: the exported `applications` schema is a
|
|
171
151
|
// JSONSchemaType<object[]> (an ARRAY schema), and validate() is called with
|
|
172
152
|
// a matching array of application configs before they are handed to
|
|
@@ -189,7 +169,7 @@ export declare function loadConfiguration (
|
|
|
189
169
|
// hence the index signature).
|
|
190
170
|
export interface ConfigurationModule {
|
|
191
171
|
loadConfiguration?: (configPath: string) => Promise<unknown>
|
|
192
|
-
|
|
172
|
+
skipTracingHooks?: boolean
|
|
193
173
|
createCommands?: (applicationId: string) => unknown
|
|
194
174
|
modulesToLoad?: string[]
|
|
195
175
|
[key: string]: unknown
|
package/lib/cli.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import Deepmerge from '@fastify/deepmerge'
|
|
2
2
|
import { bgGreen, black, bold, green, isColorSupported } from 'colorette'
|
|
3
|
+
import { stat } from 'node:fs/promises'
|
|
3
4
|
import { basename, resolve } from 'node:path'
|
|
4
5
|
import { parseArgs as nodeParseArgs } from 'node:util'
|
|
5
6
|
import { pino } from 'pino'
|
|
6
7
|
import pinoPretty from 'pino-pretty'
|
|
7
|
-
import { findConfigurationFileRecursive
|
|
8
|
-
import { hasJavascriptFiles } from './file-system.js'
|
|
8
|
+
import { findConfigurationFileRecursive } from './configuration.js'
|
|
9
9
|
import { setPinoTimestamp } from './logger.js'
|
|
10
|
-
import { detectApplicationType, getPlatformaticVersion } from './module.js'
|
|
11
10
|
import { findDecidingFile, isConfigurationFileName } from './v4/index.js'
|
|
12
11
|
|
|
13
12
|
/* c8 ignore next 4 - else branches */
|
|
@@ -266,61 +265,37 @@ export async function findRuntimeConfigurationFile (
|
|
|
266
265
|
configFile = await findConfigurationFileRecursive(root, configurationFile)
|
|
267
266
|
}
|
|
268
267
|
|
|
269
|
-
|
|
268
|
+
/*
|
|
269
|
+
No configuration file anywhere. `fallback` used to mean "detect the application type and write
|
|
270
|
+
a watt.json into the user's tree so there is something to load"; it now means the caller can
|
|
271
|
+
load the directory itself, which the v4 loader answers by synthesizing a configuration in
|
|
272
|
+
memory. Nothing is written to disk, so nothing is left behind to be committed by accident.
|
|
273
|
+
*/
|
|
270
274
|
if (!configFile) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
/*
|
|
276
|
+
Level 0 only applies to a directory that is actually there. A path that is not one has no
|
|
277
|
+
application to infer and no defaults to apply, and answering null would hand the caller a
|
|
278
|
+
root to synthesize from that does not exist — the v3 resolver then reports the missing file
|
|
279
|
+
by its v3 names, which is not what someone who typo'd a path needs to read.
|
|
280
|
+
*/
|
|
281
|
+
if (fallback && (await stat(root).catch(() => null))?.isDirectory()) {
|
|
282
|
+
return null
|
|
278
283
|
}
|
|
279
284
|
|
|
280
285
|
if (throwOnError) {
|
|
281
286
|
return logFatalError(
|
|
282
287
|
logger,
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
288
|
+
/*
|
|
289
|
+
The v4 names first, because they are what someone reading this should create. The legacy
|
|
290
|
+
ones are still named -- this command reads them while v3 is supported -- but a user told to
|
|
291
|
+
write a `watt.json` would be told to write a file v4 refuses.
|
|
292
|
+
*/
|
|
293
|
+
`Cannot find a supported ${executableName} configuration file (like ${bold('watt.config.ts')} or ${bold(
|
|
294
|
+
'watt.config.js'
|
|
295
|
+
)}, or a legacy ${bold('watt.json')}) in ${bold(resolve(root))}.`
|
|
286
296
|
)
|
|
287
297
|
}
|
|
288
298
|
}
|
|
289
299
|
|
|
290
300
|
return configFile
|
|
291
301
|
}
|
|
292
|
-
|
|
293
|
-
export async function fallbackToTemporaryConfigFile (logger, root, verifyPackages) {
|
|
294
|
-
const hasJsFiles = await hasJavascriptFiles(root)
|
|
295
|
-
|
|
296
|
-
if (!hasJsFiles) {
|
|
297
|
-
// Do not return false here, that is reserved below to signal that a file was created but no module was available.
|
|
298
|
-
return
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const { name, label } = await detectApplicationType(root)
|
|
302
|
-
|
|
303
|
-
/* c8 ignore next - else */
|
|
304
|
-
const autodetectDescription = name === '@platformatic/node' ? 'is a generic Node.js application' : `is using ${label}`
|
|
305
|
-
|
|
306
|
-
logger.warn(
|
|
307
|
-
`We have auto-detected that the current folder ${bold(autodetectDescription)} so we have created a ${bold('watt.json')} file for you automatically.`
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
const schema = `https://schemas.platformatic.dev/${name}/${await getPlatformaticVersion()}.json?autogenerated=true`
|
|
311
|
-
const configurationFile = resolve(root, 'watt.json')
|
|
312
|
-
await saveConfigurationFile(configurationFile, { $schema: schema })
|
|
313
|
-
|
|
314
|
-
// Try to load the module, if it is missing, we will throw an error
|
|
315
|
-
if (verifyPackages) {
|
|
316
|
-
try {
|
|
317
|
-
await loadConfigurationModule(root, { $schema: schema })
|
|
318
|
-
/* c8 ignore next 4 - covered */
|
|
319
|
-
} catch (error) {
|
|
320
|
-
logFatalError(logger, `Cannot load module ${bold(name)}. Please add it to your package.json and try again.`)
|
|
321
|
-
return false
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return configurationFile
|
|
326
|
-
}
|
package/lib/configuration.js
CHANGED
|
@@ -1,32 +1,25 @@
|
|
|
1
1
|
import toml from '@iarna/toml'
|
|
2
2
|
import Ajv from 'ajv'
|
|
3
|
-
import jsonPatch from 'fast-json-patch'
|
|
4
3
|
import JSON5 from 'json5'
|
|
5
4
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
6
5
|
import { createRequire } from 'node:module'
|
|
7
|
-
import { dirname, extname,
|
|
8
|
-
import { parseEnv } from 'node:util'
|
|
6
|
+
import { dirname, extname, resolve } from 'node:path'
|
|
9
7
|
import { parse as rawParseYAML, stringify as stringifyYAML } from 'yaml'
|
|
10
8
|
import {
|
|
11
9
|
AddAModulePropertyToTheConfigOrAddAKnownSchemaError,
|
|
12
10
|
CannotParseConfigFileError,
|
|
13
11
|
ConfigurationDoesNotValidateAgainstSchemaError,
|
|
14
12
|
InvalidConfigFileExtensionError,
|
|
15
|
-
MissingEnvVariablesError,
|
|
16
13
|
RootMissingError,
|
|
17
14
|
SourceMissingError
|
|
18
15
|
} from './errors.js'
|
|
19
16
|
import { isFileAccessible } from './file-system.js'
|
|
20
17
|
import { loadModule, splitModuleFromVersion } from './module.js'
|
|
21
|
-
import {
|
|
18
|
+
import { kMetadata } from './symbols.js'
|
|
22
19
|
|
|
23
20
|
const { parse: parseJSON5, stringify: rawStringifyJSON5 } = JSON5
|
|
24
21
|
const { parse: parseTOML, stringify: stringifyTOML } = toml
|
|
25
22
|
|
|
26
|
-
const kReplaceEnvIgnore = Symbol('plt.foundation.replaceEnvIgnore')
|
|
27
|
-
|
|
28
|
-
export const envVariablePattern = /(?:\{{1,2})([a-z0-9_]+)(?:\}{1,2})/i
|
|
29
|
-
|
|
30
23
|
export const knownConfigurationFilesExtensions = ['json', 'json5', 'yaml', 'yml', 'toml', 'tml']
|
|
31
24
|
|
|
32
25
|
// Important: do not put $ in any RegExp since we might use the querystring to deliver additional information
|
|
@@ -111,14 +104,6 @@ export function getStringifier (path) {
|
|
|
111
104
|
return stringifer
|
|
112
105
|
}
|
|
113
106
|
|
|
114
|
-
export function printValidationErrors (err) {
|
|
115
|
-
const tabularData = err.validation.map(err => {
|
|
116
|
-
return { path: err.path, message: err.message }
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
console.table(tabularData, ['path', 'message'])
|
|
120
|
-
}
|
|
121
|
-
|
|
122
107
|
export function listRecognizedConfigurationFiles (suffixes, extensions) {
|
|
123
108
|
if (typeof suffixes === 'undefined' || suffixes === null) {
|
|
124
109
|
// composer is retained for backward compatibility with V2
|
|
@@ -341,131 +326,15 @@ export function validate (schema, config, validationOptions = {}, fixPaths = tru
|
|
|
341
326
|
}
|
|
342
327
|
}
|
|
343
328
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
let envFile = customEnvFile
|
|
350
|
-
|
|
351
|
-
// If a custom env file is provided, resolve it and check if it exists
|
|
352
|
-
if (customEnvFile) {
|
|
353
|
-
envFile = isAbsolute(customEnvFile) ? customEnvFile : resolve(root, customEnvFile)
|
|
354
|
-
if (!(await isFileAccessible(envFile))) {
|
|
355
|
-
throw new Error(`Custom env file not found: ${envFile}`)
|
|
356
|
-
}
|
|
357
|
-
} else {
|
|
358
|
-
// Default behavior: search for .env file in the current directory and its parents
|
|
359
|
-
let currentPath = root
|
|
360
|
-
const rootPath = parse(root).root
|
|
361
|
-
|
|
362
|
-
while (currentPath !== rootPath) {
|
|
363
|
-
const candidate = resolve(currentPath, '.env')
|
|
364
|
-
|
|
365
|
-
if (await isFileAccessible(candidate)) {
|
|
366
|
-
envFile = candidate
|
|
367
|
-
break
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
currentPath = dirname(currentPath)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// If not found, check the current working directory
|
|
374
|
-
if (!envFile) {
|
|
375
|
-
const cwdCandidate = resolve(process.cwd(), '.env')
|
|
376
|
-
|
|
377
|
-
if (await isFileAccessible(cwdCandidate)) {
|
|
378
|
-
envFile = cwdCandidate
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const baseEnv = ignoreProcessEnv ? {} : process.env
|
|
384
|
-
const envFromFile = envFile ? parseEnv(await readFile(envFile, 'utf-8')) : {}
|
|
385
|
-
|
|
386
|
-
// The env file provides fallback defaults: variables already set in the real
|
|
387
|
-
// environment (and explicit programmatic values) take precedence over it,
|
|
388
|
-
// matching the dotenv/docker-compose/Vite convention.
|
|
389
|
-
const env = {
|
|
390
|
-
...envFromFile,
|
|
391
|
-
...baseEnv,
|
|
392
|
-
...additionalEnv
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Keys whose only source is the env file are not real environment variables, they are defaults:
|
|
396
|
-
// a more specific env file, like the one of a single application inside a runtime, must still be
|
|
397
|
-
// able to override them. The list is attached non enumerably, so spreading, Object.keys,
|
|
398
|
-
// JSON.stringify and structuredClone of the returned environment are all unchanged.
|
|
399
|
-
const fallbackKeys = Object.keys(envFromFile).filter(key => !(key in baseEnv) && !(key in additionalEnv))
|
|
400
|
-
|
|
401
|
-
Object.defineProperty(env, kEnvFileFallbackKeys, { value: fallbackKeys, enumerable: false })
|
|
402
|
-
|
|
403
|
-
return env
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
export function replaceEnv (config, env, onMissingEnv, ignore) {
|
|
407
|
-
// First of all, apply the ignore list
|
|
408
|
-
if (ignore) {
|
|
409
|
-
for (let path of ignore) {
|
|
410
|
-
// Migrate JSON Path to JSON Pointer
|
|
411
|
-
if (path.startsWith('$')) {
|
|
412
|
-
path = '/' + path.slice(2).replaceAll('.', '/')
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
const value = jsonPatch.getValueByPointer(config, path)
|
|
417
|
-
|
|
418
|
-
if (typeof value !== 'undefined') {
|
|
419
|
-
jsonPatch.applyOperation(config, {
|
|
420
|
-
op: 'add',
|
|
421
|
-
path,
|
|
422
|
-
value: { [kReplaceEnvIgnore]: true, originalValue: value }
|
|
423
|
-
})
|
|
424
|
-
}
|
|
425
|
-
} catch {
|
|
426
|
-
// No-op, the path does not exist
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (typeof config === 'object' && config !== null) {
|
|
432
|
-
if (config[kReplaceEnvIgnore]) {
|
|
433
|
-
return config.originalValue
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
for (const key of Object.keys(config)) {
|
|
437
|
-
config[key] = replaceEnv(config[key], env, onMissingEnv)
|
|
438
|
-
}
|
|
439
|
-
} else if (typeof config === 'string') {
|
|
440
|
-
let matches = config.match(envVariablePattern)
|
|
441
|
-
|
|
442
|
-
while (matches) {
|
|
443
|
-
const [template, key] = matches
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
const replacement = env[key] ?? onMissingEnv?.(key) ?? ''
|
|
447
|
-
config = config.replace(template, replacement)
|
|
448
|
-
|
|
449
|
-
matches = config.match(envVariablePattern)
|
|
450
|
-
} catch (error) {
|
|
451
|
-
throw new CannotParseConfigFileError(error.message, { cause: error })
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return config
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function normalizeStrictEnv (value) {
|
|
460
|
-
if (value === 'warn') {
|
|
461
|
-
return 'warn'
|
|
462
|
-
} else if (value === 'false' || value === '') {
|
|
463
|
-
return false
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
return Boolean(value)
|
|
467
|
-
}
|
|
329
|
+
/*
|
|
330
|
+
What remains of the v3 reader: parse a serialized configuration, run its capability's `upgrade`
|
|
331
|
+
chain, validate it, and hand it to `transform`.
|
|
468
332
|
|
|
333
|
+
It does not resolve an environment and does not substitute `{PLT_X}`. Those went with the format:
|
|
334
|
+
a v4 configuration is a program that reads `process.env` itself, and the loader resolves every
|
|
335
|
+
application's environment main-side, exactly once. What is left here reads the documents that are
|
|
336
|
+
deliberately still v3 -- the upgrade chains, which exist to be old -- and nothing else.
|
|
337
|
+
*/
|
|
469
338
|
export async function loadConfiguration (source, schema, options = {}) {
|
|
470
339
|
const {
|
|
471
340
|
validate: shouldValidate,
|
|
@@ -473,21 +342,12 @@ export async function loadConfiguration (source, schema, options = {}) {
|
|
|
473
342
|
transform,
|
|
474
343
|
upgrade,
|
|
475
344
|
env: additionalEnv,
|
|
476
|
-
ignoreProcessEnv,
|
|
477
|
-
replaceEnv: shouldReplaceEnv,
|
|
478
|
-
replaceEnvIgnore,
|
|
479
|
-
onMissingEnv,
|
|
480
|
-
strictEnv: strictEnvOption,
|
|
481
345
|
fixPaths,
|
|
482
346
|
logger,
|
|
483
|
-
skipMetadata
|
|
484
|
-
envFile: customEnvFile
|
|
347
|
+
skipMetadata
|
|
485
348
|
} = {
|
|
486
349
|
validate: !!schema,
|
|
487
350
|
validationOptions: {},
|
|
488
|
-
ignoreProcessEnv: false,
|
|
489
|
-
replaceEnv: true,
|
|
490
|
-
replaceEnvIgnore: [],
|
|
491
351
|
fixPaths: true,
|
|
492
352
|
...options
|
|
493
353
|
}
|
|
@@ -508,67 +368,7 @@ export async function loadConfiguration (source, schema, options = {}) {
|
|
|
508
368
|
throw new RootMissingError()
|
|
509
369
|
}
|
|
510
370
|
|
|
511
|
-
const env =
|
|
512
|
-
env.PLT_ROOT = root
|
|
513
|
-
|
|
514
|
-
if (shouldReplaceEnv) {
|
|
515
|
-
const missingEnv = new Set()
|
|
516
|
-
const fallbackEnv = new Set()
|
|
517
|
-
|
|
518
|
-
config = replaceEnv(
|
|
519
|
-
config,
|
|
520
|
-
env,
|
|
521
|
-
key => {
|
|
522
|
-
const value = onMissingEnv?.(key)
|
|
523
|
-
|
|
524
|
-
if (typeof value === 'undefined' || value === null) {
|
|
525
|
-
missingEnv.add(key)
|
|
526
|
-
} else {
|
|
527
|
-
// The variable is not set: it only has a value because onMissingEnv provided a fallback.
|
|
528
|
-
// Track it separately so that strictEnv can still report it, as a fallback can silently
|
|
529
|
-
// mask a misconfiguration.
|
|
530
|
-
fallbackEnv.add(key)
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return value
|
|
534
|
-
},
|
|
535
|
-
replaceEnvIgnore
|
|
536
|
-
)
|
|
537
|
-
|
|
538
|
-
// strictEnv can be set programmatically or in the configuration file itself, either at the top level
|
|
539
|
-
// (runtime configurations) or in the runtime property (capabilities configurations).
|
|
540
|
-
const strictEnv = normalizeStrictEnv(strictEnvOption ?? config.strictEnv ?? config.runtime?.strictEnv)
|
|
541
|
-
|
|
542
|
-
function warn (message) {
|
|
543
|
-
if (logger) {
|
|
544
|
-
logger.warn(message)
|
|
545
|
-
} else {
|
|
546
|
-
process.emitWarning(message)
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Variables resolved by a fallback are always reported as a warning, never as an error: they did
|
|
551
|
-
// resolve to a value, so failing on them would change which configurations are able to boot.
|
|
552
|
-
// This is emitted before handling the missing ones so that a throw does not swallow it.
|
|
553
|
-
if (strictEnv && fallbackEnv.size > 0) {
|
|
554
|
-
const keys = Array.from(fallbackEnv).sort().join(', ')
|
|
555
|
-
|
|
556
|
-
warn(
|
|
557
|
-
'The configuration references the following environment variables which are not set ' +
|
|
558
|
-
`and have been replaced by a fallback value: ${keys}`
|
|
559
|
-
)
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (strictEnv && missingEnv.size > 0) {
|
|
563
|
-
const keys = Array.from(missingEnv).sort().join(', ')
|
|
564
|
-
|
|
565
|
-
if (strictEnv === 'warn') {
|
|
566
|
-
warn(`The configuration references the following environment variables which are not set: ${keys}`)
|
|
567
|
-
} else {
|
|
568
|
-
throw new MissingEnvVariablesError(keys)
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
371
|
+
const env = { ...additionalEnv }
|
|
572
372
|
|
|
573
373
|
const moduleInfo = extractModuleFromSchemaUrl(config)
|
|
574
374
|
|
package/lib/file-system.js
CHANGED
|
@@ -165,7 +165,18 @@ export class FileWatcher extends EventEmitter {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
} /* c8 ignore next */
|
|
168
|
-
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
Caught at creation, not only in stopWatching. The loop rejects when the watch ends: normally
|
|
171
|
+
that is the AbortError stopWatching triggers, but fs.watch can also give up on its own -- a
|
|
172
|
+
recursive watch is best-effort on some platforms, and a transient failure on Windows is an
|
|
173
|
+
ordinary event. Left uncaught until stopWatching attaches its handler, such a failure is an
|
|
174
|
+
unhandled rejection, which under Node's default policy takes the whole process down. That is
|
|
175
|
+
what crashed `wattpm dev` for a configuration-less project on Windows, where the sole watcher
|
|
176
|
+
is a recursive one on the project directory: a dead watcher must stop reporting changes, not
|
|
177
|
+
end the process.
|
|
178
|
+
*/
|
|
179
|
+
this.handlePromise = eventHandler().catch(() => {})
|
|
169
180
|
}
|
|
170
181
|
|
|
171
182
|
async stopWatching () {
|
package/lib/schema.js
CHANGED
|
@@ -752,7 +752,7 @@ export const openTelemetryExporter = {
|
|
|
752
752
|
}
|
|
753
753
|
}
|
|
754
754
|
|
|
755
|
-
export const
|
|
755
|
+
export const tracing = {
|
|
756
756
|
type: 'object',
|
|
757
757
|
properties: {
|
|
758
758
|
enabled: {
|
|
@@ -866,7 +866,10 @@ export const compileCache = {
|
|
|
866
866
|
|
|
867
867
|
export const application = {
|
|
868
868
|
type: 'object',
|
|
869
|
-
anyOf: [
|
|
869
|
+
anyOf: [
|
|
870
|
+
{ required: ['id', 'path'] },
|
|
871
|
+
{ required: ['id', 'url'] }
|
|
872
|
+
],
|
|
870
873
|
properties: {
|
|
871
874
|
id: {
|
|
872
875
|
type: 'string'
|
|
@@ -898,6 +901,9 @@ export const application = {
|
|
|
898
901
|
url: {
|
|
899
902
|
type: 'string'
|
|
900
903
|
},
|
|
904
|
+
module: {
|
|
905
|
+
type: 'string'
|
|
906
|
+
},
|
|
901
907
|
gitBranch: {
|
|
902
908
|
type: 'string',
|
|
903
909
|
default: 'main'
|
|
@@ -1000,12 +1006,12 @@ export const application = {
|
|
|
1000
1006
|
},
|
|
1001
1007
|
additionalProperties: false
|
|
1002
1008
|
},
|
|
1003
|
-
|
|
1009
|
+
tracing: {
|
|
1004
1010
|
type: 'object',
|
|
1005
1011
|
properties: {
|
|
1006
1012
|
instrumentations: {
|
|
1007
1013
|
type: 'array',
|
|
1008
|
-
description: 'An array of instrumentations loaded if
|
|
1014
|
+
description: 'An array of instrumentations loaded if tracing is enabled',
|
|
1009
1015
|
items: {
|
|
1010
1016
|
oneOf: [
|
|
1011
1017
|
{
|
|
@@ -1088,7 +1094,7 @@ export const runtimeProperties = {
|
|
|
1088
1094
|
type: 'object',
|
|
1089
1095
|
additionalProperties: false,
|
|
1090
1096
|
required: ['id'],
|
|
1091
|
-
properties: omitProperties(applications.items.properties, ['path', 'url', 'gitBranch'])
|
|
1097
|
+
properties: omitProperties(applications.items.properties, ['path', 'url', 'gitBranch', 'module'])
|
|
1092
1098
|
}
|
|
1093
1099
|
}
|
|
1094
1100
|
}
|
|
@@ -1492,9 +1498,10 @@ export const runtimeProperties = {
|
|
|
1492
1498
|
},
|
|
1493
1499
|
additionalProperties: false
|
|
1494
1500
|
}
|
|
1495
|
-
]
|
|
1501
|
+
],
|
|
1502
|
+
default: false
|
|
1496
1503
|
},
|
|
1497
|
-
|
|
1504
|
+
tracing,
|
|
1498
1505
|
verticalScaler,
|
|
1499
1506
|
inspectorOptions: {
|
|
1500
1507
|
type: 'object',
|
|
@@ -1616,6 +1623,58 @@ export const runtimeProperties = {
|
|
|
1616
1623
|
compileCache
|
|
1617
1624
|
}
|
|
1618
1625
|
|
|
1626
|
+
/*
|
|
1627
|
+
The names the generated types carry.
|
|
1628
|
+
|
|
1629
|
+
`json-schema-to-typescript` hoists a subschema that has a `title` into an interface of that name
|
|
1630
|
+
and inlines one that does not, so without this every capability's `config.d.ts` spells `health` as
|
|
1631
|
+
an anonymous object literal -- eighteen copies of the same shape, none of which a person can name
|
|
1632
|
+
in their own code. With it, the document, the editor and the generated types use one vocabulary.
|
|
1633
|
+
|
|
1634
|
+
Assigned here rather than written at each site because several of these objects are copies of
|
|
1635
|
+
another: the application `health` is the runtime `health` with its defaults removed, and a `title`
|
|
1636
|
+
written into the shared literal would name both the same thing. Assignment also keeps the table
|
|
1637
|
+
readable as a table, which is what it is.
|
|
1638
|
+
|
|
1639
|
+
A `title` is annotation only -- AJV ignores it -- so this changes what is generated and nothing
|
|
1640
|
+
about what validates.
|
|
1641
|
+
*/
|
|
1642
|
+
workers.anyOf[2].title = 'WorkersOptions'
|
|
1643
|
+
extension.anyOf[1].title = 'ExtensionEntry'
|
|
1644
|
+
compileCache.anyOf[1].title = 'CompileCacheOptions'
|
|
1645
|
+
watch.title = 'WatchOptions'
|
|
1646
|
+
logger.title = 'AppLoggerOptions'
|
|
1647
|
+
server.title = 'AppServerOptions'
|
|
1648
|
+
server.properties.https.title = 'HttpsOptions'
|
|
1649
|
+
health.title = 'HealthOptions'
|
|
1650
|
+
tracing.title = 'TelemetryOptions'
|
|
1651
|
+
|
|
1652
|
+
/*
|
|
1653
|
+
Titled for the capability schemas, which list one application entry and generate it in full.
|
|
1654
|
+
|
|
1655
|
+
The runtime's own schema does not get the benefit: it lists three -- `applications` and the v3
|
|
1656
|
+
aliases `services` and `web` -- and the pinned generator, handed three copies of one 24-property
|
|
1657
|
+
object, gives up and emits `{ [k: string]: unknown }` for all three. Two copies generate
|
|
1658
|
+
correctly; three do not. Its next major fixes this, and a `$ref` for the aliases works there too,
|
|
1659
|
+
but that release is hours old and this repository will not install a package that new. So the
|
|
1660
|
+
runtime entry and the four option types nested in it stay anonymous for now, recorded in
|
|
1661
|
+
`scripts/check-blocks.mjs` rather than worked around.
|
|
1662
|
+
*/
|
|
1663
|
+
application.title = 'ApplicationEntry'
|
|
1664
|
+
|
|
1665
|
+
application.properties.health.title = 'ApplicationHealthOptions'
|
|
1666
|
+
application.properties.workers.anyOf[2].title = 'ApplicationWorkersOptions'
|
|
1667
|
+
application.properties.permissions.title = 'PermissionsOptions'
|
|
1668
|
+
application.properties.tracing.title = 'ApplicationTelemetryOverrides'
|
|
1669
|
+
|
|
1670
|
+
runtimeProperties.autoload.properties.mappings.additionalProperties.title = 'ApplicationEntryOverrides'
|
|
1671
|
+
runtimeProperties.gracefulShutdown.title = 'GracefulShutdownOptions'
|
|
1672
|
+
runtimeProperties.healthProbes.anyOf[2].title = 'HealthProbesOptions'
|
|
1673
|
+
runtimeProperties.undici.title = 'UndiciOptions'
|
|
1674
|
+
runtimeProperties.httpCache.oneOf[1].title = 'HttpCacheOptions'
|
|
1675
|
+
runtimeProperties.managementApi.anyOf[2].title = 'ManagementApiOptions'
|
|
1676
|
+
runtimeProperties.metrics.anyOf[1].title = 'MetricsOptions'
|
|
1677
|
+
|
|
1619
1678
|
export const runtimeUnwrappablePropertiesList = [
|
|
1620
1679
|
'$schema',
|
|
1621
1680
|
'entrypoint',
|
|
@@ -1634,6 +1693,7 @@ export const applicationsUnwrappablePropertiesList = [
|
|
|
1634
1693
|
'path',
|
|
1635
1694
|
'config',
|
|
1636
1695
|
'url',
|
|
1696
|
+
'module',
|
|
1637
1697
|
'gitBranch',
|
|
1638
1698
|
'dependencies',
|
|
1639
1699
|
'management'
|
|
@@ -1671,7 +1731,7 @@ export const schemaComponents = {
|
|
|
1671
1731
|
health,
|
|
1672
1732
|
healthWithoutDefaults,
|
|
1673
1733
|
openTelemetryExporter,
|
|
1674
|
-
|
|
1734
|
+
tracing,
|
|
1675
1735
|
policies,
|
|
1676
1736
|
compileCache,
|
|
1677
1737
|
applications,
|
package/lib/symbols.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export const kCanceled = Symbol('plt.foundation.canceled')
|
|
2
|
-
export const kEnvFileFallbackKeys = Symbol('plt.foundation.envFileFallbackKeys')
|
|
3
2
|
export const kFailedImport = Symbol('plt.foundation.failedImport')
|
|
4
3
|
export const kHandledError = Symbol('plt.foundation.handledError')
|
|
5
4
|
export const kMetadata = Symbol('plt.foundation.metadata')
|