@smoothbricks/cli 0.1.1 → 0.3.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 (131) hide show
  1. package/README.md +161 -32
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +110 -10
  4. package/dist/devenv/index.d.ts +10 -0
  5. package/dist/devenv/index.d.ts.map +1 -0
  6. package/dist/devenv/index.js +27 -0
  7. package/dist/generate/index.d.ts +20 -0
  8. package/dist/generate/index.d.ts.map +1 -0
  9. package/dist/generate/index.js +47 -0
  10. package/dist/lib/cli-package.d.ts +4 -0
  11. package/dist/lib/cli-package.d.ts.map +1 -0
  12. package/dist/lib/cli-package.js +15 -0
  13. package/dist/lib/run.d.ts +6 -0
  14. package/dist/lib/run.d.ts.map +1 -1
  15. package/dist/lib/run.js +32 -0
  16. package/dist/lib/workspace.d.ts +25 -1
  17. package/dist/lib/workspace.d.ts.map +1 -1
  18. package/dist/lib/workspace.js +79 -17
  19. package/dist/monorepo/commit-msg.d.ts +8 -1
  20. package/dist/monorepo/commit-msg.d.ts.map +1 -1
  21. package/dist/monorepo/commit-msg.js +85 -7
  22. package/dist/monorepo/index.d.ts +19 -2
  23. package/dist/monorepo/index.d.ts.map +1 -1
  24. package/dist/monorepo/index.js +104 -16
  25. package/dist/monorepo/lockfile.d.ts.map +1 -1
  26. package/dist/monorepo/lockfile.js +9 -0
  27. package/dist/monorepo/managed-files.js +1 -1
  28. package/dist/monorepo/nx-sync.d.ts +2 -1
  29. package/dist/monorepo/nx-sync.d.ts.map +1 -1
  30. package/dist/monorepo/nx-sync.js +8 -2
  31. package/dist/monorepo/package-hygiene.d.ts +6 -2
  32. package/dist/monorepo/package-hygiene.d.ts.map +1 -1
  33. package/dist/monorepo/package-hygiene.js +12 -4
  34. package/dist/monorepo/package-policy.d.ts +13 -4
  35. package/dist/monorepo/package-policy.d.ts.map +1 -1
  36. package/dist/monorepo/package-policy.js +208 -113
  37. package/dist/monorepo/packed-package.d.ts +3 -0
  38. package/dist/monorepo/packed-package.d.ts.map +1 -1
  39. package/dist/monorepo/packed-package.js +68 -36
  40. package/dist/monorepo/packs/index.d.ts +20 -1
  41. package/dist/monorepo/packs/index.d.ts.map +1 -1
  42. package/dist/monorepo/packs/index.js +238 -19
  43. package/dist/monorepo/publish-workflow.d.ts +0 -4
  44. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  45. package/dist/monorepo/publish-workflow.js +7 -15
  46. package/dist/monorepo/tool-validation.d.ts +22 -0
  47. package/dist/monorepo/tool-validation.d.ts.map +1 -0
  48. package/dist/monorepo/tool-validation.js +399 -0
  49. package/dist/nx/index.d.ts +25 -0
  50. package/dist/nx/index.d.ts.map +1 -0
  51. package/dist/nx/index.js +112 -0
  52. package/dist/release/bootstrap-npm-packages.d.ts +19 -0
  53. package/dist/release/bootstrap-npm-packages.d.ts.map +1 -0
  54. package/dist/release/bootstrap-npm-packages.js +58 -0
  55. package/dist/release/candidates.d.ts +11 -0
  56. package/dist/release/candidates.d.ts.map +1 -0
  57. package/dist/release/candidates.js +181 -0
  58. package/dist/release/core.d.ts +4 -1
  59. package/dist/release/core.d.ts.map +1 -1
  60. package/dist/release/core.js +76 -9
  61. package/dist/release/github-release.d.ts +1 -0
  62. package/dist/release/github-release.d.ts.map +1 -1
  63. package/dist/release/github-release.js +3 -3
  64. package/dist/release/index.d.ts +32 -1
  65. package/dist/release/index.d.ts.map +1 -1
  66. package/dist/release/index.js +488 -64
  67. package/dist/release/npm-auth.d.ts +0 -1
  68. package/dist/release/npm-auth.d.ts.map +1 -1
  69. package/dist/release/npm-auth.js +4 -16
  70. package/dist/release/orchestration.d.ts +11 -2
  71. package/dist/release/orchestration.d.ts.map +1 -1
  72. package/dist/release/orchestration.js +41 -3
  73. package/dist/release/publish-plan.js +1 -1
  74. package/managed/raw/git-format-staged.yml +54 -0
  75. package/managed/raw/tooling/git-hooks/commit-msg.sh +1 -1
  76. package/managed/templates/github/actions/setup-devenv/action.yml +1 -7
  77. package/managed/templates/github/workflows/ci.yml +4 -2
  78. package/package.json +15 -8
  79. package/src/cli.ts +153 -22
  80. package/src/devenv/index.test.ts +49 -0
  81. package/src/devenv/index.ts +38 -0
  82. package/src/generate/index.ts +92 -0
  83. package/src/lib/cli-package.ts +18 -0
  84. package/src/lib/run.ts +44 -0
  85. package/src/lib/workspace.ts +112 -17
  86. package/src/monorepo/__tests__/publish-workflow.test.ts +16 -9
  87. package/src/monorepo/commit-msg.test.ts +45 -0
  88. package/src/monorepo/commit-msg.ts +107 -7
  89. package/src/monorepo/index.ts +142 -16
  90. package/src/monorepo/lockfile.ts +9 -0
  91. package/src/monorepo/managed-files.ts +1 -1
  92. package/src/monorepo/nx-sync.ts +9 -2
  93. package/src/monorepo/package-hygiene.test.ts +34 -0
  94. package/src/monorepo/package-hygiene.ts +26 -4
  95. package/src/monorepo/package-policy.test.ts +1130 -0
  96. package/src/monorepo/package-policy.ts +237 -114
  97. package/src/monorepo/packed-package.ts +89 -37
  98. package/src/monorepo/packs/index.test.ts +189 -0
  99. package/src/monorepo/packs/index.ts +311 -20
  100. package/src/monorepo/publish-workflow.ts +10 -19
  101. package/src/monorepo/setup-test-tracing.test.ts +120 -0
  102. package/src/monorepo/tool-validation.test.ts +261 -0
  103. package/src/monorepo/tool-validation.ts +462 -0
  104. package/src/nx/index.test.ts +73 -0
  105. package/src/nx/index.ts +139 -0
  106. package/src/release/__tests__/bootstrap-npm-packages.test.ts +127 -0
  107. package/src/release/__tests__/candidates.test.ts +262 -0
  108. package/src/release/__tests__/core-properties.test.ts +6 -6
  109. package/src/release/__tests__/core-scenarios.test.ts +27 -20
  110. package/src/release/__tests__/core.test.ts +52 -5
  111. package/src/release/__tests__/fixture-repo.test.ts +90 -31
  112. package/src/release/__tests__/github-release.test.ts +22 -24
  113. package/src/release/__tests__/helpers/fixture-repo.ts +1 -0
  114. package/src/release/__tests__/npm-auth.test.ts +12 -21
  115. package/src/release/__tests__/orchestration.test.ts +142 -12
  116. package/src/release/__tests__/publish-plan.test.ts +9 -3
  117. package/src/release/__tests__/retag-unpublished.test.ts +10 -10
  118. package/src/release/__tests__/trust-publisher.test.ts +165 -0
  119. package/src/release/bootstrap-npm-packages.ts +88 -0
  120. package/src/release/candidates.ts +225 -0
  121. package/src/release/core.ts +96 -10
  122. package/src/release/github-release.ts +3 -3
  123. package/src/release/index.ts +658 -74
  124. package/src/release/npm-auth.ts +4 -33
  125. package/src/release/orchestration.ts +60 -5
  126. package/src/release/publish-plan.ts +1 -1
  127. package/dist/nx-version-actions.cjs +0 -25
  128. package/dist/nx-version-actions.d.cts +0 -6
  129. package/dist/nx-version-actions.d.cts.map +0 -1
  130. package/managed/raw/.git-format-staged.yml +0 -47
  131. package/src/nx-version-actions.cts +0 -36
