@cbnventures/nova 0.12.0 → 0.13.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.
Files changed (183) hide show
  1. package/build/eslint.config.d.ts +32 -1
  2. package/build/eslint.config.d.ts.map +1 -1
  3. package/build/eslint.config.js +9 -1
  4. package/build/eslint.config.js.map +1 -1
  5. package/build/package.json +63 -59
  6. package/build/src/api/node-releases.d.ts +7 -0
  7. package/build/src/api/node-releases.d.ts.map +1 -0
  8. package/build/src/api/node-releases.js +67 -0
  9. package/build/src/api/node-releases.js.map +1 -0
  10. package/build/src/api/spdx-licenses.d.ts +7 -0
  11. package/build/src/api/spdx-licenses.d.ts.map +1 -0
  12. package/build/src/api/spdx-licenses.js +43 -0
  13. package/build/src/api/spdx-licenses.js.map +1 -0
  14. package/build/src/cli/index.js +60 -18
  15. package/build/src/cli/index.js.map +1 -1
  16. package/build/src/cli/recipe/pin-versions.d.ts +7 -0
  17. package/build/src/cli/recipe/pin-versions.d.ts.map +1 -0
  18. package/build/src/cli/recipe/pin-versions.js +145 -0
  19. package/build/src/cli/recipe/pin-versions.js.map +1 -0
  20. package/build/src/cli/recipe/sync-lts-engines.d.ts +6 -0
  21. package/build/src/cli/recipe/sync-lts-engines.d.ts.map +1 -0
  22. package/build/src/cli/recipe/sync-lts-engines.js +118 -0
  23. package/build/src/cli/recipe/sync-lts-engines.js.map +1 -0
  24. package/build/src/cli/recipe/sync-packages.d.ts +18 -0
  25. package/build/src/cli/recipe/sync-packages.d.ts.map +1 -0
  26. package/build/src/cli/recipe/sync-packages.js +1212 -0
  27. package/build/src/cli/recipe/sync-packages.js.map +1 -0
  28. package/build/src/cli/utility/changelog.d.ts +11 -0
  29. package/build/src/cli/utility/changelog.d.ts.map +1 -0
  30. package/build/src/cli/utility/changelog.js +670 -0
  31. package/build/src/cli/utility/changelog.js.map +1 -0
  32. package/build/src/cli/utility/initialize.d.ts +9 -5
  33. package/build/src/cli/utility/initialize.d.ts.map +1 -1
  34. package/build/src/cli/utility/initialize.js +478 -210
  35. package/build/src/cli/utility/initialize.js.map +1 -1
  36. package/build/src/cli/utility/type-check.d.ts +9 -0
  37. package/build/src/cli/utility/type-check.d.ts.map +1 -0
  38. package/build/src/cli/utility/type-check.js +59 -0
  39. package/build/src/cli/utility/type-check.js.map +1 -0
  40. package/build/src/cli/utility/version.d.ts +2 -2
  41. package/build/src/cli/utility/version.d.ts.map +1 -1
  42. package/build/src/cli/utility/version.js +107 -68
  43. package/build/src/cli/utility/version.js.map +1 -1
  44. package/build/src/lib/item.d.ts +23 -5
  45. package/build/src/lib/item.d.ts.map +1 -1
  46. package/build/src/lib/item.js +329 -4
  47. package/build/src/lib/item.js.map +1 -1
  48. package/build/src/lib/nova-config.d.ts +4 -4
  49. package/build/src/lib/nova-config.d.ts.map +1 -1
  50. package/build/src/lib/nova-config.js +76 -88
  51. package/build/src/lib/nova-config.js.map +1 -1
  52. package/build/src/lib/regex.d.ts +9 -1
  53. package/build/src/lib/regex.d.ts.map +1 -1
  54. package/build/src/lib/regex.js +9 -1
  55. package/build/src/lib/regex.js.map +1 -1
  56. package/build/src/lib/schema.d.ts +18 -0
  57. package/build/src/lib/schema.d.ts.map +1 -0
  58. package/build/src/lib/schema.js +13 -0
  59. package/build/src/lib/schema.js.map +1 -0
  60. package/build/src/lib/utility.d.ts +9 -1
  61. package/build/src/lib/utility.d.ts.map +1 -1
  62. package/build/src/lib/utility.js +219 -40
  63. package/build/src/lib/utility.js.map +1 -1
  64. package/build/src/presets/eslint/dx-code-style.d.mts.map +1 -1
  65. package/build/src/presets/eslint/dx-code-style.mjs +0 -20
  66. package/build/src/presets/eslint/dx-code-style.mjs.map +1 -1
  67. package/build/src/presets/eslint/lang-mdx.d.mts.map +1 -1
  68. package/build/src/presets/eslint/lang-mdx.mjs +0 -21
  69. package/build/src/presets/eslint/lang-mdx.mjs.map +1 -1
  70. package/build/src/presets/tsconfig/dx-strict.json +2 -1
  71. package/build/src/rules/eslint/index.d.ts +4 -0
  72. package/build/src/rules/eslint/index.d.ts.map +1 -1
  73. package/build/src/rules/eslint/index.js +4 -0
  74. package/build/src/rules/eslint/index.js.map +1 -1
  75. package/build/src/rules/eslint/no-logger-dev.d.ts +3 -1
  76. package/build/src/rules/eslint/no-logger-dev.d.ts.map +1 -1
  77. package/build/src/rules/eslint/no-logger-dev.js +4 -4
  78. package/build/src/rules/eslint/no-logger-dev.js.map +1 -1
  79. package/build/src/rules/eslint/no-raw-text-in-code.d.ts +6 -0
  80. package/build/src/rules/eslint/no-raw-text-in-code.d.ts.map +1 -0
  81. package/build/src/rules/eslint/no-raw-text-in-code.js +34 -0
  82. package/build/src/rules/eslint/no-raw-text-in-code.js.map +1 -0
  83. package/build/src/rules/eslint/no-regex-literal-flags.d.ts +6 -0
  84. package/build/src/rules/eslint/no-regex-literal-flags.d.ts.map +1 -0
  85. package/build/src/rules/eslint/no-regex-literal-flags.js +30 -0
  86. package/build/src/rules/eslint/no-regex-literal-flags.js.map +1 -0
  87. package/build/src/rules/eslint/no-regex-literals.d.ts +9 -0
  88. package/build/src/rules/eslint/no-regex-literals.d.ts.map +1 -0
  89. package/build/src/rules/eslint/no-regex-literals.js +55 -0
  90. package/build/src/rules/eslint/no-regex-literals.js.map +1 -0
  91. package/build/src/rules/eslint/switch-case-blocks.d.ts +6 -0
  92. package/build/src/rules/eslint/switch-case-blocks.d.ts.map +1 -0
  93. package/build/src/rules/eslint/switch-case-blocks.js +36 -0
  94. package/build/src/rules/eslint/switch-case-blocks.js.map +1 -0
  95. package/build/src/tests/api/node-releases.test.d.ts +2 -0
  96. package/build/src/tests/api/node-releases.test.d.ts.map +1 -0
  97. package/build/src/tests/api/node-releases.test.js +193 -0
  98. package/build/src/tests/api/node-releases.test.js.map +1 -0
  99. package/build/src/tests/api/spdx-licenses.test.d.ts +2 -0
  100. package/build/src/tests/api/spdx-licenses.test.d.ts.map +1 -0
  101. package/build/src/tests/api/spdx-licenses.test.js +91 -0
  102. package/build/src/tests/api/spdx-licenses.test.js.map +1 -0
  103. package/build/src/tests/cli/recipe/pin-versions.test.d.ts +2 -0
  104. package/build/src/tests/cli/recipe/pin-versions.test.d.ts.map +1 -0
  105. package/build/src/tests/cli/recipe/pin-versions.test.js +197 -0
  106. package/build/src/tests/cli/recipe/pin-versions.test.js.map +1 -0
  107. package/build/src/tests/cli/recipe/sync-lts-engines.test.d.ts +2 -0
  108. package/build/src/tests/cli/recipe/sync-lts-engines.test.d.ts.map +1 -0
  109. package/build/src/tests/cli/recipe/sync-lts-engines.test.js +131 -0
  110. package/build/src/tests/cli/recipe/sync-lts-engines.test.js.map +1 -0
  111. package/build/src/tests/lib/item.test.d.ts +2 -0
  112. package/build/src/tests/lib/item.test.d.ts.map +1 -0
  113. package/build/src/tests/lib/item.test.js +142 -0
  114. package/build/src/tests/lib/item.test.js.map +1 -0
  115. package/build/src/tests/lib/nova-config.test.d.ts +2 -0
  116. package/build/src/tests/lib/nova-config.test.d.ts.map +1 -0
  117. package/build/src/tests/lib/nova-config.test.js +489 -0
  118. package/build/src/tests/lib/nova-config.test.js.map +1 -0
  119. package/build/src/tests/lib/regex.test.d.ts +2 -0
  120. package/build/src/tests/lib/regex.test.d.ts.map +1 -0
  121. package/build/src/tests/lib/regex.test.js +342 -0
  122. package/build/src/tests/lib/regex.test.js.map +1 -0
  123. package/build/src/tests/lib/schema.test.d.ts +2 -0
  124. package/build/src/tests/lib/schema.test.d.ts.map +1 -0
  125. package/build/src/tests/lib/schema.test.js +260 -0
  126. package/build/src/tests/lib/schema.test.js.map +1 -0
  127. package/build/src/tests/lib/utility.test.js +704 -44
  128. package/build/src/tests/lib/utility.test.js.map +1 -1
  129. package/build/src/tests/rules/eslint/no-logger-dev.test.d.ts +2 -0
  130. package/build/src/tests/rules/eslint/no-logger-dev.test.d.ts.map +1 -0
  131. package/build/src/tests/rules/eslint/no-logger-dev.test.js +55 -0
  132. package/build/src/tests/rules/eslint/no-logger-dev.test.js.map +1 -0
  133. package/build/src/tests/rules/eslint/no-raw-text-in-code.test.d.ts +2 -0
  134. package/build/src/tests/rules/eslint/no-raw-text-in-code.test.d.ts.map +1 -0
  135. package/build/src/tests/rules/eslint/no-raw-text-in-code.test.js +47 -0
  136. package/build/src/tests/rules/eslint/no-raw-text-in-code.test.js.map +1 -0
  137. package/build/src/tests/rules/eslint/no-regex-literal-flags.test.d.ts +2 -0
  138. package/build/src/tests/rules/eslint/no-regex-literal-flags.test.d.ts.map +1 -0
  139. package/build/src/tests/rules/eslint/no-regex-literal-flags.test.js +47 -0
  140. package/build/src/tests/rules/eslint/no-regex-literal-flags.test.js.map +1 -0
  141. package/build/src/tests/rules/eslint/no-regex-literals.test.d.ts +2 -0
  142. package/build/src/tests/rules/eslint/no-regex-literals.test.d.ts.map +1 -0
  143. package/build/src/tests/rules/eslint/no-regex-literals.test.js +49 -0
  144. package/build/src/tests/rules/eslint/no-regex-literals.test.js.map +1 -0
  145. package/build/src/tests/rules/eslint/switch-case-blocks.test.d.ts +2 -0
  146. package/build/src/tests/rules/eslint/switch-case-blocks.test.d.ts.map +1 -0
  147. package/build/src/tests/rules/eslint/switch-case-blocks.test.js +43 -0
  148. package/build/src/tests/rules/eslint/switch-case-blocks.test.js.map +1 -0
  149. package/build/src/tests/toolkit/cli-header.test.d.ts +2 -0
  150. package/build/src/tests/toolkit/cli-header.test.d.ts.map +1 -0
  151. package/build/src/tests/toolkit/cli-header.test.js +143 -0
  152. package/build/src/tests/toolkit/cli-header.test.js.map +1 -0
  153. package/build/src/tests/toolkit/logger.test.d.ts +2 -0
  154. package/build/src/tests/toolkit/logger.test.d.ts.map +1 -0
  155. package/build/src/tests/toolkit/logger.test.js +96 -0
  156. package/build/src/tests/toolkit/logger.test.js.map +1 -0
  157. package/build/src/tests/toolkit/markdown-table.test.d.ts +2 -0
  158. package/build/src/tests/toolkit/markdown-table.test.d.ts.map +1 -0
  159. package/build/src/tests/toolkit/markdown-table.test.js +138 -0
  160. package/build/src/tests/toolkit/markdown-table.test.js.map +1 -0
  161. package/build/src/toolkit/cli-header.d.ts +1 -0
  162. package/build/src/toolkit/cli-header.d.ts.map +1 -1
  163. package/build/src/toolkit/cli-header.js +24 -13
  164. package/build/src/toolkit/cli-header.js.map +1 -1
  165. package/build/src/toolkit/index.d.ts +1 -1
  166. package/build/src/toolkit/index.d.ts.map +1 -1
  167. package/build/src/toolkit/index.js +1 -1
  168. package/build/src/toolkit/index.js.map +1 -1
  169. package/build/src/toolkit/logger.d.ts.map +1 -1
  170. package/build/src/toolkit/logger.js +25 -10
  171. package/build/src/toolkit/logger.js.map +1 -1
  172. package/build/src/toolkit/markdown-table.d.ts.map +1 -1
  173. package/build/src/toolkit/markdown-table.js +3 -3
  174. package/build/src/toolkit/markdown-table.js.map +1 -1
  175. package/package.json +63 -59
  176. package/build/src/cli/recipe/sync-metadata.d.ts +0 -5
  177. package/build/src/cli/recipe/sync-metadata.d.ts.map +0 -1
  178. package/build/src/cli/recipe/sync-metadata.js +0 -7
  179. package/build/src/cli/recipe/sync-metadata.js.map +0 -1
  180. package/build/src/cli/recipe/sync-versions.d.ts +0 -5
  181. package/build/src/cli/recipe/sync-versions.d.ts.map +0 -1
  182. package/build/src/cli/recipe/sync-versions.js +0 -7
  183. package/build/src/cli/recipe/sync-versions.js.map +0 -1
