@c9up/ream 0.1.25 → 0.1.27

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.
Files changed (302) hide show
  1. package/README.md +65 -0
  2. package/dist/ConfigLoader.d.ts +46 -5
  3. package/dist/ConfigLoader.d.ts.map +1 -1
  4. package/dist/ConfigLoader.js +60 -9
  5. package/dist/ConfigLoader.js.map +1 -1
  6. package/dist/HttpKernel.d.ts.map +1 -1
  7. package/dist/HttpKernel.js +46 -6
  8. package/dist/HttpKernel.js.map +1 -1
  9. package/dist/Ignitor.d.ts +115 -7
  10. package/dist/Ignitor.d.ts.map +1 -1
  11. package/dist/Ignitor.js +254 -40
  12. package/dist/Ignitor.js.map +1 -1
  13. package/dist/console/BaseCommand.d.ts +217 -0
  14. package/dist/console/BaseCommand.d.ts.map +1 -0
  15. package/dist/console/BaseCommand.js +340 -0
  16. package/dist/console/BaseCommand.js.map +1 -0
  17. package/dist/console/Console.d.ts +86 -0
  18. package/dist/console/Console.d.ts.map +1 -0
  19. package/dist/console/Console.js +134 -0
  20. package/dist/console/Console.js.map +1 -0
  21. package/dist/console/ExceptionHandler.d.ts +28 -0
  22. package/dist/console/ExceptionHandler.d.ts.map +1 -0
  23. package/dist/console/ExceptionHandler.js +76 -0
  24. package/dist/console/ExceptionHandler.js.map +1 -0
  25. package/dist/console/HelpCommand.d.ts +16 -0
  26. package/dist/console/HelpCommand.d.ts.map +1 -0
  27. package/dist/console/HelpCommand.js +40 -0
  28. package/dist/console/HelpCommand.js.map +1 -0
  29. package/dist/console/IndexGenerator.d.ts +24 -0
  30. package/dist/console/IndexGenerator.d.ts.map +1 -0
  31. package/dist/console/IndexGenerator.js +65 -0
  32. package/dist/console/IndexGenerator.js.map +1 -0
  33. package/dist/console/Kernel.d.ts +298 -0
  34. package/dist/console/Kernel.d.ts.map +1 -0
  35. package/dist/console/Kernel.js +1120 -0
  36. package/dist/console/Kernel.js.map +1 -0
  37. package/dist/console/ListCommand.d.ts +18 -0
  38. package/dist/console/ListCommand.d.ts.map +1 -0
  39. package/dist/console/ListCommand.js +60 -0
  40. package/dist/console/ListCommand.js.map +1 -0
  41. package/dist/console/assertions.d.ts +20 -0
  42. package/dist/console/assertions.d.ts.map +1 -0
  43. package/dist/console/assertions.js +84 -0
  44. package/dist/console/assertions.js.map +1 -0
  45. package/dist/console/cliui.d.ts +248 -0
  46. package/dist/console/cliui.d.ts.map +1 -0
  47. package/dist/console/cliui.js +587 -0
  48. package/dist/console/cliui.js.map +1 -0
  49. package/dist/console/decorators.d.ts +90 -0
  50. package/dist/console/decorators.d.ts.map +1 -0
  51. package/dist/console/decorators.js +142 -0
  52. package/dist/console/decorators.js.map +1 -0
  53. package/dist/console/index.d.ts +32 -0
  54. package/dist/console/index.d.ts.map +1 -0
  55. package/dist/console/index.js +24 -0
  56. package/dist/console/index.js.map +1 -0
  57. package/dist/console/loaders.d.ts +52 -0
  58. package/dist/console/loaders.d.ts.map +1 -0
  59. package/dist/console/loaders.js +131 -0
  60. package/dist/console/loaders.js.map +1 -0
  61. package/dist/console/parser.d.ts +86 -0
  62. package/dist/console/parser.d.ts.map +1 -0
  63. package/dist/console/parser.js +440 -0
  64. package/dist/console/parser.js.map +1 -0
  65. package/dist/console/prompts.d.ts +124 -0
  66. package/dist/console/prompts.d.ts.map +1 -0
  67. package/dist/console/prompts.js +356 -0
  68. package/dist/console/prompts.js.map +1 -0
  69. package/dist/console/tracing.d.ts +17 -0
  70. package/dist/console/tracing.d.ts.map +1 -0
  71. package/dist/console/tracing.js +11 -0
  72. package/dist/console/tracing.js.map +1 -0
  73. package/dist/console/types.d.ts +255 -0
  74. package/dist/console/types.d.ts.map +1 -0
  75. package/dist/console/types.js +25 -0
  76. package/dist/console/types.js.map +1 -0
  77. package/dist/console/ui.d.ts +20 -0
  78. package/dist/console/ui.d.ts.map +1 -0
  79. package/dist/console/ui.js +31 -0
  80. package/dist/console/ui.js.map +1 -0
  81. package/dist/console/utils.d.ts +27 -0
  82. package/dist/console/utils.d.ts.map +1 -0
  83. package/dist/console/utils.js +56 -0
  84. package/dist/console/utils.js.map +1 -0
  85. package/dist/container/Container.d.ts +23 -3
  86. package/dist/container/Container.d.ts.map +1 -1
  87. package/dist/container/Container.js +177 -70
  88. package/dist/container/Container.js.map +1 -1
  89. package/dist/container/types.d.ts +14 -2
  90. package/dist/container/types.d.ts.map +1 -1
  91. package/dist/decorators/Service.d.ts +7 -0
  92. package/dist/decorators/Service.d.ts.map +1 -1
  93. package/dist/decorators/Service.js +23 -3
  94. package/dist/decorators/Service.js.map +1 -1
  95. package/dist/env/Env.d.ts +29 -4
  96. package/dist/env/Env.d.ts.map +1 -1
  97. package/dist/env/Env.js +27 -4
  98. package/dist/env/Env.js.map +1 -1
  99. package/dist/env/Secret.d.ts +11 -0
  100. package/dist/env/Secret.d.ts.map +1 -0
  101. package/dist/env/Secret.js +36 -0
  102. package/dist/env/Secret.js.map +1 -0
  103. package/dist/env/interpolate.d.ts +26 -0
  104. package/dist/env/interpolate.d.ts.map +1 -0
  105. package/dist/env/interpolate.js +48 -0
  106. package/dist/env/interpolate.js.map +1 -0
  107. package/dist/env/loadEnvFiles.d.ts.map +1 -1
  108. package/dist/env/loadEnvFiles.js +14 -4
  109. package/dist/env/loadEnvFiles.js.map +1 -1
  110. package/dist/env/schema.d.ts +23 -6
  111. package/dist/env/schema.d.ts.map +1 -1
  112. package/dist/env/schema.js +67 -33
  113. package/dist/env/schema.js.map +1 -1
  114. package/dist/errors/ReamError.d.ts +25 -0
  115. package/dist/errors/ReamError.d.ts.map +1 -1
  116. package/dist/errors/ReamError.js +5 -0
  117. package/dist/errors/ReamError.js.map +1 -1
  118. package/dist/errors/prettyPrintError.d.ts +9 -0
  119. package/dist/errors/prettyPrintError.d.ts.map +1 -0
  120. package/dist/errors/prettyPrintError.js +22 -0
  121. package/dist/errors/prettyPrintError.js.map +1 -0
  122. package/dist/events/Emitter.d.ts +1 -1
  123. package/dist/events/Emitter.d.ts.map +1 -1
  124. package/dist/events/Emitter.js +1 -1
  125. package/dist/events/Emitter.js.map +1 -1
  126. package/dist/events/EventsProvider.d.ts +2 -2
  127. package/dist/events/EventsProvider.d.ts.map +1 -1
  128. package/dist/events/EventsProvider.js +3 -3
  129. package/dist/events/EventsProvider.js.map +1 -1
  130. package/dist/graphql/GraphQLEngine.d.ts +1 -1
  131. package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
  132. package/dist/graphql/GraphQLEngine.js +2 -2
  133. package/dist/graphql/GraphQLEngine.js.map +1 -1
  134. package/dist/graphql/GraphQLProvider.d.ts.map +1 -1
  135. package/dist/graphql/GraphQLProvider.js +7 -3
  136. package/dist/graphql/GraphQLProvider.js.map +1 -1
  137. package/dist/http/Exception.d.ts +63 -1
  138. package/dist/http/Exception.d.ts.map +1 -1
  139. package/dist/http/Exception.js +185 -48
  140. package/dist/http/Exception.js.map +1 -1
  141. package/dist/http/HttpContext.d.ts +53 -7
  142. package/dist/http/HttpContext.d.ts.map +1 -1
  143. package/dist/http/HttpContext.js +56 -15
  144. package/dist/http/HttpContext.js.map +1 -1
  145. package/dist/http/RedirectBuilder.d.ts.map +1 -1
  146. package/dist/http/RedirectBuilder.js +4 -1
  147. package/dist/http/RedirectBuilder.js.map +1 -1
  148. package/dist/http/Request.d.ts +141 -10
  149. package/dist/http/Request.d.ts.map +1 -1
  150. package/dist/http/Request.js +244 -34
  151. package/dist/http/Request.js.map +1 -1
  152. package/dist/http/Response.d.ts +176 -9
  153. package/dist/http/Response.d.ts.map +1 -1
  154. package/dist/http/Response.js +404 -16
  155. package/dist/http/Response.js.map +1 -1
  156. package/dist/index.d.ts +19 -8
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +20 -5
  159. package/dist/index.js.map +1 -1
  160. package/dist/middleware/Pipeline.d.ts +29 -8
  161. package/dist/middleware/Pipeline.d.ts.map +1 -1
  162. package/dist/middleware/Pipeline.js +40 -2
  163. package/dist/middleware/Pipeline.js.map +1 -1
  164. package/dist/openapi/OpenApiProvider.js +4 -4
  165. package/dist/openapi/OpenApiProvider.js.map +1 -1
  166. package/dist/router/Router.d.ts +223 -31
  167. package/dist/router/Router.d.ts.map +1 -1
  168. package/dist/router/Router.js +520 -102
  169. package/dist/router/Router.js.map +1 -1
  170. package/dist/rpc/RpcProvider.d.ts +1 -0
  171. package/dist/rpc/RpcProvider.d.ts.map +1 -1
  172. package/dist/rpc/RpcProvider.js +7 -3
  173. package/dist/rpc/RpcProvider.js.map +1 -1
  174. package/dist/rpc/RpcRouter.d.ts +2 -2
  175. package/dist/rpc/RpcRouter.d.ts.map +1 -1
  176. package/dist/rpc/RpcRouter.js +17 -5
  177. package/dist/rpc/RpcRouter.js.map +1 -1
  178. package/dist/scheduler/ScheduleProvider.d.ts.map +1 -1
  179. package/dist/scheduler/ScheduleProvider.js +7 -15
  180. package/dist/scheduler/ScheduleProvider.js.map +1 -1
  181. package/dist/server/Server.d.ts +17 -4
  182. package/dist/server/Server.d.ts.map +1 -1
  183. package/dist/server/Server.js +41 -7
  184. package/dist/server/Server.js.map +1 -1
  185. package/dist/services/app.d.ts +1 -1
  186. package/dist/services/app.js +1 -1
  187. package/dist/services/console.d.ts +21 -0
  188. package/dist/services/console.d.ts.map +1 -0
  189. package/dist/services/console.js +35 -0
  190. package/dist/services/console.js.map +1 -0
  191. package/dist/session/SessionMiddleware.js +3 -3
  192. package/dist/session/SessionMiddleware.js.map +1 -1
  193. package/dist/storage/StaticProvider.js +1 -1
  194. package/dist/storage/StaticProvider.js.map +1 -1
  195. package/dist/testing/ApiResponse.d.ts +218 -0
  196. package/dist/testing/ApiResponse.d.ts.map +1 -0
  197. package/dist/testing/ApiResponse.js +576 -0
  198. package/dist/testing/ApiResponse.js.map +1 -0
  199. package/dist/testing/RequestBuilder.d.ts +155 -35
  200. package/dist/testing/RequestBuilder.d.ts.map +1 -1
  201. package/dist/testing/RequestBuilder.js +455 -109
  202. package/dist/testing/RequestBuilder.js.map +1 -1
  203. package/dist/testing/TestClient.d.ts +53 -37
  204. package/dist/testing/TestClient.d.ts.map +1 -1
  205. package/dist/testing/TestClient.js +84 -52
  206. package/dist/testing/TestClient.js.map +1 -1
  207. package/dist/utils/Macroable.d.ts +30 -0
  208. package/dist/utils/Macroable.d.ts.map +1 -0
  209. package/dist/utils/Macroable.js +43 -0
  210. package/dist/utils/Macroable.js.map +1 -0
  211. package/dist/utils/inflect.d.ts +17 -0
  212. package/dist/utils/inflect.d.ts.map +1 -0
  213. package/dist/utils/inflect.js +32 -0
  214. package/dist/utils/inflect.js.map +1 -0
  215. package/dist/utils/objectPath.d.ts +57 -0
  216. package/dist/utils/objectPath.d.ts.map +1 -0
  217. package/dist/utils/objectPath.js +141 -0
  218. package/dist/utils/objectPath.js.map +1 -0
  219. package/events.darwin-arm64.node +0 -0
  220. package/events.darwin-x64.node +0 -0
  221. package/events.linux-arm64-gnu.node +0 -0
  222. package/events.linux-x64-gnu.node +0 -0
  223. package/events.win32-x64-msvc.node +0 -0
  224. package/index.darwin-arm64.node +0 -0
  225. package/index.darwin-x64.node +0 -0
  226. package/index.linux-arm64-gnu.node +0 -0
  227. package/index.linux-x64-gnu.node +0 -0
  228. package/index.win32-x64-msvc.node +0 -0
  229. package/package.json +20 -2
  230. package/scheduler.darwin-arm64.node +0 -0
  231. package/scheduler.darwin-x64.node +0 -0
  232. package/scheduler.linux-arm64-gnu.node +0 -0
  233. package/scheduler.linux-x64-gnu.node +0 -0
  234. package/scheduler.win32-x64-msvc.node +0 -0
  235. package/src/ConfigLoader.ts +72 -9
  236. package/src/HttpKernel.ts +53 -15
  237. package/src/Ignitor.ts +364 -45
  238. package/src/console/BaseCommand.ts +467 -0
  239. package/src/console/Console.ts +174 -0
  240. package/src/console/ExceptionHandler.ts +91 -0
  241. package/src/console/HelpCommand.ts +37 -0
  242. package/src/console/IndexGenerator.ts +79 -0
  243. package/src/console/Kernel.ts +1382 -0
  244. package/src/console/ListCommand.ts +55 -0
  245. package/src/console/assertions.ts +114 -0
  246. package/src/console/cliui.ts +756 -0
  247. package/src/console/decorators.ts +199 -0
  248. package/src/console/index.ts +67 -0
  249. package/src/console/loaders.ts +145 -0
  250. package/src/console/parser.ts +598 -0
  251. package/src/console/prompts.ts +548 -0
  252. package/src/console/tracing.ts +22 -0
  253. package/src/console/types.ts +280 -0
  254. package/src/console/ui.ts +37 -0
  255. package/src/console/utils.ts +69 -0
  256. package/src/container/Container.ts +198 -72
  257. package/src/container/types.ts +15 -2
  258. package/src/decorators/Service.ts +28 -3
  259. package/src/env/Env.ts +49 -5
  260. package/src/env/Secret.ts +41 -0
  261. package/src/env/interpolate.ts +54 -0
  262. package/src/env/loadEnvFiles.ts +12 -4
  263. package/src/env/schema.ts +109 -35
  264. package/src/errors/ReamError.ts +31 -0
  265. package/src/errors/prettyPrintError.ts +20 -0
  266. package/src/events/Emitter.ts +2 -2
  267. package/src/events/EventsProvider.ts +5 -5
  268. package/src/graphql/GraphQLEngine.ts +4 -4
  269. package/src/graphql/GraphQLProvider.ts +7 -3
  270. package/src/http/Exception.ts +214 -37
  271. package/src/http/HttpContext.ts +87 -18
  272. package/src/http/RedirectBuilder.ts +4 -1
  273. package/src/http/Request.ts +264 -34
  274. package/src/http/Response.ts +478 -27
  275. package/src/index.ts +41 -6
  276. package/src/middleware/Pipeline.ts +77 -6
  277. package/src/openapi/OpenApiProvider.ts +4 -4
  278. package/src/router/Router.ts +631 -113
  279. package/src/rpc/RpcProvider.ts +6 -2
  280. package/src/rpc/RpcRouter.ts +22 -7
  281. package/src/scheduler/ScheduleProvider.ts +7 -15
  282. package/src/server/Server.ts +49 -10
  283. package/src/services/app.ts +1 -1
  284. package/src/services/console.ts +42 -0
  285. package/src/session/SessionMiddleware.ts +4 -4
  286. package/src/storage/StaticProvider.ts +1 -1
  287. package/src/testing/ApiResponse.ts +691 -0
  288. package/src/testing/RequestBuilder.ts +577 -137
  289. package/src/testing/TestClient.ts +110 -77
  290. package/src/utils/Macroable.ts +54 -0
  291. package/src/utils/inflect.ts +29 -0
  292. package/src/utils/objectPath.ts +142 -0
  293. package/dist/console/CommandRunner.d.ts +0 -18
  294. package/dist/console/CommandRunner.d.ts.map +0 -1
  295. package/dist/console/CommandRunner.js +0 -65
  296. package/dist/console/CommandRunner.js.map +0 -1
  297. package/dist/decorators/Lazy.d.ts +0 -19
  298. package/dist/decorators/Lazy.d.ts.map +0 -1
  299. package/dist/decorators/Lazy.js +0 -53
  300. package/dist/decorators/Lazy.js.map +0 -1
  301. package/src/console/CommandRunner.ts +0 -82
  302. package/src/decorators/Lazy.ts +0 -57
