nodefony 7.0.2 → 10.0.0-alpha.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/.ai/symbols.json +36135 -0
- package/LICENSE +544 -0
- package/README.md +316 -694
- package/bin/nodefony +2183 -85
- package/dist/client/Container.js +367 -0
- package/dist/client/Event.js +198 -0
- package/dist/client/Service.js +344 -0
- package/dist/client/Tools.js +162 -0
- package/dist/client/client/ClientKernel.js +276 -0
- package/dist/client/client/angular/index.js +352 -0
- package/dist/client/client/announce.js +212 -0
- package/dist/client/client/debugbar/DebugBar.js +1624 -0
- package/dist/client/client/debugbar/format.js +103 -0
- package/dist/client/client/debugbar/hmr.js +37 -0
- package/dist/client/client/debugbar/index.js +29 -0
- package/dist/client/client/debugbar/model.js +133 -0
- package/dist/client/client/debugbar/network.js +173 -0
- package/dist/client/client/debugbar/profile.js +114 -0
- package/dist/client/client/index.js +34 -0
- package/dist/client/client/react/index.js +268 -0
- package/dist/client/client/realtime/AdaptiveRate.js +240 -0
- package/dist/client/client/realtime/BrowserWsTransport.js +57 -0
- package/dist/client/client/realtime/RealtimeClient.js +898 -0
- package/dist/client/client/realtime/localEvents.js +56 -0
- package/dist/client/client/realtime/notice.js +125 -0
- package/dist/client/client/realtime/observe.js +280 -0
- package/dist/client/client/roles/index.js +3 -0
- package/dist/client/client/roles/registry.js +97 -0
- package/dist/client/client/roles/roles.js +91 -0
- package/dist/client/client/shim/events.js +88 -0
- package/dist/client/client/shim/util.js +20 -0
- package/dist/client/client/svelte/index.js +319 -0
- package/dist/client/client/syslog/context.js +122 -0
- package/dist/client/client/syslog/errors.js +49 -0
- package/dist/client/client/syslog/uplink.js +106 -0
- package/dist/client/client/vue/index.js +321 -0
- package/dist/client/colors.js +63 -0
- package/dist/client/debugbar.standalone.js +4274 -0
- package/dist/client/realtime/IRealtimeTransport.js +32 -0
- package/dist/client/realtime/JsonRpcPeer.js +251 -0
- package/dist/client/realtime/channelRate.js +39 -0
- package/dist/client/realtime/platformChannels.js +155 -0
- package/dist/client/syslog/Pdu.js +224 -0
- package/dist/client/syslog/Syslog.js +1159 -0
- package/dist/client/syslog/drivers/pduFlow.js +106 -0
- package/dist/client/syslog/drivers/pduProtocol.js +28 -0
- package/dist/client/syslog/logColor.js +80 -0
- package/dist/client/types/src/Container.d.ts +221 -0
- package/dist/client/types/src/Event.d.ts +171 -0
- package/dist/client/types/src/FileClass.d.ts +184 -0
- package/dist/client/types/src/Service.d.ts +165 -0
- package/dist/client/types/src/Tools.d.ts +116 -0
- package/dist/client/types/src/client/ClientKernel.d.ts +90 -0
- package/dist/client/types/src/client/IClientKernel.d.ts +195 -0
- package/dist/client/types/src/client/angular/index.d.ts +273 -0
- package/dist/client/types/src/client/announce.d.ts +74 -0
- package/dist/client/types/src/client/debugbar/DebugBar.d.ts +279 -0
- package/dist/client/types/src/client/debugbar/format.d.ts +48 -0
- package/dist/client/types/src/client/debugbar/hmr.d.ts +16 -0
- package/dist/client/types/src/client/debugbar/index.d.ts +30 -0
- package/dist/client/types/src/client/debugbar/model.d.ts +158 -0
- package/dist/client/types/src/client/debugbar/network.d.ts +55 -0
- package/dist/client/types/src/client/debugbar/profile.d.ts +99 -0
- package/dist/client/types/src/client/index.d.ts +47 -0
- package/dist/client/types/src/client/react/index.d.ts +207 -0
- package/dist/client/types/src/client/realtime/AdaptiveRate.d.ts +138 -0
- package/dist/client/types/src/client/realtime/BrowserWsTransport.d.ts +27 -0
- package/dist/client/types/src/client/realtime/RealtimeClient.d.ts +573 -0
- package/dist/client/types/src/client/realtime/localEvents.d.ts +53 -0
- package/dist/client/types/src/client/realtime/notice.d.ts +70 -0
- package/dist/client/types/src/client/realtime/observe.d.ts +284 -0
- package/dist/client/types/src/client/roles/index.d.ts +15 -0
- package/dist/client/types/src/client/roles/registry.d.ts +72 -0
- package/dist/client/types/src/client/roles/roles.d.ts +68 -0
- package/dist/client/types/src/client/shim/cli-color.d.ts +2 -0
- package/dist/client/types/src/client/shim/events.d.ts +39 -0
- package/dist/client/types/src/client/shim/util.d.ts +14 -0
- package/dist/client/types/src/client/svelte/index.d.ts +216 -0
- package/dist/client/types/src/client/syslog/context.d.ts +76 -0
- package/dist/client/types/src/client/syslog/errors.d.ts +46 -0
- package/dist/client/types/src/client/syslog/uplink.d.ts +89 -0
- package/dist/client/types/src/client/transport/websocket.d.ts +5 -0
- package/dist/client/types/src/client/vue/index.d.ts +234 -0
- package/dist/client/types/src/colors.d.ts +32 -0
- package/dist/client/types/src/config/infra.d.ts +171 -0
- package/dist/client/types/src/kernel/bootReport.d.ts +112 -0
- package/dist/client/types/src/kernel/readinessRegistry.d.ts +104 -0
- package/dist/client/types/src/realtime/IRealtimeSocket.d.ts +142 -0
- package/dist/client/types/src/realtime/IRealtimeTransport.d.ts +50 -0
- package/dist/client/types/src/realtime/JsonRpcPeer.d.ts +251 -0
- package/dist/client/types/src/realtime/RealtimeEventMap.d.ts +269 -0
- package/dist/client/types/src/realtime/channelRate.d.ts +55 -0
- package/dist/client/types/src/realtime/platformChannels.d.ts +147 -0
- package/dist/client/types/src/syslog/Pdu.d.ts +134 -0
- package/dist/client/types/src/syslog/Syslog.d.ts +482 -0
- package/dist/client/types/src/syslog/drivers/ILogDriver.d.ts +177 -0
- package/dist/client/types/src/syslog/drivers/pduFlow.d.ts +53 -0
- package/dist/client/types/src/syslog/drivers/pduProtocol.d.ts +20 -0
- package/dist/client/types/src/syslog/logColor.d.ts +59 -0
- package/dist/client/types/src/types/ICliKernel.d.ts +28 -0
- package/dist/client/types/src/types/ICommand.d.ts +14 -0
- package/dist/client/types/src/types/IContainer.d.ts +35 -0
- package/dist/client/types/src/types/IKernel.d.ts +138 -0
- package/dist/client/types/src/types/IMcpTool.d.ts +157 -0
- package/dist/client/types/src/types/IModule.d.ts +73 -0
- package/dist/client/types/src/types/IPage.d.ts +119 -0
- package/dist/client/types/src/types/IService.d.ts +62 -0
- package/dist/client/types/src/types/ISyslog.d.ts +58 -0
- package/dist/client/types/src/types/ITransport.d.ts +5 -0
- package/dist/client/types/src/types/globals.d.ts +47 -0
- package/dist/node/Cli.js +750 -0
- package/dist/node/Container.js +367 -0
- package/dist/node/Error.js +309 -0
- package/dist/node/Event.js +198 -0
- package/dist/node/FileClass.js +308 -0
- package/dist/node/Nodefony.js +78 -0
- package/dist/node/Service.js +344 -0
- package/dist/node/Tools.js +205 -0
- package/dist/node/bin/resolveLocalCli.js +10 -0
- package/dist/node/bundler/index.js +103 -0
- package/dist/node/cli/agentTargets.js +461 -0
- package/dist/node/cli/aiMcp.js +553 -0
- package/dist/node/cli/aiMcpReport.js +129 -0
- package/dist/node/cli/aiSync.js +323 -0
- package/dist/node/cli/aiSyncReport.js +142 -0
- package/dist/node/cli/card.js +186 -0
- package/dist/node/cli/cardReport.js +110 -0
- package/dist/node/cli/completion.js +415 -0
- package/dist/node/cli/create.js +805 -0
- package/dist/node/cli/env.js +332 -0
- package/dist/node/cli/envReport.js +159 -0
- package/dist/node/cli/execPortable.js +60 -0
- package/dist/node/cli/gitHooks.js +191 -0
- package/dist/node/cli/gitHooksReport.js +119 -0
- package/dist/node/cli/globalFlags.js +67 -0
- package/dist/node/cli/helpReport.js +288 -0
- package/dist/node/cli/manPage.js +165 -0
- package/dist/node/cli/nodefonyBin.js +61 -0
- package/dist/node/cli/outdated.js +144 -0
- package/dist/node/cli/progress.js +555 -0
- package/dist/node/cli/projectRoot.js +32 -0
- package/dist/node/cli/promptPassword.js +44 -0
- package/dist/node/cli/prompts.js +86 -0
- package/dist/node/cli/scaffold/destination.js +92 -0
- package/dist/node/cli/scaffold/engine.js +2156 -0
- package/dist/node/cli/scaffold/entityFields.js +589 -0
- package/dist/node/cli/scaffold/format.js +243 -0
- package/dist/node/cli/scaffold/interactive.js +124 -0
- package/dist/node/cli/scaffold/moduleLayout.js +38 -0
- package/dist/node/cli/scaffold/reservedEntities.js +74 -0
- package/dist/node/cli/scaffold/spec.js +704 -0
- package/dist/node/cli/scaffold/steps.js +40 -0
- package/dist/node/cli/scaffold/userContractSource.js +133 -0
- package/dist/node/cli/scaffold/versions.js +57 -0
- package/dist/node/cli/scaffold/writer.js +172 -0
- package/dist/node/cli/startMenu.js +496 -0
- package/dist/node/cli/symbols.js +205 -0
- package/dist/node/cli/sysexits.js +34 -0
- package/dist/node/cli/tableReport.js +184 -0
- package/dist/node/cli/usageReport.js +144 -0
- package/dist/node/colors.js +63 -0
- package/dist/node/command/Builder.js +153 -0
- package/dist/node/command/Command.js +348 -0
- package/dist/node/config/configProvenance.js +101 -0
- package/dist/node/config/defaults.js +39 -0
- package/dist/node/config/defineConfig.js +129 -0
- package/dist/node/config/defineEnv.js +264 -0
- package/dist/node/config/envExample.js +41 -0
- package/dist/node/config/envOverride.js +423 -0
- package/dist/node/config/infra.js +163 -0
- package/dist/node/config/reactivity.js +23 -0
- package/dist/node/config/reservedEnv.js +118 -0
- package/dist/node/config/schema.js +134 -0
- package/dist/node/config/use.js +24 -0
- package/dist/node/finder/File.js +49 -0
- package/dist/node/finder/FileResult.js +86 -0
- package/dist/node/finder/Finder.js +201 -0
- package/dist/node/finder/Result.js +45 -0
- package/dist/node/index.js +121 -0
- package/dist/node/kernel/BootConfigurationError.js +54 -0
- package/dist/node/kernel/CliKernel.js +630 -0
- package/dist/node/kernel/Kernel.js +2198 -0
- package/dist/node/kernel/Module.js +453 -0
- package/dist/node/kernel/adminPlane/adminCaller.js +98 -0
- package/dist/node/kernel/adminPlane/adminRbac.js +51 -0
- package/dist/node/kernel/adminPlane/catalog.js +158 -0
- package/dist/node/kernel/adminPlane/executeAdmin.js +78 -0
- package/dist/node/kernel/checks/deep.js +335 -0
- package/dist/node/kernel/checks/freshness.js +139 -0
- package/dist/node/kernel/checks/gating.js +231 -0
- package/dist/node/kernel/checks/guards.js +232 -0
- package/dist/node/kernel/checks/lastBoot.js +136 -0
- package/dist/node/kernel/checks/live.js +330 -0
- package/dist/node/kernel/checks/packageDeps.js +292 -0
- package/dist/node/kernel/checks/readiness.js +153 -0
- package/dist/node/kernel/checks/renderReport.js +780 -0
- package/dist/node/kernel/checks/report.js +421 -0
- package/dist/node/kernel/checks/runDoctor.js +731 -0
- package/dist/node/kernel/checks/surface.js +364 -0
- package/dist/node/kernel/checks/walk.js +100 -0
- package/dist/node/kernel/checks/wiring.js +450 -0
- package/dist/node/kernel/commands/AiMcpCommand.js +65 -0
- package/dist/node/kernel/commands/AiSyncCommand.js +48 -0
- package/dist/node/kernel/commands/BuildCommand.js +49 -0
- package/dist/node/kernel/commands/CardCommand.js +50 -0
- package/dist/node/kernel/commands/ClusterCommand.js +58 -0
- package/dist/node/kernel/commands/CompletionCommand.js +31 -0
- package/dist/node/kernel/commands/CreateCommand.js +45 -0
- package/dist/node/kernel/commands/DevCommand.js +99 -0
- package/dist/node/kernel/commands/DoctorCommand.js +115 -0
- package/dist/node/kernel/commands/EnvCommand.js +52 -0
- package/dist/node/kernel/commands/GitHooksCommand.js +47 -0
- package/dist/node/kernel/commands/InspectCommand.js +153 -0
- package/dist/node/kernel/commands/InstallCommand.js +20 -0
- package/dist/node/kernel/commands/MenuCommand.js +261 -0
- package/dist/node/kernel/commands/OutdatedCommand.js +164 -0
- package/dist/node/kernel/commands/ProdCommand.js +60 -0
- package/dist/node/kernel/commands/StatusCommand.js +30 -0
- package/dist/node/kernel/commands/StopCommand.js +35 -0
- package/dist/node/kernel/commands/SymbolsCommand.js +45 -0
- package/dist/node/kernel/commands/runtimeLauncher.js +95 -0
- package/dist/node/kernel/decorators/kernelDecorator.js +110 -0
- package/dist/node/kernel/injector/injector.js +137 -0
- package/dist/node/kernel/injector/serviceOrder.js +106 -0
- package/dist/node/kernel/inspect/adminSubjects.js +194 -0
- package/dist/node/kernel/inspect/docOutline.js +86 -0
- package/dist/node/kernel/lifecycleTags.js +76 -0
- package/dist/node/kernel/moduleConfig.js +75 -0
- package/dist/node/kernel/moduleGating.js +68 -0
- package/dist/node/kernel/readinessRegistry.js +98 -0
- package/dist/node/kernel/resolveModuleEntry.js +61 -0
- package/dist/node/mcp/caller.js +40 -0
- package/dist/node/mcp/guard.js +51 -0
- package/dist/node/mcp/protocol.js +157 -0
- package/dist/node/mcp/server.js +216 -0
- package/dist/node/mcp/tools.js +861 -0
- package/dist/node/oauth/authorizationServer.js +221 -0
- package/dist/node/oauth/protectedResource.js +293 -0
- package/dist/node/package.js +4 -0
- package/dist/node/realtime/IRealtimeTransport.js +32 -0
- package/dist/node/realtime/JsonRpcPeer.js +251 -0
- package/dist/node/realtime/RealtimeEventMap.js +15 -0
- package/dist/node/realtime/channelRate.js +39 -0
- package/dist/node/realtime/platformChannels.js +155 -0
- package/dist/node/runtime/GcScheduler.js +92 -0
- package/dist/node/runtime/RequestContext.js +76 -0
- package/dist/node/runtime/bearer.js +84 -0
- package/dist/node/runtime/engineEnvironment.js +79 -0
- package/dist/node/runtime/loadEnv.js +105 -0
- package/dist/node/runtime/pageFacets.js +85 -0
- package/dist/node/runtime/pageFilters.js +103 -0
- package/dist/node/runtime/pageGuard.js +84 -0
- package/dist/node/runtime/pageQuery.js +160 -0
- package/dist/node/runtime/pageSort.js +114 -0
- package/dist/node/runtime/redact.js +43 -0
- package/dist/node/runtime/withTimeout.js +71 -0
- package/dist/node/service/cluster/ClusterManager.js +202 -0
- package/dist/node/service/cluster/ClusterProbeAggregator.js +120 -0
- package/dist/node/service/cluster/ClusterRelay.js +74 -0
- package/dist/node/service/cluster/clusterMaster.js +65 -0
- package/dist/node/service/cluster/clusterMessage.js +70 -0
- package/dist/node/service/cluster/cpuQuota.js +59 -0
- package/dist/node/service/cluster/instanceProbe.js +52 -0
- package/dist/node/service/cluster/podEnvironment.js +48 -0
- package/dist/node/service/cluster/processProbe.js +70 -0
- package/dist/node/service/cluster/richProcessProbe.js +115 -0
- package/dist/node/service/cluster/topology.js +71 -0
- package/dist/node/service/dev/BootReporter.js +490 -0
- package/dist/node/service/dev/DevSupervisor.js +1004 -0
- package/dist/node/service/dev/bootVerdict.js +158 -0
- package/dist/node/service/dev/detachedStart.js +372 -0
- package/dist/node/service/dev/devProcess.js +1137 -0
- package/dist/node/service/dev/devProjects.js +138 -0
- package/dist/node/service/dev/devStatusReport.js +395 -0
- package/dist/node/service/dev/devStop.js +263 -0
- package/dist/node/service/fetchService.js +35 -0
- package/dist/node/service/gitService.js +62 -0
- package/dist/node/syslog/Pdu.js +224 -0
- package/dist/node/syslog/Syslog.js +1159 -0
- package/dist/node/syslog/drivers/ClusterFileLogDriver.js +114 -0
- package/dist/node/syslog/drivers/FileLogDriver.js +127 -0
- package/dist/node/syslog/drivers/ILogDriver.js +26 -0
- package/dist/node/syslog/drivers/LokiLogDriver.js +144 -0
- package/dist/node/syslog/drivers/MemoryLogDriver.js +33 -0
- package/dist/node/syslog/drivers/OpenSearchLogDriver.js +159 -0
- package/dist/node/syslog/drivers/builtinLogDrivers.js +114 -0
- package/dist/node/syslog/drivers/filterPdus.js +71 -0
- package/dist/node/syslog/drivers/logDriverRegistry.js +83 -0
- package/dist/node/syslog/drivers/opensearchShared.js +17 -0
- package/dist/node/syslog/drivers/pduFlow.js +106 -0
- package/dist/node/syslog/drivers/pduProtocol.js +28 -0
- package/dist/node/syslog/httpFetch.js +43 -0
- package/dist/node/syslog/logColor.js +80 -0
- package/dist/node/syslog/sinks/FileSink.js +142 -0
- package/dist/node/syslog/transports/BatchingHttpTransport.js +98 -0
- package/dist/node/syslog/transports/ConsoleTransport.js +14 -0
- package/dist/node/syslog/transports/FileTransport.js +17 -0
- package/dist/node/syslog/transports/HttpTransport.js +44 -0
- package/dist/node/syslog/transports/LokiTransport.js +80 -0
- package/dist/node/syslog/transports/OpenSearchTransport.js +53 -0
- package/dist/node/syslog/transports/SyslogTransport.js +13 -0
- package/dist/node/syslog/transports/index.js +8 -0
- package/dist/node/testing/index.js +190 -0
- package/dist/types/Cli.d.ts +177 -0
- package/dist/types/Container.d.ts +221 -0
- package/dist/types/Error.d.ts +162 -0
- package/dist/types/Event.d.ts +171 -0
- package/dist/types/FileClass.d.ts +184 -0
- package/dist/types/Nodefony.d.ts +66 -0
- package/dist/types/Service.d.ts +165 -0
- package/dist/types/Tools.d.ts +116 -0
- package/dist/types/bin/resolveLocalCli.d.ts +49 -0
- package/dist/types/bundler/index.d.ts +66 -0
- package/dist/types/cli/agentTargets.d.ts +378 -0
- package/dist/types/cli/aiMcp.d.ts +239 -0
- package/dist/types/cli/aiMcpReport.d.ts +113 -0
- package/dist/types/cli/aiSync.d.ts +68 -0
- package/dist/types/cli/aiSyncReport.d.ts +149 -0
- package/dist/types/cli/card.d.ts +49 -0
- package/dist/types/cli/cardReport.d.ts +127 -0
- package/dist/types/cli/completion.d.ts +154 -0
- package/dist/types/cli/create.d.ts +177 -0
- package/dist/types/cli/env.d.ts +103 -0
- package/dist/types/cli/envReport.d.ts +153 -0
- package/dist/types/cli/execPortable.d.ts +54 -0
- package/dist/types/cli/gitHooks.d.ts +48 -0
- package/dist/types/cli/gitHooksReport.d.ts +101 -0
- package/dist/types/cli/globalFlags.d.ts +48 -0
- package/dist/types/cli/helpReport.d.ts +110 -0
- package/dist/types/cli/manPage.d.ts +21 -0
- package/dist/types/cli/nodefonyBin.d.ts +45 -0
- package/dist/types/cli/outdated.d.ts +119 -0
- package/dist/types/cli/progress.d.ts +349 -0
- package/dist/types/cli/projectRoot.d.ts +19 -0
- package/dist/types/cli/promptPassword.d.ts +22 -0
- package/dist/types/cli/prompts.d.ts +102 -0
- package/dist/types/cli/scaffold/destination.d.ts +70 -0
- package/dist/types/cli/scaffold/engine.d.ts +545 -0
- package/dist/types/cli/scaffold/entityFields.d.ts +238 -0
- package/dist/types/cli/scaffold/format.d.ts +78 -0
- package/dist/types/cli/scaffold/interactive.d.ts +11 -0
- package/dist/types/cli/scaffold/moduleLayout.d.ts +69 -0
- package/dist/types/cli/scaffold/reservedEntities.d.ts +49 -0
- package/dist/types/cli/scaffold/spec.d.ts +159 -0
- package/dist/types/cli/scaffold/steps.d.ts +24 -0
- package/dist/types/cli/scaffold/userContractSource.d.ts +86 -0
- package/dist/types/cli/scaffold/versions.d.ts +14 -0
- package/dist/types/cli/scaffold/writer.d.ts +100 -0
- package/dist/types/cli/startMenu.d.ts +195 -0
- package/dist/types/cli/symbols.d.ts +84 -0
- package/dist/types/cli/sysexits.d.ts +31 -0
- package/dist/types/cli/tableReport.d.ts +44 -0
- package/dist/types/cli/usageReport.d.ts +108 -0
- package/dist/types/client/ClientKernel.d.ts +90 -0
- package/dist/types/client/IClientKernel.d.ts +195 -0
- package/dist/types/client/angular/index.d.ts +273 -0
- package/dist/types/client/announce.d.ts +74 -0
- package/dist/types/client/debugbar/DebugBar.d.ts +279 -0
- package/dist/types/client/debugbar/format.d.ts +48 -0
- package/dist/types/client/debugbar/hmr.d.ts +16 -0
- package/dist/types/client/debugbar/index.d.ts +30 -0
- package/dist/types/client/debugbar/model.d.ts +158 -0
- package/dist/types/client/debugbar/network.d.ts +55 -0
- package/dist/types/client/debugbar/profile.d.ts +99 -0
- package/dist/types/client/index.d.ts +47 -0
- package/dist/types/client/react/index.d.ts +207 -0
- package/dist/types/client/realtime/AdaptiveRate.d.ts +138 -0
- package/dist/types/client/realtime/BrowserWsTransport.d.ts +27 -0
- package/dist/types/client/realtime/RealtimeClient.d.ts +573 -0
- package/dist/types/client/realtime/localEvents.d.ts +53 -0
- package/dist/types/client/realtime/notice.d.ts +70 -0
- package/dist/types/client/realtime/observe.d.ts +284 -0
- package/dist/types/client/roles/index.d.ts +15 -0
- package/dist/types/client/roles/registry.d.ts +72 -0
- package/dist/types/client/roles/roles.d.ts +68 -0
- package/dist/types/client/shim/cli-color.d.ts +2 -0
- package/dist/types/client/shim/events.d.ts +39 -0
- package/dist/types/client/shim/util.d.ts +14 -0
- package/dist/types/client/svelte/index.d.ts +216 -0
- package/dist/types/client/syslog/context.d.ts +76 -0
- package/dist/types/client/syslog/errors.d.ts +46 -0
- package/dist/types/client/syslog/uplink.d.ts +89 -0
- package/dist/types/client/transport/websocket.d.ts +5 -0
- package/dist/types/client/vue/index.d.ts +234 -0
- package/dist/types/colors.d.ts +32 -0
- package/dist/types/command/Builder.d.ts +47 -0
- package/dist/types/command/Command.d.ts +268 -0
- package/dist/types/config/configMeta.d.ts +53 -0
- package/dist/types/config/configProvenance.d.ts +58 -0
- package/dist/types/config/defaults.d.ts +33 -0
- package/dist/types/config/defineConfig.d.ts +70 -0
- package/dist/types/config/defineEnv.d.ts +170 -0
- package/dist/types/config/envExample.d.ts +23 -0
- package/dist/types/config/envOverride.d.ts +236 -0
- package/dist/types/config/index.d.ts +23 -0
- package/dist/types/config/infra.d.ts +171 -0
- package/dist/types/config/reactivity.d.ts +36 -0
- package/dist/types/config/reservedEnv.d.ts +145 -0
- package/dist/types/config/schema.d.ts +113 -0
- package/dist/types/config/types.d.ts +374 -0
- package/dist/types/config/use.d.ts +80 -0
- package/dist/types/finder/File.d.ts +26 -0
- package/dist/types/finder/FileResult.d.ts +14 -0
- package/dist/types/finder/Finder.d.ts +51 -0
- package/dist/types/finder/Result.d.ts +10 -0
- package/dist/types/index.d.ts +205 -0
- package/dist/types/kernel/BootConfigurationError.d.ts +50 -0
- package/dist/types/kernel/CliKernel.d.ts +289 -0
- package/dist/types/kernel/Kernel.d.ts +1255 -0
- package/dist/types/kernel/Module.d.ts +326 -0
- package/dist/types/kernel/adminPlane/adminCaller.d.ts +105 -0
- package/dist/types/kernel/adminPlane/adminRbac.d.ts +44 -0
- package/dist/types/kernel/adminPlane/catalog.d.ts +122 -0
- package/dist/types/kernel/adminPlane/executeAdmin.d.ts +99 -0
- package/dist/types/kernel/bootReport.d.ts +112 -0
- package/dist/types/kernel/checks/deep.d.ts +122 -0
- package/dist/types/kernel/checks/freshness.d.ts +49 -0
- package/dist/types/kernel/checks/gating.d.ts +134 -0
- package/dist/types/kernel/checks/guards.d.ts +57 -0
- package/dist/types/kernel/checks/lastBoot.d.ts +189 -0
- package/dist/types/kernel/checks/live.d.ts +110 -0
- package/dist/types/kernel/checks/packageDeps.d.ts +44 -0
- package/dist/types/kernel/checks/readiness.d.ts +97 -0
- package/dist/types/kernel/checks/renderReport.d.ts +107 -0
- package/dist/types/kernel/checks/report.d.ts +414 -0
- package/dist/types/kernel/checks/runDoctor.d.ts +361 -0
- package/dist/types/kernel/checks/surface.d.ts +148 -0
- package/dist/types/kernel/checks/walk.d.ts +45 -0
- package/dist/types/kernel/checks/wiring.d.ts +54 -0
- package/dist/types/kernel/commands/AiMcpCommand.d.ts +39 -0
- package/dist/types/kernel/commands/AiSyncCommand.d.ts +28 -0
- package/dist/types/kernel/commands/BuildCommand.d.ts +13 -0
- package/dist/types/kernel/commands/CardCommand.d.ts +34 -0
- package/dist/types/kernel/commands/ClusterCommand.d.ts +29 -0
- package/dist/types/kernel/commands/CompletionCommand.d.ts +17 -0
- package/dist/types/kernel/commands/CreateCommand.d.ts +16 -0
- package/dist/types/kernel/commands/DevCommand.d.ts +40 -0
- package/dist/types/kernel/commands/DoctorCommand.d.ts +55 -0
- package/dist/types/kernel/commands/EnvCommand.d.ts +35 -0
- package/dist/types/kernel/commands/GitHooksCommand.d.ts +27 -0
- package/dist/types/kernel/commands/InspectCommand.d.ts +59 -0
- package/dist/types/kernel/commands/InstallCommand.d.ts +7 -0
- package/dist/types/kernel/commands/MenuCommand.d.ts +58 -0
- package/dist/types/kernel/commands/OutdatedCommand.d.ts +47 -0
- package/dist/types/kernel/commands/ProdCommand.d.ts +29 -0
- package/dist/types/kernel/commands/StatusCommand.d.ts +17 -0
- package/dist/types/kernel/commands/StopCommand.d.ts +17 -0
- package/dist/types/kernel/commands/SymbolsCommand.d.ts +29 -0
- package/dist/types/kernel/commands/runtimeLauncher.d.ts +76 -0
- package/dist/types/kernel/decorators/kernelDecorator.d.ts +76 -0
- package/dist/types/kernel/injector/injector.d.ts +47 -0
- package/dist/types/kernel/injector/serviceOrder.d.ts +29 -0
- package/dist/types/kernel/inspect/adminSubjects.d.ts +169 -0
- package/dist/types/kernel/inspect/docOutline.d.ts +53 -0
- package/dist/types/kernel/lifecycleTags.d.ts +79 -0
- package/dist/types/kernel/moduleConfig.d.ts +35 -0
- package/dist/types/kernel/moduleGating.d.ts +78 -0
- package/dist/types/kernel/readinessRegistry.d.ts +104 -0
- package/dist/types/kernel/resolveModuleEntry.d.ts +46 -0
- package/dist/types/mcp/caller.d.ts +55 -0
- package/dist/types/mcp/guard.d.ts +66 -0
- package/dist/types/mcp/protocol.d.ts +163 -0
- package/dist/types/mcp/server.d.ts +70 -0
- package/dist/types/mcp/tools.d.ts +209 -0
- package/dist/types/oauth/authorizationServer.d.ts +204 -0
- package/dist/types/oauth/protectedResource.d.ts +339 -0
- package/dist/types/realtime/IRealtimeSocket.d.ts +142 -0
- package/dist/types/realtime/IRealtimeTransport.d.ts +50 -0
- package/dist/types/realtime/JsonRpcPeer.d.ts +251 -0
- package/dist/types/realtime/RealtimeEventMap.d.ts +269 -0
- package/dist/types/realtime/channelRate.d.ts +55 -0
- package/dist/types/realtime/platformChannels.d.ts +147 -0
- package/dist/types/runtime/GcScheduler.d.ts +96 -0
- package/dist/types/runtime/RequestContext.d.ts +152 -0
- package/dist/types/runtime/bearer.d.ts +88 -0
- package/dist/types/runtime/engineEnvironment.d.ts +115 -0
- package/dist/types/runtime/loadEnv.d.ts +86 -0
- package/dist/types/runtime/pageFacets.d.ts +104 -0
- package/dist/types/runtime/pageFilters.d.ts +139 -0
- package/dist/types/runtime/pageGuard.d.ts +78 -0
- package/dist/types/runtime/pageQuery.d.ts +138 -0
- package/dist/types/runtime/pageSort.d.ts +86 -0
- package/dist/types/runtime/redact.d.ts +27 -0
- package/dist/types/runtime/withTimeout.d.ts +51 -0
- package/dist/types/service/cluster/ClusterManager.d.ts +112 -0
- package/dist/types/service/cluster/ClusterProbeAggregator.d.ts +68 -0
- package/dist/types/service/cluster/ClusterRelay.d.ts +57 -0
- package/dist/types/service/cluster/clusterMaster.d.ts +40 -0
- package/dist/types/service/cluster/clusterMessage.d.ts +94 -0
- package/dist/types/service/cluster/cpuQuota.d.ts +37 -0
- package/dist/types/service/cluster/instanceProbe.d.ts +82 -0
- package/dist/types/service/cluster/podEnvironment.d.ts +35 -0
- package/dist/types/service/cluster/processProbe.d.ts +53 -0
- package/dist/types/service/cluster/richProcessProbe.d.ts +77 -0
- package/dist/types/service/cluster/topology.d.ts +64 -0
- package/dist/types/service/dev/BootReporter.d.ts +67 -0
- package/dist/types/service/dev/DevSupervisor.d.ts +117 -0
- package/dist/types/service/dev/bootVerdict.d.ts +99 -0
- package/dist/types/service/dev/detachedStart.d.ts +164 -0
- package/dist/types/service/dev/devProcess.d.ts +638 -0
- package/dist/types/service/dev/devProjects.d.ts +121 -0
- package/dist/types/service/dev/devStatusReport.d.ts +141 -0
- package/dist/types/service/dev/devStop.d.ts +57 -0
- package/dist/types/service/fetchService.d.ts +29 -0
- package/dist/types/service/gitService.d.ts +24 -0
- package/dist/types/syslog/Pdu.d.ts +134 -0
- package/dist/types/syslog/Syslog.d.ts +482 -0
- package/dist/types/syslog/drivers/ClusterFileLogDriver.d.ts +40 -0
- package/dist/types/syslog/drivers/FileLogDriver.d.ts +55 -0
- package/dist/types/syslog/drivers/ILogDriver.d.ts +177 -0
- package/dist/types/syslog/drivers/LokiLogDriver.d.ts +44 -0
- package/dist/types/syslog/drivers/MemoryLogDriver.d.ts +20 -0
- package/dist/types/syslog/drivers/OpenSearchLogDriver.d.ts +40 -0
- package/dist/types/syslog/drivers/builtinLogDrivers.d.ts +43 -0
- package/dist/types/syslog/drivers/filterPdus.d.ts +21 -0
- package/dist/types/syslog/drivers/logDriverRegistry.d.ts +100 -0
- package/dist/types/syslog/drivers/opensearchShared.d.ts +11 -0
- package/dist/types/syslog/drivers/pduFlow.d.ts +53 -0
- package/dist/types/syslog/drivers/pduProtocol.d.ts +20 -0
- package/dist/types/syslog/httpFetch.d.ts +48 -0
- package/dist/types/syslog/logColor.d.ts +59 -0
- package/dist/types/syslog/sinks/FileSink.d.ts +78 -0
- package/dist/types/syslog/transports/BatchingHttpTransport.d.ts +59 -0
- package/dist/types/syslog/transports/ConsoleTransport.d.ts +8 -0
- package/dist/types/syslog/transports/FileTransport.d.ts +13 -0
- package/dist/types/syslog/transports/HttpTransport.d.ts +15 -0
- package/dist/types/syslog/transports/LokiTransport.d.ts +45 -0
- package/dist/types/syslog/transports/OpenSearchTransport.d.ts +40 -0
- package/dist/types/syslog/transports/SyslogTransport.d.ts +9 -0
- package/dist/types/syslog/transports/index.d.ts +12 -0
- package/dist/types/testing/index.d.ts +197 -0
- package/dist/types/types/IAdminApi.d.ts +246 -0
- package/dist/types/types/ICliKernel.d.ts +28 -0
- package/dist/types/types/ICommand.d.ts +14 -0
- package/dist/types/types/IContainer.d.ts +35 -0
- package/dist/types/types/IIdempotencyStore.d.ts +159 -0
- package/dist/types/types/IKernel.d.ts +138 -0
- package/dist/types/types/IMcpTool.d.ts +157 -0
- package/dist/types/types/IModule.d.ts +73 -0
- package/dist/types/types/IModuleManifest.d.ts +49 -0
- package/dist/types/types/IPage.d.ts +119 -0
- package/dist/types/types/IService.d.ts +62 -0
- package/dist/types/types/ISyslog.d.ts +58 -0
- package/dist/types/types/ITransport.d.ts +5 -0
- package/dist/types/types/globals.d.ts +47 -0
- package/docs/angular-services.md +318 -0
- package/docs/catalogue.md +184 -0
- package/docs/cli.md +369 -0
- package/docs/client.md +616 -0
- package/docs/debugbar.md +218 -0
- package/docs/environnement.md +308 -0
- package/docs/index.md +388 -0
- package/docs/kernel.md +670 -0
- package/docs/progression.md +228 -0
- package/docs/react-hooks.md +686 -0
- package/docs/request-context.md +507 -0
- package/docs/service.md +590 -0
- package/docs/svelte-reactivite.md +293 -0
- package/docs/syslog.md +848 -0
- package/docs/testing.md +203 -0
- package/docs/vue-composables.md +255 -0
- package/man/nodefony.1 +152 -0
- package/package.json +163 -83
- package/templates/app/agents/AGENTS.md.tpl +967 -0
- package/templates/app/agents/POINTEUR.md.tpl +12 -0
- package/templates/app/base/Dockerfile.tpl +104 -0
- package/templates/app/base/README.md.tpl +314 -0
- package/templates/app/base/dockerignore.tpl +28 -0
- package/templates/app/base/env.ts.tpl +130 -0
- package/templates/app/base/gitattributes.tpl +28 -0
- package/templates/app/base/github/workflows/ci.yml.tpl +91 -0
- package/templates/app/base/gitignore.tpl +46 -0
- package/templates/app/base/gitlab-ci.yml.tpl +42 -0
- package/templates/app/base/index.ts.tpl +69 -0
- package/templates/app/base/nodefony.config.ts.tpl +342 -0
- package/templates/app/base/oxlintrc.json.tpl +127 -0
- package/templates/app/base/package.json.tpl +75 -0
- package/templates/app/base/prettierignore.tpl +24 -0
- package/templates/app/base/prettierrc.json.tpl +12 -0
- package/templates/app/base/rolldown.config.ts.tpl +8 -0
- package/templates/app/base/tests/config.test.ts.tpl +29 -0
- package/templates/app/base/tests/e2e.setup.ts.tpl +181 -0
- package/templates/app/base/tests/e2e.test.ts.tpl +105 -0
- package/templates/app/base/tests/migrations.e2e.test.ts.tpl +579 -0
- package/templates/app/base/tsconfig.json.tpl +47 -0
- package/templates/app/base/vitest.config.ts.tpl +88 -0
- package/templates/app/base/vitest.e2e.config.ts.tpl +39 -0
- package/templates/app/complete/compose.yaml.tpl +328 -0
- package/templates/app/complete/deploy/migrate-job.yaml.tpl +100 -0
- package/templates/app/complete/docker/db/init-nodefony-e2e.sql.tpl +30 -0
- package/templates/app/complete/docker/grafana/provisioning/datasources/loki.yaml.tpl +15 -0
- package/templates/app/complete/env.local.tpl +11 -0
- package/templates/app/complete/env.tpl +53 -0
- package/templates/app/complete/nodefony/entity/User.ts.tpl +69 -0
- package/templates/app/complete/nodefony/security/provisionUsers.ts.tpl +124 -0
- package/templates/app/complete/nodefony/service/AppBannerService.ts.tpl +98 -0
- package/templates/app/complete/nodefony/service/AppInfoService.ts.tpl +85 -0
- package/templates/app/frontend/angular/frontend/src/accent.css.tpl +34 -0
- package/templates/app/frontend/angular/frontend/src/app/app.component.ts.tpl +400 -0
- package/templates/app/frontend/react/frontend/src/App.tsx.tpl +421 -0
- package/templates/app/frontend/react/frontend/src/accent.css.tpl +30 -0
- package/templates/app/frontend/shared/frontend/src/brand.ts.tpl +3 -0
- package/templates/app/frontend/shared/frontend/src/showcase.css.tpl +224 -0
- package/templates/app/frontend/shared/nodefony/controllers/AppController.ts.tpl +45 -0
- package/templates/app/frontend/svelte/frontend/src/App.svelte.tpl +371 -0
- package/templates/app/frontend/svelte/frontend/src/accent.css.tpl +34 -0
- package/templates/app/frontend/vue/frontend/src/App.vue.tpl +367 -0
- package/templates/app/frontend/vue/frontend/src/accent.css.tpl +34 -0
- package/templates/app/home/nodefony/controllers/HomeController.ts.tpl +38 -0
- package/templates/command/nodefony/command/__NAME__.ts.tpl +132 -0
- package/templates/controller/duplex/nodefony/controllers/__NAME__.ts.tpl +188 -0
- package/templates/controller/example/nodefony/controllers/__NAME__.ts.tpl +348 -0
- package/templates/controller/hello/nodefony/controllers/__NAME__.ts.tpl +143 -0
- package/templates/controller/realtime/nodefony/controllers/__NAME__.ts.tpl +153 -0
- package/templates/controller/realtime/tests/__KEBAB__-realtime.test.ts.tpl +79 -0
- package/templates/controller/rest/nodefony/controllers/__NAME__.ts.tpl +159 -0
- package/templates/entity/base/nodefony/entity/__PASCAL__.schema.ts.tpl +31 -0
- package/templates/entity/base/nodefony/entity/__PASCAL__.ts.tpl +68 -0
- package/templates/entity/controller/nodefony/controllers/__PASCAL__Controller.ts.tpl +335 -0
- package/templates/entity/service/nodefony/service/__PASCAL__Service.ts.tpl +126 -0
- package/templates/entity/tests/tests/__KEBAB__.e2e.test.ts.tpl +358 -0
- package/templates/entity/tests/tests/__KEBAB__.test.ts.tpl +87 -0
- package/templates/front/angular/frontend/src/app/app.component.ts.tpl +30 -0
- package/templates/front/base/nodefony/controllers/__NAME__.ts.tpl +60 -0
- package/templates/front/react/frontend/src/App.tsx.tpl +26 -0
- package/templates/front/svelte/frontend/src/App.svelte.tpl +19 -0
- package/templates/front/vue/frontend/src/App.vue.tpl +23 -0
- package/templates/module/ai/AGENTS.md.tpl +71 -0
- package/templates/module/base/README.md.tpl +62 -0
- package/templates/module/base/docs/index.md.tpl +55 -0
- package/templates/module/base/index.ts.tpl +50 -0
- package/templates/module/base/nodefony/config/config.ts.tpl +60 -0
- package/templates/module/base/nodefony/config/defineModuleConfig.ts.tpl +55 -0
- package/templates/module/base/nodefony/src/errors/__PASCAL__Error.ts.tpl +20 -0
- package/templates/module/base/package.json.tpl +53 -0
- package/templates/module/base/rolldown.config.ts.tpl +14 -0
- package/templates/module/base/tests/__KEBAB__.test.ts.tpl +39 -0
- package/templates/module/base/tsconfig.json.tpl +42 -0
- package/templates/module/base/vitest.config.ts.tpl +27 -0
- package/templates/module/packages/CLAUDE.md.tpl +51 -0
- package/templates/module/packages/MEMORY.md.tpl +39 -0
- package/templates/module/packages/tsconfig.declarations.json.tpl +19 -0
- package/templates/module/packages/tsconfig.tests.json.tpl +11 -0
- package/templates/module/service/nodefony/interfaces/I__PASCAL__Service.ts.tpl +14 -0
- package/templates/module/service/nodefony/interfaces/index.ts.tpl +1 -0
- package/templates/module/service/nodefony/service/__PASCAL__Service.ts.tpl +116 -0
- package/templates/service/nodefony/interfaces/I__PASCAL__Service.ts.tpl +19 -0
- package/templates/service/nodefony/service/__PASCAL__Service.ts.tpl +136 -0
- package/templates/service/tests/__PASCAL__Service.test.ts.tpl +53 -0
- package/templates/shared/front-entry/angular/frontend/src/main.ts.tpl +17 -0
- package/templates/shared/front-entry/react/frontend/src/main.tsx.tpl +11 -0
- package/templates/shared/front-entry/svelte/frontend/src/main.ts.tpl +13 -0
- package/templates/shared/front-entry/vue/frontend/src/main.ts.tpl +12 -0
- package/templates/shared/front-registrar/nodefony/frontend/register__PASCAL__Entry.ts.tpl +40 -0
- package/templates/shared/front-shell/frontend/index.html.tpl +39 -0
- package/templates/shared/ng-app-tsconfig/frontend/tsconfig.app.json.tpl +18 -0
- package/templates/shared/svelte-shim/frontend/src/env.d.ts.tpl +12 -0
- package/templates/shared/vue-shim/frontend/src/env.d.ts.tpl +15 -0
- package/.yarnclean +0 -45
- package/CHANGELOG.md +0 -406
- package/autoloader.es6 +0 -253
- package/builder.es6 +0 -366
- package/cli/builder/bundles/bundle.js +0 -328
- package/cli/builder/bundles/controller.js +0 -73
- package/cli/builder/bundles/users-bundle/.yarnclean +0 -45
- package/cli/builder/bundles/users-bundle/Command/fixtureTask.js +0 -47
- package/cli/builder/bundles/users-bundle/Command/usersCommand.js +0 -105
- package/cli/builder/bundles/users-bundle/Entity/mongoose/userEntity.js +0 -168
- package/cli/builder/bundles/users-bundle/Entity/sequelize/userEntity.js +0 -231
- package/cli/builder/bundles/users-bundle/Fixtures/users.js +0 -117
- package/cli/builder/bundles/users-bundle/Fixtures/usersFixtures.js +0 -114
- package/cli/builder/bundles/users-bundle/Resources/config/config.js +0 -90
- package/cli/builder/bundles/users-bundle/Resources/config/routing.js +0 -17
- package/cli/builder/bundles/users-bundle/Resources/config/security.js +0 -96
- package/cli/builder/bundles/users-bundle/Resources/config/services.js +0 -7
- package/cli/builder/bundles/users-bundle/Resources/config/webpack/webpack.dev.config.js +0 -13
- package/cli/builder/bundles/users-bundle/Resources/config/webpack/webpack.prod.config.js +0 -31
- package/cli/builder/bundles/users-bundle/Resources/config/webpack.config.js +0 -146
- package/cli/builder/bundles/users-bundle/Resources/js/users.js +0 -153
- package/cli/builder/bundles/users-bundle/Resources/public/favicon.ico +0 -0
- package/cli/builder/bundles/users-bundle/Resources/public/images/users-logo.png +0 -0
- package/cli/builder/bundles/users-bundle/Resources/scss/awesome/font-awesome.config.js +0 -11
- package/cli/builder/bundles/users-bundle/Resources/scss/custom.scss +0 -23
- package/cli/builder/bundles/users-bundle/Resources/scss/users.scss +0 -59
- package/cli/builder/bundles/users-bundle/Resources/swagger/openapi/login.js +0 -235
- package/cli/builder/bundles/users-bundle/Resources/swagger/openapi/users.js +0 -232
- package/cli/builder/bundles/users-bundle/Resources/translations/login.en_en.yml +0 -14
- package/cli/builder/bundles/users-bundle/Resources/translations/login.fr_fr.yml +0 -14
- package/cli/builder/bundles/users-bundle/Resources/translations/users.en_en.yml +0 -35
- package/cli/builder/bundles/users-bundle/Resources/translations/users.fr_fr.yml +0 -36
- package/cli/builder/bundles/users-bundle/Resources/views/base.html.twig +0 -32
- package/cli/builder/bundles/users-bundle/Resources/views/footer.html.twig +0 -7
- package/cli/builder/bundles/users-bundle/Resources/views/header.html.twig +0 -90
- package/cli/builder/bundles/users-bundle/Resources/views/login/login.html.twig +0 -43
- package/cli/builder/bundles/users-bundle/Resources/views/users/createUser.html.twig +0 -296
- package/cli/builder/bundles/users-bundle/Resources/views/users/readUsers.html.twig +0 -94
- package/cli/builder/bundles/users-bundle/controller/api/graphql/graphqlController.js +0 -56
- package/cli/builder/bundles/users-bundle/controller/api/graphql/userResolver.js +0 -121
- package/cli/builder/bundles/users-bundle/controller/api/graphql/usertype.js +0 -58
- package/cli/builder/bundles/users-bundle/controller/api/openapi/loginApiController.js +0 -210
- package/cli/builder/bundles/users-bundle/controller/api/openapi/restUserController.js +0 -244
- package/cli/builder/bundles/users-bundle/controller/loginController.js +0 -91
- package/cli/builder/bundles/users-bundle/controller/usersController.js +0 -332
- package/cli/builder/bundles/users-bundle/package.json +0 -58
- package/cli/builder/bundles/users-bundle/readme.md +0 -360
- package/cli/builder/bundles/users-bundle/services/usersService.js +0 -411
- package/cli/builder/bundles/users-bundle/src/providers/mongoose/userProvider.js +0 -47
- package/cli/builder/bundles/users-bundle/src/providers/sequelize/userProvider.js +0 -48
- package/cli/builder/bundles/users-bundle/tests/loginTest.js +0 -182
- package/cli/builder/bundles/users-bundle/usersBundle.js +0 -26
- package/cli/builder/microService/microService.js +0 -261
- package/cli/builder/microService/skeletons/.editorconfig +0 -18
- package/cli/builder/microService/skeletons/.env-cmdrc.js +0 -32
- package/cli/builder/microService/skeletons/.jshintrc +0 -37
- package/cli/builder/microService/skeletons/README.md +0 -117
- package/cli/builder/microService/skeletons/bin/bash/hello.sh +0 -5
- package/cli/builder/microService/skeletons/bin/cli +0 -13
- package/cli/builder/microService/skeletons/bin/python/hello.py +0 -20
- package/cli/builder/microService/skeletons/config/config.js +0 -29
- package/cli/builder/microService/skeletons/config/pm2.config.js +0 -72
- package/cli/builder/microService/skeletons/config/webpack/webpack.config.dev.js +0 -8
- package/cli/builder/microService/skeletons/config/webpack/webpack.config.prod.js +0 -25
- package/cli/builder/microService/skeletons/config/webpack.config.js +0 -114
- package/cli/builder/microService/skeletons/package.json +0 -76
- package/cli/builder/microService/skeletons/src/browser/index.css +0 -3
- package/cli/builder/microService/skeletons/src/browser/index.js +0 -16
- package/cli/builder/microService/skeletons/src/browser/socketio.js +0 -46
- package/cli/builder/microService/skeletons/src/cli/cli.js +0 -224
- package/cli/builder/microService/skeletons/src/cli/menu.js +0 -28
- package/cli/builder/microService/skeletons/src/n-api/README.md +0 -1
- package/cli/builder/microService/skeletons/src/n-api/binding.gyp +0 -14
- package/cli/builder/microService/skeletons/src/n-api/hello.cc +0 -14
- package/cli/builder/microService/skeletons/src/n-api/hello.js +0 -23
- package/cli/builder/microService/skeletons/src/n-api/package.json +0 -15
- package/cli/builder/microService/skeletons/src/node/examples/index.js +0 -120
- package/cli/builder/microService/skeletons/src/node/index.js +0 -102
- package/cli/builder/microService/skeletons/src/node/services/markdown/markdown.js +0 -27
- package/cli/builder/microService/skeletons/src/node/services/servers/http.js +0 -45
- package/cli/builder/microService/skeletons/src/node/services/servers/https.js +0 -44
- package/cli/builder/microService/skeletons/src/node/services/socketio/socketio.js +0 -46
- package/cli/builder/microService/skeletons/src/node/services/syscall/syscall.js +0 -111
- package/cli/builder/microService/skeletons/src/node/services/worker/thread.js +0 -59
- package/cli/builder/microService/skeletons/src/node/services/worker/worker.js +0 -60
- package/cli/builder/microService/skeletons/src/templates/base.html +0 -12
- package/cli/builder/microService/skeletons/src/templates/socket.html +0 -23
- package/cli/builder/microService/skeletons/tests/unit/serviceTest.js +0 -30
- package/cli/builder/project/project.js +0 -474
- package/cli/builder/project/skeletons/README.md +0 -778
- package/cli/builder/project/skeletons/bin/dev-deploy.sh +0 -5
- package/cli/builder/project/skeletons/bin/generateCertificates.sh.skeleton +0 -180
- package/cli/builder/project/skeletons/bin/prod-deploy.sh +0 -3
- package/cli/builder/project/skeletons/config/config.js.skeleton +0 -178
- package/cli/builder/project/skeletons/config/openssl/ca/openssl.cnf.skeleton +0 -150
- package/cli/builder/project/skeletons/config/openssl/ca_intermediate/openssl.cnf.skeleton +0 -152
- package/cli/builder/project/skeletons/config/pm2.config.js.skeleton +0 -62
- package/cli/builder/project/skeletons/documentation.html.twig +0 -24
- package/cli/builder/project/skeletons/editorconfig.skeleton +0 -18
- package/cli/builder/project/skeletons/eslintignore.skeleton +0 -15
- package/cli/builder/project/skeletons/eslintrc.js.skeleton +0 -97
- package/cli/builder/project/skeletons/gitignore.skeleton +0 -32
- package/cli/builder/project/skeletons/migrations/2022.12.25T17.37.37.entity-user.js +0 -137
- package/cli/builder/project/skeletons/migrations/2022.12.25T17.37.38.entity-session.js +0 -97
- package/cli/builder/project/skeletons/migrations/2022.12.25T17.37.39.entity-requests.js +0 -115
- package/cli/builder/project/skeletons/migrations/2022.12.25T17.37.40.entity-jwts.js +0 -97
- package/cli/builder/project/skeletons/migrations/migrations.skeleton.js +0 -101
- package/cli/builder/project/skeletons/package.json.twig +0 -71
- package/cli/builder/react/reactBuilder.js +0 -224
- package/cli/builder/react/skeletons/app/Resources/databases/nodefony.db +0 -0
- package/cli/builder/react/skeletons/app/Resources/translations/messages.en_en.yml +0 -5
- package/cli/builder/react/skeletons/app/Resources/translations/messages.fr_fr.yml +0 -5
- package/cli/builder/react/skeletons/app/Resources/views/base.html.twig +0 -33
- package/cli/builder/react/skeletons/app/config/config.js +0 -392
- package/cli/builder/react/skeletons/app/config/routing.js +0 -10
- package/cli/builder/react/skeletons/app/config/security.js +0 -74
- package/cli/builder/react/skeletons/app/controller/appController.js +0 -22
- package/cli/builder/sandbox/sandBoxBuilder.js +0 -610
- package/cli/builder/sandbox/skeletons/Resources/css/entry.css +0 -48
- package/cli/builder/sandbox/skeletons/Resources/js/entry.js +0 -60
- package/cli/builder/sandbox/skeletons/Resources/public/manifest.json +0 -32
- package/cli/builder/sandbox/skeletons/Resources/translations/messages.en_en.yml +0 -5
- package/cli/builder/sandbox/skeletons/Resources/translations/messages.fr_fr.yml +0 -5
- package/cli/builder/sandbox/skeletons/Resources/views/base.html.twig +0 -46
- package/cli/builder/sandbox/skeletons/Resources/views/index.html.twig +0 -34
- package/cli/builder/sandbox/skeletons/bootstrap/custom.scss +0 -27
- package/cli/builder/sandbox/skeletons/bootstrap/entry.scss +0 -60
- package/cli/builder/sandbox/skeletons/workbox/templates/index.html.twig +0 -32
- package/cli/builder/sandbox/skeletons/workbox/workers/service-worker.js +0 -196
- package/cli/builder/skeletons/Resources/public/favicon.ico +0 -0
- package/cli/builder/skeletons/Resources/public/images/app-logo.png +0 -0
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/401.html.twig +0 -1
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/401.json.twig +0 -24
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/403.html.twig +0 -1
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/403.json.twig +0 -20
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/404.html.twig +0 -1
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/404.json.twig +0 -20
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/500.html.twig +0 -1
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/base.html.twig +0 -30
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/exception.html.twig +0 -1
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/exception.json.twig +0 -24
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/footer.html.twig +0 -33
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/header.html.twig +0 -6
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/index.html.twig +0 -6
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/index.json.twig +0 -1
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/layout.html.twig +0 -125
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/layout.json.twig +0 -5
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/timeout.html.twig +0 -1
- package/cli/builder/skeletons/Resources/views/framework-bundle/views/timeout.json.twig +0 -24
- package/cli/builder/skeletons/app/appKernel.js +0 -133
- package/cli/builder/skeletons/binding/binding.cc +0 -26
- package/cli/builder/skeletons/binding/binding.skeleton +0 -8
- package/cli/builder/skeletons/bundle/bundleClass.js +0 -37
- package/cli/builder/skeletons/command/commandClass.js +0 -11
- package/cli/builder/skeletons/command/taskClass.js +0 -20
- package/cli/builder/skeletons/config/config.js +0 -138
- package/cli/builder/skeletons/config/nodefony/elastic-bundle.js +0 -37
- package/cli/builder/skeletons/config/nodefony/framework-bundle.js +0 -22
- package/cli/builder/skeletons/config/nodefony/http-bundle.js +0 -104
- package/cli/builder/skeletons/config/nodefony/mail-bundle.js +0 -44
- package/cli/builder/skeletons/config/nodefony/mongoose-bundle.js +0 -51
- package/cli/builder/skeletons/config/nodefony/monitoring-bundle.js +0 -34
- package/cli/builder/skeletons/config/nodefony/realtime-bundle.js +0 -15
- package/cli/builder/skeletons/config/nodefony/redis-bundle.js +0 -44
- package/cli/builder/skeletons/config/nodefony/security-bundle.js +0 -25
- package/cli/builder/skeletons/config/nodefony/sequelize-bundle.js +0 -156
- package/cli/builder/skeletons/config/routing.js +0 -55
- package/cli/builder/skeletons/config/security.js +0 -102
- package/cli/builder/skeletons/config/services.js +0 -1
- package/cli/builder/skeletons/config/webpack/webpack.dev.config.js +0 -13
- package/cli/builder/skeletons/config/webpack/webpack.prod.config.js +0 -20
- package/cli/builder/skeletons/config/webpack.config.js +0 -148
- package/cli/builder/skeletons/controller/controllerClass.js +0 -75
- package/cli/builder/skeletons/package.json.twig +0 -57
- package/cli/builder/skeletons/unittest/testFile.js +0 -46
- package/cli/builder/vue/skeletons/Resources/public/images/app-logo.png +0 -0
- package/cli/builder/vue/skeletons/Resources/translations/messages.en_en.yml +0 -5
- package/cli/builder/vue/skeletons/Resources/translations/messages.fr_fr.yml +0 -5
- package/cli/builder/vue/skeletons/vue.config.js +0 -129
- package/cli/builder/vue/vueBuilder.js +0 -410
- package/cli/generate/generate.es6 +0 -103
- package/cli/install/install.js +0 -190
- package/cli/sequelize/sequelize.js +0 -94
- package/cli/start.js +0 -702
- package/cli/tools/pm2.es6 +0 -112
- package/cli/tools/tools.es6 +0 -97
- package/cli.es6 +0 -925
- package/container.es6 +0 -272
- package/error.es6 +0 -293
- package/fileClass.es6 +0 -242
- package/finder/file.es6 +0 -33
- package/finder/fileResult.es6 +0 -138
- package/finder/finder2.es6 +0 -275
- package/finder.es6 +0 -427
- package/kernel/annotations/annotations.es6 +0 -286
- package/kernel/api/api.es6 +0 -61
- package/kernel/api/graphqlApi.es6 +0 -107
- package/kernel/api/jsonApi.es6 +0 -177
- package/kernel/api/openApi.es6 +0 -139
- package/kernel/api/schemas/openApiSchema.js +0 -394
- package/kernel/babylon.es6 +0 -162
- package/kernel/bundle.es6 +0 -1200
- package/kernel/cliKernel.es6 +0 -869
- package/kernel/command.es6 +0 -78
- package/kernel/commands/nodefonyCommand.es6 +0 -247
- package/kernel/commands/tasks/bundlesTask.es6 +0 -59
- package/kernel/controllers/controller.es6 +0 -791
- package/kernel/fixture.es6 +0 -19
- package/kernel/injections/injections.es6 +0 -424
- package/kernel/kernel.es6 +0 -1960
- package/kernel/kernelWatcher.es6 +0 -387
- package/kernel/orm/entity.es6 +0 -59
- package/kernel/orm/orm.es6 +0 -139
- package/kernel/reader.es6 +0 -290
- package/kernel/security/encoder.es6 +0 -17
- package/kernel/security/factories/factory.es6 +0 -111
- package/kernel/security/factories/passeportFactory.es6 +0 -61
- package/kernel/security/provider.es6 +0 -58
- package/kernel/security/providers/chainProvider.es6 +0 -99
- package/kernel/security/providers/memoryProvider.es6 +0 -55
- package/kernel/security/providers/providerManager.es6 +0 -67
- package/kernel/security/providers/userEntityProvider.es6 +0 -50
- package/kernel/security/role.es6 +0 -12
- package/kernel/security/secureArea.es6 +0 -431
- package/kernel/security/tokens/token.es6 +0 -171
- package/kernel/security/user.es6 +0 -106
- package/kernel/serviceRealTime.es6 +0 -21
- package/kernel/services/connectionsService.es6 +0 -93
- package/kernel/services/cron/cronService.es6 +0 -123
- package/kernel/task.es6 +0 -80
- package/kernel/templates.es6 +0 -22
- package/kernel/templating/twig.es6 +0 -218
- package/kernel/tests/cliTest.js +0 -59
- package/kernel/tests/containerTest.js +0 -232
- package/kernel/tests/kernelTest.js +0 -39
- package/nodefony.es6 +0 -1084
- package/notificationsCenter.es6 +0 -118
- package/protocol.es6 +0 -86
- package/protocols/bayeux.es6 +0 -174
- package/protocols/jsonRpc/jsonrpc.es6 +0 -56
- package/result.es6 +0 -99
- package/service.es6 +0 -288
- package/syslog/pdu.es6 +0 -141
- package/syslog/syslog.es6 +0 -754
- package/watcher.es6 +0 -113
|
@@ -0,0 +1,2198 @@
|
|
|
1
|
+
import { Nodefony } from "../Nodefony.js";
|
|
2
|
+
import { logColor, resolveColorEnabled, setLogColor } from "../syslog/logColor.js";
|
|
3
|
+
import { extend, isSubclassOf } from "../Tools.js";
|
|
4
|
+
import FileClass from "../FileClass.js";
|
|
5
|
+
import { DEFAULT_ENGINE_ENVIRONMENT, engineModeOf } from "../runtime/engineEnvironment.js";
|
|
6
|
+
import Syslog, { NULL_LOG_SINK } from "../syslog/Syslog.js";
|
|
7
|
+
import Service from "../Service.js";
|
|
8
|
+
import { FileSink } from "../syslog/sinks/FileSink.js";
|
|
9
|
+
import { getActiveLogDriver, getLogDriverFactory, listLogDriverFactories, registerLogDriver, setActiveLogDriver } from "../syslog/drivers/logDriverRegistry.js";
|
|
10
|
+
import { registerBuiltinLogDrivers, resolveQueryDriver } from "../syslog/drivers/builtinLogDrivers.js";
|
|
11
|
+
import { isInKubernetes, shouldWarnPerPodView } from "../service/cluster/podEnvironment.js";
|
|
12
|
+
import "../cli/sysexits.js";
|
|
13
|
+
import { resolveModuleEntry, toImportSpecifier } from "./resolveModuleEntry.js";
|
|
14
|
+
import { isUnboundedListener, readListenerTags } from "./lifecycleTags.js";
|
|
15
|
+
import nodefonyError from "../Error.js";
|
|
16
|
+
import { clearReadinessState, writeReadinessState } from "../service/dev/devProcess.js";
|
|
17
|
+
import { isClusterMessage } from "../service/cluster/clusterMessage.js";
|
|
18
|
+
import { writeLastBoot } from "./checks/lastBoot.js";
|
|
19
|
+
import { applyResolvedPath, closestMatch, parseNfEnvOverrides, pathLooksSecret, readResolvedPath, resolveFailureHint } from "../config/envOverride.js";
|
|
20
|
+
import { gateModuleManifest } from "./moduleGating.js";
|
|
21
|
+
import { resolveInfra } from "../config/infra.js";
|
|
22
|
+
import CliKernel from "./CliKernel.js";
|
|
23
|
+
import Injector from "./injector/injector.js";
|
|
24
|
+
import Module from "./Module.js";
|
|
25
|
+
import { defaultAppConfig } from "../config/defaults.js";
|
|
26
|
+
import { isConfigDescriptor, readAppEnvOverrideReport } from "../config/defineConfig.js";
|
|
27
|
+
import { findSetReservedKeys } from "../config/configProvenance.js";
|
|
28
|
+
import { TimeoutError, withTimeout } from "../runtime/withTimeout.js";
|
|
29
|
+
import { BootConfigurationError } from "./BootConfigurationError.js";
|
|
30
|
+
import { ReadinessRegistry } from "./readinessRegistry.js";
|
|
31
|
+
import cluster from "node:cluster";
|
|
32
|
+
import fs from "node:fs";
|
|
33
|
+
import os from "node:os";
|
|
34
|
+
import path from "node:path";
|
|
35
|
+
//#region src/kernel/Kernel.ts
|
|
36
|
+
const colorLogEvent = () => logColor.cyanBgBlue("EVENT KERNEL");
|
|
37
|
+
const SERVER_SCHEME = {
|
|
38
|
+
http: "http",
|
|
39
|
+
https: "https",
|
|
40
|
+
websocket: "ws",
|
|
41
|
+
"websocket-secure": "wss"
|
|
42
|
+
};
|
|
43
|
+
const kernelDefaultOptions = { events: { nbListeners: 60 } };
|
|
44
|
+
const DEFAULT_SHUTDOWN_DEADLINE = 15e3;
|
|
45
|
+
const SHUTDOWN_DEADLINE = Symbol("shutdown-deadline");
|
|
46
|
+
const SHUTDOWN_DRAIN_ERROR = Symbol("shutdown-drain-error");
|
|
47
|
+
/**
|
|
48
|
+
* Dérogation au gating `policy:"dev"` en production — posée à `1`, les modules de
|
|
49
|
+
* développement sont chargés malgré le mode.
|
|
50
|
+
*
|
|
51
|
+
* Existe pour éprouver un runtime de production AVEC les briques de banc (une suite
|
|
52
|
+
* d'intégration interroge des routes qu'un module `policy:"dev"` porte, et qui
|
|
53
|
+
* n'existent donc pas là-bas : tout répond 404). Réservée aux bancs et au
|
|
54
|
+
* diagnostic — chaque module chargé par dérogation est journalisé en WARNING, et
|
|
55
|
+
* la variable n'a **aucun effet** hors production.
|
|
56
|
+
*/
|
|
57
|
+
const FORCE_DEV_MODULES_ENV = "NF_WITH_DEV_MODULES";
|
|
58
|
+
/**
|
|
59
|
+
* Durée de vie d'un runtime de production qui a dérogé au gating des modules dev.
|
|
60
|
+
*
|
|
61
|
+
* La variable seule ne protège de rien : elle s'oublie, dans une image, un manifeste
|
|
62
|
+
* de déploiement, un fichier d'environnement recopié — et le jour où elle survit à
|
|
63
|
+
* une mise en production, plus rien ne le dit. Un runtime en dérogation **s'arrête
|
|
64
|
+
* donc tout seul** : l'oubli devient un incident immédiat et lisible plutôt qu'une
|
|
65
|
+
* surface offerte pour des mois. Large devant toute suite de tests (les tâches
|
|
66
|
+
* d'intégration continue du dépôt expirent à 20 minutes), dérisoire devant la durée
|
|
67
|
+
* de vie d'un déploiement.
|
|
68
|
+
*/
|
|
69
|
+
const FORCE_DEV_MODULES_TTL_MS = 18e5;
|
|
70
|
+
/**
|
|
71
|
+
* Plafond DUR de cette durée de vie. Un banc de charge légitime dure plus qu'une
|
|
72
|
+
* suite d'intégration, d'où le réglage ci-dessous — mais aucune valeur ne désarme
|
|
73
|
+
* la minuterie : une protection qu'on peut annuler par la même variable que celle
|
|
74
|
+
* qu'elle protège n'en est pas une.
|
|
75
|
+
*/
|
|
76
|
+
const FORCE_DEV_MODULES_TTL_MAX_MS = 144e5;
|
|
77
|
+
/** Variable de réglage de la durée de vie, en MINUTES (bornée par le plafond dur). */
|
|
78
|
+
const FORCE_DEV_MODULES_TTL_ENV = "NF_WITH_DEV_MODULES_TTL_MIN";
|
|
79
|
+
/**
|
|
80
|
+
* Durée de vie effective d'un runtime en dérogation — fonction PURE.
|
|
81
|
+
*
|
|
82
|
+
* @param env - environnement à lire (injecté : la règle s'éprouve sans toucher au process).
|
|
83
|
+
* @returns la durée en millisecondes, toujours comprise entre le défaut et le plafond.
|
|
84
|
+
*/
|
|
85
|
+
function resolveDevModulesTtlMs(env) {
|
|
86
|
+
const raw = Number.parseInt(env["NF_WITH_DEV_MODULES_TTL_MIN"] ?? "", 10);
|
|
87
|
+
if (!Number.isFinite(raw) || raw <= 0) return FORCE_DEV_MODULES_TTL_MS;
|
|
88
|
+
return Math.min(Math.max(raw * 6e4, FORCE_DEV_MODULES_TTL_MS), FORCE_DEV_MODULES_TTL_MAX_MS);
|
|
89
|
+
}
|
|
90
|
+
const Events = Object.freeze({
|
|
91
|
+
onInit: 1,
|
|
92
|
+
onPreStart: 2,
|
|
93
|
+
onStart: 4,
|
|
94
|
+
onPreRegister: 8,
|
|
95
|
+
onRegister: 16,
|
|
96
|
+
onPreBoot: 32,
|
|
97
|
+
onBoot: 64,
|
|
98
|
+
onReady: 128,
|
|
99
|
+
onServersReady: 256,
|
|
100
|
+
onPostReady: 512,
|
|
101
|
+
onTerminate: 1024
|
|
102
|
+
});
|
|
103
|
+
/** Profil par défaut — équivaut à l'ancien `type = "CONSOLE"` (one-shot, sans serveur). */
|
|
104
|
+
const CONSOLE_RUN_PROFILE = Object.freeze({
|
|
105
|
+
servers: false,
|
|
106
|
+
lifetime: "oneshot",
|
|
107
|
+
interactive: false
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Combien de messages CRITIC un bilan de démarrage retient.
|
|
111
|
+
*
|
|
112
|
+
* Trois : au-delà, ce sont presque toujours des conséquences du premier, et un
|
|
113
|
+
* bilan qu'on ne lit pas ne vaut pas mieux qu'un bilan muet.
|
|
114
|
+
*/
|
|
115
|
+
const MAX_BOOT_CRITICALS = 3;
|
|
116
|
+
/** Longueur d'un message retenu — au-delà, la ligne déborde tout rendu. */
|
|
117
|
+
const MAX_BOOT_CRITICAL_LENGTH = 200;
|
|
118
|
+
/**
|
|
119
|
+
* Orchestrateur central de Nodefony — gère le boot, les modules, le DI Container racine,
|
|
120
|
+
* et expose les events lifecycle auxquels services/modules se branchent.
|
|
121
|
+
*
|
|
122
|
+
* Hérite de {@link Service} → bénéficie DI, EventEmitter, Syslog. Pollue le singleton
|
|
123
|
+
* `Nodefony.#kernel` au constructor (`Nodefony.setKernel(this)`) — isoler les tests avec
|
|
124
|
+
* un mock minimal.
|
|
125
|
+
*
|
|
126
|
+
* **Lifecycle phases** (chronologiques, jamais régressives) :
|
|
127
|
+
* `started → preRegistered → registered → booted → ready → postReady`
|
|
128
|
+
*
|
|
129
|
+
* **Events bitmask** (`progress` = OR cumulatif) :
|
|
130
|
+
* - `onInit=1`, `onPreStart=2`, `onStart=4`
|
|
131
|
+
* - `onPreRegister=8`, `onRegister=16`
|
|
132
|
+
* - `onPreBoot=32`, `onBoot=64`
|
|
133
|
+
* - `onReady=128`, `onServersReady=256`, `onPostReady=512`
|
|
134
|
+
* - `onTerminate=1024`
|
|
135
|
+
*
|
|
136
|
+
* **Chaîne async** (chaque maillon appelle le suivant si `!setCommandComplete`) :
|
|
137
|
+
* `start() → preRegister() → boot() → onReady() → initServers()`
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* import { CliKernel } from "nodefony";
|
|
142
|
+
* const cli = new CliKernel("development");
|
|
143
|
+
* await cli.start(); // → new Kernel(env, cli) → kernel.start() → boot complet
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
var Kernel = class extends Service {
|
|
147
|
+
Events = Events;
|
|
148
|
+
runProfile;
|
|
149
|
+
version = "1.0.0";
|
|
150
|
+
started = false;
|
|
151
|
+
booted = false;
|
|
152
|
+
ready = false;
|
|
153
|
+
postReady = false;
|
|
154
|
+
/**
|
|
155
|
+
* Composants qui RETIENNENT la mise en service (cf `readinessRegistry.ts`).
|
|
156
|
+
*
|
|
157
|
+
* `null` tant que personne ne s'inscrit — et de nouveau `null` quand le
|
|
158
|
+
* dernier contributeur se retire : la sonde `/readyz` est sur le chemin le
|
|
159
|
+
* plus chaud du framework, elle ne doit rien payer pour un mécanisme dont
|
|
160
|
+
* aucune application ne se sert. Lire {@link Kernel.readinessBlocked}.
|
|
161
|
+
*/
|
|
162
|
+
readiness = null;
|
|
163
|
+
/**
|
|
164
|
+
* Supprime les bannières serveurs « Server Listen on… » (`showBanner`) — posé
|
|
165
|
+
* par le `BootReporter` animé (dev TTY non-debug) : le bloc « ✓ Prêt » liste
|
|
166
|
+
* déjà les URLs, ces logs feraient doublon. Reste `false` en prod/CI/`--debug`
|
|
167
|
+
* → bannières affichées (aucun changement hors écran de boot animé).
|
|
168
|
+
*/
|
|
169
|
+
suppressBootBanners = false;
|
|
170
|
+
trunk = null;
|
|
171
|
+
_appEntry = void 0;
|
|
172
|
+
core = false;
|
|
173
|
+
command = null;
|
|
174
|
+
commandArgs = [];
|
|
175
|
+
preRegistered = false;
|
|
176
|
+
registered = false;
|
|
177
|
+
app = null;
|
|
178
|
+
cli = null;
|
|
179
|
+
/**
|
|
180
|
+
* Mode MOTEUR par défaut, quand RIEN ne le dit.
|
|
181
|
+
*
|
|
182
|
+
* 🔴 `development`, et c'est un choix de sûreté — pas de confort. Le défaut
|
|
183
|
+
* ne s'applique que si `NODE_ENV` est absent ET que la commande n'exprime
|
|
184
|
+
* aucune intention. Or cette information ne manque JAMAIS en production : le
|
|
185
|
+
* `Dockerfile` généré pose `ENV NODE_ENV=production`, tout orchestrateur pose
|
|
186
|
+
* la variable, et les trois lanceurs posent leur mode eux-mêmes
|
|
187
|
+
* (`ProdCommand`, `DevCommand`, `ClusterCommand`). Elle ne manque que sur un
|
|
188
|
+
* poste de développement — le défaut doit donc protéger CE cas.
|
|
189
|
+
*
|
|
190
|
+
* L'ancien défaut `production` a produit trois défauts constatés : un jeton
|
|
191
|
+
* émis contre la mauvaise configuration, une inspection qui montrait les
|
|
192
|
+
* modules de production sur une machine de développement, et un contrôle de
|
|
193
|
+
* banc qui comparait deux applications différentes. Aucun n'a levé d'erreur :
|
|
194
|
+
* deviner « production » là où l'on est manifestement en développement est
|
|
195
|
+
* une dégradation SILENCIEUSE, ce que ce framework s'interdit.
|
|
196
|
+
*
|
|
197
|
+
* Il était de surcroît incohérent avec lui-même : la cascade `.env` ne
|
|
198
|
+
* chargeait alors NI `.env.production` ni `.env.development` (le lanceur
|
|
199
|
+
* rendait `undefined`), si bien que l'application se déclarait en production
|
|
200
|
+
* sans en avoir la configuration.
|
|
201
|
+
*/
|
|
202
|
+
environment = DEFAULT_ENGINE_ENVIRONMENT;
|
|
203
|
+
debug = false;
|
|
204
|
+
appEnvironment = { environment: process.env.NODE_ENV };
|
|
205
|
+
path = process.cwd();
|
|
206
|
+
typeCluster = this.clusterIsMaster() ? "master" : "worker";
|
|
207
|
+
pid = process.pid;
|
|
208
|
+
workerId = cluster.worker?.id;
|
|
209
|
+
worker = cluster.worker;
|
|
210
|
+
console = this.isConsole();
|
|
211
|
+
/**
|
|
212
|
+
* Vrai terminal disponible ? Résolu UNE fois dans le constructor (volet
|
|
213
|
+
* « environnement », cf {@link IKernel.isTTY}). Surchargeable via `NF_NO_TTY` (test/CI).
|
|
214
|
+
*/
|
|
215
|
+
isTTY = process.env.NF_NO_TTY ? false : process.stdout?.isTTY === true;
|
|
216
|
+
/**
|
|
217
|
+
* Timer no-op ref'd gardant l'event loop vivant pendant un {@link park} `keepAlive`
|
|
218
|
+
* (daemon CONSOLE sans socket). `null` tant qu'aucun park alive — lazy. Nettoyé par
|
|
219
|
+
* {@link terminate}.
|
|
220
|
+
*/
|
|
221
|
+
parkTimer = null;
|
|
222
|
+
/**
|
|
223
|
+
* Le shutdown en cours, s'il y en a un — garde de ré-entrance de
|
|
224
|
+
* {@link terminate}. `null` par défaut : aucune allocation tant qu'on ne
|
|
225
|
+
* termine pas, et un kernel ne termine qu'une fois.
|
|
226
|
+
*/
|
|
227
|
+
terminating = null;
|
|
228
|
+
/** Minuterie d'auto-arrêt d'un runtime en dérogation de modules dev (`null` = aucune). */
|
|
229
|
+
devModulesStopTimer = null;
|
|
230
|
+
node_start = process.env.NF_START || this.options.node_start;
|
|
231
|
+
platform = process.platform;
|
|
232
|
+
projectName = "NODEFONY";
|
|
233
|
+
uptime = (/* @__PURE__ */ new Date()).getTime();
|
|
234
|
+
numberCpu = os.cpus().length;
|
|
235
|
+
modules = {};
|
|
236
|
+
tmpDir;
|
|
237
|
+
/**
|
|
238
|
+
* Répertoire des données runtime **persistées** (`<path>/var`) — base commune des
|
|
239
|
+
* stores fichier (passkeys, TOTP, sessions) et bases SQLite. Gitignoré comme
|
|
240
|
+
* `tmpDir` → garanti créé au boot. Distinct de `tmpDir` (éphémère).
|
|
241
|
+
*/
|
|
242
|
+
varDir;
|
|
243
|
+
interfaces;
|
|
244
|
+
domain = "localhost";
|
|
245
|
+
progress = Events.onInit;
|
|
246
|
+
injector;
|
|
247
|
+
/**
|
|
248
|
+
* Transports d'ÉCRITURE montés par {@link initializeLog} (drivers du Log
|
|
249
|
+
* Backplane). `initializeLog` est ré-entrant (logger précoce dans `start()`,
|
|
250
|
+
* puis re-init avec la config app dans `loadApp()`) : on retire ces transports
|
|
251
|
+
* AVANT d'en monter de nouveaux, sinon chaque `FileTransport` (nouvelle instance
|
|
252
|
+
* → dédup `addTransport` par référence inopérante) s'accumule et chaque log est
|
|
253
|
+
* écrit N× dans le JSONL. Lazy (`null` tant qu'aucun transport monté).
|
|
254
|
+
*/
|
|
255
|
+
_mountedLogTransports = null;
|
|
256
|
+
isDev = false;
|
|
257
|
+
isProd = true;
|
|
258
|
+
/**
|
|
259
|
+
* En dev, `BootReporter` (checklist animée) prend la main sur l'en-tête de boot
|
|
260
|
+
* (splash + banner `SERVER`). Quand `true`, `initCluster()` ne ré-imprime PAS le
|
|
261
|
+
* banner `logEnv()` (le reporter le place lui-même, dans le bon ordre). Dev-only,
|
|
262
|
+
* boot-only — 0 impact runtime/requête.
|
|
263
|
+
*/
|
|
264
|
+
reporterOwnsHeader = false;
|
|
265
|
+
pendingModuleAddLogs = [];
|
|
266
|
+
/**
|
|
267
|
+
* Échecs de boot non fatals (fail-soft) agrégés pour le {@link IBootReport}.
|
|
268
|
+
* **Lazy** : reste `null` tant qu'aucun module/hook n'échoue → 0 allocation sur
|
|
269
|
+
* un boot nominal (règle perf core).
|
|
270
|
+
*/
|
|
271
|
+
bootFailures = null;
|
|
272
|
+
/**
|
|
273
|
+
* Modules du manifeste volontairement NON chargés (gating `policy`/`when` de
|
|
274
|
+
* {@link resolveModuleEntries}), avec leur raison — un gating silencieux se lit
|
|
275
|
+
* comme un module perdu. **Lazy** : `null` si rien n'est gaté (cas nominal).
|
|
276
|
+
*/
|
|
277
|
+
modulesGated = null;
|
|
278
|
+
/**
|
|
279
|
+
* Compteurs WARNING/ERROR du journal de boot, **figés** quand `postReady` passe
|
|
280
|
+
* true (après, le ring syslog mélange boot et runtime). `null` = boot en cours
|
|
281
|
+
* → {@link getBootReport} compte à la volée.
|
|
282
|
+
*/
|
|
283
|
+
bootLogCounts = null;
|
|
284
|
+
/**
|
|
285
|
+
* Serveurs réellement en écoute, figés à `onPostReady`. `null` tant que le boot
|
|
286
|
+
* n'a pas atteint cette phase ; `[]` si profil serveur mais rien n'écoute (cas
|
|
287
|
+
* du garde-fou 0-serveur).
|
|
288
|
+
*/
|
|
289
|
+
bootServers = null;
|
|
290
|
+
/** Horodatage (`Date.now()`) du début de `start()` — base de `durationMs`. */
|
|
291
|
+
bootStartedAt = 0;
|
|
292
|
+
/**
|
|
293
|
+
* Lignes de détail de boot par phase (canal NEUTRE) : un module pousse via
|
|
294
|
+
* {@link reportBootLine} ce qu'il veut voir RACONTÉ sous sa phase (ex. un
|
|
295
|
+
* adapter ORM : « drizzle → sqlite »). Le core reste agnostique du contenu.
|
|
296
|
+
* Lazy : `null` tant qu'aucune ligne (dev-only en pratique).
|
|
297
|
+
*/
|
|
298
|
+
bootLines = null;
|
|
299
|
+
/**
|
|
300
|
+
* Construit le Kernel. **Side effect critique** : appelle `Nodefony.setKernel(this)` →
|
|
301
|
+
* écrase le singleton global. Isoler les tests avec un mock minimal pour éviter de
|
|
302
|
+
* polluer les autres tests qui dépendent de `Nodefony.getKernel()`.
|
|
303
|
+
*
|
|
304
|
+
* Récupère le container du CLI si présent, sinon en crée un nouveau. Initialise les
|
|
305
|
+
* interfaces réseau OS pour `setDomain()`. Fire `"onInit"` à la fin.
|
|
306
|
+
*
|
|
307
|
+
* @param environment - mode MOTEUR (`"development"` / `"production"`, ou leurs
|
|
308
|
+
* abrégés `"dev"` / `"prod"`). ⚠️ Ni `"test"` ni `"staging"` : `test` est une valeur
|
|
309
|
+
* de `NODE_ENV` normalisée en `runtimeEnv` (→ `ConfigContext.isTest`), et un staging
|
|
310
|
+
* tourne EN mode `production` — il se distingue par `APP_ENV`, pas par son moteur.
|
|
311
|
+
* @param cli - kernel CLI parent (fournit container, packageManager, commander). Peut être null.
|
|
312
|
+
* @param options - options surchargées (events.nbListeners, log, etc.).
|
|
313
|
+
*/
|
|
314
|
+
constructor(environment, cli, options) {
|
|
315
|
+
const container = cli?.container;
|
|
316
|
+
const mergedOptions = extend({}, kernelDefaultOptions, options);
|
|
317
|
+
super("KERNEL", container, void 0, mergedOptions);
|
|
318
|
+
this.environment = environment;
|
|
319
|
+
if (process.env.NF_KERNEL_TRACE_FILE) try {
|
|
320
|
+
fs.appendFileSync(process.env.NF_KERNEL_TRACE_FILE, `${process.pid}:${environment}\n`);
|
|
321
|
+
} catch {}
|
|
322
|
+
this.setMaxListeners(mergedOptions.events?.nbListeners ?? 60);
|
|
323
|
+
Nodefony.setKernel(this);
|
|
324
|
+
this.kernel = this;
|
|
325
|
+
this.set("kernel", this);
|
|
326
|
+
this.runProfile = { ...CONSOLE_RUN_PROFILE };
|
|
327
|
+
this.cli = this.setCli(cli);
|
|
328
|
+
this.interfaces = this.getNetworkInterfaces();
|
|
329
|
+
this.injector = new Injector(this);
|
|
330
|
+
this.set("injector", this.injector);
|
|
331
|
+
this.fire("onInit", this);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Point d'entrée du boot — et POINT DE PASSAGE UNIQUE de l'échec de démarrage.
|
|
335
|
+
*
|
|
336
|
+
* Le déroulé vit dans {@link startBoot} ; cette enveloppe ne fait qu'une
|
|
337
|
+
* chose : figer le bilan (`var/last-boot.json`) quand le démarrage est
|
|
338
|
+
* abandonné, puis relancer l'erreur inchangée. Le bilan d'un démarrage
|
|
339
|
+
* ABOUTI est écrit ailleurs — au moment où il est complet (cf
|
|
340
|
+
* {@link captureBootServers} / `onPostReady`), pas ici.
|
|
341
|
+
*
|
|
342
|
+
* Pourquoi l'échec ICI et pas dans les `catch` du pipeline : ils sont une
|
|
343
|
+
* douzaine, et chacun relance. Y répartir l'écriture donnerait autant
|
|
344
|
+
* d'implémentations d'une même règle — dont certaines seraient oubliées au
|
|
345
|
+
* premier ajout de phase, sans que rien ne le signale. Un seul point ne peut
|
|
346
|
+
* pas diverger de lui-même.
|
|
347
|
+
*
|
|
348
|
+
* Et pourquoi RIEN n'est effacé sur un succès : une commande console
|
|
349
|
+
* (`inspect`, une commande de module) démarre et réussit sans jamais monter
|
|
350
|
+
* de serveur. Effacer ici ferait disparaître le bilan d'un échec applicatif
|
|
351
|
+
* au premier `nodefony inspect` lancé pour le diagnostiquer — l'outil de
|
|
352
|
+
* diagnostic détruirait la preuve qu'il vient chercher.
|
|
353
|
+
*
|
|
354
|
+
* @returns `this` après boot complet.
|
|
355
|
+
* @throws Toute exception du pipeline est loggée CRITIC puis re-throw — le
|
|
356
|
+
* bilan n'en absorbe aucune.
|
|
357
|
+
*/
|
|
358
|
+
async start() {
|
|
359
|
+
try {
|
|
360
|
+
return await this.startBoot();
|
|
361
|
+
} catch (e) {
|
|
362
|
+
this.traceFatalBootFailure(e);
|
|
363
|
+
throw e;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Traduit le bitmask {@link progress} en NOM de la dernière phase atteinte.
|
|
368
|
+
*
|
|
369
|
+
* C'est l'information la plus discriminante d'une trace d'échec : elle situe
|
|
370
|
+
* le défaut dans le cycle de vie (« mort à `onBoot` » désigne les modules,
|
|
371
|
+
* « mort à `onReady` » désigne les serveurs) sans qu'on ait à lire une pile.
|
|
372
|
+
*
|
|
373
|
+
* @returns le nom de la phase la plus avancée, `"onInit"` par défaut.
|
|
374
|
+
*/
|
|
375
|
+
lastReachedPhase() {
|
|
376
|
+
let reached = "onInit";
|
|
377
|
+
for (const [name, bit] of Object.entries(Events)) if (this.progress & bit) reached = name;
|
|
378
|
+
return reached;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Socle commun des deux bilans (abouti et abandonné) — ce qui est vrai dans
|
|
382
|
+
* les deux cas se remplit ICI, une seule fois.
|
|
383
|
+
*
|
|
384
|
+
* @param status - issue du démarrage.
|
|
385
|
+
* @returns le bilan, prêt à être complété par l'appelant.
|
|
386
|
+
*/
|
|
387
|
+
baseLastBoot(status) {
|
|
388
|
+
return {
|
|
389
|
+
status,
|
|
390
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
391
|
+
profile: this.lastBootProfile(),
|
|
392
|
+
command: this.command?.name,
|
|
393
|
+
environment: this.environment ?? "unknown",
|
|
394
|
+
pid: process.pid,
|
|
395
|
+
node: process.version,
|
|
396
|
+
phase: this.lastReachedPhase()
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* QUI démarre — la distinction qui empêche `doctor` de désigner le mauvais
|
|
401
|
+
* coupable.
|
|
402
|
+
*
|
|
403
|
+
* Un `nodefony inspect` ou un `orm:migrate` boote en console et échoue
|
|
404
|
+
* souvent pour une raison qui ne concerne pas le serveur : un environnement
|
|
405
|
+
* absent du terminal où l'on tape. Sans ce champ, son bilan s'écrivait
|
|
406
|
+
* par-dessus celui du serveur, et le diagnostic envoyait chercher là où il
|
|
407
|
+
* n'y a rien.
|
|
408
|
+
*
|
|
409
|
+
* `cluster` est distingué de `server` parce que le maître ne sert AUCUNE
|
|
410
|
+
* requête lui-même : ses workers le font. Le critère se CONSTATE — la
|
|
411
|
+
* commande `cluster` dans le process primaire ; un worker forké, lui, porte
|
|
412
|
+
* `cluster.isWorker` et sert bien des requêtes, donc c'est un `server`.
|
|
413
|
+
*
|
|
414
|
+
* @returns le profil de ce démarrage.
|
|
415
|
+
*/
|
|
416
|
+
lastBootProfile() {
|
|
417
|
+
if (!this.runProfile?.servers) return "console";
|
|
418
|
+
if (this.command?.name === "cluster" && cluster.isPrimary) return "cluster";
|
|
419
|
+
return "server";
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Fige le bilan d'un démarrage ABOUTI.
|
|
423
|
+
*
|
|
424
|
+
* Ce n'est pas une redondance avec le journal : le bilan retient ce que le
|
|
425
|
+
* terminal a montré une fois puis perdu — les briques ignorées AVEC leur
|
|
426
|
+
* raison, celles que le gating a écartées, les comptes d'avertissements. Une
|
|
427
|
+
* application qui démarre amputée ne se signale plus après sa première
|
|
428
|
+
* seconde de vie ; c'est justement le cas que personne ne diagnostique.
|
|
429
|
+
*
|
|
430
|
+
* @param report - bilan de boot figé (serveurs capturés).
|
|
431
|
+
*/
|
|
432
|
+
writeBootSummary(report) {
|
|
433
|
+
const entry = this.baseLastBoot("ok");
|
|
434
|
+
entry.durationMs = report.durationMs;
|
|
435
|
+
entry.healthy = report.healthy;
|
|
436
|
+
entry.modulesLoaded = report.modulesLoaded;
|
|
437
|
+
entry.warnings = report.warnings;
|
|
438
|
+
entry.errors = report.errors;
|
|
439
|
+
entry.criticals = report.criticals ?? void 0;
|
|
440
|
+
entry.remediation = report.remediation ?? void 0;
|
|
441
|
+
if (report.modulesSkipped.length) entry.bricksSkipped = report.modulesSkipped.map((f) => ({
|
|
442
|
+
module: f.module,
|
|
443
|
+
reason: f.reason,
|
|
444
|
+
phase: f.phase
|
|
445
|
+
}));
|
|
446
|
+
if (report.modulesGated.length) entry.bricksGated = report.modulesGated.map((g) => ({
|
|
447
|
+
module: g.module,
|
|
448
|
+
reason: g.reason
|
|
449
|
+
}));
|
|
450
|
+
if (report.serversListening.length) entry.serversListening = report.serversListening.map((s) => `${s.type}${s.port ? `:${s.port}` : ""}`);
|
|
451
|
+
writeLastBoot(this.path, entry);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Consigne le démarrage ABANDONNÉ, sans jamais pouvoir aggraver la panne.
|
|
455
|
+
*
|
|
456
|
+
* À ne pas confondre avec {@link recordBootFailure}, qui collecte les échecs
|
|
457
|
+
* **non fatals** (fail-soft) d'un boot qui, lui, continue. Les deux se
|
|
458
|
+
* complètent, et c'est pourquoi le bilan embarque le second : quand le boot
|
|
459
|
+
* meurt, savoir quelles briques avaient DÉJÀ été ignorées avant désigne
|
|
460
|
+
* souvent la cause réelle — un module optionnel tombé emporte plus loin celle
|
|
461
|
+
* qui en dépendait, et seul le premier incident l'explique.
|
|
462
|
+
*
|
|
463
|
+
* @param e - l'erreur qui a fait abandonner le boot.
|
|
464
|
+
*/
|
|
465
|
+
traceFatalBootFailure(e) {
|
|
466
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
467
|
+
const entry = this.baseLastBoot("failed");
|
|
468
|
+
entry.error = {
|
|
469
|
+
message: err.message,
|
|
470
|
+
name: err.name,
|
|
471
|
+
exitCode: err.exitCode,
|
|
472
|
+
stack: err.stack
|
|
473
|
+
};
|
|
474
|
+
if (this.bootFailures?.length) entry.bricksSkipped = this.bootFailures.map((f) => ({
|
|
475
|
+
module: f.module,
|
|
476
|
+
reason: f.reason,
|
|
477
|
+
phase: f.phase
|
|
478
|
+
}));
|
|
479
|
+
const { criticals } = this.countBootLogIssues();
|
|
480
|
+
if (criticals) entry.criticals = criticals;
|
|
481
|
+
writeLastBoot(this.path, entry);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Déroulé du boot. Fire `"onPreStart"` puis `"onStart"`, charge l'application
|
|
485
|
+
* (`loadApp()`), instancie services kernel (Rollup), puis enchaîne sur
|
|
486
|
+
* `preRegister()` → `boot()` → `onReady()` → `initServers()`.
|
|
487
|
+
*
|
|
488
|
+
* Si `command.kernelEvent` matche la phase atteinte → {@link finishOrPark} immédiat
|
|
489
|
+
* (la command a fini son boulot : terminate one-shot OU park daemon long-running) —
|
|
490
|
+
* même sémantique de sortie à TOUTES les phases d'arrêt.
|
|
491
|
+
*
|
|
492
|
+
* Appelé UNIQUEMENT par {@link start}, qui porte la trace d'échec.
|
|
493
|
+
*
|
|
494
|
+
* @returns `this` après boot complet.
|
|
495
|
+
* @throws Toute exception du pipeline est loggée CRITIC puis re-throw.
|
|
496
|
+
*/
|
|
497
|
+
async startBoot() {
|
|
498
|
+
if (this.bootStartedAt === 0) this.bootStartedAt = Date.now();
|
|
499
|
+
this.debug = Boolean(this.cli?.commander?.opts().debug) || false;
|
|
500
|
+
this.trunk = await this.isTrunk();
|
|
501
|
+
this.initializeLog();
|
|
502
|
+
if (!this.trunk && this.cli) {
|
|
503
|
+
const hint = this.diagnoseUnbootableProject();
|
|
504
|
+
if (hint) {
|
|
505
|
+
this.log(hint, "CRITIC");
|
|
506
|
+
return await this.terminate(1);
|
|
507
|
+
}
|
|
508
|
+
if (!this.isTTY) {
|
|
509
|
+
this.log(`Pas de projet Nodefony ici (${this.path}) : aucune entrée d'app résolue (package.json \`main\`, dist/index.js ou index.js). Vérifie le répertoire de travail et le build (dist/).`, "CRITIC");
|
|
510
|
+
return await this.terminate(1);
|
|
511
|
+
}
|
|
512
|
+
return await this.cli.runCommandAsync("menu", ["-i"]).then(() => {
|
|
513
|
+
if (this.command) return this.command?.action(...this.commandArgs).then(() => {
|
|
514
|
+
return this;
|
|
515
|
+
});
|
|
516
|
+
return this;
|
|
517
|
+
}).catch((e) => {
|
|
518
|
+
this.log(e, "ERROR");
|
|
519
|
+
throw e;
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
const devSplash = this.environment === "development" && process.env.NF_DEV_CHILD === "1";
|
|
523
|
+
if (this.cli && devSplash) {
|
|
524
|
+
await this.cli.showAsciify(this.projectName).catch((e) => {
|
|
525
|
+
this.log(e, "WARNING");
|
|
526
|
+
});
|
|
527
|
+
this.printDevHeader();
|
|
528
|
+
this.reporterOwnsHeader = true;
|
|
529
|
+
}
|
|
530
|
+
const tmpPath = path.resolve(process.cwd(), "tmp");
|
|
531
|
+
fs.mkdirSync(tmpPath, { recursive: true });
|
|
532
|
+
this.tmpDir = new FileClass(tmpPath);
|
|
533
|
+
const varPath = path.resolve(this.path, "var");
|
|
534
|
+
fs.mkdirSync(varPath, { recursive: true });
|
|
535
|
+
this.varDir = new FileClass(varPath);
|
|
536
|
+
if (!this.started) {
|
|
537
|
+
await this.fireAsync("onPreStart", this).catch((e) => {
|
|
538
|
+
this.log(e, "CRITIC");
|
|
539
|
+
throw e;
|
|
540
|
+
});
|
|
541
|
+
if (this.setCommandComplete(Events.onPreStart)) return this.finishOrPark(0);
|
|
542
|
+
await this.loadApp().catch((e) => {
|
|
543
|
+
if (!e.presented) this.log(e, "CRITIC");
|
|
544
|
+
throw e;
|
|
545
|
+
});
|
|
546
|
+
this.domain = this.setDomain();
|
|
547
|
+
if (this.app) this.projectName = this.app.getModuleName();
|
|
548
|
+
return this.fireAsync("onStart", this).then(async () => {
|
|
549
|
+
this.started = true;
|
|
550
|
+
if (this.cli) this.runProfile = this.cli.runProfile;
|
|
551
|
+
if (this.setCommandComplete(Events.onStart)) return this.finishOrPark(0);
|
|
552
|
+
return this.preRegister();
|
|
553
|
+
}).catch((e) => {
|
|
554
|
+
if (e.message !== "(outputHelp)") this.log(e, "CRITIC");
|
|
555
|
+
throw e;
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return this;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Phase pré-registration — fire `"onPreRegister"` puis `"onRegister"`. Le manifeste
|
|
562
|
+
* `config.modules` est consommé ici (chargement via {@link loadModulesFromManifest}).
|
|
563
|
+
*
|
|
564
|
+
* @returns `this` ou chaîne sur `boot()`.
|
|
565
|
+
*/
|
|
566
|
+
async preRegister() {
|
|
567
|
+
await this.fireLifecycle("onPreRegister", this);
|
|
568
|
+
if (this.resolveRuntimeEnv(this.cli?.environment) === "development" || !this.runProfile?.servers) this.cli?.writeCompletionManifest().catch(() => {});
|
|
569
|
+
if (this.setCommandComplete(Events.onPreRegister)) return this.finishOrPark(0);
|
|
570
|
+
this.preRegistered = true;
|
|
571
|
+
if (this.cli) {
|
|
572
|
+
this.debug = Boolean(this.cli?.commander?.opts().debug) || false;
|
|
573
|
+
this.setEnv(this.cli.environment);
|
|
574
|
+
this.cli.setProcessTitle(this.projectName.toLowerCase());
|
|
575
|
+
}
|
|
576
|
+
this.setNodeEnv(this.environment);
|
|
577
|
+
this.initCluster();
|
|
578
|
+
this.applyModuleConfigOverrides();
|
|
579
|
+
return this.fireLifecycle("onRegister", this).then(() => {
|
|
580
|
+
this.registered = true;
|
|
581
|
+
this.warnReservedConfigKeys();
|
|
582
|
+
if (this.setCommandComplete(Events.onRegister)) return this.finishOrPark(0);
|
|
583
|
+
return this.boot().catch((e) => {
|
|
584
|
+
throw e;
|
|
585
|
+
});
|
|
586
|
+
}).catch((e) => {
|
|
587
|
+
throw e;
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
fixCommanderCli(version = true, debug = false) {
|
|
591
|
+
if (this.cli && this.cli.commander && this.cli.commander?.options.length) {
|
|
592
|
+
if (version) {
|
|
593
|
+
if (this.cli?.commander?.options.some((opt) => opt.short === "-v" || opt.long === "--version")) {
|
|
594
|
+
const index = this.cli.commander.options.findIndex((value) => {
|
|
595
|
+
if (value.flags === "-v, --version") return value;
|
|
596
|
+
});
|
|
597
|
+
if (index >= 0) this.cli.commander.options.splice(index, 1);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (debug) {
|
|
601
|
+
if (this.cli?.commander?.options.some((opt) => opt.short === "-d" || opt.long === "--debug")) {
|
|
602
|
+
const index = this.cli.commander.options.findIndex((value) => {
|
|
603
|
+
if (value.flags === "-d, --debug") return value;
|
|
604
|
+
});
|
|
605
|
+
if (index >= 0) this.cli.commander.options.splice(index, 1);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Phase boot — fire `"onPreBoot"` puis `"onBoot"`. Les services kernel (router, certificats,
|
|
612
|
+
* sessions, http-kernel) sont créés ici via les décorateurs `@services`.
|
|
613
|
+
*
|
|
614
|
+
* @returns `this` ou chaîne sur `onReady()`.
|
|
615
|
+
*/
|
|
616
|
+
async boot() {
|
|
617
|
+
await this.fireLifecycle("onPreBoot", this);
|
|
618
|
+
if (this.setCommandComplete(Events.onPreBoot)) return this.finishOrPark(0);
|
|
619
|
+
return this.fireLifecycle("onBoot", this).then(() => {
|
|
620
|
+
this.booted = true;
|
|
621
|
+
if (this.setCommandComplete(Events.onBoot)) return this.finishOrPark(0);
|
|
622
|
+
return this.onReady().catch((e) => {
|
|
623
|
+
throw e;
|
|
624
|
+
});
|
|
625
|
+
}).catch((e) => {
|
|
626
|
+
throw e;
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Phase ready — fire `"onReady"`, démarre les serveurs HTTP/WS via `initServers()`, log
|
|
631
|
+
* memoryUsage, puis fire `"onPostReady"`. C'est ici que les Server Listen apparaissent.
|
|
632
|
+
*
|
|
633
|
+
* @returns `this` après tout le pipeline post-ready.
|
|
634
|
+
*/
|
|
635
|
+
async onReady() {
|
|
636
|
+
return this.fireLifecycle("onReady", this).then(async () => {
|
|
637
|
+
this.ready = true;
|
|
638
|
+
if (this.setCommandComplete(Events.onReady)) return this.finishOrPark(0);
|
|
639
|
+
return this.initServers().then(async (servers) => {
|
|
640
|
+
this.captureBootServers(servers);
|
|
641
|
+
const report = this.getBootReport();
|
|
642
|
+
this.logBootVerdict(report);
|
|
643
|
+
this.writeBootSummary(report);
|
|
644
|
+
if (global && global.gc) {
|
|
645
|
+
this.memoryUsage("MEMORY POST READY ");
|
|
646
|
+
setTimeout(() => {
|
|
647
|
+
if (global && global.gc) global.gc();
|
|
648
|
+
this.memoryUsage("EXPOSE GARBADGE COLLECTOR ON START");
|
|
649
|
+
}, 2e4);
|
|
650
|
+
} else this.memoryUsage("MEMORY POST READY ");
|
|
651
|
+
return this.fireLifecycle("onPostReady", this).then(() => {
|
|
652
|
+
this.postReady = true;
|
|
653
|
+
this.bootLogCounts = this.countBootLogIssues();
|
|
654
|
+
if (!this.suppressBootBanners) servers.map((server) => {
|
|
655
|
+
server.showBanner();
|
|
656
|
+
});
|
|
657
|
+
if (!report.healthy && report.serversExpected) return this.terminate(69);
|
|
658
|
+
if (this.setCommandComplete(Events.onPostReady)) {
|
|
659
|
+
if (!this.runProfile.servers) return this.finishOrPark(typeof process.exitCode === "number" ? process.exitCode : 0);
|
|
660
|
+
this.log(`Live cycle terminate`, "DEBUG");
|
|
661
|
+
return this;
|
|
662
|
+
} else return this;
|
|
663
|
+
}).catch((e) => {
|
|
664
|
+
throw e;
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
}).catch((e) => {
|
|
668
|
+
throw e;
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Démarre les serveurs HTTP/HTTPS/HTTP2/WS/WSS via le `HttpKernel` (@nodefony/http).
|
|
673
|
+
*
|
|
674
|
+
* Si le HttpKernel n'est pas dans le container (mode CONSOLE pur) → retourne tableau vide.
|
|
675
|
+
* Sinon → délègue à `httpKernel.initServers()`, fire `"onServersReady"` après succès.
|
|
676
|
+
*
|
|
677
|
+
* **Le profil d'exécution fait foi** : une commande qui déclare ne pas vouloir
|
|
678
|
+
* de serveur (`runProfile.servers === false`, le défaut console) n'en obtient
|
|
679
|
+
* pas, même si le `HttpKernel` est chargé. Sans cette garde, toute commande
|
|
680
|
+
* poussée jusqu'à `onPostReady` ouvrait les ports par effet de bord — elle
|
|
681
|
+
* échouait alors si un serveur tournait déjà, et refusait le service à celui
|
|
682
|
+
* qui tournait pendant sa brève existence. `onServersReady` n'est pas émis :
|
|
683
|
+
* annoncer des serveurs prêts quand il n'y en a aucun rendrait l'événement
|
|
684
|
+
* inexploitable pour ceux qui l'écoutent.
|
|
685
|
+
*
|
|
686
|
+
* @returns array d'instances de serveurs démarrés (ou `[]`).
|
|
687
|
+
*/
|
|
688
|
+
async initServers() {
|
|
689
|
+
if (this.runProfile?.servers === false) return [];
|
|
690
|
+
const httpKernel = this.get("HttpKernel");
|
|
691
|
+
if (httpKernel) return await httpKernel.initServers().then(async (servers) => {
|
|
692
|
+
await this.fireAsync("onServersReady");
|
|
693
|
+
return servers;
|
|
694
|
+
}).catch((e) => {
|
|
695
|
+
throw e;
|
|
696
|
+
});
|
|
697
|
+
return [];
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Parke le flow : retourne une Promise qui ne se résout JAMAIS (met en pause le
|
|
701
|
+
* pipeline async appelant). **Source UNIQUE** du « rester vivant jusqu'à un signal »
|
|
702
|
+
* — remplace les `new Promise(() => {})` inline disséminés (DevSupervisor parent,
|
|
703
|
+
* master cluster, daemon).
|
|
704
|
+
*
|
|
705
|
+
* ⚠️ Une Promise pending ne garde PAS Node en vie (le process sort dès l'event loop
|
|
706
|
+
* vide). Pour un daemon CONSOLE qui n'a aucun socket/handle → `keepAlive: true` ref un
|
|
707
|
+
* timer no-op (réveil ~tous les 12 j, coût nul). Les superviseurs ont DÉJÀ leurs propres
|
|
708
|
+
* handles (watchers fs du DevSupervisor / canaux IPC workers + timers de sonde du
|
|
709
|
+
* master) → `keepAlive` défaut `false` : aucun handle en trop qui empêcherait leur
|
|
710
|
+
* sortie naturelle au shutdown. Le timer alive est nettoyé par {@link terminate}.
|
|
711
|
+
*
|
|
712
|
+
* Signal handling : déjà fourni globalement par `Cli.handleSignals` → `terminate()`.
|
|
713
|
+
*
|
|
714
|
+
* @param opts.keepAlive - ref un timer no-op pour garder l'event loop vivant.
|
|
715
|
+
* @returns Promise jamais résolue.
|
|
716
|
+
*/
|
|
717
|
+
park(opts = {}) {
|
|
718
|
+
if (opts.keepAlive && this.parkTimer === null) this.parkTimer = setInterval(() => {}, 1 << 30);
|
|
719
|
+
return new Promise(() => {});
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Fin de cycle d'une commande ayant atteint sa phase cible : `terminate(code)` pour un
|
|
723
|
+
* run one-shot (build, install, batch, help), MAIS **park** pour un run long-running
|
|
724
|
+
* sans serveur (daemon CONSOLE : worker de queue, consumer, agent IA, cron daemon).
|
|
725
|
+
*
|
|
726
|
+
* C'est ICI que `lifetime` devient effectif : avant, chaque commande daemon inlinait
|
|
727
|
+
* son propre park ; désormais le Kernel décide à partir de la `lifetime` DÉCLARÉE par
|
|
728
|
+
* la commande (`command.lifetime`, fallback `runProfile.lifetime`) croisée avec
|
|
729
|
+
* `runProfile.servers` (les serveurs gardent déjà le process vivant → jamais de park).
|
|
730
|
+
*
|
|
731
|
+
* @param code - exit code si run one-shot.
|
|
732
|
+
* @returns Promise du terminate (one-shot) ou park (daemon, jamais résolue).
|
|
733
|
+
*/
|
|
734
|
+
finishOrPark(code) {
|
|
735
|
+
if ((this.command?.lifetime ?? this.runProfile.lifetime) === "longrunning" && !this.runProfile.servers) {
|
|
736
|
+
this.log("CONSOLE long-running — park (no server, until signal)", "DEBUG");
|
|
737
|
+
return this.park({ keepAlive: true });
|
|
738
|
+
}
|
|
739
|
+
const failure = typeof process.exitCode === "number" && process.exitCode !== 0 ? process.exitCode : null;
|
|
740
|
+
return this.terminate(failure ?? code);
|
|
741
|
+
}
|
|
742
|
+
clean() {
|
|
743
|
+
this.removeAllListeners();
|
|
744
|
+
this.modules = {};
|
|
745
|
+
}
|
|
746
|
+
setCommand(command) {
|
|
747
|
+
this.command = command;
|
|
748
|
+
}
|
|
749
|
+
setDomain() {
|
|
750
|
+
if (this.options.domain == "selectAuto") return this.getFirstExternalInterface()?.address || "localhost";
|
|
751
|
+
else return this.options.domain || "localhost";
|
|
752
|
+
}
|
|
753
|
+
readConfig(config) {
|
|
754
|
+
if (!config) return this.options;
|
|
755
|
+
return extend(this.options, config);
|
|
756
|
+
}
|
|
757
|
+
async addService(service, module, ...args) {
|
|
758
|
+
return module.addService(service, module, ...args);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Instancie un service au niveau kernel (vs niveau module via `Module.addService`).
|
|
762
|
+
*
|
|
763
|
+
* Utilisé pour les services partagés essentiels au boot (Rollup, HttpKernel).
|
|
764
|
+
* Stocke directement dans le container kernel.
|
|
765
|
+
*
|
|
766
|
+
* @param ctor - constructeur du service (typiquement décoré `@injectable`).
|
|
767
|
+
* @param args - args additionnels après les `@inject` resolved.
|
|
768
|
+
* @returns instance du service prête à l'usage.
|
|
769
|
+
*/
|
|
770
|
+
async addKernelService(service, ...args) {
|
|
771
|
+
const inst = Injector.instantiate(service, this, ...args);
|
|
772
|
+
if (this.get(inst.name)) this.log(`SERVICE ALREADY EXIST override old service : ${inst.name}`, "WARNING");
|
|
773
|
+
this.log(`SERVICE ADD : ${inst.name}`, "DEBUG");
|
|
774
|
+
const serviceInit = inst;
|
|
775
|
+
await this.guardServiceInitialize(serviceInit, this, true);
|
|
776
|
+
Injector.rememberContainerKey(service, inst.name);
|
|
777
|
+
this.set(inst.name, inst);
|
|
778
|
+
return this.get(inst.name);
|
|
779
|
+
}
|
|
780
|
+
async loadService(service, module = this.app, ...args) {
|
|
781
|
+
if (!module) throw new Error(`Applcation not ready`);
|
|
782
|
+
const res = await import(toImportSpecifier(service));
|
|
783
|
+
return this.addService(res.default, module, ...args);
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Charge un module du manifeste. La résolution part de l'APPLICATION
|
|
787
|
+
* ({@link resolveModuleEntry}) et non du paquet `nodefony` : sans cela, un
|
|
788
|
+
* module local de l'app (workspace `modules/*`) est introuvable dès que le core
|
|
789
|
+
* vit hors de l'arbre `node_modules` de l'app (`--link`, monorepo, pnpm).
|
|
790
|
+
*/
|
|
791
|
+
async loadModule(moduleName) {
|
|
792
|
+
const moduleClass = await import(resolveModuleEntry(this.path, moduleName));
|
|
793
|
+
return await this.addModule(moduleClass.default);
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Résout la liste ORDONNÉE des entrées de modules à charger depuis le manifeste
|
|
797
|
+
* `config.modules`, AVEC leur config colocalisée (`use(name, config)`). L'ordre
|
|
798
|
+
* du tableau est conservé (= priorité de chargement) ; `policy`/`when` ne font
|
|
799
|
+
* que FILTRER, jamais réordonner.
|
|
800
|
+
*
|
|
801
|
+
* - `policy:"dev"` → ignoré en production (gain mémoire = non-chargement ; en
|
|
802
|
+
* ESM un module importé n'est jamais déchargé).
|
|
803
|
+
* - `when(config)` faux → ignoré (sa config colocalisée l'est aussi → pas de
|
|
804
|
+
* warning « module absent » sur un module volontairement gaté).
|
|
805
|
+
* - string nue → policy `optional`, toujours chargée, sans config.
|
|
806
|
+
*
|
|
807
|
+
* @returns entrées `{ name, config? }` à charger, dans l'ordre.
|
|
808
|
+
*/
|
|
809
|
+
resolveModuleEntries() {
|
|
810
|
+
const isProd = this.resolveRuntimeEnv(this.cli?.environment) === "production";
|
|
811
|
+
const devModulesForced = isProd && process.env["NF_WITH_DEV_MODULES"] === "1";
|
|
812
|
+
const outcome = gateModuleManifest(this.options.modules, {
|
|
813
|
+
isProduction: isProd,
|
|
814
|
+
forceDevModules: devModulesForced,
|
|
815
|
+
config: this.options
|
|
816
|
+
});
|
|
817
|
+
this.modulesGated = null;
|
|
818
|
+
for (const gated of outcome.gated) this.recordModuleGated(gated.module, gated.reason);
|
|
819
|
+
for (const name of outcome.derogated) {
|
|
820
|
+
this.log(`module "${name}" (policy "dev") chargé en PRODUCTION sur ${FORCE_DEV_MODULES_ENV}=1 — dérogation explicite, à ne jamais poser sur un déploiement réel`, "WARNING", "KERNEL");
|
|
821
|
+
this.armDevModulesSelfStop();
|
|
822
|
+
}
|
|
823
|
+
return outcome.entries;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Enregistre un module volontairement non chargé (gating `policy`/`when`) avec
|
|
827
|
+
* sa raison — surfacé par le bilan de boot ({@link getBootReport}). Lazy : 0
|
|
828
|
+
* allocation si rien n'est gaté.
|
|
829
|
+
*
|
|
830
|
+
* @param module - nom d'entrée du manifeste.
|
|
831
|
+
* @param reason - raison lisible du non-chargement.
|
|
832
|
+
*/
|
|
833
|
+
recordModuleGated(module, reason) {
|
|
834
|
+
(this.modulesGated ??= []).push({
|
|
835
|
+
module,
|
|
836
|
+
reason
|
|
837
|
+
});
|
|
838
|
+
this.log(`MODULE GATED : ${module} — ${reason}`, "DEBUG");
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Arme l'auto-arrêt d'un runtime de production ayant dérogé au gating des modules
|
|
842
|
+
* de développement (cf {@link FORCE_DEV_MODULES_ENV}). Idempotent : une seule
|
|
843
|
+
* minuterie, quel que soit le nombre de modules concernés.
|
|
844
|
+
*
|
|
845
|
+
* **L'échéance est ANNONCÉE, deux fois.** Un arrêt temporisé qui surprend est pire
|
|
846
|
+
* que pas de garde-fou : il coupe un banc de charge au milieu d'une mesure, et le
|
|
847
|
+
* journal qu'on relira ensuite accusera le code. On dit donc l'échéance au
|
|
848
|
+
* démarrage — avec le moyen de l'allonger — puis on prévient avant de tomber, et
|
|
849
|
+
* l'arrêt lui-même en donne la raison. Les minuteries sont `unref` : elles
|
|
850
|
+
* n'empêchent jamais une sortie naturelle.
|
|
851
|
+
*/
|
|
852
|
+
armDevModulesSelfStop() {
|
|
853
|
+
if (this.devModulesStopTimer) return;
|
|
854
|
+
const ttl = resolveDevModulesTtlMs(process.env);
|
|
855
|
+
const minutes = Math.round(ttl / 6e4);
|
|
856
|
+
this.log(`runtime en DÉROGATION (modules dev en production) — arrêt automatique dans ${minutes} min. Allonger : ${FORCE_DEV_MODULES_TTL_ENV}=<minutes> (plafond ${Math.round(FORCE_DEV_MODULES_TTL_MAX_MS / 6e4)} min, jamais désarmé)`, "WARNING", "KERNEL");
|
|
857
|
+
const notice = ttl - 3e5;
|
|
858
|
+
if (notice > 0) setTimeout(() => {
|
|
859
|
+
this.log(`arrêt automatique dans 5 min (dérogation ${FORCE_DEV_MODULES_ENV}) — termine ta mesure ou relance avec ${FORCE_DEV_MODULES_TTL_ENV} plus haut`, "WARNING", "KERNEL");
|
|
860
|
+
}, notice).unref();
|
|
861
|
+
this.devModulesStopTimer = setTimeout(() => {
|
|
862
|
+
this.log(`arrêt automatique : ce runtime tournait en production avec des modules "policy: dev" (${FORCE_DEV_MODULES_ENV}=1). Ce n'est pas une panne — c'est la garde qui empêche une dérogation de banc de survivre à un déploiement`, "CRITIC", "KERNEL");
|
|
863
|
+
this.terminate(0);
|
|
864
|
+
}, ttl);
|
|
865
|
+
this.devModulesStopTimer.unref();
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Charge en série les modules résolus par {@link resolveModuleEntries} via
|
|
869
|
+
* `loadModule` (import dynamique → lazy : un module hors liste n'est jamais
|
|
870
|
+
* importé), puis applique la config colocalisée (`use(name, config)`) de
|
|
871
|
+
* chaque entrée. Branché à `onPreRegister` par {@link loadApp} si un manifeste
|
|
872
|
+
* `config.modules` est présent. Seul orchestrateur du chargement de modules.
|
|
873
|
+
*/
|
|
874
|
+
async loadModulesFromManifest() {
|
|
875
|
+
for (const entry of this.resolveModuleEntries()) try {
|
|
876
|
+
const mod = await this.loadModule(entry.name);
|
|
877
|
+
if (entry.config) {
|
|
878
|
+
mod.options = extend(true, {}, mod.options, entry.config);
|
|
879
|
+
this.log(`MODULE CONFIG (use) : ${entry.name}`, "DEBUG");
|
|
880
|
+
}
|
|
881
|
+
} catch (error) {
|
|
882
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
883
|
+
this.log(`MODULE LOAD: échec non bloquant (fail-soft) de "${entry.name}" — ${msg}`, "WARNING");
|
|
884
|
+
this.recordBootFailure({
|
|
885
|
+
module: entry.name,
|
|
886
|
+
reason: msg,
|
|
887
|
+
phase: "load"
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Instancie un module et l'enregistre dans `kernel.modules[name]`. Appelle `initialize(this)`
|
|
893
|
+
* sur le module si défini (équivalent constructeur async).
|
|
894
|
+
*
|
|
895
|
+
* @param Mod - constructeur du module (extends `Module`).
|
|
896
|
+
* @param args - arguments additionnels (après `kernel, path, options` par défaut).
|
|
897
|
+
* @returns instance du module enregistrée.
|
|
898
|
+
*/
|
|
899
|
+
async addModule(Mod, ...args) {
|
|
900
|
+
const mod = new Mod(this, ...args);
|
|
901
|
+
this.modules[mod.name] = mod;
|
|
902
|
+
if (this.pendingModuleAddLogs) this.pendingModuleAddLogs.push(mod.name);
|
|
903
|
+
else this.log(`MODULE ADD : ${mod.name}`, "INFO");
|
|
904
|
+
if (mod.init) {
|
|
905
|
+
this.log(`MODULE INITIALIZE : ${mod.name}`, "DEBUG");
|
|
906
|
+
const init = mod.init.bind(mod);
|
|
907
|
+
await this.guardInitialize(() => init(this), mod.name, mod.constructor.critical);
|
|
908
|
+
}
|
|
909
|
+
return mod;
|
|
910
|
+
}
|
|
911
|
+
getModule(name) {
|
|
912
|
+
return this.modules[name];
|
|
913
|
+
}
|
|
914
|
+
getModules() {
|
|
915
|
+
return this.modules;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Applique les overrides de config inter-modules (clés `Module-<name>` /
|
|
919
|
+
* `module-<name>` dans les options d'un module — typiquement l'app qui
|
|
920
|
+
* reconfigure un autre module) en itérant TOUS les modules enregistrés.
|
|
921
|
+
*
|
|
922
|
+
* Appelé par {@link preRegister} ENTRE `onPreRegister` (tous les modules sont
|
|
923
|
+
* alors enregistrés via `@modules`/`loadApp`) et `onRegister` (où les modules
|
|
924
|
+
* valident + gèlent leur config Zod dans `onKernelRegister`). Corrige la dette
|
|
925
|
+
* d'ordering : l'override était auparavant posé à `onPreBoot` (après la
|
|
926
|
+
* validation) → silencieusement ignoré par tout module qui fige sa config tôt
|
|
927
|
+
* (redis, realtime…). Désormais il précède la validation, qui le voit.
|
|
928
|
+
*
|
|
929
|
+
* Ordre d'application = ordre d'insertion dans `this.modules` (app chargée en
|
|
930
|
+
* premier → configure les modules avant qu'ils ne se valident). Idempotent.
|
|
931
|
+
*/
|
|
932
|
+
applyModuleConfigOverrides() {
|
|
933
|
+
for (const name in this.modules) this.modules[name].readOverrideModuleConfig();
|
|
934
|
+
this.applyEnvConfigOverrides();
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Applique les overrides de config par variable d'environnement générique
|
|
938
|
+
* `NF__<MODULE>__<CHEMIN…>` (ADR-0006 D3) sur la config de chaque module ciblé.
|
|
939
|
+
*
|
|
940
|
+
* Résolu 1× au boot (hors hot path). La surcharge est déposée sur
|
|
941
|
+
* `module.options` → elle sera validée par le schéma Zod du module à
|
|
942
|
+
* `onKernelRegister` (fail-closed si invalide). Un module ou un chemin
|
|
943
|
+
* introuvable est SIGNALÉ (WARNING) sans bloquer le boot — jamais de clé
|
|
944
|
+
* fantôme silencieuse. Les valeurs de chemins « secrets » sont rédigées au log.
|
|
945
|
+
*/
|
|
946
|
+
applyEnvConfigOverrides() {
|
|
947
|
+
const overrides = parseNfEnvOverrides(process.env);
|
|
948
|
+
if (!overrides.length) return;
|
|
949
|
+
/**
|
|
950
|
+
* Le schéma d'un module, quand il en publie un — jamais une hypothèse.
|
|
951
|
+
*
|
|
952
|
+
* `configSchema()` est une méthode de `Module`, mais rien ne garantit qu'un
|
|
953
|
+
* module l'ait : un paquet tiers, un module ancien ou un objet de banc
|
|
954
|
+
* peuvent n'être que compatibles de forme. Appeler à l'aveugle ferait tomber
|
|
955
|
+
* le BOOT sur une amélioration de confort — le pire échange possible.
|
|
956
|
+
*/
|
|
957
|
+
const schemaDe = (m) => {
|
|
958
|
+
const f = m.configSchema;
|
|
959
|
+
return typeof f === "function" ? f.call(m) ?? void 0 : void 0;
|
|
960
|
+
};
|
|
961
|
+
for (const ov of overrides) {
|
|
962
|
+
const mod = this.findModuleBySegment(ov.moduleSeg);
|
|
963
|
+
if (!mod) {
|
|
964
|
+
const suggestion = closestMatch(ov.moduleSeg, this.moduleSegments());
|
|
965
|
+
this.log(`Override env ignoré : module "${ov.moduleSeg}" introuvable (${ov.envKey})` + (suggestion ? ` — vouliez-vous dire « ${suggestion} » ?` : ""), "WARNING");
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
if (applyResolvedPath(mod.options, ov.path, ov.value, ov.raw, schemaDe(mod))) {
|
|
969
|
+
const appliedValue = readResolvedPath(mod.options, ov.path);
|
|
970
|
+
const shown = pathLooksSecret(ov.path) ? "«***»" : JSON.stringify(appliedValue);
|
|
971
|
+
this.log(`Override env: ${mod.name}.${ov.path.join(".")} = ${shown}`, "INFO");
|
|
972
|
+
} else this.log(`Override env ignoré : chemin "${ov.path.join(".")}" inconnu sur ${mod.name} (${ov.envKey})` + resolveFailureHint(mod.options, ov.path, schemaDe(mod)), "WARNING");
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Filet « clé réservée » : avertit au BOOT quand une application a posé une clé de
|
|
977
|
+
* config marquée `reserved` (inerte — son levier vit ailleurs) à une valeur
|
|
978
|
+
* non-défaut. C'est le pendant runtime du drapeau que Studio se contente de griser :
|
|
979
|
+
* sans lui, une clé réservée écrite reste un silence total (l'app croit régler un
|
|
980
|
+
* comportement, rien ne se produit, rien ne le dit). Symétrique du signalement des
|
|
981
|
+
* chemins env inconnus ({@link applyEnvConfigOverrides}).
|
|
982
|
+
*
|
|
983
|
+
* Générique — 0 `if` par module : le JSON Schema (`Module.configSchema()`, avec les
|
|
984
|
+
* flags `.meta()`) et la config résolue suffisent. Un module non migré (schéma
|
|
985
|
+
* `null`) est ignoré. Appelé 1× après `onRegister` (options validées) → hors hot
|
|
986
|
+
* path ; le message inclut la `description` du champ, qui nomme la remplaçante.
|
|
987
|
+
*/
|
|
988
|
+
warnReservedConfigKeys() {
|
|
989
|
+
for (const name in this.modules) {
|
|
990
|
+
const mod = this.modules[name];
|
|
991
|
+
const schema = mod.configSchema();
|
|
992
|
+
if (!schema) continue;
|
|
993
|
+
const hits = findSetReservedKeys(schema, mod.options);
|
|
994
|
+
for (const hit of hits) this.log(`Clé de config RÉSERVÉE posée sans effet : ${mod.name}.${hit.path} est inerte.${hit.description ? " " + hit.description : ""}`, "WARNING", "CONFIG");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Résout un segment de nom de module (minuscule, issu d'un `NF__<MODULE>__…`)
|
|
999
|
+
* vers son instance : compare au **basename** du nom de module (après le `/`),
|
|
1000
|
+
* insensible à la casse (`security` → `@nodefony/security`).
|
|
1001
|
+
*
|
|
1002
|
+
* @param seg - segment module normalisé (minuscule).
|
|
1003
|
+
* @returns le module correspondant, ou `undefined`.
|
|
1004
|
+
*/
|
|
1005
|
+
findModuleBySegment(seg) {
|
|
1006
|
+
for (const name in this.modules) {
|
|
1007
|
+
const slash = name.lastIndexOf("/");
|
|
1008
|
+
if ((slash >= 0 ? name.slice(slash + 1) : name).toLowerCase() === seg) return this.modules[name];
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Basenames de tous les modules chargés (segment ciblable par `NF__<MODULE>__…`).
|
|
1013
|
+
* Sert au « did you mean » quand le segment module d'un override ne résout pas.
|
|
1014
|
+
*
|
|
1015
|
+
* @returns la liste des basenames (ex. `security`, `http`).
|
|
1016
|
+
*/
|
|
1017
|
+
moduleSegments() {
|
|
1018
|
+
const out = [];
|
|
1019
|
+
for (const name in this.modules) {
|
|
1020
|
+
const slash = name.lastIndexOf("/");
|
|
1021
|
+
out.push(slash >= 0 ? name.slice(slash + 1) : name);
|
|
1022
|
+
}
|
|
1023
|
+
return out;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Valide la config de l'app au boot via le schéma fourni par l'app, si présent.
|
|
1027
|
+
*
|
|
1028
|
+
* Convention générique (le core ne dépend PAS de Zod) : si l'entrée de l'app
|
|
1029
|
+
* (`dist/index.js`) exporte une fonction `validateConfig`, le Kernel l'exécute sur
|
|
1030
|
+
* la config résolue de l'app (`app.options`) — l'app porte le schéma + la dépendance
|
|
1031
|
+
* Zod. Absent → étape sautée (rétro-compatible avec une app sans schéma). L'import
|
|
1032
|
+
* est déjà résolu par `loadModule` (cache ESM) → coût négligeable, hors hot path.
|
|
1033
|
+
*
|
|
1034
|
+
* @throws si le validateur de l'app rejette la config (message agrégé).
|
|
1035
|
+
*/
|
|
1036
|
+
async validateAppConfig() {
|
|
1037
|
+
const entry = this.resolveAppEntry();
|
|
1038
|
+
if (entry === null) return;
|
|
1039
|
+
(await import(toImportSpecifier(entry))).validateConfig?.(this.app?.options);
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Construit le contexte d'environnement (`ctx`) passé au descripteur
|
|
1043
|
+
* {@link defineConfig} d'une app moderne. `runtimeEnv` = `NODE_ENV` normalisé
|
|
1044
|
+
* (`dev`→`development`, conserve `test`/`production`) — granularité que le ctx
|
|
1045
|
+
* expose (`isTest`), DISTINCTE du collapse dev/prod de {@link resolveRuntimeEnv}
|
|
1046
|
+
* (gating moteur : un staging tourne « comme prod »). `appEnv` = axe déploiement
|
|
1047
|
+
* libre (`APP_ENV`/`NF_ENV`). `process.env` est déjà peuplé par `loadEnv`
|
|
1048
|
+
* (bin/nodefony) avant le boot → lecture sûre ici.
|
|
1049
|
+
*
|
|
1050
|
+
* @param env - catalogue env typé exposé par l'app (`export const env = defineEnv(…)`) ;
|
|
1051
|
+
* `undefined` (app legacy / pas de catalogue) → `process.env` brut.
|
|
1052
|
+
* @returns contexte d'environnement pour `descriptor.resolve(ctx)`.
|
|
1053
|
+
*/
|
|
1054
|
+
buildConfigContext(env) {
|
|
1055
|
+
const raw = process.env.NODE_ENV || this.cli?.environment || "development";
|
|
1056
|
+
const runtimeEnv = raw === "dev" ? "development" : raw;
|
|
1057
|
+
const appEnv = process.env.APP_ENV || process.env.NF_ENV || runtimeEnv;
|
|
1058
|
+
return {
|
|
1059
|
+
env: env ?? process.env,
|
|
1060
|
+
infra: this.infra,
|
|
1061
|
+
appEnv,
|
|
1062
|
+
runtimeEnv,
|
|
1063
|
+
isProd: runtimeEnv === "production",
|
|
1064
|
+
isDev: runtimeEnv === "development",
|
|
1065
|
+
isTest: runtimeEnv === "test"
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
/** Infra déclarée mémoïsés — lazy, `null` tant que non lus. */
|
|
1069
|
+
_infra = null;
|
|
1070
|
+
/**
|
|
1071
|
+
* Infra déclarée (`database`/`cache`/`logs`) résolus depuis
|
|
1072
|
+
* `process.env` (modèle « infra déclarée », Phase 0.8). Calcul 1 fois, mémoïsé —
|
|
1073
|
+
* consommé par le ctx `defineConfig` (`ctx.infra`) et par les briques dont
|
|
1074
|
+
* le store vaut `"auto"` (`resolveAutoStore`).
|
|
1075
|
+
*
|
|
1076
|
+
* @throws Error au premier accès si `NF_DATABASE_URL` a un scheme non
|
|
1077
|
+
* supporté (fail-loud : jamais de repli sqlite silencieux).
|
|
1078
|
+
*/
|
|
1079
|
+
get infra() {
|
|
1080
|
+
if (this._infra === null) this._infra = resolveInfra(process.env);
|
|
1081
|
+
return this._infra;
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Registre des résolutions de stores — lazy (`null` tant qu'aucune brique n'a
|
|
1085
|
+
* résolu son store). Alloué au premier `registerStoreResolution` (au boot, une
|
|
1086
|
+
* fois par brique) — jamais dans le hot-path. Clé = `brick` (dernière gagne).
|
|
1087
|
+
*/
|
|
1088
|
+
_storeResolutions = null;
|
|
1089
|
+
/**
|
|
1090
|
+
* Résolutions EFFECTIVES des stores de persistance capturées au boot — la
|
|
1091
|
+
* vérité vécue (replis annoncés inclus). Alimente `/nodefony/kernel/api/stores`
|
|
1092
|
+
* et l'écran Studio « Stores ». Vide si aucune brique n'a encore résolu.
|
|
1093
|
+
*/
|
|
1094
|
+
get storeResolutions() {
|
|
1095
|
+
return this._storeResolutions ? Array.from(this._storeResolutions.values()) : [];
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Enregistre la résolution effective d'une brique (idempotent par `brick`).
|
|
1099
|
+
* La provenance est dérivée de `configured` : la sentinelle `"auto"` ⇒
|
|
1100
|
+
* `"infra"` (résolu depuis l'infra déclarée), un backend nommé ⇒ `"explicit"`.
|
|
1101
|
+
*
|
|
1102
|
+
* @param resolution - triplet brique/configuré/résolu + raison + nature
|
|
1103
|
+
* ({@link IStoreResolution} sans `provenance`, dérivée ici).
|
|
1104
|
+
*/
|
|
1105
|
+
registerStoreResolution(resolution) {
|
|
1106
|
+
if (this._storeResolutions === null) this._storeResolutions = /* @__PURE__ */ new Map();
|
|
1107
|
+
this._storeResolutions.set(resolution.brick, {
|
|
1108
|
+
...resolution,
|
|
1109
|
+
provenance: resolution.configured === "auto" ? "infra" : "explicit"
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Résout les options brutes de l'app. App moderne (`export default defineConfig(…)`)
|
|
1114
|
+
* → `raw` est un descripteur {@link AppConfigDescriptor} (le symbole de marque
|
|
1115
|
+
* survit au spread d'options de `Service`) → résolu avec `ctx` (deep-merge des
|
|
1116
|
+
* défauts framework + validation Zod intégrée à `resolve`). App legacy → objet de
|
|
1117
|
+
* config retourné tel quel (validé séparément par son export `validateConfig`).
|
|
1118
|
+
*
|
|
1119
|
+
* @param raw - export par défaut de l'app (descripteur ou objet de config).
|
|
1120
|
+
* @param ctx - contexte d'environnement ({@link buildConfigContext}).
|
|
1121
|
+
* @returns options résolues + `wasDescriptor` (pilote le fallback de validation).
|
|
1122
|
+
*/
|
|
1123
|
+
resolveAppOptions(raw, ctx) {
|
|
1124
|
+
if (isConfigDescriptor(raw)) {
|
|
1125
|
+
const options = raw.resolve(ctx);
|
|
1126
|
+
this.surfaceAppEnvOverrides(options);
|
|
1127
|
+
return {
|
|
1128
|
+
options,
|
|
1129
|
+
wasDescriptor: true
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
return {
|
|
1133
|
+
options: extend(true, {}, defaultAppConfig, raw ?? {}),
|
|
1134
|
+
wasDescriptor: false
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Émet au log le rapport des overrides `NF__APP__*` appliqués à la config app
|
|
1139
|
+
* (INFO par override, secret rédigé) + les warnings « did you mean » des chemins
|
|
1140
|
+
* non résolus. Appelé APRÈS `resolve()` : le logger kernel est prêt (le merge,
|
|
1141
|
+
* lui, tourne dans `resolve()` AVANT le logger → rapport différé, cf defineConfig).
|
|
1142
|
+
*
|
|
1143
|
+
* @param options - config app résolue (porte le rapport en clé non-énumérable).
|
|
1144
|
+
*/
|
|
1145
|
+
surfaceAppEnvOverrides(options) {
|
|
1146
|
+
const report = readAppEnvOverrideReport(options);
|
|
1147
|
+
if (!report) return;
|
|
1148
|
+
for (const ov of report.applied) this.log(`Override env app: ${ov.path.join(".")} = ${ov.secret ? "«***»" : "(env)"}`, "INFO");
|
|
1149
|
+
for (const w of report.warnings) this.log(w, "WARNING");
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Construit + PRÉSENTE une erreur de configuration de boot de façon
|
|
1153
|
+
* EXCEPTIONNELLEMENT claire, et EXPLICITE sur la config PAR DÉFAUT du framework
|
|
1154
|
+
* (ce qui s'applique pour tout champ omis). Une config cassée n'est pas
|
|
1155
|
+
* récupérable (le framework ne peut pas deviner ports/modules) → fail-fast PROPRE :
|
|
1156
|
+
* log lisible SANS stack brute (faute de config, pas bug framework), erreur marquée
|
|
1157
|
+
* `presented` (le catch de boot ne re-loggue pas) + `exitCode` `EX_CONFIG`
|
|
1158
|
+
* (l'orchestrateur distingue « mauvaise config » d'un crash logiciel).
|
|
1159
|
+
*
|
|
1160
|
+
* @param title - titre court de l'erreur.
|
|
1161
|
+
* @param detail - phrase de contexte.
|
|
1162
|
+
* @param cause - erreur d'origine (Zod, import, fonction de config).
|
|
1163
|
+
* @param hints - correctifs actionnables.
|
|
1164
|
+
* @returns nodefonyError marquée (`exitCode`/`presented`), à `throw`.
|
|
1165
|
+
*/
|
|
1166
|
+
bootConfigError(title, detail, cause, hints) {
|
|
1167
|
+
const causeMsg = cause instanceof Error ? cause.message : String(cause);
|
|
1168
|
+
const lines = [
|
|
1169
|
+
"",
|
|
1170
|
+
` ✖ ${title}`,
|
|
1171
|
+
` ${detail}`,
|
|
1172
|
+
` Cause : ${causeMsg}`,
|
|
1173
|
+
"",
|
|
1174
|
+
" Configuration PAR DÉFAUT du framework (appliquée à tout champ omis) :",
|
|
1175
|
+
this.formatDefaults(),
|
|
1176
|
+
"",
|
|
1177
|
+
" Pour corriger :",
|
|
1178
|
+
...hints.map((h) => ` → ${h}`),
|
|
1179
|
+
""
|
|
1180
|
+
];
|
|
1181
|
+
this.log(lines.join("\n"), "CRITIC");
|
|
1182
|
+
const err = new nodefonyError(`${title} : ${causeMsg}`);
|
|
1183
|
+
err.exitCode = 78;
|
|
1184
|
+
err.presented = true;
|
|
1185
|
+
return err;
|
|
1186
|
+
}
|
|
1187
|
+
/** Rendu lisible (1 ligne/clé top-level) des valeurs par défaut du framework. */
|
|
1188
|
+
formatDefaults() {
|
|
1189
|
+
return Object.entries(defaultAppConfig).map(([k, v]) => {
|
|
1190
|
+
return ` • ${k} = ${v !== null && typeof v === "object" ? JSON.stringify(v) : String(v)}`;
|
|
1191
|
+
}).join("\n");
|
|
1192
|
+
}
|
|
1193
|
+
async loadApp(config) {
|
|
1194
|
+
const appEntry = this.resolveAppEntry() ?? `${this.path}/dist/index.js`;
|
|
1195
|
+
try {
|
|
1196
|
+
this.app = await this.loadModule(appEntry);
|
|
1197
|
+
} catch (e) {
|
|
1198
|
+
throw this.bootConfigError("Chargement de l'application impossible", `Le point d'entrée \`${appEntry}\` n'a pas pu être importé/évalué.`, e, ["Build périmé ou absent → `npm run clean && npm run build`.", "Un fichier de config déréférence le kernel au top-level (résolu à l'import) → différer en getter/lazy."]);
|
|
1199
|
+
}
|
|
1200
|
+
this.app.isApp = true;
|
|
1201
|
+
const appModule = await import(toImportSpecifier(appEntry));
|
|
1202
|
+
const ctx = this.buildConfigContext(appModule.env);
|
|
1203
|
+
let wasDescriptor = false;
|
|
1204
|
+
try {
|
|
1205
|
+
const resolved = this.resolveAppOptions(this.app.options, ctx);
|
|
1206
|
+
this.app.options = resolved.options;
|
|
1207
|
+
wasDescriptor = resolved.wasDescriptor;
|
|
1208
|
+
} catch (e) {
|
|
1209
|
+
throw this.bootConfigError("Configuration de l'application invalide", "La résolution de la configuration (`defineConfig`) a échoué.", e, ["Vérifie les types/valeurs des champs signalés dans `nodefony.config.ts`.", "Une fonction `defineConfig((ctx) => …)` qui lève → corrige la logique par-env."]);
|
|
1210
|
+
}
|
|
1211
|
+
this.options = this.readConfig(extend(this.app.options, config));
|
|
1212
|
+
if (!wasDescriptor) try {
|
|
1213
|
+
await this.validateAppConfig();
|
|
1214
|
+
} catch (e) {
|
|
1215
|
+
throw this.bootConfigError("Configuration de l'application invalide", "Le schéma de validation de l'app a rejeté la configuration.", e, ["Corrige les champs signalés ci-dessus.", "Les valeurs par défaut du framework listées s'appliquent aux champs omis."]);
|
|
1216
|
+
}
|
|
1217
|
+
if (Array.isArray(this.options.modules) && this.options.modules.length) this.once("onPreRegister", async () => {
|
|
1218
|
+
await this.loadModulesFromManifest();
|
|
1219
|
+
});
|
|
1220
|
+
this.initializeLog();
|
|
1221
|
+
this.cli?.setPackageManager(this.options.packageManager);
|
|
1222
|
+
this.core = await this.isCore();
|
|
1223
|
+
this.app.package = await this.app.getPackageJson();
|
|
1224
|
+
this.version = this.app?.getModuleVersion();
|
|
1225
|
+
this.fixCommanderCli();
|
|
1226
|
+
this.cli?.setCommandVersion(this.version);
|
|
1227
|
+
await this.fireAsync("onAppLoad", this.app).catch((e) => {
|
|
1228
|
+
throw e;
|
|
1229
|
+
});
|
|
1230
|
+
return this.app;
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Résout le point d'entrée de l'application depuis SON `package.json`
|
|
1234
|
+
* (champ `main` — la source de vérité Node), fallback legacy `dist/index.js`
|
|
1235
|
+
* puis `index.js`. Vérifie l'EXISTENCE du fichier, sans jamais l'importer :
|
|
1236
|
+
* l'exécution (side effects top-level, cache ESM) et la validation du module
|
|
1237
|
+
* (export default = `Module`) appartiennent à {@link loadApp}, une seule
|
|
1238
|
+
* fois, avec son diagnostic `bootConfigError`. Mémoïsé (stable pour le boot).
|
|
1239
|
+
*
|
|
1240
|
+
* Anti faux-positif : un projet Node QUELCONQUE (Express…) a aussi un
|
|
1241
|
+
* `package.json` + `main` → on exige le signal d'identité Nodefony, sans
|
|
1242
|
+
* import : `nodefony` déclaré dans les dépendances, OU installé
|
|
1243
|
+
* (`node_modules/nodefony` — couvre le monorepo self-hosted où la racine ne
|
|
1244
|
+
* le déclare pas mais où le workspace le symlinke).
|
|
1245
|
+
*
|
|
1246
|
+
* En production (image Docker), seuls `package.json` + `dist/` +
|
|
1247
|
+
* `node_modules` sont déployés : la détection ne dépend d'AUCUN source.
|
|
1248
|
+
*
|
|
1249
|
+
* @returns chemin absolu de l'entrée, ou `null` (cwd hors projet Nodefony).
|
|
1250
|
+
*/
|
|
1251
|
+
resolveAppEntry() {
|
|
1252
|
+
if (this._appEntry !== void 0) return this._appEntry;
|
|
1253
|
+
let pkg;
|
|
1254
|
+
try {
|
|
1255
|
+
pkg = JSON.parse(fs.readFileSync(path.resolve(this.path, "package.json"), "utf8"));
|
|
1256
|
+
} catch {
|
|
1257
|
+
this._appEntry = null;
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
if (!Boolean(pkg.dependencies?.nodefony ?? pkg.devDependencies?.nodefony ?? pkg.peerDependencies?.nodefony) && !fs.existsSync(path.resolve(this.path, "node_modules", "nodefony"))) {
|
|
1261
|
+
this._appEntry = null;
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
for (const candidate of pkg.main ? [pkg.main] : ["dist/index.js", "index.js"]) {
|
|
1265
|
+
const abs = path.resolve(this.path, candidate);
|
|
1266
|
+
if (fs.existsSync(abs)) {
|
|
1267
|
+
this._appEntry = abs;
|
|
1268
|
+
return abs;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
this._appEntry = null;
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
async isTrunk() {
|
|
1275
|
+
const entry = this.resolveAppEntry();
|
|
1276
|
+
if (entry === null) return null;
|
|
1277
|
+
return entry.includes(`${path.sep}dist${path.sep}`) ? "typescript" : "javascript";
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Diagnostic fail-loud d'un PROJET Nodefony non bootable — différencie les
|
|
1281
|
+
* trois raisons qu'un `resolveAppEntry()` nul confond : « vraiment pas un
|
|
1282
|
+
* projet » (→ `null`, wizard/message générique), « dépendances non
|
|
1283
|
+
* installées » et « non construit ». Sans lui, un `nodefony dev` dans une
|
|
1284
|
+
* app fraîchement générée tombait sur le wizard de création — déroutant
|
|
1285
|
+
* alors qu'il ne manque que `npm install` / `npm run build` (vécu).
|
|
1286
|
+
*
|
|
1287
|
+
* @returns le message actionnable à logger CRITIC, ou `null` si ce dossier
|
|
1288
|
+
* n'est pas un projet Nodefony (le flux hors-projet reste inchangé)
|
|
1289
|
+
*/
|
|
1290
|
+
diagnoseUnbootableProject() {
|
|
1291
|
+
let pkg;
|
|
1292
|
+
try {
|
|
1293
|
+
pkg = JSON.parse(fs.readFileSync(path.resolve(this.path, "package.json"), "utf8"));
|
|
1294
|
+
} catch {
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
if (!Boolean(pkg.dependencies?.nodefony ?? pkg.devDependencies?.nodefony ?? pkg.peerDependencies?.nodefony)) return null;
|
|
1298
|
+
if (!fs.existsSync(path.resolve(this.path, "node_modules"))) return `Projet Nodefony détecté (${this.path}) mais dépendances NON INSTALLÉES.\nLance :\n npm install\n npm run build\npuis relance ta commande.`;
|
|
1299
|
+
return `Projet Nodefony détecté (${this.path}) mais NON CONSTRUIT (aucune entrée d'app : package.json \`main\`, dist/index.js ou index.js).\nLance :\n npm run build\npuis relance ta commande.`;
|
|
1300
|
+
}
|
|
1301
|
+
async isCore() {
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
isModule(subclass) {
|
|
1305
|
+
return isSubclassOf(subclass, Module);
|
|
1306
|
+
}
|
|
1307
|
+
getEventName(event) {
|
|
1308
|
+
return Object.keys(Events).find((key) => Events[key] === event);
|
|
1309
|
+
}
|
|
1310
|
+
setCommandComplete(progress) {
|
|
1311
|
+
const index = this.getEventName(progress);
|
|
1312
|
+
this.progress |= Events[index];
|
|
1313
|
+
return this.isCommandComplete(progress);
|
|
1314
|
+
}
|
|
1315
|
+
isCommandComplete(progress) {
|
|
1316
|
+
const index = this.getEventName(progress);
|
|
1317
|
+
if (this.command) {
|
|
1318
|
+
const int = Events[this.command.kernelEvent];
|
|
1319
|
+
const res = !!(this.progress & int);
|
|
1320
|
+
this.log(`Ckeck Command event : ${this.getEventName(int)} Progress: ${index} : Complete : ${res}`, "DEBUG", `COMMAND ${this.command.name}`);
|
|
1321
|
+
return res;
|
|
1322
|
+
}
|
|
1323
|
+
return false;
|
|
1324
|
+
}
|
|
1325
|
+
initializeLog() {
|
|
1326
|
+
this.syslog?.removeAllListeners();
|
|
1327
|
+
if (this._mountedLogTransports !== null) {
|
|
1328
|
+
for (const t of this._mountedLogTransports) this.syslog?.removeTransport(t);
|
|
1329
|
+
this._mountedLogTransports = null;
|
|
1330
|
+
}
|
|
1331
|
+
if (this.options.log && !this.options.log.active) return;
|
|
1332
|
+
const logCfg = this.options.log;
|
|
1333
|
+
Syslog.setOutputBuffering(logCfg?.buffered ?? "auto");
|
|
1334
|
+
setLogColor(resolveColorEnabled(this.isTTY));
|
|
1335
|
+
const logDirAbs = path.resolve(process.cwd(), logCfg?.dir ?? "logs");
|
|
1336
|
+
const logDriver = logCfg?.driver ?? "stdout";
|
|
1337
|
+
if (logDriver === "file") {
|
|
1338
|
+
const logPath = logCfg?.file?.path ?? path.join(logDirAbs, `nodefony-${process.pid}.log`);
|
|
1339
|
+
Syslog.setLogSink(new FileSink({
|
|
1340
|
+
path: logPath,
|
|
1341
|
+
sync: logCfg?.file?.sync
|
|
1342
|
+
}));
|
|
1343
|
+
} else if (logDriver === "null") Syslog.setLogSink(NULL_LOG_SINK);
|
|
1344
|
+
else Syslog.setLogSink(null);
|
|
1345
|
+
const maxStack = logCfg?.maxStack ?? (this.environment === "development" ? 2e3 : void 0);
|
|
1346
|
+
if (maxStack) this.syslog?.setMaxStack(maxStack);
|
|
1347
|
+
const queryDriver = resolveQueryDriver(logCfg?.queryDriver, cluster.isWorker, {
|
|
1348
|
+
loki: logCfg?.loki?.url,
|
|
1349
|
+
opensearch: logCfg?.opensearch?.url
|
|
1350
|
+
});
|
|
1351
|
+
registerBuiltinLogDrivers();
|
|
1352
|
+
const driverCtx = {
|
|
1353
|
+
logCfg,
|
|
1354
|
+
environment: this.environment ?? "development",
|
|
1355
|
+
logDir: logDirAbs,
|
|
1356
|
+
pid: process.pid,
|
|
1357
|
+
getRingStack: () => this.syslog?.ringStack ?? []
|
|
1358
|
+
};
|
|
1359
|
+
const toMount = /* @__PURE__ */ new Set(["memory", queryDriver]);
|
|
1360
|
+
if (this.environment === "development") for (const name of listLogDriverFactories()) toMount.add(name);
|
|
1361
|
+
const addedWrites = /* @__PURE__ */ new Set();
|
|
1362
|
+
for (const name of toMount) {
|
|
1363
|
+
const factory = getLogDriverFactory(name);
|
|
1364
|
+
if (factory === void 0) continue;
|
|
1365
|
+
const mount = factory(driverCtx);
|
|
1366
|
+
if (mount === null) continue;
|
|
1367
|
+
registerLogDriver(mount.driver);
|
|
1368
|
+
if (mount.transport !== void 0) {
|
|
1369
|
+
const key = mount.writeKey;
|
|
1370
|
+
if (key === void 0 || !addedWrites.has(key)) {
|
|
1371
|
+
this.syslog?.addTransport(mount.transport);
|
|
1372
|
+
(this._mountedLogTransports ??= []).push(mount.transport);
|
|
1373
|
+
if (key !== void 0) addedWrites.add(key);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
try {
|
|
1378
|
+
setActiveLogDriver(queryDriver);
|
|
1379
|
+
} catch {
|
|
1380
|
+
this.syslog?.log(`Log query driver "${queryDriver}" introuvable — fallback "memory".`, "WARNING", "SYSLOG");
|
|
1381
|
+
setActiveLogDriver("memory");
|
|
1382
|
+
}
|
|
1383
|
+
if (!this.cli && !this.debug && this.options.log?.debug) this.debug = this.options.log.debug;
|
|
1384
|
+
this.syslog?.setSeverityThreshold(this.environment === "production" && !this.debug ? "INFO" : null);
|
|
1385
|
+
const debugSpec = process.env.NF__DEBUG;
|
|
1386
|
+
if (debugSpec && this.syslog) {
|
|
1387
|
+
const { global, overrides } = Syslog.parseDebugSpec(debugSpec);
|
|
1388
|
+
if (global) this.syslog.setSeverityThreshold(null);
|
|
1389
|
+
for (const ov of overrides) this.syslog.setDebugOverride(ov.module, ov.level);
|
|
1390
|
+
}
|
|
1391
|
+
if (this.cli) return this.cli.initSyslog(this.environment, this.debug);
|
|
1392
|
+
else return this.initSyslog(this.environment, this.debug);
|
|
1393
|
+
}
|
|
1394
|
+
setCli(cli) {
|
|
1395
|
+
if (cli) {
|
|
1396
|
+
this.runProfile = cli.runProfile;
|
|
1397
|
+
this.debug = Boolean(cli?.commander?.opts().debug) || false;
|
|
1398
|
+
if (this.typeCluster === "worker") cli.setPid();
|
|
1399
|
+
this.set("cli", cli);
|
|
1400
|
+
return cli;
|
|
1401
|
+
}
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
isConsole() {
|
|
1405
|
+
return this.runProfile ? !this.runProfile.servers : false;
|
|
1406
|
+
}
|
|
1407
|
+
setNodeEnv(environment) {
|
|
1408
|
+
if (environment) switch (environment) {
|
|
1409
|
+
case "dev":
|
|
1410
|
+
case "development":
|
|
1411
|
+
process.env.NODE_ENV = "development";
|
|
1412
|
+
process.env.BABEL_ENV = "development";
|
|
1413
|
+
this.isDev = true;
|
|
1414
|
+
this.isProd = false;
|
|
1415
|
+
break;
|
|
1416
|
+
default:
|
|
1417
|
+
process.env.NODE_ENV = "production";
|
|
1418
|
+
process.env.BABEL_ENV = "production";
|
|
1419
|
+
this.isProd = true;
|
|
1420
|
+
this.isDev = false;
|
|
1421
|
+
}
|
|
1422
|
+
process.env.NODE_DEBUG = this.debug ? "true" : "false";
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Résout le **mode runtime** (dev/prod) : `NODE_ENV`
|
|
1426
|
+
* (ambient, posé par l'orchestrateur en cloud) PRIME sur l'intention de la
|
|
1427
|
+
* commande locale, qui prime sur la valeur courante. Normalise en
|
|
1428
|
+
* `"development" | "production"`. Pur (lit `process.env`, n'écrit rien) → sûr à
|
|
1429
|
+
* appeler avant que `setNodeEnv` n'ait posé `NODE_ENV` (ex. gating de modules à
|
|
1430
|
+
* `onPreRegister`). Cf project_app_config_refonte_chantier (deux axes d'env).
|
|
1431
|
+
*/
|
|
1432
|
+
resolveRuntimeEnv(fromCommand) {
|
|
1433
|
+
const raw = process.env.NODE_ENV || fromCommand || this.environment || "development";
|
|
1434
|
+
return engineModeOf(raw);
|
|
1435
|
+
}
|
|
1436
|
+
setEnv(environment) {
|
|
1437
|
+
const runtime = this.resolveRuntimeEnv(environment);
|
|
1438
|
+
this.environment = runtime;
|
|
1439
|
+
this.appEnvironment.environment = process.env.APP_ENV || process.env.NF_ENV || runtime;
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Header de boot dev consolidé, imprimé juste sous l'ASCII (dev-only) : nom +
|
|
1443
|
+
* version + environnement + meta (`cluster · platform · node · pid`). Remplace la
|
|
1444
|
+
* ligne « Version … » et le banner « SERVER … » bruts. Couleurs gatées TTY (logColor).
|
|
1445
|
+
*/
|
|
1446
|
+
printDevHeader() {
|
|
1447
|
+
let version = "";
|
|
1448
|
+
try {
|
|
1449
|
+
const v = this.cli?.commander?.version();
|
|
1450
|
+
if (typeof v === "string") version = v;
|
|
1451
|
+
} catch {}
|
|
1452
|
+
const meta = [
|
|
1453
|
+
String(this.typeCluster ?? ""),
|
|
1454
|
+
process.platform,
|
|
1455
|
+
`node ${process.version}`,
|
|
1456
|
+
`pid ${process.pid}`,
|
|
1457
|
+
`tty ${this.isTTY ? "yes" : "no"}`
|
|
1458
|
+
].filter(Boolean).join(" · ");
|
|
1459
|
+
const tag = version ? ` ${logColor.blackBright(`v${version}`)}` : "";
|
|
1460
|
+
const env = this.environment ? ` ${logColor.green(String(this.environment))}` : "";
|
|
1461
|
+
const appEnv = process.env.APP_ENV || process.env.NF_ENV;
|
|
1462
|
+
const deploy = appEnv && appEnv !== String(this.environment) ? ` ${logColor.blackBright("·")} ${logColor.magenta(appEnv)}` : "";
|
|
1463
|
+
console.log(` ${logColor.cyan("⬢")} ${logColor.cyanBold("Nodefony")}${tag}${env}${deploy}`);
|
|
1464
|
+
console.log(` ${logColor.blackBright(meta)}\n`);
|
|
1465
|
+
}
|
|
1466
|
+
logEnv() {
|
|
1467
|
+
if (this.cli) this.runProfile = this.cli.runProfile;
|
|
1468
|
+
const profileLabel = this.runProfile.servers ? "server" : "console";
|
|
1469
|
+
let txt = ` \x1b ${logColor.blue(profileLabel)} `;
|
|
1470
|
+
txt += ` ${logColor.magenta("Cluster")} : ${this.typeCluster} `;
|
|
1471
|
+
txt += ` ${logColor.magenta("Nodefony Environment")} : ${this.environment} `;
|
|
1472
|
+
if (this.appEnvironment) txt += ` ${logColor.magenta("App Environment")} : ${this.appEnvironment.environment} `;
|
|
1473
|
+
txt += ` ${logColor.magenta("Debug")} : ${this.debug}\n`;
|
|
1474
|
+
return txt;
|
|
1475
|
+
}
|
|
1476
|
+
clusterIsMaster() {
|
|
1477
|
+
return cluster.isPrimary;
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Vide le buffer des `MODULE ADD` accumulés pendant `loadApp()` et bascule
|
|
1481
|
+
* `addModule()` en mode log immédiat. Appelé par `initCluster()` juste
|
|
1482
|
+
* après l'en-tête SERVER, pour que les modules apparaissent SOUS le banner.
|
|
1483
|
+
*/
|
|
1484
|
+
flushPendingModuleAddLogs() {
|
|
1485
|
+
if (!this.pendingModuleAddLogs) return;
|
|
1486
|
+
const pending = this.pendingModuleAddLogs;
|
|
1487
|
+
this.pendingModuleAddLogs = null;
|
|
1488
|
+
for (const name of pending) this.log(`MODULE ADD : ${name}`, "INFO");
|
|
1489
|
+
}
|
|
1490
|
+
initCluster() {
|
|
1491
|
+
this.pid = process.pid;
|
|
1492
|
+
if (this.console && this.cli && this.cli.commander && this.cli.commander.opts().json) return;
|
|
1493
|
+
if (cluster.isPrimary) {
|
|
1494
|
+
if (!this.reporterOwnsHeader && !this.cli?.quietBoot) console.log(this.logEnv());
|
|
1495
|
+
this.flushPendingModuleAddLogs();
|
|
1496
|
+
this.fire("onCluster", "MASTER", this, process);
|
|
1497
|
+
} else if (cluster.isWorker) {
|
|
1498
|
+
if (!this.reporterOwnsHeader && !this.cli?.quietBoot) console.log(this.logEnv());
|
|
1499
|
+
this.flushPendingModuleAddLogs();
|
|
1500
|
+
this.workerId = cluster.worker?.id;
|
|
1501
|
+
this.worker = cluster.worker;
|
|
1502
|
+
this.fire("onCluster", "WORKER", this, process);
|
|
1503
|
+
process.on("message", (msg) => {
|
|
1504
|
+
if (isClusterMessage(msg) && (msg.kind === "nf:rt" || msg.kind === "nf:probe:snap")) return;
|
|
1505
|
+
this.log(msg, "INFO", "IPC MESSAGE");
|
|
1506
|
+
this.fire("onMessage", msg);
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
fire(event, ...args) {
|
|
1511
|
+
this.log(`${colorLogEvent()} ${event}`, "DEBUG");
|
|
1512
|
+
return super.fire(event, ...args);
|
|
1513
|
+
}
|
|
1514
|
+
emit(event, ...args) {
|
|
1515
|
+
this.log(`${colorLogEvent()} ${event}`, "DEBUG");
|
|
1516
|
+
return super.emit(event, ...args);
|
|
1517
|
+
}
|
|
1518
|
+
emitAsync(event, ...args) {
|
|
1519
|
+
this.log(`${colorLogEvent()} ${event}`, "DEBUG");
|
|
1520
|
+
return super.emitAsync(event, ...args);
|
|
1521
|
+
}
|
|
1522
|
+
fireAsync(event, ...args) {
|
|
1523
|
+
this.log(`${colorLogEvent()} ${event}`, "DEBUG");
|
|
1524
|
+
return super.emitAsync(event, ...args);
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Timeout par listener appliqué au boot (résilience Phase 3). Précédence :
|
|
1528
|
+
* `NF_BOOT_TIMEOUT_MS` (env, orchestrateur) > défaut par env (dev 20 s,
|
|
1529
|
+
* prod 60 s). Large à dessein : il borne la PENDAISON infinie (ex. file Redis
|
|
1530
|
+
* offline qui ne rejette jamais), pas la lenteur normale d'un hook.
|
|
1531
|
+
*/
|
|
1532
|
+
bootTimeoutMs() {
|
|
1533
|
+
const env = Number(process.env.NF_BOOT_TIMEOUT_MS);
|
|
1534
|
+
if (Number.isFinite(env) && env > 0) return env;
|
|
1535
|
+
return this.environment === "production" ? 6e4 : 2e4;
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Seuil d'alerte de lenteur d'un hook de boot (NOTICE, sans le tuer). Précédence :
|
|
1539
|
+
* `NF_BOOT_WARN_MS` (env) > défaut 5 s. `0` désactive la mesure.
|
|
1540
|
+
*/
|
|
1541
|
+
bootWarnMs() {
|
|
1542
|
+
const env = Number(process.env.NF_BOOT_WARN_MS);
|
|
1543
|
+
if (Number.isFinite(env) && env >= 0) return env;
|
|
1544
|
+
return 5e3;
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Politique d'échec de boot commune (lifecycle + `initialize`). Log + verdict
|
|
1548
|
+
* de propagation. **Fatal** = module critique (tag `critical !== false`) ET
|
|
1549
|
+
* (`production` OU erreur de **configuration** {@link BootConfigurationError})
|
|
1550
|
+
* → on interrompt le boot (le pod crashe, l'orchestrateur le redémarre —
|
|
1551
|
+
* cloud-native). Sinon **fail-soft** : WARNING, le boot continue.
|
|
1552
|
+
*
|
|
1553
|
+
* **Pourquoi la config est fatale même en dev** : le fail-soft protège la DX
|
|
1554
|
+
* d'un module optionnel cassé — mais une configuration EXPLICITE non
|
|
1555
|
+
* honorable (infra déclarée injoignable, entité non portée sur le dialecte
|
|
1556
|
+
* demandé) ne se répare pas en continuant : le serveur « vivant » qui en
|
|
1557
|
+
* résulte est un piège (briques durables mortes, login impossible, cause
|
|
1558
|
+
* noyée dans un WARNING — vécu). Cf {@link BootConfigurationError}.
|
|
1559
|
+
*
|
|
1560
|
+
* @param error - erreur/timeout capturé.
|
|
1561
|
+
* @param owner - module propriétaire (tag), ou `undefined` (listener interne).
|
|
1562
|
+
* @param critical - criticité (tag) ; `undefined` → traité comme critique.
|
|
1563
|
+
* @param timedOut - `true` si l'échec est un dépassement de timeout.
|
|
1564
|
+
* @returns `true` si l'échec doit interrompre le boot.
|
|
1565
|
+
*/
|
|
1566
|
+
isBootErrorFatal(error, owner, critical, timedOut, phase) {
|
|
1567
|
+
const who = owner ?? "(anonyme)";
|
|
1568
|
+
const configError = BootConfigurationError.is(error);
|
|
1569
|
+
const heldBy = this.#readinessHold();
|
|
1570
|
+
const fatal = configError || critical !== false && this.environment === "production" && heldBy === null;
|
|
1571
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1572
|
+
const tag = timedOut ? " [timeout]" : "";
|
|
1573
|
+
this.log(`boot lifecycle: ${fatal ? configError ? "erreur de CONFIGURATION (boot interrompu)" : "échec critique" : "échec non bloquant (fail-soft)"} de "${who}"${tag} — ${msg}`, fatal ? "ERROR" : "WARNING");
|
|
1574
|
+
if (this.debug && error instanceof Error && error.stack) this.log(error.stack, "DEBUG");
|
|
1575
|
+
if (!fatal && critical !== false) this.log(`boot lifecycle: en production, cet échec de "${who}" INTERROMPRAIT le boot` + (critical === void 0 ? " — ce hook ne porte aucun tag de criticité, et un hook non tagué est traité comme CRITIQUE. S'il vient d'un module (ou d'un de ses services), le poser avec `module.hookKernel(\"<event>\", …)` plutôt que `kernel.once(…)` : il héritera du nom et de la criticité du module. `static critical = false` ne couvre QUE les hooks de classe — un hook posé à la main y échappe." : `.`), "WARNING");
|
|
1576
|
+
if (!fatal && heldBy !== null) this.log(`boot lifecycle: l'échec de "${who}" n'interrompt PAS le boot — la mise en service est retenue par ${heldBy}. Cet exemplaire reste vivant et ne reçoit aucun trafic (/readyz rend 503) ; il attend le geste qui lèvera la rétention.`, "WARNING");
|
|
1577
|
+
if (!fatal) this.recordBootFailure({
|
|
1578
|
+
module: who,
|
|
1579
|
+
reason: msg,
|
|
1580
|
+
phase,
|
|
1581
|
+
timedOut
|
|
1582
|
+
});
|
|
1583
|
+
return fatal;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Qui retient la mise en service, en clair — ou `null` si rien ne la retient.
|
|
1587
|
+
*
|
|
1588
|
+
* Lu au moment d'un échec de boot pour décider si l'exemplaire doit mourir.
|
|
1589
|
+
* Ne consulte que l'état DÉJÀ calculé par le registre : aucun appel réseau,
|
|
1590
|
+
* aucune sonde déclenchée ici.
|
|
1591
|
+
*
|
|
1592
|
+
* @returns les contributeurs qui retiennent, séparés par « ; », ou `null`.
|
|
1593
|
+
*/
|
|
1594
|
+
#readinessHold() {
|
|
1595
|
+
const bloquants = this.readinessReport().filter((c) => !c.ready);
|
|
1596
|
+
if (bloquants.length === 0) return null;
|
|
1597
|
+
return bloquants.map((c) => c.reason ? `${c.name} (${c.reason})` : c.name).join(" ; ");
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Enregistre un échec de boot **non fatal** (fail-soft) dans le buffer agrégé,
|
|
1601
|
+
* alloué paresseusement au premier échec (0 allocation si le boot est nominal).
|
|
1602
|
+
*
|
|
1603
|
+
* @param failure - module en échec + raison + étape.
|
|
1604
|
+
*/
|
|
1605
|
+
recordBootFailure(failure) {
|
|
1606
|
+
(this.bootFailures ??= []).push(failure);
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Fige la liste des serveurs **réellement en écoute** à `onPostReady`
|
|
1610
|
+
* (`initServers()` ne retourne un serveur qu'une fois son `listen()` résolu).
|
|
1611
|
+
* Source de vérité de {@link getBootReport}.
|
|
1612
|
+
*
|
|
1613
|
+
* @param servers - instances de serveurs retournées par `initServers()`.
|
|
1614
|
+
*/
|
|
1615
|
+
captureBootServers(servers) {
|
|
1616
|
+
if (!servers.length) {
|
|
1617
|
+
this.bootServers = [];
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
this.bootServers = servers.map((s) => {
|
|
1621
|
+
const type = typeof s?.type === "string" ? s.type : "server";
|
|
1622
|
+
const port = Number(s?.port ?? 0);
|
|
1623
|
+
const address = typeof s?.address === "string" ? s.address : void 0;
|
|
1624
|
+
const scheme = SERVER_SCHEME[type] ?? type;
|
|
1625
|
+
return {
|
|
1626
|
+
type,
|
|
1627
|
+
scheme,
|
|
1628
|
+
port,
|
|
1629
|
+
address,
|
|
1630
|
+
url: `${scheme}://${address ? address.includes(":") ? `[${address}]` : address : "127.0.0.1"}:${port}`
|
|
1631
|
+
};
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Verdict agrégé du dernier boot (vérité unique, recalculable à la demande pour
|
|
1636
|
+
* l'introspection Studio/IA). `healthy=false` ⇒ un profil serveur a fini sans
|
|
1637
|
+
* aucun serveur en écoute (garde-fou 0-serveur). Les modules ignorés seuls
|
|
1638
|
+
* laissent le boot `healthy` (dégradé mais vivant).
|
|
1639
|
+
*
|
|
1640
|
+
* @returns le {@link IBootReport}.
|
|
1641
|
+
*/
|
|
1642
|
+
getBootReport() {
|
|
1643
|
+
const measured = this.bootServers !== null;
|
|
1644
|
+
const serversListening = this.bootServers ?? [];
|
|
1645
|
+
const serversExpected = Boolean(this.runProfile?.servers);
|
|
1646
|
+
const modulesSkipped = this.bootFailures ?? [];
|
|
1647
|
+
const { warnings, errors, criticals } = this.bootLogCounts ?? this.countBootLogIssues();
|
|
1648
|
+
const manifestEntries = Array.isArray(this.options.modules) ? this.options.modules.length : 0;
|
|
1649
|
+
return {
|
|
1650
|
+
durationMs: this.bootStartedAt > 0 ? Date.now() - this.bootStartedAt : 0,
|
|
1651
|
+
modulesLoaded: Object.keys(this.modules),
|
|
1652
|
+
manifestEntries,
|
|
1653
|
+
modulesSkipped,
|
|
1654
|
+
modulesGated: this.modulesGated ?? [],
|
|
1655
|
+
warnings,
|
|
1656
|
+
errors,
|
|
1657
|
+
criticals,
|
|
1658
|
+
serversExpected,
|
|
1659
|
+
serversListening,
|
|
1660
|
+
healthy: !(serversExpected && measured && serversListening.length === 0),
|
|
1661
|
+
remediation: this.bootRemediationHint(modulesSkipped, manifestEntries, serversExpected) ?? void 0
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Nombre de composants qui retiennent la mise en service — `0` = disponible.
|
|
1666
|
+
*
|
|
1667
|
+
* C'est ce que lit la sonde `/readyz`, et c'est pour elle que ce membre est un
|
|
1668
|
+
* ENTIER déjà calculé : une comparaison, aucune allocation, aucun `await`,
|
|
1669
|
+
* aucune vérification déclenchée. Une sonde qui interrogerait la base tomberait
|
|
1670
|
+
* avec elle ; celle-ci lit un verdict que le contributeur a posé lui-même.
|
|
1671
|
+
*
|
|
1672
|
+
* @returns le nombre de contributeurs non prêts (`0` quand personne n'est inscrit)
|
|
1673
|
+
*/
|
|
1674
|
+
/**
|
|
1675
|
+
* Signature du dernier état publié sur le disque — `null` tant que rien ne
|
|
1676
|
+
* l'a été. Sert UNIQUEMENT à ne pas réécrire un fichier identique.
|
|
1677
|
+
*/
|
|
1678
|
+
#readinessPublished = null;
|
|
1679
|
+
get readinessBlocked() {
|
|
1680
|
+
return this.readiness === null ? 0 : this.readiness.blocked;
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Ce processus a-t-il le droit de recevoir du trafic ?
|
|
1684
|
+
*
|
|
1685
|
+
* **La règle de mise en service, en UN seul endroit.** Elle est lue par la
|
|
1686
|
+
* sonde `/readyz` (que le kubelet interroge) ET par le champ `ready` du plan
|
|
1687
|
+
* d'administration (que lit un exploitant) : deux vérités sur la
|
|
1688
|
+
* disponibilité, c'en est une de trop — quelqu'un qui voit `ready: true` d'un
|
|
1689
|
+
* côté pendant que l'orchestrateur reçoit 503 de l'autre cherche au mauvais
|
|
1690
|
+
* endroit. Les deux ont divergé (`booted` contre `postReady`), et l'écart ne
|
|
1691
|
+
* s'est vu qu'en production, où la fin du cycle de démarrage arrive assez tard
|
|
1692
|
+
* pour ouvrir une fenêtre de plusieurs secondes.
|
|
1693
|
+
*
|
|
1694
|
+
* Deux conditions, et rien d'autre :
|
|
1695
|
+
*
|
|
1696
|
+
* - **le cycle de démarrage est TERMINÉ** (`postReady`) — `booted` ne suffit
|
|
1697
|
+
* pas : les écouteurs de `onPostReady` montent encore des choses dont une
|
|
1698
|
+
* requête a besoin ;
|
|
1699
|
+
* - **aucun composant ne retient la mise en service** — un entier déjà
|
|
1700
|
+
* calculé par eux ({@link Kernel.setReadiness}), jamais une vérification
|
|
1701
|
+
* déclenchée ici : une sonde ne doit tomber avec RIEN.
|
|
1702
|
+
*
|
|
1703
|
+
* Ce que ce getter ne connaît pas, volontairement : l'ARRÊT. Le drain
|
|
1704
|
+
* appartient au transport, qui bascule sa propre sonde à 503 avant même de
|
|
1705
|
+
* commencer à drainer — et `/livez`, lui, doit rester vert pendant ce
|
|
1706
|
+
* temps, sans quoi un redémarrage casserait l'arrêt gracieux en cours.
|
|
1707
|
+
*
|
|
1708
|
+
* @returns `true` si le processus peut servir du trafic.
|
|
1709
|
+
*/
|
|
1710
|
+
get servable() {
|
|
1711
|
+
return this.postReady === true && this.readinessBlocked === 0;
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Pose le verdict de disponibilité d'un composant nommé — un schéma de base en
|
|
1715
|
+
* retard, un cache froid, un service tiers muet. Tant qu'un seul contributeur
|
|
1716
|
+
* répond `false`, `/readyz` rend **503** et l'orchestrateur n'envoie pas de
|
|
1717
|
+
* trafic ; le processus reste VIVANT (`/livez` inchangé), car un état externe
|
|
1718
|
+
* ne se répare pas en redémarrant le processus.
|
|
1719
|
+
*
|
|
1720
|
+
* Le verdict doit être DÉJÀ CALCULÉ : ce n'est pas une vérification que la
|
|
1721
|
+
* sonde déclenchera, c'est un booléen que le composant met à jour à son propre
|
|
1722
|
+
* rythme. Dès qu'il repose `true`, le processus redevient disponible tout seul
|
|
1723
|
+
* — sans redéploiement, ce qui est toute la raison d'être du mécanisme.
|
|
1724
|
+
*
|
|
1725
|
+
* **`blocking: false` publie l'état SANS retenir le trafic.** Un composant
|
|
1726
|
+
* réglé pour tolérer sa propre panne (« journalise et sers quand même ») doit
|
|
1727
|
+
* pouvoir le dire sans disparaître du registre : c'est sa présence, et non la
|
|
1728
|
+
* rétention, qui apprend au noyau qu'un état EXTERNE est en cours et qu'un
|
|
1729
|
+
* échec de démarrage ne doit donc pas être fatal
|
|
1730
|
+
* ({@link Kernel.isBootErrorFatal}). Sans cette distinction, affaiblir la
|
|
1731
|
+
* sonde faisait perdre la tolérance du démarrage — au moment précis où l'on
|
|
1732
|
+
* cherchait de l'air.
|
|
1733
|
+
*
|
|
1734
|
+
* Idempotent par nom : réenregistrer le même nom remplace son verdict.
|
|
1735
|
+
*
|
|
1736
|
+
* @param name - nom du contributeur, qui apparaîtra au journal (`"drizzle:schema"`)
|
|
1737
|
+
* @param ready - `false` retient la mise en service, `true` la libère
|
|
1738
|
+
* @param reason - ce qui retient, en clair (journalisé ; ignoré si `ready`)
|
|
1739
|
+
*/
|
|
1740
|
+
setReadiness(name, ready, reason, blocking = true) {
|
|
1741
|
+
if (this.readiness === null) this.readiness = new ReadinessRegistry();
|
|
1742
|
+
if (this.readiness.set(name, ready, reason, blocking)) this.logReadinessFlip();
|
|
1743
|
+
this.publishReadinessState();
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Retire un contributeur du registre — sa voix ne compte plus. Geste d'un
|
|
1747
|
+
* module qui s'arrête ; le registre est LIBÉRÉ quand le dernier part, pour que
|
|
1748
|
+
* la sonde retrouve exactement le coût qu'elle avait avant toute inscription.
|
|
1749
|
+
*
|
|
1750
|
+
* @param name - nom du contributeur, tel que passé à {@link Kernel.setReadiness}
|
|
1751
|
+
*/
|
|
1752
|
+
clearReadiness(name) {
|
|
1753
|
+
if (this.readiness === null) return;
|
|
1754
|
+
const flipped = this.readiness.clear(name);
|
|
1755
|
+
if (this.readiness.size === 0) this.readiness = null;
|
|
1756
|
+
if (flipped) this.logReadinessFlip();
|
|
1757
|
+
this.publishReadinessState();
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Restitue l'état de chaque contributeur — pour un diagnostic (journal,
|
|
1761
|
+
* `status`, console d'administration), jamais pour la sonde.
|
|
1762
|
+
*
|
|
1763
|
+
* @returns un contributeur par inscription, triés par nom ; vide si aucun
|
|
1764
|
+
*/
|
|
1765
|
+
readinessReport() {
|
|
1766
|
+
return this.readiness === null ? [] : this.readiness.report();
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Publie sur le disque QUI retient la mise en service — le canal que lit
|
|
1770
|
+
* `nodefony status`.
|
|
1771
|
+
*
|
|
1772
|
+
* **Pourquoi un fichier plutôt que la sonde** : le détail existe déjà sur
|
|
1773
|
+
* `livez`, mais réservé à un appelant authentifié, et cette route est publique
|
|
1774
|
+
* en production. Le système de fichiers est la vraie frontière « local », il
|
|
1775
|
+
* répond même quand la boucle d'événements ne répond plus, et il n'ouvre
|
|
1776
|
+
* aucune surface publique. Détail : {@link readinessStateFile}.
|
|
1777
|
+
*
|
|
1778
|
+
* **N'écrit que si l'état a CHANGÉ.** En régime établi — un contributeur qui
|
|
1779
|
+
* repose le même verdict toutes les quinze secondes — ce mécanisme ne touche
|
|
1780
|
+
* pas le disque. La signature est une chaîne courte, composée hors du chemin
|
|
1781
|
+
* d'une requête : `setReadiness` n'est appelé qu'au démarrage et au rythme
|
|
1782
|
+
* propre de chaque contributeur.
|
|
1783
|
+
*/
|
|
1784
|
+
publishReadinessState() {
|
|
1785
|
+
const contributors = this.readinessReport();
|
|
1786
|
+
const signature = contributors.map((c) => `${c.name}\u0000${c.ready ? 1 : 0}\u0000${c.reason ?? ""}`).join("");
|
|
1787
|
+
if (signature === this.#readinessPublished) return;
|
|
1788
|
+
this.#readinessPublished = signature;
|
|
1789
|
+
if (contributors.length === 0) {
|
|
1790
|
+
clearReadinessState(this.path);
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
writeReadinessState(this.path, contributors);
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Dit au journal ce qui vient de basculer. Appelé UNIQUEMENT quand le verdict
|
|
1797
|
+
* agrégé change (jamais à chaque pose de verdict identique) : un pod retenu
|
|
1798
|
+
* plusieurs heures ne doit pas noyer son propre journal, et un exploitant qui
|
|
1799
|
+
* voit un pod jamais disponible doit trouver ici le NOM de ce qui le retient —
|
|
1800
|
+
* le corps de la sonde, lui, reste une constante pré-allouée.
|
|
1801
|
+
*/
|
|
1802
|
+
logReadinessFlip() {
|
|
1803
|
+
const retenant = this.readiness === null ? [] : this.readiness.report().filter((c) => !c.ready);
|
|
1804
|
+
if (retenant.length === 0) {
|
|
1805
|
+
this.log("Mise en service libérée — /readyz rend de nouveau 200", "INFO");
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
const details = retenant.map((c) => c.reason ? `${c.name} (${c.reason})` : c.name).join(", ");
|
|
1809
|
+
this.log(`Mise en service RETENUE — /readyz rend 503 : ${details}`, "CRITIC");
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Compte les WARNING (severity 4) et les ERROR-et-pire (severity 0-3 :
|
|
1813
|
+
* EMERGENCY/ALERT/CRITIC/ERROR) présents dans le ring buffer syslog. Pendant le
|
|
1814
|
+
* boot, le ring ne contient QUE des logs de boot → le compte est le « journal
|
|
1815
|
+
* du boot ». Borné par la capacité du ring (compte plancher, jamais gonflé).
|
|
1816
|
+
*
|
|
1817
|
+
* @returns compteurs `{ warnings, errors }`.
|
|
1818
|
+
*/
|
|
1819
|
+
countBootLogIssues() {
|
|
1820
|
+
let warnings = 0;
|
|
1821
|
+
let errors = 0;
|
|
1822
|
+
let criticals = null;
|
|
1823
|
+
const ring = this.syslog?.ringStack;
|
|
1824
|
+
if (ring) {
|
|
1825
|
+
for (const pdu of ring) if (pdu.severity === 4) warnings++;
|
|
1826
|
+
else if (pdu.severity >= 0 && pdu.severity <= 3) {
|
|
1827
|
+
errors++;
|
|
1828
|
+
if (criticals === null) criticals = [];
|
|
1829
|
+
if (criticals.length < MAX_BOOT_CRITICALS) criticals.push(this.condenseBootMessage(pdu));
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
return {
|
|
1833
|
+
warnings,
|
|
1834
|
+
errors,
|
|
1835
|
+
criticals
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Une entrée de journal ramenée à ce qui tient dans un bilan.
|
|
1840
|
+
*
|
|
1841
|
+
* Le message brut porte parfois une pile entière : la première ligne dit ce
|
|
1842
|
+
* qui s'est passé, le reste appartient au journal. Le module émetteur est
|
|
1843
|
+
* conservé — c'est lui qui répond à « QUI a crié ? », la question qu'un
|
|
1844
|
+
* compte seul laissait entière.
|
|
1845
|
+
*
|
|
1846
|
+
* @param pdu - l'entrée du journal.
|
|
1847
|
+
* @returns une ligne, bornée.
|
|
1848
|
+
*/
|
|
1849
|
+
condenseBootMessage(pdu) {
|
|
1850
|
+
const premiere = (String(pdu.payload ?? "").split("\n")[0] ?? "").trim();
|
|
1851
|
+
const message = premiere.length > MAX_BOOT_CRITICAL_LENGTH ? `${premiere.slice(0, 199)}…` : premiere;
|
|
1852
|
+
return `${pdu.moduleName ? `${String(pdu.moduleName)} : ` : ""}${message}`;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Déclare une ligne de détail à afficher SOUS une phase de boot (canal NEUTRE
|
|
1856
|
+
* dev-only : le `BootReporter` la rend ; le core ne connaît pas son contenu).
|
|
1857
|
+
* Un adapter ORM/frontend/… l'appelle à son hook de phase pour que le boot
|
|
1858
|
+
* RACONTE ce qu'il met en place — sans coupler le core au domaine du module.
|
|
1859
|
+
*
|
|
1860
|
+
* @param phase - libellé de la phase (cf `BootReporter` PHASES, ex. « Services & ORM »).
|
|
1861
|
+
* @param line - ligne lisible (ex. « drizzle → sqlite (./var/app.db) »).
|
|
1862
|
+
*/
|
|
1863
|
+
reportBootLine(phase, line) {
|
|
1864
|
+
const map = this.bootLines ??= /* @__PURE__ */ new Map();
|
|
1865
|
+
const lines = map.get(phase);
|
|
1866
|
+
if (lines) lines.push(line);
|
|
1867
|
+
else map.set(phase, [line]);
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* REMPLACE les lignes de détail d'une phase (vs {@link reportBootLine} qui
|
|
1871
|
+
* AJOUTE). Pour un producteur idempotent invoqué plusieurs fois qui reconstruit
|
|
1872
|
+
* la liste complète à chaque appel (ex. le wiring ORM, déclenché par N drivers,
|
|
1873
|
+
* itère tout le registre → « dernier gagne »).
|
|
1874
|
+
*
|
|
1875
|
+
* @param phase - libellé de la phase.
|
|
1876
|
+
* @param lines - liste complète des lignes (remplace l'existant ; vide = efface).
|
|
1877
|
+
*/
|
|
1878
|
+
setBootLines(phase, lines) {
|
|
1879
|
+
if (!lines.length) {
|
|
1880
|
+
this.bootLines?.delete(phase);
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
(this.bootLines ??= /* @__PURE__ */ new Map()).set(phase, [...lines]);
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Lignes de détail déclarées pour une phase de boot (vide si aucune).
|
|
1887
|
+
*
|
|
1888
|
+
* @param phase - libellé de la phase.
|
|
1889
|
+
* @returns lignes dans l'ordre de déclaration.
|
|
1890
|
+
*/
|
|
1891
|
+
getBootLines(phase) {
|
|
1892
|
+
return this.bootLines?.get(phase) ?? [];
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Action corrective suggérée d'après les raisons d'échec — surfacée dans le
|
|
1896
|
+
* verdict (écran + log). Heuristique : un `import()` qui échoue (« Cannot find
|
|
1897
|
+
* package/module ») pointe presque toujours un `dist/` périmé après pull/merge.
|
|
1898
|
+
*
|
|
1899
|
+
* Le manifeste VIDE est traité en premier parce qu'il ne produit aucune raison
|
|
1900
|
+
* d'échec : rien n'ayant été tenté, `skipped` est vide et l'heuristique
|
|
1901
|
+
* suivante n'a rien à lire. C'est précisément l'état qui laissait le diagnostic
|
|
1902
|
+
* muet — il faut donc le nommer ici, à défaut de pouvoir le déduire.
|
|
1903
|
+
*
|
|
1904
|
+
* Le manifeste vide n'est signalé que sous un profil SERVEUR : une commande
|
|
1905
|
+
* batch ou un test qui boote sans manifeste est un cas nominal, et une
|
|
1906
|
+
* remédiation posée là serait un faux diagnostic recopié partout où le rapport
|
|
1907
|
+
* est lu.
|
|
1908
|
+
*
|
|
1909
|
+
* @param skipped - modules ignorés.
|
|
1910
|
+
* @param manifestEntries - entrées déclarées au manifeste `config.modules`.
|
|
1911
|
+
* @param serversExpected - le profil d'exécution attendait-il des serveurs.
|
|
1912
|
+
* @returns une phrase d'action, ou `null`.
|
|
1913
|
+
*/
|
|
1914
|
+
bootRemediationHint(skipped, manifestEntries, serversExpected) {
|
|
1915
|
+
if (serversExpected && manifestEntries === 0) return "la configuration LUE ne déclare aucun module (`modules: []`) ⇒ vérifier `nodefony.config` et l'exécutable employé (`nodefony inspect config` dit la config effective et sa provenance)";
|
|
1916
|
+
if (skipped.some((f) => /Cannot find package|Cannot find module|ERR_MODULE_NOT_FOUND/i.test(f.reason))) return "dist périmé probable ⇒ npm run clean && npm run build";
|
|
1917
|
+
return null;
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Log structuré du verdict de boot — émis **toujours** (prod incluse), donc
|
|
1921
|
+
* indépendant du `BootReporter` (qui n'existe qu'en dev). En non-TTY/prod c'est
|
|
1922
|
+
* la seule trace écran ; en dev TTY animé le sink est muté → va au backplane,
|
|
1923
|
+
* et le `BootReporter` rend le verdict joli (✓/⚠/⛔).
|
|
1924
|
+
*
|
|
1925
|
+
* @param report - verdict à logger.
|
|
1926
|
+
*/
|
|
1927
|
+
logBootVerdict(report) {
|
|
1928
|
+
const skipped = report.modulesSkipped;
|
|
1929
|
+
if (!report.healthy) {
|
|
1930
|
+
const reasons = skipped.map((f) => `${f.module}: ${f.reason}`).join(" · ");
|
|
1931
|
+
this.log(`BOOT ÉCHEC — profil serveur mais aucun serveur en écoute · manifeste : ${report.manifestEntries} module(s) déclaré(s), ${report.modulesLoaded.length} chargé(s)` + (skipped.length ? ` · ${skipped.length} module(s) en échec : ${reasons}` : "") + (report.remediation ? ` — ${report.remediation}` : ""), "CRITIC");
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
if (skipped.length) {
|
|
1935
|
+
this.log(`BOOT dégradé — ${report.modulesLoaded.length} module(s) chargé(s), ${skipped.length} en échec (fail-soft) : ` + skipped.map((f) => `${f.module} (${f.reason})`).join(" · "), "WARNING");
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
const urls = report.serversListening.map((s) => s.url).join(", ");
|
|
1939
|
+
const gated = report.modulesGated.length ? `, ${report.modulesGated.length} ignoré(s) (policy/when)` : "";
|
|
1940
|
+
const journal = report.warnings || report.errors ? ` · journal : ${report.errors} ERROR, ${report.warnings} WARNING` : "";
|
|
1941
|
+
this.log(`BOOT ok — ${report.modulesLoaded.length} module(s)${gated}, ${report.serversListening.length} serveur(s) en écoute` + (urls ? ` (${urls})` : "") + journal, "NOTICE");
|
|
1942
|
+
const logCfg = this.options.log;
|
|
1943
|
+
if (shouldWarnPerPodView(isInKubernetes(), getActiveLogDriver()?.name, Boolean(logCfg?.loki?.url || logCfg?.opensearch?.url))) this.log(`Kubernetes détecté + log.queryDriver="${getActiveLogDriver()?.name}" (vue PAR POD) : la relecture des logs ne couvre que CE pod. En multi-pod (replicas > 1), configure log.queryDriver="loki"|"opensearch" (+ URL) pour une vue globale.`, "NOTICE", "SYSLOG");
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Émet une phase de **lifecycle de boot** de façon GARDÉE (cf
|
|
1947
|
+
* `Event.emitAsyncGuarded`) : chaque hook de module est isolé par try/catch +
|
|
1948
|
+
* timeout ; un module qui throw ou se fige ne gèle/ne tue plus tout le boot. La
|
|
1949
|
+
* politique (propager vs fail-soft) est tranchée par {@link isBootErrorFatal}
|
|
1950
|
+
* d'après les tags `owner`/`critical` posés par `Module.setEvents()`.
|
|
1951
|
+
*
|
|
1952
|
+
* Remplace `fireAsync` **uniquement** dans la chaîne boot (`onPreRegister` →
|
|
1953
|
+
* `onPostReady`). Le hot path HTTP/WS garde `emitAsync` nu (aucun timer/alloc).
|
|
1954
|
+
*
|
|
1955
|
+
* @param event - phase lifecycle à émettre.
|
|
1956
|
+
* @param args - arguments passés aux hooks (typiquement `this`).
|
|
1957
|
+
* @returns le {@link IGuardedEmitResult} (results / errors / stopped).
|
|
1958
|
+
* @throws l'erreur d'un module critique en production (interrompt le boot).
|
|
1959
|
+
*/
|
|
1960
|
+
async fireLifecycle(event, ...args) {
|
|
1961
|
+
this.log(`${colorLogEvent()} ${event} [guarded]`, "DEBUG");
|
|
1962
|
+
const warnMs = this.bootWarnMs();
|
|
1963
|
+
let fatalError = null;
|
|
1964
|
+
let hasFatal = false;
|
|
1965
|
+
const bootTimeout = this.bootTimeoutMs();
|
|
1966
|
+
const result = await super.emitAsyncGuarded(event, {
|
|
1967
|
+
timeoutMs: (listener) => isUnboundedListener(listener) ? 0 : bootTimeout,
|
|
1968
|
+
warnMs,
|
|
1969
|
+
onListenerError: (error, info) => {
|
|
1970
|
+
const { owner, critical, name } = readListenerTags(info.listener);
|
|
1971
|
+
if (this.isBootErrorFatal(error, owner ?? name, critical, info.timedOut, "lifecycle")) {
|
|
1972
|
+
fatalError = error;
|
|
1973
|
+
hasFatal = true;
|
|
1974
|
+
return true;
|
|
1975
|
+
}
|
|
1976
|
+
},
|
|
1977
|
+
onListenerSlow: (info) => {
|
|
1978
|
+
const { owner, name } = readListenerTags(info.listener);
|
|
1979
|
+
this.log(`boot lifecycle: hook "${owner ?? name ?? "(anonyme)"}" lent (${Math.round(info.durationMs)}ms ≥ ${warnMs}ms) sur ${event}`, "NOTICE");
|
|
1980
|
+
}
|
|
1981
|
+
}, ...args);
|
|
1982
|
+
if (hasFatal) throw fatalError instanceof Error ? fatalError : new Error(String(fatalError));
|
|
1983
|
+
return result;
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Initialise un service **sous garde de boot** (timeout + politique de criticité
|
|
1987
|
+
* via {@link guardInitialize}) si le service expose le hook `init`. Point
|
|
1988
|
+
* d'entrée commun à {@link addKernelService} (service kernel, `critical=true`) et
|
|
1989
|
+
* à {@link Module.addService} (service de module, criticité = celle du module
|
|
1990
|
+
* porteur), pour que les deux chemins d'init partagent la même résilience.
|
|
1991
|
+
*
|
|
1992
|
+
* @remarks Avant 2026-05-29 les services de module passaient par un `await` NU
|
|
1993
|
+
* (aucun timeout) : un `init` qui pend (ex. `redis` = connexion réseau)
|
|
1994
|
+
* gelait le boot indéfiniment. Désormais borné comme les services kernel.
|
|
1995
|
+
*
|
|
1996
|
+
* @param serviceInit - instance du service ; ne fait rien si `init` absent.
|
|
1997
|
+
* @param owner - propriétaire passé au hook : le {@link Module} ou ce {@link Kernel}.
|
|
1998
|
+
* @param critical - criticité transmise à {@link isBootErrorFatal}.
|
|
1999
|
+
*/
|
|
2000
|
+
async guardServiceInitialize(serviceInit, owner, critical) {
|
|
2001
|
+
if (!serviceInit.init) return;
|
|
2002
|
+
this.log(`SERVICE INITIALIZE : ${serviceInit.name}`, "DEBUG");
|
|
2003
|
+
const init = serviceInit.init.bind(serviceInit);
|
|
2004
|
+
await this.guardInitialize(() => init(owner), serviceInit.name, critical);
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Applique la politique de criticité du boot à l'échec d'un service déclaré via
|
|
2008
|
+
* `@services([...])` — **construction (`new`) comprise**, pas seulement `init`.
|
|
2009
|
+
* Pendant symétrique de {@link guardServiceInitialize} pour le chemin décorateur.
|
|
2010
|
+
*
|
|
2011
|
+
* @remarks Le `catch` du décorateur `@services` avalait auparavant TOUT échec
|
|
2012
|
+
* (`log(e, "ERROR")` et rien d'autre) : il n'atteignait ni cette politique —
|
|
2013
|
+
* donc jamais fatal, **même en production** — ni le {@link getBootReport}, si
|
|
2014
|
+
* bien qu'un boot amputé d'un service critique se déclarait « UP ». Un service
|
|
2015
|
+
* qu'on ne peut pas CONSTRUIRE doit suivre exactement la règle de celui qu'on
|
|
2016
|
+
* ne peut pas INITIALISER.
|
|
2017
|
+
*
|
|
2018
|
+
* @param error - l'échec remonté par `addService`/`loadService`.
|
|
2019
|
+
* @param serviceName - nom lisible (pour le log + le BootReport).
|
|
2020
|
+
* @param critical - criticité du module porteur ({@link Module.critical}).
|
|
2021
|
+
* @returns `true` si l'échec est fatal → l'appelant **doit** propager.
|
|
2022
|
+
*/
|
|
2023
|
+
serviceBootErrorFatal(error, serviceName, critical) {
|
|
2024
|
+
return this.isBootErrorFatal(error, serviceName, critical, false, "init");
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Exécute un `initialize()` (module ou service kernel) sous garde de boot :
|
|
2028
|
+
* borné par {@link bootTimeoutMs} et soumis à {@link isBootErrorFatal}. Un
|
|
2029
|
+
* `initialize` qui se fige (connexion réseau qui pend) ne gèle plus le boot.
|
|
2030
|
+
*
|
|
2031
|
+
* @param run - thunk qui lance l'`initialize` (déjà lié à son instance).
|
|
2032
|
+
* @param owner - nom du module/service (pour le log).
|
|
2033
|
+
* @param critical - criticité (module → `Module.critical` ; service kernel → `true`).
|
|
2034
|
+
*/
|
|
2035
|
+
async guardInitialize(run, owner, critical) {
|
|
2036
|
+
try {
|
|
2037
|
+
await withTimeout(Promise.resolve(run()), this.bootTimeoutMs(), `initialize ${owner}`);
|
|
2038
|
+
} catch (error) {
|
|
2039
|
+
const timedOut = error instanceof TimeoutError;
|
|
2040
|
+
if (this.isBootErrorFatal(error, owner, critical, timedOut, "init")) throw error;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
checkPath(myPath) {
|
|
2044
|
+
if (!myPath) return null;
|
|
2045
|
+
if (path.isAbsolute(myPath)) return myPath;
|
|
2046
|
+
return path.resolve(this.path, myPath);
|
|
2047
|
+
}
|
|
2048
|
+
sendMessage(message, handle, options, callback) {
|
|
2049
|
+
if (process.send) return process.send({
|
|
2050
|
+
type: "process:msg",
|
|
2051
|
+
data: message
|
|
2052
|
+
}, handle, options, callback);
|
|
2053
|
+
throw new Error(`process.send not found `);
|
|
2054
|
+
}
|
|
2055
|
+
memoryUsage(message, severity = "DEBUG") {
|
|
2056
|
+
const { memory } = this.stats();
|
|
2057
|
+
for (const ele in memory) switch (ele) {
|
|
2058
|
+
case "rss":
|
|
2059
|
+
this.log(`${message || ele} ( Resident Set Size ) PID ( ${this.pid} ) : ${CliKernel.niceBytes(memory[ele])}`, severity, `MEMORY ${ele}`);
|
|
2060
|
+
break;
|
|
2061
|
+
case "heapTotal":
|
|
2062
|
+
this.log(`${message || ele} ( Total Size of the Heap ) PID ( ${this.pid} ) : ${CliKernel.niceBytes(memory[ele])}`, severity, `MEMORY ${ele}`);
|
|
2063
|
+
break;
|
|
2064
|
+
case "heapUsed":
|
|
2065
|
+
this.log(`${message || ele} ( Heap actually Used ) PID ( ${this.pid} ) : ${CliKernel.niceBytes(memory[ele])}`, severity, `MEMORY ${ele}`);
|
|
2066
|
+
break;
|
|
2067
|
+
case "external": this.log(`${message || ele} PID ( ${this.pid} ) : ${CliKernel.niceBytes(memory[ele])}`, severity, `MEMORY ${ele}`);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
stats() {
|
|
2071
|
+
return { memory: process.memoryUsage() };
|
|
2072
|
+
}
|
|
2073
|
+
interfacesFilter(filters) {
|
|
2074
|
+
if (filters) {
|
|
2075
|
+
let condition = (ele, ele2) => {
|
|
2076
|
+
return ele && ele2;
|
|
2077
|
+
};
|
|
2078
|
+
if (filters.condition && filters.condition === "||") condition = (ele, ele2) => {
|
|
2079
|
+
return ele || ele2;
|
|
2080
|
+
};
|
|
2081
|
+
const interfaces = {};
|
|
2082
|
+
for (const myinterface in this.interfaces) {
|
|
2083
|
+
interfaces[myinterface] = [];
|
|
2084
|
+
for (const infos of this.interfaces[myinterface]) {
|
|
2085
|
+
let matchType = false;
|
|
2086
|
+
let matchFamily = false;
|
|
2087
|
+
for (const filter in filters) switch (filter) {
|
|
2088
|
+
case "type":
|
|
2089
|
+
if (filters["type"] === "external") {
|
|
2090
|
+
if (!infos.internal) matchType = true;
|
|
2091
|
+
}
|
|
2092
|
+
if (filters["type"] === "local") {
|
|
2093
|
+
if (infos.internal) matchType = true;
|
|
2094
|
+
}
|
|
2095
|
+
break;
|
|
2096
|
+
case "family": if (filters["family"] === infos.family) matchFamily = true;
|
|
2097
|
+
}
|
|
2098
|
+
if (condition(matchType, matchFamily)) interfaces[myinterface].push(infos);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
return interfaces;
|
|
2102
|
+
}
|
|
2103
|
+
return this.interfaces;
|
|
2104
|
+
}
|
|
2105
|
+
getNetworkInterfaces() {
|
|
2106
|
+
const nets = os.networkInterfaces();
|
|
2107
|
+
const devices = {};
|
|
2108
|
+
Object.entries(nets).forEach(([name, ni]) => {
|
|
2109
|
+
if (!ni) return;
|
|
2110
|
+
devices[name] = ni;
|
|
2111
|
+
});
|
|
2112
|
+
return devices;
|
|
2113
|
+
}
|
|
2114
|
+
getNetwork() {
|
|
2115
|
+
return {
|
|
2116
|
+
external: this.interfacesFilter({ type: "external" }),
|
|
2117
|
+
local: this.interfacesFilter({
|
|
2118
|
+
type: "local",
|
|
2119
|
+
condition: "=="
|
|
2120
|
+
}),
|
|
2121
|
+
ipv4: this.interfacesFilter({ family: "IPv4" }),
|
|
2122
|
+
ipv6: this.interfacesFilter({ family: "IPv6" }),
|
|
2123
|
+
interfaces: this.interfaces
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
getFirstExternalInterface(family) {
|
|
2127
|
+
const filter = {
|
|
2128
|
+
type: "external",
|
|
2129
|
+
family: family || "IPv4",
|
|
2130
|
+
condition: "&&"
|
|
2131
|
+
};
|
|
2132
|
+
const res = this.interfacesFilter(filter);
|
|
2133
|
+
const ele = [];
|
|
2134
|
+
for (const myinterface in res) for (const info of res[myinterface]) ele.push(info);
|
|
2135
|
+
return ele[0] || void 0;
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Shutdown propre du kernel — fire `"onTerminate"` puis `CliKernel.quit(code)` sur next tick.
|
|
2139
|
+
*
|
|
2140
|
+
* Le drain complet (`fireAsync("onTerminate")` : bascule readiness → close WS
|
|
2141
|
+
* 1001 → drain HTTP → cleanup services) est borné par la deadline GLOBALE
|
|
2142
|
+
* `shutdownDeadline` (défaut 15 s, < grace period orchestrateur) : un listener
|
|
2143
|
+
* qui pend (SSE ouvert, store bloqué, module tiers) force la sortie code 1 —
|
|
2144
|
+
* jamais un process zombie qui attend le SIGKILL externe. `0` = filet désactivé.
|
|
2145
|
+
*
|
|
2146
|
+
* @param code - exit code Unix (0 = succès, 1+ = erreur).
|
|
2147
|
+
* @param quiet - muselle l'AFFICHAGE (le log « terminate : N ») — jamais le
|
|
2148
|
+
* drain : une sortie voulue par l'utilisateur (Ctrl+C d'un menu) n'a pas à
|
|
2149
|
+
* ressembler à un événement système.
|
|
2150
|
+
* @returns Promise résolue avec `this` (ou rejected si `quit()` throw).
|
|
2151
|
+
*/
|
|
2152
|
+
async terminate(code, quiet) {
|
|
2153
|
+
if (code === void 0) code = typeof process.exitCode === "number" ? process.exitCode : 0;
|
|
2154
|
+
if (this.terminating) return this.terminating;
|
|
2155
|
+
this.terminating = this.doTerminate(code, quiet);
|
|
2156
|
+
return this.terminating;
|
|
2157
|
+
}
|
|
2158
|
+
/** Le shutdown lui-même — protégé de la ré-entrance par {@link terminate}. */
|
|
2159
|
+
async doTerminate(code, quiet) {
|
|
2160
|
+
if (this.parkTimer) {
|
|
2161
|
+
clearInterval(this.parkTimer);
|
|
2162
|
+
this.parkTimer = null;
|
|
2163
|
+
}
|
|
2164
|
+
if (!quiet) this.log(`terminate : ${code}`);
|
|
2165
|
+
const drain = this.fireAsync("onTerminate", this, code).catch((e) => {
|
|
2166
|
+
this.log(e, "ERROR");
|
|
2167
|
+
return SHUTDOWN_DRAIN_ERROR;
|
|
2168
|
+
});
|
|
2169
|
+
const deadlineMs = this.options.shutdownDeadline ?? DEFAULT_SHUTDOWN_DEADLINE;
|
|
2170
|
+
let raced;
|
|
2171
|
+
if (deadlineMs > 0) {
|
|
2172
|
+
let deadlineTimer = null;
|
|
2173
|
+
raced = await Promise.race([drain, new Promise((resolve) => {
|
|
2174
|
+
deadlineTimer = setTimeout(() => resolve(SHUTDOWN_DEADLINE), deadlineMs);
|
|
2175
|
+
deadlineTimer.unref();
|
|
2176
|
+
})]);
|
|
2177
|
+
if (deadlineTimer !== null) clearTimeout(deadlineTimer);
|
|
2178
|
+
} else raced = await drain;
|
|
2179
|
+
if (raced === SHUTDOWN_DEADLINE) {
|
|
2180
|
+
this.log(`shutdown deadline exceeded (${deadlineMs} ms) — forcing exit, onTerminate listeners still pending`, "CRITIC");
|
|
2181
|
+
code = 1;
|
|
2182
|
+
} else if (raced === SHUTDOWN_DRAIN_ERROR) code = 1;
|
|
2183
|
+
return new Promise((resolve, reject) => {
|
|
2184
|
+
process.nextTick(() => {
|
|
2185
|
+
this.log(`NODEFONY Kernel Life Cycle Terminate CODE : ${code}`, "DEBUG");
|
|
2186
|
+
try {
|
|
2187
|
+
CliKernel.quit(code);
|
|
2188
|
+
return resolve(this);
|
|
2189
|
+
} catch (e) {
|
|
2190
|
+
this.log(e, "ERROR");
|
|
2191
|
+
return reject(e);
|
|
2192
|
+
}
|
|
2193
|
+
});
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
//#endregion
|
|
2198
|
+
export { CONSOLE_RUN_PROFILE, Events, FORCE_DEV_MODULES_ENV, FORCE_DEV_MODULES_TTL_ENV, FORCE_DEV_MODULES_TTL_MAX_MS, FORCE_DEV_MODULES_TTL_MS, Kernel as default, resolveDevModulesTtlMs };
|