@@ -1,8 +1,57 @@
1
1
  import { join } from 'node:path';
2
+ import { applyWorkspaceBoundedTestTargetPolicy, checkWorkspaceBoundedTestTargetPolicy, } from '@smoothbricks/nx-plugin/bounded-test-policy';
3
+ import { applyPackageTargetPolicy, checkPackageTargetPolicy, } from '@smoothbricks/nx-plugin/package-target-policy';
4
+ import { applyReleaseConfigPolicy, checkReleaseConfigPolicy, SMOO_NX_RELEASE_TAG_PATTERN, SMOO_NX_VERSION_ACTIONS, } from '@smoothbricks/nx-plugin/release-config-policy';
5
+ import { applyTypecheckTestPolicy, checkTypecheckTestPolicy } from '@smoothbricks/nx-plugin/typecheck-test-policy';
6
+ import { applyWorkspaceConfigPolicy, checkWorkspaceConfigPolicy, } from '@smoothbricks/nx-plugin/workspace-config-policy';
2
7
  import { getOrCreateRecord, hasOwnString, isRecord, readJsonObject, recordProperty, requiredJsonObject, setMissingStringProperty, setStringProperty, stringProperty, writeJsonObject, } from '../lib/json.js';
3
- import { getWorkspacePackages, listPackageJsonRecords, listPublicPackages, packageRepositoryInfo, repositoryInfo, sameRepositoryAfterNormalization, workspaceDependencyFields, } from '../lib/workspace.js';
4
- export const SMOO_NX_VERSION_ACTIONS = '@smoothbricks/cli/nx-version-actions';
5
- export const SMOO_NX_RELEASE_TAG_PATTERN = '{projectName}@{version}';
8
+ import { getWorkspacePackageManifests, getWorkspacePackages, listPackageJsonRecords, listPublicPackages, packageRepositoryInfo, repositoryInfo, sameRepositoryAfterNormalization, workspaceDependencyFields, } from '../lib/workspace.js';
9
+ export { SMOO_NX_RELEASE_TAG_PATTERN, SMOO_NX_VERSION_ACTIONS };
10
+ const extraCommitScopes = ['release'];
11
+ const rootScriptPolicy = {
12
+ lint: 'nx run-many -t lint',
13
+ 'lint:fix': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --unstaged',
14
+ 'format:staged': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml',
15
+ 'format:changed': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --also-unstaged',
16
+ };
17
+ export function applyFixableMonorepoDefaults(root) {
18
+ applyRootScriptDefaults(root);
19
+ applyNxPluginDefaults(root);
20
+ applyNxProjectNameDefaults(root);
21
+ }
22
+ export function applyRootScriptDefaults(root) {
23
+ const rootPackagePath = join(root, 'package.json');
24
+ const rootPackage = readJsonObject(rootPackagePath);
25
+ if (!rootPackage) {
26
+ return;
27
+ }
28
+ const scripts = getOrCreateRecord(rootPackage, 'scripts');
29
+ let changed = false;
30
+ for (const [name, command] of Object.entries(rootScriptPolicy)) {
31
+ changed = setStringProperty(scripts, name, command) || changed;
32
+ }
33
+ const nx = getOrCreateRecord(rootPackage, 'nx');
34
+ if (!Array.isArray(nx.includedScripts) || nx.includedScripts.length !== 0) {
35
+ nx.includedScripts = [];
36
+ changed = true;
37
+ }
38
+ changed = sortRecordInPlace(scripts) || changed;
39
+ if (changed) {
40
+ writeJsonObject(rootPackagePath, rootPackage);
41
+ console.log('updated package.json root smoo scripts');
42
+ }
43
+ else {
44
+ console.log('unchanged package.json root smoo scripts');
45
+ }
46
+ }
47
+ export function applyNxPluginDefaults(root) {
48
+ if (applyWorkspaceConfigPolicy(root)) {
49
+ console.log('updated nx.json smoo plugin config');
50
+ }
51
+ else {
52
+ console.log('unchanged nx.json smoo plugin config');
53
+ }
54
+ }
6
55
  export function applyPublicPackageDefaults(root) {
7
56
  const rootPackage = requiredJsonObject(join(root, 'package.json'));
8
57
  const rootLicense = stringProperty(rootPackage, 'license');
@@ -39,7 +88,7 @@ export function applyPublicPackageDefaults(root) {
39
88
  }
40
89
  }
