@smoothbricks/cli 0.1.1 → 0.2.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 (117) hide show
  1. package/README.md +155 -30
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +91 -7
  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/lib/cli-package.d.ts +4 -0
  8. package/dist/lib/cli-package.d.ts.map +1 -0
  9. package/dist/lib/cli-package.js +15 -0
  10. package/dist/lib/run.d.ts +5 -0
  11. package/dist/lib/run.d.ts.map +1 -1
  12. package/dist/lib/run.js +13 -0
  13. package/dist/lib/workspace.d.ts +15 -1
  14. package/dist/lib/workspace.d.ts.map +1 -1
  15. package/dist/lib/workspace.js +25 -2
  16. package/dist/monorepo/commit-msg.d.ts +8 -1
  17. package/dist/monorepo/commit-msg.d.ts.map +1 -1
  18. package/dist/monorepo/commit-msg.js +85 -7
  19. package/dist/monorepo/index.d.ts +19 -2
  20. package/dist/monorepo/index.d.ts.map +1 -1
  21. package/dist/monorepo/index.js +104 -16
  22. package/dist/monorepo/managed-files.js +1 -1
  23. package/dist/monorepo/nx-sync.d.ts +2 -1
  24. package/dist/monorepo/nx-sync.d.ts.map +1 -1
  25. package/dist/monorepo/nx-sync.js +8 -2
  26. package/dist/monorepo/package-hygiene.d.ts +6 -2
  27. package/dist/monorepo/package-hygiene.d.ts.map +1 -1
  28. package/dist/monorepo/package-hygiene.js +12 -4
  29. package/dist/monorepo/package-policy.d.ts +22 -2
  30. package/dist/monorepo/package-policy.d.ts.map +1 -1
  31. package/dist/monorepo/package-policy.js +1167 -8
  32. package/dist/monorepo/packed-package.d.ts +3 -0
  33. package/dist/monorepo/packed-package.d.ts.map +1 -1
  34. package/dist/monorepo/packed-package.js +68 -36
  35. package/dist/monorepo/packs/index.d.ts +20 -1
  36. package/dist/monorepo/packs/index.d.ts.map +1 -1
  37. package/dist/monorepo/packs/index.js +238 -19
  38. package/dist/monorepo/publish-workflow.d.ts +0 -4
  39. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  40. package/dist/monorepo/publish-workflow.js +5 -12
  41. package/dist/monorepo/tool-validation.d.ts +22 -0
  42. package/dist/monorepo/tool-validation.d.ts.map +1 -0
  43. package/dist/monorepo/tool-validation.js +399 -0
  44. package/dist/nx/index.d.ts +24 -0
  45. package/dist/nx/index.d.ts.map +1 -0
  46. package/dist/nx/index.js +94 -0
  47. package/dist/release/bootstrap-npm-packages.d.ts +19 -0
  48. package/dist/release/bootstrap-npm-packages.d.ts.map +1 -0
  49. package/dist/release/bootstrap-npm-packages.js +58 -0
  50. package/dist/release/candidates.d.ts +11 -0
  51. package/dist/release/candidates.d.ts.map +1 -0
  52. package/dist/release/candidates.js +181 -0
  53. package/dist/release/core.d.ts +1 -0
  54. package/dist/release/core.d.ts.map +1 -1
  55. package/dist/release/github-release.js +2 -2
  56. package/dist/release/index.d.ts +25 -0
  57. package/dist/release/index.d.ts.map +1 -1
  58. package/dist/release/index.js +247 -49
  59. package/dist/release/npm-auth.d.ts +0 -1
  60. package/dist/release/npm-auth.d.ts.map +1 -1
  61. package/dist/release/npm-auth.js +4 -16
  62. package/dist/release/orchestration.d.ts +2 -1
  63. package/dist/release/orchestration.d.ts.map +1 -1
  64. package/dist/release/orchestration.js +9 -1
  65. package/dist/release/publish-plan.js +1 -1
  66. package/managed/raw/git-format-staged.yml +54 -0
  67. package/managed/raw/tooling/git-hooks/commit-msg.sh +1 -1
  68. package/managed/templates/github/actions/setup-devenv/action.yml +1 -7
  69. package/managed/templates/github/workflows/ci.yml +1 -0
  70. package/package.json +15 -3
  71. package/src/cli.ts +134 -20
  72. package/src/devenv/index.test.ts +49 -0
  73. package/src/devenv/index.ts +38 -0
  74. package/src/lib/cli-package.ts +18 -0
  75. package/src/lib/run.ts +19 -0
  76. package/src/lib/workspace.ts +41 -2
  77. package/src/monorepo/__tests__/publish-workflow.test.ts +16 -9
  78. package/src/monorepo/commit-msg.test.ts +45 -0
  79. package/src/monorepo/commit-msg.ts +107 -7
  80. package/src/monorepo/index.ts +142 -16
  81. package/src/monorepo/managed-files.ts +1 -1
  82. package/src/monorepo/nx-sync.ts +9 -2
  83. package/src/monorepo/package-hygiene.test.ts +34 -0
  84. package/src/monorepo/package-hygiene.ts +26 -4
  85. package/src/monorepo/package-policy.test.ts +902 -0
  86. package/src/monorepo/package-policy.ts +1393 -7
  87. package/src/monorepo/packed-package.ts +89 -37
  88. package/src/monorepo/packs/index.test.ts +189 -0
  89. package/src/monorepo/packs/index.ts +311 -20
  90. package/src/monorepo/publish-workflow.ts +8 -16
  91. package/src/monorepo/setup-test-tracing.test.ts +120 -0
  92. package/src/monorepo/tool-validation.test.ts +261 -0
  93. package/src/monorepo/tool-validation.ts +462 -0
  94. package/src/nx/index.test.ts +64 -0
  95. package/src/nx/index.ts +119 -0
  96. package/src/release/__tests__/bootstrap-npm-packages.test.ts +127 -0
  97. package/src/release/__tests__/candidates.test.ts +217 -0
  98. package/src/release/__tests__/core-properties.test.ts +4 -4
  99. package/src/release/__tests__/core-scenarios.test.ts +15 -5
  100. package/src/release/__tests__/core.test.ts +2 -2
  101. package/src/release/__tests__/fixture-repo.test.ts +22 -13
  102. package/src/release/__tests__/github-release.test.ts +13 -8
  103. package/src/release/__tests__/helpers/fixture-repo.ts +1 -0
  104. package/src/release/__tests__/npm-auth.test.ts +12 -21
  105. package/src/release/__tests__/orchestration.test.ts +51 -7
  106. package/src/release/__tests__/publish-plan.test.ts +9 -3
  107. package/src/release/__tests__/retag-unpublished.test.ts +1 -1
  108. package/src/release/__tests__/trust-publisher.test.ts +133 -0
  109. package/src/release/bootstrap-npm-packages.ts +88 -0
  110. package/src/release/candidates.ts +225 -0
  111. package/src/release/core.ts +1 -0
  112. package/src/release/github-release.ts +2 -2
  113. package/src/release/index.ts +346 -56
  114. package/src/release/npm-auth.ts +4 -33
  115. package/src/release/orchestration.ts +12 -2
  116. package/src/release/publish-plan.ts +1 -1
  117. package/managed/raw/.git-format-staged.yml +0 -47
