@lorion-org/nuxt 1.0.0-beta.1 → 1.0.0-beta.2

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/src/module.ts ADDED
@@ -0,0 +1,876 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import {
4
+ addImports,
5
+ addServerImports,
6
+ addServerTemplate,
7
+ addTemplate,
8
+ addTypeTemplate,
9
+ createResolver,
10
+ defineNuxtModule,
11
+ useLogger,
12
+ } from '@nuxt/kit';
13
+ import type { Nuxt, NuxtConfigLayer, NuxtModule, NuxtOptions } from '@nuxt/schema';
14
+ import {
15
+ createNuxtExtensionBootstrap,
16
+ createNuxtProviderSelectionRuntimeConfig,
17
+ createNuxtSelectedProviderPreferences,
18
+ type NuxtExtensionBootstrap,
19
+ type NuxtExtensionEntry,
20
+ } from './extensions';
21
+ import {
22
+ createNuxtRuntimeConfig,
23
+ getNuxtExtensionSelection,
24
+ getNuxtProviderSelection,
25
+ mergeNuxtRuntimeConfig,
26
+ } from './runtime-config';
27
+ import {
28
+ createNuxtRuntimeConfigFromSource,
29
+ resolveNuxtRuntimeConfigPublicRootPath,
30
+ validateNuxtRuntimeConfigSourceScopes,
31
+ } from './runtime-config-node';
32
+ import type {
33
+ NuxtExtensionBootstrapLogEvent,
34
+ LorionNuxtModuleOptions,
35
+ NuxtPrivateRuntimeConfigMode,
36
+ NuxtProviderSelectionModuleOptions,
37
+ NuxtRuntimeConfig,
38
+ RuntimeConfigNuxtModuleOptions,
39
+ } from './types';
40
+ import {
41
+ resolveRuntimeConfigValidationMode,
42
+ shouldRequireRuntimeConfigAtStartup,
43
+ type RuntimeConfigValidationMode,
44
+ type RuntimeConfigValidationPolicyInput,
45
+ type RuntimeConfigValidationSchemaRegistry,
46
+ } from '@lorion-org/runtime-config';
47
+ import {
48
+ assertRequiredRuntimeConfigValidationTargets,
49
+ readRuntimeConfigSchemaRegistry,
50
+ type RuntimeConfigSchemaTargetInput,
51
+ type RuntimeConfigValidateResult,
52
+ } from '@lorion-org/runtime-config-node';
53
+
54
+ type RuntimeConfigComposableDefaults = {
55
+ contextOutputKey?: string;
56
+ privateInput?: NuxtPrivateRuntimeConfigMode;
57
+ };
58
+
59
+ type RuntimeConfigProjectionOptions = Omit<
60
+ RuntimeConfigNuxtModuleOptions,
61
+ 'enabled' | 'imports' | 'publicAssets' | 'source' | 'validation'
62
+ > & {
63
+ scopeIds?: string[];
64
+ };
65
+
66
+ type MutableNuxtOptions = NuxtOptions & {
67
+ _layers: NuxtConfigLayer[];
68
+ };
69
+
70
+ type RuntimeConfigPublicAssetsNitroConfig = {
71
+ publicAssets?: Array<{
72
+ dir: string;
73
+ maxAge?: number;
74
+ }>;
75
+ };
76
+
77
+ type RuntimeConfigPublicAssetsHook = (
78
+ name: 'nitro:config',
79
+ handler: (nitroConfig: RuntimeConfigPublicAssetsNitroConfig) => void,
80
+ ) => void;
81
+
82
+ const runtimeConfigImportNames = [
83
+ 'useRuntimeConfigFragment',
84
+ 'useValidatedRuntimeConfigFragment',
85
+ 'useRuntimeConfigScope',
86
+ 'useValidatedRuntimeConfigScope',
87
+ 'usePublicRuntimeConfigScope',
88
+ 'usePublicValidatedRuntimeConfigScope',
89
+ 'usePrivateRuntimeConfigScope',
90
+ 'usePrivateValidatedRuntimeConfigScope',
91
+ 'useRuntimeConfigValue',
92
+ ] as const;
93
+
94
+ const runtimeConfigComposablesTemplate = 'lorion/runtime-config-composables.mjs';
95
+ const runtimeConfigComposablesImport = '#build/lorion/runtime-config-composables';
96
+ const serverRuntimeConfigComposablesTemplate = '#internal/lorion-runtime-config-composables.mjs';
97
+
98
+ const defaultRuntimeConfigSource = {
99
+ contextInputKey: 'contexts',
100
+ contextOutputKey: '__contexts',
101
+ paths: ['.runtimeconfig/runtime-config/*/runtime.config.json'],
102
+ } as const;
103
+
104
+ const joinLogIds = (ids: string[]): string => ids.join(', ');
105
+
106
+ const hasLogIds = (ids: string[]): boolean => ids.length > 0;
107
+
108
+ function pickRuntimeConfigOptions(
109
+ options: RuntimeConfigNuxtModuleOptions,
110
+ bootstrap?: NuxtExtensionBootstrap,
111
+ ): RuntimeConfigProjectionOptions {
112
+ const runtimeConfigOptions: RuntimeConfigProjectionOptions = {};
113
+
114
+ if (options.contextInputKey !== undefined)
115
+ runtimeConfigOptions.contextInputKey = options.contextInputKey;
116
+ if (options.contextOutputKey !== undefined)
117
+ runtimeConfigOptions.contextOutputKey = options.contextOutputKey;
118
+ if (options.fragments !== undefined) runtimeConfigOptions.fragments = options.fragments;
119
+ if (options.includeContexts !== undefined)
120
+ runtimeConfigOptions.includeContexts = options.includeContexts;
121
+ if (options.keyStrategy !== undefined) runtimeConfigOptions.keyStrategy = options.keyStrategy;
122
+ if (options.privateOutput !== undefined)
123
+ runtimeConfigOptions.privateOutput = options.privateOutput;
124
+ if (options.runtimeConfig !== undefined)
125
+ runtimeConfigOptions.runtimeConfig = options.runtimeConfig;
126
+ if (bootstrap?.resolvedExtensionIds.length) {
127
+ runtimeConfigOptions.scopeIds = bootstrap.resolvedExtensionIds;
128
+ }
129
+
130
+ return runtimeConfigOptions;
131
+ }
132
+
133
+ function createRuntimeConfigComposableDefaults(
134
+ options: RuntimeConfigNuxtModuleOptions,
135
+ ): RuntimeConfigComposableDefaults {
136
+ return {
137
+ ...(options.contextOutputKey ? { contextOutputKey: options.contextOutputKey } : {}),
138
+ privateInput: options.privateOutput === 'section' ? 'section' : 'root',
139
+ };
140
+ }
141
+
142
+ function normalizeImportPath(path: string): string {
143
+ return path.replace(/\\/g, '/');
144
+ }
145
+
146
+ function createRuntimeConfigComposablesSource(input: {
147
+ validationSchemas: RuntimeConfigValidationSchemaRegistry;
148
+ runtimeConfigFrom: string;
149
+ typed: boolean;
150
+ useRuntimeConfigFrom: string;
151
+ }): string {
152
+ const validationSchemasSource = JSON.stringify(input.validationSchemas);
153
+ const typeImports = input.typed
154
+ ? `
155
+ import type {
156
+ GetRuntimeConfigFragmentOptions,
157
+ GetRuntimeConfigScopeOptions,
158
+ ResolveRuntimeConfigValueFromRuntimeConfigOptions,
159
+ RuntimeConfigContext,
160
+ RuntimeConfigSection,
161
+ } from '@lorion-org/runtime-config'
162
+ import type {
163
+ NuxtPrivateRuntimeConfigMode,
164
+ NuxtRuntimeConfigInput,
165
+ ReadNuxtRuntimeConfigOptions,
166
+ } from '${input.runtimeConfigFrom}'
167
+
168
+ export type RuntimeConfigComposableDefaults = {
169
+ contextOutputKey?: string
170
+ privateInput?: NuxtPrivateRuntimeConfigMode
171
+ }
172
+
173
+ export type UseRuntimeConfigScopeOptions = GetRuntimeConfigScopeOptions & ReadNuxtRuntimeConfigOptions
174
+ export type UsePublicRuntimeConfigScopeOptions =
175
+ Omit<GetRuntimeConfigScopeOptions, 'visibility'> &
176
+ ReadNuxtRuntimeConfigOptions
177
+ export type UseRuntimeConfigValueOptions<T = unknown> =
178
+ ResolveRuntimeConfigValueFromRuntimeConfigOptions<T> &
179
+ ReadNuxtRuntimeConfigOptions
180
+ export type UseRuntimeConfigFragmentOptions =
181
+ GetRuntimeConfigFragmentOptions &
182
+ ReadNuxtRuntimeConfigOptions
183
+ `
184
+ : '';
185
+ const typeAnnotations = input.typed
186
+ ? {
187
+ defaults: ': RuntimeConfigComposableDefaults',
188
+ fragmentReturn: ': RuntimeConfigContext',
189
+ fragmentOptions: ': UseRuntimeConfigFragmentOptions',
190
+ privateOptions: ': UsePublicRuntimeConfigScopeOptions',
191
+ publicOptions: ': UsePublicRuntimeConfigScopeOptions',
192
+ runtimeConfig: ': NuxtRuntimeConfigInput',
193
+ scopeOptions: ': UseRuntimeConfigScopeOptions',
194
+ scopeReturn: '<T extends RuntimeConfigSection = RuntimeConfigSection>',
195
+ valueOptions: '<T = unknown>',
196
+ valueOptionsType: ': UseRuntimeConfigValueOptions<T>',
197
+ valueReturn: ': T | undefined',
198
+ }
199
+ : {
200
+ defaults: '',
201
+ fragmentReturn: '',
202
+ fragmentOptions: '',
203
+ privateOptions: '',
204
+ publicOptions: '',
205
+ runtimeConfig: '',
206
+ scopeOptions: '',
207
+ scopeReturn: '',
208
+ valueOptions: '',
209
+ valueOptionsType: '',
210
+ valueReturn: '',
211
+ };
212
+
213
+ return `import { useRuntimeConfig } from '${input.useRuntimeConfigFrom}'
214
+ import { createRuntimeConfigValidatorRegistry } from '@lorion-org/runtime-config'
215
+ import {
216
+ getNuxtRuntimeConfigFragment,
217
+ getNuxtRuntimeConfigScope,
218
+ getPrivateNuxtRuntimeConfigScope,
219
+ getPublicNuxtRuntimeConfigScope,
220
+ resolveNuxtRuntimeConfigValue,
221
+ } from '${input.runtimeConfigFrom}'
222
+ ${typeImports}
223
+ const runtimeConfigDefaultsKey = '__lorionNuxt'
224
+ const runtimeConfigValidationSchemas = ${validationSchemasSource}
225
+ const runtimeConfigValidatorRegistry = createRuntimeConfigValidatorRegistry(runtimeConfigValidationSchemas)
226
+
227
+ function assertValidatedRuntimeConfigFragment(scopeId, fragment) {
228
+ runtimeConfigValidatorRegistry.assert(scopeId, fragment)
229
+ }
230
+
231
+ function getComposableDefaults(runtimeConfig${typeAnnotations.runtimeConfig})${typeAnnotations.defaults} {
232
+ const container = runtimeConfig.public?.[runtimeConfigDefaultsKey]
233
+
234
+ if (typeof container !== 'object' || container === null || Array.isArray(container)) {
235
+ return {}
236
+ }
237
+
238
+ const defaults = container.runtimeConfig
239
+
240
+ return typeof defaults === 'object' && defaults !== null && !Array.isArray(defaults)
241
+ ? defaults
242
+ : {}
243
+ }
244
+
245
+ function withComposableDefaults(runtimeConfig${typeAnnotations.runtimeConfig}, options = {}) {
246
+ return {
247
+ ...getComposableDefaults(runtimeConfig),
248
+ ...options,
249
+ }
250
+ }
251
+
252
+ export function useRuntimeConfigFragment(scopeId, options${typeAnnotations.fragmentOptions} = {})${typeAnnotations.fragmentReturn} {
253
+ const runtimeConfig = useRuntimeConfig()
254
+
255
+ return getNuxtRuntimeConfigFragment(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options))
256
+ }
257
+
258
+ export function useValidatedRuntimeConfigFragment(scopeId, options${typeAnnotations.fragmentOptions} = {})${typeAnnotations.fragmentReturn} {
259
+ const runtimeConfig = useRuntimeConfig()
260
+ const fragment = getNuxtRuntimeConfigFragment(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options))
261
+
262
+ assertValidatedRuntimeConfigFragment(scopeId, fragment)
263
+
264
+ return fragment
265
+ }
266
+
267
+ export function useRuntimeConfigScope${typeAnnotations.scopeReturn}(scopeId, options${typeAnnotations.scopeOptions} = {}) {
268
+ const runtimeConfig = useRuntimeConfig()
269
+
270
+ return getNuxtRuntimeConfigScope(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options))
271
+ }
272
+
273
+ export function useValidatedRuntimeConfigScope${typeAnnotations.scopeReturn}(scopeId, options${typeAnnotations.scopeOptions} = {}) {
274
+ const runtimeConfig = useRuntimeConfig()
275
+
276
+ assertValidatedRuntimeConfigFragment(
277
+ scopeId,
278
+ getNuxtRuntimeConfigFragment(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options)),
279
+ )
280
+
281
+ return getNuxtRuntimeConfigScope(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options))
282
+ }
283
+
284
+ export function usePublicRuntimeConfigScope${typeAnnotations.scopeReturn}(scopeId, options${typeAnnotations.publicOptions} = {}) {
285
+ const runtimeConfig = useRuntimeConfig()
286
+
287
+ return getPublicNuxtRuntimeConfigScope(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options))
288
+ }
289
+
290
+ export function usePublicValidatedRuntimeConfigScope${typeAnnotations.scopeReturn}(scopeId, options${typeAnnotations.publicOptions} = {}) {
291
+ const runtimeConfig = useRuntimeConfig()
292
+
293
+ assertValidatedRuntimeConfigFragment(
294
+ scopeId,
295
+ getNuxtRuntimeConfigFragment(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options)),
296
+ )
297
+
298
+ return getPublicNuxtRuntimeConfigScope(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options))
299
+ }
300
+
301
+ export function usePrivateRuntimeConfigScope${typeAnnotations.scopeReturn}(scopeId, options${typeAnnotations.privateOptions} = {}) {
302
+ const runtimeConfig = useRuntimeConfig()
303
+
304
+ return getPrivateNuxtRuntimeConfigScope(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options))
305
+ }
306
+
307
+ export function usePrivateValidatedRuntimeConfigScope${typeAnnotations.scopeReturn}(scopeId, options${typeAnnotations.privateOptions} = {}) {
308
+ const runtimeConfig = useRuntimeConfig()
309
+
310
+ assertValidatedRuntimeConfigFragment(
311
+ scopeId,
312
+ getNuxtRuntimeConfigFragment(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options)),
313
+ )
314
+
315
+ return getPrivateNuxtRuntimeConfigScope(runtimeConfig, scopeId, withComposableDefaults(runtimeConfig, options))
316
+ }
317
+
318
+ export function useRuntimeConfigValue${typeAnnotations.valueOptions}(scopeId, key, options${typeAnnotations.valueOptionsType} = {})${typeAnnotations.valueReturn} {
319
+ const runtimeConfig = useRuntimeConfig()
320
+
321
+ return resolveNuxtRuntimeConfigValue(runtimeConfig, scopeId, key, withComposableDefaults(runtimeConfig, options))
322
+ }
323
+ `;
324
+ }
325
+
326
+ function createRuntimeConfigComposablesTypesSource(): string {
327
+ return `declare module '${runtimeConfigComposablesImport}' {
328
+ import type {
329
+ GetRuntimeConfigFragmentOptions,
330
+ GetRuntimeConfigScopeOptions,
331
+ ResolveRuntimeConfigValueFromRuntimeConfigOptions,
332
+ RuntimeConfigContext,
333
+ RuntimeConfigSection,
334
+ } from '@lorion-org/runtime-config'
335
+
336
+ type NuxtPrivateRuntimeConfigMode = 'root' | 'section'
337
+ type ReadNuxtRuntimeConfigOptions = {
338
+ privateInput?: NuxtPrivateRuntimeConfigMode
339
+ }
340
+
341
+ export function useRuntimeConfigFragment(
342
+ scopeId: string,
343
+ options?: GetRuntimeConfigFragmentOptions & ReadNuxtRuntimeConfigOptions,
344
+ ): RuntimeConfigContext
345
+ export function useValidatedRuntimeConfigFragment(
346
+ scopeId: string,
347
+ options?: GetRuntimeConfigFragmentOptions & ReadNuxtRuntimeConfigOptions,
348
+ ): RuntimeConfigContext
349
+ export function useRuntimeConfigScope<T extends RuntimeConfigSection = RuntimeConfigSection>(
350
+ scopeId: string,
351
+ options?: GetRuntimeConfigScopeOptions & ReadNuxtRuntimeConfigOptions,
352
+ ): T
353
+ export function useValidatedRuntimeConfigScope<
354
+ T extends RuntimeConfigSection = RuntimeConfigSection,
355
+ >(scopeId: string, options?: GetRuntimeConfigScopeOptions & ReadNuxtRuntimeConfigOptions): T
356
+ export function usePublicRuntimeConfigScope<
357
+ T extends RuntimeConfigSection = RuntimeConfigSection,
358
+ >(
359
+ scopeId: string,
360
+ options?: Omit<GetRuntimeConfigScopeOptions, 'visibility'> & ReadNuxtRuntimeConfigOptions,
361
+ ): T
362
+ export function usePublicValidatedRuntimeConfigScope<
363
+ T extends RuntimeConfigSection = RuntimeConfigSection,
364
+ >(
365
+ scopeId: string,
366
+ options?: Omit<GetRuntimeConfigScopeOptions, 'visibility'> & ReadNuxtRuntimeConfigOptions,
367
+ ): T
368
+ export function usePrivateRuntimeConfigScope<
369
+ T extends RuntimeConfigSection = RuntimeConfigSection,
370
+ >(
371
+ scopeId: string,
372
+ options?: Omit<GetRuntimeConfigScopeOptions, 'visibility'> & ReadNuxtRuntimeConfigOptions,
373
+ ): T
374
+ export function usePrivateValidatedRuntimeConfigScope<
375
+ T extends RuntimeConfigSection = RuntimeConfigSection,
376
+ >(
377
+ scopeId: string,
378
+ options?: Omit<GetRuntimeConfigScopeOptions, 'visibility'> & ReadNuxtRuntimeConfigOptions,
379
+ ): T
380
+ export function useRuntimeConfigValue<T = unknown>(
381
+ scopeId: string,
382
+ key: string,
383
+ options?: ResolveRuntimeConfigValueFromRuntimeConfigOptions<T> & ReadNuxtRuntimeConfigOptions,
384
+ ): T | undefined
385
+ }
386
+
387
+ declare module '${serverRuntimeConfigComposablesTemplate}' {
388
+ export * from '${runtimeConfigComposablesImport}'
389
+ }
390
+ `;
391
+ }
392
+
393
+ function createDefaultRuntimeConfigOptions(
394
+ rootDir: string,
395
+ ): RuntimeConfigNuxtModuleOptions | undefined {
396
+ const paths = defaultRuntimeConfigSource.paths.map((pattern) => join(rootDir, pattern));
397
+ const runtimeConfigDir = join(rootDir, '.runtimeconfig', 'runtime-config');
398
+
399
+ if (!existsSync(runtimeConfigDir)) return undefined;
400
+
401
+ return {
402
+ contextInputKey: defaultRuntimeConfigSource.contextInputKey,
403
+ contextOutputKey: defaultRuntimeConfigSource.contextOutputKey,
404
+ source: { paths },
405
+ };
406
+ }
407
+
408
+ function resolveRuntimeConfigModuleOptions(
409
+ options: RuntimeConfigNuxtModuleOptions | undefined,
410
+ rootDir: string,
411
+ ): RuntimeConfigNuxtModuleOptions | undefined {
412
+ if (options?.enabled === false) return undefined;
413
+
414
+ return options ?? createDefaultRuntimeConfigOptions(rootDir);
415
+ }
416
+
417
+ function registerRuntimeConfigPublicAssets(
418
+ options: RuntimeConfigNuxtModuleOptions,
419
+ nuxt: Nuxt,
420
+ ): void {
421
+ if (options.publicAssets === false || !options.source) return;
422
+
423
+ const publicRoot = resolveNuxtRuntimeConfigPublicRootPath(options.source);
424
+ if (!publicRoot || !existsSync(publicRoot)) return;
425
+
426
+ const maxAge = typeof options.publicAssets === 'object' ? options.publicAssets.maxAge : undefined;
427
+ const hook = nuxt.hook as unknown as RuntimeConfigPublicAssetsHook;
428
+
429
+ hook('nitro:config', (nitroConfig) => {
430
+ nitroConfig.publicAssets = nitroConfig.publicAssets ?? [];
431
+ nitroConfig.publicAssets.push({
432
+ dir: publicRoot,
433
+ ...(maxAge === undefined ? {} : { maxAge }),
434
+ });
435
+ });
436
+ }
437
+
438
+ function resolveRuntimeConfigValidationTargets(
439
+ bootstrap?: NuxtExtensionBootstrap,
440
+ ): RuntimeConfigSchemaTargetInput[] {
441
+ return (
442
+ bootstrap?.resolvedExtensions
443
+ .filter((extension) => extension.descriptor.runtimeConfig !== undefined)
444
+ .map((extension) => ({
445
+ scopeId: extension.descriptor.id,
446
+ cwd: extension.cwd,
447
+ policy: extension.descriptor.runtimeConfig,
448
+ })) ?? []
449
+ );
450
+ }
451
+
452
+ function createRuntimeConfigValidationSchemas(
453
+ options: RuntimeConfigNuxtModuleOptions,
454
+ bootstrap?: NuxtExtensionBootstrap,
455
+ ): RuntimeConfigValidationSchemaRegistry {
456
+ if (options.validation === false || !options.validation) return {};
457
+
458
+ return readRuntimeConfigSchemaRegistry(resolveRuntimeConfigValidationTargets(bootstrap), {
459
+ ...(options.validation.schemaFileName
460
+ ? { schemaFileName: options.validation.schemaFileName }
461
+ : {}),
462
+ });
463
+ }
464
+
465
+ function createRuntimeConfigValidationPolicyByScope(
466
+ targets: RuntimeConfigSchemaTargetInput[],
467
+ ): Map<string, RuntimeConfigValidationPolicyInput> {
468
+ return new Map(targets.map((target) => [target.scopeId, target.policy]));
469
+ }
470
+
471
+ function formatRuntimeConfigValidationMode(
472
+ policy: RuntimeConfigValidationPolicyInput,
473
+ ): RuntimeConfigValidationMode {
474
+ return resolveRuntimeConfigValidationMode(policy);
475
+ }
476
+
477
+ function isRuntimeConfigValidationFatal(
478
+ scopeId: string,
479
+ policyByScope: Map<string, RuntimeConfigValidationPolicyInput>,
480
+ ): boolean {
481
+ return shouldRequireRuntimeConfigAtStartup(policyByScope.get(scopeId));
482
+ }
483
+
484
+ function formatRuntimeConfigSkippedValidationDetails(
485
+ skipped: RuntimeConfigValidateResult['skipped'][number],
486
+ mode: RuntimeConfigValidationMode,
487
+ ): string {
488
+ if (skipped.reason === 'missing-schema') {
489
+ return [
490
+ `RuntimeConfig schema file missing for "${skipped.scopeId}" (${mode}).`,
491
+ ...(skipped.schemaPath ? [`missing schema file: ${skipped.schemaPath}`] : []),
492
+ ...(skipped.configPath ? [`runtime config file: ${skipped.configPath}`] : []),
493
+ ].join(' ');
494
+ }
495
+
496
+ if (skipped.reason === 'missing-config') {
497
+ return [
498
+ `RuntimeConfig file missing for "${skipped.scopeId}" (${mode}).`,
499
+ ...(skipped.configPath ? [`expected runtime config file: ${skipped.configPath}`] : []),
500
+ ...(skipped.schemaPath ? [`schema file: ${skipped.schemaPath}`] : []),
501
+ ].join(' ');
502
+ }
503
+
504
+ return [
505
+ `RuntimeConfig validation target has no schema directory for "${skipped.scopeId}" (${mode}).`,
506
+ ...(skipped.configPath ? [`runtime config file: ${skipped.configPath}`] : []),
507
+ ...(skipped.schemaPath ? [`schema file: ${skipped.schemaPath}`] : []),
508
+ ].join(' ');
509
+ }
510
+
511
+ function reportRuntimeConfigValidationResult(
512
+ result: RuntimeConfigValidateResult,
513
+ targets: RuntimeConfigSchemaTargetInput[],
514
+ ): void {
515
+ if (!result.skipped.length && !result.invalid.length) return;
516
+
517
+ const logger = useLogger('lorion');
518
+ const policyByScope = createRuntimeConfigValidationPolicyByScope(targets);
519
+
520
+ for (const skipped of result.skipped) {
521
+ const fatal = isRuntimeConfigValidationFatal(skipped.scopeId, policyByScope);
522
+ const mode = formatRuntimeConfigValidationMode(policyByScope.get(skipped.scopeId));
523
+ const details = formatRuntimeConfigSkippedValidationDetails(skipped, mode);
524
+
525
+ logger[fatal ? 'error' : 'warn'](details);
526
+ }
527
+
528
+ for (const invalid of result.invalid) {
529
+ const fatal = isRuntimeConfigValidationFatal(invalid.scopeId, policyByScope);
530
+ const mode = formatRuntimeConfigValidationMode(policyByScope.get(invalid.scopeId));
531
+
532
+ logger[fatal ? 'error' : 'warn'](
533
+ [
534
+ `RuntimeConfig validation failed for "${invalid.scopeId}" (${mode}).`,
535
+ `runtime config file: ${invalid.configPath}`,
536
+ `schema file: ${invalid.schemaPath}`,
537
+ invalid.error.message,
538
+ ].join(' '),
539
+ );
540
+ }
541
+ }
542
+
543
+ function validateRuntimeConfigSource(
544
+ options: RuntimeConfigNuxtModuleOptions,
545
+ bootstrap?: NuxtExtensionBootstrap,
546
+ ): void {
547
+ if (!options.source || options.validation === false || !options.validation) return;
548
+ const targets = resolveRuntimeConfigValidationTargets(bootstrap);
549
+
550
+ if (!targets.length) return;
551
+
552
+ const result = validateNuxtRuntimeConfigSourceScopes(options.source, targets, {
553
+ ...(options.validation.formatError ? { formatError: options.validation.formatError } : {}),
554
+ ...(options.validation.schemaFileName
555
+ ? { schemaFileName: options.validation.schemaFileName }
556
+ : {}),
557
+ });
558
+
559
+ reportRuntimeConfigValidationResult(result, targets);
560
+ assertRequiredRuntimeConfigValidationTargets(result, targets);
561
+ }
562
+
563
+ function addRuntimeConfigComposables(
564
+ options: RuntimeConfigNuxtModuleOptions,
565
+ bootstrap?: NuxtExtensionBootstrap,
566
+ hostImports?: { scan?: unknown },
567
+ registerImports = true,
568
+ ): void {
569
+ const resolver = createResolver(import.meta.url);
570
+ const runtimeConfigFrom = normalizeImportPath(resolver.resolve('./runtime-config'));
571
+ const validationSchemas = createRuntimeConfigValidationSchemas(options, bootstrap);
572
+ addTemplate({
573
+ filename: runtimeConfigComposablesTemplate,
574
+ write: true,
575
+ getContents: () =>
576
+ createRuntimeConfigComposablesSource({
577
+ runtimeConfigFrom,
578
+ typed: false,
579
+ useRuntimeConfigFrom: 'nuxt/app',
580
+ validationSchemas,
581
+ }),
582
+ });
583
+ addTypeTemplate(
584
+ {
585
+ filename: 'types/lorion-runtime-config-composables.d.ts',
586
+ getContents: createRuntimeConfigComposablesTypesSource,
587
+ },
588
+ { nitro: true, nuxt: true },
589
+ );
590
+
591
+ addServerTemplate({
592
+ filename: serverRuntimeConfigComposablesTemplate,
593
+ getContents: () =>
594
+ createRuntimeConfigComposablesSource({
595
+ runtimeConfigFrom,
596
+ typed: false,
597
+ useRuntimeConfigFrom: 'nitropack/runtime',
598
+ validationSchemas,
599
+ }),
600
+ });
601
+
602
+ const imports = runtimeConfigImportNames.map((name) => ({
603
+ as: name,
604
+ from: runtimeConfigComposablesImport,
605
+ name,
606
+ }));
607
+ const serverImports = runtimeConfigImportNames.map((name) => ({
608
+ as: name,
609
+ from: serverRuntimeConfigComposablesTemplate,
610
+ name,
611
+ }));
612
+
613
+ if (!registerImports) return;
614
+ if (!shouldRegisterRuntimeConfigImports(options, hostImports)) return;
615
+
616
+ addImports(imports);
617
+ addServerImports(serverImports);
618
+ }
619
+
620
+ export function shouldRegisterRuntimeConfigImports(
621
+ options: Pick<RuntimeConfigNuxtModuleOptions, 'imports'>,
622
+ hostImports?: { scan?: unknown },
623
+ ): boolean {
624
+ if (options.imports !== undefined) return options.imports;
625
+
626
+ return hostImports?.scan !== false;
627
+ }
628
+
629
+ function createRuntimeConfigDefaultsConfig(
630
+ options: RuntimeConfigNuxtModuleOptions,
631
+ ): NuxtRuntimeConfig {
632
+ return {
633
+ public: {
634
+ __lorionNuxt: {
635
+ runtimeConfig: createRuntimeConfigComposableDefaults(options),
636
+ },
637
+ },
638
+ };
639
+ }
640
+
641
+ export function createConfiguredNuxtRuntimeConfig(
642
+ options: LorionNuxtModuleOptions,
643
+ bootstrap?: NuxtExtensionBootstrap,
644
+ ): NuxtRuntimeConfig | undefined {
645
+ if (!options.runtimeConfig || options.runtimeConfig.enabled === false) return undefined;
646
+
647
+ const runtimeConfigOptions = pickRuntimeConfigOptions(options.runtimeConfig, bootstrap);
648
+
649
+ return options.runtimeConfig.source
650
+ ? createNuxtRuntimeConfigFromSource(options.runtimeConfig.source, runtimeConfigOptions)
651
+ : createNuxtRuntimeConfig(runtimeConfigOptions);
652
+ }
653
+
654
+ export function createNuxtRuntimeConfigExtension(
655
+ options: RuntimeConfigNuxtModuleOptions,
656
+ ): NuxtRuntimeConfig | undefined {
657
+ return createConfiguredNuxtRuntimeConfig({
658
+ runtimeConfig: options,
659
+ });
660
+ }
661
+
662
+ function applyRuntimeConfigModule(input: {
663
+ bootstrap?: NuxtExtensionBootstrap;
664
+ currentRuntimeConfig: NuxtRuntimeConfig;
665
+ moduleOptions?: RuntimeConfigNuxtModuleOptions;
666
+ nuxt: Nuxt;
667
+ rootDir: string;
668
+ }): NuxtRuntimeConfig {
669
+ const runtimeConfigOptions = resolveRuntimeConfigModuleOptions(
670
+ input.moduleOptions,
671
+ input.rootDir,
672
+ );
673
+ const runtimeConfig = runtimeConfigOptions
674
+ ? createConfiguredNuxtRuntimeConfig({ runtimeConfig: runtimeConfigOptions }, input.bootstrap)
675
+ : undefined;
676
+
677
+ addRuntimeConfigComposables(
678
+ runtimeConfigOptions ?? {},
679
+ input.bootstrap,
680
+ input.nuxt.options.imports,
681
+ Boolean(runtimeConfigOptions),
682
+ );
683
+
684
+ if (!runtimeConfigOptions) return input.currentRuntimeConfig;
685
+
686
+ registerRuntimeConfigPublicAssets(runtimeConfigOptions, input.nuxt);
687
+ validateRuntimeConfigSource(runtimeConfigOptions, input.bootstrap);
688
+
689
+ return mergeNuxtRuntimeConfig(
690
+ input.currentRuntimeConfig,
691
+ mergeNuxtRuntimeConfig(createRuntimeConfigDefaultsConfig(runtimeConfigOptions), runtimeConfig),
692
+ );
693
+ }
694
+
695
+ function createProviderSelectionRuntimeConfig(
696
+ bootstrap: NuxtExtensionBootstrap,
697
+ options: NuxtProviderSelectionModuleOptions | undefined,
698
+ ): NuxtRuntimeConfig | undefined {
699
+ if (options?.enabled === false) return undefined;
700
+
701
+ return createNuxtProviderSelectionRuntimeConfig(bootstrap.resolvedExtensions, {
702
+ ...options,
703
+ selectedProviders: {
704
+ ...createNuxtSelectedProviderPreferences({
705
+ entries: bootstrap.resolvedExtensions,
706
+ selectedExtensions: bootstrap.selectedExtensions,
707
+ }),
708
+ ...(options?.selectedProviders ?? {}),
709
+ },
710
+ });
711
+ }
712
+
713
+ export function createNuxtExtensionBootstrapLogEvent(input: {
714
+ bootstrap: NuxtExtensionBootstrap;
715
+ providerSelectionRuntimeConfig?: NuxtRuntimeConfig;
716
+ }): NuxtExtensionBootstrapLogEvent {
717
+ const providerSelection = input.providerSelectionRuntimeConfig
718
+ ? getNuxtProviderSelection(input.providerSelectionRuntimeConfig)
719
+ : undefined;
720
+
721
+ return {
722
+ bootstrap: input.bootstrap,
723
+ ...(providerSelection ? { providerSelection } : {}),
724
+ };
725
+ }
726
+
727
+ export function formatNuxtExtensionBootstrapLog(event: NuxtExtensionBootstrapLogEvent): string {
728
+ const lines = ['LORION Nuxt'];
729
+ const bootstrap = event.bootstrap;
730
+ const extensionSelection = getNuxtExtensionSelection(bootstrap.publicRuntimeConfig);
731
+
732
+ if (hasLogIds(bootstrap.selectedExtensions)) {
733
+ lines.push(`Selected: ${joinLogIds(bootstrap.selectedExtensions)}`);
734
+ }
735
+
736
+ if (hasLogIds(bootstrap.baseExtensionIds)) {
737
+ lines.push(`Base: ${joinLogIds(bootstrap.baseExtensionIds)}`);
738
+ }
739
+
740
+ lines.push(`Descriptors found: ${bootstrap.discoveredExtensions.length}`);
741
+
742
+ if (hasLogIds(bootstrap.resolvedExtensionIds)) {
743
+ lines.push(`Injected: ${joinLogIds(bootstrap.resolvedExtensionIds)}`);
744
+ }
745
+
746
+ if (hasLogIds(extensionSelection.notInjectedExtensionIds)) {
747
+ lines.push(`Not injected: ${joinLogIds(extensionSelection.notInjectedExtensionIds)}`);
748
+ }
749
+
750
+ const providerSelections = Object.values(event.providerSelection?.selections ?? {}).sort(
751
+ (left, right) => left.capabilityId.localeCompare(right.capabilityId),
752
+ );
753
+
754
+ for (const selection of providerSelections) {
755
+ lines.push(`Provider ${selection.capabilityId}: ${selection.selectedProviderId}`);
756
+ }
757
+
758
+ return lines.join('\n');
759
+ }
760
+
761
+ export function reportNuxtExtensionBootstrap(input: {
762
+ bootstrap: NuxtExtensionBootstrap;
763
+ logging?: LorionNuxtModuleOptions['logging'];
764
+ providerSelectionRuntimeConfig?: NuxtRuntimeConfig;
765
+ }): void {
766
+ if (!input.logging || input.logging === true || !input.logging.reporter) return;
767
+
768
+ input.logging.reporter(
769
+ createNuxtExtensionBootstrapLogEvent({
770
+ bootstrap: input.bootstrap,
771
+ ...(input.providerSelectionRuntimeConfig
772
+ ? { providerSelectionRuntimeConfig: input.providerSelectionRuntimeConfig }
773
+ : {}),
774
+ }),
775
+ );
776
+ }
777
+
778
+ function getLayerRoot(layer: NuxtConfigLayer): string {
779
+ return layer.config.rootDir ?? layer.cwd;
780
+ }
781
+
782
+ function createLayerConfig(extension: NuxtExtensionEntry): NuxtConfigLayer {
783
+ return {
784
+ cwd: extension.cwd,
785
+ configFile: '',
786
+ config: {
787
+ rootDir: extension.cwd,
788
+ serverDir: extension.serverDir ?? join(extension.cwd, 'server'),
789
+ srcDir: extension.appDir ?? extension.cwd,
790
+ },
791
+ };
792
+ }
793
+
794
+ function registerFileOnlyExtensionLayers(
795
+ bootstrap: NuxtExtensionBootstrap,
796
+ nuxtOptions: NuxtOptions,
797
+ ): void {
798
+ const fileOnlyExtensions = bootstrap.activeExtensions.filter(
799
+ (extension) => !extension.configFile,
800
+ );
801
+
802
+ if (!fileOnlyExtensions.length) return;
803
+
804
+ const options = nuxtOptions as MutableNuxtOptions;
805
+ const layers = [...options._layers];
806
+ const existingRoots = new Set(layers.map((layer) => getLayerRoot(layer)));
807
+ const extensionLayers = fileOnlyExtensions
808
+ .filter((extension) => {
809
+ if (existingRoots.has(extension.cwd)) return false;
810
+
811
+ existingRoots.add(extension.cwd);
812
+ return true;
813
+ })
814
+ .map(createLayerConfig);
815
+
816
+ if (!extensionLayers.length) return;
817
+
818
+ const [projectLayer, ...baseLayers] = layers;
819
+ options._layers = projectLayer
820
+ ? [projectLayer, ...extensionLayers, ...baseLayers]
821
+ : [...extensionLayers];
822
+ }
823
+
824
+ const lorionNuxtModule: NuxtModule<LorionNuxtModuleOptions> =
825
+ defineNuxtModule<LorionNuxtModuleOptions>({
826
+ meta: {
827
+ name: '@lorion-org/nuxt',
828
+ configKey: 'lorion',
829
+ },
830
+ setup(options, nuxt) {
831
+ const bootstrap =
832
+ options.extensionBootstrap ??
833
+ (options.extensions?.enabled === false || !options.extensions
834
+ ? undefined
835
+ : createNuxtExtensionBootstrap({
836
+ rootDir: nuxt.options.rootDir,
837
+ options: options.extensions,
838
+ }));
839
+
840
+ if (bootstrap) {
841
+ const providerSelectionRuntimeConfig = createProviderSelectionRuntimeConfig(
842
+ bootstrap,
843
+ options.providers,
844
+ );
845
+ const logger = useLogger('@lorion-org/nuxt');
846
+ const logging =
847
+ options.logging === true
848
+ ? {
849
+ reporter: (event: NuxtExtensionBootstrapLogEvent) =>
850
+ logger.info(formatNuxtExtensionBootstrapLog(event)),
851
+ }
852
+ : options.logging;
853
+
854
+ reportNuxtExtensionBootstrap({
855
+ bootstrap,
856
+ logging,
857
+ ...(providerSelectionRuntimeConfig ? { providerSelectionRuntimeConfig } : {}),
858
+ });
859
+ registerFileOnlyExtensionLayers(bootstrap, nuxt.options);
860
+ nuxt.options.runtimeConfig = mergeNuxtRuntimeConfig(
861
+ nuxt.options.runtimeConfig,
862
+ mergeNuxtRuntimeConfig(bootstrap.publicRuntimeConfig, providerSelectionRuntimeConfig),
863
+ );
864
+ }
865
+
866
+ nuxt.options.runtimeConfig = applyRuntimeConfigModule({
867
+ ...(bootstrap ? { bootstrap } : {}),
868
+ currentRuntimeConfig: nuxt.options.runtimeConfig,
869
+ nuxt,
870
+ rootDir: nuxt.options.rootDir,
871
+ ...(options.runtimeConfig ? { moduleOptions: options.runtimeConfig } : {}),
872
+ });
873
+ },
874
+ });
875
+
876
+ export default lorionNuxtModule;