41
90
  }
42
- export function applyWorkspaceDependencyDefaults(root) {
91
+ export function applyWorkspaceDependencyDefaults(root, options = {}) {
43
92
  const workspaceNames = new Set(getWorkspacePackages(root).map((pkg) => pkg.name));
44
93
  for (const pkg of listPackageJsonRecords(root)) {
45
94
  const changed = fixWorkspaceDependencyRanges(pkg.json, workspaceNames);
@@ -47,49 +96,76 @@ export function applyWorkspaceDependencyDefaults(root) {
47
96
  writeJsonObject(pkg.packageJsonPath, pkg.json);
48
97
  console.log(`updated ${pkg.path}/package.json workspace dependency ranges`);
49
98
  }
50
- else {
51
- console.log(`unchanged ${pkg.path}/package.json workspace dependency ranges`);
52
- }
53
99
  }
54
- }
55
- export function applyNxReleaseDefaults(root) {
56
- const nxJsonPath = join(root, 'nx.json');
57
- const nxJson = requiredJsonObject(nxJsonPath);
58
- let changed = false;
59
- const release = getOrCreateRecord(nxJson, 'release');
60
- changed = setStringProperty(release, 'projectsRelationship', 'independent') || changed;
61
- const version = getOrCreateRecord(release, 'version');
62
- changed = setStringProperty(version, 'specifierSource', 'conventional-commits') || changed;
63
- changed = setStringProperty(version, 'currentVersionResolver', 'git-tag') || changed;
64
- changed = setStringProperty(version, 'fallbackCurrentVersionResolver', 'disk') || changed;
65
- changed = setStringProperty(version, 'versionActions', SMOO_NX_VERSION_ACTIONS) || changed;
66
- if (delete version.preVersionCommand) {
67
- changed = true;
100
+ if (applyPackageTargetPolicy(root, options)) {
101
+ console.log('updated package Nx target policy');
68
102
  }
69
- const releaseTag = getOrCreateRecord(release, 'releaseTag');
70
- changed = setStringProperty(releaseTag, 'pattern', SMOO_NX_RELEASE_TAG_PATTERN) || changed;
71
- const changelog = getOrCreateRecord(release, 'changelog');
72
- changed = setBooleanProperty(changelog, 'workspaceChangelog', false) || changed;
73
- const projectChangelogs = getOrCreateRecord(changelog, 'projectChangelogs');
74
- changed = setBooleanProperty(projectChangelogs, 'createRelease', false) || changed;
75
- changed = setBooleanProperty(projectChangelogs, 'file', false) || changed;
76
- const renderOptions = getOrCreateRecord(projectChangelogs, 'renderOptions');
77
- if (typeof renderOptions.authors !== 'boolean') {
78
- renderOptions.authors = true;
79
- changed = true;
103
+ else {
104
+ console.log('unchanged package Nx target policy');
80
105
  }
81
- if (typeof renderOptions.applyUsernameToAuthors !== 'boolean') {
82
- renderOptions.applyUsernameToAuthors = true;
83
- changed = true;
106
+ if (applyTypecheckTestPolicy(root)) {
107
+ console.log('updated tsconfig.test.json policy');
84
108
  }
85
- if (changed) {
86
- writeJsonObject(nxJsonPath, nxJson);
109
+ else {
110
+ console.log('unchanged tsconfig.test.json policy');
111
+ }
112
+ if (applyWorkspaceBoundedTestTargetPolicy(root)) {
113
+ console.log('updated package test targets bounded execution policy');
114
+ }
115
+ else {
116
+ console.log('unchanged package test targets bounded execution policy');
117
+ }
118
+ }
119
+ export function applyNxReleaseDefaults(root) {
120
+ if (applyReleaseConfigPolicy(root)) {
87
121
  console.log('updated nx.json release config');
88
122
  }
89
123
  else {
90
124
  console.log('unchanged nx.json release config');
91
125
  }
92
126
  }
127
+ export function applyNxProjectNameDefaults(root) {
128
+ const rootPackage = requiredJsonObject(join(root, 'package.json'));
129
+ const rootName = stringProperty(rootPackage, 'name');
130
+ if (!rootName) {
131
+ return;
132
+ }
133
+ for (const pkg of getWorkspacePackageManifests(root)) {
134
+ const suggestedName = suggestNxProjectName(rootName, pkg.name);
135
+ if (!suggestedName) {
136
+ continue;
137
+ }
138
+ const nx = getOrCreateRecord(pkg.json, 'nx');
139
+ const changed = setStringProperty(nx, 'name', suggestedName);
140
+ if (changed) {
141
+ writeJsonObject(pkg.packageJsonPath, pkg.json);
142
+ console.log(`updated ${pkg.path}/package.json nx.name`);
143
+ }
144
+ else {
145
+ console.log(`unchanged ${pkg.path}/package.json nx.name`);
146
+ }
147
+ }
148
+ }
149
+ export function listValidCommitScopes(root) {
150
+ return new Set([...listNxProjectNames(root), ...extraCommitScopes]);
151
+ }
152
+ export function listNxProjectNames(root) {
153
+ const rootPackage = readJsonObject(join(root, 'package.json'));
154
+ const rootName = rootPackage ? stringProperty(rootPackage, 'name') : null;
155
+ const names = [];
156
+ for (const pkg of getWorkspacePackageManifests(root)) {
157
+ const nx = recordProperty(pkg.json, 'nx');
158
+ const configuredName = nx ? stringProperty(nx, 'name') : null;
159
+ const suggestedName = rootName ? suggestNxProjectName(rootName, pkg.name) : null;
160
+ if (configuredName) {
161
+ names.push(configuredName);
162
+ }
163
+ else if (suggestedName) {
164
+ names.push(suggestedName);
165
+ }
166
+ }
167
+ return names;
168
+ }
93
169
  export function validateRootPackagePolicy(root) {
94
170
  const rootPackage = readJsonObject(join(root, 'package.json'));
95
171
  if (!rootPackage) {
@@ -109,6 +185,8 @@ export function validateRootPackagePolicy(root) {
109
185
  console.error('package.json must define repository.url');
110
186
  failures++;
111
187
  }
188
+ failures += validateRootScripts(rootPackage);
189
+ failures += validateRootNxScriptInference(rootPackage);
112
190
  const packageManager = stringProperty(rootPackage, 'packageManager');
113
191
  if (!packageManager?.startsWith('bun@')) {
114
192
  console.error('package.json packageManager must use bun@<version>');
@@ -128,83 +206,38 @@ export function validateRootPackagePolicy(root) {
128
206
  return failures;
129
207
  }
130
208
  export function validateNxReleaseConfig(root) {
131
- const nxJson = readJsonObject(join(root, 'nx.json'));
132
- if (!nxJson) {
133
- console.error('nx.json not found or invalid');
134
- return 1;
135
- }
136
- const release = recordProperty(nxJson, 'release');
137
- const version = release ? recordProperty(release, 'version') : null;
138
- const releaseTag = release ? recordProperty(release, 'releaseTag') : null;
139
- const changelog = release ? recordProperty(release, 'changelog') : null;
140
- const projectChangelogs = changelog ? recordProperty(changelog, 'projectChangelogs') : null;
141
- const renderOptions = projectChangelogs ? recordProperty(projectChangelogs, 'renderOptions') : null;
142
209
  let failures = 0;
143
- if (!release) {
144
- console.error('nx.json release config is missing');
145
- failures++;
146
- }
147
- if (release && stringProperty(release, 'projectsRelationship') !== 'independent') {
148
- console.error('nx.json release.projectsRelationship must be independent');
149
- failures++;
150
- }
151
- if (!version) {
152
- console.error('nx.json release.version config is missing');
210
+ for (const issue of checkWorkspaceConfigPolicy(root)) {
211
+ console.error(issue.message);
153
212
  failures++;
154
213
  }
155
- if (version && stringProperty(version, 'specifierSource') !== 'conventional-commits') {
156
- console.error('nx.json release.version.specifierSource must be conventional-commits');
214
+ for (const issue of checkReleaseConfigPolicy(root)) {
215
+ console.error(issue.message);
157
216
  failures++;
158
217
  }
159
- // Nx requires git-tag as the primary resolver when deriving versions from
160
- // conventional commits. Disk is only a fallback for initial untagged packages.
161
- if (version && stringProperty(version, 'currentVersionResolver') !== 'git-tag') {
162
- console.error('nx.json release.version.currentVersionResolver must be git-tag');
163
- failures++;
164
- }
165
- if (version && stringProperty(version, 'fallbackCurrentVersionResolver') !== 'disk') {
166
- console.error('nx.json release.version.fallbackCurrentVersionResolver must be disk');
167
- failures++;
168
- }
169
- if (version && stringProperty(version, 'versionActions') !== SMOO_NX_VERSION_ACTIONS) {
170
- console.error(`nx.json release.version.versionActions must be ${SMOO_NX_VERSION_ACTIONS}`);
171
- failures++;
172
- }
173
- if (version && stringProperty(version, 'preVersionCommand')) {
174
- console.error('nx.json release.version.preVersionCommand must not be defined; smoo builds npm-missing packages before publish');
175
- failures++;
176
- }
177
- if (!releaseTag) {
178
- console.error('nx.json release.releaseTag config is missing');
179
- failures++;
180
- }
181
- if (releaseTag && stringProperty(releaseTag, 'pattern') !== SMOO_NX_RELEASE_TAG_PATTERN) {
182
- console.error(`nx.json release.releaseTag.pattern must be ${SMOO_NX_RELEASE_TAG_PATTERN}`);
183
- failures++;
184
- }
185
- if (!changelog) {
186
- console.error('nx.json release.changelog config is missing');
187
- failures++;
188
- }
189
- if (changelog && changelog.workspaceChangelog !== false) {
190
- console.error('nx.json release.changelog.workspaceChangelog must be false');
191
- failures++;
192
- }
193
- if (!projectChangelogs) {
194
- console.error('nx.json release.changelog.projectChangelogs config is missing');
195
- failures++;
196
- }
197
- if (projectChangelogs && projectChangelogs.createRelease !== false) {
198
- console.error('nx.json release.changelog.projectChangelogs.createRelease must be false');
199
- failures++;
218
+ return failures;
219
+ }
220
+ export function validateNxProjectNames(root) {
221
+ const rootPackage = readJsonObject(join(root, 'package.json'));
222
+ const rootName = rootPackage ? stringProperty(rootPackage, 'name') : null;
223
+ if (!rootName) {
224
+ return 0;
200
225
  }
201
- if (projectChangelogs && projectChangelogs.file !== false) {
202
- console.error('nx.json release.changelog.projectChangelogs.file must be false');
203
- failures++;
226
+ let failures = 0;
227
+ for (const pkg of getWorkspacePackageManifests(root)) {
228
+ const suggestedName = suggestNxProjectName(rootName, pkg.name);
229
+ if (!suggestedName) {
230
+ continue;
231
+ }
232
+ const nx = recordProperty(pkg.json, 'nx');
233
+ const configuredName = nx ? stringProperty(nx, 'name') : null;
234
+ if (configuredName !== suggestedName) {
235
+ console.error(`${pkg.path}: package.json nx.name must be "${suggestedName}" so fix(${suggestedName}): maps to this project`);
236
+ failures++;
237
+ }
204
238
  }
205
- if (!renderOptions) {
206
- console.error('nx.json release.changelog.projectChangelogs.renderOptions config is missing');
207
- failures++;
239
+ if (failures === 0) {
240
+ console.log('Nx project names are valid.');
208
241
  }
209
242
  return failures;
210
243
  }
@@ -278,9 +311,9 @@ export function validatePublicPackageMetadata(root) {
278
311
  }
279
312
  return failures;
280
313
  }
281
- export function validateWorkspaceDependencies(root) {
282
- const workspaceNames = new Set(getWorkspacePackages(root).map((pkg) => pkg.name));
314
+ export function validateWorkspaceDependencies(root, options = {}) {
283
315
  let failures = 0;
316
+ const workspaceNames = new Set(getWorkspacePackages(root).map((pkg) => pkg.name));
284
317
  for (const pkg of listPackageJsonRecords(root)) {
285
318
  for (const field of workspaceDependencyFields) {
286
319
  const dependencies = recordProperty(pkg.json, field);
@@ -295,11 +328,26 @@ export function validateWorkspaceDependencies(root) {
295
328
  }
296
329
  }
297
330
  }
331
+ for (const issue of checkPackageTargetPolicy(root, options)) {
332
+ console.error(`${issue.path}: ${issue.message}`);
333
+ failures++;
334
+ }
335
+ for (const issue of checkTypecheckTestPolicy(root)) {
336
+ console.error(`${issue.path}: ${issue.message}`);
337
+ failures++;
338
+ }
339
+ for (const issue of checkWorkspaceBoundedTestTargetPolicy(root)) {
340
+ console.error(`${issue.path}: ${issue.message}`);
341
+ failures++;
342
+ }
298
343
  if (failures === 0) {
299
- console.log('Workspace dependency ranges are valid.');
344
+ console.log('Workspace dependency policy is valid.');
300
345
  }
301
346
  return failures;
302
347
  }
348
+ // ---------------------------------------------------------------------------
349
+ // Helpers kept in CLI (not Nx-specific)
350
+ // ---------------------------------------------------------------------------
303
351
  function fixWorkspaceDependencyRanges(pkg, workspaceNames) {
304
352
  let changed = false;
305
353
  for (const field of workspaceDependencyFields) {
@@ -316,11 +364,58 @@ function fixWorkspaceDependencyRanges(pkg, workspaceNames) {
316
364
  }
317
365
  return changed;
318
366
  }
319
- function setBooleanProperty(record, key, value) {
320
- if (record[key] === value) {
367
+ function validateRootScripts(rootPackage) {
368
+ const scripts = recordProperty(rootPackage, 'scripts');
369
+ let failures = 0;
370
+ for (const [name, command] of Object.entries(rootScriptPolicy)) {
371
+ if (scripts?.[name] !== command) {
372
+ console.error(`package.json scripts.${name} must be ${command}`);
373
+ failures++;
374
+ }
375
+ }
376
+ if (scripts && !recordKeysAreSorted(scripts)) {
377
+ console.error('package.json scripts must be sorted alphabetically so root command policy stays stable across fixes.');
378
+ failures++;
379
+ }
380
+ return failures;
381
+ }
382
+ function validateRootNxScriptInference(rootPackage) {
383
+ const nx = recordProperty(rootPackage, 'nx');
384
+ if (nx && Array.isArray(nx.includedScripts) && nx.includedScripts.length === 0) {
385
+ return 0;
386
+ }
387
+ console.error('package.json nx.includedScripts must be [] so root scripts do not become recursive Nx targets.');
388
+ return 1;
389
+ }
390
+ function suggestNxProjectName(rootPackageName, packageName) {
391
+ const rootScope = npmScope(rootPackageName);
392
+ if (!rootScope || npmScope(packageName) !== rootScope) {
393
+ return null;
394
+ }
395
+ return unscopedPackageName(packageName);
396
+ }
397
+ function npmScope(packageName) {
398
+ const match = /^(@[^/]+)\//.exec(packageName);
399
+ return match?.[1] ?? null;
400
+ }
401
+ function unscopedPackageName(packageName) {
402
+ return packageName.startsWith('@') ? packageName.slice(packageName.indexOf('/') + 1) : packageName;
403
+ }
404
+ function recordKeysAreSorted(record) {
405
+ const keys = Object.keys(record);
406
+ return keys.every((key, index) => index === 0 || keys[index - 1] <= key);
407
+ }
408
+ function sortRecordInPlace(record) {
409
+ if (recordKeysAreSorted(record)) {
321
410
  return false;
322
411
  }
323
- record[key] = value;
412
+ const entries = Object.entries(record).sort(([a], [b]) => a.localeCompare(b));
413
+ for (const key of Object.keys(record)) {
414
+ delete record[key];
415
+ }
416
+ for (const [key, value] of entries) {
417
+ record[key] = value;
418
+ }
324
419
  return true;
325
420
  }
326
421
  function normalizeExportConditionOrder(value) {
@@ -1,2 +1,5 @@
1
1
  export declare function validatePackedPublicPackages(root: string): Promise<number>;
2
+ export declare function validatePackedPublicPackagePublint(root: string): Promise<number>;
3
+ export declare function validatePackedPublicPackageManifest(root: string): Promise<number>;
4
+ export declare function validatePackedPublicPackageTypes(root: string): Promise<number>;
2
5
  //# sourceMappingURL=packed-package.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"packed-package.d.ts","sourceRoot":"","sources":["../../src/monorepo/packed-package.ts"],"names":[],"mappings":"AAUA,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhF"}
1
+ {"version":3,"file":"packed-package.d.ts","sourceRoot":"","sources":["../../src/monorepo/packed-package.ts"],"names":[],"mappings":"AAUA,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhF;AAED,wBAAsB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMtF;AAED,wBAAsB,mCAAmC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMvF;AAED,wBAAsB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMpF"}
@@ -3,64 +3,96 @@ import { join } from 'node:path';
3
3
  import { publint } from 'publint';
4
4
  import { formatMessage } from 'publint/utils';
5
5
  import { isRecord } from '../lib/json.js';
6
- import { runStatus } from '../lib/run.js';
6
+ import { runResult, runStatus } from '../lib/run.js';
7
7
  import { listPublicPackages } from '../lib/workspace.js';
8
8
  import { readPackedPackageJson, validatePackedWorkspaceDependencies } from './packed-manifest.js';
9
9
  export async function validatePackedPublicPackages(root) {
10
+ return ((await validatePackedPublicPackagePublint(root)) +
11
+ (await validatePackedPublicPackageManifest(root)) +
12
+ (await validatePackedPublicPackageTypes(root)));
13
+ }
14
+ export async function validatePackedPublicPackagePublint(root) {
10
15
  let failures = 0;
11
16
  for (const pkg of listPublicPackages(root)) {
12
- failures += await validatePackedPublicPackage(root, pkg);
17
+ failures += await validatePackedPublicPackageTool(root, pkg, validatePublint);
13
18
  }
14
19
  return failures;
15
20
  }
16
- async function validatePackedPublicPackage(root, pkg) {
21
+ export async function validatePackedPublicPackageManifest(root) {
17
22
  let failures = 0;
23
+ for (const pkg of listPublicPackages(root)) {
24
+ failures += await validatePackedPublicPackageTool(root, pkg, validatePackedManifest);
25
+ }
26
+ return failures;
27
+ }
28
+ export async function validatePackedPublicPackageTypes(root) {
29
+ let failures = 0;
30
+ for (const pkg of listPublicPackages(root)) {
31
+ failures += await validatePackedPublicPackageTool(root, pkg, validateAttw);
32
+ }
33
+ return failures;
34
+ }
35
+ async function validatePackedPublicPackageTool(root, pkg, validate) {
18
36
  const packed = await packPackage(root, pkg);
19
37
  try {
20
- const lint = await publint({ pack: { tarball: packed.arrayBuffer }, level: 'warning', strict: true });
21
- for (const message of lint.messages) {
22
- console.error(`${pkg.path}: publint ${message.type} ${message.code}: ${formatMessage(message, lint.pkg)}`);
23
- failures++;
24
- }
25
- const packedPackage = await readPackedPackageJson(root, packed.path, pkg.name);
26
- for (const message of validatePackedWorkspaceDependencies(root, pkg, packedPackage)) {
27
- console.error(message);
28
- failures++;
29
- }
30
- const attwArgs = [
31
- packed.path,
32
- '--format',
33
- 'ascii',
34
- '--no-color',
35
- '--profile',
36
- 'node16',
37
- '--ignore-rules',
38
- 'cjs-resolves-to-esm',
39
- ...attwExcludedEntrypointArgs(pkg),
40
- ];
41
- const attwStatus = await runStatus('attw', ['--quiet', ...attwArgs], root, true);
42
- if (attwStatus !== 0) {
43
- await runStatus('attw', attwArgs, root);
44
- console.error(`${pkg.path}: are-the-types-wrong validation failed`);
45
- failures++;
46
- }
47
- else {
48
- console.log(`${pkg.path}: are-the-types-wrong valid.`);
49
- }
38
+ return await validate(root, pkg, packed);
50
39
  }
51
40
  catch (error) {
52
41
  const message = error instanceof Error ? error.message : String(error);
53
42
  console.error(`${pkg.path}: packed package validation failed: ${message}`);
54
- failures++;
43
+ return 1;
55
44
  }
56
45
  finally {
57
46
  unlinkSync(packed.path);
58
47
  }
59
- if (failures === 0) {
60
- console.log(`${pkg.path}: packed package is valid.`);
48
+ }
49
+ async function validatePublint(_root, pkg, packed) {
50
+ let failures = 0;
51
+ const lint = await publint({ pack: { tarball: packed.arrayBuffer }, level: 'warning', strict: true });
52
+ for (const message of lint.messages) {
53
+ console.error(`${pkg.path}: publint ${message.type} ${message.code}: ${formatMessage(message, lint.pkg)}`);
54
+ failures++;
55
+ }
56
+ return failures;
57
+ }
58
+ async function validatePackedManifest(root, pkg, packed) {
59
+ let failures = 0;
60
+ const packedPackage = await readPackedPackageJson(root, packed.path, pkg.name);
61
+ for (const message of validatePackedWorkspaceDependencies(root, pkg, packedPackage)) {
62
+ console.error(message);
63
+ failures++;
61
64
  }
62
65
  return failures;
63
66
  }
67
+ async function validateAttw(root, pkg, packed) {
68
+ const attwArgs = [
69
+ packed.path,
70
+ '--format',
71
+ 'ascii',
72
+ '--no-color',
73
+ '--profile',
74
+ 'node16',
75
+ '--ignore-rules',
76
+ 'cjs-resolves-to-esm',
77
+ ...attwExcludedEntrypointArgs(pkg),
78
+ ];
79
+ const attw = await runResult('attw', attwArgs, root);
80
+ if (attw.exitCode === 0) {
81
+ return 0;
82
+ }
83
+ printAttwOutput(attw.stdout);
84
+ printAttwOutput(attw.stderr);
85
+ console.error(`${pkg.path}: are-the-types-wrong validation failed`);
86
+ return 1;
87
+ }
88
+ function printAttwOutput(output) {
89
+ for (const line of output.split('\n')) {
90
+ if (!line || line.includes('(ignored)')) {
91
+ continue;
92
+ }
93
+ console.error(line);
94
+ }
95
+ }
64
96
  function attwExcludedEntrypointArgs(pkg) {
65
97
  const excluded = wasmExportEntrypoints(pkg.json.exports);
66
98
  return excluded.length === 0 ? [] : ['--exclude-entrypoints', ...excluded];
@@ -1,10 +1,29 @@
1
1
  export interface MonorepoContext {
2
2
  root: string;
3
3
  syncRuntime: boolean;
4
+ verbose?: boolean;
4
5
  }
5
6
  export interface ValidatePackOptions {
6
7
  failFast?: boolean;
8
+ fix?: boolean;
9
+ verbose?: boolean;
10
+ }
11
+ export interface MonorepoPack {
12
+ name: string;
13
+ init?(ctx: MonorepoContext): Promise<void> | void;
14
+ fixPreBuild?(ctx: MonorepoContext): Promise<void> | void;
15
+ fixPostBuild?(ctx: MonorepoContext): Promise<void> | void;
16
+ validatePreBuild?(ctx: MonorepoContext): Promise<number> | number;
17
+ validatePostBuild?(ctx: MonorepoContext): Promise<number> | number;
18
+ }
19
+ export interface ValidatePackRunHooks {
20
+ packs?: readonly MonorepoPack[];
21
+ runBuild?: (ctx: MonorepoContext, options?: ValidatePackOptions) => Promise<number> | number;
22
+ }
23
+ export interface ValidatePackResult {
24
+ failures: number;
25
+ failedChecks: number;
7
26
  }
8
27
  export declare function runInitPacks(ctx: MonorepoContext): Promise<void>;
9
- export declare function runValidatePacks(ctx: MonorepoContext, options?: ValidatePackOptions): Promise<number>;
28
+ export declare function runValidatePacks(ctx: MonorepoContext, options?: ValidatePackOptions, hooks?: ValidatePackRunHooks): Promise<ValidatePackResult>;
10
29
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/monorepo/packs/index.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAyDD,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAItE;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB/G"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/monorepo/packs/index.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClD,WAAW,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,YAAY,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1D,gBAAgB,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAClE,iBAAiB,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CACpE;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CAC9F;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAgGD,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAItE;AAED,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,eAAe,EACpB,OAAO,GAAE,mBAAwB,EACjC,KAAK,GAAE,oBAAyB,GAC/B,OAAO,CAAC,kBAAkB,CAAC,CA0B7B"}