@@ -2,7 +2,7 @@ import {ListrEnquirerPromptAdapter} from '@listr2/prompt-adapter-enquirer'
2
2
import { Options , execa } from '@rebundled/execa'
3
3
import findUp from 'find-up'
4
4
import * as fs from 'fs'
5
- import { Listr , ListrTaskWrapper } from 'listr2'
5
+ import { Listr , ListrTask , ListrTaskWrapper } from 'listr2'
6
6
import * as path from 'path'
7
7
import * as semver from 'semver'
8
8
import { z } from 'trpc-cli'
@@ -18,116 +18,9 @@ export type PublishInput = z.infer<typeof PublishInput>
18
18
19
19
export const publish = async ( input : PublishInput ) => {
20
20
const { sortPackageJson} = await import ( 'sort-package-json' )
21
- const monorepoRoot = path . dirname ( findUpOrThrow ( 'pnpm-workspace.yaml' ) )
22
- process . chdir ( monorepoRoot )
23
21
const tasks = new Listr (
24
22
[
25
- {
26
- title : 'Building' ,
27
- task : async ( _ctx , task ) => pipeExeca ( task , 'pnpm' , [ '-w' , 'build' ] ) ,
28
- } ,
29
- {
30
- title : 'Get temp directory' ,
31
- rendererOptions : { persistentOutput : true } ,
32
- task : async ( ctx , task ) => {
33
- const list = await execa ( 'pnpm' , [ 'list' , '--json' , '--depth' , '0' , '--filter' , '.' ] )
34
- const pkgName = JSON . parse ( list . stdout ) ?. [ 0 ] ?. name as string | undefined
35
- if ( ! pkgName ) throw new Error ( `Couldn't get package name from pnpm list output: ${ list . stdout } ` )
36
- ctx . tempDir = path . join ( '/tmp/npmono' , pkgName , Date . now ( ) . toString ( ) )
37
- task . output = ctx . tempDir
38
- fs . mkdirSync ( ctx . tempDir , { recursive : true } )
39
- } ,
40
- } ,
41
- {
42
- title : 'Collecting packages' ,
43
- rendererOptions : { persistentOutput : true } ,
44
- task : async ( ctx , task ) => {
45
- const list = await execa ( 'pnpm' , [
46
- 'list' ,
47
- '--json' ,
48
- '--recursive' ,
49
- '--only-projects' ,
50
- '--prod' ,
51
- '--filter' ,
52
- './packages/*' ,
53
- ] )
54
-
55
- ctx . packages = JSON . parse ( list . stdout ) as never
56
- ctx . packages = ctx . packages . filter ( pkg => ! pkg . private )
57
-
58
- const pwdsCommand = await execa ( 'pnpm' , [ 'recursive' , 'exec' , 'pwd' ] ) // use `pnpm recursive exec` to get the correct topological sort order // https://github.com/pnpm/pnpm/issues/7716
59
- const pwds = pwdsCommand . stdout
60
- . split ( '\n' )
61
- . map ( s => s . trim ( ) )
62
- . filter ( Boolean )
63
-
64
- ctx . packages
65
- . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) // sort alphabetically first, as a tiebreaker (`.sort` is stable)
66
- . sort ( ( a , b ) => pwds . indexOf ( a . path ) - pwds . indexOf ( b . path ) ) // then topologically
67
-
68
- ctx . packages . forEach ( ( pkg , i , { length} ) => {
69
- const number = Number ( `1${ '0' . repeat ( length . toString ( ) . length + 1 ) } ` ) + i
70
- pkg . folder = path . join ( ctx . tempDir , `${ number } .${ pkg . name . replace ( '/' , '__' ) } ` )
71
- } )
72
- task . output = ctx . packages . map ( pkg => `${ pkg . name } ` ) . join ( '\n' )
73
- return `Found ${ ctx . packages . length } packages to publish`
74
- } ,
75
- } ,
76
- {
77
- title : `Writing local packages` ,
78
- task : ( ctx , task ) => {
79
- return task . newListr (
80
- ctx . packages . map ( pkg => ( {
81
- title : `Packing ${ pkg . name } ` ,
82
- task : async ( _ctx , subtask ) => {
83
- const localFolder = path . join ( pkg . folder , 'local' )
84
- await pipeExeca ( subtask , 'pnpm' , [ 'pack' , '--pack-destination' , localFolder ] , { cwd : pkg . path } )
85
-
86
- const tgzFileName = fs . readdirSync ( localFolder ) . at ( 0 ) !
87
- await pipeExeca ( subtask , 'tar' , [ '-xvzf' , tgzFileName ] , { cwd : localFolder } )
88
- } ,
89
- } ) ) ,
90
- { concurrent : true } ,
91
- )
92
- } ,
93
- } ,
94
- {
95
- title : `Writing registry packages` ,
96
- task : ( ctx , task ) => {
97
- return task . newListr (
98
- ctx . packages . map ( pkg => ( {
99
- title : `Pulling ${ pkg . name } ` ,
100
- task : async ( _ctx , subtask ) => {
101
- const registryFolder = path . join ( pkg . folder , 'registry' )
102
- fs . mkdirSync ( registryFolder , { recursive : true } )
103
- // note: `npm pack foobar` will actually pull foobar.1-2-3.tgz from the registry. It's not actually doing a "pack" at all. `pnpm pack` does not do the same thing - it packs the local directory
104
- await pipeExeca ( subtask , 'npm' , [ 'pack' , pkg . name ] , {
105
- reject : false ,
106
- cwd : registryFolder ,
107
- } )
108
-
109
- const tgzFileName = fs . readdirSync ( registryFolder ) . at ( 0 )
110
- if ( ! tgzFileName ) {
111
- return
112
- }
113
-
114
- await pipeExeca ( subtask , 'tar' , [ '-xvzf' , tgzFileName ] , { cwd : registryFolder } )
115
-
116
- const registryPackageJson = loadRegistryPackageJson ( pkg )
117
- if ( registryPackageJson ) {
118
- const registryPackageJsonPath = packageJsonFilepath ( pkg , 'registry' )
119
- // avoid churn on package.json field ordering, which npm seems to mess with
120
- fs . writeFileSync (
121
- registryPackageJsonPath ,
122
- sortPackageJson ( JSON . stringify ( registryPackageJson , null , 2 ) ) ,
123
- )
124
- }
125
- } ,
126
- } ) ) ,
127
- { concurrent : true } ,
128
- )
129
- } ,
130
- } ,
23
+ ...setupContextTasks ,
131
24
{
132
25
title : 'Get version strategy' ,
133
26
rendererOptions : { persistentOutput : true } ,
@@ -351,6 +244,159 @@ export const publish = async (input: PublishInput) => {
351
244
await tasks . run ( )
352
245
}
353
246
247
+ export const ReleaseNotesInput = z . object ( {
248
+ baseComparisonSha : z . string ( ) . optional ( ) ,
249
+ } )
250
+ export type ReleaseNotesInput = z . infer < typeof ReleaseNotesInput >
251
+
252
+ // this doesn't work yet
253
+ export const releaseNotes = async ( input : ReleaseNotesInput ) => {
254
+ const tasks = new Listr (
255
+ [
256
+ ...setupContextTasks ,
257
+ {
258
+ title : 'Generate release notes' ,
259
+ task : async ( ctx , task ) => {
260
+ for ( const pkg of ctx . packages ) {
261
+ pkg . baseComparisonSha = input . baseComparisonSha
262
+ const body = await getOrCreateChangelog ( ctx , pkg )
263
+ const title = `${ pkg . name } @${ pkg . version } `
264
+ const message = `👇👇👇${ title } changelog👇👇👇\n\n${ body } \n\n👆👆👆${ title } changelog👆👆👆`
265
+ const doRelease = await task . prompt ( ListrEnquirerPromptAdapter ) . run < boolean > ( {
266
+ type : 'confirm' ,
267
+ message : message + '\n\nDraft relesae?' ,
268
+ initial : false ,
269
+ } )
270
+ if ( doRelease ) {
271
+ const releaseParams = { title, body}
272
+ await execa ( 'open' , [
273
+ `https://github.com/mmkal/pgkit/releases/new?${ new URLSearchParams ( releaseParams ) . toString ( ) } ` ,
274
+ ] )
275
+ }
276
+ }
277
+ } ,
278
+ } ,
279
+ ] ,
280
+ { ctx : { } as Ctx } ,
281
+ )
282
+
283
+ await tasks . run ( )
284
+ }
285
+
286
+ export const setupContextTasks : ListrTask < Ctx > [ ] = [
287
+ {
288
+ title : 'Set working directory' ,
289
+ task : async ( ) => {
290
+ const monorepoRoot = path . dirname ( findUpOrThrow ( 'pnpm-workspace.yaml' ) )
291
+ process . chdir ( monorepoRoot )
292
+ } ,
293
+ } ,
294
+ {
295
+ title : 'Building' ,
296
+ task : async ( _ctx , task ) => pipeExeca ( task , 'pnpm' , [ '-w' , 'build' ] ) ,
297
+ } ,
298
+ {
299
+ title : 'Get temp directory' ,
300
+ rendererOptions : { persistentOutput : true } ,
301
+ task : async ( ctx , task ) => {
302
+ const list = await execa ( 'pnpm' , [ 'list' , '--json' , '--depth' , '0' , '--filter' , '.' ] )
303
+ const pkgName = JSON . parse ( list . stdout ) ?. [ 0 ] ?. name as string | undefined
304
+ if ( ! pkgName ) throw new Error ( `Couldn't get package name from pnpm list output: ${ list . stdout } ` )
305
+ ctx . tempDir = path . join ( '/tmp/npmono' , pkgName , Date . now ( ) . toString ( ) )
306
+ task . output = ctx . tempDir
307
+ fs . mkdirSync ( ctx . tempDir , { recursive : true } )
308
+ } ,
309
+ } ,
310
+ {
311
+ title : 'Collecting packages' ,
312
+ rendererOptions : { persistentOutput : true } ,
313
+ task : async ( ctx , task ) => {
314
+ const list = await execa ( 'pnpm' , [
315
+ 'list' ,
316
+ '--json' ,
317
+ '--recursive' ,
318
+ '--only-projects' ,
319
+ '--prod' ,
320
+ '--filter' ,
321
+ './packages/*' ,
322
+ ] )
323
+
324
+ ctx . packages = JSON . parse ( list . stdout ) as never
325
+ ctx . packages = ctx . packages . filter ( pkg => ! pkg . private )
326
+
327
+ const pwdsCommand = await execa ( 'pnpm' , [ 'recursive' , 'exec' , 'pwd' ] ) // use `pnpm recursive exec` to get the correct topological sort order // https://github.com/pnpm/pnpm/issues/7716
328
+ const pwds = pwdsCommand . stdout
329
+ . split ( '\n' )
330
+ . map ( s => s . trim ( ) )
331
+ . filter ( Boolean )
332
+
333
+ ctx . packages
334
+ . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) // sort alphabetically first, as a tiebreaker (`.sort` is stable)
335
+ . sort ( ( a , b ) => pwds . indexOf ( a . path ) - pwds . indexOf ( b . path ) ) // then topologically
336
+
337
+ ctx . packages . forEach ( ( pkg , i , { length} ) => {
338
+ const number = Number ( `1${ '0' . repeat ( length . toString ( ) . length + 1 ) } ` ) + i
339
+ pkg . folder = path . join ( ctx . tempDir , `${ number } .${ pkg . name . replace ( '/' , '__' ) } ` )
340
+ } )
341
+ task . output = ctx . packages . map ( pkg => `${ pkg . name } ` ) . join ( '\n' )
342
+ return `Found ${ ctx . packages . length } packages to publish`
343
+ } ,
344
+ } ,
345
+ {
346
+ title : `Writing local packages` ,
347
+ task : ( ctx , task ) => {
348
+ return task . newListr (
349
+ ctx . packages . map ( pkg => ( {
350
+ title : `Packing ${ pkg . name } ` ,
351
+ task : async ( _ctx , subtask ) => {
352
+ const localFolder = path . join ( pkg . folder , 'local' )
353
+ await pipeExeca ( subtask , 'pnpm' , [ 'pack' , '--pack-destination' , localFolder ] , { cwd : pkg . path } )
354
+
355
+ const tgzFileName = fs . readdirSync ( localFolder ) . at ( 0 ) !
356
+ await pipeExeca ( subtask , 'tar' , [ '-xvzf' , tgzFileName ] , { cwd : localFolder } )
357
+ } ,
358
+ } ) ) ,
359
+ { concurrent : true } ,
360
+ )
361
+ } ,
362
+ } ,
363
+ {
364
+ title : `Writing registry packages` ,
365
+ task : ( ctx , task ) => {
366
+ return task . newListr (
367
+ ctx . packages . map ( pkg => ( {
368
+ title : `Pulling ${ pkg . name } ` ,
369
+ task : async ( _ctx , subtask ) => {
370
+ const { sortPackageJson} = await import ( 'sort-package-json' )
371
+ const registryFolder = path . join ( pkg . folder , 'registry' )
372
+ fs . mkdirSync ( registryFolder , { recursive : true } )
373
+ // note: `npm pack foobar` will actually pull foobar.1-2-3.tgz from the registry. It's not actually doing a "pack" at all. `pnpm pack` does not do the same thing - it packs the local directory
374
+ await pipeExeca ( subtask , 'npm' , [ 'pack' , pkg . name ] , {
375
+ reject : false ,
376
+ cwd : registryFolder ,
377
+ } )
378
+
379
+ const tgzFileName = fs . readdirSync ( registryFolder ) . at ( 0 )
380
+ if ( ! tgzFileName ) {
381
+ return
382
+ }
383
+
384
+ await pipeExeca ( subtask , 'tar' , [ '-xvzf' , tgzFileName ] , { cwd : registryFolder } )
385
+
386
+ const registryPackageJson = loadRegistryPackageJson ( pkg )
387
+ if ( registryPackageJson ) {
388
+ const registryPackageJsonPath = packageJsonFilepath ( pkg , 'registry' )
389
+ // avoid churn on package.json field ordering, which npm seems to mess with
390
+ fs . writeFileSync ( registryPackageJsonPath , sortPackageJson ( JSON . stringify ( registryPackageJson , null , 2 ) ) )
391
+ }
392
+ } ,
393
+ } ) ) ,
394
+ { concurrent : true } ,
395
+ )
396
+ } ,
397
+ } ,
398
+ ]
399
+
354
400
const packageJsonFilepath = ( pkg : PkgMeta , type : 'local' | 'registry' ) =>
355
401
path . join ( pkg . folder , type , 'package' , 'package.json' )
356
402
@@ -392,7 +438,7 @@ const bumpChoices = (oldVersion: string) => {
392
438
393
439
/** Pessimistic comparison ref. Tries to use the registry package.json's `git.sha` property, and uses the first ever commit to the package folder if that can't be found. */
394
440
async function getPackageLastPublishRef ( pkg : Pkg ) {
395
- const registryRef = loadRegistryPackageJson ( pkg ) ?. git ?. sha
441
+ const registryRef = pkg . baseComparisonSha || loadRegistryPackageJson ( pkg ) ?. git ?. sha
396
442
if ( registryRef ) return registryRef
397
443
398
444
const { stdout : firstRef } = await execa ( 'git' , [ 'log' , '--reverse' , '-n' , '1' , '--pretty=format:%h' , '--' , '.' ] , {
@@ -633,6 +679,7 @@ type PkgMeta = {
633
679
folder : string
634
680
lastPublished : PackageJson | null
635
681
targetVersion : string | null
682
+ baseComparisonSha : string | undefined
636
683
}
637
684
638
685
// eslint-disable-next-line @typescript-eslint/no-explicit-any
0 commit comments