Skip to content

Commit 42fe680

Browse files
committed
Handle partial publish recovery for tags and releases
1 parent 82bbb07 commit 42fe680

File tree

1 file changed

+101
-34
lines changed

1 file changed

+101
-34
lines changed

scripts/publish.ts

Lines changed: 101 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ interface PublishSummary {
5252
}>
5353
}
5454

55+
interface LocalPackage {
56+
dirName: string
57+
npmName: string
58+
localVersion: string
59+
}
60+
5561
/**
5662
* Read published packages from pnpm's publish summary file.
5763
* See https://pnpm.io/cli/publish#--report-summary
@@ -74,6 +80,32 @@ function readPublishSummary(): PublishedPackage[] {
7480
}))
7581
}
7682

83+
/**
84+
* Get local package metadata from the workspace.
85+
*/
86+
function getLocalPackages(): LocalPackage[] {
87+
let packageDirNames = getAllPackageDirNames()
88+
let localPackages: LocalPackage[] = []
89+
90+
for (let packageDirName of packageDirNames) {
91+
let packageJsonPath = getPackageFile(packageDirName, 'package.json')
92+
93+
// Skip directories without a package.json
94+
if (!fileExists(packageJsonPath)) {
95+
continue
96+
}
97+
98+
let packageJson = readJson(packageJsonPath)
99+
localPackages.push({
100+
dirName: packageDirName,
101+
npmName: packageJson.name as string,
102+
localVersion: packageJson.version as string,
103+
})
104+
}
105+
106+
return localPackages
107+
}
108+
77109
/**
78110
* Check if a specific version of a package is published on npm.
79111
*/
@@ -90,35 +122,11 @@ async function isVersionPublished(packageName: string, version: string): Promise
90122
})
91123
}
92124

93-
interface LocalPackage {
94-
dirName: string
95-
npmName: string
96-
localVersion: string
97-
}
98-
99125
/**
100126
* Get all packages that have versions not yet published to npm.
101127
*/
102128
async function getUnpublishedPackages(): Promise<PublishedPackage[]> {
103-
let packageDirNames = getAllPackageDirNames()
104-
105-
// Collect all local package info first
106-
let localPackages: LocalPackage[] = []
107-
for (let packageDirName of packageDirNames) {
108-
let packageJsonPath = getPackageFile(packageDirName, 'package.json')
109-
110-
// Skip directories without a package.json
111-
if (!fileExists(packageJsonPath)) {
112-
continue
113-
}
114-
115-
let packageJson = readJson(packageJsonPath)
116-
localPackages.push({
117-
dirName: packageDirName,
118-
npmName: packageJson.name as string,
119-
localVersion: packageJson.version as string,
120-
})
121-
}
129+
let localPackages = getLocalPackages()
122130

123131
// Query npm for all packages in parallel
124132
let npmResults = await Promise.all(
@@ -143,6 +151,54 @@ async function getUnpublishedPackages(): Promise<PublishedPackage[]> {
143151
return unpublished
144152
}
145153

154+
/**
155+
* Find package versions that are already published to npm but missing local git tags.
156+
* This enables release recovery after partial publish failures.
157+
*/
158+
async function getPublishedPackagesMissingTags(): Promise<PublishedPackage[]> {
159+
let localPackages = getLocalPackages()
160+
161+
let npmResults = await Promise.all(
162+
localPackages.map(async (pkg) => ({
163+
pkg,
164+
isPublished: await isVersionPublished(pkg.npmName, pkg.localVersion),
165+
})),
166+
)
167+
168+
let missingTags: PublishedPackage[] = []
169+
for (let { pkg, isPublished } of npmResults) {
170+
if (!isPublished) {
171+
continue
172+
}
173+
174+
let tag = getGitTag(pkg.npmName, pkg.localVersion)
175+
if (!tagExists(tag)) {
176+
missingTags.push({
177+
packageName: pkg.npmName,
178+
version: pkg.localVersion,
179+
tag,
180+
})
181+
}
182+
}
183+
184+
return missingTags
185+
}
186+
187+
function dedupePublishedPackages(packages: PublishedPackage[]): PublishedPackage[] {
188+
let seenTags = new Set<string>()
189+
let deduped: PublishedPackage[] = []
190+
191+
for (let pkg of packages) {
192+
if (seenTags.has(pkg.tag)) {
193+
continue
194+
}
195+
seenTags.add(pkg.tag)
196+
deduped.push(pkg)
197+
}
198+
199+
return deduped
200+
}
201+
146202
interface ChangelogWarning {
147203
packageName: string
148204
version: string
@@ -300,14 +356,23 @@ async function main() {
300356
return
301357
}
302358

303-
if (published.length === 0) {
304-
console.log('\nNo packages were published.')
305-
return
359+
if (published.length > 0) {
360+
console.log(`\n${published.length} package${published.length === 1 ? '' : 's'} published:`)
361+
for (let pkg of published) {
362+
console.log(` • ${pkg.packageName}@${pkg.version}`)
363+
}
364+
} else {
365+
console.log('\nNo new packages were published.')
306366
}
307367

308-
console.log(`\n${published.length} package${published.length === 1 ? '' : 's'} published:`)
309-
for (let pkg of published) {
310-
console.log(` • ${pkg.packageName}@${pkg.version}`)
368+
let packagesNeedingTagsOrReleases = dedupePublishedPackages([
369+
...published,
370+
...(await getPublishedPackagesMissingTags()),
371+
])
372+
373+
if (packagesNeedingTagsOrReleases.length === 0) {
374+
console.log('\nNo packages need git tags or GitHub releases.')
375+
return
311376
}
312377

313378
// Configure git
@@ -316,9 +381,11 @@ async function main() {
316381
logAndExec('git config user.email "hello@remix.run"')
317382

318383
// Create tags (skip if already exist)
319-
console.log(`\nCreating tag${published.length === 1 ? '' : 's'}...`)
384+
console.log(
385+
`\nCreating tag${packagesNeedingTagsOrReleases.length === 1 ? '' : 's'} for published packages...`,
386+
)
320387
let tagsCreated = 0
321-
for (let pkg of published) {
388+
for (let pkg of packagesNeedingTagsOrReleases) {
322389
if (tagExists(pkg.tag)) {
323390
console.log(` ⊘ ${pkg.tag} (already exists)`)
324391
} else {
@@ -340,7 +407,7 @@ async function main() {
340407
console.log('\nCreating GitHub releases...')
341408
let failedReleases: Array<{ pkg: PublishedPackage; error: string }> = []
342409

343-
for (let pkg of published) {
410+
for (let pkg of packagesNeedingTagsOrReleases) {
344411
let result = await createRelease(pkg.packageName, pkg.version)
345412
if (result.status === 'created') {
346413
console.log(` ✓ ${pkg.packageName} v${pkg.version}`)

0 commit comments

Comments
 (0)