@@ -0,0 +1,261 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
2
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import {
6
+ applyRootDevDependencyDefaults,
7
+ applyToolConfigDefaults,
8
+ applyToolingPackageDefaults,
9
+ readToolContext,
10
+ validateToolConfig,
11
+ } from './tool-validation.js';
12
+
13
+ const registryVersions: Record<string, string[]> = {
14
+ '@biomejs/biome': ['2.3.5'],
15
+ '@nx/js': ['22.5.4'],
16
+ '@smoothbricks/nx-plugin': ['0.0.1'],
17
+ eslint: ['9.39.1'],
18
+ 'eslint-stdout': ['1.1.1', '1.1.2'],
19
+ nx: ['22.5.4'],
20
+ prettier: ['3.6.1'],
21
+ typescript: ['5.9.3'],
22
+ };
23
+
24
+ const realFetch = globalThis.fetch;
25
+
26
+ describe('tool configuration validation', () => {
27
+ beforeEach(() => {
28
+ globalThis.fetch = mockRegistryFetch as typeof fetch;
29
+ });
30
+
31
+ afterEach(() => {
32
+ globalThis.fetch = realFetch;
33
+ });
34
+
35
+ it('fixes root, tooling, and devenv tool declarations', async () => {
36
+ const root = await mkdtemp(join(tmpdir(), 'smoo-tool-validation-'));
37
+ try {
38
+ await writeJson(join(root, 'package.json'), {
39
+ name: '@smoothbricks/codebase',
40
+ version: '0.0.0',
41
+ private: true,
42
+ devDependencies: {
43
+ '@smoothbricks/cli': 'workspace:*',
44
+ },
45
+ });
46
+ const devenvPath = join(root, 'tooling/direnv/devenv.nix');
47
+ await mkdir(dirname(devenvPath), { recursive: true });
48
+ await writeFile(
49
+ devenvPath,
50
+ `{
51
+ pkgs,
52
+ ...
53
+ }: {
54
+ packages = with pkgs; [
55
+ ];
56
+ }
57
+ `,
58
+ );
59
+
60
+ expect(validateToolConfig(root)).toBeGreaterThan(0);
61
+ await applyToolConfigDefaults(root);
62
+ expect(validateToolConfig(root)).toBe(0);
63
+
64
+ const rootPackage = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
65
+ const toolingPackage = JSON.parse(await readFile(join(root, 'tooling/package.json'), 'utf8'));
66
+ const devenv = await readFile(join(root, 'tooling/direnv/devenv.nix'), 'utf8');
67
+ expect(rootPackage.devDependencies['@smoothbricks/cli']).toBeUndefined();
68
+ expect(rootPackage.devDependencies['@smoothbricks/nx-plugin']).toBe('workspace:*');
69
+ expect(rootPackage.devDependencies['eslint-stdout']).toBe('workspace:*');
70
+ expect(rootPackage.devDependencies.nx).toBe('22.5.4');
71
+ expect(rootPackage.workspaces).toContain('tooling');
72
+ expect(toolingPackage.name).toBe('@smoothbricks/tooling');
73
+ expect(toolingPackage.dependencies['@smoothbricks/cli']).toBe('workspace:*');
74
+ expect(devenv).toContain('nodejs_latest');
75
+ expect(devenv).toContain('coreutils');
76
+ expect(devenv).toContain('git-format-staged');
77
+ } finally {
78
+ await rm(root, { recursive: true, force: true });
79
+ }
80
+ });
81
+
82
+ it('accepts newer dependency versions above the policy floor', async () => {
83
+ const root = await mkdtemp(join(tmpdir(), 'smoo-tool-validation-'));
84
+ try {
85
+ await writeJson(join(root, 'package.json'), {
86
+ name: '@fixture/app',
87
+ version: '0.0.0',
88
+ private: true,
89
+ workspaces: ['packages/*', 'tooling'],
90
+ devDependencies: {
91
+ '@biomejs/biome': '^3.0.0',
92
+ '@nx/js': '22.6.0',
93
+ '@smoothbricks/nx-plugin': '^0.1.0',
94
+ '@types/bun': '1.3.99',
95
+ eslint: '^10.0.0',
96
+ 'eslint-stdout': await currentEslintStdoutRange(),
97
+ nx: '23.0.0',
98
+ prettier: '^3.7.0',
99
+ typescript: '^6.0.0',
100
+ },
101
+ });
102
+ await writeJson(join(root, 'tooling/package.json'), {
103
+ name: '@fixture/tooling',
104
+ private: true,
105
+ dependencies: { '@smoothbricks/cli': await currentCliRange() },
106
+ });
107
+ const devenvPath = join(root, 'tooling/direnv/devenv.nix');
108
+ await mkdir(dirname(devenvPath), { recursive: true });
109
+ await writeFile(
110
+ devenvPath,
111
+ `{
112
+ pkgs,
113
+ ...
114
+ }: {
115
+ packages = with pkgs; [
116
+ nodejs_latest
117
+ bun
118
+ git
119
+ git-format-staged
120
+ jq
121
+ alejandra
122
+ coreutils
123
+ gnutar
124
+ ];
125
+ }
126
+ `,
127
+ );
128
+
129
+ expect(validateToolConfig(root)).toBe(0);
130
+ } finally {
131
+ await rm(root, { recursive: true, force: true });
132
+ }
133
+ });
134
+
135
+ it('uses workspace smoo in the SmoothBricks codebase repo', async () => {
136
+ const root = await mkdtemp(join(tmpdir(), 'smoo-tool-validation-'));
137
+ try {
138
+ await writeJson(join(root, 'package.json'), {
139
+ name: '@smoothbricks/codebase',
140
+ version: '0.0.0',
141
+ private: true,
142
+ workspaces: ['packages/*', 'tooling'],
143
+ });
144
+ applyToolingPackageDefaults(root, readToolContext(root).policy);
145
+
146
+ const toolingPackage = JSON.parse(await readFile(join(root, 'tooling/package.json'), 'utf8'));
147
+ expect(toolingPackage.name).toBe('@smoothbricks/tooling');
148
+ expect(toolingPackage.dependencies['@smoothbricks/cli']).toBe('workspace:*');
149
+ } finally {
150
+ await rm(root, { recursive: true, force: true });
151
+ }
152
+ });
153
+
154
+ it('uses published smoo and eslint formatter ranges in user repos', async () => {
155
+ const root = await mkdtemp(join(tmpdir(), 'smoo-tool-validation-'));
156
+ try {
157
+ await writeJson(join(root, 'package.json'), {
158
+ name: '@fixture/app',
159
+ version: '0.0.0',
160
+ private: true,
161
+ workspaces: ['packages/*', 'tooling'],
162
+ devDependencies: {
163
+ '@biomejs/biome': '^3.0.0',
164
+ '@nx/js': '22.6.0',
165
+ eslint: '^10.0.0',
166
+ nx: '23.0.0',
167
+ prettier: '^3.7.0',
168
+ typescript: '^6.0.0',
169
+ },
170
+ });
171
+
172
+ const context = readToolContext(root);
173
+ await applyRootDevDependencyDefaults(root, context);
174
+ applyToolingPackageDefaults(root, context.policy);
175
+
176
+ const rootPackage = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
177
+ const toolingPackage = JSON.parse(await readFile(join(root, 'tooling/package.json'), 'utf8'));
178
+ expect(rootPackage.devDependencies['eslint-stdout']).toBe(await currentEslintStdoutRange());
179
+ expect(rootPackage.devDependencies['@smoothbricks/nx-plugin']).toBe(await currentNxPluginRange());
180
+ expect(toolingPackage.name).toBe('@fixture/tooling');
181
+ expect(toolingPackage.dependencies['@smoothbricks/cli']).toBe(await currentCliRange());
182
+ } finally {
183
+ await rm(root, { recursive: true, force: true });
184
+ }
185
+ });
186
+ });
187
+
188
+ async function currentCliRange(): Promise<string> {
189
+ const pkg = JSON.parse(await readFile(new URL('../../package.json', import.meta.url), 'utf8'));
190
+ return `^${pkg.version}`;
191
+ }
192
+
193
+ async function currentEslintStdoutRange(): Promise<string> {
194
+ const pkg = JSON.parse(await readFile(new URL('../../../eslint-stdout/package.json', import.meta.url), 'utf8'));
195
+ return `^${latestPatchVersion('eslint-stdout', pkg.version) ?? pkg.version}`;
196
+ }
197
+
198
+ function mockRegistryFetch(input: Parameters<typeof fetch>[0]): Promise<Response> {
199
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
200
+ const packageName = decodeURIComponent(new URL(url).pathname.slice(1));
201
+ const versions = registryVersions[packageName];
202
+ if (!versions) {
203
+ return Promise.resolve(new Response('{}', { status: 404 }));
204
+ }
205
+ return Promise.resolve(
206
+ new Response(JSON.stringify({ versions: Object.fromEntries(versions.map((version) => [version, {}])) }), {
207
+ status: 200,
208
+ headers: { 'content-type': 'application/json' },
209
+ }),
210
+ );
211
+ }
212
+
213
+ function latestPatchVersion(packageName: string, version: string): string | null {
214
+ const [major, minor] = version.split('.');
215
+ if (!major || !minor) {
216
+ return null;
217
+ }
218
+ return (
219
+ (registryVersions[packageName] ?? [])
220
+ .filter((candidate) => candidate.startsWith(`${major}.${minor}.`))
221
+ .sort((left, right) => compareDotVersions(right, left))[0] ?? null
222
+ );
223
+ }
224
+
225
+ function compareDotVersions(left: string, right: string): number {
226
+ const leftParts = left.split('.').map(Number);
227
+ const rightParts = right.split('.').map(Number);
228
+ for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
229
+ const diff = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
230
+ if (diff !== 0) {
231
+ return diff;
232
+ }
233
+ }
234
+ return 0;
235
+ }
236
+
237
+ async function currentNxPluginRange(): Promise<string> {
238
+ return `^${latestRegistryVersion('@smoothbricks/nx-plugin')}`;
239
+ }
240
+
241
+ function latestRegistryVersion(packageName: string): string {
242
+ const versions = registryVersions[packageName];
243
+ if (!versions) {
244
+ throw new Error(`missing mocked registry versions for ${packageName}`);
245
+ }
246
+ let latest = versions[0];
247
+ for (const version of versions) {
248
+ if (!latest || compareDotVersions(version, latest) > 0) {
249
+ latest = version;
250
+ }
251
+ }
252
+ if (!latest) {
253
+ throw new Error(`empty mocked registry versions for ${packageName}`);
254
+ }
255
+ return latest;
256
+ }
257
+
258
+ async function writeJson(path: string, value: Record<string, unknown>): Promise<void> {
259
+ await mkdir(dirname(path), { recursive: true });
260
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
261
+ }