@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.
- package/README.md +161 -32
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +110 -10
- package/dist/devenv/index.d.ts +10 -0
- package/dist/devenv/index.d.ts.map +1 -0
- package/dist/devenv/index.js +27 -0
- package/dist/generate/index.d.ts +20 -0
- package/dist/generate/index.d.ts.map +1 -0
- package/dist/generate/index.js +47 -0
- package/dist/lib/cli-package.d.ts +4 -0
- package/dist/lib/cli-package.d.ts.map +1 -0
- package/dist/lib/cli-package.js +15 -0
- package/dist/lib/run.d.ts +6 -0
- package/dist/lib/run.d.ts.map +1 -1
- package/dist/lib/run.js +32 -0
- package/dist/lib/workspace.d.ts +25 -1
- package/dist/lib/workspace.d.ts.map +1 -1
- package/dist/lib/workspace.js +79 -17
- package/dist/monorepo/commit-msg.d.ts +8 -1
- package/dist/monorepo/commit-msg.d.ts.map +1 -1
- package/dist/monorepo/commit-msg.js +85 -7
- package/dist/monorepo/index.d.ts +19 -2
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +104 -16
- package/dist/monorepo/lockfile.d.ts.map +1 -1
- package/dist/monorepo/lockfile.js +9 -0
- package/dist/monorepo/managed-files.js +1 -1
- package/dist/monorepo/nx-sync.d.ts +2 -1
- package/dist/monorepo/nx-sync.d.ts.map +1 -1
- package/dist/monorepo/nx-sync.js +8 -2
- package/dist/monorepo/package-hygiene.d.ts +6 -2
- package/dist/monorepo/package-hygiene.d.ts.map +1 -1
- package/dist/monorepo/package-hygiene.js +12 -4
- package/dist/monorepo/package-policy.d.ts +13 -4
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +208 -113
- package/dist/monorepo/packed-package.d.ts +3 -0
- package/dist/monorepo/packed-package.d.ts.map +1 -1
- package/dist/monorepo/packed-package.js +68 -36
- package/dist/monorepo/packs/index.d.ts +20 -1
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +238 -19
- package/dist/monorepo/publish-workflow.d.ts +0 -4
- package/dist/monorepo/publish-workflow.d.ts.map +1 -1
- package/dist/monorepo/publish-workflow.js +7 -15
- package/dist/monorepo/tool-validation.d.ts +22 -0
- package/dist/monorepo/tool-validation.d.ts.map +1 -0
- package/dist/monorepo/tool-validation.js +399 -0
- package/dist/nx/index.d.ts +25 -0
- package/dist/nx/index.d.ts.map +1 -0
- package/dist/nx/index.js +112 -0
- package/dist/release/bootstrap-npm-packages.d.ts +19 -0
- package/dist/release/bootstrap-npm-packages.d.ts.map +1 -0
- package/dist/release/bootstrap-npm-packages.js +58 -0
- package/dist/release/candidates.d.ts +11 -0
- package/dist/release/candidates.d.ts.map +1 -0
- package/dist/release/candidates.js +181 -0
- package/dist/release/core.d.ts +4 -1
- package/dist/release/core.d.ts.map +1 -1
- package/dist/release/core.js +76 -9
- package/dist/release/github-release.d.ts +1 -0
- package/dist/release/github-release.d.ts.map +1 -1
- package/dist/release/github-release.js +3 -3
- package/dist/release/index.d.ts +32 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +488 -64
- package/dist/release/npm-auth.d.ts +0 -1
- package/dist/release/npm-auth.d.ts.map +1 -1
- package/dist/release/npm-auth.js +4 -16
- package/dist/release/orchestration.d.ts +11 -2
- package/dist/release/orchestration.d.ts.map +1 -1
- package/dist/release/orchestration.js +41 -3
- package/dist/release/publish-plan.js +1 -1
- package/managed/raw/git-format-staged.yml +54 -0
- package/managed/raw/tooling/git-hooks/commit-msg.sh +1 -1
- package/managed/templates/github/actions/setup-devenv/action.yml +1 -7
- package/managed/templates/github/workflows/ci.yml +4 -2
- package/package.json +15 -8
- package/src/cli.ts +153 -22
- package/src/devenv/index.test.ts +49 -0
- package/src/devenv/index.ts +38 -0
- package/src/generate/index.ts +92 -0
- package/src/lib/cli-package.ts +18 -0
- package/src/lib/run.ts +44 -0
- package/src/lib/workspace.ts +112 -17
- package/src/monorepo/__tests__/publish-workflow.test.ts +16 -9
- package/src/monorepo/commit-msg.test.ts +45 -0
- package/src/monorepo/commit-msg.ts +107 -7
- package/src/monorepo/index.ts +142 -16
- package/src/monorepo/lockfile.ts +9 -0
- package/src/monorepo/managed-files.ts +1 -1
- package/src/monorepo/nx-sync.ts +9 -2
- package/src/monorepo/package-hygiene.test.ts +34 -0
- package/src/monorepo/package-hygiene.ts +26 -4
- package/src/monorepo/package-policy.test.ts +1130 -0
- package/src/monorepo/package-policy.ts +237 -114
- package/src/monorepo/packed-package.ts +89 -37
- package/src/monorepo/packs/index.test.ts +189 -0
- package/src/monorepo/packs/index.ts +311 -20
- package/src/monorepo/publish-workflow.ts +10 -19
- package/src/monorepo/setup-test-tracing.test.ts +120 -0
- package/src/monorepo/tool-validation.test.ts +261 -0
- package/src/monorepo/tool-validation.ts +462 -0
- package/src/nx/index.test.ts +73 -0
- package/src/nx/index.ts +139 -0
- package/src/release/__tests__/bootstrap-npm-packages.test.ts +127 -0
- package/src/release/__tests__/candidates.test.ts +262 -0
- package/src/release/__tests__/core-properties.test.ts +6 -6
- package/src/release/__tests__/core-scenarios.test.ts +27 -20
- package/src/release/__tests__/core.test.ts +52 -5
- package/src/release/__tests__/fixture-repo.test.ts +90 -31
- package/src/release/__tests__/github-release.test.ts +22 -24
- package/src/release/__tests__/helpers/fixture-repo.ts +1 -0
- package/src/release/__tests__/npm-auth.test.ts +12 -21
- package/src/release/__tests__/orchestration.test.ts +142 -12
- package/src/release/__tests__/publish-plan.test.ts +9 -3
- package/src/release/__tests__/retag-unpublished.test.ts +10 -10
- package/src/release/__tests__/trust-publisher.test.ts +165 -0
- package/src/release/bootstrap-npm-packages.ts +88 -0
- package/src/release/candidates.ts +225 -0
- package/src/release/core.ts +96 -10
- package/src/release/github-release.ts +3 -3
- package/src/release/index.ts +658 -74
- package/src/release/npm-auth.ts +4 -33
- package/src/release/orchestration.ts +60 -5
- package/src/release/publish-plan.ts +1 -1
- package/dist/nx-version-actions.cjs +0 -25
- package/dist/nx-version-actions.d.cts +0 -6
- package/dist/nx-version-actions.d.cts.map +0 -1
- package/managed/raw/.git-format-staged.yml +0 -47
- package/src/nx-version-actions.cts +0 -36
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
applyWorkspaceBoundedTestTargetPolicy,
|
|
4
|
+
checkWorkspaceBoundedTestTargetPolicy,
|
|
5
|
+
} from '@smoothbricks/nx-plugin/bounded-test-policy';
|
|
6
|
+
import {
|
|
7
|
+
applyPackageTargetPolicy,
|
|
8
|
+
checkPackageTargetPolicy,
|
|
9
|
+
type PackageTargetPolicyOptions,
|
|
10
|
+
type ResolvedProjectTargets,
|
|
11
|
+
} from '@smoothbricks/nx-plugin/package-target-policy';
|
|
12
|
+
import {
|
|
13
|
+
applyReleaseConfigPolicy,
|
|
14
|
+
checkReleaseConfigPolicy,
|
|
15
|
+
SMOO_NX_RELEASE_TAG_PATTERN,
|
|
16
|
+
SMOO_NX_VERSION_ACTIONS,
|
|
17
|
+
} from '@smoothbricks/nx-plugin/release-config-policy';
|
|
18
|
+
import { applyTypecheckTestPolicy, checkTypecheckTestPolicy } from '@smoothbricks/nx-plugin/typecheck-test-policy';
|
|
19
|
+
import {
|
|
20
|
+
applyWorkspaceConfigPolicy,
|
|
21
|
+
checkWorkspaceConfigPolicy,
|
|
22
|
+
} from '@smoothbricks/nx-plugin/workspace-config-policy';
|
|
2
23
|
import {
|
|
3
24
|
getOrCreateRecord,
|
|
4
25
|
hasOwnString,
|
|
@@ -12,6 +33,7 @@ import {
|
|
|
12
33
|
writeJsonObject,
|
|
13
34
|
} from '../lib/json.js';
|
|
14
35
|
import {
|
|
36
|
+
getWorkspacePackageManifests,
|
|
15
37
|
getWorkspacePackages,
|
|
16
38
|
listPackageJsonRecords,
|
|
17
39
|
listPublicPackages,
|
|
@@ -21,8 +43,55 @@ import {
|
|
|
21
43
|
workspaceDependencyFields,
|
|
22
44
|
} from '../lib/workspace.js';
|
|
23
45
|
|
|
24
|
-
export
|
|
25
|
-
export
|
|
46
|
+
export type { PackageTargetPolicyOptions as WorkspaceDependencyDefaultOptions, ResolvedProjectTargets };
|
|
47
|
+
export { SMOO_NX_RELEASE_TAG_PATTERN, SMOO_NX_VERSION_ACTIONS };
|
|
48
|
+
|
|
49
|
+
const extraCommitScopes = ['release'];
|
|
50
|
+
const rootScriptPolicy: Record<string, string> = {
|
|
51
|
+
lint: 'nx run-many -t lint',
|
|
52
|
+
'lint:fix': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --unstaged',
|
|
53
|
+
'format:staged': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml',
|
|
54
|
+
'format:changed': 'git-format-staged --config tooling/git-hooks/git-format-staged.yml --also-unstaged',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function applyFixableMonorepoDefaults(root: string): void {
|
|
58
|
+
applyRootScriptDefaults(root);
|
|
59
|
+
applyNxPluginDefaults(root);
|
|
60
|
+
applyNxProjectNameDefaults(root);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function applyRootScriptDefaults(root: string): void {
|
|
64
|
+
const rootPackagePath = join(root, 'package.json');
|
|
65
|
+
const rootPackage = readJsonObject(rootPackagePath);
|
|
66
|
+
if (!rootPackage) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const scripts = getOrCreateRecord(rootPackage, 'scripts');
|
|
70
|
+
let changed = false;
|
|
71
|
+
for (const [name, command] of Object.entries(rootScriptPolicy)) {
|
|
72
|
+
changed = setStringProperty(scripts, name, command) || changed;
|
|
73
|
+
}
|
|
74
|
+
const nx = getOrCreateRecord(rootPackage, 'nx');
|
|
75
|
+
if (!Array.isArray(nx.includedScripts) || nx.includedScripts.length !== 0) {
|
|
76
|
+
nx.includedScripts = [];
|
|
77
|
+
changed = true;
|
|
78
|
+
}
|
|
79
|
+
changed = sortRecordInPlace(scripts) || changed;
|
|
80
|
+
if (changed) {
|
|
81
|
+
writeJsonObject(rootPackagePath, rootPackage);
|
|
82
|
+
console.log('updated package.json root smoo scripts');
|
|
83
|
+
} else {
|
|
84
|
+
console.log('unchanged package.json root smoo scripts');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function applyNxPluginDefaults(root: string): void {
|
|
89
|
+
if (applyWorkspaceConfigPolicy(root)) {
|
|
90
|
+
console.log('updated nx.json smoo plugin config');
|
|
91
|
+
} else {
|
|
92
|
+
console.log('unchanged nx.json smoo plugin config');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
26
95
|
|
|
27
96
|
export function applyPublicPackageDefaults(root: string): void {
|
|
28
97
|
const rootPackage = requiredJsonObject(join(root, 'package.json'));
|
|
@@ -65,58 +134,83 @@ export function applyPublicPackageDefaults(root: string): void {
|
|
|
65
134
|
}
|
|
66
135
|
}
|
|
67
136
|
|
|
68
|
-
export function applyWorkspaceDependencyDefaults(root: string): void {
|
|
137
|
+
export function applyWorkspaceDependencyDefaults(root: string, options: PackageTargetPolicyOptions = {}): void {
|
|
69
138
|
const workspaceNames = new Set(getWorkspacePackages(root).map((pkg) => pkg.name));
|
|
70
139
|
for (const pkg of listPackageJsonRecords(root)) {
|
|
71
140
|
const changed = fixWorkspaceDependencyRanges(pkg.json, workspaceNames);
|
|
72
141
|
if (changed) {
|
|
73
142
|
writeJsonObject(pkg.packageJsonPath, pkg.json);
|
|
74
143
|
console.log(`updated ${pkg.path}/package.json workspace dependency ranges`);
|
|
75
|
-
} else {
|
|
76
|
-
console.log(`unchanged ${pkg.path}/package.json workspace dependency ranges`);
|
|
77
144
|
}
|
|
78
145
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const nxJson = requiredJsonObject(nxJsonPath);
|
|
84
|
-
let changed = false;
|
|
85
|
-
const release = getOrCreateRecord(nxJson, 'release');
|
|
86
|
-
changed = setStringProperty(release, 'projectsRelationship', 'independent') || changed;
|
|
87
|
-
const version = getOrCreateRecord(release, 'version');
|
|
88
|
-
changed = setStringProperty(version, 'specifierSource', 'conventional-commits') || changed;
|
|
89
|
-
changed = setStringProperty(version, 'currentVersionResolver', 'git-tag') || changed;
|
|
90
|
-
changed = setStringProperty(version, 'fallbackCurrentVersionResolver', 'disk') || changed;
|
|
91
|
-
changed = setStringProperty(version, 'versionActions', SMOO_NX_VERSION_ACTIONS) || changed;
|
|
92
|
-
if (delete version.preVersionCommand) {
|
|
93
|
-
changed = true;
|
|
146
|
+
if (applyPackageTargetPolicy(root, options)) {
|
|
147
|
+
console.log('updated package Nx target policy');
|
|
148
|
+
} else {
|
|
149
|
+
console.log('unchanged package Nx target policy');
|
|
94
150
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const projectChangelogs = getOrCreateRecord(changelog, 'projectChangelogs');
|
|
100
|
-
changed = setBooleanProperty(projectChangelogs, 'createRelease', false) || changed;
|
|
101
|
-
changed = setBooleanProperty(projectChangelogs, 'file', false) || changed;
|
|
102
|
-
const renderOptions = getOrCreateRecord(projectChangelogs, 'renderOptions');
|
|
103
|
-
if (typeof renderOptions.authors !== 'boolean') {
|
|
104
|
-
renderOptions.authors = true;
|
|
105
|
-
changed = true;
|
|
151
|
+
if (applyTypecheckTestPolicy(root)) {
|
|
152
|
+
console.log('updated tsconfig.test.json policy');
|
|
153
|
+
} else {
|
|
154
|
+
console.log('unchanged tsconfig.test.json policy');
|
|
106
155
|
}
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
if (applyWorkspaceBoundedTestTargetPolicy(root)) {
|
|
157
|
+
console.log('updated package test targets bounded execution policy');
|
|
158
|
+
} else {
|
|
159
|
+
console.log('unchanged package test targets bounded execution policy');
|
|
110
160
|
}
|
|
161
|
+
}
|
|
111
162
|
|
|
112
|
-
|
|
113
|
-
|
|
163
|
+
export function applyNxReleaseDefaults(root: string): void {
|
|
164
|
+
if (applyReleaseConfigPolicy(root)) {
|
|
114
165
|
console.log('updated nx.json release config');
|
|
115
166
|
} else {
|
|
116
167
|
console.log('unchanged nx.json release config');
|
|
117
168
|
}
|
|
118
169
|
}
|
|
119
170
|
|
|
171
|
+
export function applyNxProjectNameDefaults(root: string): void {
|
|
172
|
+
const rootPackage = requiredJsonObject(join(root, 'package.json'));
|
|
173
|
+
const rootName = stringProperty(rootPackage, 'name');
|
|
174
|
+
if (!rootName) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const pkg of getWorkspacePackageManifests(root)) {
|
|
178
|
+
const suggestedName = suggestNxProjectName(rootName, pkg.name);
|
|
179
|
+
if (!suggestedName) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const nx = getOrCreateRecord(pkg.json, 'nx');
|
|
183
|
+
const changed = setStringProperty(nx, 'name', suggestedName);
|
|
184
|
+
if (changed) {
|
|
185
|
+
writeJsonObject(pkg.packageJsonPath, pkg.json);
|
|
186
|
+
console.log(`updated ${pkg.path}/package.json nx.name`);
|
|
187
|
+
} else {
|
|
188
|
+
console.log(`unchanged ${pkg.path}/package.json nx.name`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function listValidCommitScopes(root: string): ReadonlySet<string> {
|
|
194
|
+
return new Set([...listNxProjectNames(root), ...extraCommitScopes]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function listNxProjectNames(root: string): string[] {
|
|
198
|
+
const rootPackage = readJsonObject(join(root, 'package.json'));
|
|
199
|
+
const rootName = rootPackage ? stringProperty(rootPackage, 'name') : null;
|
|
200
|
+
const names: string[] = [];
|
|
201
|
+
for (const pkg of getWorkspacePackageManifests(root)) {
|
|
202
|
+
const nx = recordProperty(pkg.json, 'nx');
|
|
203
|
+
const configuredName = nx ? stringProperty(nx, 'name') : null;
|
|
204
|
+
const suggestedName = rootName ? suggestNxProjectName(rootName, pkg.name) : null;
|
|
205
|
+
if (configuredName) {
|
|
206
|
+
names.push(configuredName);
|
|
207
|
+
} else if (suggestedName) {
|
|
208
|
+
names.push(suggestedName);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return names;
|
|
212
|
+
}
|
|
213
|
+
|
|
120
214
|
export function validateRootPackagePolicy(root: string): number {
|
|
121
215
|
const rootPackage = readJsonObject(join(root, 'package.json'));
|
|
122
216
|
if (!rootPackage) {
|
|
@@ -136,6 +230,8 @@ export function validateRootPackagePolicy(root: string): number {
|
|
|
136
230
|
console.error('package.json must define repository.url');
|
|
137
231
|
failures++;
|
|
138
232
|
}
|
|
233
|
+
failures += validateRootScripts(rootPackage);
|
|
234
|
+
failures += validateRootNxScriptInference(rootPackage);
|
|
139
235
|
const packageManager = stringProperty(rootPackage, 'packageManager');
|
|
140
236
|
if (!packageManager?.startsWith('bun@')) {
|
|
141
237
|
console.error('package.json packageManager must use bun@<version>');
|
|
@@ -156,85 +252,41 @@ export function validateRootPackagePolicy(root: string): number {
|
|
|
156
252
|
}
|
|
157
253
|
|
|
158
254
|
export function validateNxReleaseConfig(root: string): number {
|
|
159
|
-
const nxJson = readJsonObject(join(root, 'nx.json'));
|
|
160
|
-
if (!nxJson) {
|
|
161
|
-
console.error('nx.json not found or invalid');
|
|
162
|
-
return 1;
|
|
163
|
-
}
|
|
164
|
-
const release = recordProperty(nxJson, 'release');
|
|
165
|
-
const version = release ? recordProperty(release, 'version') : null;
|
|
166
|
-
const releaseTag = release ? recordProperty(release, 'releaseTag') : null;
|
|
167
|
-
const changelog = release ? recordProperty(release, 'changelog') : null;
|
|
168
|
-
const projectChangelogs = changelog ? recordProperty(changelog, 'projectChangelogs') : null;
|
|
169
|
-
const renderOptions = projectChangelogs ? recordProperty(projectChangelogs, 'renderOptions') : null;
|
|
170
255
|
let failures = 0;
|
|
171
|
-
|
|
172
|
-
console.error(
|
|
173
|
-
failures++;
|
|
174
|
-
}
|
|
175
|
-
if (release && stringProperty(release, 'projectsRelationship') !== 'independent') {
|
|
176
|
-
console.error('nx.json release.projectsRelationship must be independent');
|
|
177
|
-
failures++;
|
|
178
|
-
}
|
|
179
|
-
if (!version) {
|
|
180
|
-
console.error('nx.json release.version config is missing');
|
|
181
|
-
failures++;
|
|
182
|
-
}
|
|
183
|
-
if (version && stringProperty(version, 'specifierSource') !== 'conventional-commits') {
|
|
184
|
-
console.error('nx.json release.version.specifierSource must be conventional-commits');
|
|
185
|
-
failures++;
|
|
186
|
-
}
|
|
187
|
-
// Nx requires git-tag as the primary resolver when deriving versions from
|
|
188
|
-
// conventional commits. Disk is only a fallback for initial untagged packages.
|
|
189
|
-
if (version && stringProperty(version, 'currentVersionResolver') !== 'git-tag') {
|
|
190
|
-
console.error('nx.json release.version.currentVersionResolver must be git-tag');
|
|
256
|
+
for (const issue of checkWorkspaceConfigPolicy(root)) {
|
|
257
|
+
console.error(issue.message);
|
|
191
258
|
failures++;
|
|
192
259
|
}
|
|
193
|
-
|
|
194
|
-
console.error(
|
|
260
|
+
for (const issue of checkReleaseConfigPolicy(root)) {
|
|
261
|
+
console.error(issue.message);
|
|
195
262
|
failures++;
|
|
196
263
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
failures++;
|
|
206
|
-
}
|
|
207
|
-
if (!releaseTag) {
|
|
208
|
-
console.error('nx.json release.releaseTag config is missing');
|
|
209
|
-
failures++;
|
|
210
|
-
}
|
|
211
|
-
if (releaseTag && stringProperty(releaseTag, 'pattern') !== SMOO_NX_RELEASE_TAG_PATTERN) {
|
|
212
|
-
console.error(`nx.json release.releaseTag.pattern must be ${SMOO_NX_RELEASE_TAG_PATTERN}`);
|
|
213
|
-
failures++;
|
|
214
|
-
}
|
|
215
|
-
if (!changelog) {
|
|
216
|
-
console.error('nx.json release.changelog config is missing');
|
|
217
|
-
failures++;
|
|
218
|
-
}
|
|
219
|
-
if (changelog && changelog.workspaceChangelog !== false) {
|
|
220
|
-
console.error('nx.json release.changelog.workspaceChangelog must be false');
|
|
221
|
-
failures++;
|
|
222
|
-
}
|
|
223
|
-
if (!projectChangelogs) {
|
|
224
|
-
console.error('nx.json release.changelog.projectChangelogs config is missing');
|
|
225
|
-
failures++;
|
|
226
|
-
}
|
|
227
|
-
if (projectChangelogs && projectChangelogs.createRelease !== false) {
|
|
228
|
-
console.error('nx.json release.changelog.projectChangelogs.createRelease must be false');
|
|
229
|
-
failures++;
|
|
264
|
+
return failures;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function validateNxProjectNames(root: string): number {
|
|
268
|
+
const rootPackage = readJsonObject(join(root, 'package.json'));
|
|
269
|
+
const rootName = rootPackage ? stringProperty(rootPackage, 'name') : null;
|
|
270
|
+
if (!rootName) {
|
|
271
|
+
return 0;
|
|
230
272
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
273
|
+
let failures = 0;
|
|
274
|
+
for (const pkg of getWorkspacePackageManifests(root)) {
|
|
275
|
+
const suggestedName = suggestNxProjectName(rootName, pkg.name);
|
|
276
|
+
if (!suggestedName) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const nx = recordProperty(pkg.json, 'nx');
|
|
280
|
+
const configuredName = nx ? stringProperty(nx, 'name') : null;
|
|
281
|
+
if (configuredName !== suggestedName) {
|
|
282
|
+
console.error(
|
|
283
|
+
`${pkg.path}: package.json nx.name must be "${suggestedName}" so fix(${suggestedName}): maps to this project`,
|
|
284
|
+
);
|
|
285
|
+
failures++;
|
|
286
|
+
}
|
|
234
287
|
}
|
|
235
|
-
if (
|
|
236
|
-
console.
|
|
237
|
-
failures++;
|
|
288
|
+
if (failures === 0) {
|
|
289
|
+
console.log('Nx project names are valid.');
|
|
238
290
|
}
|
|
239
291
|
return failures;
|
|
240
292
|
}
|
|
@@ -315,9 +367,9 @@ export function validatePublicPackageMetadata(root: string): number {
|
|
|
315
367
|
return failures;
|
|
316
368
|
}
|
|
317
369
|
|
|
318
|
-
export function validateWorkspaceDependencies(root: string): number {
|
|
319
|
-
const workspaceNames = new Set(getWorkspacePackages(root).map((pkg) => pkg.name));
|
|
370
|
+
export function validateWorkspaceDependencies(root: string, options: PackageTargetPolicyOptions = {}): number {
|
|
320
371
|
let failures = 0;
|
|
372
|
+
const workspaceNames = new Set(getWorkspacePackages(root).map((pkg) => pkg.name));
|
|
321
373
|
for (const pkg of listPackageJsonRecords(root)) {
|
|
322
374
|
for (const field of workspaceDependencyFields) {
|
|
323
375
|
const dependencies = recordProperty(pkg.json, field);
|
|
@@ -332,12 +384,28 @@ export function validateWorkspaceDependencies(root: string): number {
|
|
|
332
384
|
}
|
|
333
385
|
}
|
|
334
386
|
}
|
|
387
|
+
for (const issue of checkPackageTargetPolicy(root, options)) {
|
|
388
|
+
console.error(`${issue.path}: ${issue.message}`);
|
|
389
|
+
failures++;
|
|
390
|
+
}
|
|
391
|
+
for (const issue of checkTypecheckTestPolicy(root)) {
|
|
392
|
+
console.error(`${issue.path}: ${issue.message}`);
|
|
393
|
+
failures++;
|
|
394
|
+
}
|
|
395
|
+
for (const issue of checkWorkspaceBoundedTestTargetPolicy(root)) {
|
|
396
|
+
console.error(`${issue.path}: ${issue.message}`);
|
|
397
|
+
failures++;
|
|
398
|
+
}
|
|
335
399
|
if (failures === 0) {
|
|
336
|
-
console.log('Workspace dependency
|
|
400
|
+
console.log('Workspace dependency policy is valid.');
|
|
337
401
|
}
|
|
338
402
|
return failures;
|
|
339
403
|
}
|
|
340
404
|
|
|
405
|
+
// ---------------------------------------------------------------------------
|
|
406
|
+
// Helpers kept in CLI (not Nx-specific)
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
|
|
341
409
|
function fixWorkspaceDependencyRanges(pkg: Record<string, unknown>, workspaceNames: Set<string>): boolean {
|
|
342
410
|
let changed = false;
|
|
343
411
|
for (const field of workspaceDependencyFields) {
|
|
@@ -355,11 +423,66 @@ function fixWorkspaceDependencyRanges(pkg: Record<string, unknown>, workspaceNam
|
|
|
355
423
|
return changed;
|
|
356
424
|
}
|
|
357
425
|
|
|
358
|
-
function
|
|
359
|
-
|
|
426
|
+
function validateRootScripts(rootPackage: Record<string, unknown>): number {
|
|
427
|
+
const scripts = recordProperty(rootPackage, 'scripts');
|
|
428
|
+
let failures = 0;
|
|
429
|
+
for (const [name, command] of Object.entries(rootScriptPolicy)) {
|
|
430
|
+
if (scripts?.[name] !== command) {
|
|
431
|
+
console.error(`package.json scripts.${name} must be ${command}`);
|
|
432
|
+
failures++;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (scripts && !recordKeysAreSorted(scripts)) {
|
|
436
|
+
console.error(
|
|
437
|
+
'package.json scripts must be sorted alphabetically so root command policy stays stable across fixes.',
|
|
438
|
+
);
|
|
439
|
+
failures++;
|
|
440
|
+
}
|
|
441
|
+
return failures;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function validateRootNxScriptInference(rootPackage: Record<string, unknown>): number {
|
|
445
|
+
const nx = recordProperty(rootPackage, 'nx');
|
|
446
|
+
if (nx && Array.isArray(nx.includedScripts) && nx.includedScripts.length === 0) {
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
console.error('package.json nx.includedScripts must be [] so root scripts do not become recursive Nx targets.');
|
|
450
|
+
return 1;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function suggestNxProjectName(rootPackageName: string, packageName: string): string | null {
|
|
454
|
+
const rootScope = npmScope(rootPackageName);
|
|
455
|
+
if (!rootScope || npmScope(packageName) !== rootScope) {
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
return unscopedPackageName(packageName);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function npmScope(packageName: string): string | null {
|
|
462
|
+
const match = /^(@[^/]+)\//.exec(packageName);
|
|
463
|
+
return match?.[1] ?? null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function unscopedPackageName(packageName: string): string {
|
|
467
|
+
return packageName.startsWith('@') ? packageName.slice(packageName.indexOf('/') + 1) : packageName;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function recordKeysAreSorted(record: Record<string, unknown>): boolean {
|
|
471
|
+
const keys = Object.keys(record);
|
|
472
|
+
return keys.every((key, index) => index === 0 || keys[index - 1] <= key);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function sortRecordInPlace(record: Record<string, unknown>): boolean {
|
|
476
|
+
if (recordKeysAreSorted(record)) {
|
|
360
477
|
return false;
|
|
361
478
|
}
|
|
362
|
-
record[
|
|
479
|
+
const entries = Object.entries(record).sort(([a], [b]) => a.localeCompare(b));
|
|
480
|
+
for (const key of Object.keys(record)) {
|
|
481
|
+
delete record[key];
|
|
482
|
+
}
|
|
483
|
+
for (const [key, value] of entries) {
|
|
484
|
+
record[key] = value;
|
|
485
|
+
}
|
|
363
486
|
return true;
|
|
364
487
|
}
|
|
365
488
|
|
|
@@ -3,67 +3,119 @@ 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 type { PackageInfo } from '../lib/workspace.js';
|
|
8
8
|
import { listPublicPackages } from '../lib/workspace.js';
|
|
9
9
|
import { readPackedPackageJson, validatePackedWorkspaceDependencies } from './packed-manifest.js';
|
|
10
10
|
|
|
11
11
|
export async function validatePackedPublicPackages(root: string): Promise<number> {
|
|
12
|
+
return (
|
|
13
|
+
(await validatePackedPublicPackagePublint(root)) +
|
|
14
|
+
(await validatePackedPublicPackageManifest(root)) +
|
|
15
|
+
(await validatePackedPublicPackageTypes(root))
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function validatePackedPublicPackagePublint(root: string): Promise<number> {
|
|
12
20
|
let failures = 0;
|
|
13
21
|
for (const pkg of listPublicPackages(root)) {
|
|
14
|
-
failures += await
|
|
22
|
+
failures += await validatePackedPublicPackageTool(root, pkg, validatePublint);
|
|
15
23
|
}
|
|
16
24
|
return failures;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
|
-
async function
|
|
27
|
+
export async function validatePackedPublicPackageManifest(root: string): Promise<number> {
|
|
20
28
|
let failures = 0;
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
failures++;
|
|
27
|
-
}
|
|
29
|
+
for (const pkg of listPublicPackages(root)) {
|
|
30
|
+
failures += await validatePackedPublicPackageTool(root, pkg, validatePackedManifest);
|
|
31
|
+
}
|
|
32
|
+
return failures;
|
|
33
|
+
}
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
export async function validatePackedPublicPackageTypes(root: string): Promise<number> {
|
|
36
|
+
let failures = 0;
|
|
37
|
+
for (const pkg of listPublicPackages(root)) {
|
|
38
|
+
failures += await validatePackedPublicPackageTool(root, pkg, validateAttw);
|
|
39
|
+
}
|
|
40
|
+
return failures;
|
|
41
|
+
}
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
'cjs-resolves-to-esm',
|
|
44
|
-
...attwExcludedEntrypointArgs(pkg),
|
|
45
|
-
];
|
|
46
|
-
const attwStatus = await runStatus('attw', ['--quiet', ...attwArgs], root, true);
|
|
47
|
-
if (attwStatus !== 0) {
|
|
48
|
-
await runStatus('attw', attwArgs, root);
|
|
49
|
-
console.error(`${pkg.path}: are-the-types-wrong validation failed`);
|
|
50
|
-
failures++;
|
|
51
|
-
} else {
|
|
52
|
-
console.log(`${pkg.path}: are-the-types-wrong valid.`);
|
|
53
|
-
}
|
|
43
|
+
async function validatePackedPublicPackageTool(
|
|
44
|
+
root: string,
|
|
45
|
+
pkg: PackageInfo,
|
|
46
|
+
validate: (root: string, pkg: PackageInfo, packed: { path: string; arrayBuffer: ArrayBuffer }) => Promise<number>,
|
|
47
|
+
): Promise<number> {
|
|
48
|
+
const packed = await packPackage(root, pkg);
|
|
49
|
+
try {
|
|
50
|
+
return await validate(root, pkg, packed);
|
|
54
51
|
} catch (error) {
|
|
55
52
|
const message = error instanceof Error ? error.message : String(error);
|
|
56
53
|
console.error(`${pkg.path}: packed package validation failed: ${message}`);
|
|
57
|
-
|
|
54
|
+
return 1;
|
|
58
55
|
} finally {
|
|
59
56
|
unlinkSync(packed.path);
|
|
60
57
|
}
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function validatePublint(
|
|
61
|
+
_root: string,
|
|
62
|
+
pkg: PackageInfo,
|
|
63
|
+
packed: { path: string; arrayBuffer: ArrayBuffer },
|
|
64
|
+
): Promise<number> {
|
|
65
|
+
let failures = 0;
|
|
66
|
+
const lint = await publint({ pack: { tarball: packed.arrayBuffer }, level: 'warning', strict: true });
|
|
67
|
+
for (const message of lint.messages) {
|
|
68
|
+
console.error(`${pkg.path}: publint ${message.type} ${message.code}: ${formatMessage(message, lint.pkg)}`);
|
|
69
|
+
failures++;
|
|
70
|
+
}
|
|
71
|
+
return failures;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function validatePackedManifest(
|
|
75
|
+
root: string,
|
|
76
|
+
pkg: PackageInfo,
|
|
77
|
+
packed: { path: string; arrayBuffer: ArrayBuffer },
|
|
78
|
+
): Promise<number> {
|
|
79
|
+
let failures = 0;
|
|
80
|
+
const packedPackage = await readPackedPackageJson(root, packed.path, pkg.name);
|
|
81
|
+
for (const message of validatePackedWorkspaceDependencies(root, pkg, packedPackage)) {
|
|
82
|
+
console.error(message);
|
|
83
|
+
failures++;
|
|
63
84
|
}
|
|
64
85
|
return failures;
|
|
65
86
|
}
|
|
66
87
|
|
|
88
|
+
async function validateAttw(root: string, pkg: PackageInfo, packed: { path: string }): Promise<number> {
|
|
89
|
+
const attwArgs = [
|
|
90
|
+
packed.path,
|
|
91
|
+
'--format',
|
|
92
|
+
'ascii',
|
|
93
|
+
'--no-color',
|
|
94
|
+
'--profile',
|
|
95
|
+
'node16',
|
|
96
|
+
'--ignore-rules',
|
|
97
|
+
'cjs-resolves-to-esm',
|
|
98
|
+
...attwExcludedEntrypointArgs(pkg),
|
|
99
|
+
];
|
|
100
|
+
const attw = await runResult('attw', attwArgs, root);
|
|
101
|
+
if (attw.exitCode === 0) {
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
printAttwOutput(attw.stdout);
|
|
105
|
+
printAttwOutput(attw.stderr);
|
|
106
|
+
console.error(`${pkg.path}: are-the-types-wrong validation failed`);
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function printAttwOutput(output: string): void {
|
|
111
|
+
for (const line of output.split('\n')) {
|
|
112
|
+
if (!line || line.includes('(ignored)')) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
console.error(line);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
67
119
|
function attwExcludedEntrypointArgs(pkg: PackageInfo): string[] {
|
|
68
120
|
const excluded = wasmExportEntrypoints(pkg.json.exports);
|
|
69
121
|
return excluded.length === 0 ? [] : ['--exclude-entrypoints', ...excluded];
|