diff --git a/packages/publisher/src/calculate-versions.ts b/packages/publisher/src/calculate-versions.ts index 000e4d6ad4..77cebe1af3 100644 --- a/packages/publisher/src/calculate-versions.ts +++ b/packages/publisher/src/calculate-versions.ts @@ -42,9 +42,9 @@ async function computeAndSaveChangedPackages( async function computeChangedPackages(allPackages: AllPackages, log: LoggerWithErrors): Promise { log.info("# Computing changed packages..."); const changedTypings = await mapDefinedAsync(allPackages.allTypings(), async (pkg) => { - const { version, needsPublish } = await fetchTypesPackageVersionInfo(pkg, /*publish*/ true, log); - if (needsPublish) { - log.info(`Need to publish: ${pkg.desc}@${version}`); + const { incipientVersion } = await fetchTypesPackageVersionInfo(pkg, log); + if (incipientVersion) { + log.info(`Need to publish: ${pkg.desc}@${incipientVersion}`); for (const { name } of pkg.packageJsonDependencies) { await pacote.manifest(name, { cache: defaultCacheDir }).catch((cause) => { throw cause.code === "E404" @@ -57,8 +57,12 @@ async function computeChangedPackages(allPackages: AllPackages, log: LoggerWithE } const latestVersion = pkg.isLatest ? undefined - : (await fetchTypesPackageVersionInfo(allPackages.getLatest(pkg), /*publish*/ true)).version; - return { pkg, version, latestVersion }; + : await fetchTypesPackageVersionInfo(allPackages.getLatest(pkg), log); + return { + pkg, + version: incipientVersion, + latestVersion: latestVersion?.incipientVersion || latestVersion?.publishedVersion, + }; } return undefined; }); diff --git a/packages/retag/src/index.ts b/packages/retag/src/index.ts index 453ec04db1..6f65274eea 100644 --- a/packages/retag/src/index.ts +++ b/packages/retag/src/index.ts @@ -62,14 +62,18 @@ async function tag(dry: boolean, nProcesses: number, name?: string) { if (name) { const pkg = await AllPackages.readSingle(name); const version = await getLatestTypingVersion(pkg); - await updateTypeScriptVersionTags(pkg, version, publishClient, consoleLogger.info, dry); - await updateLatestTag(pkg.fullNpmName, version, publishClient, consoleLogger.info, dry); + if (version) { + await updateTypeScriptVersionTags(pkg, version, publishClient, consoleLogger.info, dry); + await updateLatestTag(pkg.fullNpmName, version, publishClient, consoleLogger.info, dry); + } } else { await nAtATime(10, await AllPackages.readLatestTypings(), async (pkg) => { // Only update tags for the latest version of the package. const version = await getLatestTypingVersion(pkg); - await updateTypeScriptVersionTags(pkg, version, publishClient, consoleLogger.info, dry); - await updateLatestTag(pkg.fullNpmName, version, publishClient, consoleLogger.info, dry); + if (version) { + await updateTypeScriptVersionTags(pkg, version, publishClient, consoleLogger.info, dry); + await updateLatestTag(pkg.fullNpmName, version, publishClient, consoleLogger.info, dry); + } }); } // Don't tag notNeeded packages @@ -109,15 +113,20 @@ export async function updateLatestTag( } } -export async function getLatestTypingVersion(pkg: TypingsData): Promise { - return (await fetchTypesPackageVersionInfo(pkg, /*publish*/ false)).version; +export async function getLatestTypingVersion(pkg: TypingsData): Promise { + return (await fetchTypesPackageVersionInfo(pkg)).publishedVersion; } +/** + * Used for two purposes: to determine whether a @types package has changed since it was last published, and to get a package's version in the npm registry. + * We ignore whether the cached metadata is fresh or stale: We always revalidate if the content hashes differ (fresh or not) and never revalidate if they match (stale or not). + * Because the decider is the content hash, this isn't applicable to other npm packages. + * Target JS packages and not-needed stubs don't have content hashes. + */ export async function fetchTypesPackageVersionInfo( pkg: TypingsData, - canPublish: boolean, log?: LoggerWithErrors -): Promise<{ version: string; needsPublish: boolean }> { +): Promise<{ publishedVersion?: string; incipientVersion?: string }> { const spec = `${pkg.fullNpmName}@~${pkg.major}.${pkg.minor}`; let info = await pacote .manifest(spec, { cache: defaultCacheDir, fullMetadata: true, preferOffline: true }) @@ -125,7 +134,7 @@ export async function fetchTypesPackageVersionInfo( if (cause.code !== "E404" && cause.code !== "ETARGET") throw cause; }); if (!info) { - return { version: `${pkg.major}.${pkg.minor}.0`, needsPublish: true }; + return { incipientVersion: `${pkg.major}.${pkg.minor}.0` }; } if (info._cached && info.typesPublisherContentHash !== pkg.contentHash) { if (log) { @@ -141,6 +150,10 @@ export async function fetchTypesPackageVersionInfo( `Package ${pkg.name} has been deprecated, so we shouldn't have parsed it. Was it re-added?` ); } - const needsPublish = canPublish && pkg.contentHash !== info.typesPublisherContentHash; - return { version: needsPublish ? semver.inc(info.version, "patch")! : info.version, needsPublish }; + return { + publishedVersion: info.version, + ...(((pkg.contentHash === info.typesPublisherContentHash) as {}) || { + incipientVersion: semver.inc(info.version, "patch")!, + }), + }; } diff --git a/packages/retag/test/index.test.ts b/packages/retag/test/index.test.ts new file mode 100644 index 0000000000..3736f35873 --- /dev/null +++ b/packages/retag/test/index.test.ts @@ -0,0 +1,52 @@ +import * as util from "util"; +import { fetchTypesPackageVersionInfo } from "../src/"; + +jest.mock("pacote", () => ({ + async manifest(spec: string) { + switch (spec) { + case "@types/already-published@~1.2": // An already-published @types package. + return { version: "1.2.3", typesPublisherContentHash: "already-published-content-hash" }; + case "@types/first-publish@~1.2": // A new, not-yet-published @types package. + // eslint-disable-next-line no-throw-literal + throw { code: "E404" }; + } + throw new Error(`Unexpected npm registry fetch: ${util.inspect(spec)}`); + }, +})); + +const unchangedTypesPackage = { + fullNpmName: "@types/already-published", + major: 1, + minor: 2, + contentHash: "already-published-content-hash", +}; +const changedTypesPackage = { + fullNpmName: "@types/already-published", + major: 1, + minor: 2, + contentHash: "changed-content-hash", +}; +const firstPublishTypesPackage = { + fullNpmName: "@types/first-publish", + major: 1, + minor: 2, +}; + +test("Increments already-published patch version", () => { + return expect(fetchTypesPackageVersionInfo(changedTypesPackage as never)).resolves.toEqual({ + publishedVersion: "1.2.3", + incipientVersion: "1.2.4", + }); +}); + +test("Doesn't increment unchanged @types package version", () => { + return expect(fetchTypesPackageVersionInfo(unchangedTypesPackage as never)).resolves.toEqual({ + publishedVersion: "1.2.3", + }); +}); + +test("First-publish version", () => { + return expect(fetchTypesPackageVersionInfo(firstPublishTypesPackage as never)).resolves.toEqual({ + incipientVersion: "1.2.0", + }); +});