@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,3 +1,5 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
1
3
|
const validCommitTypes = new Set([
|
|
2
4
|
'build',
|
|
3
5
|
'chore',
|
|
@@ -10,26 +12,124 @@ const validCommitTypes = new Set([
|
|
|
10
12
|
'revert',
|
|
11
13
|
'style',
|
|
12
14
|
'test',
|
|
15
|
+
'types',
|
|
13
16
|
]);
|
|
14
17
|
|
|
15
|
-
export
|
|
18
|
+
export interface FormatCommitMessageOptions {
|
|
19
|
+
wrapBody?: (paragraph: string) => string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ValidateCommitMessageOptions {
|
|
23
|
+
validScopes?: ReadonlySet<string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function validateCommitMessage(message: string, options: ValidateCommitMessageOptions = {}): string | null {
|
|
16
27
|
const subject = message.split('\n', 1)[0]?.trim() ?? '';
|
|
17
28
|
if (!subject) {
|
|
18
29
|
return 'Commit message subject is empty.';
|
|
19
30
|
}
|
|
20
|
-
if (
|
|
31
|
+
if (isGitGeneratedSubject(subject)) {
|
|
21
32
|
return null;
|
|
22
33
|
}
|
|
23
|
-
const match = /^(?<type>[a-z]+)(\([a-z0-9._
|
|
34
|
+
const match = /^(?<type>[a-z]+)(\((?<scope>[a-z0-9._/@-]+(?:,[a-z0-9._/@-]+)*)\))?(?<breaking>!)?: .+$/.exec(subject);
|
|
24
35
|
const type = match?.groups?.type;
|
|
25
|
-
if (type
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
return `Invalid conventional commit subject: ${subject}
|
|
36
|
+
if (!type || !validCommitTypes.has(type)) {
|
|
37
|
+
return `Invalid conventional commit subject: ${subject}
|
|
29
38
|
|
|
30
39
|
Expected examples:
|
|
31
40
|
feat(statebus-core): add optimistic transactions
|
|
32
41
|
fix(money): round negative amounts consistently
|
|
33
42
|
chore(release): publish 1.2.3
|
|
34
43
|
feat!: remove deprecated API`;
|
|
44
|
+
}
|
|
45
|
+
const scope = match.groups?.scope;
|
|
46
|
+
if (scope && options.validScopes) {
|
|
47
|
+
const invalidScopes = scope.split(',').filter((entry) => !options.validScopes?.has(entry));
|
|
48
|
+
if (invalidScopes.length > 0) {
|
|
49
|
+
return `Invalid conventional commit scope: ${invalidScopes.join(',')}
|
|
50
|
+
|
|
51
|
+
Use package.json nx.name values, for example:
|
|
52
|
+
feat(statebus-core): add optimistic transactions
|
|
53
|
+
fix(money): round negative amounts consistently`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatCommitMessage(message: string, options: FormatCommitMessageOptions = {}): string {
|
|
60
|
+
const wrapBody = options.wrapBody ?? wrapBodyWithFmt;
|
|
61
|
+
const normalized = message.replace(/\r\n?/g, '\n');
|
|
62
|
+
const lines = normalized.split('\n');
|
|
63
|
+
const subject = lines.shift()?.trimEnd() ?? '';
|
|
64
|
+
const formatted = [subject];
|
|
65
|
+
let paragraph: string[] = [];
|
|
66
|
+
let inFence = false;
|
|
67
|
+
|
|
68
|
+
const flushParagraph = () => {
|
|
69
|
+
if (paragraph.length === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
formatted.push(wrapBody(paragraph.join('\n')).trimEnd());
|
|
73
|
+
paragraph = [];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
const trimmedEnd = line.trimEnd();
|
|
78
|
+
if (trimmedEnd.startsWith('```') || trimmedEnd.startsWith('~~~')) {
|
|
79
|
+
flushParagraph();
|
|
80
|
+
inFence = !inFence;
|
|
81
|
+
formatted.push(trimmedEnd);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (inFence || shouldPreserveLine(trimmedEnd)) {
|
|
85
|
+
flushParagraph();
|
|
86
|
+
formatted.push(trimmedEnd);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (trimmedEnd === '') {
|
|
90
|
+
flushParagraph();
|
|
91
|
+
formatted.push('');
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
paragraph.push(trimmedEnd);
|
|
95
|
+
}
|
|
96
|
+
flushParagraph();
|
|
97
|
+
|
|
98
|
+
while (formatted.length > 1 && formatted.at(-1) === '') {
|
|
99
|
+
formatted.pop();
|
|
100
|
+
}
|
|
101
|
+
return `${formatted.join('\n')}\n`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function wrapBodyWithFmt(paragraph: string): string {
|
|
105
|
+
const result = spawnSync('fmt', ['-w', '72'], { input: `${paragraph}\n`, encoding: 'utf8' });
|
|
106
|
+
if (result.error) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`fmt is required to format commit messages. Install it through devenv and retry.\n${result.error.message}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (result.status !== 0) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
[result.stderr.trim(), result.stdout.trim()].filter(Boolean).join('\n') ||
|
|
114
|
+
'fmt failed to wrap commit message body.',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return result.stdout;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function shouldPreserveLine(line: string): boolean {
|
|
121
|
+
return (
|
|
122
|
+
line.startsWith('#') ||
|
|
123
|
+
line.startsWith('>') ||
|
|
124
|
+
/^\s/.test(line) ||
|
|
125
|
+
/^[-*+]\s+/.test(line) ||
|
|
126
|
+
/^\d+\.\s+/.test(line) ||
|
|
127
|
+
/^https?:\/\//.test(line) ||
|
|
128
|
+
/^[A-Za-z][A-Za-z0-9-]*: /.test(line) ||
|
|
129
|
+
/^BREAKING CHANGE: /.test(line)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isGitGeneratedSubject(subject: string): boolean {
|
|
134
|
+
return /^(Merge|Revert ")/.test(subject) || /^(fixup|squash)! /.test(subject);
|
|
35
135
|
}
|
package/src/monorepo/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { appendFileSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { appendFileSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { $ } from 'bun';
|
|
3
|
-
import { decode } from '../lib/run.js';
|
|
4
|
-
import { escapeRegex, getWorkspacePatterns, listReleasePackages } from '../lib/workspace.js';
|
|
5
|
-
import { validateCommitMessage } from './commit-msg.js';
|
|
3
|
+
import { decode, run } from '../lib/run.js';
|
|
4
|
+
import { escapeRegex, getWorkspacePackages, getWorkspacePatterns, listReleasePackages } from '../lib/workspace.js';
|
|
5
|
+
import { formatCommitMessage, validateCommitMessage } from './commit-msg.js';
|
|
6
6
|
import { applyWorkspaceGitConfig } from './git-config.js';
|
|
7
7
|
import { syncBunLockfileVersions } from './lockfile.js';
|
|
8
8
|
import { applyManagedFiles, printResults } from './managed-files.js';
|
|
9
|
-
import { validatePublicTags } from './package-policy.js';
|
|
9
|
+
import { listValidCommitScopes, validatePublicTags } from './package-policy.js';
|
|
10
10
|
import { runInitPacks, runValidatePacks } from './packs/index.js';
|
|
11
11
|
import { syncRootRuntimeVersions } from './runtime.js';
|
|
12
12
|
|
|
@@ -18,6 +18,12 @@ export interface InitOptions {
|
|
|
18
18
|
export interface ValidateOptions {
|
|
19
19
|
failFast?: boolean;
|
|
20
20
|
onlyIfNewWorkspacePackage?: boolean;
|
|
21
|
+
fix?: boolean;
|
|
22
|
+
verbose?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ValidateCommitMessageOptions {
|
|
26
|
+
fix?: boolean;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
export interface ListReleasePackagesOptions {
|
|
@@ -25,6 +31,19 @@ export interface ListReleasePackagesOptions {
|
|
|
25
31
|
githubOutput?: string;
|
|
26
32
|
}
|
|
27
33
|
|
|
34
|
+
export interface SetupTestTracingOptions {
|
|
35
|
+
all?: boolean;
|
|
36
|
+
projects?: string;
|
|
37
|
+
opContextExport?: string;
|
|
38
|
+
tracerModule?: string;
|
|
39
|
+
dryRun?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SetupTestTracingShell {
|
|
43
|
+
run(command: string, args: string[], cwd: string): Promise<void>;
|
|
44
|
+
log(message: string): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
export async function initMonorepo(root: string, options: InitOptions): Promise<void> {
|
|
29
48
|
if (options.runtimeOnly) {
|
|
30
49
|
await syncRootRuntimeVersions(root);
|
|
@@ -39,13 +58,20 @@ export async function validateMonorepo(root: string, options: ValidateOptions =
|
|
|
39
58
|
if (options.onlyIfNewWorkspacePackage && !(await hasNewWorkspacePackage(root))) {
|
|
40
59
|
return;
|
|
41
60
|
}
|
|
42
|
-
const
|
|
43
|
-
if (failures > 0) {
|
|
44
|
-
const
|
|
45
|
-
|
|
61
|
+
const result = await runValidatePacks({ root, syncRuntime: false, verbose: options.verbose === true }, options);
|
|
62
|
+
if (result.failures > 0) {
|
|
63
|
+
const checkNoun = result.failedChecks === 1 ? 'check' : 'checks';
|
|
64
|
+
const problemNoun = result.failures === 1 ? 'problem' : 'problems';
|
|
65
|
+
throw new Error(
|
|
66
|
+
`\n🔴 Monorepo validation failed: ${result.failedChecks} ${checkNoun} failed with ${result.failures} ${problemNoun}.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (options.verbose) {
|
|
70
|
+
console.log('\n== summary ==');
|
|
71
|
+
console.log('Monorepo configuration is valid.');
|
|
72
|
+
} else {
|
|
73
|
+
console.log('🟢 Monorepo configuration is valid.');
|
|
46
74
|
}
|
|
47
|
-
console.log('\n== summary ==');
|
|
48
|
-
console.log('Monorepo configuration is valid.');
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
export function updateManagedFiles(root: string): void {
|
|
@@ -64,20 +90,31 @@ export function diffManagedFiles(root: string): void {
|
|
|
64
90
|
printResults(applyManagedFiles(root, 'diff'));
|
|
65
91
|
}
|
|
66
92
|
|
|
67
|
-
export function validateCommitMessageFile(
|
|
93
|
+
export function validateCommitMessageFile(
|
|
94
|
+
path: string | undefined,
|
|
95
|
+
options: ValidateCommitMessageOptions = {},
|
|
96
|
+
root = process.cwd(),
|
|
97
|
+
): void {
|
|
68
98
|
if (!path) {
|
|
69
99
|
throw new Error('Usage: smoo monorepo validate-commit-msg <commit-msg-file>');
|
|
70
100
|
}
|
|
71
|
-
|
|
72
|
-
|
|
101
|
+
let message = readFileSync(path, 'utf8');
|
|
102
|
+
if (options.fix) {
|
|
103
|
+
const formatted = formatCommitMessage(message);
|
|
104
|
+
if (formatted !== message) {
|
|
105
|
+
writeFileSync(path, formatted);
|
|
106
|
+
message = formatted;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const error = validateCommitMessage(message, { validScopes: listValidCommitScopes(root) });
|
|
73
110
|
if (error) {
|
|
74
111
|
throw new Error(error);
|
|
75
112
|
}
|
|
76
113
|
}
|
|
77
114
|
|
|
78
|
-
export function
|
|
115
|
+
export function listReleaseProjectNamesForNx(root: string, options: ListReleasePackagesOptions = {}): string {
|
|
79
116
|
const packages = listReleasePackages(root)
|
|
80
|
-
.map((pkg) => pkg.
|
|
117
|
+
.map((pkg) => pkg.projectName)
|
|
81
118
|
.join(',');
|
|
82
119
|
if (!packages && options.failEmpty) {
|
|
83
120
|
throw new Error('No owned release packages found.');
|
|
@@ -94,8 +131,97 @@ export function validatePublicPackageTags(root: string): void {
|
|
|
94
131
|
}
|
|
95
132
|
}
|
|
96
133
|
|
|
134
|
+
export async function setupTestTracing(
|
|
135
|
+
root: string,
|
|
136
|
+
options: SetupTestTracingOptions,
|
|
137
|
+
shell: SetupTestTracingShell = defaultSetupTestTracingShell,
|
|
138
|
+
): Promise<void> {
|
|
139
|
+
const selectedPackages = selectTestTracingPackages(root, options);
|
|
140
|
+
const opContextExport = options.opContextExport ?? 'opContext';
|
|
141
|
+
const tracerModule = options.tracerModule ?? '@smoothbricks/lmao/testing/bun';
|
|
142
|
+
|
|
143
|
+
if (selectedPackages.length === 0) {
|
|
144
|
+
throw new Error('No workspace packages matched LMAO test tracing setup selection.');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const pkg of selectedPackages) {
|
|
148
|
+
const args = [
|
|
149
|
+
'g',
|
|
150
|
+
'@smoothbricks/nx-plugin:bun-test-tracing',
|
|
151
|
+
'--project',
|
|
152
|
+
pkg.projectName,
|
|
153
|
+
'--opContextModule',
|
|
154
|
+
pkg.name,
|
|
155
|
+
'--opContextExport',
|
|
156
|
+
opContextExport,
|
|
157
|
+
'--tracerModule',
|
|
158
|
+
tracerModule,
|
|
159
|
+
];
|
|
160
|
+
const commandPreview = `nx ${args.join(' ')}`;
|
|
161
|
+
if (options.dryRun) {
|
|
162
|
+
shell.log(`would run ${commandPreview}`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
shell.log(`running ${commandPreview}`);
|
|
166
|
+
await shell.run('nx', args, root);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
97
170
|
export { applyWorkspaceGitConfig, syncBunLockfileVersions };
|
|
98
171
|
|
|
172
|
+
const defaultSetupTestTracingShell: SetupTestTracingShell = {
|
|
173
|
+
run,
|
|
174
|
+
log(message) {
|
|
175
|
+
console.log(message);
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
function selectTestTracingPackages(root: string, options: SetupTestTracingOptions) {
|
|
180
|
+
const packages = getWorkspacePackages(root);
|
|
181
|
+
const requested = splitCommaList(options.projects);
|
|
182
|
+
|
|
183
|
+
if (options.all && requested.length > 0) {
|
|
184
|
+
throw new Error('Use either --all or --projects, not both.');
|
|
185
|
+
}
|
|
186
|
+
if (!options.all && requested.length === 0) {
|
|
187
|
+
throw new Error('Pass --all or --projects <projects> to select packages for LMAO test tracing setup.');
|
|
188
|
+
}
|
|
189
|
+
if (options.all) {
|
|
190
|
+
return packages;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const bySelector = new Map<string, (typeof packages)[number]>();
|
|
194
|
+
for (const pkg of packages) {
|
|
195
|
+
bySelector.set(pkg.projectName, pkg);
|
|
196
|
+
bySelector.set(pkg.name, pkg);
|
|
197
|
+
bySelector.set(pkg.path, pkg);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const selected = [];
|
|
201
|
+
const missing = [];
|
|
202
|
+
for (const selector of requested) {
|
|
203
|
+
const pkg = bySelector.get(selector);
|
|
204
|
+
if (pkg) {
|
|
205
|
+
selected.push(pkg);
|
|
206
|
+
} else {
|
|
207
|
+
missing.push(selector);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (missing.length > 0) {
|
|
212
|
+
throw new Error(`Unknown workspace package selection for LMAO test tracing setup: ${missing.join(', ')}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return selected;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function splitCommaList(value: string | undefined): string[] {
|
|
219
|
+
return (value ?? '')
|
|
220
|
+
.split(',')
|
|
221
|
+
.map((entry) => entry.trim())
|
|
222
|
+
.filter(Boolean);
|
|
223
|
+
}
|
|
224
|
+
|
|
99
225
|
async function hasNewWorkspacePackage(root: string): Promise<boolean> {
|
|
100
226
|
const result = await $`git diff --cached --name-only --diff-filter=A -- ${'*/package.json'}`
|
|
101
227
|
.cwd(root)
|
package/src/monorepo/lockfile.ts
CHANGED
|
@@ -6,6 +6,15 @@ export interface SyncBunLockfileVersionsOptions {
|
|
|
6
6
|
log?: boolean;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
// Temporary Bun workaround. Delete this sync function, validateBunLockfileVersions,
|
|
10
|
+
// the `smoo monorepo sync-bun-lockfile-versions` command, and the matching Nx
|
|
11
|
+
// versionActions hook once supported Bun versions stop leaving workspace package
|
|
12
|
+
// versions stale in bun.lock after manifest bumps. Until then, `bun pm pack`
|
|
13
|
+
// rewrites `workspace:*` dependencies using those stale lockfile versions instead
|
|
14
|
+
// of the current package.json versions. Track removal against:
|
|
15
|
+
// - https://github.com/oven-sh/bun/issues/18906
|
|
16
|
+
// - https://github.com/oven-sh/bun/issues/20477
|
|
17
|
+
// - https://github.com/oven-sh/bun/issues/20829
|
|
9
18
|
export function syncBunLockfileVersions(root: string, options: SyncBunLockfileVersionsOptions = {}): number {
|
|
10
19
|
const log = options.log ?? true;
|
|
11
20
|
const lockfilePath = join(root, 'bun.lock');
|
package/src/monorepo/nx-sync.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { runStatus } from '../lib/run.js';
|
|
2
2
|
|
|
3
|
-
export async function
|
|
4
|
-
const status = await runStatus('nx', ['sync
|
|
3
|
+
export async function fixNxSync(root: string, verbose = false): Promise<void> {
|
|
4
|
+
const status = await runStatus('nx', ['sync'], root, !verbose);
|
|
5
|
+
if (status !== 0) {
|
|
6
|
+
throw new Error(`nx sync failed with exit code ${status}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function validateNxSync(root: string, verbose = false): Promise<number> {
|
|
11
|
+
const status = await runStatus('nx', ['sync:check'], root, !verbose);
|
|
5
12
|
return status === 0 ? 0 : 1;
|
|
6
13
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { fixPackageHygiene, type PackageHygieneShell, validatePackageHygiene } from './package-hygiene.js';
|
|
3
|
+
|
|
4
|
+
describe('package hygiene', () => {
|
|
5
|
+
it('runs sherif in autofix mode and selects highest dependency versions', async () => {
|
|
6
|
+
const shell = new RecordingShell();
|
|
7
|
+
|
|
8
|
+
await fixPackageHygiene('/repo', shell);
|
|
9
|
+
|
|
10
|
+
expect(shell.statuses).toEqual([{ command: 'sherif', args: ['-f', '--select', 'highest'], cwd: '/repo' }]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('validates sherif warnings as failures', async () => {
|
|
14
|
+
const shell = new RecordingShell();
|
|
15
|
+
|
|
16
|
+
await validatePackageHygiene('/repo', shell);
|
|
17
|
+
|
|
18
|
+
expect(shell.statuses).toEqual([{ command: 'sherif', args: ['--fail-on-warnings'], cwd: '/repo' }]);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
class RecordingShell implements PackageHygieneShell {
|
|
23
|
+
readonly runs: { command: string; args: string[]; cwd: string }[] = [];
|
|
24
|
+
readonly statuses: { command: string; args: string[]; cwd: string }[] = [];
|
|
25
|
+
|
|
26
|
+
async run(command: string, args: string[], cwd: string): Promise<void> {
|
|
27
|
+
this.runs.push({ command, args, cwd });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async runStatus(command: string, args: string[], cwd: string): Promise<number> {
|
|
31
|
+
this.statuses.push({ command, args, cwd });
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
import { run, runStatus } from '../lib/run.js';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
|
|
3
|
+
export interface PackageHygieneShell {
|
|
4
|
+
run(command: string, args: string[], cwd: string): Promise<void>;
|
|
5
|
+
runStatus(command: string, args: string[], cwd: string, quiet?: boolean): Promise<number>;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
const defaultShell: PackageHygieneShell = { run, runStatus };
|
|
9
|
+
|
|
10
|
+
export async function fixPackageHygiene(
|
|
11
|
+
root: string,
|
|
12
|
+
verboseOrShell: boolean | PackageHygieneShell = false,
|
|
13
|
+
maybeShell: PackageHygieneShell = defaultShell,
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const verbose = typeof verboseOrShell === 'boolean' ? verboseOrShell : false;
|
|
16
|
+
const shell = typeof verboseOrShell === 'boolean' ? maybeShell : verboseOrShell;
|
|
17
|
+
const status = await shell.runStatus('sherif', ['-f', '--select', 'highest'], root, !verbose);
|
|
18
|
+
if (status !== 0) {
|
|
19
|
+
throw new Error(`sherif -f --select highest failed with exit code ${status}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function validatePackageHygiene(
|
|
24
|
+
root: string,
|
|
25
|
+
verboseOrShell: boolean | PackageHygieneShell = false,
|
|
26
|
+
maybeShell: PackageHygieneShell = defaultShell,
|
|
27
|
+
): Promise<number> {
|
|
28
|
+
const verbose = typeof verboseOrShell === 'boolean' ? verboseOrShell : false;
|
|
29
|
+
const shell = typeof verboseOrShell === 'boolean' ? maybeShell : verboseOrShell;
|
|
30
|
+
const status = await shell.runStatus('sherif', ['--fail-on-warnings'], root, !verbose);
|
|
9
31
|
if (status !== 0) {
|
|
10
32
|
console.error('sherif package hygiene validation failed');
|
|
11
33
|
return 1;
|