@coinagerie/output 0.0.0-stage → 0.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ ## 0.0.2 (2026-11-04)
2
+
3
+ ### 🧱 Updated Dependencies
4
+
5
+ - Updated coinagerie-configuration to 0.0.2
6
+ - Updated coinagerie-core to 0.0.2
7
+
8
+ ## 0.0.1 (2026-11-04)
9
+
10
+ ### 🚀 Features
11
+
12
+ - **coinagerie:** 📦️ publish coinagerie to npm ([#2088](https://github.com/organizzolini/codebase/pull/2088), [#1793](https://github.com/organizzolini/codebase/issues/1793))
13
+ - **configuration,coinagerie:** ✨ adopt coinagerie with a root glossary ([#1888](https://github.com/organizzolini/codebase/pull/1888), [#1785](https://github.com/organizzolini/codebase/issues/1785))
14
+ - **coinagerie:** ✨ write json glossaries and override configuration with flags ([#1880](https://github.com/organizzolini/codebase/pull/1880), [#1766](https://github.com/organizzolini/codebase/issues/1766))
15
+ - **coinagerie:** ✨ build a markdown glossary from typescript ([#1875](https://github.com/organizzolini/codebase/pull/1875), [#1755](https://github.com/organizzolini/codebase/issues/1755))
16
+
17
+ ### 🧱 Updated Dependencies
18
+
19
+ - Updated coinagerie-configuration to 0.0.1
20
+ - Updated coinagerie-core to 0.0.1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jimmy Paolini
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,442 @@
1
- # Temporary Holding Version
1
+ # @coinagerie/output
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@coinagerie/output?logo=npm&label=npm)](https://www.npmjs.com/package/@coinagerie/output)
4
+
5
+ Renders and parses the markdown and JSON glossaries coinagerie writes, and compares them with the vocabulary of the codebase to name the words that are missing or stale.
6
+
7
+ ```bash
8
+ npm install @coinagerie/output
9
+ ```
10
+
11
+ ## Test
12
+
13
+ ```bash
14
+ nx run coinagerie-output:vitest
15
+ ```
16
+
17
+ ## 👔 Conformetry
18
+
19
+ This project was generated from the [nestjs-service-project](../../configuration/conformetry-templates/nestjs-service-project) conformetry template.
20
+
21
+ <!-- coinagerie:start -->
22
+
23
+ ## 🪙 Coinagerie
24
+
25
+ The words this package uses that the root glossary does not define, regenerated with `nx run coinagerie-output:coinagerie-check:write`.
26
+
27
+ - **anchor**:
28
+ - **boundary**:
29
+ - **breadth**:
30
+ - **bullet**:
31
+ - **comparison**:
32
+ - **continuation**:
33
+ - **deliver**:
34
+ - **delivery**:
35
+ - **destination**:
36
+ - **flush**:
37
+ - **glossary**:
38
+ - **hand**:
39
+ - **lone**:
40
+ - **malformed**:
41
+ - **preamble**:
42
+ - **reconcile**:
43
+ - **refuse**:
44
+ - **reload**:
45
+ - **splice**:
46
+ - **stale**:
47
+ - **structural**:
48
+ - **tokenize**:
49
+ - **unmock**:
50
+ - **unsorted**:
51
+ - **vocabulary**:
52
+
53
+ <!-- coinagerie:end -->
54
+
55
+ <!-- callidescope:start -->
56
+
57
+ ## 🔭 Callidescope
58
+
59
+ Call stacks traced through `projects/ic-suite/coinagerie/coinagerie-output`, deepest first. Each frame shows what it takes, what it returns, and what its documentation says.
60
+
61
+ | Measure | Value |
62
+ | --- | --- |
63
+ | Callables | 53 |
64
+ | Files | 24 |
65
+ | Calls traced | 69 |
66
+ | Call stacks | 0 |
67
+ | Deepest stack | 0 |
68
+ | Stacks through recursion | 0 |
69
+ | Unfollowable calls | 0 |
70
+
71
+ ### Limits
72
+
73
+ What this project is judged against, as declared in its own `callidescope.config.ts`.
74
+
75
+ | Limit | Value |
76
+ | --- | --- |
77
+ | `maximumDepth` | 10 |
78
+ | `maximumBreadth` | 4 |
79
+
80
+ ### Call stacks (depth)
81
+
82
+ None.
83
+
84
+ ### Breadth
85
+
86
+ | Callable | Breadth | Calls directly | Location |
87
+ | --- | --- | --- | --- |
88
+ | `JsonService.parse` | 4 | `JsonService.parseJson`, `JsonService.isObject`, `JsonService.fail`, `JsonService.refuseDuplicates` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/json/json.service.ts:176` |
89
+ | `DeliveryService.contentFor` | 4 | `JsonService.write`, `MarkdownService.write`, `DeliveryService.handlerFor`, `ComparisonService.reconcile` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/delivery/delivery.service.ts:55` |
90
+ | `MarkdownService.readDescription` | 3 | `MarkdownService.findAnchors`, `MarkdownService.findIndex(…)`, `MarkdownService.filter(…)` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:156` |
91
+
92
+ <details>
93
+ <summary>30 more callables</summary>
94
+
95
+ | Callable | Breadth | Calls directly | Location |
96
+ | --- | --- | --- | --- |
97
+ | `MarkdownService.parse` | 3 | `MarkdownService.findAnchors`, `MarkdownService.forEach(…)`, `MarkdownService.flush` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:212` |
98
+ | `MarkdownService.write` | 3 | `MarkdownService.splice`, `MarkdownService.render`, `MarkdownService.readDescription` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:300` |
99
+ | `DeliveryService.map(…)` | 3 | `JsonService.read`, `MarkdownService.read`, `DeliveryService.handlerFor` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/delivery/delivery.service.ts:84` |
100
+ | `ComparisonService.compare` | 2 | `ComparisonService.filter(…)`, `ComparisonService.filter(…)` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/comparison/comparison.service.ts:26` |
101
+ | `JsonService.endOfToken` | 2 | `JsonService.endOfString`, `JsonService.isBoundary` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/json/json.service.ts:46` |
102
+ | `JsonService.refuseDuplicates` | 2 | `JsonService.tokenize`, `JsonService.fail` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/json/json.service.ts:108` |
103
+ | `JsonService.render` | 2 | `JsonService.map(…)`, `JsonService.toSorted(…)` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/json/json.service.ts:215` |
104
+ | `MarkdownService.findAnchors` | 2 | `MarkdownService.indexesOf`, `MarkdownService.fail` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:64` |
105
+ | `MarkdownService.readBullet` | 2 | `MarkdownService.fail`, `MarkdownService.flush` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:110` |
106
+ | `MarkdownService.readContinuation` | 2 | `MarkdownService.fail`, `MarkdownService.from(…)` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:136` |
107
+ | `MarkdownService.readLine` | 2 | `MarkdownService.readBullet`, `MarkdownService.readContinuation` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:173` |
108
+ | `MarkdownService.render` | 2 | `MarkdownService.map(…)`, `MarkdownService.toSorted(…)` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:245` |
109
+ | `DeliveryService.check` | 2 | `DeliveryService.map(…)`, `DeliveryService.readAll` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/delivery/delivery.service.ts:102` |
110
+ | `DeliveryService.write` | 2 | `DeliveryService.map(…)`, `DeliveryService.readAll` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/delivery/delivery.service.ts:113` |
111
+ | `DeliveryService.map(…)` | 2 | `DeliveryService.contentFor`, `DeliveryService.differenceOf` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/delivery/delivery.service.ts:114` |
112
+ | `ComparisonService.reconcile` | 1 | `ComparisonService.map(…)` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/comparison/comparison.service.ts:42` |
113
+ | `JsonService.fail` | 1 | `MalformedGlossaryError.constructor` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/json/json.service.ts:67` |
114
+ | `JsonService.parseJson` | 1 | `JsonService.fail` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/json/json.service.ts:90` |
115
+ | `JsonService.tokenize` | 1 | `JsonService.endOfToken` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/json/json.service.ts:150` |
116
+ | `JsonService.read` | 1 | `JsonService.parse` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/json/json.service.ts:202` |
117
+ | `JsonService.write` | 1 | `JsonService.render` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/json/json.service.ts:227` |
118
+ | `MarkdownService.fail` | 1 | `MalformedGlossaryError.constructor` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:50` |
119
+ | `MarkdownService.indexesOf` | 1 | `MarkdownService.flatMap(…)` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:68` |
120
+ | `MarkdownService.forEach(…)` | 1 | `MarkdownService.readLine` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:228` |
121
+ | `MarkdownService.read` | 1 | `MarkdownService.parse` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:237` |
122
+ | `MarkdownService.map(…)` | 1 | `MarkdownService.renderBullet` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:253` |
123
+ | `MarkdownService.splice` | 1 | `MarkdownService.findAnchors` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/markdown/markdown.service.ts:269` |
124
+ | `DeliveryService.differenceOf` | 1 | `ComparisonService.compare` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/delivery/delivery.service.ts:64` |
125
+ | `DeliveryService.readAll` | 1 | `DeliveryService.map(…)` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/delivery/delivery.service.ts:83` |
126
+ | `DeliveryService.map(…)` | 1 | `DeliveryService.differenceOf` | `projects/ic-suite/coinagerie/coinagerie-output/src/modules/delivery/delivery.service.ts:103` |
127
+
128
+ </details>
129
+ <!-- callidescope:end -->
130
+
131
+ ## 🕸️ Codependix
132
+
133
+ Dependency graphs exported by [codependix](https://github.com/Organizzolini/codebase/tree/main/projects/ic-suite/codependix/codependix-cli), regenerated by `nx run configuration:codependix:write`.
134
+
135
+ ### Nx Neighborhood
136
+
137
+ <!-- codependix:start name="codependix-nx-projects" -->
138
+ ```mermaid
139
+ graph LR
140
+ coinagerie_cli["coinagerie-cli"]
141
+ coinagerie_configuration["coinagerie-configuration"]
142
+ coinagerie_core["coinagerie-core"]
143
+ coinagerie_output["coinagerie-output"]
144
+ coinagerie_cli --> coinagerie_output
145
+ coinagerie_output --> coinagerie_configuration
146
+ coinagerie_output --> coinagerie_core
147
+ classDef subject fill:#7c3aed,color:#fff,stroke:#4c1d95,stroke-width:2px
148
+ class coinagerie_output subject
149
+ ```
150
+ <!-- codependix:end name="codependix-nx-projects" -->
151
+
152
+ ### NestJS Module Graph
153
+
154
+ <!-- codependix:start name="codependix-nestjs-modules" -->
155
+ ```mermaid
156
+ flowchart LR
157
+ ComparisonModule
158
+ DeliveryModule
159
+ JsonModule
160
+ MarkdownModule
161
+ DeliveryModule --> ComparisonModule
162
+ DeliveryModule --> JsonModule
163
+ DeliveryModule --> MarkdownModule
164
+ ```
165
+ <!-- codependix:end name="codependix-nestjs-modules" -->
166
+
167
+ ### File Imports
168
+
169
+ <!-- codependix:start name="codependix-file-imports" -->
170
+ ```mermaid
171
+ graph LR
172
+ file_callidescope_config_ts["callidescope.config.ts"]
173
+ file_codependix_config_ts["codependix.config.ts"]
174
+ file_codometer_config_ts["codometer.config.ts"]
175
+ file_coinagerie_config_ts["coinagerie.config.ts"]
176
+ file_eslint_config_ts["eslint.config.ts"]
177
+ file_src_index_ts["src/index.ts"]
178
+ file_src_index_unit_test_ts["src/index.unit.test.ts"]
179
+ file_src_modules_comparison_comparison_constants_ts["src/modules/comparison/comparison.constants.ts"]
180
+ file_src_modules_comparison_comparison_module_ts["src/modules/comparison/comparison.module.ts"]
181
+ file_src_modules_comparison_comparison_service_ts["src/modules/comparison/comparison.service.ts"]
182
+ file_src_modules_comparison_comparison_service_unit_test_ts["src/modules/comparison/comparison.service.unit.test.ts"]
183
+ file_src_modules_comparison_comparison_types_ts["src/modules/comparison/comparison.types.ts"]
184
+ file_src_modules_delivery_delivery_constants_ts["src/modules/delivery/delivery.constants.ts"]
185
+ file_src_modules_delivery_delivery_module_ts["src/modules/delivery/delivery.module.ts"]
186
+ file_src_modules_delivery_delivery_service_ts["src/modules/delivery/delivery.service.ts"]
187
+ file_src_modules_delivery_delivery_service_unit_test_ts["src/modules/delivery/delivery.service.unit.test.ts"]
188
+ file_src_modules_delivery_delivery_types_ts["src/modules/delivery/delivery.types.ts"]
189
+ file_src_modules_json_json_constants_ts["src/modules/json/json.constants.ts"]
190
+ file_src_modules_json_json_module_ts["src/modules/json/json.module.ts"]
191
+ file_src_modules_json_json_service_ts["src/modules/json/json.service.ts"]
192
+ file_src_modules_json_json_service_unit_test_ts["src/modules/json/json.service.unit.test.ts"]
193
+ file_src_modules_json_json_types_ts["src/modules/json/json.types.ts"]
194
+ file_src_modules_markdown_markdown_constants_ts["src/modules/markdown/markdown.constants.ts"]
195
+ file_src_modules_markdown_markdown_module_ts["src/modules/markdown/markdown.module.ts"]
196
+ file_src_modules_markdown_markdown_service_ts["src/modules/markdown/markdown.service.ts"]
197
+ file_src_modules_markdown_markdown_service_unit_test_ts["src/modules/markdown/markdown.service.unit.test.ts"]
198
+ file_src_modules_markdown_markdown_types_ts["src/modules/markdown/markdown.types.ts"]
199
+ file_testing_mocks_ts["testing/mocks.ts"]
200
+ file_testing_setup_ts["testing/setup.ts"]
201
+ file_vite_config_ts["vite.config.ts"]
202
+ file_vitest_config_ts["vitest.config.ts"]
203
+ file_src_index_unit_test_ts --> file_src_index_ts
204
+ file_src_modules_comparison_comparison_module_ts --> file_src_modules_comparison_comparison_service_ts
205
+ file_src_modules_comparison_comparison_service_ts --> file_src_modules_comparison_comparison_types_ts
206
+ file_src_modules_comparison_comparison_service_unit_test_ts --> file_src_modules_comparison_comparison_service_ts
207
+ file_src_modules_delivery_delivery_module_ts --> file_src_modules_comparison_comparison_module_ts
208
+ file_src_modules_delivery_delivery_module_ts --> file_src_modules_delivery_delivery_service_ts
209
+ file_src_modules_delivery_delivery_module_ts --> file_src_modules_json_json_module_ts
210
+ file_src_modules_delivery_delivery_module_ts --> file_src_modules_markdown_markdown_module_ts
211
+ file_src_modules_delivery_delivery_service_ts --> file_src_modules_comparison_comparison_service_ts
212
+ file_src_modules_delivery_delivery_service_ts --> file_src_modules_delivery_delivery_types_ts
213
+ file_src_modules_delivery_delivery_service_ts --> file_src_modules_json_json_service_ts
214
+ file_src_modules_delivery_delivery_service_ts --> file_src_modules_markdown_markdown_service_ts
215
+ file_src_modules_delivery_delivery_service_unit_test_ts --> file_src_modules_comparison_comparison_module_ts
216
+ file_src_modules_delivery_delivery_service_unit_test_ts --> file_src_modules_delivery_delivery_service_ts
217
+ file_src_modules_delivery_delivery_service_unit_test_ts --> file_src_modules_json_json_module_ts
218
+ file_src_modules_delivery_delivery_service_unit_test_ts --> file_src_modules_markdown_markdown_module_ts
219
+ file_src_modules_json_json_module_ts --> file_src_modules_json_json_service_ts
220
+ file_src_modules_json_json_service_ts --> file_src_modules_delivery_delivery_types_ts
221
+ file_src_modules_json_json_service_ts --> file_src_modules_json_json_constants_ts
222
+ file_src_modules_json_json_service_ts --> file_src_modules_json_json_types_ts
223
+ file_src_modules_json_json_service_unit_test_ts --> file_src_modules_json_json_service_ts
224
+ file_src_modules_markdown_markdown_module_ts --> file_src_modules_markdown_markdown_service_ts
225
+ file_src_modules_markdown_markdown_service_ts --> file_src_modules_delivery_delivery_types_ts
226
+ file_src_modules_markdown_markdown_service_ts --> file_src_modules_markdown_markdown_constants_ts
227
+ file_src_modules_markdown_markdown_service_ts --> file_src_modules_markdown_markdown_types_ts
228
+ file_src_modules_markdown_markdown_service_unit_test_ts --> file_src_modules_markdown_markdown_service_ts
229
+ ```
230
+ <!-- codependix:end name="codependix-file-imports" -->
231
+
232
+ <!-- codometer:start -->
233
+
234
+ ## ⏲️ Codometer
235
+
236
+ ### Project
237
+
238
+ ![Lines of Code](https://img.shields.io/badge/Lines_of_Code-1985-22c55e?style=flat-square)
239
+ ![Repository Size](https://img.shields.io/badge/Repository_Size-71.31_kB-6b7280?style=flat-square)
240
+ ![Folders](https://img.shields.io/badge/Folders-7-4a4a4a?style=flat-square)
241
+ ![Source Files](https://img.shields.io/badge/Source_Files-31-3178c6?style=flat-square)
242
+
243
+ ### TypeScript
244
+
245
+ ![TypeScript Files](https://img.shields.io/badge/TypeScript_Files-31-3178c6?style=flat-square)
246
+ ![Interfaces](https://img.shields.io/badge/Interfaces-10-0ea5e9?style=flat-square)
247
+ ![Generic Declarations](https://img.shields.io/badge/Generic_Declarations-0-0369a1?style=flat-square)
248
+ ![Enums](https://img.shields.io/badge/Enums-0-f97316?style=flat-square)
249
+ ![Decorators](https://img.shields.io/badge/Decorators-8-db2777?style=flat-square)
250
+ ![Doc Comments](https://img.shields.io/badge/Doc_Comments-62-6366f1?style=flat-square)
251
+ ![Static Methods](https://img.shields.io/badge/Static_Methods-0-166534?style=flat-square)
252
+
253
+ ### JavaScript
254
+
255
+ ![JavaScript Files](https://img.shields.io/badge/JavaScript_Files-0-f7df1e?style=flat-square)
256
+ ![Test Files](https://img.shields.io/badge/Test_Files-5-10b981?style=flat-square)
257
+ ![External Packages](https://img.shields.io/badge/External_Packages-9-8b5cf6?style=flat-square)
258
+ ![Classes](https://img.shields.io/badge/Classes-8-7c3aed?style=flat-square)
259
+ ![Functions](https://img.shields.io/badge/Functions-94-16a34a?style=flat-square)
260
+ ![Methods](https://img.shields.io/badge/Methods-49-15803d?style=flat-square)
261
+ ![Sync Functions](https://img.shields.io/badge/Sync_Functions-138-4ade80?style=flat-square)
262
+ ![Async Functions](https://img.shields.io/badge/Async_Functions-5-059669?style=flat-square)
263
+ ![Constants](https://img.shields.io/badge/Constants-91-dc2626?style=flat-square)
264
+ ![Imports](https://img.shields.io/badge/Imports-77-0284c7?style=flat-square)
265
+ ![Exported Symbols](https://img.shields.io/badge/Exported_Symbols-25-ea580c?style=flat-square)
266
+ ![Comments](https://img.shields.io/badge/Comments-96-64748b?style=flat-square)
267
+ ![Comment Lines](https://img.shields.io/badge/Comment_Lines-181-475569?style=flat-square)
268
+ ![TODO Comments](https://img.shields.io/badge/TODO_Comments-0-ca8a04?style=flat-square)
269
+
270
+ ### Python
271
+
272
+ ![Python Files](https://img.shields.io/badge/Python_Files-0-3776ab?style=flat-square)
273
+ ![Python Lines](https://img.shields.io/badge/Python_Lines-0-4b8bbe?style=flat-square)
274
+ ![Python Classes](https://img.shields.io/badge/Python_Classes-0-7c3aed?style=flat-square)
275
+ ![Python Functions](https://img.shields.io/badge/Python_Functions-0-16a34a?style=flat-square)
276
+ ![Python Protocols](https://img.shields.io/badge/Python_Protocols-0-0ea5e9?style=flat-square)
277
+ ![Python Constants](https://img.shields.io/badge/Python_Constants-0-dc2626?style=flat-square)
278
+ ![Python Imports](https://img.shields.io/badge/Python_Imports-0-0284c7?style=flat-square)
279
+ ![Python Decorators](https://img.shields.io/badge/Python_Decorators-0-db2777?style=flat-square)
280
+ ![Docstrings](https://img.shields.io/badge/Docstrings-0-6366f1?style=flat-square)
281
+ ![Docstring Lines](https://img.shields.io/badge/Docstring_Lines-0-818cf8?style=flat-square)
282
+ ![Python Comments](https://img.shields.io/badge/Python_Comments-0-64748b?style=flat-square)
283
+ ![Python Comment Lines](https://img.shields.io/badge/Python_Comment_Lines-0-475569?style=flat-square)
284
+
285
+ ### JSON
286
+
287
+ ![JSON Files](https://img.shields.io/badge/JSON_Files-4-a16207?style=flat-square)
288
+ ![JSON Lines](https://img.shields.io/badge/JSON_Lines-155-ca8a04?style=flat-square)
289
+ ![JSON Objects](https://img.shields.io/badge/JSON_Objects-37-7c3aed?style=flat-square)
290
+ ![JSON Arrays](https://img.shields.io/badge/JSON_Arrays-12-8b5cf6?style=flat-square)
291
+ ![JSON Properties](https://img.shields.io/badge/JSON_Properties-94-0284c7?style=flat-square)
292
+ ![JSON Strings](https://img.shields.io/badge/JSON_Strings-84-16a34a?style=flat-square)
293
+ ![JSON Numbers](https://img.shields.io/badge/JSON_Numbers-1-059669?style=flat-square)
294
+ ![JSON Booleans](https://img.shields.io/badge/JSON_Booleans-6-0ea5e9?style=flat-square)
295
+ ![JSON Nulls](https://img.shields.io/badge/JSON_Nulls-0-64748b?style=flat-square)
296
+ ![JSON Items](https://img.shields.io/badge/JSON_Items-42-475569?style=flat-square)
297
+ ![JSON Nodes](https://img.shields.io/badge/JSON_Nodes-140-dc2626?style=flat-square)
298
+ ![JSON Max Depth](https://img.shields.io/badge/JSON_Max_Depth-5-ea580c?style=flat-square)
299
+
300
+ ### YAML
301
+
302
+ ![YAML Files](https://img.shields.io/badge/YAML_Files-0-cb171e?style=flat-square)
303
+ ![YAML Lines](https://img.shields.io/badge/YAML_Lines-0-e34c26?style=flat-square)
304
+ ![YAML Documents](https://img.shields.io/badge/YAML_Documents-0-f97316?style=flat-square)
305
+ ![YAML Mappings](https://img.shields.io/badge/YAML_Mappings-0-7c3aed?style=flat-square)
306
+ ![YAML Sequences](https://img.shields.io/badge/YAML_Sequences-0-8b5cf6?style=flat-square)
307
+ ![YAML Keys](https://img.shields.io/badge/YAML_Keys-0-0284c7?style=flat-square)
308
+ ![YAML Scalars](https://img.shields.io/badge/YAML_Scalars-0-16a34a?style=flat-square)
309
+ ![YAML Anchors](https://img.shields.io/badge/YAML_Anchors-0-059669?style=flat-square)
310
+ ![YAML Aliases](https://img.shields.io/badge/YAML_Aliases-0-10b981?style=flat-square)
311
+ ![YAML Comments](https://img.shields.io/badge/YAML_Comments-0-64748b?style=flat-square)
312
+ ![YAML Max Depth](https://img.shields.io/badge/YAML_Max_Depth-0-ea580c?style=flat-square)
313
+
314
+ ### TOML
315
+
316
+ ![TOML Files](https://img.shields.io/badge/TOML_Files-0-9c4221?style=flat-square)
317
+ ![TOML Lines](https://img.shields.io/badge/TOML_Lines-0-b45309?style=flat-square)
318
+ ![TOML Tables](https://img.shields.io/badge/TOML_Tables-0-7c3aed?style=flat-square)
319
+ ![TOML Array Tables](https://img.shields.io/badge/TOML_Array_Tables-0-8b5cf6?style=flat-square)
320
+ ![TOML Keys](https://img.shields.io/badge/TOML_Keys-0-0284c7?style=flat-square)
321
+ ![TOML Arrays](https://img.shields.io/badge/TOML_Arrays-0-16a34a?style=flat-square)
322
+ ![TOML Comments](https://img.shields.io/badge/TOML_Comments-0-64748b?style=flat-square)
323
+
324
+ ### Shell
325
+
326
+ ![Shell Files](https://img.shields.io/badge/Shell_Files-0-89e051?style=flat-square)
327
+ ![Shell Lines](https://img.shields.io/badge/Shell_Lines-0-4eaa25?style=flat-square)
328
+ ![Shell Functions](https://img.shields.io/badge/Shell_Functions-0-16a34a?style=flat-square)
329
+ ![Shell Variables](https://img.shields.io/badge/Shell_Variables-0-0284c7?style=flat-square)
330
+ ![Shell Exports](https://img.shields.io/badge/Shell_Exports-0-ea580c?style=flat-square)
331
+ ![Shell Conditionals](https://img.shields.io/badge/Shell_Conditionals-0-7c3aed?style=flat-square)
332
+ ![Shell Loops](https://img.shields.io/badge/Shell_Loops-0-8b5cf6?style=flat-square)
333
+ ![Shell Pipelines](https://img.shields.io/badge/Shell_Pipelines-0-059669?style=flat-square)
334
+ ![Shebangs](https://img.shields.io/badge/Shebangs-0-6b7280?style=flat-square)
335
+ ![Shell Comments](https://img.shields.io/badge/Shell_Comments-0-64748b?style=flat-square)
336
+ ![Shell Comment Lines](https://img.shields.io/badge/Shell_Comment_Lines-0-475569?style=flat-square)
337
+
338
+ ### SQL
339
+
340
+ ![SQL Files](https://img.shields.io/badge/SQL_Files-0-e38c00?style=flat-square)
341
+ ![SQL Lines](https://img.shields.io/badge/SQL_Lines-0-f29111?style=flat-square)
342
+ ![SQL Statements](https://img.shields.io/badge/SQL_Statements-0-7c3aed?style=flat-square)
343
+ ![SQL Selects](https://img.shields.io/badge/SQL_Selects-0-16a34a?style=flat-square)
344
+ ![SQL Inserts](https://img.shields.io/badge/SQL_Inserts-0-22c55e?style=flat-square)
345
+ ![SQL Updates](https://img.shields.io/badge/SQL_Updates-0-0ea5e9?style=flat-square)
346
+ ![SQL Deletes](https://img.shields.io/badge/SQL_Deletes-0-dc2626?style=flat-square)
347
+ ![SQL Creates](https://img.shields.io/badge/SQL_Creates-0-0284c7?style=flat-square)
348
+ ![SQL Joins](https://img.shields.io/badge/SQL_Joins-0-8b5cf6?style=flat-square)
349
+ ![SQL CTEs](https://img.shields.io/badge/SQL_CTEs-0-059669?style=flat-square)
350
+ ![SQL Comments](https://img.shields.io/badge/SQL_Comments-0-64748b?style=flat-square)
351
+
352
+ ### HCL
353
+
354
+ ![HCL Files](https://img.shields.io/badge/HCL_Files-0-844fba?style=flat-square)
355
+ ![HCL Lines](https://img.shields.io/badge/HCL_Lines-0-a78bfa?style=flat-square)
356
+ ![HCL Blocks](https://img.shields.io/badge/HCL_Blocks-0-7c3aed?style=flat-square)
357
+ ![HCL Resources](https://img.shields.io/badge/HCL_Resources-0-0284c7?style=flat-square)
358
+ ![HCL Variables](https://img.shields.io/badge/HCL_Variables-0-16a34a?style=flat-square)
359
+ ![HCL Outputs](https://img.shields.io/badge/HCL_Outputs-0-059669?style=flat-square)
360
+ ![HCL Attributes](https://img.shields.io/badge/HCL_Attributes-0-0ea5e9?style=flat-square)
361
+ ![HCL Interpolations](https://img.shields.io/badge/HCL_Interpolations-0-db2777?style=flat-square)
362
+ ![HCL Comments](https://img.shields.io/badge/HCL_Comments-0-64748b?style=flat-square)
363
+
364
+ ### CSS
365
+
366
+ ![CSS Files](https://img.shields.io/badge/CSS_Files-0-264de4?style=flat-square)
367
+ ![CSS Lines](https://img.shields.io/badge/CSS_Lines-0-2965f1?style=flat-square)
368
+ ![CSS Rules](https://img.shields.io/badge/CSS_Rules-0-7c3aed?style=flat-square)
369
+ ![CSS Selectors](https://img.shields.io/badge/CSS_Selectors-0-8b5cf6?style=flat-square)
370
+ ![CSS Declarations](https://img.shields.io/badge/CSS_Declarations-0-0284c7?style=flat-square)
371
+ ![CSS At Rules](https://img.shields.io/badge/CSS_At_Rules-0-f97316?style=flat-square)
372
+ ![CSS Media Queries](https://img.shields.io/badge/CSS_Media_Queries-0-ea580c?style=flat-square)
373
+ ![CSS Custom Properties](https://img.shields.io/badge/CSS_Custom_Properties-0-16a34a?style=flat-square)
374
+ ![CSS Comments](https://img.shields.io/badge/CSS_Comments-0-64748b?style=flat-square)
375
+
376
+ ### Conventions
377
+
378
+ ![Module Files](https://img.shields.io/badge/Module_Files-4-7c3aed?style=flat-square)
379
+ ![Service Files](https://img.shields.io/badge/Service_Files-4-0284c7?style=flat-square)
380
+ ![Command Files](https://img.shields.io/badge/Command_Files-0-16a34a?style=flat-square)
381
+ ![Constants Files](https://img.shields.io/badge/Constants_Files-4-ea580c?style=flat-square)
382
+ ![Types Files](https://img.shields.io/badge/Types_Files-4-db2777?style=flat-square)
383
+ ![Utilities Files](https://img.shields.io/badge/Utilities_Files-0-0ea5e9?style=flat-square)
384
+ ![TypeORM Entities](https://img.shields.io/badge/TypeORM_Entities-0-059669?style=flat-square)
385
+ ![Unit Tests](https://img.shields.io/badge/Unit_Tests-5-ca8a04?style=flat-square)
386
+ ![Integration Tests](https://img.shields.io/badge/Integration_Tests-0-7c3aed?style=flat-square)
387
+ ![End To End Tests](https://img.shields.io/badge/End_To_End_Tests-0-0284c7?style=flat-square)
388
+ ![CSS Comment Budget](https://img.shields.io/badge/CSS_Comment_Budget-0-16a34a?style=flat-square)
389
+ ![HCL Comment Budget](https://img.shields.io/badge/HCL_Comment_Budget-0-ea580c?style=flat-square)
390
+ ![Python Comment Budget](https://img.shields.io/badge/Python_Comment_Budget-0-db2777?style=flat-square)
391
+ ![SQL Comment Budget](https://img.shields.io/badge/SQL_Comment_Budget-0-0ea5e9?style=flat-square)
392
+ ![TOML Comment Budget](https://img.shields.io/badge/TOML_Comment_Budget-0-059669?style=flat-square)
393
+ ![TypeScript Comment Budget](https://img.shields.io/badge/TypeScript_Comment_Budget-0-ca8a04?style=flat-square)
394
+ ![YAML Comment Budget](https://img.shields.io/badge/YAML_Comment_Budget-0-7c3aed?style=flat-square)
395
+ ![Shell Comment Budget](https://img.shields.io/badge/Shell_Comment_Budget-0-0284c7?style=flat-square)
396
+
397
+ ### Jupyter
398
+
399
+ ![Notebooks](https://img.shields.io/badge/Notebooks-0-f37626?style=flat-square)
400
+ ![Notebook Cells](https://img.shields.io/badge/Notebook_Cells-0-e8a33d?style=flat-square)
401
+ ![Code Cells](https://img.shields.io/badge/Code_Cells-0-3776ab?style=flat-square)
402
+ ![Markdown Cells](https://img.shields.io/badge/Markdown_Cells-0-083fa1?style=flat-square)
403
+ ![Raw Cells](https://img.shields.io/badge/Raw_Cells-0-9ca3af?style=flat-square)
404
+ ![Executed Cells](https://img.shields.io/badge/Executed_Cells-0-16a34a?style=flat-square)
405
+ ![Cell Outputs](https://img.shields.io/badge/Cell_Outputs-0-059669?style=flat-square)
406
+ ![Notebook Code Lines](https://img.shields.io/badge/Notebook_Code_Lines-0-4b8bbe?style=flat-square)
407
+ ![Notebook Classes](https://img.shields.io/badge/Notebook_Classes-0-7c3aed?style=flat-square)
408
+ ![Notebook Functions](https://img.shields.io/badge/Notebook_Functions-0-22c55e?style=flat-square)
409
+ ![Notebook Imports](https://img.shields.io/badge/Notebook_Imports-0-0284c7?style=flat-square)
410
+ ![Notebook Decorators](https://img.shields.io/badge/Notebook_Decorators-0-db2777?style=flat-square)
411
+ ![Notebook Prose Lines](https://img.shields.io/badge/Notebook_Prose_Lines-0-1f6feb?style=flat-square)
412
+ ![Notebook Headings](https://img.shields.io/badge/Notebook_Headings-0-a78bfa?style=flat-square)
413
+ ![Notebook Links](https://img.shields.io/badge/Notebook_Links-0-10b981?style=flat-square)
414
+ ![Notebook Images](https://img.shields.io/badge/Notebook_Images-0-34d399?style=flat-square)
415
+ ![Notebook Code Blocks](https://img.shields.io/badge/Notebook_Code_Blocks-0-dc2626?style=flat-square)
416
+ ![Notebook Properties](https://img.shields.io/badge/Notebook_Properties-0-ca8a04?style=flat-square)
417
+ ![Notebook Nodes](https://img.shields.io/badge/Notebook_Nodes-0-a16207?style=flat-square)
418
+ ![Notebook Max Depth](https://img.shields.io/badge/Notebook_Max_Depth-0-ea580c?style=flat-square)
419
+
420
+ ### Markdown
421
+
422
+ ![Markdown Files](https://img.shields.io/badge/Markdown_Files-2-083fa1?style=flat-square)
423
+ ![Markdown Lines](https://img.shields.io/badge/Markdown_Lines-242-1f6feb?style=flat-square)
424
+ ![H1](https://img.shields.io/badge/H1-1-7c3aed?style=flat-square)
425
+ ![H2](https://img.shields.io/badge/H2-9-8b5cf6?style=flat-square)
426
+ ![H3](https://img.shields.io/badge/H3-15-a78bfa?style=flat-square)
427
+ ![H4](https://img.shields.io/badge/H4-0-c4b5fd?style=flat-square)
428
+ ![H5](https://img.shields.io/badge/H5-0-ddd6fe?style=flat-square)
429
+ ![H6](https://img.shields.io/badge/H6-0-ede9fe?style=flat-square)
430
+ ![Paragraphs](https://img.shields.io/badge/Paragraphs-53-64748b?style=flat-square)
431
+ ![Lists](https://img.shields.io/badge/Lists-9-16a34a?style=flat-square)
432
+ ![List Items](https://img.shields.io/badge/List_Items-33-22c55e?style=flat-square)
433
+ ![Task List Items](https://img.shields.io/badge/Task_List_Items-0-4ade80?style=flat-square)
434
+ ![Tables](https://img.shields.io/badge/Tables-2-0284c7?style=flat-square)
435
+ ![Table Rows](https://img.shields.io/badge/Table_Rows-10-0ea5e9?style=flat-square)
436
+ ![Links](https://img.shields.io/badge/Links-17-059669?style=flat-square)
437
+ ![Images](https://img.shields.io/badge/Images-0-10b981?style=flat-square)
438
+ ![Code Blocks](https://img.shields.io/badge/Code_Blocks-11-dc2626?style=flat-square)
439
+ ![Inline Code](https://img.shields.io/badge/Inline_Code-72-ef4444?style=flat-square)
440
+ ![Block Quotes](https://img.shields.io/badge/Block_Quotes-0-ca8a04?style=flat-square)
441
+ ![Thematic Breaks](https://img.shields.io/badge/Thematic_Breaks-0-a16207?style=flat-square)
442
+ <!-- codometer:end -->
@@ -0,0 +1,273 @@
1
+ import { Glossary } from '@coinagerie/core';
2
+ import { GlossaryDifference } from '@coinagerie/core';
3
+ import { ResolvedCoinagerieOutput } from '@coinagerie/configuration';
4
+
5
+ /** How a glossary disagrees with the vocabulary; both lists sorted. */
6
+ export declare interface Comparison {
7
+ /** Words the vocabulary has that the glossary does not list. */
8
+ missing: string[];
9
+ /** Words the glossary lists that the vocabulary no longer has. */
10
+ stale: string[];
11
+ }
12
+
13
+ /**
14
+ * Provides the comparison of a glossary with the codebase's vocabulary.
15
+ */
16
+ export declare class ComparisonModule {
17
+ }
18
+
19
+ /**
20
+ * Compares a glossary with the codebase's vocabulary, and reconciles one to
21
+ * the other. Only words are compared: an empty definition is never a
22
+ * disagreement.
23
+ */
24
+ export declare class ComparisonService {
25
+ constructor();
26
+ /** The words missing from the glossary and the words stale in it. */
27
+ compare(words: string[], glossary: Glossary): Comparison;
28
+ /**
29
+ * The glossary as `--write` leaves it: exactly the vocabulary, sorted, each
30
+ * word keeping the definition it had and a new word starting empty. Stale
31
+ * words are dropped with their definitions.
32
+ */
33
+ reconcile(words: string[], glossary: Glossary): Glossary;
34
+ }
35
+
36
+ /** What `check` and `write` are asked to do. */
37
+ export declare interface DeliverArguments {
38
+ destinations: ResolvedCoinagerieOutput[];
39
+ /** The codebase's vocabulary, sorted and de-duplicated. */
40
+ words: string[];
41
+ }
42
+
43
+ /**
44
+ * Provides checking and writing every glossary destination, wired to every
45
+ * format a destination can be written in.
46
+ */
47
+ export declare class DeliveryModule {
48
+ }
49
+
50
+ /** How one destination differed, and whether `write` changed its file. */
51
+ export declare interface DeliveryResult extends GlossaryDifference {
52
+ /** False when the file already held exactly what would be written. */
53
+ written: boolean;
54
+ }
55
+
56
+ /**
57
+ * Checks and writes every configured glossary destination.
58
+ *
59
+ * Every destination is read and parsed before any is written, so one that
60
+ * cannot be parsed stops the run with every file untouched. Destinations are
61
+ * independent: each is compared with the vocabulary alone, and no definition
62
+ * is ever copied from one to another.
63
+ */
64
+ export declare class DeliveryService {
65
+ private readonly comparisonService;
66
+ constructor(comparisonService: ComparisonService, jsonService: JsonService, markdownService: MarkdownService);
67
+ /**
68
+ * Each destination type to the format that reads and writes it. A record
69
+ * rather than a map, so adding a destination type fails to compile until
70
+ * its format is registered here.
71
+ */
72
+ private readonly handlers;
73
+ /** What a destination's file holds once written with exactly the words. */
74
+ private contentFor;
75
+ /** How one read destination differs from the vocabulary. */
76
+ private differenceOf;
77
+ /** The format a destination is written in. */
78
+ private handlerFor;
79
+ /** Reads and parses every destination, throwing before anything is written. */
80
+ private readAll;
81
+ /**
82
+ * How each destination differs from the vocabulary, writing nothing. A
83
+ * destination that does not exist yet is missing every word.
84
+ */
85
+ check(args: DeliverArguments): GlossaryDifference[];
86
+ /**
87
+ * Rewrites every destination to hold exactly the vocabulary, keeping every
88
+ * definition still in use. A file already holding exactly that is not
89
+ * touched, so a second write changes nothing.
90
+ */
91
+ write(args: DeliverArguments): DeliveryResult[];
92
+ }
93
+
94
+ /**
95
+ * What every glossary format provides to delivery: reading a glossary out of
96
+ * a file's content, and writing one back into it.
97
+ *
98
+ * `read` is handed `undefined` for a file that does not exist, returns
99
+ * `undefined` when the content holds no glossary yet, and throws
100
+ * `MalformedGlossaryError` when it holds one it cannot fully parse. `write`
101
+ * returns the file's whole new content, keeping whatever else the file held.
102
+ */
103
+ export declare interface GlossaryFormatHandler {
104
+ read(content: string | undefined, filePath: string): Glossary | undefined;
105
+ write(args: WriteGlossaryArguments): string;
106
+ }
107
+
108
+ /**
109
+ * Provides the JSON glossary format: rendering and strictly parsing a flat
110
+ * object of words and definitions.
111
+ */
112
+ export declare class JsonModule {
113
+ }
114
+
115
+ /**
116
+ * Renders and parses the JSON glossary: a flat object sorted by word, each
117
+ * value the definition or `""` when there is none yet.
118
+ *
119
+ * The whole file is the glossary, so writing replaces it. Parsing is strict
120
+ * on purpose, like the markdown format: anything it cannot read is refused
121
+ * rather than dropped, because a dropped word reads as stale and writing
122
+ * would delete its definition.
123
+ */
124
+ export declare class JsonService implements GlossaryFormatHandler {
125
+ constructor();
126
+ /** Where the string opening at `start` ends, past its closing quote. */
127
+ private endOfString;
128
+ /** Where the token starting at `start` ends. */
129
+ private endOfToken;
130
+ /** Fails a parse, naming the file and, when known, the one-indexed line. */
131
+ private fail;
132
+ /** Whether a character ends a run of literal characters. */
133
+ private isBoundary;
134
+ /** Whether a parsed value is an object, as opposed to an array or `null`. */
135
+ private isObject;
136
+ /** The parsed value, refusing content that is not valid JSON. */
137
+ private parseJson;
138
+ /**
139
+ * Refuses a word listed twice, which `JSON.parse` would resolve silently
140
+ * by keeping the last definition and dropping the first.
141
+ *
142
+ * Walks the tokens of content already known to be an object, reading the
143
+ * first string after the opening brace or after a top-level comma as a key.
144
+ */
145
+ private refuseDuplicates;
146
+ /**
147
+ * The tokens of content already known to be valid JSON: a string with its
148
+ * quotes and escapes, a bracket, comma, or colon, or any other run of
149
+ * literal characters. Whitespace is skipped.
150
+ *
151
+ * Enough to walk an object's top level and tell its keys from its values,
152
+ * which is what lets a duplicated key be found: `JSON.parse` keeps only the
153
+ * last, whatever type the first one had. Scanned by hand in one pass, since
154
+ * a regular expression for strings backtracks on input it cannot finish.
155
+ */
156
+ private tokenize;
157
+ /**
158
+ * The glossary a file holds.
159
+ *
160
+ * Throws `MalformedGlossaryError` on invalid JSON, anything but an object,
161
+ * a value that is not a string, or a word listed twice.
162
+ */
163
+ parse(content: string, filePath: string): Glossary;
164
+ /**
165
+ * Reads a destination's glossary; a missing or blank file holds none.
166
+ */
167
+ read(content: string | undefined, filePath: string): Glossary | undefined;
168
+ /**
169
+ * The whole file: one word per line, sorted by code unit whatever order
170
+ * the glossary holds them in, ending in a newline.
171
+ *
172
+ * Written out entry by entry rather than through an object, because an
173
+ * object would reorder a key that looks like an array index.
174
+ */
175
+ render(glossary: Glossary): string;
176
+ /** The destination's whole new content: nothing else is kept. */
177
+ write(args: WriteGlossaryArguments): string;
178
+ }
179
+
180
+ /**
181
+ * Provides the markdown glossary format: rendering, parsing, and splicing the
182
+ * anchored bullet block.
183
+ */
184
+ export declare class MarkdownModule {
185
+ }
186
+
187
+ /**
188
+ * Renders, parses, and splices the markdown glossary block.
189
+ *
190
+ * The block is the `## 🪙 Coinagerie` heading, an optional description, and
191
+ * one `- **word**: definition` bullet per word, between the
192
+ * `coinagerie:start` and `coinagerie:end` anchors. A definition continues on
193
+ * indented lines below its bullet, kept verbatim. Parsing is strict on
194
+ * purpose: anything it cannot read is refused rather than dropped, because a
195
+ * dropped word reads as stale and writing would delete its definition.
196
+ */
197
+ export declare class MarkdownService implements GlossaryFormatHandler {
198
+ constructor();
199
+ /** Fails a parse, naming the file and the one-indexed line. */
200
+ private fail;
201
+ /**
202
+ * Where the block sits, or `undefined` when the file has no anchors.
203
+ *
204
+ * Exactly one start anchor followed by exactly one end anchor; any other
205
+ * arrangement is refused.
206
+ */
207
+ private findAnchors;
208
+ /** Stores the word being read, with its definition, and forgets it. */
209
+ private flush;
210
+ /** Reads one bullet, starting a new word. */
211
+ private readBullet;
212
+ /**
213
+ * Adds an indented line, and any blank lines above it, to the definition
214
+ * being read; anything else in the bullet list is refused.
215
+ */
216
+ private readContinuation;
217
+ /**
218
+ * The prose between a block's heading and its first bullet, or `undefined`
219
+ * when there is none. Only ever read from content `parse` already accepted.
220
+ */
221
+ private readDescription;
222
+ /** Reads one line of the block, or refuses it. */
223
+ private readLine;
224
+ /** One word's bullet, with its definition after the colon. */
225
+ private renderBullet;
226
+ /**
227
+ * The glossary in a file's block, or `undefined` when it has no anchors.
228
+ *
229
+ * Throws `MalformedGlossaryError` on an unrecognized line, a duplicated
230
+ * word, or misplaced anchors. Free text between the heading and the first
231
+ * bullet is the description: never a word, and kept by `write`.
232
+ */
233
+ parse(content: string, filePath: string): Glossary | undefined;
234
+ /** Reads a destination's glossary; a missing file holds none. */
235
+ read(content: string | undefined, filePath: string): Glossary | undefined;
236
+ /**
237
+ * The whole block, anchors included, with no trailing newline. Words are
238
+ * rendered sorted, whatever order the glossary holds them in.
239
+ */
240
+ render(args: RenderMarkdownArguments): string;
241
+ /**
242
+ * A file's content with the block in place: replacing the anchored block
243
+ * when there is one, appending it after a blank line when there is not,
244
+ * and standing alone in a file that is empty or does not exist.
245
+ */
246
+ splice(content: string | undefined, block: string): string;
247
+ /**
248
+ * Writes a glossary into a destination's content as a rendered block.
249
+ *
250
+ * The configured description wins; without one, whatever prose already
251
+ * sits between the heading and the first bullet is kept, so a write never
252
+ * deletes text a person wrote there.
253
+ */
254
+ write(args: WriteGlossaryArguments): string;
255
+ }
256
+
257
+ /** What a markdown block is rendered from. */
258
+ export declare interface RenderMarkdownArguments {
259
+ /** Rendered under the heading when present. */
260
+ description?: string | undefined;
261
+ glossary: Glossary;
262
+ }
263
+
264
+ /** What a format writes a glossary from. */
265
+ export declare interface WriteGlossaryArguments {
266
+ /** The file's current content, or `undefined` when it does not exist. */
267
+ content: string | undefined;
268
+ destination: ResolvedCoinagerieOutput;
269
+ /** The glossary to write, already reconciled and sorted. */
270
+ glossary: Glossary;
271
+ }
272
+
273
+ export { }
@@ -0,0 +1,301 @@
1
+ import { Injectable as e, Module as t } from "@nestjs/common";
2
+ import { COINAGERIE_END_ANCHOR as n, COINAGERIE_HEADING as r, COINAGERIE_START_ANCHOR as i, MalformedGlossaryError as a } from "@coinagerie/core";
3
+ import { existsSync as o, mkdirSync as s, readFileSync as c, writeFileSync as l } from "node:fs";
4
+ import u from "node:path";
5
+ // \0@oxc-project+runtime@0.155.0/helpers/esm/decorateMetadata.js
6
+ function __decorateMetadata(e, t) {
7
+ if (typeof Reflect == "object" && typeof Reflect.metadata == "function") return Reflect.metadata(e, t);
8
+ }
9
+ // \0@oxc-project+runtime@0.155.0/helpers/esm/decorate.js
10
+ function __decorate(e, t, n, r) {
11
+ var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
12
+ if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
13
+ else for (var s = e.length - 1; s >= 0; s--) (o = e[s]) && (a = (i < 3 ? o(a) : i > 3 ? o(t, n, a) : o(t, n)) || a);
14
+ return i > 3 && a && Object.defineProperty(t, n, a), a;
15
+ }
16
+ // src/modules/comparison/comparison.service.ts
17
+ var d = class ComparisonService {
18
+ constructor() {}
19
+ compare(e, t) {
20
+ let n = new Set(e);
21
+ return {
22
+ missing: e.filter((e) => !t.has(e)).toSorted(),
23
+ stale: [...t.keys()].filter((e) => !n.has(e)).toSorted()
24
+ };
25
+ }
26
+ reconcile(e, t) {
27
+ return new Map(e.toSorted().map((e) => [e, t.get(e) ?? ""]));
28
+ }
29
+ };
30
+ d = __decorate([e(), __decorateMetadata("design:paramtypes", [])], d);
31
+ // src/modules/comparison/comparison.module.ts
32
+ var f = class ComparisonModule {};
33
+ f = __decorate([t({
34
+ controllers: [],
35
+ exports: [d],
36
+ imports: [],
37
+ providers: [d]
38
+ })], f);
39
+ // src/modules/json/json.service.ts
40
+ var p = class JsonService {
41
+ constructor() {}
42
+ endOfString(e, t) {
43
+ let n = t + 1;
44
+ for (; n < e.length && e.charAt(n) !== "\"";) n += e.charAt(n) === "\\" ? 2 : 1;
45
+ return n + 1;
46
+ }
47
+ endOfToken(e, t) {
48
+ let n = e.charAt(t);
49
+ if (n === "\"") return this.endOfString(e, t);
50
+ if ("[]{},:".includes(n)) return t + 1;
51
+ let r = t + 1;
52
+ for (; r < e.length && !this.isBoundary(e.charAt(r));) r += 1;
53
+ return r;
54
+ }
55
+ fail(e, t, n) {
56
+ throw new a({
57
+ line: n,
58
+ path: e.filePath,
59
+ reason: t
60
+ });
61
+ }
62
+ isBoundary(e) {
63
+ return e === "\"" || "[]{},:".includes(e) || /\s/u.test(e);
64
+ }
65
+ isObject(e) {
66
+ return typeof e == "object" && !!e && !Array.isArray(e);
67
+ }
68
+ parseJson(e) {
69
+ try {
70
+ return JSON.parse(e.content);
71
+ } catch (t) {
72
+ return this.fail(e, `the file is not valid JSON (${String(t)})`);
73
+ }
74
+ }
75
+ refuseDuplicates(e) {
76
+ let t = /* @__PURE__ */ new Set(), n = 0, r = "";
77
+ for (let i of this.tokenize(e.content)) {
78
+ let { text: a } = i;
79
+ if (n === 1 && a.startsWith("\"") && (r === "{" || r === ",")) {
80
+ let n = String(JSON.parse(a));
81
+ t.has(n) && this.fail(e, `the word "${n}" is listed twice`, e.content.slice(0, i.index).split("\n").length), t.add(n);
82
+ }
83
+ n += +!!"{[".includes(a), n -= +!!"}]".includes(a), r = a;
84
+ }
85
+ }
86
+ tokenize(e) {
87
+ let t = [], n = 0;
88
+ for (; n < e.length;) if (/\s/u.test(e.charAt(n))) n += 1;
89
+ else {
90
+ let r = this.endOfToken(e, n);
91
+ t.push({
92
+ index: n,
93
+ text: e.slice(n, r)
94
+ }), n = r;
95
+ }
96
+ return t;
97
+ }
98
+ parse(e, t) {
99
+ let n = {
100
+ content: e,
101
+ filePath: t
102
+ }, r = this.parseJson(n);
103
+ if (!this.isObject(r)) return this.fail(n, "the file must be an object of words");
104
+ this.refuseDuplicates(n);
105
+ let i = /* @__PURE__ */ new Map();
106
+ for (let [e, t] of Object.entries(r)) typeof t != "string" && this.fail(n, `the value of "${e}" is not a string`), i.set(e, t);
107
+ return i;
108
+ }
109
+ read(e, t) {
110
+ return e === void 0 || e.trim() === "" ? void 0 : this.parse(e, t);
111
+ }
112
+ render(e) {
113
+ let t = [...e].toSorted(([e], [t]) => e < t ? -1 : 1).map(([e, t]) => ` ${JSON.stringify(e)}: ${JSON.stringify(t)}`);
114
+ return t.length === 0 ? "{}\n" : `{\n${t.join(",\n")}\n}\n`;
115
+ }
116
+ write(e) {
117
+ return this.render(e.glossary);
118
+ }
119
+ };
120
+ p = __decorate([e(), __decorateMetadata("design:paramtypes", [])], p);
121
+ // src/modules/json/json.module.ts
122
+ var m = class JsonModule {};
123
+ m = __decorate([t({
124
+ controllers: [],
125
+ exports: [p],
126
+ imports: [],
127
+ providers: [p]
128
+ })], m);
129
+ // src/modules/markdown/markdown.constants.ts
130
+ var h = /^- \*\*(?<word>[^*]+)\*\*:(?<rest>.*)$/u, g = /^[-*+]\s/u, _ = /\r?\n/u, v = /^[ \t]+\S/u, y = class MarkdownService {
131
+ constructor() {}
132
+ fail(e, t, n) {
133
+ throw new a({
134
+ line: t,
135
+ path: e.filePath,
136
+ reason: n
137
+ });
138
+ }
139
+ findAnchors(e, t) {
140
+ let indexesOf = (t) => e.flatMap((e, n) => e.trim() === t ? [n] : []), [r, a] = indexesOf(i), [o, s] = indexesOf(n), c = { filePath: t };
141
+ if (a !== void 0 && this.fail(c, a + 1, "a second start anchor"), s !== void 0 && this.fail(c, s + 1, "a second end anchor"), r !== void 0 && o !== void 0) return o < r && this.fail(c, o + 1, "an end anchor before the start anchor"), {
142
+ end: o,
143
+ start: r
144
+ };
145
+ let l = r ?? o;
146
+ l !== void 0 && this.fail(c, l + 1, "an anchor without its partner");
147
+ }
148
+ flush(e) {
149
+ e.current !== void 0 && e.glossary.set(e.current.word, e.current.lines.join("\n")), e.current = void 0, e.pendingBlankLines = 0;
150
+ }
151
+ readBullet(e, t, n) {
152
+ t.rest !== "" && !t.rest.startsWith(" ") && this.fail(e, n, "a definition must follow its colon after a space"), this.flush(e), e.glossary.has(t.word) && this.fail(e, n, `the word "${t.word}" is listed twice`), e.current = {
153
+ lines: [t.rest.slice(1)],
154
+ word: t.word
155
+ };
156
+ }
157
+ readContinuation(e, t, n) {
158
+ e.current !== void 0 && v.test(t) || this.fail(e, n, "not a word bullet or an indented continuation"), e.current.lines.push(...Array.from({ length: e.pendingBlankLines }, () => ""), t), e.pendingBlankLines = 0;
159
+ }
160
+ readDescription(e) {
161
+ let t = e?.split(_) ?? [], n = this.findAnchors(t, ""), i = n === void 0 ? [] : t.slice(n.start + 1, n.end), a = i.findIndex((e) => g.test(e)), o = (a === -1 ? i : i.slice(0, a)).filter((e) => e.trim() !== r).join("\n").trim();
162
+ return o === "" ? void 0 : o;
163
+ }
164
+ readLine(e, t, n) {
165
+ if (!e.readingBullets && !g.test(t)) return;
166
+ e.readingBullets = !0;
167
+ let r = h.exec(t)?.groups;
168
+ r?.word !== void 0 && r.rest !== void 0 ? this.readBullet(e, {
169
+ rest: r.rest,
170
+ word: r.word
171
+ }, n) : t.trim() === "" ? e.pendingBlankLines += 1 : this.readContinuation(e, t, n);
172
+ }
173
+ renderBullet(e, t) {
174
+ return t === "" || t.startsWith("\n") ? `- **${e}**:${t}` : `- **${e}**: ${t}`;
175
+ }
176
+ parse(e, t) {
177
+ let n = e.split(_), r = this.findAnchors(n, t);
178
+ if (r === void 0) return;
179
+ let i = {
180
+ current: void 0,
181
+ filePath: t,
182
+ glossary: /* @__PURE__ */ new Map(),
183
+ pendingBlankLines: 0,
184
+ readingBullets: !1
185
+ };
186
+ return n.slice(r.start + 1, r.end).forEach((e, t) => {
187
+ this.readLine(i, e, r.start + t + 2);
188
+ }), this.flush(i), i.glossary;
189
+ }
190
+ read(e, t) {
191
+ return e === void 0 ? void 0 : this.parse(e, t);
192
+ }
193
+ render(e) {
194
+ let t = [
195
+ i,
196
+ "",
197
+ r,
198
+ ""
199
+ ], a = e.description === void 0 || e.description === "" ? [] : [e.description, ""], o = [...e.glossary].toSorted(([e], [t]) => e < t ? -1 : 1).map(([e, t]) => this.renderBullet(e, t));
200
+ return [
201
+ ...t,
202
+ ...a,
203
+ ...o,
204
+ ...o.length > 0 ? [""] : [],
205
+ n
206
+ ].join("\n");
207
+ }
208
+ splice(e, t) {
209
+ if (e === void 0 || e === "") return `${t}\n`;
210
+ let n = e.includes("\r\n") ? "\r\n" : "\n", r = t.split("\n"), i = e.split(_), a = this.findAnchors(i, "");
211
+ return a === void 0 ? `${e.endsWith("\n") ? e : `${e}${n}`}${n}${r.join(n)}${n}` : [
212
+ ...i.slice(0, a.start),
213
+ ...r,
214
+ ...i.slice(a.end + 1)
215
+ ].join(n);
216
+ }
217
+ write(e) {
218
+ let t = "description" in e.destination ? e.destination.description : void 0;
219
+ return this.splice(e.content, this.render({
220
+ description: t ?? this.readDescription(e.content),
221
+ glossary: e.glossary
222
+ }));
223
+ }
224
+ };
225
+ y = __decorate([e(), __decorateMetadata("design:paramtypes", [])], y);
226
+ // src/modules/markdown/markdown.module.ts
227
+ var b = class MarkdownModule {};
228
+ b = __decorate([t({
229
+ controllers: [],
230
+ exports: [y],
231
+ imports: [],
232
+ providers: [y]
233
+ })], b);
234
+ // src/modules/delivery/delivery.service.ts
235
+ var x = class DeliveryService {
236
+ comparisonService;
237
+ constructor(e, t, n) {
238
+ this.comparisonService = e, this.handlers = {
239
+ json: t,
240
+ markdown: n
241
+ };
242
+ }
243
+ handlers;
244
+ contentFor(e, t) {
245
+ return this.handlerFor(e.destination).write({
246
+ content: e.content,
247
+ destination: e.destination,
248
+ glossary: this.comparisonService.reconcile(t, e.glossary)
249
+ });
250
+ }
251
+ differenceOf(e, t) {
252
+ return {
253
+ ...this.comparisonService.compare(t, e.glossary),
254
+ path: e.destination.path,
255
+ type: e.destination.type
256
+ };
257
+ }
258
+ handlerFor(e) {
259
+ return this.handlers[e.type];
260
+ }
261
+ readAll(e) {
262
+ return e.map((e) => {
263
+ let t = o(e.path) ? c(e.path, "utf8") : void 0;
264
+ return {
265
+ content: t,
266
+ destination: e,
267
+ glossary: this.handlerFor(e).read(t, e.path) ?? /* @__PURE__ */ new Map()
268
+ };
269
+ });
270
+ }
271
+ check(e) {
272
+ return this.readAll(e.destinations).map((t) => this.differenceOf(t, e.words));
273
+ }
274
+ write(e) {
275
+ return this.readAll(e.destinations).map((t) => {
276
+ let n = this.contentFor(t, e.words), r = n !== t.content;
277
+ return r && (s(u.dirname(t.destination.path), { recursive: !0 }), l(t.destination.path, n)), {
278
+ ...this.differenceOf(t, e.words),
279
+ written: r
280
+ };
281
+ });
282
+ }
283
+ };
284
+ x = __decorate([e(), __decorateMetadata("design:paramtypes", [
285
+ d === void 0 ? Object : d,
286
+ p === void 0 ? Object : p,
287
+ y === void 0 ? Object : y
288
+ ])], x);
289
+ // src/modules/delivery/delivery.module.ts
290
+ var S = class DeliveryModule {};
291
+ S = __decorate([t({
292
+ controllers: [],
293
+ exports: [x],
294
+ imports: [
295
+ f,
296
+ m,
297
+ b
298
+ ],
299
+ providers: [x]
300
+ })], S);
301
+ export { f as ComparisonModule, d as ComparisonService, S as DeliveryModule, x as DeliveryService, m as JsonModule, p as JsonService, b as MarkdownModule, y as MarkdownService };
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
+ "author": "Jimmy Paolini",
3
+ "bugs": {
4
+ "url": "https://github.com/organizzolini/codebase/issues"
5
+ },
6
+ "dependencies": {
7
+ "@coinagerie/configuration": "0.0.2",
8
+ "@coinagerie/core": "0.0.2",
9
+ "@nestjs/common": "^11.1.21 || ^12.0.1",
10
+ "reflect-metadata": "^0.2.2"
11
+ },
12
+ "description": "Renders and parses the markdown and JSON glossaries and compares them with the vocabulary of a codebase",
13
+ "devDependencies": {
14
+ "@golevelup/ts-vitest": "^4.0.0",
15
+ "@nestjs/testing": "^12.0.1",
16
+ "@nx/eslint-plugin": "^24.0.0",
17
+ "@swc/helpers": "^0.5.21",
18
+ "@types/node": "^26.5.1",
19
+ "@vitest/coverage-v8": "^5.0.0",
20
+ "vitest": "^5.0.0"
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/src/index.d.ts",
25
+ "default": "./dist/src/index.js"
26
+ },
27
+ "./package.json": "./package.json"
28
+ },
29
+ "files": [
30
+ "CHANGELOG.md",
31
+ "dist",
32
+ "LICENSE",
33
+ "README.md"
34
+ ],
35
+ "homepage": "https://github.com/organizzolini/codebase/tree/main/projects/ic-suite/coinagerie/coinagerie-output#readme",
36
+ "keywords": [
37
+ "coinagerie",
38
+ "glossary",
39
+ "rendering",
40
+ "vocabulary"
41
+ ],
42
+ "license": "MIT",
43
+ "main": "./dist/src/index.js",
2
44
  "name": "@coinagerie/output",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
45
+ "publishConfig": {
46
+ "access": "public",
47
+ "registry": "https://registry.npmjs.org"
48
+ },
49
+ "repository": {
50
+ "directory": "projects/ic-suite/coinagerie/coinagerie-output",
51
+ "type": "git",
52
+ "url": "git+https://github.com/organizzolini/codebase.git"
53
+ },
54
+ "type": "module",
55
+ "typeCoverage": {
56
+ "atLeast": 100,
57
+ "strict": true
58
+ },
59
+ "types": "./dist/src/index.d.ts",
60
+ "version": "0.0.2"
6
61
  }