@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,1212 @@
1
+ import { dirname, join, relative, sep, } from 'path';
2
+ import chalk from 'chalk';
3
+ import { NodeReleases } from '../../api/node-releases.js';
4
+ import { SpdxLicenses } from '../../api/spdx-licenses.js';
5
+ import { itemPackageJsonKeysBundler, itemPackageJsonKeysCorepack, itemPackageJsonKeysNodeJs, itemPackageJsonKeysNpm, itemPackageJsonSortOrder, } from '../../lib/item.js';
6
+ import { NovaConfig } from '../../lib/nova-config.js';
7
+ import { PATTERN_DIGITS, PATTERN_NAME_AT_VERSION, PATTERN_RANGE_CAPTURE_REMAINDER, PATTERN_RANGE_GREATER_EQUAL_MAJOR, PATTERN_RANGE_MAJOR, PATTERN_SEMVER_STRICT, } from '../../lib/regex.js';
8
+ import { isPlainObject, isProjectRoot, loadWorkspaceManifests, pathExists, saveWorkspaceManifest, } from '../../lib/utility.js';
9
+ import { Logger } from '../../toolkit/index.js';
10
+ export class CLIRecipeSyncPackages {
11
+ static async run(options) {
12
+ const currentDirectory = process.cwd();
13
+ const isAtProjectRoot = await isProjectRoot(currentDirectory);
14
+ if (isAtProjectRoot !== true) {
15
+ process.exitCode = 1;
16
+ return;
17
+ }
18
+ const isDryRun = options.dryRun === true;
19
+ const isIgnoreUnknown = options.ignoreUnknown === true;
20
+ const isReplaceFile = options.replaceFile === true;
21
+ if (isDryRun === true) {
22
+ Logger.customize({
23
+ name: 'CLIRecipeSyncPackages.run',
24
+ purpose: 'options',
25
+ }).warn('Dry run enabled. File changes will not be made in this session.');
26
+ }
27
+ if (isIgnoreUnknown === true) {
28
+ Logger.customize({
29
+ name: 'CLIRecipeSyncPackages.run',
30
+ purpose: 'options',
31
+ }).warn('Ignore unknown enabled. Unknown keys from the "package.json" file will ignored.');
32
+ }
33
+ if (isReplaceFile === true) {
34
+ const replaceFileNotice = (isDryRun) ? 'This option has no effect during a dry run session.' : 'Backup file will not be created.';
35
+ Logger.customize({
36
+ name: 'CLIRecipeSyncPackages.run',
37
+ purpose: 'options',
38
+ }).warn(`Replace file enabled. ${replaceFileNotice}`);
39
+ }
40
+ const novaConfig = new NovaConfig();
41
+ const workingFile = await novaConfig.load();
42
+ const workingFileWorkspaces = Object.entries(workingFile.workspaces ?? {});
43
+ if (workingFileWorkspaces.length === 0) {
44
+ Logger.customize({
45
+ name: 'CLIRecipeSyncPackages.run',
46
+ purpose: 'workspaces',
47
+ }).warn('Skipping package sync. No workspaces detected in the "nova.config.json" file.');
48
+ return;
49
+ }
50
+ const workspaces = await loadWorkspaceManifests({
51
+ projectRoot: currentDirectory,
52
+ workspaces: workingFileWorkspaces,
53
+ });
54
+ if (workspaces.length === 0) {
55
+ Logger.customize({
56
+ name: 'CLIRecipeSyncPackages.run',
57
+ purpose: 'workspaces',
58
+ }).warn('Skipping package sync. No accessible "package.json" files were found for the configured workspaces.');
59
+ return;
60
+ }
61
+ Logger.customize({
62
+ name: 'CLIRecipeSyncPackages.run',
63
+ purpose: 'summary',
64
+ }).info(`Prepared ${workspaces.length} workspace "package.json" file(s) for syncing.`);
65
+ for (const workspace of workspaces) {
66
+ Logger.customize({
67
+ name: 'CLIRecipeSyncPackages.run',
68
+ purpose: 'iteration',
69
+ }).info(`Syncing the "${workspace.manifest.name}" workspace ...`);
70
+ if (isIgnoreUnknown !== true) {
71
+ CLIRecipeSyncPackages.handleUnknown(workspace);
72
+ }
73
+ await CLIRecipeSyncPackages.handleIdentity(workspace, workingFile);
74
+ CLIRecipeSyncPackages.handleOwnership(workspace, workingFile);
75
+ CLIRecipeSyncPackages.handleRuntime(workspace);
76
+ await CLIRecipeSyncPackages.handleArtifacts(workspace);
77
+ await CLIRecipeSyncPackages.handlePublish(workspace);
78
+ await CLIRecipeSyncPackages.handleTooling(workspace);
79
+ CLIRecipeSyncPackages.handleCorepack(workspace);
80
+ await CLIRecipeSyncPackages.handleEnvironment(workspace);
81
+ await CLIRecipeSyncPackages.handleDependencies(workspace);
82
+ CLIRecipeSyncPackages.handleBundler(workspace);
83
+ CLIRecipeSyncPackages.handleReorder(workspace);
84
+ if (isDryRun === true) {
85
+ continue;
86
+ }
87
+ await saveWorkspaceManifest(workspace, isReplaceFile);
88
+ }
89
+ }
90
+ static async handleIdentity(workspace, workingFile) {
91
+ const { fileContents, filePath, manifest } = workspace;
92
+ const packageName = fileContents['name'];
93
+ const packageVersion = fileContents['version'];
94
+ const packageDescription = fileContents['description'];
95
+ const packageKeywords = fileContents['keywords'];
96
+ const packageLicense = fileContents['license'];
97
+ if (typeof packageName !== 'string'
98
+ || packageName !== manifest.name) {
99
+ Logger.customize({
100
+ name: 'CLIRecipeSyncPackages.handleIdentity',
101
+ purpose: 'name',
102
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "name" from workspace manifest ...`);
103
+ Reflect.set(fileContents, 'name', manifest.name);
104
+ }
105
+ if (typeof packageVersion !== 'string'
106
+ || !PATTERN_SEMVER_STRICT.test(packageVersion)
107
+ || (['freezable'].includes(manifest.policy)
108
+ && packageVersion !== '0.0.0') || (['trackable', 'distributable'].includes(manifest.policy)
109
+ && packageVersion === '0.0.0')) {
110
+ const validVersion = (manifest.policy === 'freezable') ? '0.0.0' : '0.0.1';
111
+ Logger.customize({
112
+ name: 'CLIRecipeSyncPackages.handleIdentity',
113
+ purpose: 'version',
114
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Invalid version detected. Setting it to "${validVersion}" ...`);
115
+ Reflect.set(fileContents, 'version', validVersion);
116
+ }
117
+ if (packageDescription !== undefined
118
+ && manifest.policy !== 'distributable') {
119
+ Logger.customize({
120
+ name: 'CLIRecipeSyncPackages.handleIdentity',
121
+ purpose: 'description',
122
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "description". Workspace policy "${manifest.policy}" does not allow it.`);
123
+ Reflect.deleteProperty(fileContents, 'description');
124
+ }
125
+ else {
126
+ const validDescription = (workingFile.project !== undefined && workingFile.project.description !== undefined) ? workingFile.project.description.short : undefined;
127
+ if ((manifest.policy === 'distributable'
128
+ && manifest.syncProperties !== undefined
129
+ && manifest.syncProperties.includes('description')
130
+ && validDescription !== undefined)
131
+ && (typeof packageDescription !== 'string'
132
+ || packageDescription !== validDescription)) {
133
+ Logger.customize({
134
+ name: 'CLIRecipeSyncPackages.handleIdentity',
135
+ purpose: 'description',
136
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "description" from workspace manifest ...`);
137
+ Reflect.set(fileContents, 'description', validDescription);
138
+ }
139
+ if (manifest.policy === 'distributable'
140
+ && manifest.syncProperties !== undefined
141
+ && manifest.syncProperties.includes('description')
142
+ && validDescription === undefined) {
143
+ Logger.customize({
144
+ name: 'CLIRecipeSyncPackages.handleIdentity',
145
+ purpose: 'description',
146
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "description". No description is defined.`);
147
+ Reflect.deleteProperty(fileContents, 'description');
148
+ }
149
+ }
150
+ if (packageKeywords !== undefined
151
+ && manifest.policy !== 'distributable') {
152
+ Logger.customize({
153
+ name: 'CLIRecipeSyncPackages.handleIdentity',
154
+ purpose: 'keywords',
155
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "keywords". Workspace policy "${manifest.policy}" does not allow it.`);
156
+ Reflect.deleteProperty(fileContents, 'keywords');
157
+ }
158
+ else {
159
+ const validKeywords = (workingFile.project !== undefined) ? workingFile.project.keywords : undefined;
160
+ if ((manifest.policy === 'distributable'
161
+ && manifest.syncProperties !== undefined
162
+ && manifest.syncProperties.includes('keywords')
163
+ && validKeywords !== undefined)
164
+ && (JSON.stringify(packageKeywords) !== JSON.stringify(validKeywords))) {
165
+ Logger.customize({
166
+ name: 'CLIRecipeSyncPackages.handleIdentity',
167
+ purpose: 'keywords',
168
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "keywords" from workspace manifest ...`);
169
+ Reflect.set(fileContents, 'keywords', validKeywords);
170
+ }
171
+ if (manifest.policy === 'distributable'
172
+ && manifest.syncProperties !== undefined
173
+ && manifest.syncProperties.includes('keywords')
174
+ && validKeywords === undefined) {
175
+ Logger.customize({
176
+ name: 'CLIRecipeSyncPackages.handleIdentity',
177
+ purpose: 'keywords',
178
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "keywords". No keywords are defined.`);
179
+ Reflect.deleteProperty(fileContents, 'keywords');
180
+ }
181
+ }
182
+ const spdxLicenses = await SpdxLicenses.fetchLicenses();
183
+ if (typeof packageLicense !== 'string'
184
+ || (spdxLicenses !== undefined && !spdxLicenses.has(packageLicense))) {
185
+ const packageDirectory = dirname(filePath);
186
+ const projectRoot = process.cwd();
187
+ const licenseCandidates = [
188
+ join(packageDirectory, 'LICENSE'),
189
+ join(packageDirectory, 'LICENSE.md'),
190
+ join(projectRoot, 'LICENSE'),
191
+ join(projectRoot, 'LICENSE.md'),
192
+ ];
193
+ let resolvedLicensePath;
194
+ for (const candidate of licenseCandidates) {
195
+ if (await pathExists(candidate)) {
196
+ resolvedLicensePath = candidate;
197
+ break;
198
+ }
199
+ }
200
+ const relativeLicensePath = (resolvedLicensePath !== undefined) ? relative(packageDirectory, resolvedLicensePath) : undefined;
201
+ let normalizedLicenseReference;
202
+ if (relativeLicensePath !== undefined) {
203
+ normalizedLicenseReference = (relativeLicensePath.startsWith('.')) ? relativeLicensePath : `./${relativeLicensePath}`;
204
+ }
205
+ const fallbackLicense = (normalizedLicenseReference !== undefined) ? `SEE LICENSE IN ${normalizedLicenseReference}` : 'UNLICENSED';
206
+ if (packageLicense === fallbackLicense) {
207
+ return;
208
+ }
209
+ Logger.customize({
210
+ name: 'CLIRecipeSyncPackages.handleIdentity',
211
+ purpose: 'license',
212
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "license" to "${fallbackLicense}" ...`);
213
+ Reflect.set(fileContents, 'license', fallbackLicense);
214
+ }
215
+ }
216
+ static async handleArtifacts(workspace) {
217
+ const { fileContents, manifest } = workspace;
218
+ const packageFiles = fileContents['files'];
219
+ const packageBin = fileContents['bin'];
220
+ const packageMan = fileContents['man'];
221
+ const packageDirectories = fileContents['directories'];
222
+ if (packageFiles !== undefined
223
+ && !['package', 'tool'].includes(manifest.role)) {
224
+ Logger.customize({
225
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
226
+ purpose: 'files',
227
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "files". Workspace role "${manifest.role}" does not allow it.`);
228
+ Reflect.deleteProperty(fileContents, 'files');
229
+ }
230
+ else {
231
+ if ((manifest.role === 'package' || manifest.role === 'tool')
232
+ && packageFiles !== undefined
233
+ && CLIRecipeSyncPackages.isEmpty(packageFiles)) {
234
+ Logger.customize({
235
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
236
+ purpose: 'files',
237
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "files" ...`);
238
+ Reflect.deleteProperty(fileContents, 'files');
239
+ }
240
+ }
241
+ if (packageBin !== undefined
242
+ && !['package', 'tool'].includes(manifest.role)) {
243
+ Logger.customize({
244
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
245
+ purpose: 'bin',
246
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "bin". Workspace role "${manifest.role}" does not allow it.`);
247
+ Reflect.deleteProperty(fileContents, 'bin');
248
+ }
249
+ else {
250
+ if ((manifest.role === 'package' || manifest.role === 'tool')
251
+ && packageBin !== undefined) {
252
+ if (typeof packageBin === 'string') {
253
+ const packageName = manifest.name;
254
+ const binName = (packageName.includes('/')) ? packageName.split('/').pop() : packageName;
255
+ Logger.customize({
256
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
257
+ purpose: 'bin',
258
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Normalizing "bin" from string to object ...`);
259
+ Reflect.set(fileContents, 'bin', {
260
+ [binName ?? packageName]: packageBin,
261
+ });
262
+ }
263
+ else if (CLIRecipeSyncPackages.isEmpty(packageBin)) {
264
+ Logger.customize({
265
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
266
+ purpose: 'bin',
267
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "bin" ...`);
268
+ Reflect.deleteProperty(fileContents, 'bin');
269
+ }
270
+ }
271
+ }
272
+ if (packageMan !== undefined
273
+ && !['package', 'tool'].includes(manifest.role)) {
274
+ Logger.customize({
275
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
276
+ purpose: 'man',
277
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "man". Workspace role "${manifest.role}" does not allow it.`);
278
+ Reflect.deleteProperty(fileContents, 'man');
279
+ }
280
+ else {
281
+ if ((manifest.role === 'package' || manifest.role === 'tool')
282
+ && packageMan !== undefined) {
283
+ if (typeof packageMan === 'string') {
284
+ Logger.customize({
285
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
286
+ purpose: 'man',
287
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Normalizing "man" from string to array ...`);
288
+ Reflect.set(fileContents, 'man', [packageMan]);
289
+ }
290
+ else if (CLIRecipeSyncPackages.isEmpty(packageMan)) {
291
+ Logger.customize({
292
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
293
+ purpose: 'man',
294
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "man" ...`);
295
+ Reflect.deleteProperty(fileContents, 'man');
296
+ }
297
+ }
298
+ }
299
+ if (packageDirectories !== undefined
300
+ && !['package', 'tool'].includes(manifest.role)) {
301
+ Logger.customize({
302
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
303
+ purpose: 'directories',
304
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "directories". Workspace role "${manifest.role}" does not allow it.`);
305
+ Reflect.deleteProperty(fileContents, 'directories');
306
+ }
307
+ else {
308
+ if ((manifest.role === 'package' || manifest.role === 'tool')
309
+ && packageDirectories !== undefined
310
+ && CLIRecipeSyncPackages.isEmpty(packageDirectories)) {
311
+ Logger.customize({
312
+ name: 'CLIRecipeSyncPackages.handleArtifacts',
313
+ purpose: 'directories',
314
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "directories" ...`);
315
+ Reflect.deleteProperty(fileContents, 'directories');
316
+ }
317
+ }
318
+ }
319
+ static async handlePublish(workspace) {
320
+ const { fileContents, manifest } = workspace;
321
+ const packagePrivate = fileContents['private'];
322
+ const packagePublishConfig = fileContents['publishConfig'];
323
+ if (typeof packagePrivate !== 'boolean'
324
+ || (manifest.policy === 'distributable'
325
+ && packagePrivate)
326
+ || (manifest.policy !== 'distributable'
327
+ && !packagePrivate)) {
328
+ const privateValue = (manifest.policy !== 'distributable');
329
+ Logger.customize({
330
+ name: 'CLIRecipeSyncPackages.handlePublish',
331
+ purpose: 'private',
332
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "private" to "${privateValue}" ...`);
333
+ Reflect.set(fileContents, 'private', privateValue);
334
+ }
335
+ if (packagePublishConfig !== undefined
336
+ && manifest.policy !== 'distributable') {
337
+ Logger.customize({
338
+ name: 'CLIRecipeSyncPackages.handlePublish',
339
+ purpose: 'publishConfig',
340
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "publishConfig". Workspace policy "${manifest.policy}" does not allow it.`);
341
+ Reflect.deleteProperty(fileContents, 'publishConfig');
342
+ }
343
+ else {
344
+ if (manifest.policy === 'distributable'
345
+ && packagePublishConfig !== undefined
346
+ && CLIRecipeSyncPackages.isEmpty(packagePublishConfig)) {
347
+ Logger.customize({
348
+ name: 'CLIRecipeSyncPackages.handlePublish',
349
+ purpose: 'publishConfig',
350
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "publishConfig" ...`);
351
+ Reflect.deleteProperty(fileContents, 'publishConfig');
352
+ }
353
+ }
354
+ }
355
+ static async handleTooling(workspace) {
356
+ const { fileContents, filePath, manifest } = workspace;
357
+ const packageScripts = fileContents['scripts'];
358
+ const packageGypfile = fileContents['gypfile'];
359
+ const packageConfig = fileContents['config'];
360
+ const packageWorkspaces = fileContents['workspaces'];
361
+ const hasBindingGyp = await pathExists(join(dirname(filePath), 'binding.gyp'));
362
+ if (packageScripts === undefined) {
363
+ Logger.customize({
364
+ name: 'CLIRecipeSyncPackages.handleTooling',
365
+ purpose: 'scripts',
366
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Adding "scripts" as an empty object ...`);
367
+ Reflect.set(fileContents, 'scripts', {});
368
+ }
369
+ if (packageGypfile !== undefined
370
+ && !hasBindingGyp) {
371
+ Logger.customize({
372
+ name: 'CLIRecipeSyncPackages.handleTooling',
373
+ purpose: 'gypfile',
374
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "gypfile". No "binding.gyp" file is found.`);
375
+ Reflect.deleteProperty(fileContents, 'gypfile');
376
+ }
377
+ else {
378
+ if (packageGypfile === undefined
379
+ && hasBindingGyp
380
+ && (!isPlainObject(packageScripts)
381
+ || (isPlainObject(packageScripts)
382
+ && packageScripts['preinstall'] === undefined
383
+ && packageScripts['install'] === undefined))) {
384
+ Logger.customize({
385
+ name: 'CLIRecipeSyncPackages.handleTooling',
386
+ purpose: 'gypfile',
387
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Adding "gypfile" as the npm default ...`);
388
+ Reflect.set(fileContents, 'gypfile', true);
389
+ }
390
+ }
391
+ if (packageConfig !== undefined
392
+ && CLIRecipeSyncPackages.isEmpty(packageConfig)) {
393
+ Logger.customize({
394
+ name: 'CLIRecipeSyncPackages.handleTooling',
395
+ purpose: 'config',
396
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "config" ...`);
397
+ Reflect.deleteProperty(fileContents, 'config');
398
+ }
399
+ if (packageWorkspaces !== undefined
400
+ && manifest.role !== 'project') {
401
+ Logger.customize({
402
+ name: 'CLIRecipeSyncPackages.handleTooling',
403
+ purpose: 'workspaces',
404
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "workspaces". Workspace role "${manifest.role}" does not allow it.`);
405
+ Reflect.deleteProperty(fileContents, 'workspaces');
406
+ }
407
+ else {
408
+ if (manifest.role === 'project'
409
+ && packageWorkspaces === undefined) {
410
+ Logger.customize({
411
+ name: 'CLIRecipeSyncPackages.handleTooling',
412
+ purpose: 'workspaces',
413
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Adding "workspaces" as an empty array ...`);
414
+ Reflect.set(fileContents, 'workspaces', []);
415
+ }
416
+ }
417
+ }
418
+ static async handleEnvironment(workspace) {
419
+ const { fileContents, manifest } = workspace;
420
+ const packageEngines = fileContents['engines'];
421
+ const packageOs = fileContents['os'];
422
+ const packageCpu = fileContents['cpu'];
423
+ const packageLibc = fileContents['libc'];
424
+ const packageDevEngines = fileContents['devEngines'];
425
+ const ltsConstraint = await NodeReleases.fetchLtsVersions();
426
+ if (packageEngines === undefined) {
427
+ if (ltsConstraint !== undefined) {
428
+ Logger.customize({
429
+ name: 'CLIRecipeSyncPackages.handleEnvironment',
430
+ purpose: 'engines',
431
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Adding "engines" with "node" set to "${ltsConstraint}" ...`);
432
+ Reflect.set(fileContents, 'engines', {
433
+ node: ltsConstraint,
434
+ });
435
+ }
436
+ else {
437
+ Logger.customize({
438
+ name: 'CLIRecipeSyncPackages.handleEnvironment',
439
+ purpose: 'engines',
440
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Adding "engines" as an empty object ...`);
441
+ Reflect.set(fileContents, 'engines', {});
442
+ }
443
+ }
444
+ else {
445
+ if (isPlainObject(packageEngines)
446
+ && packageEngines['node'] === undefined
447
+ && ltsConstraint !== undefined) {
448
+ Logger.customize({
449
+ name: 'CLIRecipeSyncPackages.handleEnvironment',
450
+ purpose: 'engines',
451
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Adding "engines.node" set to "${ltsConstraint}" ...`);
452
+ Reflect.set(packageEngines, 'node', ltsConstraint);
453
+ }
454
+ }
455
+ if (manifest.syncLtsEngines === true
456
+ && isPlainObject(fileContents['engines'])
457
+ && typeof fileContents['engines']['node'] === 'string'
458
+ && ltsConstraint !== undefined) {
459
+ const existingNode = fileContents['engines']['node'];
460
+ const ltsMatches = [...ltsConstraint.matchAll(new RegExp(PATTERN_DIGITS.source, 'g'))];
461
+ if (ltsMatches.length > 0) {
462
+ const ltsMajors = ltsMatches.map((match) => parseInt(match[0], 10));
463
+ const branches = existingNode.split('||').map((branch) => branch.trim());
464
+ const coversAll = ltsMajors.every((major) => {
465
+ return branches.some((branch) => {
466
+ if (branch === '*') {
467
+ return true;
468
+ }
469
+ const geMatch = branch.match(PATTERN_RANGE_GREATER_EQUAL_MAJOR);
470
+ if (geMatch !== null) {
471
+ return parseInt(geMatch[1] ?? '', 10) <= major;
472
+ }
473
+ const majorMatch = branch.match(PATTERN_RANGE_MAJOR);
474
+ if (majorMatch !== null) {
475
+ return parseInt(majorMatch[1] ?? '', 10) === major;
476
+ }
477
+ return false;
478
+ });
479
+ });
480
+ if (coversAll !== true) {
481
+ Logger.customize({
482
+ name: 'CLIRecipeSyncPackages.handleEnvironment',
483
+ purpose: 'engines',
484
+ }).warn(`${chalk.magenta(`"${manifest.name}" workspace`)} → "engines.node" is "${existingNode}" but must cover all active LTS versions (${ltsMajors.join(', ')}). Run "nova recipe sync-lts-engines" to update.`);
485
+ }
486
+ }
487
+ }
488
+ if (packageOs !== undefined
489
+ && CLIRecipeSyncPackages.isEmpty(packageOs)) {
490
+ Logger.customize({
491
+ name: 'CLIRecipeSyncPackages.handleEnvironment',
492
+ purpose: 'os',
493
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "os" ...`);
494
+ Reflect.deleteProperty(fileContents, 'os');
495
+ }
496
+ if (packageCpu !== undefined
497
+ && CLIRecipeSyncPackages.isEmpty(packageCpu)) {
498
+ Logger.customize({
499
+ name: 'CLIRecipeSyncPackages.handleEnvironment',
500
+ purpose: 'cpu',
501
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "cpu" ...`);
502
+ Reflect.deleteProperty(fileContents, 'cpu');
503
+ }
504
+ if (packageLibc !== undefined
505
+ && !(Array.isArray(fileContents['os'])
506
+ && fileContents['os'].includes('linux'))) {
507
+ Logger.customize({
508
+ name: 'CLIRecipeSyncPackages.handleEnvironment',
509
+ purpose: 'libc',
510
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "libc". Package "os" does not include "linux".`);
511
+ Reflect.deleteProperty(fileContents, 'libc');
512
+ }
513
+ else {
514
+ if (packageLibc === undefined
515
+ && (Array.isArray(fileContents['os'])
516
+ && fileContents['os'].includes('linux'))) {
517
+ Logger.customize({
518
+ name: 'CLIRecipeSyncPackages.handleEnvironment',
519
+ purpose: 'libc',
520
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Adding "libc" as "glibc" ...`);
521
+ Reflect.set(fileContents, 'libc', ['glibc']);
522
+ }
523
+ }
524
+ if (packageDevEngines !== undefined
525
+ && CLIRecipeSyncPackages.isEmpty(packageDevEngines)) {
526
+ Logger.customize({
527
+ name: 'CLIRecipeSyncPackages.handleEnvironment',
528
+ purpose: 'devEngines',
529
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "devEngines" ...`);
530
+ Reflect.deleteProperty(fileContents, 'devEngines');
531
+ }
532
+ }
533
+ static async handleDependencies(workspace) {
534
+ const { fileContents, manifest } = workspace;
535
+ const packageDependencies = fileContents['dependencies'];
536
+ const packageDevDependencies = fileContents['devDependencies'];
537
+ const packagePeerDependencies = fileContents['peerDependencies'];
538
+ const packagePeerDependenciesMeta = fileContents['peerDependenciesMeta'];
539
+ const packageBundleDependencies = fileContents['bundleDependencies'];
540
+ const packageBundledDependencies = fileContents['bundledDependencies'];
541
+ const packageOptionalDependencies = fileContents['optionalDependencies'];
542
+ const packageOverrides = fileContents['overrides'];
543
+ if (packageDependencies !== undefined
544
+ && CLIRecipeSyncPackages.isEmpty(packageDependencies)) {
545
+ Logger.customize({
546
+ name: 'CLIRecipeSyncPackages.handleDependencies',
547
+ purpose: 'dependencies',
548
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "dependencies" ...`);
549
+ Reflect.deleteProperty(fileContents, 'dependencies');
550
+ }
551
+ if (packageDevDependencies !== undefined
552
+ && CLIRecipeSyncPackages.isEmpty(packageDevDependencies)) {
553
+ Logger.customize({
554
+ name: 'CLIRecipeSyncPackages.handleDependencies',
555
+ purpose: 'devDependencies',
556
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "devDependencies" ...`);
557
+ Reflect.deleteProperty(fileContents, 'devDependencies');
558
+ }
559
+ if (packagePeerDependencies !== undefined
560
+ && CLIRecipeSyncPackages.isEmpty(packagePeerDependencies)) {
561
+ Logger.customize({
562
+ name: 'CLIRecipeSyncPackages.handleDependencies',
563
+ purpose: 'peerDependencies',
564
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "peerDependencies" ...`);
565
+ Reflect.deleteProperty(fileContents, 'peerDependencies');
566
+ }
567
+ if (packagePeerDependenciesMeta !== undefined
568
+ && CLIRecipeSyncPackages.isEmpty(packagePeerDependenciesMeta)) {
569
+ Logger.customize({
570
+ name: 'CLIRecipeSyncPackages.handleDependencies',
571
+ purpose: 'peerDependenciesMeta',
572
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "peerDependenciesMeta" ...`);
573
+ Reflect.deleteProperty(fileContents, 'peerDependenciesMeta');
574
+ }
575
+ if (packageBundledDependencies !== undefined) {
576
+ const bundleDependencies = Array.isArray(packageBundleDependencies) ? packageBundleDependencies : [];
577
+ const bundledDependencies = Array.isArray(packageBundledDependencies) ? packageBundledDependencies : [];
578
+ const mergedBundleDependencies = [...new Set([...bundleDependencies, ...bundledDependencies])];
579
+ Logger.customize({
580
+ name: 'CLIRecipeSyncPackages.handleDependencies',
581
+ purpose: 'bundleDependencies',
582
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Merging "bundledDependencies" into "bundleDependencies" ...`);
583
+ Reflect.set(fileContents, 'bundleDependencies', mergedBundleDependencies);
584
+ Logger.customize({
585
+ name: 'CLIRecipeSyncPackages.handleDependencies',
586
+ purpose: 'bundledDependencies',
587
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "bundledDependencies". "bundledDependencies" is an alias of "bundleDependencies".`);
588
+ Reflect.deleteProperty(fileContents, 'bundledDependencies');
589
+ }
590
+ if (fileContents['bundleDependencies'] !== undefined
591
+ && CLIRecipeSyncPackages.isEmpty(fileContents['bundleDependencies'])) {
592
+ Logger.customize({
593
+ name: 'CLIRecipeSyncPackages.handleDependencies',
594
+ purpose: 'bundleDependencies',
595
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "bundleDependencies" ...`);
596
+ Reflect.deleteProperty(fileContents, 'bundleDependencies');
597
+ }
598
+ if (packageOptionalDependencies !== undefined
599
+ && CLIRecipeSyncPackages.isEmpty(packageOptionalDependencies)) {
600
+ Logger.customize({
601
+ name: 'CLIRecipeSyncPackages.handleDependencies',
602
+ purpose: 'optionalDependencies',
603
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "optionalDependencies" ...`);
604
+ Reflect.deleteProperty(fileContents, 'optionalDependencies');
605
+ }
606
+ if (packageOverrides !== undefined
607
+ && CLIRecipeSyncPackages.isEmpty(packageOverrides)) {
608
+ Logger.customize({
609
+ name: 'CLIRecipeSyncPackages.handleDependencies',
610
+ purpose: 'overrides',
611
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing empty "overrides" ...`);
612
+ Reflect.deleteProperty(fileContents, 'overrides');
613
+ }
614
+ if (manifest.pinVersions === true) {
615
+ const depGroups = [
616
+ 'dependencies',
617
+ 'devDependencies',
618
+ ];
619
+ for (const depGroup of depGroups) {
620
+ const deps = fileContents[depGroup];
621
+ if (!isPlainObject(deps)) {
622
+ continue;
623
+ }
624
+ for (const [depName, depVersion] of Object.entries(deps)) {
625
+ if (typeof depVersion !== 'string') {
626
+ continue;
627
+ }
628
+ if (PATTERN_RANGE_CAPTURE_REMAINDER.test(depVersion)) {
629
+ Logger.customize({
630
+ name: 'CLIRecipeSyncPackages.handleDependencies',
631
+ purpose: depGroup,
632
+ }).warn(`${chalk.magenta(`"${manifest.name}" workspace`)} → "${depName}" has unpinned version "${depVersion}". Run "nova recipe pin-versions" to fix.`);
633
+ }
634
+ }
635
+ }
636
+ }
637
+ }
638
+ static handleUnknown(workspace) {
639
+ const allowedKeys = new Set([
640
+ ...itemPackageJsonKeysBundler,
641
+ ...itemPackageJsonKeysCorepack,
642
+ ...itemPackageJsonKeysNodeJs,
643
+ ...itemPackageJsonKeysNpm,
644
+ ]);
645
+ const manifestContents = workspace.fileContents ?? {};
646
+ const manifestKeys = Object.keys(manifestContents);
647
+ const unknownKeys = manifestKeys.filter((key) => !allowedKeys.has(key));
648
+ if (unknownKeys.length === 0) {
649
+ return;
650
+ }
651
+ Logger.customize({
652
+ name: 'CLIRecipeSyncPackages.handleUnknown',
653
+ purpose: 'unsupported',
654
+ }).warn([
655
+ `Workspace "${workspace.manifest.name}" contains unsupported "package.json" key(s).`,
656
+ 'The unsupported keys are:',
657
+ `- "${unknownKeys.join('"\n- "')}"`,
658
+ 'Review the references below:',
659
+ '- https://cbnventures.github.io/nova/docs/cli/recipes/sync-packages#unsupported-keys',
660
+ ].join('\n'));
661
+ for (const unknownKey of unknownKeys) {
662
+ Logger.customize({
663
+ name: 'CLIRecipeSyncPackages.handleUnknown',
664
+ purpose: 'removal',
665
+ }).info(`Removing unsupported key "${unknownKey}" from workspace "${workspace.manifest.name}".`);
666
+ Reflect.deleteProperty(manifestContents, unknownKey);
667
+ }
668
+ }
669
+ static handleOwnership(workspace, workingFile) {
670
+ const { fileContents, manifest } = workspace;
671
+ const packageHomepage = fileContents['homepage'];
672
+ const packageBugs = fileContents['bugs'];
673
+ const packageAuthor = fileContents['author'];
674
+ const packageContributors = fileContents['contributors'];
675
+ const packageFundingSources = fileContents['funding'];
676
+ const packageRepository = fileContents['repository'];
677
+ if (packageHomepage !== undefined
678
+ && manifest.policy !== 'distributable') {
679
+ Logger.customize({
680
+ name: 'CLIRecipeSyncPackages.handleOwnership',
681
+ purpose: 'homepage',
682
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "homepage". Workspace policy "${manifest.policy}" does not allow it.`);
683
+ Reflect.deleteProperty(fileContents, 'homepage');
684
+ }
685
+ else {
686
+ const validHomepage = (workingFile.urls !== undefined) ? workingFile.urls.homepage : undefined;
687
+ if ((manifest.policy === 'distributable'
688
+ && manifest.syncProperties !== undefined
689
+ && manifest.syncProperties.includes('homepage')
690
+ && validHomepage !== undefined)
691
+ && (typeof packageHomepage !== 'string'
692
+ || packageHomepage !== validHomepage)) {
693
+ Logger.customize({
694
+ name: 'CLIRecipeSyncPackages.handleOwnership',
695
+ purpose: 'homepage',
696
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "homepage" from workspace manifest ...`);
697
+ Reflect.set(fileContents, 'homepage', validHomepage);
698
+ }
699
+ if (manifest.policy === 'distributable'
700
+ && manifest.syncProperties !== undefined
701
+ && manifest.syncProperties.includes('homepage')
702
+ && validHomepage === undefined) {
703
+ Logger.customize({
704
+ name: 'CLIRecipeSyncPackages.handleOwnership',
705
+ purpose: 'homepage',
706
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "homepage". No homepage is defined.`);
707
+ Reflect.deleteProperty(fileContents, 'homepage');
708
+ }
709
+ }
710
+ if (packageBugs !== undefined
711
+ && manifest.policy !== 'distributable') {
712
+ Logger.customize({
713
+ name: 'CLIRecipeSyncPackages.handleOwnership',
714
+ purpose: 'bugs',
715
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "bugs". Workspace policy "${manifest.policy}" does not allow it.`);
716
+ Reflect.deleteProperty(fileContents, 'bugs');
717
+ }
718
+ else {
719
+ const validBugs = {
720
+ email: (workingFile.emails !== undefined) ? workingFile.emails.bugs : undefined,
721
+ url: (workingFile.urls !== undefined) ? workingFile.urls.bugs : undefined,
722
+ };
723
+ if ((manifest.policy === 'distributable'
724
+ && manifest.syncProperties !== undefined
725
+ && manifest.syncProperties.includes('bugs')
726
+ && (validBugs.email !== undefined
727
+ || validBugs.url !== undefined))
728
+ && (JSON.stringify(packageBugs) !== JSON.stringify(validBugs))) {
729
+ Logger.customize({
730
+ name: 'CLIRecipeSyncPackages.handleOwnership',
731
+ purpose: 'bugs',
732
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "bugs" from workspace manifest ...`);
733
+ Reflect.set(fileContents, 'bugs', validBugs);
734
+ }
735
+ if (manifest.policy === 'distributable'
736
+ && manifest.syncProperties !== undefined
737
+ && manifest.syncProperties.includes('bugs')
738
+ && (validBugs.email === undefined
739
+ && validBugs.url === undefined)) {
740
+ Logger.customize({
741
+ name: 'CLIRecipeSyncPackages.handleOwnership',
742
+ purpose: 'bugs',
743
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "bugs". No bug contacts are defined.`);
744
+ Reflect.deleteProperty(fileContents, 'bugs');
745
+ }
746
+ }
747
+ if (packageAuthor !== undefined
748
+ && manifest.policy !== 'distributable') {
749
+ Logger.customize({
750
+ name: 'CLIRecipeSyncPackages.handleOwnership',
751
+ purpose: 'author',
752
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "author". Workspace policy "${manifest.policy}" does not allow it.`);
753
+ Reflect.deleteProperty(fileContents, 'author');
754
+ }
755
+ else {
756
+ const validAuthor = (() => {
757
+ const entity = (workingFile.entities !== undefined) ? workingFile.entities.find((entity) => {
758
+ return Array.isArray(entity.roles) && entity.roles.includes('author');
759
+ }) : undefined;
760
+ if (entity === undefined) {
761
+ return {
762
+ name: undefined,
763
+ email: undefined,
764
+ url: undefined,
765
+ };
766
+ }
767
+ return {
768
+ name: entity.name,
769
+ email: entity.email,
770
+ url: entity.url,
771
+ };
772
+ })();
773
+ if ((manifest.policy === 'distributable'
774
+ && manifest.syncProperties !== undefined
775
+ && manifest.syncProperties.includes('author')
776
+ && (validAuthor.name !== undefined
777
+ || validAuthor.email !== undefined
778
+ || validAuthor.url !== undefined))
779
+ && (JSON.stringify(packageAuthor) !== JSON.stringify(validAuthor))) {
780
+ Logger.customize({
781
+ name: 'CLIRecipeSyncPackages.handleOwnership',
782
+ purpose: 'author',
783
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "author" from workspace manifest ...`);
784
+ Reflect.set(fileContents, 'author', validAuthor);
785
+ }
786
+ if (manifest.policy === 'distributable'
787
+ && manifest.syncProperties !== undefined
788
+ && manifest.syncProperties.includes('author')
789
+ && (validAuthor.name === undefined
790
+ && validAuthor.email === undefined
791
+ && validAuthor.url === undefined)) {
792
+ Logger.customize({
793
+ name: 'CLIRecipeSyncPackages.handleOwnership',
794
+ purpose: 'author',
795
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "author". No author is defined.`);
796
+ Reflect.deleteProperty(fileContents, 'author');
797
+ }
798
+ }
799
+ if (packageContributors !== undefined
800
+ && manifest.policy !== 'distributable') {
801
+ Logger.customize({
802
+ name: 'CLIRecipeSyncPackages.handleOwnership',
803
+ purpose: 'contributors',
804
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "contributors". Workspace policy "${manifest.policy}" does not allow it.`);
805
+ Reflect.deleteProperty(fileContents, 'contributors');
806
+ }
807
+ else {
808
+ const validContributors = (() => {
809
+ const entities = workingFile.entities ?? [];
810
+ return entities
811
+ .filter((entity) => Array.isArray(entity.roles) && entity.roles.includes('contributor'))
812
+ .map((entity) => ({
813
+ name: entity.name,
814
+ email: entity.email,
815
+ url: entity.url,
816
+ }))
817
+ .filter((entity) => entity.name !== undefined || entity.email !== undefined || entity.url !== undefined);
818
+ })();
819
+ if ((manifest.policy === 'distributable'
820
+ && manifest.syncProperties !== undefined
821
+ && manifest.syncProperties.includes('contributors')
822
+ && (validContributors.length > 0))
823
+ && (JSON.stringify(packageContributors) !== JSON.stringify(validContributors))) {
824
+ Logger.customize({
825
+ name: 'CLIRecipeSyncPackages.handleOwnership',
826
+ purpose: 'contributors',
827
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "contributors" from workspace manifest ...`);
828
+ Reflect.set(fileContents, 'contributors', validContributors);
829
+ }
830
+ if (manifest.policy === 'distributable'
831
+ && manifest.syncProperties !== undefined
832
+ && manifest.syncProperties.includes('contributors')
833
+ && (validContributors.length === 0)) {
834
+ Logger.customize({
835
+ name: 'CLIRecipeSyncPackages.handleOwnership',
836
+ purpose: 'contributors',
837
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "contributors". No contributors are defined.`);
838
+ Reflect.deleteProperty(fileContents, 'contributors');
839
+ }
840
+ }
841
+ if (packageFundingSources !== undefined
842
+ && manifest.policy !== 'distributable') {
843
+ Logger.customize({
844
+ name: 'CLIRecipeSyncPackages.handleOwnership',
845
+ purpose: 'funding',
846
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "funding". Workspace policy "${manifest.policy}" does not allow it.`);
847
+ Reflect.deleteProperty(fileContents, 'funding');
848
+ }
849
+ else {
850
+ const validFundingSources = (workingFile.urls !== undefined) ? workingFile.urls.fundSources : undefined;
851
+ if ((manifest.policy === 'distributable'
852
+ && manifest.syncProperties !== undefined
853
+ && manifest.syncProperties.includes('funding')
854
+ && validFundingSources !== undefined)
855
+ && (JSON.stringify(packageFundingSources) !== JSON.stringify(validFundingSources))) {
856
+ Logger.customize({
857
+ name: 'CLIRecipeSyncPackages.handleOwnership',
858
+ purpose: 'funding',
859
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "funding" from workspace manifest ...`);
860
+ Reflect.set(fileContents, 'funding', validFundingSources);
861
+ }
862
+ if (manifest.policy === 'distributable'
863
+ && manifest.syncProperties !== undefined
864
+ && manifest.syncProperties.includes('funding')
865
+ && validFundingSources === undefined) {
866
+ Logger.customize({
867
+ name: 'CLIRecipeSyncPackages.handleOwnership',
868
+ purpose: 'funding',
869
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "funding". No funding sources are defined.`);
870
+ Reflect.deleteProperty(fileContents, 'funding');
871
+ }
872
+ }
873
+ if (packageRepository !== undefined
874
+ && manifest.policy !== 'distributable') {
875
+ Logger.customize({
876
+ name: 'CLIRecipeSyncPackages.handleOwnership',
877
+ purpose: 'repository',
878
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "repository". Workspace policy "${manifest.policy}" does not allow it.`);
879
+ Reflect.deleteProperty(fileContents, 'repository');
880
+ }
881
+ else {
882
+ const validRepository = (() => {
883
+ const repositoryUrl = (workingFile.urls !== undefined) ? workingFile.urls.repository : undefined;
884
+ if (repositoryUrl === undefined) {
885
+ return undefined;
886
+ }
887
+ const projectRoot = process.cwd();
888
+ const packageDirectory = dirname(workspace.filePath);
889
+ const repositoryDirectory = relative(projectRoot, packageDirectory).replaceAll(sep, '/');
890
+ return {
891
+ type: 'git',
892
+ url: repositoryUrl,
893
+ ...(repositoryDirectory !== '' && repositoryDirectory !== '.' ? {
894
+ directory: repositoryDirectory,
895
+ } : {}),
896
+ };
897
+ })();
898
+ if ((manifest.policy === 'distributable'
899
+ && manifest.syncProperties !== undefined
900
+ && manifest.syncProperties.includes('repository')
901
+ && validRepository !== undefined)
902
+ && (JSON.stringify(packageRepository) !== JSON.stringify(validRepository))) {
903
+ Logger.customize({
904
+ name: 'CLIRecipeSyncPackages.handleOwnership',
905
+ purpose: 'repository',
906
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "repository" from workspace manifest ...`);
907
+ Reflect.set(fileContents, 'repository', validRepository);
908
+ }
909
+ if (manifest.policy === 'distributable'
910
+ && manifest.syncProperties !== undefined
911
+ && manifest.syncProperties.includes('repository')
912
+ && validRepository === undefined) {
913
+ Logger.customize({
914
+ name: 'CLIRecipeSyncPackages.handleOwnership',
915
+ purpose: 'repository',
916
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "repository". No repository url is defined.`);
917
+ Reflect.deleteProperty(fileContents, 'repository');
918
+ }
919
+ }
920
+ }
921
+ static handleRuntime(workspace) {
922
+ const { fileContents, manifest } = workspace;
923
+ const packageExports = fileContents['exports'];
924
+ const packageMain = fileContents['main'];
925
+ const packageType = fileContents['type'];
926
+ const packageBrowser = fileContents['browser'];
927
+ const packageImports = fileContents['imports'];
928
+ if (packageExports !== undefined
929
+ && !['config', 'package', 'tool'].includes(manifest.role)) {
930
+ Logger.customize({
931
+ name: 'CLIRecipeSyncPackages.handleRuntime',
932
+ purpose: 'exports',
933
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "exports". Workspace role "${manifest.role}" does not allow it.`);
934
+ Reflect.deleteProperty(fileContents, 'exports');
935
+ }
936
+ else {
937
+ if (manifest.role === 'config'
938
+ || manifest.role === 'package'
939
+ || manifest.role === 'tool') {
940
+ if (typeof packageExports === 'string') {
941
+ Logger.customize({
942
+ name: 'CLIRecipeSyncPackages.handleRuntime',
943
+ purpose: 'exports',
944
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Normalizing "exports" from string to object ...`);
945
+ Reflect.set(fileContents, 'exports', {
946
+ '.': {
947
+ default: packageExports,
948
+ },
949
+ });
950
+ }
951
+ }
952
+ }
953
+ if (packageMain !== undefined
954
+ && !['config', 'app', 'package', 'tool'].includes(manifest.role)) {
955
+ Logger.customize({
956
+ name: 'CLIRecipeSyncPackages.handleRuntime',
957
+ purpose: 'main',
958
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "main". Workspace role "${manifest.role}" does not allow it.`);
959
+ Reflect.deleteProperty(fileContents, 'main');
960
+ }
961
+ else {
962
+ if (manifest.role === 'config'
963
+ || manifest.role === 'app'
964
+ || manifest.role === 'package'
965
+ || manifest.role === 'tool') {
966
+ const currentPackageExports = fileContents['exports'];
967
+ if (typeof packageMain === 'string'
968
+ && (isPlainObject(currentPackageExports)
969
+ && isPlainObject(currentPackageExports['.'])
970
+ && typeof currentPackageExports['.']['require'] === 'string')
971
+ && packageMain !== currentPackageExports['.']['require']) {
972
+ Logger.customize({
973
+ name: 'CLIRecipeSyncPackages.handleRuntime',
974
+ purpose: 'main',
975
+ }).warn(`${chalk.magenta(`"${manifest.name}" workspace`)} → "main" differs from "exports['.'].require". No changes applied.`);
976
+ }
977
+ else if (typeof packageMain === 'string'
978
+ && (isPlainObject(currentPackageExports)
979
+ && isPlainObject(currentPackageExports['.']))
980
+ && typeof currentPackageExports['.']['require'] !== 'string') {
981
+ Logger.customize({
982
+ name: 'CLIRecipeSyncPackages.handleRuntime',
983
+ purpose: 'main',
984
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "exports['.'].require" from "main" ...`);
985
+ Reflect.set(currentPackageExports['.'], 'require', packageMain);
986
+ }
987
+ else if ((isPlainObject(currentPackageExports)
988
+ && isPlainObject(currentPackageExports['.'])
989
+ && typeof currentPackageExports['.']['require'] === 'string')
990
+ && typeof packageMain !== 'string') {
991
+ Logger.customize({
992
+ name: 'CLIRecipeSyncPackages.handleRuntime',
993
+ purpose: 'main',
994
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "main" from "exports['.'].require" ...`);
995
+ Reflect.set(fileContents, 'main', currentPackageExports['.']['require']);
996
+ }
997
+ else if (typeof packageMain === 'string'
998
+ && isPlainObject(currentPackageExports)
999
+ && typeof currentPackageExports['.'] === 'string') {
1000
+ Logger.customize({
1001
+ name: 'CLIRecipeSyncPackages.handleRuntime',
1002
+ purpose: 'main',
1003
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Normalizing "exports['.']" from string to object ...`);
1004
+ Reflect.set(currentPackageExports, '.', {
1005
+ default: currentPackageExports['.'],
1006
+ require: packageMain,
1007
+ });
1008
+ }
1009
+ }
1010
+ }
1011
+ if (packageType !== undefined
1012
+ && !['config', 'app', 'package', 'tool'].includes(manifest.role)) {
1013
+ Logger.customize({
1014
+ name: 'CLIRecipeSyncPackages.handleRuntime',
1015
+ purpose: 'type',
1016
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "type". Workspace role "${manifest.role}" does not allow it.`);
1017
+ Reflect.deleteProperty(fileContents, 'type');
1018
+ }
1019
+ if (packageBrowser !== undefined
1020
+ && !['package'].includes(manifest.role)) {
1021
+ Logger.customize({
1022
+ name: 'CLIRecipeSyncPackages.handleRuntime',
1023
+ purpose: 'browser',
1024
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "browser". Workspace role "${manifest.role}" does not allow it.`);
1025
+ Reflect.deleteProperty(fileContents, 'browser');
1026
+ }
1027
+ else {
1028
+ if (manifest.role === 'package') {
1029
+ const currentPackageExports = fileContents['exports'];
1030
+ if (typeof packageBrowser === 'string'
1031
+ && (isPlainObject(currentPackageExports)
1032
+ && isPlainObject(currentPackageExports['.'])
1033
+ && typeof currentPackageExports['.']['browser'] === 'string')
1034
+ && packageBrowser !== currentPackageExports['.']['browser']) {
1035
+ Logger.customize({
1036
+ name: 'CLIRecipeSyncPackages.handleRuntime',
1037
+ purpose: 'browser',
1038
+ }).warn(`${chalk.magenta(`"${manifest.name}" workspace`)} → "browser" differs from "exports['.'].browser". No changes applied.`);
1039
+ }
1040
+ else if (typeof packageBrowser === 'string'
1041
+ && (isPlainObject(currentPackageExports)
1042
+ && isPlainObject(currentPackageExports['.']))
1043
+ && typeof currentPackageExports['.']['browser'] !== 'string') {
1044
+ Logger.customize({
1045
+ name: 'CLIRecipeSyncPackages.handleRuntime',
1046
+ purpose: 'browser',
1047
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "exports['.'].browser" from "browser" ...`);
1048
+ Reflect.set(currentPackageExports['.'], 'browser', packageBrowser);
1049
+ }
1050
+ else if ((isPlainObject(currentPackageExports)
1051
+ && isPlainObject(currentPackageExports['.'])
1052
+ && typeof currentPackageExports['.']['browser'] === 'string')
1053
+ && typeof packageBrowser !== 'string'
1054
+ && !isPlainObject(packageBrowser)) {
1055
+ Logger.customize({
1056
+ name: 'CLIRecipeSyncPackages.handleRuntime',
1057
+ purpose: 'browser',
1058
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Syncing "browser" from "exports['.'].browser" ...`);
1059
+ Reflect.set(fileContents, 'browser', currentPackageExports['.']['browser']);
1060
+ }
1061
+ else if (typeof packageBrowser === 'string'
1062
+ && isPlainObject(currentPackageExports)
1063
+ && typeof currentPackageExports['.'] === 'string') {
1064
+ Logger.customize({
1065
+ name: 'CLIRecipeSyncPackages.handleRuntime',
1066
+ purpose: 'browser',
1067
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Normalizing "exports['.']" from string to object ...`);
1068
+ Reflect.set(currentPackageExports, '.', {
1069
+ default: currentPackageExports['.'],
1070
+ browser: packageBrowser,
1071
+ });
1072
+ }
1073
+ }
1074
+ }
1075
+ if (packageImports !== undefined
1076
+ && !['config', 'app', 'package', 'tool'].includes(manifest.role)) {
1077
+ Logger.customize({
1078
+ name: 'CLIRecipeSyncPackages.handleRuntime',
1079
+ purpose: 'imports',
1080
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "imports". Workspace role "${manifest.role}" does not allow it.`);
1081
+ Reflect.deleteProperty(fileContents, 'imports');
1082
+ }
1083
+ }
1084
+ static handleCorepack(workspace) {
1085
+ const { fileContents, manifest } = workspace;
1086
+ const packageManager = fileContents['packageManager'];
1087
+ if (packageManager !== undefined
1088
+ && manifest.role !== 'project') {
1089
+ Logger.customize({
1090
+ name: 'CLIRecipeSyncPackages.handleCorepack',
1091
+ purpose: 'packageManager',
1092
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "packageManager". Workspace role "${manifest.role}" does not allow it.`);
1093
+ Reflect.deleteProperty(fileContents, 'packageManager');
1094
+ }
1095
+ else {
1096
+ if (manifest.role === 'project'
1097
+ && packageManager !== undefined
1098
+ && (typeof packageManager !== 'string'
1099
+ || !PATTERN_NAME_AT_VERSION.test(packageManager))) {
1100
+ Logger.customize({
1101
+ name: 'CLIRecipeSyncPackages.handleCorepack',
1102
+ purpose: 'packageManager',
1103
+ }).warn(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "packageManager". Invalid format detected.`);
1104
+ Reflect.deleteProperty(fileContents, 'packageManager');
1105
+ }
1106
+ }
1107
+ }
1108
+ static handleBundler(workspace) {
1109
+ const { fileContents, manifest } = workspace;
1110
+ const packageTypes = fileContents['types'];
1111
+ const packageTypings = fileContents['typings'];
1112
+ const packageModule = fileContents['module'];
1113
+ const packageSideEffects = fileContents['sideEffects'];
1114
+ const packageEsnext = fileContents['esnext'];
1115
+ if (packageTypings !== undefined) {
1116
+ if (packageTypes !== undefined) {
1117
+ Logger.customize({
1118
+ name: 'CLIRecipeSyncPackages.handleBundler',
1119
+ purpose: 'typings',
1120
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Merging "typings" into "types". Keeping existing "types" value.`);
1121
+ }
1122
+ else {
1123
+ Logger.customize({
1124
+ name: 'CLIRecipeSyncPackages.handleBundler',
1125
+ purpose: 'typings',
1126
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Renaming "typings" to "types" ...`);
1127
+ Reflect.set(fileContents, 'types', packageTypings);
1128
+ }
1129
+ Reflect.deleteProperty(fileContents, 'typings');
1130
+ }
1131
+ const allowsTypesModule = ['config', 'package', 'tool'].includes(manifest.role);
1132
+ const allowsSideEffectsEsnext = ['package'].includes(manifest.role);
1133
+ if (fileContents['types'] !== undefined
1134
+ && !allowsTypesModule) {
1135
+ Logger.customize({
1136
+ name: 'CLIRecipeSyncPackages.handleBundler',
1137
+ purpose: 'types',
1138
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "types". Workspace role "${manifest.role}" does not allow it.`);
1139
+ Reflect.deleteProperty(fileContents, 'types');
1140
+ }
1141
+ if (packageModule !== undefined
1142
+ && !allowsTypesModule) {
1143
+ Logger.customize({
1144
+ name: 'CLIRecipeSyncPackages.handleBundler',
1145
+ purpose: 'module',
1146
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "module". Workspace role "${manifest.role}" does not allow it.`);
1147
+ Reflect.deleteProperty(fileContents, 'module');
1148
+ }
1149
+ if (packageSideEffects !== undefined
1150
+ && !allowsSideEffectsEsnext) {
1151
+ Logger.customize({
1152
+ name: 'CLIRecipeSyncPackages.handleBundler',
1153
+ purpose: 'sideEffects',
1154
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "sideEffects". Workspace role "${manifest.role}" does not allow it.`);
1155
+ Reflect.deleteProperty(fileContents, 'sideEffects');
1156
+ }
1157
+ if (packageEsnext !== undefined
1158
+ && !allowsSideEffectsEsnext) {
1159
+ Logger.customize({
1160
+ name: 'CLIRecipeSyncPackages.handleBundler',
1161
+ purpose: 'esnext',
1162
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Removing "esnext". Workspace role "${manifest.role}" does not allow it.`);
1163
+ Reflect.deleteProperty(fileContents, 'esnext');
1164
+ }
1165
+ }
1166
+ static handleReorder(workspace) {
1167
+ const { fileContents, manifest } = workspace;
1168
+ const sortedKeys = new Set(itemPackageJsonSortOrder);
1169
+ const currentKeys = Object.keys(fileContents);
1170
+ const reordered = {};
1171
+ for (const key of itemPackageJsonSortOrder) {
1172
+ if (key in fileContents) {
1173
+ reordered[key] = fileContents[key];
1174
+ }
1175
+ }
1176
+ for (const key of currentKeys) {
1177
+ if (!sortedKeys.has(key)) {
1178
+ reordered[key] = fileContents[key];
1179
+ }
1180
+ }
1181
+ const reorderedKeys = Object.keys(reordered);
1182
+ if (currentKeys.every((key, index) => key === reorderedKeys[index])) {
1183
+ return;
1184
+ }
1185
+ Logger.customize({
1186
+ name: 'CLIRecipeSyncPackages.handleReorder',
1187
+ purpose: 'reorder',
1188
+ }).info(`${chalk.magenta(`"${manifest.name}" workspace`)} → Reordering "package.json" keys ...`);
1189
+ for (const key of currentKeys) {
1190
+ Reflect.deleteProperty(fileContents, key);
1191
+ }
1192
+ for (const [key, value] of Object.entries(reordered)) {
1193
+ Reflect.set(fileContents, key, value);
1194
+ }
1195
+ }
1196
+ static isEmpty(value) {
1197
+ if (value === null || value === undefined) {
1198
+ return true;
1199
+ }
1200
+ if (typeof value === 'string') {
1201
+ return value.trim() === '';
1202
+ }
1203
+ if (Array.isArray(value)) {
1204
+ return value.length === 0;
1205
+ }
1206
+ if (typeof value === 'object') {
1207
+ return Object.keys(value).length === 0;
1208
+ }
1209
+ return false;
1210
+ }
1211
+ }
1212
+ //# sourceMappingURL=sync-packages.js.map