@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
@@ -25,14 +25,14 @@ describe('release planning with fixture git repositories', () => {
25
25
  await git(root, ['add', '.']);
26
26
  await git(root, ['commit', '-m', 'initial release']);
27
27
  const first = await gitOutput(root, ['rev-parse', 'HEAD']);
28
- await tag(root, '@scope/a@1.0.0', '2025-01-01T00:00:00Z');
29
- await tag(root, '@scope/b@1.0.0', '2025-01-01T00:00:01Z');
28
+ await tag(root, 'a@1.0.0', '2025-01-01T00:00:00Z');
29
+ await tag(root, 'b@1.0.0', '2025-01-01T00:00:01Z');
30
30
 
31
31
  await writePackage(root, '@scope/a', 'packages/a', '1.1.0');
32
32
  await git(root, ['add', '.']);
33
33
  await git(root, ['commit', '-m', 'second release']);
34
34
  const second = await gitOutput(root, ['rev-parse', 'HEAD']);
35
- await tag(root, '@scope/a@1.1.0', '2025-01-02T00:00:00Z');
35
+ await tag(root, 'a@1.1.0', '2025-01-02T00:00:00Z');
36
36
 
37
37
  await writeFile(join(root, 'readme.md'), 'not a release\n');
38
38
  await git(root, ['add', '.']);
@@ -41,11 +41,11 @@ describe('release planning with fixture git repositories', () => {
41
41
  await tag(root, 'not-owned@9.9.9', '2025-01-03T00:00:00Z');
42
42
 
43
43
  const npmPublished = new Set(['@scope/a@1.0.0', '@scope/b@1.0.0']);
44
- const githubReleases = new Set(['@scope/a@1.0.0']);
44
+ const githubReleases = new Set(['a@1.0.0']);
45
45
  const records = await collectOwnedReleaseTagRecords(
46
46
  [
47
- { name: '@scope/a', path: 'packages/a' },
48
- { name: '@scope/b', path: 'packages/b' },
47
+ { name: '@scope/a', projectName: 'a', path: 'packages/a' },
48
+ { name: '@scope/b', projectName: 'b', path: 'packages/b' },
49
49
  ],
50
50
  head,
51
51
  {
@@ -59,7 +59,7 @@ describe('release planning with fixture git repositories', () => {
59
59
  },
60
60
  );
61
61
 
62
- expect(records.map((record) => record.tag)).toEqual(['@scope/a@1.1.0', '@scope/b@1.0.0', '@scope/a@1.0.0']);
62
+ expect(records.map((record) => record.tag)).toEqual(['a@1.1.0', 'b@1.0.0', 'a@1.0.0']);
63
63
  const pending = pendingReleaseTargets(records, head);
64
64
  expect(pending.map((target) => target.sha)).toEqual([first, second]);
65
65
  expect(pending[0]?.packages.map((pkg) => pkg.name)).toEqual(['@scope/b']);
@@ -74,7 +74,7 @@ describe('release planning with fixture git repositories', () => {
74
74
  await writePackage(root, '@scope/a', 'packages/a', '1.0.0');
75
75
  await git(root, ['add', '.']);
76
76
  await git(root, ['commit', '-m', 'release a']);
77
- await tag(root, '@scope/a@1.0.0', '2025-01-01T00:00:00Z');
77
+ await tag(root, 'a@1.0.0', '2025-01-01T00:00:00Z');
78
78
  await git(root, ['init', '--bare', 'remote.git']);
79
79
  await git(root, ['remote', 'add', 'origin', join(root, 'remote.git')]);
80
80
  await git(root, ['push', 'origin', 'main', '--tags']);
@@ -83,14 +83,18 @@ describe('release planning with fixture git repositories', () => {
83
83
  await git(checkout, ['fetch', '--tags', 'origin', 'main']);
84
84
  const head = await gitOutput(checkout, ['rev-parse', 'HEAD']);
85
85
 
86
- const records = await collectOwnedReleaseTagRecords([{ name: '@scope/a', path: 'packages/a' }], head, {
87
- listReleaseTagsByCreatorDate: () => gitReleaseTagsByCreatorDate(checkout),
88
- isAncestor: (ancestor, descendant) => gitIsAncestor(checkout, ancestor, descendant),
89
- packageVersionAtRef: (packagePath, ref) => packageVersionAtRef(checkout, packagePath, ref),
90
- durableTagState: async () => ({ npmPublished: false, githubReleaseExists: false }),
91
- });
86
+ const records = await collectOwnedReleaseTagRecords(
87
+ [{ name: '@scope/a', projectName: 'a', path: 'packages/a' }],
88
+ head,
89
+ {
90
+ listReleaseTagsByCreatorDate: () => gitReleaseTagsByCreatorDate(checkout),
91
+ isAncestor: (ancestor, descendant) => gitIsAncestor(checkout, ancestor, descendant),
92
+ packageVersionAtRef: (packagePath, ref) => packageVersionAtRef(checkout, packagePath, ref),
93
+ durableTagState: async () => ({ npmPublished: false, githubReleaseExists: false }),
94
+ },
95
+ );
92
96
 
93
- expect(records.map((record) => record.tag)).toEqual(['@scope/a@1.0.0']);
97
+ expect(records.map((record) => record.tag)).toEqual(['a@1.0.0']);
94
98
  expect(pendingReleaseTargets(records, 'not-head').map((target) => target.sha)).toEqual([head]);
95
99
  });
96
100
  });
@@ -101,7 +105,7 @@ describe('release planning with fixture git repositories', () => {
101
105
  await writeBuildablePackage(root, '@scope/a', 'packages/a');
102
106
  await writeBuildablePackage(root, '@scope/b', 'packages/b');
103
107
 
104
- await $`nx run-many -t build --projects=${'@scope/a,@scope/b'}`.cwd(root).quiet();
108
+ await $`nx run-many -t build --projects=${'a,b'}`.cwd(root).quiet();
105
109
 
106
110
  await expect(readFile(join(root, 'packages/a/dist/index.js'), 'utf8')).resolves.toBe('{}\n');
107
111
  await expect(readFile(join(root, 'packages/b/dist/index.js'), 'utf8')).resolves.toBe('{}\n');
@@ -115,26 +119,26 @@ describe('release planning with fixture git repositories', () => {
115
119
  await writeBuildablePackage(author, '@scope/b', 'packages/b', '1.0.0');
116
120
  await git(author, ['add', '.']);
117
121
  await git(author, ['commit', '-m', 'initial release']);
118
- await tag(author, '@scope/a@1.0.0', '2025-01-01T00:00:00Z');
119
- await tag(author, '@scope/b@1.0.0', '2025-01-01T00:00:01Z');
122
+ await tag(author, 'a@1.0.0', '2025-01-01T00:00:00Z');
123
+ await tag(author, 'b@1.0.0', '2025-01-01T00:00:01Z');
120
124
 
121
125
  await writeBuildablePackage(author, '@scope/a', 'packages/a', '1.1.0');
122
126
  await git(author, ['add', 'packages/a/package.json']);
123
127
  await git(author, ['commit', '-m', 'release a 1.1.0']);
124
128
  const githubOnlySha = await gitOutput(author, ['rev-parse', 'HEAD']);
125
- await tag(author, '@scope/a@1.1.0', '2025-01-02T00:00:00Z');
129
+ await tag(author, 'a@1.1.0', '2025-01-02T00:00:00Z');
126
130
 
127
131
  await writeBuildablePackage(author, '@scope/b', 'packages/b', '2.0.0-beta.1');
128
132
  await git(author, ['add', 'packages/b/package.json']);
129
133
  await git(author, ['commit', '-m', 'release b prerelease']);
130
134
  const npmAndGithubSha = await gitOutput(author, ['rev-parse', 'HEAD']);
131
- await tag(author, '@scope/b@2.0.0-beta.1', '2025-01-03T00:00:00Z');
135
+ await tag(author, 'b@2.0.0-beta.1', '2025-01-03T00:00:00Z');
132
136
 
133
137
  await writeBuildablePackage(author, '@scope/a', 'packages/a', '1.2.0');
134
138
  await git(author, ['add', 'packages/a/package.json']);
135
139
  await git(author, ['commit', '-m', 'head release a 1.2.0']);
136
140
  const headSha = await gitOutput(author, ['rev-parse', 'HEAD']);
137
- await tag(author, '@scope/a@1.2.0', '2025-01-04T00:00:00Z');
141
+ await tag(author, 'a@1.2.0', '2025-01-04T00:00:00Z');
138
142
 
139
143
  await git(author, ['init', '--bare', 'remote.git']);
140
144
  await git(author, ['remote', 'add', 'origin', join(author, 'remote.git')]);
@@ -147,7 +151,7 @@ describe('release planning with fixture git repositories', () => {
147
151
  const restoreRef = 'origin/main';
148
152
  const packages = releaseFixturePackages();
149
153
  const npmPublished = new Set(['@scope/a@1.0.0', '@scope/b@1.0.0', '@scope/a@1.1.0']);
150
- const githubReleases = new Set(['@scope/a@1.0.0', '@scope/b@1.0.0']);
154
+ const githubReleases = new Set(['a@1.0.0', 'b@1.0.0']);
151
155
 
152
156
  const records = await collectOwnedReleaseTagRecords(packages, restoreRef, {
153
157
  listReleaseTagsByCreatorDate: () => gitReleaseTagsByCreatorDate(runner),
@@ -177,7 +181,7 @@ describe('release planning with fixture git repositories', () => {
177
181
  { name: '@scope/a', version: '1.1.0', dryRun: false },
178
182
  { name: '@scope/b', version: '2.0.0-beta.1', dryRun: false },
179
183
  ]);
180
- expect(shell.pushes).toEqual([['@scope/a@1.1.0'], ['@scope/b@2.0.0-beta.1']]);
184
+ expect(shell.pushes).toEqual([['a@1.1.0'], ['b@2.0.0-beta.1']]);
181
185
  expect(summaries.map((summary) => summary.sha)).toEqual([githubOnlySha, npmAndGithubSha]);
182
186
  await expect(readFile(join(runner, 'packages/b/dist/index.js'), 'utf8')).resolves.toBe('{}\n');
183
187
  await expect(readFile(join(runner, 'packages/a/dist/index.js'), 'utf8')).rejects.toThrow();
@@ -197,27 +201,81 @@ describe('release planning with fixture git repositories', () => {
197
201
  const runner = join(author, 'runner');
198
202
  await git(runner, ['config', 'user.name', 'Test User']);
199
203
  await git(runner, ['config', 'user.email', 'test@example.com']);
200
- const pkg: ReleasePackageInfo = { name: '@scope/pushed', path: 'packages/pushed', version: '1.0.0' };
204
+ const pkg: ReleasePackageInfo = {
205
+ name: '@scope/pushed',
206
+ projectName: 'pushed',
207
+ path: 'packages/pushed',
208
+ version: '1.0.0',
209
+ };
201
210
  const shell = new LocalGitRepairShell(runner);
202
211
 
203
212
  const summary = await completeReleaseAtHead(shell, [pkg], false, false);
204
213
 
205
214
  expect(summary.pushed).toBe(true);
206
- expect(shell.pushes).toEqual([['@scope/pushed@1.0.0']]);
215
+ expect(shell.pushes).toEqual([['pushed@1.0.0']]);
207
216
  await $`git clone --branch main ${join(author, 'remote.git')} auditor`.cwd(author).quiet();
208
217
  const auditor = join(author, 'auditor');
209
218
  await git(auditor, ['fetch', '--tags', 'origin', 'main']);
210
- await expect(gitOutput(auditor, ['rev-parse', 'refs/tags/@scope/pushed@1.0.0^{}'])).resolves.toBe(
219
+ await expect(gitOutput(auditor, ['rev-parse', 'refs/tags/pushed@1.0.0^{}'])).resolves.toBe(
211
220
  await gitOutput(runner, ['rev-parse', 'HEAD']),
212
221
  );
222
+ await expect(gitSucceeds(auditor, ['rev-parse', '--verify', 'refs/tags/@scope/pushed@1.0.0'])).resolves.toBe(
223
+ false,
224
+ );
225
+ });
226
+ });
227
+
228
+ it('repairs a scoped package from its project-name release tag without creating a package-name tag', async () => {
229
+ await withFixtureRepo(async (author) => {
230
+ await writeWorkspace(author);
231
+ await writeBuildablePackage(author, '@scope/cli', 'packages/cli', '0.2.0');
232
+ await git(author, ['add', '.']);
233
+ await git(author, ['commit', '-m', 'release cli 0.2.0']);
234
+ const releaseSha = await gitOutput(author, ['rev-parse', 'HEAD']);
235
+ await tag(author, 'cli@0.2.0', '2025-01-01T00:00:00Z');
236
+
237
+ await git(author, ['init', '--bare', 'remote.git']);
238
+ await git(author, ['remote', 'add', 'origin', join(author, 'remote.git')]);
239
+ await git(author, ['push', 'origin', 'main', '--tags']);
240
+ await $`git clone --branch main ${join(author, 'remote.git')} runner`.cwd(author).quiet();
241
+ const runner = join(author, 'runner');
242
+ await git(runner, ['config', 'user.name', 'Test User']);
243
+ await git(runner, ['config', 'user.email', 'test@example.com']);
244
+ await git(runner, ['fetch', '--tags', 'origin', 'main']);
245
+
246
+ const pkg: ReleasePackageInfo = {
247
+ name: '@scope/cli',
248
+ projectName: 'cli',
249
+ path: 'packages/cli',
250
+ version: '0.0.0',
251
+ };
252
+ const records = await collectOwnedReleaseTagRecords([pkg], 'origin/main', {
253
+ listReleaseTagsByCreatorDate: () => gitReleaseTagsByCreatorDate(runner),
254
+ isAncestor: (ancestor, descendant) => gitIsAncestor(runner, ancestor, descendant),
255
+ packageVersionAtRef: (packagePath, ref) => packageVersionAtRef(runner, packagePath, ref),
256
+ durableTagState: async () => ({ npmPublished: false, githubReleaseExists: false }),
257
+ });
258
+ const pending = pendingReleaseTargets(records, 'not-head');
259
+
260
+ const shell = new LocalGitRepairShell(runner);
261
+ const summaries = await repairPendingTargets(shell, pending, 'origin/main', false);
262
+
263
+ expect(records.map((record) => record.tag)).toEqual(['cli@0.2.0']);
264
+ expect(pending.map((target) => target.sha)).toEqual([releaseSha]);
265
+ expect(shell.pushes).toEqual([['cli@0.2.0']]);
266
+ expect(shell.publishes).toEqual([{ name: '@scope/cli', version: '0.2.0', distTag: 'latest', dryRun: false }]);
267
+ expect(shell.githubCreates).toEqual([{ name: '@scope/cli', version: '0.2.0', dryRun: false }]);
268
+ expect(summaries[0]?.packages.map((releasePackage) => releasePackage.name)).toEqual(['@scope/cli']);
269
+ await expect(gitSucceeds(runner, ['rev-parse', '--verify', 'refs/tags/cli@0.2.0'])).resolves.toBe(true);
270
+ await expect(gitSucceeds(runner, ['rev-parse', '--verify', 'refs/tags/@scope/cli@0.2.0'])).resolves.toBe(false);
213
271
  });
214
272
  });
215
273
  });
216
274
 
217
275
  function releaseFixturePackages(): ReleasePackageInfo[] {
218
276
  return [
219
- { name: '@scope/a', path: 'packages/a', version: '0.0.0' },
220
- { name: '@scope/b', path: 'packages/b', version: '0.0.0' },
277
+ { name: '@scope/a', projectName: 'a', path: 'packages/a', version: '0.0.0' },
278
+ { name: '@scope/b', projectName: 'b', path: 'packages/b', version: '0.0.0' },
221
279
  ];
222
280
  }
223
281
 
@@ -236,7 +294,7 @@ class LocalGitRepairShell implements ReleaseRepairShell<ReleasePackageInfo> {
236
294
  }
237
295
 
238
296
  async pushReleaseRefs(packages: ReleasePackageInfo[]): Promise<boolean> {
239
- this.pushes.push(packages.map((pkg) => `${pkg.name}@${pkg.version}`));
297
+ this.pushes.push(packages.map((pkg) => releaseTag(pkg)));
240
298
  await git(this.root, ['fetch', '--tags', 'origin', 'main']);
241
299
  for (const pkg of packages) {
242
300
  await this.ensureLocalReleaseTag(pkg);
@@ -265,7 +323,7 @@ class LocalGitRepairShell implements ReleaseRepairShell<ReleasePackageInfo> {
265
323
 
266
324
  async buildReleaseCandidate(packages: ReleasePackageInfo[]): Promise<void> {
267
325
  this.builds.push(packages.map((pkg) => pkg.name));
268
- await $`nx run-many -t build --projects=${packages.map((pkg) => pkg.name).join(',')}`.cwd(this.root).quiet();
326
+ await $`nx run-many -t build --projects=${packages.map((pkg) => pkg.projectName).join(',')}`.cwd(this.root).quiet();
269
327
  }
270
328
 
271
329
  async publishPackage(pkg: ReleasePackageInfo, distTag: string, dryRun: boolean): Promise<void> {
@@ -276,8 +334,9 @@ class LocalGitRepairShell implements ReleaseRepairShell<ReleasePackageInfo> {
276
334
  return [];
277
335
  }
278
336
 
279
- async createGithubRelease(pkg: ReleasePackageInfo, dryRun: boolean): Promise<void> {
337
+ async createGithubRelease(pkg: ReleasePackageInfo, dryRun: boolean): Promise<string | null> {
280
338
  this.githubCreates.push({ name: pkg.name, version: pkg.version, dryRun });
339
+ return dryRun ? null : `https://github.test/${releaseTag(pkg)}`;
281
340
  }
282
341
 
283
342
  async checkout(ref: string): Promise<void> {
@@ -8,14 +8,19 @@ import {
8
8
  projectChangelogContents,
9
9
  } from '../github-release.js';
10
10
 
11
- const stable: ReleasePackageInfo = { name: '@scope/pkg', path: 'packages/pkg', version: '1.2.3' };
12
- const prerelease: ReleasePackageInfo = { name: '@scope/pkg', path: 'packages/pkg', version: '2.0.0-beta.1' };
11
+ const stable: ReleasePackageInfo = { name: '@scope/pkg', projectName: 'pkg', path: 'packages/pkg', version: '1.2.3' };
12
+ const prerelease: ReleasePackageInfo = {
13
+ name: '@scope/pkg',
14
+ projectName: 'pkg',
15
+ path: 'packages/pkg',
16
+ version: '2.0.0-beta.1',
17
+ };
13
18
 
14
19
  describe('GitHub release helpers', () => {
15
20
  it('configures the Nx changelog API for render-only project changelogs', () => {
16
- expect(nxProjectChangelogArgs(stable, '@scope/pkg@1.2.2', false)).toEqual({
21
+ expect(nxProjectChangelogArgs(stable, 'pkg@1.2.2', false)).toEqual({
17
22
  version: '1.2.3',
18
- projects: ['@scope/pkg'],
23
+ projects: ['pkg'],
19
24
  gitCommit: false,
20
25
  gitTag: false,
21
26
  gitPush: false,
@@ -23,14 +28,14 @@ describe('GitHub release helpers', () => {
23
28
  createRelease: false,
24
29
  deleteVersionPlans: false,
25
30
  dryRun: false,
26
- from: '@scope/pkg@1.2.2',
31
+ from: 'pkg@1.2.2',
27
32
  });
28
33
  });
29
34
 
30
35
  it('marks the Nx changelog request as a first release when there is no previous tag', () => {
31
36
  expect(nxProjectChangelogArgs(stable, null, true)).toEqual({
32
37
  version: '1.2.3',
33
- projects: ['@scope/pkg'],
38
+ projects: ['pkg'],
34
39
  gitCommit: false,
35
40
  gitTag: false,
36
41
  gitPush: false,
@@ -47,17 +52,17 @@ describe('GitHub release helpers', () => {
47
52
  projectChangelogContents(
48
53
  {
49
54
  projectChangelogs: {
50
- '@scope/pkg': { contents: 'generated release notes' },
55
+ pkg: { contents: 'generated release notes' },
51
56
  },
52
57
  },
53
- '@scope/pkg',
58
+ 'pkg',
54
59
  ),
55
60
  ).toBe('generated release notes');
56
61
  });
57
62
 
58
63
  it('fails when Nx omits the requested project changelog', () => {
59
- expect(() => projectChangelogContents({ projectChangelogs: {} }, '@scope/pkg')).toThrow(
60
- 'Nx did not generate a project changelog for @scope/pkg.',
64
+ expect(() => projectChangelogContents({ projectChangelogs: {} }, 'pkg')).toThrow(
65
+ 'Nx did not generate a project changelog for pkg.',
61
66
  );
62
67
  });
63
68
 
@@ -66,18 +71,11 @@ describe('GitHub release helpers', () => {
66
71
 
67
72
  await createOrUpdateGithubRelease(stable, 'stable notes', shell);
68
73
 
69
- expect(shell.existsQueries).toEqual(['@scope/pkg@1.2.3']);
74
+ expect(shell.existsQueries).toEqual(['pkg@1.2.3']);
70
75
  expect(shell.notes).toEqual(['stable notes']);
71
- expect(shell.logs).toEqual(['@scope/pkg@1.2.3: creating GitHub Release for @scope/pkg@1.2.3.']);
76
+ expect(shell.logs).toEqual(['@scope/pkg@1.2.3: creating GitHub Release for pkg@1.2.3.']);
72
77
  const command = onlyCommand(shell.commands);
73
- expect(command.slice(0, 6)).toEqual([
74
- 'release',
75
- 'create',
76
- '@scope/pkg@1.2.3',
77
- '--title',
78
- '@scope/pkg@1.2.3',
79
- '--notes-file',
80
- ]);
78
+ expect(command.slice(0, 6)).toEqual(['release', 'create', 'pkg@1.2.3', '--title', 'pkg@1.2.3', '--notes-file']);
81
79
  expect(command).toContain('--verify-tag');
82
80
  expect(command).toContain('--latest=true');
83
81
  expect(command).not.toContain('--prerelease');
@@ -88,16 +86,16 @@ describe('GitHub release helpers', () => {
88
86
 
89
87
  await createOrUpdateGithubRelease(prerelease, 'prerelease notes', shell);
90
88
 
91
- expect(shell.existsQueries).toEqual(['@scope/pkg@2.0.0-beta.1']);
89
+ expect(shell.existsQueries).toEqual(['pkg@2.0.0-beta.1']);
92
90
  expect(shell.notes).toEqual(['prerelease notes']);
93
- expect(shell.logs).toEqual(['@scope/pkg@2.0.0-beta.1: updating GitHub Release for @scope/pkg@2.0.0-beta.1.']);
91
+ expect(shell.logs).toEqual(['@scope/pkg@2.0.0-beta.1: updating GitHub Release for pkg@2.0.0-beta.1.']);
94
92
  const command = onlyCommand(shell.commands);
95
93
  expect(command.slice(0, 6)).toEqual([
96
94
  'release',
97
95
  'edit',
98
- '@scope/pkg@2.0.0-beta.1',
96
+ 'pkg@2.0.0-beta.1',
99
97
  '--title',
100
- '@scope/pkg@2.0.0-beta.1',
98
+ 'pkg@2.0.0-beta.1',
101
99
  '--notes-file',
102
100
  ]);
103
101
  expect(command).toContain('--verify-tag');
@@ -47,6 +47,7 @@ export async function writeBuildablePackage(
47
47
  name,
48
48
  version,
49
49
  nx: {
50
+ name: name.startsWith('@') ? name.slice(name.indexOf('/') + 1) : name,
50
51
  targets: {
51
52
  build: {
52
53
  executor: 'nx:run-commands',
@@ -12,12 +12,10 @@ const pkg: Pick<ReleasePackageInfo, 'name' | 'version'> = { name: '@scope/pkg',
12
12
  describe('npm publish auth diagnostics', () => {
13
13
  it('explains trusted publishing setup when an existing package publish is unauthenticated', () => {
14
14
  const message = npmPublishAuthFailureMessage(pkg, {
15
- useBootstrapToken: false,
16
15
  tokenPresent: true,
17
16
  repository: 'smoothbricks/codebase',
18
17
  });
19
18
  const markdown = npmPublishAuthFailureMarkdown(pkg, {
20
- useBootstrapToken: false,
21
19
  tokenPresent: true,
22
20
  repository: 'smoothbricks/codebase',
23
21
  });
@@ -32,16 +30,12 @@ describe('npm publish auth diagnostics', () => {
32
30
  expect(markdown).toContain('repository `smoothbricks/codebase` and workflow `publish.yml`');
33
31
  });
34
32
 
35
- it('explains the bootstrap token path for first package publishes', () => {
36
- const message = npmPublishAuthFailureMessage(pkg, { useBootstrapToken: true, tokenPresent: false });
37
- const markdown = npmPublishAuthFailureMarkdown(pkg, { useBootstrapToken: true, tokenPresent: false });
33
+ it('points first package publishes to local placeholder bootstrap', () => {
34
+ const message = npmPublishAuthFailureMessage(pkg, { tokenPresent: false });
35
+ const markdown = npmPublishAuthFailureMarkdown(pkg, { tokenPresent: false });
38
36
 
39
- expect(message).toContain('temporary npm automation token');
40
- expect(message).toContain('NODE_AUTH_TOKEN/NPM_TOKEN is not set');
41
- expect(message).toContain('After the first successful publish, run:');
42
- expect(message).toContain('smoo release trust-publisher');
43
- expect(markdown).toContain('Add a temporary `NPM_TOKEN` repository secret');
44
- expect(markdown).toContain('After the first successful publish, run `smoo release trust-publisher`');
37
+ expect(message).toContain('smoo release trust-publisher --bootstrap');
38
+ expect(markdown).toContain('smoo release trust-publisher --bootstrap');
45
39
  });
46
40
 
47
41
  it('reports trusted-publishing guidance and writes it to the publish summary after existing package auth failure', async () => {
@@ -49,7 +43,6 @@ describe('npm publish auth diagnostics', () => {
49
43
 
50
44
  await expect(
51
45
  publishWithAuthDiagnostics(pkg, shell, {
52
- useBootstrapToken: false,
53
46
  tokenPresent: true,
54
47
  repository: 'smoothbricks/codebase',
55
48
  }),
@@ -66,26 +59,24 @@ describe('npm publish auth diagnostics', () => {
66
59
  expect(shell.logs).toEqual([]);
67
60
  });
68
61
 
69
- it('reports bootstrap token guidance and writes it to the publish summary after first-publish auth failure', async () => {
62
+ it('reports bootstrap command guidance and writes it to the publish summary after first-publish auth failure', async () => {
70
63
  const shell = new RecordingPublishShell({ publishFails: true });
71
64
 
72
- await expect(
73
- publishWithAuthDiagnostics(pkg, shell, { useBootstrapToken: true, tokenPresent: false }),
74
- ).rejects.toThrow('@scope/pkg@1.2.3: npm publish authentication failed');
65
+ await expect(publishWithAuthDiagnostics(pkg, shell, { tokenPresent: false })).rejects.toThrow(
66
+ '@scope/pkg@1.2.3: npm publish authentication failed',
67
+ );
75
68
 
76
69
  expect(shell.errors).toHaveLength(1);
77
- expect(shell.errors[0]).toContain('temporary npm automation token');
78
- expect(shell.errors[0]).toContain('NODE_AUTH_TOKEN/NPM_TOKEN is not set');
79
- expect(shell.errors[0]).toContain('smoo release trust-publisher');
70
+ expect(shell.errors[0]).toContain('smoo release trust-publisher --bootstrap');
80
71
  expect(shell.summaries).toHaveLength(1);
81
- expect(shell.summaries[0]).toContain('Add a temporary `NPM_TOKEN` repository secret');
72
+ expect(shell.summaries[0]).toContain('smoo release trust-publisher --bootstrap');
82
73
  expect(shell.logs).toEqual([]);
83
74
  });
84
75
 
85
76
  it('continues without auth warning when the package version appears on npm after publish failure', async () => {
86
77
  const shell = new RecordingPublishShell({ publishFails: true, versionVisibleAfterFailure: true });
87
78
 
88
- await publishWithAuthDiagnostics(pkg, shell, { useBootstrapToken: false, tokenPresent: true });
79
+ await publishWithAuthDiagnostics(pkg, shell, { tokenPresent: true });
89
80
 
90
81
  expect(shell.errors).toEqual([]);
91
82
  expect(shell.summaries).toEqual([]);