@lakutata/entrypoint 3.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +183 -0
- package/THIRD_PARTY_NOTICES.md +206 -0
- package/dist/cjs/Entrypoint.d.ts +868 -0
- package/dist/cjs/Entrypoint.js +998 -0
- package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.d.ts +192 -0
- package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.js +439 -0
- package/dist/cjs/adapter/commander/Prompt.d.ts +22 -0
- package/dist/cjs/adapter/commander/Prompt.js +164 -0
- package/dist/cjs/adapter/commander/Width.d.ts +12 -0
- package/dist/cjs/adapter/commander/Width.js +67 -0
- package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +203 -0
- package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.js +249 -0
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.d.ts +172 -0
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.js +301 -0
- package/dist/cjs/adapter/express/Responders.d.ts +89 -0
- package/dist/cjs/adapter/express/Responders.js +71 -0
- package/dist/cjs/adapter/express/StaticUI.d.ts +83 -0
- package/dist/cjs/adapter/express/StaticUI.js +104 -0
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.d.ts +156 -0
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.js +339 -0
- package/dist/cjs/adapter/fastify/Responders.d.ts +88 -0
- package/dist/cjs/adapter/fastify/Responders.js +77 -0
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.d.ts +117 -0
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.js +257 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCProtocol.d.ts +194 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCProtocol.js +187 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +154 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +397 -0
- package/dist/cjs/adapter/schedule/ScheduleEntrypoint.d.ts +86 -0
- package/dist/cjs/adapter/schedule/ScheduleEntrypoint.js +275 -0
- package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +138 -0
- package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.js +216 -0
- package/dist/cjs/channel/BuiltinChannels.d.ts +18 -0
- package/dist/cjs/channel/BuiltinChannels.js +128 -0
- package/dist/cjs/channel/Channel.d.ts +196 -0
- package/dist/cjs/channel/Channel.js +106 -0
- package/dist/cjs/channel/EntrypointAdapter.d.ts +195 -0
- package/dist/cjs/channel/EntrypointAdapter.js +43 -0
- package/dist/cjs/context/BaseContext.d.ts +75 -0
- package/dist/cjs/context/BaseContext.js +90 -0
- package/dist/cjs/context/CLIContext.d.ts +38 -0
- package/dist/cjs/context/CLIContext.js +53 -0
- package/dist/cjs/context/HTTPContext.d.ts +52 -0
- package/dist/cjs/context/HTTPContext.js +78 -0
- package/dist/cjs/context/ScheduleContext.d.ts +47 -0
- package/dist/cjs/context/ScheduleContext.js +65 -0
- package/dist/cjs/context/ServiceContext.d.ts +47 -0
- package/dist/cjs/context/ServiceContext.js +62 -0
- package/dist/cjs/decorators/CLIAction.d.ts +120 -0
- package/dist/cjs/decorators/CLIAction.js +13 -0
- package/dist/cjs/decorators/HTTPAction.d.ts +178 -0
- package/dist/cjs/decorators/HTTPAction.js +14 -0
- package/dist/cjs/decorators/Prefix.d.ts +44 -0
- package/dist/cjs/decorators/Prefix.js +48 -0
- package/dist/cjs/decorators/Schedule.d.ts +120 -0
- package/dist/cjs/decorators/Schedule.js +64 -0
- package/dist/cjs/decorators/ServiceAction.d.ts +116 -0
- package/dist/cjs/decorators/ServiceAction.js +12 -0
- package/dist/cjs/decorators/UseMiddlewares.d.ts +52 -0
- package/dist/cjs/decorators/UseMiddlewares.js +58 -0
- package/dist/cjs/decorators/http/DELETE.d.ts +87 -0
- package/dist/cjs/decorators/http/DELETE.js +7 -0
- package/dist/cjs/decorators/http/GET.d.ts +89 -0
- package/dist/cjs/decorators/http/GET.js +7 -0
- package/dist/cjs/decorators/http/HEAD.d.ts +84 -0
- package/dist/cjs/decorators/http/HEAD.js +7 -0
- package/dist/cjs/decorators/http/OPTIONS.d.ts +80 -0
- package/dist/cjs/decorators/http/OPTIONS.js +7 -0
- package/dist/cjs/decorators/http/PATCH.d.ts +90 -0
- package/dist/cjs/decorators/http/PATCH.js +7 -0
- package/dist/cjs/decorators/http/POST.d.ts +91 -0
- package/dist/cjs/decorators/http/POST.js +7 -0
- package/dist/cjs/decorators/http/PUT.d.ts +92 -0
- package/dist/cjs/decorators/http/PUT.js +7 -0
- package/dist/cjs/exceptions/AccessDeniedException.d.ts +10 -0
- package/dist/cjs/exceptions/AccessDeniedException.js +17 -0
- package/dist/cjs/exceptions/ControllerActionNotFoundException.d.ts +9 -0
- package/dist/cjs/exceptions/ControllerActionNotFoundException.js +16 -0
- package/dist/cjs/exceptions/DuplicateActionNameException.d.ts +8 -0
- package/dist/cjs/exceptions/DuplicateActionNameException.js +15 -0
- package/dist/cjs/exceptions/EntrypointStoppedException.d.ts +9 -0
- package/dist/cjs/exceptions/EntrypointStoppedException.js +16 -0
- package/dist/cjs/exceptions/InvalidActionGroupException.d.ts +4 -0
- package/dist/cjs/exceptions/InvalidActionGroupException.js +11 -0
- package/dist/cjs/exceptions/InvalidChannelException.d.ts +8 -0
- package/dist/cjs/exceptions/InvalidChannelException.js +15 -0
- package/dist/cjs/exceptions/InvalidEntrypointAdapterException.d.ts +9 -0
- package/dist/cjs/exceptions/InvalidEntrypointAdapterException.js +16 -0
- package/dist/cjs/exceptions/InvalidMiddlewareException.d.ts +8 -0
- package/dist/cjs/exceptions/InvalidMiddlewareException.js +15 -0
- package/dist/cjs/exceptions/InvalidPrefixException.d.ts +8 -0
- package/dist/cjs/exceptions/InvalidPrefixException.js +15 -0
- package/dist/cjs/exceptions/MiddlewareNextCalledException.d.ts +8 -0
- package/dist/cjs/exceptions/MiddlewareNextCalledException.js +15 -0
- package/dist/cjs/exceptions/RouteNotFoundException.d.ts +9 -0
- package/dist/cjs/exceptions/RouteNotFoundException.js +16 -0
- package/dist/cjs/exceptions/ServiceInvokeException.d.ts +11 -0
- package/dist/cjs/exceptions/ServiceInvokeException.js +14 -0
- package/dist/cjs/exports/CTRLDecorators.d.ts +13 -0
- package/dist/cjs/exports/CTRLDecorators.js +38 -0
- package/dist/cjs/exports/Channel.d.ts +14 -0
- package/dist/cjs/exports/Channel.js +33 -0
- package/dist/cjs/exports/CommanderAdapter.d.ts +1 -0
- package/dist/cjs/exports/CommanderAdapter.js +8 -0
- package/dist/cjs/exports/ElectronAdapter.d.ts +1 -0
- package/dist/cjs/exports/ElectronAdapter.js +9 -0
- package/dist/cjs/exports/Entrypoint.d.ts +24 -0
- package/dist/cjs/exports/Entrypoint.js +55 -0
- package/dist/cjs/exports/ExpressAdapter.d.ts +4 -0
- package/dist/cjs/exports/ExpressAdapter.js +15 -0
- package/dist/cjs/exports/FastifyAdapter.d.ts +4 -0
- package/dist/cjs/exports/FastifyAdapter.js +14 -0
- package/dist/cjs/exports/JSONRPCAdapter.d.ts +2 -0
- package/dist/cjs/exports/JSONRPCAdapter.js +11 -0
- package/dist/cjs/exports/SocketIOAdapter.d.ts +1 -0
- package/dist/cjs/exports/SocketIOAdapter.js +9 -0
- package/dist/cjs/lib/AccessControl.d.ts +118 -0
- package/dist/cjs/lib/AccessControl.js +208 -0
- package/dist/cjs/lib/AccessControlRule.d.ts +91 -0
- package/dist/cjs/lib/AccessControlRule.js +76 -0
- package/dist/cjs/lib/ActionOptions.d.ts +67 -0
- package/dist/cjs/lib/ActionOptions.js +67 -0
- package/dist/cjs/lib/ActionResult.d.ts +28 -0
- package/dist/cjs/lib/ActionResult.js +28 -0
- package/dist/cjs/lib/ApplicationOf.d.ts +6 -0
- package/dist/cjs/lib/ApplicationOf.js +14 -0
- package/dist/cjs/lib/Controller.d.ts +59 -0
- package/dist/cjs/lib/Controller.js +64 -0
- package/dist/cjs/lib/ControllerEntrypoint.d.ts +168 -0
- package/dist/cjs/lib/ControllerEntrypoint.js +239 -0
- package/dist/cjs/lib/GetActionDTOAndOptions.d.ts +29 -0
- package/dist/cjs/lib/GetActionDTOAndOptions.js +49 -0
- package/dist/cjs/lib/HTTPErrors.d.ts +55 -0
- package/dist/cjs/lib/HTTPErrors.js +52 -0
- package/dist/cjs/lib/Middleware.d.ts +146 -0
- package/dist/cjs/lib/Middleware.js +144 -0
- package/dist/cjs/lib/Prefix.d.ts +98 -0
- package/dist/cjs/lib/Prefix.js +151 -0
- package/dist/cjs/lib/Result.d.ts +173 -0
- package/dist/cjs/lib/Result.js +153 -0
- package/dist/cjs/lib/ServiceResponseCodec.d.ts +82 -0
- package/dist/cjs/lib/ServiceResponseCodec.js +117 -0
- package/dist/cjs/lib/StringifyPattern.d.ts +7 -0
- package/dist/cjs/lib/StringifyPattern.js +58 -0
- package/dist/cjs/lib/UnsetValue.d.ts +12 -0
- package/dist/cjs/lib/UnsetValue.js +216 -0
- package/dist/cjs/lib/pattern/GlobPattern.d.ts +34 -0
- package/dist/cjs/lib/pattern/GlobPattern.js +68 -0
- package/dist/cjs/lib/pattern/PatternManager.d.ts +103 -0
- package/dist/cjs/lib/pattern/PatternManager.js +233 -0
- package/dist/cjs/lib/schedule/Cron.d.ts +56 -0
- package/dist/cjs/lib/schedule/Cron.js +282 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/types/CLIContextParams.d.ts +14 -0
- package/dist/cjs/types/CLIContextParams.js +2 -0
- package/dist/cjs/types/ErrorDescription.d.ts +14 -0
- package/dist/cjs/types/ErrorDescription.js +2 -0
- package/dist/cjs/types/HTTPContextParams.d.ts +27 -0
- package/dist/cjs/types/HTTPContextParams.js +2 -0
- package/dist/cjs/types/ScheduleContextParams.d.ts +21 -0
- package/dist/cjs/types/ScheduleContextParams.js +2 -0
- package/dist/cjs/types/ServiceContextParams.d.ts +16 -0
- package/dist/cjs/types/ServiceContextParams.js +2 -0
- package/dist/esm/Entrypoint.js +985 -0
- package/dist/esm/adapter/commander/CommanderCLIEntrypoint.js +433 -0
- package/dist/esm/adapter/commander/Prompt.js +127 -0
- package/dist/esm/adapter/commander/Width.js +63 -0
- package/dist/esm/adapter/electron/ElectronIPCServiceEntrypoint.js +212 -0
- package/dist/esm/adapter/express/ExpressHTTPEntrypoint.js +295 -0
- package/dist/esm/adapter/express/Responders.js +66 -0
- package/dist/esm/adapter/express/StaticUI.js +96 -0
- package/dist/esm/adapter/fastify/FastifyHTTPEntrypoint.js +333 -0
- package/dist/esm/adapter/fastify/Responders.js +73 -0
- package/dist/esm/adapter/http/HTTPServiceEntrypoint.js +253 -0
- package/dist/esm/adapter/jsonrpc/JSONRPCProtocol.js +182 -0
- package/dist/esm/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +361 -0
- package/dist/esm/adapter/schedule/ScheduleEntrypoint.js +272 -0
- package/dist/esm/adapter/socket.io/SocketIOServiceEntrypoint.js +212 -0
- package/dist/esm/channel/BuiltinChannels.js +125 -0
- package/dist/esm/channel/Channel.js +100 -0
- package/dist/esm/channel/EntrypointAdapter.js +39 -0
- package/dist/esm/context/BaseContext.js +87 -0
- package/dist/esm/context/CLIContext.js +49 -0
- package/dist/esm/context/HTTPContext.js +74 -0
- package/dist/esm/context/ScheduleContext.js +61 -0
- package/dist/esm/context/ServiceContext.js +58 -0
- package/dist/esm/decorators/CLIAction.js +10 -0
- package/dist/esm/decorators/HTTPAction.js +11 -0
- package/dist/esm/decorators/Prefix.js +45 -0
- package/dist/esm/decorators/Schedule.js +61 -0
- package/dist/esm/decorators/ServiceAction.js +9 -0
- package/dist/esm/decorators/UseMiddlewares.js +55 -0
- package/dist/esm/decorators/http/DELETE.js +4 -0
- package/dist/esm/decorators/http/GET.js +4 -0
- package/dist/esm/decorators/http/HEAD.js +4 -0
- package/dist/esm/decorators/http/OPTIONS.js +4 -0
- package/dist/esm/decorators/http/PATCH.js +4 -0
- package/dist/esm/decorators/http/POST.js +4 -0
- package/dist/esm/decorators/http/PUT.js +4 -0
- package/dist/esm/exceptions/AccessDeniedException.js +13 -0
- package/dist/esm/exceptions/ControllerActionNotFoundException.js +12 -0
- package/dist/esm/exceptions/DuplicateActionNameException.js +11 -0
- package/dist/esm/exceptions/EntrypointStoppedException.js +12 -0
- package/dist/esm/exceptions/InvalidActionGroupException.js +7 -0
- package/dist/esm/exceptions/InvalidChannelException.js +11 -0
- package/dist/esm/exceptions/InvalidEntrypointAdapterException.js +12 -0
- package/dist/esm/exceptions/InvalidMiddlewareException.js +11 -0
- package/dist/esm/exceptions/InvalidPrefixException.js +11 -0
- package/dist/esm/exceptions/MiddlewareNextCalledException.js +11 -0
- package/dist/esm/exceptions/RouteNotFoundException.js +12 -0
- package/dist/esm/exceptions/ServiceInvokeException.js +10 -0
- package/dist/esm/exports/CTRLDecorators.js +19 -0
- package/dist/esm/exports/Channel.js +11 -0
- package/dist/esm/exports/CommanderAdapter.js +1 -0
- package/dist/esm/exports/ElectronAdapter.js +1 -0
- package/dist/esm/exports/Entrypoint.js +22 -0
- package/dist/esm/exports/ExpressAdapter.js +3 -0
- package/dist/esm/exports/FastifyAdapter.js +3 -0
- package/dist/esm/exports/JSONRPCAdapter.js +2 -0
- package/dist/esm/exports/SocketIOAdapter.js +1 -0
- package/dist/esm/lib/AccessControl.js +204 -0
- package/dist/esm/lib/AccessControlRule.js +72 -0
- package/dist/esm/lib/ActionOptions.js +63 -0
- package/dist/esm/lib/ActionResult.js +24 -0
- package/dist/esm/lib/ApplicationOf.js +11 -0
- package/dist/esm/lib/Controller.js +61 -0
- package/dist/esm/lib/ControllerEntrypoint.js +229 -0
- package/dist/esm/lib/GetActionDTOAndOptions.js +46 -0
- package/dist/esm/lib/HTTPErrors.js +48 -0
- package/dist/esm/lib/Middleware.js +134 -0
- package/dist/esm/lib/Prefix.js +143 -0
- package/dist/esm/lib/Result.js +147 -0
- package/dist/esm/lib/ServiceResponseCodec.js +113 -0
- package/dist/esm/lib/StringifyPattern.js +55 -0
- package/dist/esm/lib/UnsetValue.js +213 -0
- package/dist/esm/lib/pattern/GlobPattern.js +64 -0
- package/dist/esm/lib/pattern/PatternManager.js +229 -0
- package/dist/esm/lib/schedule/Cron.js +277 -0
- package/dist/esm/types/CLIContextParams.js +1 -0
- package/dist/esm/types/ErrorDescription.js +1 -0
- package/dist/esm/types/HTTPContextParams.js +1 -0
- package/dist/esm/types/ScheduleContextParams.js +1 -0
- package/dist/esm/types/ServiceContextParams.js +1 -0
- package/dist/types/Entrypoint.d.ts +868 -0
- package/dist/types/adapter/commander/CommanderCLIEntrypoint.d.ts +192 -0
- package/dist/types/adapter/commander/Prompt.d.ts +22 -0
- package/dist/types/adapter/commander/Width.d.ts +12 -0
- package/dist/types/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +203 -0
- package/dist/types/adapter/express/ExpressHTTPEntrypoint.d.ts +172 -0
- package/dist/types/adapter/express/Responders.d.ts +89 -0
- package/dist/types/adapter/express/StaticUI.d.ts +83 -0
- package/dist/types/adapter/fastify/FastifyHTTPEntrypoint.d.ts +156 -0
- package/dist/types/adapter/fastify/Responders.d.ts +88 -0
- package/dist/types/adapter/http/HTTPServiceEntrypoint.d.ts +117 -0
- package/dist/types/adapter/jsonrpc/JSONRPCProtocol.d.ts +194 -0
- package/dist/types/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +154 -0
- package/dist/types/adapter/schedule/ScheduleEntrypoint.d.ts +86 -0
- package/dist/types/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +138 -0
- package/dist/types/channel/BuiltinChannels.d.ts +18 -0
- package/dist/types/channel/Channel.d.ts +196 -0
- package/dist/types/channel/EntrypointAdapter.d.ts +195 -0
- package/dist/types/context/BaseContext.d.ts +75 -0
- package/dist/types/context/CLIContext.d.ts +38 -0
- package/dist/types/context/HTTPContext.d.ts +52 -0
- package/dist/types/context/ScheduleContext.d.ts +47 -0
- package/dist/types/context/ServiceContext.d.ts +47 -0
- package/dist/types/decorators/CLIAction.d.ts +120 -0
- package/dist/types/decorators/HTTPAction.d.ts +178 -0
- package/dist/types/decorators/Prefix.d.ts +44 -0
- package/dist/types/decorators/Schedule.d.ts +120 -0
- package/dist/types/decorators/ServiceAction.d.ts +116 -0
- package/dist/types/decorators/UseMiddlewares.d.ts +52 -0
- package/dist/types/decorators/http/DELETE.d.ts +87 -0
- package/dist/types/decorators/http/GET.d.ts +89 -0
- package/dist/types/decorators/http/HEAD.d.ts +84 -0
- package/dist/types/decorators/http/OPTIONS.d.ts +80 -0
- package/dist/types/decorators/http/PATCH.d.ts +90 -0
- package/dist/types/decorators/http/POST.d.ts +91 -0
- package/dist/types/decorators/http/PUT.d.ts +92 -0
- package/dist/types/exceptions/AccessDeniedException.d.ts +10 -0
- package/dist/types/exceptions/ControllerActionNotFoundException.d.ts +9 -0
- package/dist/types/exceptions/DuplicateActionNameException.d.ts +8 -0
- package/dist/types/exceptions/EntrypointStoppedException.d.ts +9 -0
- package/dist/types/exceptions/InvalidActionGroupException.d.ts +4 -0
- package/dist/types/exceptions/InvalidChannelException.d.ts +8 -0
- package/dist/types/exceptions/InvalidEntrypointAdapterException.d.ts +9 -0
- package/dist/types/exceptions/InvalidMiddlewareException.d.ts +8 -0
- package/dist/types/exceptions/InvalidPrefixException.d.ts +8 -0
- package/dist/types/exceptions/MiddlewareNextCalledException.d.ts +8 -0
- package/dist/types/exceptions/RouteNotFoundException.d.ts +9 -0
- package/dist/types/exceptions/ServiceInvokeException.d.ts +11 -0
- package/dist/types/exports/CTRLDecorators.d.ts +13 -0
- package/dist/types/exports/Channel.d.ts +14 -0
- package/dist/types/exports/CommanderAdapter.d.ts +1 -0
- package/dist/types/exports/ElectronAdapter.d.ts +1 -0
- package/dist/types/exports/Entrypoint.d.ts +24 -0
- package/dist/types/exports/ExpressAdapter.d.ts +4 -0
- package/dist/types/exports/FastifyAdapter.d.ts +4 -0
- package/dist/types/exports/JSONRPCAdapter.d.ts +2 -0
- package/dist/types/exports/SocketIOAdapter.d.ts +1 -0
- package/dist/types/lib/AccessControl.d.ts +118 -0
- package/dist/types/lib/AccessControlRule.d.ts +91 -0
- package/dist/types/lib/ActionOptions.d.ts +67 -0
- package/dist/types/lib/ActionResult.d.ts +28 -0
- package/dist/types/lib/ApplicationOf.d.ts +6 -0
- package/dist/types/lib/Controller.d.ts +59 -0
- package/dist/types/lib/ControllerEntrypoint.d.ts +168 -0
- package/dist/types/lib/GetActionDTOAndOptions.d.ts +29 -0
- package/dist/types/lib/HTTPErrors.d.ts +55 -0
- package/dist/types/lib/Middleware.d.ts +146 -0
- package/dist/types/lib/Prefix.d.ts +98 -0
- package/dist/types/lib/Result.d.ts +173 -0
- package/dist/types/lib/ServiceResponseCodec.d.ts +82 -0
- package/dist/types/lib/StringifyPattern.d.ts +7 -0
- package/dist/types/lib/UnsetValue.d.ts +12 -0
- package/dist/types/lib/pattern/GlobPattern.d.ts +34 -0
- package/dist/types/lib/pattern/PatternManager.d.ts +103 -0
- package/dist/types/lib/schedule/Cron.d.ts +56 -0
- package/dist/types/types/CLIContextParams.d.ts +14 -0
- package/dist/types/types/ErrorDescription.d.ts +14 -0
- package/dist/types/types/HTTPContextParams.d.ts +27 -0
- package/dist/types/types/ScheduleContextParams.d.ts +21 -0
- package/dist/types/types/ServiceContextParams.d.ts +16 -0
- package/package.json +152 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { Command, CommanderError, InvalidArgumentError, Option } from 'commander';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { applicationOf } from '../../lib/ApplicationOf.js';
|
|
5
|
+
import { CLIContext } from '../../context/CLIContext.js';
|
|
6
|
+
import { CLIChannel } from '../../channel/BuiltinChannels.js';
|
|
7
|
+
import { EventsResult, FileResult, StatusResult } from '../../lib/Result.js';
|
|
8
|
+
import { loadPrompts, prompt } from './Prompt.js';
|
|
9
|
+
import { boxWrap, displayWidth } from './Width.js';
|
|
10
|
+
/**
|
|
11
|
+
* The English texts, their arguments filled
|
|
12
|
+
* @param text
|
|
13
|
+
*/
|
|
14
|
+
function english(text) {
|
|
15
|
+
return text.text.replace(/\{(\w+)\}/g, (all, name) => text.params?.[name] ?? all);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The keys of the titles of the help of commander
|
|
19
|
+
*/
|
|
20
|
+
const TITLES = {
|
|
21
|
+
'Usage:': 'help.usage',
|
|
22
|
+
'Arguments:': 'help.arguments',
|
|
23
|
+
'Options:': 'help.options',
|
|
24
|
+
'Commands:': 'help.commands',
|
|
25
|
+
'Global Options:': 'help.globalOptions'
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The type of a property, without null
|
|
29
|
+
* @param schema
|
|
30
|
+
*/
|
|
31
|
+
function typeOf(schema) {
|
|
32
|
+
const types = [].concat(schema.type ?? []).filter((type) => type !== 'null');
|
|
33
|
+
if (types.length)
|
|
34
|
+
return types[0];
|
|
35
|
+
if (schema.enum?.length)
|
|
36
|
+
return typeof schema.enum[0];
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The kebab case of a property name (deviceId → device-id)
|
|
41
|
+
* @param name
|
|
42
|
+
*/
|
|
43
|
+
function kebabCase(name) {
|
|
44
|
+
return name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/_/g, '-').toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse a value of an option by its schema
|
|
48
|
+
* @param schema
|
|
49
|
+
* @param value
|
|
50
|
+
*/
|
|
51
|
+
function parseValue(schema, value) {
|
|
52
|
+
let parsed;
|
|
53
|
+
switch (typeOf(schema)) {
|
|
54
|
+
case 'integer':
|
|
55
|
+
case 'number': {
|
|
56
|
+
parsed = Number(value);
|
|
57
|
+
if (value.trim() === '' || Number.isNaN(parsed))
|
|
58
|
+
throw new InvalidArgumentError('Not a number.');
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case 'boolean': {
|
|
62
|
+
if (!['true', 'false'].includes(value))
|
|
63
|
+
throw new InvalidArgumentError('Not a boolean.');
|
|
64
|
+
parsed = value === 'true';
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case 'object': {
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(value);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw new InvalidArgumentError('Not a JSON object.');
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
default:
|
|
77
|
+
parsed = value;
|
|
78
|
+
}
|
|
79
|
+
if (schema.enum && !schema.enum.includes(parsed))
|
|
80
|
+
throw new InvalidArgumentError(`Allowed choices are ${schema.enum.map((choice) => JSON.stringify(choice)).join(', ')}.`);
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The option of a property
|
|
85
|
+
* @param property
|
|
86
|
+
* @param schema
|
|
87
|
+
* @param mandatory
|
|
88
|
+
* @param say the text of the help in the language of the user
|
|
89
|
+
* @param command the name of the command
|
|
90
|
+
*/
|
|
91
|
+
function propertyOption(property, schema, mandatory, say, command) {
|
|
92
|
+
const flag = `--${kebabCase(property)}`;
|
|
93
|
+
//A boolean or a value (DTO.Alternatives(DTO.Boolean(), …)): a flag taking an optional value
|
|
94
|
+
const alternatives = Array.isArray(schema.anyOf) ? schema.anyOf : [];
|
|
95
|
+
const valued = alternatives.some((alternative) => typeOf(alternative) === 'boolean')
|
|
96
|
+
? alternatives.find((alternative) => typeOf(alternative) !== 'boolean')
|
|
97
|
+
: undefined;
|
|
98
|
+
const hints = [];
|
|
99
|
+
const choices = schema.enum ?? schema.items?.enum ?? valued?.enum;
|
|
100
|
+
if (choices)
|
|
101
|
+
hints.push(say('help.choices', 'choices: {choices}', { choices: choices.map((choice) => JSON.stringify(choice)).join(', ') }));
|
|
102
|
+
if (schema.default !== undefined)
|
|
103
|
+
hints.push(say('help.default', 'default: {value}', { value: JSON.stringify(schema.default) }));
|
|
104
|
+
const text = schema.description ? say(`commands.${command}.options.${property}`, schema.description) : '';
|
|
105
|
+
//The hints after the description, " (default: 1)"
|
|
106
|
+
const description = hints.length ? `${text}${say('help.hints', ' ({hints})', { hints: hints.join(say('help.separator', ', ')) })}`.trim() : text;
|
|
107
|
+
//--flag gives true, --flag <value> the value
|
|
108
|
+
if (valued)
|
|
109
|
+
return [new Option(`${flag} [value]`, description).argParser((value) => parseValue(valued, value))];
|
|
110
|
+
const type = typeOf(schema);
|
|
111
|
+
if (type === 'boolean') {
|
|
112
|
+
const options = [new Option(flag, description)];
|
|
113
|
+
//A flag on by default is turned off by its negation
|
|
114
|
+
if (schema.default === true)
|
|
115
|
+
options.push(new Option(`--no-${kebabCase(property)}`, say('help.negate', 'Turn off {flag}', { flag: flag })));
|
|
116
|
+
return options;
|
|
117
|
+
}
|
|
118
|
+
const option = type === 'array'
|
|
119
|
+
? new Option(`${flag} <values...>`, description).argParser((value, previous) => [...(Array.isArray(previous) ? previous : []), parseValue(schema.items ?? {}, value)])
|
|
120
|
+
: new Option(`${flag} <${type === 'object' ? 'json' : 'value'}>`, description).argParser((value) => parseValue(schema, value));
|
|
121
|
+
return [option.makeOptionMandatory(mandatory)];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The default format of a result
|
|
125
|
+
* @param result
|
|
126
|
+
*/
|
|
127
|
+
function defaultFormat(result) {
|
|
128
|
+
if (result === undefined || result === null)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (typeof result === 'string')
|
|
131
|
+
return result.endsWith('\n') ? result : `${result}\n`;
|
|
132
|
+
if (result instanceof Uint8Array)
|
|
133
|
+
return result;
|
|
134
|
+
return `${JSON.stringify(result, (key, value) => typeof value === 'bigint' ? value.toString() : value, 2)}\n`;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The message of an error
|
|
138
|
+
* @param error
|
|
139
|
+
*/
|
|
140
|
+
function errorMessage(error) {
|
|
141
|
+
if (error && typeof error === 'object') {
|
|
142
|
+
const { errMsg, message } = error;
|
|
143
|
+
if (typeof errMsg === 'string' && errMsg)
|
|
144
|
+
return errMsg;
|
|
145
|
+
if (typeof message === 'string' && message)
|
|
146
|
+
return message;
|
|
147
|
+
}
|
|
148
|
+
return String(error);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Create the CLI adapter of an entrypoint, parsing the command-line arguments with commander: each CLI action
|
|
152
|
+
* (`@CLIAction`) becomes a command, and the properties of its input DTO become its options. It needs the optional peer
|
|
153
|
+
* dependency `commander` (and `@inquirer/prompts` for the interactive prompts).
|
|
154
|
+
*
|
|
155
|
+
* A property becomes a kebab-case option (`deviceId` → `--device-id <value>`), parsed by its type: a number, `true` or
|
|
156
|
+
* `false`, a JSON object, an array (`--tags <values...>`), a boolean as a flag (with a `--no-<name>` negation when it
|
|
157
|
+
* defaults to `true`); the enums, the defaults and the descriptions appear in the help. The command text may declare
|
|
158
|
+
* positional arguments, given to the properties of their names (`'copy <files...> [target]'`), and its leading words
|
|
159
|
+
* before the last one name its groups (`'package exe <file>'` is the command `exe` of the group `package`).
|
|
160
|
+
*
|
|
161
|
+
* The command runs once the application has launched (all its objects, its bootstrap and the other adapters are
|
|
162
|
+
* ready). Its result is written to stdout (see `format`; an `EventsResult` writes each event's data, a `FileResult`
|
|
163
|
+
* the file's bytes); a failure writes `error: <message>` to stderr and ends with the code 1. Then the application exits
|
|
164
|
+
* with the command's code (see `exit`). When the entrypoint stops first, a command not started yet does not run, and
|
|
165
|
+
* the prompts and the event streams end.
|
|
166
|
+
* @param options The adapter options; by default it parses `process.argv`, prompts in a terminal, and exits the
|
|
167
|
+
* application once the command ended.
|
|
168
|
+
* @returns The adapter, to give to the `cli` option of `buildEntrypoints`.
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* import {Application, DTO} from 'lakutata'
|
|
172
|
+
* import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
173
|
+
* import {CLIAction} from 'lakutata/decorator/ctrl'
|
|
174
|
+
* import {Expect} from 'lakutata/decorator/dto'
|
|
175
|
+
* import {commanderCLIEntrypoint} from 'lakutata/adapter/commander'
|
|
176
|
+
*
|
|
177
|
+
* class GreetDTO extends DTO {
|
|
178
|
+
* @Expect(DTO.String().required().description('The name to greet'))
|
|
179
|
+
* public name!: string
|
|
180
|
+
*
|
|
181
|
+
* @Expect(DTO.Number().integer().optional().default(1).description('How many times'))
|
|
182
|
+
* public times?: number
|
|
183
|
+
*
|
|
184
|
+
* @Expect(DTO.Boolean().optional().default(false))
|
|
185
|
+
* public loud?: boolean
|
|
186
|
+
* }
|
|
187
|
+
*
|
|
188
|
+
* class GreetController extends Controller {
|
|
189
|
+
* //tool greet Ada --times 2 --loud
|
|
190
|
+
* @CLIAction('greet <name>', GreetDTO)
|
|
191
|
+
* public async greet(input: GreetDTO): Promise<string> {
|
|
192
|
+
* const text: string = `Hello, ${input.name}`
|
|
193
|
+
* return (input.loud ? text.toUpperCase() : text).repeat(input.times ?? 1)
|
|
194
|
+
* }
|
|
195
|
+
* }
|
|
196
|
+
*
|
|
197
|
+
* Application.run({
|
|
198
|
+
* id: 'tool.app',
|
|
199
|
+
* name: 'Tool',
|
|
200
|
+
* components: {
|
|
201
|
+
* entrypoint: buildEntrypoints({
|
|
202
|
+
* controllers: [GreetController],
|
|
203
|
+
* cli: commanderCLIEntrypoint({bin: 'tool', description: 'A greeting tool', version: '1.0.0'})
|
|
204
|
+
* })
|
|
205
|
+
* }
|
|
206
|
+
* })
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export function commanderCLIEntrypoint(options = {}) {
|
|
210
|
+
const stdout = options.stdout ?? process.stdout;
|
|
211
|
+
const stderr = options.stderr ?? process.stderr;
|
|
212
|
+
const stdin = options.stdin ?? process.stdin;
|
|
213
|
+
const format = options.format ?? defaultFormat;
|
|
214
|
+
//Aborts the prompts and the event streams when the adapter stops
|
|
215
|
+
const stopController = new AbortController();
|
|
216
|
+
const write = (output) => {
|
|
217
|
+
if (output !== undefined && output.length)
|
|
218
|
+
stdout.write(output);
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Write the result of an action
|
|
222
|
+
* @param result
|
|
223
|
+
*/
|
|
224
|
+
const output = async (result) => {
|
|
225
|
+
if (result instanceof EventsResult) {
|
|
226
|
+
return await result.producer((data) => {
|
|
227
|
+
if (!stopController.signal.aborted)
|
|
228
|
+
write(format(data));
|
|
229
|
+
}, stopController.signal);
|
|
230
|
+
}
|
|
231
|
+
if (result instanceof FileResult) {
|
|
232
|
+
const source = result.source;
|
|
233
|
+
if (Buffer.isBuffer(source))
|
|
234
|
+
return write(source);
|
|
235
|
+
const stream = typeof source === 'string' ? createReadStream(path.resolve(source)) : source;
|
|
236
|
+
return await new Promise((resolve, reject) => {
|
|
237
|
+
stream.once('error', reject).once('end', resolve).pipe(stdout, { end: false });
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
write(format(result instanceof StatusResult ? result.data : result));
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Whether the missing options are prompted: the prompts installed (else the parser requires the options)
|
|
244
|
+
*/
|
|
245
|
+
const interactive = async () => {
|
|
246
|
+
if (options.interactive === false)
|
|
247
|
+
return false;
|
|
248
|
+
if (options.interactive !== true && !(stdin.isTTY && stdout.isTTY))
|
|
249
|
+
return false;
|
|
250
|
+
return !!(await loadPrompts());
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Run a command
|
|
254
|
+
* @param runtime
|
|
255
|
+
* @param action
|
|
256
|
+
* @param data
|
|
257
|
+
* @param prompting
|
|
258
|
+
* @param describe the description of a property in the language of the user
|
|
259
|
+
* @param say the text of the prompts in the language of the user
|
|
260
|
+
*/
|
|
261
|
+
const execute = async (runtime, action, data, prompting, describe, say) => {
|
|
262
|
+
try {
|
|
263
|
+
if (prompting)
|
|
264
|
+
await prompt(action, data, { input: stdin, output: stdout, signal: stopController.signal }, describe, say);
|
|
265
|
+
const context = new CLIContext({ command: action.address.command, data: data });
|
|
266
|
+
await output(await runtime.dispatchTo(action, context));
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
stderr.write(`error: ${errorMessage(error)}\n`);
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Parse the arguments and run the command
|
|
276
|
+
* @param runtime
|
|
277
|
+
*/
|
|
278
|
+
const run = async (runtime) => {
|
|
279
|
+
const argv = options.argv ?? process.argv.slice(2);
|
|
280
|
+
if (!argv.length && options.noCommand === 'ignore')
|
|
281
|
+
return undefined;
|
|
282
|
+
const prompting = await interactive();
|
|
283
|
+
//The texts of the help and of the prompts in the language of the user
|
|
284
|
+
const translate = options.translate ? await options.translate(runtime.module) : english;
|
|
285
|
+
const say = (key, text, params) => translate({ key: key, text: text, params: params });
|
|
286
|
+
const program = new Command(options.bin ?? path.basename(process.argv[1] ?? 'cli', path.extname(process.argv[1] ?? '')))
|
|
287
|
+
.exitOverride()
|
|
288
|
+
.configureOutput({
|
|
289
|
+
writeOut: (text) => stdout.write(text),
|
|
290
|
+
writeErr: (text) => stderr.write(text)
|
|
291
|
+
});
|
|
292
|
+
if (options.translate) {
|
|
293
|
+
//Inherited by the commands created next
|
|
294
|
+
program
|
|
295
|
+
.configureHelp({
|
|
296
|
+
styleTitle: (title) => TITLES[title] ? say(TITLES[title], title) : title,
|
|
297
|
+
//The widths of the wide characters (Chinese, Japanese, Korean), their lines wrapped between them
|
|
298
|
+
displayWidth: displayWidth,
|
|
299
|
+
boxWrap: (text, width) => boxWrap(text, width)
|
|
300
|
+
})
|
|
301
|
+
.helpOption('-h, --help', say('help.helpOption', 'display help for command'));
|
|
302
|
+
if (runtime.actions.length)
|
|
303
|
+
program.helpCommand('help [command]', say('help.helpCommand', 'display help for command'));
|
|
304
|
+
}
|
|
305
|
+
if (options.description)
|
|
306
|
+
program.description(say('description', options.description));
|
|
307
|
+
/**
|
|
308
|
+
* A group of commands, its help listing them
|
|
309
|
+
* @param parent
|
|
310
|
+
* @param group its name
|
|
311
|
+
* @param names the names of its groups and its own
|
|
312
|
+
*/
|
|
313
|
+
const commandGroup = (parent, group, names) => {
|
|
314
|
+
const created = parent.command(group);
|
|
315
|
+
const description = options.groups?.[names.join(' ')];
|
|
316
|
+
if (description)
|
|
317
|
+
created.description(say(`commands.${names.join('.')}.description`, description));
|
|
318
|
+
if (options.translate)
|
|
319
|
+
created.helpCommand('help [command]', say('help.helpCommand', 'display help for command'));
|
|
320
|
+
return created;
|
|
321
|
+
};
|
|
322
|
+
if (options.version)
|
|
323
|
+
program.version(options.version, '-V, --version', say('help.versionOption', 'output the version number'));
|
|
324
|
+
let code = 0;
|
|
325
|
+
for (const action of runtime.actions) {
|
|
326
|
+
const schema = action.jsonSchema;
|
|
327
|
+
const required = new Set(schema.required ?? []);
|
|
328
|
+
//The command may declare positional arguments ("run <script>", "copy <files...> [target]"), given to the properties of their names;
|
|
329
|
+
//its words before them but the last are its groups ("package exe <file>": the command exe of the group package)
|
|
330
|
+
const words = action.address.command.trim().split(/\s+/);
|
|
331
|
+
const first = words.findIndex((word) => /^[<[]/.test(word));
|
|
332
|
+
const names = first < 0 ? words : words.slice(0, first);
|
|
333
|
+
let parent = program;
|
|
334
|
+
names.slice(0, -1).forEach((group, index) => {
|
|
335
|
+
parent = parent.commands.find((existing) => existing.name() === group) ?? commandGroup(parent, group, names.slice(0, index + 1));
|
|
336
|
+
});
|
|
337
|
+
const command = parent.command([names.at(-1), ...words.slice(names.length)].join(' ')).allowExcessArguments(false);
|
|
338
|
+
//The key of its texts: the names of its groups and its own
|
|
339
|
+
const name = names.join('.');
|
|
340
|
+
command.description(action.description ? say(`commands.${name}.description`, action.description) : action.name);
|
|
341
|
+
const positional = command.registeredArguments.map((argument) => argument.name());
|
|
342
|
+
//The description of a property: the one of its argument or of its option
|
|
343
|
+
const describe = (property) => {
|
|
344
|
+
const text = schema.properties?.[property]?.description;
|
|
345
|
+
return text ? say(`commands.${name}.${positional.includes(property) ? 'arguments' : 'options'}.${property}`, text) : undefined;
|
|
346
|
+
};
|
|
347
|
+
for (const argument of command.registeredArguments) {
|
|
348
|
+
const propertySchema = schema.properties?.[argument.name()];
|
|
349
|
+
if (!propertySchema)
|
|
350
|
+
continue;
|
|
351
|
+
if (propertySchema.description)
|
|
352
|
+
argument.description = describe(argument.name());
|
|
353
|
+
if (argument.variadic) {
|
|
354
|
+
argument.argParser((value, previous) => [...(Array.isArray(previous) ? previous : []), parseValue(propertySchema.items ?? {}, value)]);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
argument.argParser((value) => parseValue(propertySchema, value));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
//The properties by the attribute names of their options
|
|
361
|
+
const properties = new Map();
|
|
362
|
+
for (const [property, propertySchema] of Object.entries(schema.properties ?? {})) {
|
|
363
|
+
if (positional.includes(property))
|
|
364
|
+
continue;
|
|
365
|
+
const mandatory = !prompting && required.has(property) && propertySchema.default === undefined;
|
|
366
|
+
for (const option of propertyOption(property, propertySchema, mandatory, say, name)) {
|
|
367
|
+
command.addOption(option);
|
|
368
|
+
properties.set(option.attributeName(), property);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
command.action(async () => {
|
|
372
|
+
const data = {};
|
|
373
|
+
positional.forEach((name, index) => {
|
|
374
|
+
const value = command.processedArgs[index];
|
|
375
|
+
if (value !== undefined)
|
|
376
|
+
data[name] = value;
|
|
377
|
+
});
|
|
378
|
+
for (const [attribute, value] of Object.entries(command.opts())) {
|
|
379
|
+
if (value !== undefined && properties.has(attribute))
|
|
380
|
+
data[properties.get(attribute)] = value;
|
|
381
|
+
}
|
|
382
|
+
code = await execute(runtime, action, data, prompting, describe, say);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
await options.configure?.(program);
|
|
386
|
+
try {
|
|
387
|
+
await program.parseAsync(argv, { from: 'user' });
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
if (error instanceof CommanderError)
|
|
391
|
+
return error.exitCode;
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
return code;
|
|
395
|
+
};
|
|
396
|
+
/**
|
|
397
|
+
* End with the code of the command
|
|
398
|
+
* @param runtime
|
|
399
|
+
* @param code
|
|
400
|
+
*/
|
|
401
|
+
const exit = async (runtime, code) => {
|
|
402
|
+
const mode = options.exit ?? 'application';
|
|
403
|
+
if (typeof mode === 'function')
|
|
404
|
+
return await mode(code);
|
|
405
|
+
if (mode === 'none')
|
|
406
|
+
return;
|
|
407
|
+
const application = mode === 'application' ? applicationOf(runtime.module) : undefined;
|
|
408
|
+
if (application)
|
|
409
|
+
return application.exit(code);
|
|
410
|
+
process.exitCode = code;
|
|
411
|
+
};
|
|
412
|
+
return {
|
|
413
|
+
channel: 'cli',
|
|
414
|
+
definition: CLIChannel,
|
|
415
|
+
name: options.name,
|
|
416
|
+
middlewares: options.middlewares,
|
|
417
|
+
start: (runtime) => {
|
|
418
|
+
//The command runs once the application launched: every object, the bootstrap and the other adapters are ready
|
|
419
|
+
runtime.ready
|
|
420
|
+
.then(() => stopController.signal.aborted ? undefined : run(runtime))
|
|
421
|
+
.catch((error) => {
|
|
422
|
+
stderr.write(`error: ${errorMessage(error)}\n`);
|
|
423
|
+
return 1;
|
|
424
|
+
})
|
|
425
|
+
.then((code) => code === undefined || stopController.signal.aborted ? undefined : exit(runtime, code))
|
|
426
|
+
.catch((error) => stderr.write(`error: ${errorMessage(error)}\n`));
|
|
427
|
+
},
|
|
428
|
+
stop: () => {
|
|
429
|
+
//A command not started yet is not run, the prompts and the event streams are ended
|
|
430
|
+
stopController.abort();
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { PassThrough } from 'node:stream';
|
|
2
|
+
import { DTO } from '@lakutata/core';
|
|
3
|
+
/**
|
|
4
|
+
* The property names of the secrets, prompted without echo
|
|
5
|
+
*/
|
|
6
|
+
const SECRET = /pass(word)?|secret|token|api[-_]?key|credential/i;
|
|
7
|
+
/**
|
|
8
|
+
* The prompts, undefined when @inquirer/prompts is not installed
|
|
9
|
+
*/
|
|
10
|
+
let prompts;
|
|
11
|
+
/**
|
|
12
|
+
* Load the prompts
|
|
13
|
+
*/
|
|
14
|
+
export function loadPrompts() {
|
|
15
|
+
prompts ??= import('@inquirer/prompts').catch(() => undefined);
|
|
16
|
+
return prompts;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The type of a property, without null
|
|
20
|
+
* @param schema
|
|
21
|
+
*/
|
|
22
|
+
function typeOf(schema) {
|
|
23
|
+
const types = [].concat(schema.type ?? []).filter((type) => type !== 'null');
|
|
24
|
+
if (types.length)
|
|
25
|
+
return types[0];
|
|
26
|
+
if (schema.enum?.length)
|
|
27
|
+
return typeof schema.enum[0];
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate an answer by the rules of its property in the DTO of the action
|
|
32
|
+
* @param dto
|
|
33
|
+
* @param property
|
|
34
|
+
*/
|
|
35
|
+
function validator(dto, property) {
|
|
36
|
+
let field;
|
|
37
|
+
try {
|
|
38
|
+
field = dto.Schema().extract(property).label(property);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
//A property without its own rule is validated with the whole input
|
|
42
|
+
return async () => true;
|
|
43
|
+
}
|
|
44
|
+
return async (value) => {
|
|
45
|
+
try {
|
|
46
|
+
await DTO.validateAsync(value, field);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return error.errMsg ?? error.message;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Prompt for the required properties missing from the input of an action, when @inquirer/prompts is installed
|
|
56
|
+
* @param action
|
|
57
|
+
* @param data
|
|
58
|
+
* @param streams
|
|
59
|
+
* @param describe the description of a property in the language of the user
|
|
60
|
+
* @param say the text of the prompts in the language of the user
|
|
61
|
+
*/
|
|
62
|
+
export async function prompt(action, data, streams, describe = (property) => action.jsonSchema.properties?.[property]?.description, say = (key, text) => text) {
|
|
63
|
+
const schema = action.jsonSchema;
|
|
64
|
+
const missing = (schema.required ?? []).filter((property) => data[property] === undefined && schema.properties?.[property]?.default === undefined);
|
|
65
|
+
if (!missing.length)
|
|
66
|
+
return;
|
|
67
|
+
const inquirer = await loadPrompts();
|
|
68
|
+
if (!inquirer)
|
|
69
|
+
return;
|
|
70
|
+
for (const property of missing) {
|
|
71
|
+
//A prompt ends its output once answered, the output of the adapter is kept open
|
|
72
|
+
const output = new PassThrough();
|
|
73
|
+
output.pipe(streams.output, { end: false });
|
|
74
|
+
try {
|
|
75
|
+
data[property] = await ask(inquirer, action, property, schema.properties?.[property] ?? {}, { input: streams.input, output: output, signal: streams.signal }, describe(property), say);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
output.unpipe(streams.output);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Ask for a property
|
|
84
|
+
* @param inquirer
|
|
85
|
+
* @param action
|
|
86
|
+
* @param property
|
|
87
|
+
* @param propertySchema
|
|
88
|
+
* @param context
|
|
89
|
+
* @param description the description of the property in the language of the user
|
|
90
|
+
* @param say the text of the prompts in the language of the user
|
|
91
|
+
*/
|
|
92
|
+
async function ask(inquirer, action, property, propertySchema, context, description, say) {
|
|
93
|
+
const message = description ? `${description} (${property})` : property;
|
|
94
|
+
const validate = validator(action.dto, property);
|
|
95
|
+
const type = typeOf(propertySchema);
|
|
96
|
+
const choices = (values) => values.map((value) => ({ name: String(value), value: value }));
|
|
97
|
+
if (type === 'boolean')
|
|
98
|
+
return await inquirer.confirm({ message: message }, context);
|
|
99
|
+
if (propertySchema.enum)
|
|
100
|
+
return await inquirer.select({ message: message, choices: choices(propertySchema.enum) }, context);
|
|
101
|
+
if (type === 'array' && propertySchema.items?.enum)
|
|
102
|
+
return await inquirer.checkbox({ message: message, choices: choices(propertySchema.items.enum), validate: (selected) => validate(selected.map((choice) => choice.value)) }, context);
|
|
103
|
+
if (type === 'number' || type === 'integer')
|
|
104
|
+
return await inquirer.number({ message: message, required: true, step: type === 'integer' ? 1 : 'any', validate: validate }, context);
|
|
105
|
+
if (type === 'string' && (propertySchema.format === 'password' || SECRET.test(property)))
|
|
106
|
+
return await inquirer.password({ message: message, mask: '*', validate: validate }, context);
|
|
107
|
+
//The arrays are separated by commas, the objects are written in JSON
|
|
108
|
+
const parse = (answer) => {
|
|
109
|
+
if (type === 'array')
|
|
110
|
+
return answer.split(',').map((item) => item.trim()).filter((item) => !!item);
|
|
111
|
+
if (type === 'object')
|
|
112
|
+
return JSON.parse(answer);
|
|
113
|
+
return answer;
|
|
114
|
+
};
|
|
115
|
+
return parse(await inquirer.input({
|
|
116
|
+
message: message,
|
|
117
|
+
required: true,
|
|
118
|
+
validate: async (value) => {
|
|
119
|
+
try {
|
|
120
|
+
return await validate(parse(value));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return say('prompt.json', 'Not a JSON object');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}, context));
|
|
127
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
2
|
+
/**
|
|
3
|
+
* The wide characters of the terminals: the Chinese, Japanese and Korean ones, the full width forms
|
|
4
|
+
*/
|
|
5
|
+
const WIDE = '\\u1100-\\u115F\\u2E80-\\u303E\\u3041-\\u33FF\\u3400-\\u4DBF\\u4E00-\\u9FFF\\uA000-\\uA4CF\\uAC00-\\uD7A3\\uF900-\\uFAFF\\uFE30-\\uFE4F\\uFF00-\\uFF60\\uFFE0-\\uFFE6\\u{20000}-\\u{3FFFD}';
|
|
6
|
+
/**
|
|
7
|
+
* The punctuation which does not start a line
|
|
8
|
+
*/
|
|
9
|
+
const CLOSING = '、。,.:;!?)」』】〕〉》%,.:;!?)\\]}';
|
|
10
|
+
/**
|
|
11
|
+
* The punctuation which does not end a line
|
|
12
|
+
*/
|
|
13
|
+
const OPENING = '(「『【〔〈《(\\[{';
|
|
14
|
+
const WIDE_CHARACTER = new RegExp(`[${WIDE}]`, 'u');
|
|
15
|
+
/**
|
|
16
|
+
* The chunks of a line the wrapping keeps whole: a word, or a wide character, with the punctuation opening and
|
|
17
|
+
* closing it
|
|
18
|
+
*/
|
|
19
|
+
const CHUNK = new RegExp(`\\s*[${OPENING}]*(?:[${WIDE}]|[^\\s${WIDE}]+)[${CLOSING}]*`, 'gu');
|
|
20
|
+
/**
|
|
21
|
+
* The width of a text in a terminal, the wide characters taking two columns
|
|
22
|
+
* @param text
|
|
23
|
+
*/
|
|
24
|
+
export function displayWidth(text) {
|
|
25
|
+
let width = 0;
|
|
26
|
+
for (const character of stripVTControlCharacters(text))
|
|
27
|
+
width += WIDE_CHARACTER.test(character) ? 2 : 1;
|
|
28
|
+
return width;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Wrap a text to a width, as commander does, the lines of wide characters wrapped between them
|
|
32
|
+
* @param text
|
|
33
|
+
* @param width
|
|
34
|
+
* @param minimum the width under which the text is not wrapped
|
|
35
|
+
*/
|
|
36
|
+
export function boxWrap(text, width, minimum = 40) {
|
|
37
|
+
if (width < minimum)
|
|
38
|
+
return text;
|
|
39
|
+
const wrapped = [];
|
|
40
|
+
for (const line of text.split(/\r\n|\n/)) {
|
|
41
|
+
const chunks = line.match(CHUNK);
|
|
42
|
+
if (!chunks) {
|
|
43
|
+
wrapped.push('');
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
let current = chunks.shift();
|
|
47
|
+
let currentWidth = displayWidth(current);
|
|
48
|
+
for (const chunk of chunks) {
|
|
49
|
+
const chunkWidth = displayWidth(chunk);
|
|
50
|
+
if (currentWidth + chunkWidth <= width) {
|
|
51
|
+
current += chunk;
|
|
52
|
+
currentWidth += chunkWidth;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
wrapped.push(current);
|
|
56
|
+
//The space at the break removed
|
|
57
|
+
current = chunk.trimStart();
|
|
58
|
+
currentWidth = displayWidth(current);
|
|
59
|
+
}
|
|
60
|
+
wrapped.push(current);
|
|
61
|
+
}
|
|
62
|
+
return wrapped.join('\n');
|
|
63
|
+
}
|