@smoothbricks/cli 0.10.2 → 0.10.4

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 (133) hide show
  1. package/README.md +31 -23
  2. package/dist/bun/preload.d.ts +13 -0
  3. package/dist/bun/preload.d.ts.map +1 -0
  4. package/dist/bun/preload.js +12 -0
  5. package/dist/cli.js +36 -4
  6. package/dist/generate/index.d.ts.map +1 -1
  7. package/dist/generate/index.js +0 -6
  8. package/dist/github-ci/index.d.ts +17 -2
  9. package/dist/github-ci/index.d.ts.map +1 -1
  10. package/dist/github-ci/index.js +192 -26
  11. package/dist/github-ci/outputs.d.ts +22 -0
  12. package/dist/github-ci/outputs.d.ts.map +1 -0
  13. package/dist/github-ci/outputs.js +648 -0
  14. package/dist/lib/cli-package.js +2 -2
  15. package/dist/lib/json.d.ts +118 -11
  16. package/dist/lib/json.d.ts.map +1 -1
  17. package/dist/lib/json.js +317 -29
  18. package/dist/lib/workspace.d.ts +7 -14
  19. package/dist/lib/workspace.d.ts.map +1 -1
  20. package/dist/lib/workspace.js +76 -82
  21. package/dist/monorepo/ci-workflow.d.ts +2 -0
  22. package/dist/monorepo/ci-workflow.d.ts.map +1 -1
  23. package/dist/monorepo/ci-workflow.js +28 -2
  24. package/dist/monorepo/git-config.js +2 -2
  25. package/dist/monorepo/index.d.ts +3 -1
  26. package/dist/monorepo/index.d.ts.map +1 -1
  27. package/dist/monorepo/index.js +6 -2
  28. package/dist/monorepo/lockfile.d.ts +13 -1
  29. package/dist/monorepo/lockfile.d.ts.map +1 -1
  30. package/dist/monorepo/lockfile.js +33 -21
  31. package/dist/monorepo/managed-files.d.ts +12 -0
  32. package/dist/monorepo/managed-files.d.ts.map +1 -1
  33. package/dist/monorepo/managed-files.js +86 -28
  34. package/dist/monorepo/package-policy.d.ts.map +1 -1
  35. package/dist/monorepo/package-policy.js +78 -45
  36. package/dist/monorepo/packed-manifest.d.ts +5 -2
  37. package/dist/monorepo/packed-manifest.d.ts.map +1 -1
  38. package/dist/monorepo/packed-manifest.js +16 -47
  39. package/dist/monorepo/packed-package.d.ts.map +1 -1
  40. package/dist/monorepo/packed-package.js +28 -16
  41. package/dist/monorepo/packs/index.d.ts +3 -0
  42. package/dist/monorepo/packs/index.d.ts.map +1 -1
  43. package/dist/monorepo/packs/index.js +18 -6
  44. package/dist/monorepo/publish-workflow.d.ts +1 -0
  45. package/dist/monorepo/publish-workflow.d.ts.map +1 -1
  46. package/dist/monorepo/publish-workflow.js +242 -3
  47. package/dist/monorepo/runtime.d.ts +15 -0
  48. package/dist/monorepo/runtime.d.ts.map +1 -1
  49. package/dist/monorepo/runtime.js +66 -12
  50. package/dist/monorepo/tool-validation.d.ts.map +1 -1
  51. package/dist/monorepo/tool-validation.js +32 -23
  52. package/dist/monorepo/wrangler.d.ts.map +1 -1
  53. package/dist/monorepo/wrangler.js +13 -13
  54. package/dist/nx/index.d.ts +12 -4
  55. package/dist/nx/index.d.ts.map +1 -1
  56. package/dist/nx/index.js +86 -19
  57. package/dist/pr/index.d.ts.map +1 -1
  58. package/dist/pr/index.js +28 -32
  59. package/dist/release/candidates.d.ts +3 -2
  60. package/dist/release/candidates.d.ts.map +1 -1
  61. package/dist/release/candidates.js +4 -5
  62. package/dist/release/github-release.d.ts +3 -0
  63. package/dist/release/github-release.d.ts.map +1 -1
  64. package/dist/release/github-release.js +11 -0
  65. package/dist/release/index.d.ts +20 -1
  66. package/dist/release/index.d.ts.map +1 -1
  67. package/dist/release/index.js +221 -89
  68. package/dist/release/orchestration.d.ts +8 -1
  69. package/dist/release/orchestration.d.ts.map +1 -1
  70. package/dist/release/orchestration.js +38 -1
  71. package/dist/wrangler/prepare-env.d.ts +0 -1
  72. package/dist/wrangler/prepare-env.d.ts.map +1 -1
  73. package/dist/wrangler/prepare-env.js +100 -51
  74. package/dist/wrangler/scaffold.d.ts.map +1 -1
  75. package/dist/wrangler/scaffold.js +12 -12
  76. package/managed/raw/git-format-staged.yml +19 -1
  77. package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +6 -0
  78. package/managed/raw/tooling/direnv/merge-newer-pins.sh +3 -3
  79. package/managed/raw/tooling/direnv/setup-environment.ts +187 -19
  80. package/managed/raw/tooling/git-hooks/pre-commit.sh +0 -6
  81. package/managed/templates/github/actions/cache-nix-devenv/action.yml +12 -10
  82. package/managed/templates/github/actions/cache-node-modules/action.yml +6 -2
  83. package/managed/templates/github/actions/cache-nx/action.yml +2 -2
  84. package/managed/templates/github/actions/cache-ttsc-plugins/action.yml +19 -0
  85. package/managed/templates/github/actions/save-nix-devenv/action.yml +11 -3
  86. package/managed/templates/github/actions/setup-devenv/action.yml +5 -0
  87. package/managed/templates/github/workflows/managed-files.yml +80 -0
  88. package/package.json +18 -4
  89. package/src/bun/preload.ts +12 -0
  90. package/src/cli.ts +49 -11
  91. package/src/generate/index.ts +0 -6
  92. package/src/github-ci/index.test.ts +486 -0
  93. package/src/github-ci/index.ts +220 -30
  94. package/src/github-ci/outputs.ts +505 -0
  95. package/src/lib/cli-package.ts +2 -2
  96. package/src/lib/json.ts +291 -32
  97. package/src/lib/workspace.ts +88 -100
  98. package/src/monorepo/__tests__/ci-workflow.test.ts +14 -3
  99. package/src/monorepo/__tests__/publish-workflow.test.ts +222 -1
  100. package/src/monorepo/ci-workflow.ts +28 -2
  101. package/src/monorepo/git-config.ts +2 -2
  102. package/src/monorepo/index.ts +6 -2
  103. package/src/monorepo/lockfile.test.ts +35 -36
  104. package/src/monorepo/lockfile.ts +48 -24
  105. package/src/monorepo/managed-files.test.ts +63 -0
  106. package/src/monorepo/managed-files.ts +94 -34
  107. package/src/monorepo/package-policy.test.ts +70 -51
  108. package/src/monorepo/package-policy.ts +104 -59
  109. package/src/monorepo/packed-manifest.ts +18 -52
  110. package/src/monorepo/packed-package.ts +43 -22
  111. package/src/monorepo/packs/index.test.ts +18 -1
  112. package/src/monorepo/packs/index.ts +20 -7
  113. package/src/monorepo/publish-workflow.ts +482 -3
  114. package/src/monorepo/runtime.test.ts +42 -3
  115. package/src/monorepo/runtime.ts +90 -12
  116. package/src/monorepo/tool-validation.ts +35 -25
  117. package/src/monorepo/wrangler.test.ts +11 -15
  118. package/src/monorepo/wrangler.ts +15 -15
  119. package/src/nx/index.test.ts +43 -0
  120. package/src/nx/index.ts +110 -23
  121. package/src/pr/index.ts +42 -36
  122. package/src/release/__tests__/candidates.test.ts +5 -4
  123. package/src/release/__tests__/fixture-repo.test.ts +18 -16
  124. package/src/release/__tests__/github-release.test.ts +11 -0
  125. package/src/release/__tests__/orchestration.test.ts +57 -2
  126. package/src/release/__tests__/trust-publisher.test.ts +98 -2
  127. package/src/release/candidates.ts +10 -17
  128. package/src/release/github-release.ts +12 -0
  129. package/src/release/index.ts +321 -112
  130. package/src/release/orchestration.ts +55 -3
  131. package/src/wrangler/prepare-env.ts +75 -54
  132. package/src/wrangler/scaffold.test.ts +9 -12
  133. package/src/wrangler/scaffold.ts +13 -13