@@ -0,0 +1,199 @@
1
+ /**
2
+ * `@args` / `@flags` decorators — declarative command inputs (Console parity).
3
+ *
4
+ * Legacy (experimental) property decorators, matching the rest of the codebase
5
+ * and the project's swc config (`legacyDecorator: true`). Metadata is written
6
+ * onto the class as the static `args` / `flags` arrays the kernel reads, so a
7
+ * command satisfies the dispatch contract without inheriting anything.
8
+ *
9
+ * export default class Provision extends BaseCommand {
10
+ * static commandName = 'provision'
11
+ * static description = 'Create the owner account'
12
+ *
13
+ * @flags.string({ description: 'Owner email', required: true })
14
+ * declare email: string
15
+ *
16
+ * @flags.string({ default: 'Owner' })
17
+ * declare name: string
18
+ * }
19
+ *
20
+ * Properties are declared with `declare` on purpose: under `target: ES2022`
21
+ * a real class field would be re-initialised to `undefined` at construction
22
+ * and wipe the value the kernel assigned.
23
+ */
24
+
25
+ import { ReamError } from '../errors/ReamError.js'
26
+ import type { ArgumentMetaData, FlagMetaData } from './types.js'
27
+
28
+ /** `startServer` → `start-server`. */
29
+ function dashCase(value: string): string {
30
+ return value.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
31
+ }
32
+
33
+ /**
34
+ * Own-property lists per class.
35
+ *
36
+ * A subclass must not push into its parent's array — that would leak its flags
37
+ * onto every sibling. When the class does not own the list yet, seed a copy of
38
+ * whatever it inherited so extending a command keeps the parent's inputs.
39
+ */
40
+ function ownList<T>(ctor: object, key: 'args' | 'flags'): T[] {
41
+ if (!Object.hasOwn(ctor, key)) {
42
+ const inherited: unknown = Reflect.get(ctor, key)
43
+ Reflect.set(ctor, key, Array.isArray(inherited) ? [...inherited] : [])
44
+ }
45
+ const list: unknown = Reflect.get(ctor, key)
46
+ return Array.isArray(list) ? list : []
47
+ }
48
+
49
+ export interface ArgumentOptions {
50
+ /** Name shown in help. Defaults to the dash-cased property name. */
51
+ argumentName?: string
52
+ description?: string
53
+ /** Defaults to `true`, as in Console — positional arguments are expected. */
54
+ required?: boolean
55
+ default?: string | string[]
56
+ /** Accept an empty value instead of reporting it (Console `allowEmptyValue`). */
57
+ allowEmptyValue?: boolean
58
+ /** Transform or validate the value before assignment (Console `parse`). */
59
+ parse?: (value: string | string[]) => unknown
60
+ }
61
+
62
+ export interface FlagOptions {
63
+ /** Name used on the command line. Defaults to the dash-cased property name. */
64
+ flagName?: string
65
+ description?: string
66
+ /** Single-character shorthands, e.g. `['r']` for `-r`. */
67
+ alias?: string | string[]
68
+ default?: string | string[] | number | boolean
69
+ /** Defaults to `false` — a flag is optional unless stated otherwise. */
70
+ required?: boolean
71
+ /** Accept `--flag` with no value behind it (Console `allowEmptyValue`). */
72
+ allowEmptyValue?: boolean
73
+ /** Transform or validate the value before assignment (Console `parse`). */
74
+ parse?: (value: string | string[] | number | boolean) => unknown
75
+ /**
76
+ * Show `--no-<flag>` next to the flag in help. Booleans are always negatable;
77
+ * this only decides whether help says so.
78
+ */
79
+ showNegatedVariantInHelp?: boolean
80
+ }
81
+
82
+ /**
83
+ * Reject an argument order the command line could never satisfy (Console).
84
+ *
85
+ * Exported because `BaseCommand.defineArgument()` declares arguments through a
86
+ * different path: one rule, or a decorated command would accept what the same
87
+ * declaration written by hand refuses.
88
+ */
89
+ export function assertArgumentOrder(
90
+ declared: readonly ArgumentMetaData[],
91
+ incoming: Pick<ArgumentMetaData, 'argumentName' | 'required'>,
92
+ ): void {
93
+ const last = declared[declared.length - 1]
94
+ if (last === undefined) return
95
+
96
+ if (last.type === 'spread') {
97
+ throw new ReamError(
98
+ 'E_CONSOLE_INVALID_ARGUMENT',
99
+ `Argument "${incoming.argumentName}" comes after the spread argument "${last.argumentName}".`,
100
+ { hint: 'A spread argument consumes the rest, so it must be declared last.' },
101
+ )
102
+ }
103
+ if (incoming.required && last.required === false) {
104
+ throw new ReamError(
105
+ 'E_CONSOLE_INVALID_ARGUMENT',
106
+ `Required argument "${incoming.argumentName}" comes after the optional "${last.argumentName}".`,
107
+ { hint: 'Declare the required arguments first, or make this one optional.' },
108
+ )
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Build one argument's metadata from what was declared.
114
+ *
115
+ * The single builder for both declaration paths — the decorators and
116
+ * `BaseCommand.defineArgument()`. Two field-by-field copies is how a new option
117
+ * ends up honoured on one path and silently dropped on the other, which is
118
+ * exactly what happened to `allowEmptyValue`.
119
+ */
120
+ export function buildArgument(
121
+ name: string,
122
+ options: Partial<ArgumentMetaData> = {},
123
+ ): ArgumentMetaData {
124
+ return {
125
+ type: options.type ?? 'string',
126
+ propertyName: options.propertyName ?? name,
127
+ argumentName: options.argumentName ?? dashCase(name),
128
+ description: options.description,
129
+ // An argument with a default is optional by definition.
130
+ required: options.required ?? options.default === undefined,
131
+ default: options.default,
132
+ allowEmptyValue: options.allowEmptyValue,
133
+ parse: options.parse,
134
+ }
135
+ }
136
+
137
+ /** The same, for a flag. */
138
+ export function buildFlag(name: string, options: Partial<FlagMetaData> = {}): FlagMetaData {
139
+ return {
140
+ type: options.type ?? 'boolean',
141
+ propertyName: options.propertyName ?? name,
142
+ flagName: options.flagName ?? dashCase(name),
143
+ description: options.description,
144
+ alias: options.alias ?? [],
145
+ required: options.required ?? false,
146
+ default: options.default,
147
+ allowEmptyValue: options.allowEmptyValue,
148
+ parse: options.parse,
149
+ showNegatedVariantInHelp: options.showNegatedVariantInHelp,
150
+ }
151
+ }
152
+
153
+ function defineArgument(type: ArgumentMetaData['type'], options: ArgumentOptions) {
154
+ return (target: object, propertyKey: string | symbol): void => {
155
+ const argument = buildArgument(String(propertyKey), { ...options, type })
156
+ const list = ownList<ArgumentMetaData>(target.constructor, 'args')
157
+ assertArgumentOrder(list, argument)
158
+ list.push(argument)
159
+ }
160
+ }
161
+
162
+ function defineFlag(type: FlagMetaData['type'], options: FlagOptions) {
163
+ return (target: object, propertyKey: string | symbol): void => {
164
+ // `alias` is a string or a list at the decorator, always a list in the
165
+ // metadata — the only shape the builder cannot normalise for both paths.
166
+ const alias = options.alias === undefined ? [] : [options.alias].flat()
167
+ const list = ownList<FlagMetaData>(target.constructor, 'flags')
168
+ list.push(buildFlag(String(propertyKey), { ...options, type, alias }))
169
+ }
170
+ }
171
+
172
+ /** Positional arguments. Declaration order is the command-line order. */
173
+ export const args = {
174
+ /** A single positional value. */
175
+ string(options: ArgumentOptions = {}) {
176
+ return defineArgument('string', options)
177
+ },
178
+ /** Collects every remaining positional into an array. Declare it last. */
179
+ spread(options: ArgumentOptions = {}) {
180
+ return defineArgument('spread', options)
181
+ },
182
+ }
183
+
184
+ /** Named flags. */
185
+ export const flags = {
186
+ string(options: FlagOptions = {}) {
187
+ return defineFlag('string', options)
188
+ },
189
+ boolean(options: FlagOptions = {}) {
190
+ return defineFlag('boolean', options)
191
+ },
192
+ number(options: FlagOptions = {}) {
193
+ return defineFlag('number', options)
194
+ },
195
+ /** Repeatable flag: `--tag a --tag b` → `['a', 'b']`. */
196
+ array(options: FlagOptions = {}) {
197
+ return defineFlag('array', options)
198
+ },
199
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `@c9up/ream/console` — the console command surface.
3
+ *
4
+ * The SDK is ours — only the shape is borrowed: a command declares its name,
5
+ * its args and its flags, then runs.
6
+ *
7
+ * import { BaseCommand, args, flags } from '@c9up/ream/console'
8
+ */
9
+
10
+ export { BaseCommand } from './BaseCommand.js'
11
+ export type { ConsoleOptions } from './Console.js'
12
+ export { Console } from './Console.js'
13
+ export type { Colors, StyleName, TaskOutcome } from './cliui.js'
14
+ export {
15
+ Action,
16
+ Box,
17
+ Logger,
18
+ Table,
19
+ TaskContext,
20
+ Tasks,
21
+ Ui,
22
+ } from './cliui.js'
23
+ export type { ArgumentOptions, FlagOptions } from './decorators.js'
24
+ export { args, flags } from './decorators.js'
25
+ export { ExceptionHandler } from './ExceptionHandler.js'
26
+ export { default as HelpCommand } from './HelpCommand.js'
27
+ export type { CommandsManifest } from './IndexGenerator.js'
28
+ export { IndexGenerator } from './IndexGenerator.js'
29
+ export type {
30
+ CommandExecutor,
31
+ CommandLoader,
32
+ ErrorRenderer,
33
+ GlobalFlagListener,
34
+ HandleResult,
35
+ KernelOptions,
36
+ KernelState,
37
+ } from './Kernel.js'
38
+ export { Kernel, serializeCommand } from './Kernel.js'
39
+ export { default as ListCommand } from './ListCommand.js'
40
+ export { FsLoader, ListLoader } from './loaders.js'
41
+ export type { ParseOptions } from './parser.js'
42
+ export { Parser, parseArgv } from './parser.js'
43
+ export type {
44
+ BooleanPromptOptions,
45
+ MultiplePromptOptions,
46
+ PromptChoice,
47
+ PromptOptions,
48
+ SelectPromptOptions,
49
+ } from './prompts.js'
50
+ export { Prompt, PromptTrap } from './prompts.js'
51
+ export { commandExec } from './tracing.js'
52
+ export type {
53
+ ArgumentMetaData,
54
+ CommandClass,
55
+ CommandInstance,
56
+ CommandOptions,
57
+ ExecutedCommand,
58
+ FlagMetaData,
59
+ ParsedInput,
60
+ } from './types.js'
61
+ export { isCommandClass } from './types.js'
62
+ export {
63
+ renderErrorWithSuggestions,
64
+ sortAlphabetically,
65
+ validateCommand,
66
+ validateCommandMetaData,
67
+ } from './utils.js'
@@ -0,0 +1,145 @@
1
+ /**
2
+ * The two loaders Console ships — `ListLoader` and `FsLoader`.
3
+ *
4
+ * A loader answers in two steps: the metadata of everything it offers, then the
5
+ * class for one of them. The kernel reads the first at boot and the second only
6
+ * when a command is asked for, so neither listing nor describing imports a
7
+ * module nobody is going to run.
8
+ */
9
+
10
+ import { readdir } from 'node:fs/promises'
11
+ import { pathToFileURL } from 'node:url'
12
+ import type { CommandLoader } from './Kernel.js'
13
+ import { serializeCommand } from './Kernel.js'
14
+ import { type CommandClass, isCommandClass, type SerializedCommand } from './types.js'
15
+
16
+ /**
17
+ * Commands given as classes (Console `ListLoader`).
18
+ *
19
+ * The form a package uses to ship its commands: they are already imported, so
20
+ * there is nothing to defer.
21
+ */
22
+ export class ListLoader implements CommandLoader {
23
+ readonly #commands: readonly CommandClass[]
24
+
25
+ constructor(commands: readonly CommandClass[]) {
26
+ this.#commands = [...commands]
27
+ }
28
+
29
+ async getMetaData(): Promise<SerializedCommand[]> {
30
+ return this.#commands.map(serializeCommand)
31
+ }
32
+
33
+ async getCommand(metadata: SerializedCommand): Promise<CommandClass | null> {
34
+ return this.#commands.find((command) => command.commandName === metadata.commandName) ?? null
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Commands found in a directory (Console `FsLoader`).
40
+ *
41
+ * Reading the metadata imports each file — without a manifest there is no other
42
+ * way to know what a file declares, and Console's own `FsLoader` does the same. The
43
+ * gain is elsewhere: `getCommand()` never imports anything twice.
44
+ */
45
+ export class FsLoader implements CommandLoader {
46
+ readonly #directory: URL
47
+ readonly #filter: ((filePath: string) => boolean) | undefined
48
+ readonly #commands = new Map<string, CommandClass>()
49
+ readonly #paths = new Map<string, string>()
50
+ #onSkipped: ((fileName: string) => void) | undefined
51
+
52
+ /**
53
+ * @param directory Where to look, as a path or a URL.
54
+ * @param filter Files to keep. Everything is loaded when it is omitted.
55
+ */
56
+ constructor(directory: string | URL, filter?: (filePath: string) => boolean) {
57
+ this.#directory = typeof directory === 'string' ? pathToFileURL(`${directory}/`) : directory
58
+ this.#filter = filter
59
+ }
60
+
61
+ /**
62
+ * Called for a file that exports no command.
63
+ *
64
+ * Reported rather than thrown: the directory is scanned by convention, so a
65
+ * stray helper there must not make the whole CLI unusable.
66
+ */
67
+ onSkipped(handler: (fileName: string) => void): this {
68
+ this.#onSkipped = handler
69
+ return this
70
+ }
71
+
72
+ async getMetaData(): Promise<SerializedCommand[]> {
73
+ let entries: Array<{ name: string; isDirectory(): boolean; parentPath?: string }>
74
+ try {
75
+ entries = await readdir(this.#directory, { withFileTypes: true, recursive: true })
76
+ } catch {
77
+ // No such directory — perfectly normal.
78
+ return []
79
+ }
80
+
81
+ const files = entries
82
+ .filter((entry) => !entry.isDirectory() && isCommandFile(entry.name))
83
+ .map((entry) => new URL(entry.name, directoryUrl(entry.parentPath, this.#directory)))
84
+ .filter((file) => this.#filter === undefined || this.#filter(file.pathname))
85
+ .sort((a, b) => a.href.localeCompare(b.href))
86
+
87
+ for (const file of files) {
88
+ const command = commandOf(await this.import(file))
89
+ if (command === undefined) {
90
+ this.#onSkipped?.(file.pathname.split('/').pop() ?? file.pathname)
91
+ continue
92
+ }
93
+ this.#commands.set(command.commandName, command)
94
+ this.#paths.set(command.commandName, relativeTo(this.#directory, file))
95
+ }
96
+
97
+ return [...this.#commands.values()].map((command) => ({
98
+ ...serializeCommand(command),
99
+ filePath: this.#paths.get(command.commandName),
100
+ }))
101
+ }
102
+
103
+ async getCommand(metadata: SerializedCommand): Promise<CommandClass | null> {
104
+ return this.#commands.get(metadata.commandName) ?? null
105
+ }
106
+
107
+ /**
108
+ * How a file is imported. Overridable because an application may compile its
109
+ * own sources — Ream's Ignitor routes this through the app's importer.
110
+ */
111
+ protected import(file: URL): Promise<unknown> {
112
+ return import(file.href)
113
+ }
114
+ }
115
+
116
+ /** `./make/controller.ts` — how a manifest refers back to the file. */
117
+ function relativeTo(directory: URL, file: URL): string {
118
+ return `./${file.href.slice(directory.href.length)}`
119
+ }
120
+
121
+ /** `.ts` / `.js` modules only, skipping declarations and `_private` helpers. */
122
+ function isCommandFile(name: string): boolean {
123
+ if (name.startsWith('_') || name.endsWith('.d.ts')) return false
124
+ return /\.(ts|js|mts|mjs)$/.test(name)
125
+ }
126
+
127
+ /**
128
+ * `readdir(recursive)` reports nested entries via `parentPath`; older Node
129
+ * builds omit it for top-level entries, where the base directory is correct.
130
+ */
131
+ function directoryUrl(parentPath: string | undefined, fallback: URL): URL {
132
+ if (parentPath === undefined) return fallback
133
+ // `readdir` echoes the path it was given, which already ends with a slash
134
+ // when it came from a URL. Adding a second one yields `…/commands//greet.ts`:
135
+ // still importable, but wrong the moment the path is written to a manifest.
136
+ const href = pathToFileURL(parentPath).href
137
+ return new URL(href.endsWith('/') ? href : `${href}/`)
138
+ }
139
+
140
+ /** The command a module default-exports, or undefined when it exports none. */
141
+ function commandOf(mod: unknown): CommandClass | undefined {
142
+ if (typeof mod !== 'object' || mod === null) return undefined
143
+ const value = Reflect.get(mod, 'default')
144
+ return isCommandClass(value) ? value : undefined
145
+ }