@@ -0,0 +1,670 @@
1
+ import { promises as fs } from 'fs';
2
+ import { join, resolve } from 'path';
3
+ import chalk from 'chalk';
4
+ import prompts from 'prompts';
5
+ import { itemChangelogAdjectives, itemChangelogCategoryBumpMap, itemChangelogNouns, itemChangelogValidBumps, itemChangelogValidCategories, itemChangelogVerbs, } from '../../lib/item.js';
6
+ import { NovaConfig } from '../../lib/nova-config.js';
7
+ import { PATTERN_LEADING_NEWLINES } from '../../lib/regex.js';
8
+ import { Logger } from '../../toolkit/index.js';
9
+ export class CLIUtilityChangelog {
10
+ static async run(options) {
11
+ const isDryRun = options.dryRun === true;
12
+ if (isDryRun === true) {
13
+ Logger.customize({
14
+ name: 'CLIUtilityChangelog.run',
15
+ purpose: 'options',
16
+ }).warn('Dry run enabled. File changes will not be made in this session.');
17
+ }
18
+ if (options.record === true && options.release === true) {
19
+ Logger.customize({
20
+ name: 'CLIUtilityChangelog.run',
21
+ purpose: 'validate',
22
+ }).error('Cannot use --record and --release together.');
23
+ process.exitCode = 1;
24
+ return;
25
+ }
26
+ if (options.record === true) {
27
+ if ((options.package === undefined
28
+ || options.category === undefined
29
+ || options.bump === undefined
30
+ || options.message === undefined)
31
+ && (options.package !== undefined
32
+ || options.category !== undefined
33
+ || options.bump !== undefined
34
+ || options.message !== undefined)) {
35
+ Logger.customize({
36
+ name: 'CLIUtilityChangelog.run',
37
+ purpose: 'validate',
38
+ }).error('Non-interactive record requires --package, --category, --bump, and --message.');
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ await CLIUtilityChangelog.record(options);
43
+ return;
44
+ }
45
+ if (options.release === true) {
46
+ await CLIUtilityChangelog.release(options);
47
+ return;
48
+ }
49
+ const modeOutput = await CLIUtilityChangelog.promptWithCancel({
50
+ type: 'select',
51
+ name: 'action',
52
+ message: 'What would you like to do?',
53
+ choices: [
54
+ {
55
+ title: 'Record a change',
56
+ value: 'record',
57
+ },
58
+ {
59
+ title: 'Release',
60
+ value: 'release',
61
+ },
62
+ ],
63
+ });
64
+ if (modeOutput.cancelled) {
65
+ return;
66
+ }
67
+ const modeOutputResult = modeOutput.result;
68
+ if (modeOutputResult.action === undefined) {
69
+ return;
70
+ }
71
+ if (modeOutputResult.action === 'record') {
72
+ await CLIUtilityChangelog.record(options);
73
+ }
74
+ else {
75
+ await CLIUtilityChangelog.release(options);
76
+ }
77
+ }
78
+ static async record(options) {
79
+ const isDryRun = options.dryRun === true;
80
+ const novaConfig = new NovaConfig();
81
+ const config = await novaConfig.load();
82
+ const workspaces = config.workspaces ?? {};
83
+ const eligibleWorkspaces = Object.entries(workspaces).filter((workspace) => {
84
+ const workspaceConfig = workspace[1];
85
+ const workspaceConfigPolicy = workspaceConfig.policy;
86
+ return workspaceConfigPolicy !== 'freezable';
87
+ });
88
+ if (eligibleWorkspaces.length === 0) {
89
+ Logger.customize({
90
+ name: 'CLIUtilityChangelog.record',
91
+ purpose: 'workspaces',
92
+ }).error('No eligible (non-freezable) workspaces found in "nova.config.json".');
93
+ process.exitCode = 1;
94
+ return;
95
+ }
96
+ let selectedPackage;
97
+ let selectedCategory;
98
+ let selectedBump;
99
+ let selectedMessage;
100
+ if (options.package !== undefined
101
+ && options.category !== undefined
102
+ && options.bump !== undefined
103
+ && options.message !== undefined) {
104
+ const validPackageEntry = eligibleWorkspaces.find((eligibleWorkspace) => {
105
+ const eligibleWorkspaceConfig = eligibleWorkspace[1];
106
+ const eligibleWorkspaceConfigName = eligibleWorkspaceConfig.name;
107
+ return eligibleWorkspaceConfigName === options.package;
108
+ });
109
+ const validPackage = (validPackageEntry !== undefined) ? validPackageEntry[1].name : undefined;
110
+ if (validPackage === undefined) {
111
+ Logger.customize({
112
+ name: 'CLIUtilityChangelog.record',
113
+ purpose: 'validate',
114
+ }).error(`Package "${options.package}" is not a valid non-freezable workspace.`);
115
+ process.exitCode = 1;
116
+ return;
117
+ }
118
+ const validCategory = itemChangelogValidCategories.find((itemChangelogValidCategory) => itemChangelogValidCategory === options.category);
119
+ if (validCategory === undefined) {
120
+ Logger.customize({
121
+ name: 'CLIUtilityChangelog.record',
122
+ purpose: 'validate',
123
+ }).error(`Category "${options.category}" is invalid. Use: ${itemChangelogValidCategories.join(', ')}.`);
124
+ process.exitCode = 1;
125
+ return;
126
+ }
127
+ const validBump = itemChangelogValidBumps.find((itemChangelogValidBump) => itemChangelogValidBump === options.bump);
128
+ if (validBump === undefined) {
129
+ Logger.customize({
130
+ name: 'CLIUtilityChangelog.record',
131
+ purpose: 'validate',
132
+ }).error(`Bump type "${options.bump}" is invalid. Use: ${itemChangelogValidBumps.join(', ')}.`);
133
+ process.exitCode = 1;
134
+ return;
135
+ }
136
+ const validMessage = options.message.trim();
137
+ if (validMessage === '') {
138
+ Logger.customize({
139
+ name: 'CLIUtilityChangelog.record',
140
+ purpose: 'validate',
141
+ }).error('Message cannot be empty.');
142
+ process.exitCode = 1;
143
+ return;
144
+ }
145
+ selectedPackage = validPackage;
146
+ selectedCategory = validCategory;
147
+ selectedBump = validBump;
148
+ selectedMessage = validMessage;
149
+ }
150
+ else {
151
+ if (eligibleWorkspaces.length === 1 && eligibleWorkspaces[0] !== undefined) {
152
+ selectedPackage = eligibleWorkspaces[0][1].name;
153
+ Logger.customize({
154
+ name: 'CLIUtilityChangelog.record',
155
+ purpose: 'package',
156
+ }).info(`Auto-selected package: ${selectedPackage}`);
157
+ }
158
+ else {
159
+ const packageOutput = await CLIUtilityChangelog.promptWithCancel({
160
+ type: 'select',
161
+ name: 'package',
162
+ message: 'Select a package.',
163
+ choices: eligibleWorkspaces.map((eligibleWorkspace) => {
164
+ const eligibleWorkspaceConfig = eligibleWorkspace[1];
165
+ const eligibleWorkspaceConfigName = eligibleWorkspaceConfig.name;
166
+ const eligibleWorkspaceConfigRole = eligibleWorkspaceConfig.role;
167
+ const eligibleWorkspaceConfigPolicy = eligibleWorkspaceConfig.policy;
168
+ return {
169
+ title: eligibleWorkspaceConfigName,
170
+ description: `${eligibleWorkspaceConfigRole} · ${eligibleWorkspaceConfigPolicy}`,
171
+ value: eligibleWorkspaceConfigName,
172
+ };
173
+ }),
174
+ });
175
+ if (packageOutput.cancelled) {
176
+ return;
177
+ }
178
+ const packageOutputResult = packageOutput.result;
179
+ if (packageOutputResult.package === undefined) {
180
+ return;
181
+ }
182
+ selectedPackage = packageOutputResult.package;
183
+ }
184
+ const categoryOutput = await CLIUtilityChangelog.promptWithCancel({
185
+ type: 'select',
186
+ name: 'category',
187
+ message: 'Select a category.',
188
+ choices: [
189
+ {
190
+ title: 'Updated',
191
+ description: 'An enhancement to existing functionality.',
192
+ value: 'updated',
193
+ },
194
+ {
195
+ title: 'Fixed',
196
+ description: 'A bug fix.',
197
+ value: 'fixed',
198
+ },
199
+ {
200
+ title: 'Added',
201
+ description: 'A new feature or capability.',
202
+ value: 'added',
203
+ },
204
+ {
205
+ title: 'Removed',
206
+ description: 'A removed feature or capability.',
207
+ value: 'removed',
208
+ },
209
+ ],
210
+ });
211
+ if (categoryOutput.cancelled) {
212
+ return;
213
+ }
214
+ const categoryOutputResult = categoryOutput.result;
215
+ if (categoryOutputResult.category === undefined) {
216
+ return;
217
+ }
218
+ selectedCategory = categoryOutputResult.category;
219
+ const messageOutput = await CLIUtilityChangelog.promptWithCancel({
220
+ type: 'text',
221
+ name: 'message',
222
+ message: 'Describe the change.',
223
+ validate: (value) => {
224
+ if (typeof value !== 'string' || value.trim() === '') {
225
+ return 'Enter a description.';
226
+ }
227
+ return true;
228
+ },
229
+ });
230
+ if (messageOutput.cancelled) {
231
+ return;
232
+ }
233
+ const messageOutputResult = messageOutput.result;
234
+ if (messageOutputResult.message === undefined) {
235
+ return;
236
+ }
237
+ selectedMessage = messageOutputResult.message.trim();
238
+ const suggestedBump = itemChangelogCategoryBumpMap[selectedCategory];
239
+ const bumpOutput = await CLIUtilityChangelog.promptWithCancel({
240
+ type: 'select',
241
+ name: 'bump',
242
+ message: 'Select version bump type.',
243
+ choices: [
244
+ {
245
+ title: 'Major',
246
+ description: 'Breaking change.',
247
+ value: 'major',
248
+ },
249
+ {
250
+ title: 'Minor',
251
+ description: 'New feature.',
252
+ value: 'minor',
253
+ },
254
+ {
255
+ title: 'Patch',
256
+ description: 'Bug fix.',
257
+ value: 'patch',
258
+ },
259
+ ],
260
+ initial: itemChangelogValidBumps.indexOf(suggestedBump),
261
+ });
262
+ if (bumpOutput.cancelled) {
263
+ return;
264
+ }
265
+ const bumpOutputResult = bumpOutput.result;
266
+ if (bumpOutputResult.bump === undefined) {
267
+ return;
268
+ }
269
+ selectedBump = bumpOutputResult.bump;
270
+ }
271
+ const fileName = CLIUtilityChangelog.generateFileName();
272
+ const changelogDir = join(process.cwd(), '.changelog');
273
+ const filePath = join(changelogDir, `${fileName}.md`);
274
+ const content = [
275
+ '---',
276
+ `package: "${selectedPackage}"`,
277
+ `category: ${selectedCategory}`,
278
+ `bump: ${selectedBump}`,
279
+ '---',
280
+ '',
281
+ selectedMessage,
282
+ '',
283
+ ].join('\n');
284
+ if (isDryRun === true) {
285
+ Logger.customize({
286
+ name: 'CLIUtilityChangelog.record',
287
+ purpose: 'dryRun',
288
+ padTop: 1,
289
+ }).info(`Would write "${filePath}":`);
290
+ process.stdout.write(`\n${content}`);
291
+ return;
292
+ }
293
+ await fs.mkdir(changelogDir, { recursive: true });
294
+ const readmePath = join(changelogDir, 'README.md');
295
+ try {
296
+ await fs.access(readmePath);
297
+ }
298
+ catch {
299
+ await fs.writeFile(readmePath, [
300
+ '# Changelog',
301
+ '',
302
+ 'Welcome! This folder was automatically generated by `nova utility changelog`, a tool designed for managing versioning and release notes in your monorepo. To learn more about how it works, visit the [documentation](https://cbnventures.github.io/nova/docs/cli/utilities/changelog).',
303
+ '',
304
+ ].join('\n'), 'utf-8');
305
+ }
306
+ await fs.writeFile(filePath, content, 'utf-8');
307
+ Logger.customize({
308
+ name: 'CLIUtilityChangelog.record',
309
+ purpose: 'saved',
310
+ padTop: 1,
311
+ }).info(`Recorded change to "${filePath}".`);
312
+ }
313
+ static async release(options) {
314
+ const isDryRun = options.dryRun === true;
315
+ const isNonInteractive = options.release === true;
316
+ const entries = await CLIUtilityChangelog.parseEntries();
317
+ if (entries.length === 0) {
318
+ Logger.customize({
319
+ name: 'CLIUtilityChangelog.release',
320
+ purpose: 'entries',
321
+ }).info('No changelog entries found in ".changelog/".');
322
+ return;
323
+ }
324
+ const grouped = new Map();
325
+ for (const entry of entries) {
326
+ const existing = grouped.get(entry.package) ?? [];
327
+ existing.push(entry);
328
+ grouped.set(entry.package, existing);
329
+ }
330
+ const novaConfig = new NovaConfig();
331
+ const config = await novaConfig.load();
332
+ const workspaces = config.workspaces ?? {};
333
+ const bumpPriority = {
334
+ major: 3,
335
+ minor: 2,
336
+ patch: 1,
337
+ };
338
+ const releases = [];
339
+ for (const entry of grouped) {
340
+ const packageName = entry[0];
341
+ const packageEntries = entry[1];
342
+ const workspaceEntry = Object.entries(workspaces).find((workspace) => {
343
+ const workspaceConfig = workspace[1];
344
+ const workspaceConfigName = workspaceConfig.name;
345
+ return workspaceConfigName === packageName;
346
+ });
347
+ if (workspaceEntry === undefined) {
348
+ Logger.customize({
349
+ name: 'CLIUtilityChangelog.release',
350
+ purpose: 'workspace',
351
+ }).error(`Package "${packageName}" not found in "nova.config.json".`);
352
+ process.exitCode = 1;
353
+ return;
354
+ }
355
+ const workspacePath = workspaceEntry[0];
356
+ const packageDir = resolve(process.cwd(), workspacePath);
357
+ const packageJsonPath = join(packageDir, 'package.json');
358
+ let packageJsonRaw;
359
+ try {
360
+ packageJsonRaw = await fs.readFile(packageJsonPath, 'utf-8');
361
+ }
362
+ catch {
363
+ Logger.customize({
364
+ name: 'CLIUtilityChangelog.release',
365
+ purpose: 'readPackageJson',
366
+ }).error(`Unable to read "${packageJsonPath}".`);
367
+ process.exitCode = 1;
368
+ return;
369
+ }
370
+ let packageJson;
371
+ try {
372
+ packageJson = JSON.parse(packageJsonRaw);
373
+ }
374
+ catch {
375
+ Logger.customize({
376
+ name: 'CLIUtilityChangelog.release',
377
+ purpose: 'parsePackageJson',
378
+ }).error(`Unable to parse "${packageJsonPath}".`);
379
+ process.exitCode = 1;
380
+ return;
381
+ }
382
+ const currentVersion = (typeof packageJson['version'] === 'string') ? packageJson['version'] : undefined;
383
+ if (currentVersion === undefined) {
384
+ Logger.customize({
385
+ name: 'CLIUtilityChangelog.release',
386
+ purpose: 'version',
387
+ }).error(`No "version" field found in "${packageJsonPath}".`);
388
+ process.exitCode = 1;
389
+ return;
390
+ }
391
+ let highestBump = 'patch';
392
+ for (const entry of packageEntries) {
393
+ if (bumpPriority[entry.bump] > bumpPriority[highestBump]) {
394
+ highestBump = entry.bump;
395
+ }
396
+ }
397
+ const versionParts = currentVersion.split('.').map(Number);
398
+ const versionPartsMajor = versionParts[0] ?? 0;
399
+ const versionPartsMinor = versionParts[1] ?? 0;
400
+ const versionPartsPatch = versionParts[2] ?? 0;
401
+ let newVersion = currentVersion;
402
+ switch (highestBump) {
403
+ case 'major': {
404
+ newVersion = `${versionPartsMajor + 1}.0.0`;
405
+ break;
406
+ }
407
+ case 'minor': {
408
+ newVersion = `${versionPartsMajor}.${versionPartsMinor + 1}.0`;
409
+ break;
410
+ }
411
+ case 'patch': {
412
+ newVersion = `${versionPartsMajor}.${versionPartsMinor}.${versionPartsPatch + 1}`;
413
+ break;
414
+ }
415
+ default: {
416
+ break;
417
+ }
418
+ }
419
+ releases.push({
420
+ packageName,
421
+ packageDir,
422
+ currentVersion,
423
+ newVersion,
424
+ highestBump,
425
+ entries: packageEntries,
426
+ });
427
+ }
428
+ Logger.customize({
429
+ name: 'CLIUtilityChangelog.release',
430
+ purpose: 'summary',
431
+ }).info('Release summary:');
432
+ const categoryOrder = [
433
+ 'updated',
434
+ 'fixed',
435
+ 'added',
436
+ 'removed',
437
+ ];
438
+ for (const release of releases) {
439
+ const releasePackageName = release.packageName;
440
+ const releaseCurrentVersion = release.currentVersion;
441
+ const releaseNewVersion = release.newVersion;
442
+ const releaseHighestBump = release.highestBump;
443
+ const releaseEntries = release.entries;
444
+ process.stdout.write(`\n ${chalk.bold(releasePackageName)}: ${releaseCurrentVersion} → ${chalk.green(releaseNewVersion)} (${releaseHighestBump})\n`);
445
+ for (const category of categoryOrder) {
446
+ const categoryEntries = releaseEntries.filter((releaseEntry) => releaseEntry.category === category);
447
+ if (categoryEntries.length === 0) {
448
+ continue;
449
+ }
450
+ process.stdout.write(` ${chalk.yellow(category.toUpperCase())}:\n`);
451
+ for (const categoryEntry of categoryEntries) {
452
+ const categoryEntryMessage = categoryEntry.message;
453
+ process.stdout.write(` - ${categoryEntryMessage}\n`);
454
+ }
455
+ }
456
+ }
457
+ process.stdout.write('\n');
458
+ if (isNonInteractive !== true) {
459
+ const confirmOutput = await CLIUtilityChangelog.promptWithCancel({
460
+ type: 'confirm',
461
+ name: 'confirm',
462
+ message: 'Proceed with release?',
463
+ initial: false,
464
+ });
465
+ if (confirmOutput.cancelled) {
466
+ Logger.customize({
467
+ name: 'CLIUtilityChangelog.release',
468
+ purpose: 'cancelled',
469
+ }).info('Release cancelled.');
470
+ return;
471
+ }
472
+ const confirmOutputResult = confirmOutput.result;
473
+ if (confirmOutputResult.confirm !== true) {
474
+ Logger.customize({
475
+ name: 'CLIUtilityChangelog.release',
476
+ purpose: 'cancelled',
477
+ }).info('Release cancelled.');
478
+ return;
479
+ }
480
+ }
481
+ if (isDryRun === true) {
482
+ Logger.customize({
483
+ name: 'CLIUtilityChangelog.release',
484
+ purpose: 'dryRun',
485
+ }).info('Dry run complete. No files were modified.');
486
+ return;
487
+ }
488
+ for (const release of releases) {
489
+ const releasePackageName = release.packageName;
490
+ const releasePackageDir = release.packageDir;
491
+ const releaseNewVersion = release.newVersion;
492
+ const releaseEntries = release.entries;
493
+ const packageJsonPath = join(releasePackageDir, 'package.json');
494
+ const packageJsonRaw = await fs.readFile(packageJsonPath, 'utf-8');
495
+ const packageJson = JSON.parse(packageJsonRaw);
496
+ Reflect.set(packageJson, 'version', releaseNewVersion);
497
+ const updatedPackageJson = JSON.stringify(packageJson, null, 2);
498
+ const updatedContents = `${updatedPackageJson}\n`;
499
+ await fs.writeFile(packageJsonPath, updatedContents, 'utf-8');
500
+ Logger.customize({
501
+ name: 'CLIUtilityChangelog.release',
502
+ purpose: 'bumpVersion',
503
+ }).info(`Updated "${packageJsonPath}" version to ${releaseNewVersion}.`);
504
+ await CLIUtilityChangelog.writeChangelog(releasePackageDir, releasePackageName, releaseNewVersion, releaseEntries);
505
+ Logger.customize({
506
+ name: 'CLIUtilityChangelog.release',
507
+ purpose: 'writeChangelog',
508
+ }).info(`Updated "CHANGELOG.md" for ${releasePackageName}.`);
509
+ }
510
+ for (const entry of entries) {
511
+ await fs.unlink(entry.filePath);
512
+ }
513
+ Logger.customize({
514
+ name: 'CLIUtilityChangelog.release',
515
+ purpose: 'complete',
516
+ padTop: 1,
517
+ }).info('Release complete.');
518
+ }
519
+ static async parseEntries() {
520
+ const changelogDir = join(process.cwd(), '.changelog');
521
+ const entries = [];
522
+ let files;
523
+ try {
524
+ const dirEntries = await fs.readdir(changelogDir);
525
+ files = dirEntries.filter((file) => file.endsWith('.md'));
526
+ }
527
+ catch {
528
+ return entries;
529
+ }
530
+ for (const file of files) {
531
+ const filePath = join(changelogDir, file);
532
+ const content = await fs.readFile(filePath, 'utf-8');
533
+ const lines = content.split('\n');
534
+ if (lines[0] !== '---') {
535
+ continue;
536
+ }
537
+ let endIndex = -1;
538
+ for (let i = 1; i < lines.length; i += 1) {
539
+ if (lines[i] === '---') {
540
+ endIndex = i;
541
+ break;
542
+ }
543
+ }
544
+ if (endIndex === -1) {
545
+ continue;
546
+ }
547
+ let entryPackage;
548
+ let entryCategory;
549
+ let entryBump;
550
+ for (let i = 1; i < endIndex; i += 1) {
551
+ const line = lines[i];
552
+ if (line === undefined) {
553
+ continue;
554
+ }
555
+ const colonIndex = line.indexOf(':');
556
+ if (colonIndex === -1) {
557
+ continue;
558
+ }
559
+ const key = line.slice(0, colonIndex).trim();
560
+ let value = line.slice(colonIndex + 1).trim();
561
+ if ((value.startsWith('"')
562
+ && value.endsWith('"'))
563
+ || (value.startsWith('\'')
564
+ && value.endsWith('\''))) {
565
+ value = value.slice(1, -1);
566
+ }
567
+ if (key === 'package') {
568
+ entryPackage = value;
569
+ }
570
+ else if (key === 'category') {
571
+ entryCategory = itemChangelogValidCategories.find((itemChangelogValidCategory) => itemChangelogValidCategory === value);
572
+ }
573
+ else if (key === 'bump') {
574
+ entryBump = itemChangelogValidBumps.find((itemChangelogValidBump) => itemChangelogValidBump === value);
575
+ }
576
+ }
577
+ const message = lines.slice(endIndex + 1).join('\n').trim();
578
+ if (entryPackage === undefined
579
+ || entryCategory === undefined
580
+ || entryBump === undefined
581
+ || message === '') {
582
+ Logger.customize({
583
+ name: 'CLIUtilityChangelog.parseEntries',
584
+ purpose: 'skip',
585
+ }).warn(`Skipping "${file}": invalid or missing front matter.`);
586
+ continue;
587
+ }
588
+ entries.push({
589
+ package: entryPackage,
590
+ category: entryCategory,
591
+ bump: entryBump,
592
+ message,
593
+ filePath,
594
+ });
595
+ }
596
+ return entries;
597
+ }
598
+ static async writeChangelog(packageDir, packageName, version, entries) {
599
+ const changelogPath = join(packageDir, 'CHANGELOG.md');
600
+ const today = new Date();
601
+ const dateString = [
602
+ today.getFullYear(),
603
+ (today.getMonth() + 1).toString().padStart(2, '0'),
604
+ today.getDate().toString().padStart(2, '0'),
605
+ ].join('-');
606
+ const byCategory = new Map();
607
+ for (const entry of entries) {
608
+ const existing = byCategory.get(entry.category) ?? [];
609
+ existing.push(entry.message);
610
+ byCategory.set(entry.category, existing);
611
+ }
612
+ const categoryOrder = ['updated', 'fixed', 'added', 'removed'];
613
+ const sectionParts = [];
614
+ sectionParts.push(`## ${version} (${dateString})`);
615
+ for (const category of categoryOrder) {
616
+ const messages = byCategory.get(category);
617
+ if (messages === undefined || messages.length === 0) {
618
+ continue;
619
+ }
620
+ sectionParts.push('');
621
+ sectionParts.push(`### ${category.toUpperCase()}`);
622
+ for (const message of messages) {
623
+ sectionParts.push(`- ${message}`);
624
+ }
625
+ }
626
+ const newSection = sectionParts.join('\n');
627
+ let existingContent = '';
628
+ try {
629
+ existingContent = await fs.readFile(changelogPath, 'utf-8');
630
+ }
631
+ catch {
632
+ }
633
+ const packageHeading = `# ${packageName}`;
634
+ if (existingContent === '') {
635
+ await fs.writeFile(changelogPath, `${packageHeading}\n\n${newSection}\n`, 'utf-8');
636
+ }
637
+ else if (existingContent.startsWith(packageHeading)) {
638
+ const afterHeading = existingContent.slice(packageHeading.length);
639
+ await fs.writeFile(changelogPath, `${packageHeading}\n\n${newSection}\n${afterHeading.replace(PATTERN_LEADING_NEWLINES, '\n')}`, 'utf-8');
640
+ }
641
+ else {
642
+ await fs.writeFile(changelogPath, `${packageHeading}\n\n${newSection}\n\n${existingContent}`, 'utf-8');
643
+ }
644
+ }
645
+ static async promptWithCancel(questions) {
646
+ let cancelled = false;
647
+ const result = await prompts(questions, {
648
+ onCancel: () => {
649
+ cancelled = true;
650
+ return false;
651
+ },
652
+ });
653
+ if (cancelled) {
654
+ return {
655
+ cancelled: true,
656
+ };
657
+ }
658
+ return {
659
+ cancelled: false,
660
+ result,
661
+ };
662
+ }
663
+ static generateFileName() {
664
+ const adjective = itemChangelogAdjectives[Math.floor(Math.random() * itemChangelogAdjectives.length)];
665
+ const noun = itemChangelogNouns[Math.floor(Math.random() * itemChangelogNouns.length)];
666
+ const verb = itemChangelogVerbs[Math.floor(Math.random() * itemChangelogVerbs.length)];
667
+ return `${adjective}-${noun}-${verb}`;
668
+ }
669
+ }
670
+ //# sourceMappingURL=changelog.js.map