@@ -3,8 +3,11 @@ import type { BootstrapNpmPackagesOptions } from '../bootstrap-npm-packages.js';
3
3
  import type { ReleasePackageInfo } from '../core.js';
4
4
  import {
5
5
  configureTrustedPublishers,
6
+ npmTrustGithubArgs,
7
+ npmTrustListAccessDenied,
6
8
  parseTrustedPublishers,
7
9
  type TrustedPublisher,
10
+ type TrustedPublisherLookup,
8
11
  type TrustPublisherShell,
9
12
  } from '../index.js';
10
13
 
@@ -101,6 +104,91 @@ describe('trusted publisher setup', () => {
101
104
  expect(shell.events).toEqual([`exists:${missing.name}`, `list:${missing.name}`, `trust:${missing.name}:false`]);
102
105
  });
103
106
 
107
+ it('reports package owners and opens npm login after trust access is denied', async () => {
108
+ const shell = new RecordingTrustPublisherShell({
109
+ packages: [stable],
110
+ existing: [stable.name],
111
+ trustedPublisherResponses: {
112
+ [stable.name]: [
113
+ {
114
+ status: 'access-denied',
115
+ identity: 'current-user',
116
+ owners: 'package-owner <owner@example.test>',
117
+ },
118
+ [],
119
+ ],
120
+ },
121
+ });
122
+
123
+ await configureTrustedPublishers(shell, {});
124
+
125
+ expect(shell.events).toEqual([
126
+ `exists:${stable.name}`,
127
+ `list:${stable.name}`,
128
+ 'login',
129
+ `list:${stable.name}`,
130
+ `trust:${stable.name}:false`,
131
+ ]);
132
+ expect(shell.errors.join('\n')).toContain('npm account "current-user"');
133
+ expect(shell.errors.join('\n')).toContain('package-owner <owner@example.test>');
134
+ expect(shell.logs).toContain(
135
+ `${stable.name}: opening npm browser login. Sign in as a listed package owner, then return here.`,
136
+ );
137
+ });
138
+
139
+ it('stops after one owner login when the selected account still lacks access', async () => {
140
+ const shell = new RecordingTrustPublisherShell({
141
+ packages: [stable],
142
+ existing: [stable.name],
143
+ trustedPublisherResponses: {
144
+ [stable.name]: [
145
+ {
146
+ status: 'access-denied',
147
+ identity: 'current-user',
148
+ owners: 'package-owner <owner@example.test>',
149
+ },
150
+ {
151
+ status: 'access-denied',
152
+ identity: 'wrong-user',
153
+ owners: 'package-owner <owner@example.test>',
154
+ },
155
+ ],
156
+ },
157
+ });
158
+
159
+ await expect(configureTrustedPublishers(shell, {})).rejects.toThrow(
160
+ 'npm browser login completed, but the selected account still cannot manage this package',
161
+ );
162
+ expect(shell.events).toEqual([`exists:${stable.name}`, `list:${stable.name}`, 'login', `list:${stable.name}`]);
163
+ });
164
+
165
+ it('grants the trusted workflow permission to run npm publish', () => {
166
+ expect(npmTrustGithubArgs(stable.name, 'scope/repo', 'publish.yml', false)).toEqual([
167
+ 'trust',
168
+ 'github',
169
+ stable.name,
170
+ '--file',
171
+ 'publish.yml',
172
+ '--repo',
173
+ 'scope/repo',
174
+ '--allow-publish',
175
+ '--yes',
176
+ ]);
177
+ expect(npmTrustGithubArgs(stable.name, 'scope/repo', 'publish.yml', true).at(-1)).toBe('--dry-run');
178
+ });
179
+
180
+ it('recognizes npm trust access-denied output', () => {
181
+ expect(npmTrustListAccessDenied('{"error":{"code":"E403"}}', '')).toBe(true);
182
+ expect(npmTrustListAccessDenied('', 'npm error code E401')).toBe(true);
183
+ expect(
184
+ npmTrustListAccessDenied(
185
+ '',
186
+ 'npm error 401 Unauthorized - GET https://registry.npmjs.org/-/package/@scope%2fpkg/trust - {"success":false,"error":"You must be logged in to publish packages."}',
187
+ ),
188
+ ).toBe(true);
189
+ expect(npmTrustListAccessDenied('', 'npm error code EOTP')).toBe(false);
190
+ });
191
+
104
192
  it('parses empty npm trust list output as no trusted publishers', () => {
105
193
  expect(parseTrustedPublishers('', stable.name)).toEqual([]);
106
194
  expect(parseTrustedPublishers('null', stable.name)).toEqual([]);
@@ -116,15 +204,18 @@ class RecordingTrustPublisherShell implements TrustPublisherShell<ReleasePackage
116
204
  private readonly packages: ReleasePackageInfo[];
117
205
  private readonly existing: Set<string>;
118
206
  private readonly trustedPublisherByPackage: Record<string, TrustedPublisher[]>;
207
+ private readonly trustedPublisherResponses: Record<string, TrustedPublisherLookup[]>;
119
208
 
120
209
  constructor(options: {
121
210
  packages: ReleasePackageInfo[];
122
211
  existing: string[];
123
212
  trustedPublishers?: Record<string, TrustedPublisher[]>;
213
+ trustedPublisherResponses?: Record<string, TrustedPublisherLookup[]>;
124
214
  }) {
125
215
  this.packages = options.packages;
126
216
  this.existing = new Set(options.existing);
127
217
  this.trustedPublisherByPackage = options.trustedPublishers ?? {};
218
+ this.trustedPublisherResponses = options.trustedPublisherResponses ?? {};
128
219
  }
129
220
 
130
221
  listReleasePackages(): ReleasePackageInfo[] {
@@ -145,14 +236,19 @@ class RecordingTrustPublisherShell implements TrustPublisherShell<ReleasePackage
145
236
  return missingPackages;
146
237
  }
147
238
 
239
+ async login(): Promise<void> {
240
+ this.events.push('login');
241
+ }
242
+
148
243
  async trustPublisher(pkg: ReleasePackageInfo, dryRun: boolean): Promise<'configured' | 'already-configured'> {
149
244
  this.events.push(`trust:${pkg.name}:${dryRun}`);
150
245
  return 'configured';
151
246
  }
152
247
 
153
- async trustedPublishers(pkg: ReleasePackageInfo): Promise<TrustedPublisher[]> {
248
+ async trustedPublishers(pkg: ReleasePackageInfo): Promise<TrustedPublisherLookup> {
154
249
  this.events.push(`list:${pkg.name}`);
155
- return this.trustedPublisherByPackage[pkg.name] ?? [];
250
+ const response = this.trustedPublisherResponses[pkg.name]?.shift();
251
+ return response ?? this.trustedPublisherByPackage[pkg.name] ?? [];
156
252
  }
157
253
 
158
254
  log(message: string): void {
@@ -1,4 +1,4 @@
1
- import { isRecord } from '../lib/json.js';
1
+ import type { PackageJson } from '../lib/json.js';
2
2
  import type { ReleasePackageInfo } from './core.js';
3
3
  import { releaseTag } from './core.js';
4
4
 
@@ -6,8 +6,8 @@ export interface AutoReleaseCandidateShell {
6
6
  gitRefExists(ref: string): Promise<boolean>;
7
7
  latestStableReleaseRef(projectName: string): Promise<string | null>;
8
8
  packageChangedFilesSince(ref: string, packagePath: string): Promise<string[]>;
9
- packageJsonAtRef(ref: string, packagePath: string): Promise<Record<string, unknown> | null>;
10
- currentPackageJson(packagePath: string): Promise<Record<string, unknown> | null>;
9
+ packageJsonAtRef(ref: string, packagePath: string): Promise<PackageJson | null>;
10
+ currentPackageJson(packagePath: string): Promise<PackageJson | null>;
11
11
  packageBuildInputPatterns(projectName: string, packagePath: string): Promise<string[]>;
12
12
  packageHasHistory(packagePath: string): Promise<boolean>;
13
13
  }
@@ -104,23 +104,16 @@ async function packageHasReleasableChangesSince<Package extends ReleasePackageIn
104
104
  return false;
105
105
  }
106
106
 
107
- function releasableManifestChanged(
108
- previousManifest: Record<string, unknown>,
109
- currentManifest: Record<string, unknown>,
110
- ): boolean {
107
+ function releasableManifestChanged(previousManifest: PackageJson, currentManifest: PackageJson): boolean {
111
108
  for (const key of releasableManifestKeys) {
112
- if (!stableJsonEqual(previousManifest[key], currentManifest[key])) {
109
+ if (!stableJsonEqual(Reflect.get(previousManifest, key), Reflect.get(currentManifest, key))) {
113
110
  return true;
114
111
  }
115
112
  }
116
113
  return false;
117
114
  }
118
115
 
119
- function isReleasablePackagePath(
120
- path: string,
121
- manifest: Record<string, unknown> | null,
122
- buildInputPatterns: string[],
123
- ): boolean {
116
+ function isReleasablePackagePath(path: string, manifest: PackageJson | null, buildInputPatterns: string[]): boolean {
124
117
  return (
125
118
  isBuildInputPath(path, buildInputPatterns) || isPackageMetadataPath(path) || isManifestFilesPath(path, manifest)
126
119
  );
@@ -195,13 +188,13 @@ function isPackageMetadataPath(path: string): boolean {
195
188
  return /^(README|LICENSE|CHANGELOG)(\.|$)/.test(path);
196
189
  }
197
190
 
198
- function isManifestFilesPath(path: string, manifest: Record<string, unknown> | null): boolean {
191
+ function isManifestFilesPath(path: string, manifest: PackageJson | null): boolean {
199
192
  const files = manifest?.files;
200
193
  if (!Array.isArray(files)) {
201
194
  return false;
202
195
  }
203
196
  for (const entry of files) {
204
- if (typeof entry !== 'string' || entry.length === 0 || entry.startsWith('!')) {
197
+ if (entry.length === 0 || entry.startsWith('!')) {
205
198
  continue;
206
199
  }
207
200
  if (matchesManifestFilesEntry(path, entry)) {
@@ -227,10 +220,10 @@ function stableJson(value: unknown): unknown {
227
220
  if (Array.isArray(value)) {
228
221
  return value.map(stableJson);
229
222
  }
230
- if (isRecord(value)) {
223
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
231
224
  const normalized: Record<string, unknown> = {};
232
225
  for (const key of Object.keys(value).sort()) {
233
- normalized[key] = stableJson(value[key]);
226
+ normalized[key] = stableJson(Reflect.get(value, key));
234
227
  }
235
228
  return normalized;
236
229
  }
@@ -71,6 +71,7 @@ export function nxProjectChangelogArgs(pkg: ReleasePackageInfo, previousTag: str
71
71
  gitPush: false,
72
72
  stageChanges: false,
73
73
  createRelease: false,
74
+ forceChangelogGeneration: true,
74
75
  deleteVersionPlans: false,
75
76
  dryRun,
76
77
  } satisfies ChangelogOptions;
@@ -85,6 +86,17 @@ export function projectChangelogContents(result: ProjectChangelogLookupResult, p
85
86
  return changelog.contents;
86
87
  }
87
88
 
89
+ export function githubReleaseLookupExists(tag: string, exitCode: number, stdout: string, stderr: string): boolean {
90
+ if (exitCode === 0) {
91
+ return true;
92
+ }
93
+ const details = [stderr.trim(), stdout.trim()].filter(Boolean).join('\n');
94
+ if (/\bHTTP 404\b|release not found/i.test(details)) {
95
+ return false;
96
+ }
97
+ throw new Error(`Unable to inspect GitHub Release ${tag}.${details ? `\n${details}` : ''}`);
98
+ }
99
+
88
100
  export function githubReleaseCommandArgs(
89
101
  tag: string,
90
102
  notesFile: string,