Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/eight-pugs-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@definitelytyped/publisher": patch
"@definitelytyped/retag": patch
"@definitelytyped/utils": patch
---

Publish old versions without touching latest tag
3 changes: 3 additions & 0 deletions packages/publisher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
"@definitelytyped/header-parser": "workspace:*",
"@definitelytyped/retag": "workspace:*",
"@definitelytyped/utils": "workspace:*",
"libnpmpublish": "^9.0.3",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this change would let us stop depending on npm-registry-publish, but it just adds the new dependency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's half a change; this PR removed some of the registry dep but not all.

"longjohn": "^0.2.12",
"pacote": "^17.0.4",
"semver": "^7.5.4",
"typescript": "^5.2.2",
"yargs": "^17.7.2"
},
"devDependencies": {
"@npm/types": "^1.0.2",
"@types/libnpmpublish": "^4.0.3",
"@types/pacote": "^11.1.8",
"@types/semver": "^7.5.5",
"@types/yargs": "^17.0.31"
Expand Down
9 changes: 2 additions & 7 deletions packages/publisher/src/calculate-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ async function computeAndSaveChangedPackages(
): Promise<ChangedPackages> {
const cp = await computeChangedPackages(allPackages, log);
const json: ChangedPackagesJson = {
changedTypings: cp.changedTypings.map(
({ pkg: { id }, version, latestVersion }): ChangedTypingJson => ({ id, version, latestVersion }),
),
changedTypings: cp.changedTypings.map(({ pkg: { id }, version }): ChangedTypingJson => ({ id, version })),
changedNotNeededPackages: cp.changedNotNeededPackages.map((p) => p.typesDirectoryName),
};
await writeDataFile(versionsFilename, json);
Expand Down Expand Up @@ -80,10 +78,7 @@ async function computeChangedPackages(allPackages: AllPackages, log: LoggerWithE
: reason;
});
}
const latestVersion = pkg.isLatest
? undefined
: (await fetchTypesPackageVersionInfo(await allPackages.getLatest(pkg), /*publish*/ true)).version;
return { pkg, version, latestVersion };
return { pkg, version };
}
return undefined;
});
Expand Down
3 changes: 1 addition & 2 deletions packages/publisher/src/generate-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
nAtATime,
writeFile,
writeLog,
writeTgz,
} from "@definitelytyped/utils";
import * as pacote from "pacote";
import { defaultLocalOptions } from "./lib/common";
Expand Down Expand Up @@ -62,7 +61,7 @@ export default async function generatePackages(dt: FS, changedPackages: ChangedP
await nAtATime(10, changedPackages.changedTypings, async ({ pkg, version }) => {
await generateTypingPackage(pkg, version, dt);
if (tgz) {
await writeTgz(outputDirectory(pkg), `${outputDirectory(pkg)}.tgz`);
await pacote.tarball.file(outputDirectory(pkg), `${outputDirectory(pkg)}.tgz`);
}
log(` * ${pkg.desc}`);
});
Expand Down
38 changes: 21 additions & 17 deletions packages/publisher/src/lib/package-publisher.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
import assert = require("assert");
import { Logger, joinPaths, readFileAndWarn, NpmPublishClient } from "@definitelytyped/utils";
import { Logger, NpmPublishClient } from "@definitelytyped/utils";
import { NotNeededPackage, AnyPackage } from "@definitelytyped/definitions-parser";
import { updateTypeScriptVersionTags, updateLatestTag } from "@definitelytyped/retag";
import { updateTypeScriptVersionTags } from "@definitelytyped/retag";
import * as libpub from "libnpmpublish";
import * as pacote from "pacote";
import { ChangedTyping } from "./versions";
import { outputDirectory } from "../util/util";

export async function publishTypingsPackage(
client: NpmPublishClient,
changedTyping: ChangedTyping,
token: string,
dry: boolean,
log: Logger,
): Promise<void> {
const { pkg, version, latestVersion } = changedTyping;
await common(client, pkg, log, dry);
const { pkg, version } = changedTyping;
await common(pkg, token, dry);
if (pkg.isLatest) {
await updateTypeScriptVersionTags(pkg, version, client, log, dry);
}
assert((latestVersion === undefined) === pkg.isLatest);
if (latestVersion !== undefined) {
// If this is an older version of the package, we still update tags for the *latest*.
// NPM will update "latest" even if we are publishing an older version of a package (https://github.com/npm/npm/issues/6778),
// so we must undo that by re-tagging latest.
await updateLatestTag(pkg.name, latestVersion, client, log, dry);
}
}

export async function publishNotNeededPackage(
client: NpmPublishClient,
pkg: NotNeededPackage,
token: string,
dry: boolean,
log: Logger,
): Promise<void> {
log(`Deprecating ${pkg.name}`);
await common(client, pkg, log, dry);
await common(pkg, token, dry);
}

async function common(client: NpmPublishClient, pkg: AnyPackage, log: Logger, dry: boolean): Promise<void> {
async function common(pkg: AnyPackage, token: string, dry: boolean): Promise<void> {
const packageDir = outputDirectory(pkg);
const packageJson = await readFileAndWarn("generate", joinPaths(packageDir, "package.json"));
await client.publish(packageDir, packageJson, dry, log);
const manifest = await pacote.manifest(packageDir).catch((reason) => {
throw reason.code === "ENOENT" ? new Error("Run generate first!", { cause: reason }) : reason;
});
const tarData = await pacote.tarball(packageDir);
// Make sure we never assign the latest tag to an old version.
if (!dry)
await libpub.publish(manifest ? { ...manifest, bundledDependencies: undefined } : manifest, tarData, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why might manifest be undefined? Should we publish if it's missing?

I noticed this because I'd prefer to pull out a manifest.bundledDependencies = undefined instead of a spread, but that doesn't work if it's possibly undefined.

defaultTag: pkg.isLatest ? "latest" : "",
access: "public",
token,
});
}
6 changes: 1 addition & 5 deletions packages/publisher/src/lib/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export interface ChangedTyping {
readonly pkg: TypingsData;
/** This is the version to be published, meaning it's the version that doesn't exist yet. */
readonly version: string;
/** For a non-latest version, this is the latest version; publishing an old version updates the 'latest' tag and we want to change it back. */
readonly latestVersion: string | undefined;
}

export interface ChangedPackagesJson {
Expand All @@ -26,7 +24,6 @@ export interface ChangedPackagesJson {
export interface ChangedTypingJson {
readonly id: PackageId;
readonly version: string;
readonly latestVersion?: string;
}

export interface ChangedPackages {
Expand All @@ -38,10 +35,9 @@ export async function readChangedPackages(allPackages: AllPackages): Promise<Cha
const json = (await readDataFile("calculate-versions", versionsFilename)) as ChangedPackagesJson;
return {
changedTypings: await Promise.all(
json.changedTypings.map(async ({ id, version, latestVersion }) => ({
json.changedTypings.map(async ({ id, version }) => ({
pkg: await allPackages.getTypingsData(id),
version,
latestVersion,
})),
),
changedNotNeededPackages: json.changedNotNeededPackages.map((id) =>
Expand Down
7 changes: 4 additions & 3 deletions packages/publisher/src/publish-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ export default async function publishPackages(
log("=== Publishing packages ===");
}

const client = await NpmPublishClient.create(await getSecret(Secret.NPM_TYPES_TOKEN), undefined);
const token = await getSecret(Secret.NPM_TYPES_TOKEN);
const client = new NpmPublishClient(token);
Comment on lines +49 to +50
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

splitting this statement seems unnecessary


for (const cp of changedPackages.changedTypings) {
log(`Publishing ${cp.pkg.desc}...`);

await publishTypingsPackage(client, cp, dry, log);
await publishTypingsPackage(client, cp, token, dry, log);

const commits = (await queryGithub(
`repos/DefinitelyTyped/DefinitelyTyped/commits?path=types%2f${cp.pkg.subDirectoryPath}`,
Expand Down Expand Up @@ -116,7 +117,7 @@ export default async function publishPackages(

for (const n of changedPackages.changedNotNeededPackages) {
const target = await skipBadPublishes(n, log);
await publishNotNeededPackage(client, target, dry, log);
await publishNotNeededPackage(target, token, dry, log);
}

await writeLog("publishing.md", logResult());
Expand Down
19 changes: 10 additions & 9 deletions packages/publisher/src/publish-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
cacheDir,
isObject,
} from "@definitelytyped/utils";
import { PackageJson } from "@npm/types";
import * as libpub from "libnpmpublish";
import * as pacote from "pacote";
import * as semver from "semver";
// @ts-ignore
Expand Down Expand Up @@ -74,17 +76,16 @@ export default async function publishRegistry(dt: FS, allPackages: AllPackages,

const token = process.env.NPM_TOKEN!;

const publishClient = () => NpmPublishClient.create(token, { defaultTag: "next" });
if (maxVersion !== latestVersion) {
// There was an error in the last publish and types-registry wasn't validated.
// This may have just been due to a timeout, so test if types-registry@next is a subset of the one we're about to publish.
// If so, we should just update it to "latest" now.
log("Old version of types-registry was never tagged latest, so updating");
await validateIsSubset(readNotNeededPackages(dt), log);
await (await publishClient()).tag(typesRegistry, maxVersion, "latest", dry, log);
await new NpmPublishClient(token).tag(typesRegistry, maxVersion, "latest", dry, log);
} else if (latestContentHash !== newContentHash) {
log("New packages have been added, so publishing a new registry.");
await publish(await publishClient(), typesRegistry, packageJson, newVersion, dry, log);
await publish(new NpmPublishClient(token), packageJson, token, dry, log);
} else {
log("No new packages published, so no need to publish new registry.");
// Just making sure...
Expand Down Expand Up @@ -115,13 +116,13 @@ async function generate(registry: string, packageJson: {}): Promise<void> {

async function publish(
client: NpmPublishClient,
packageName: string,
packageJson: {},
version: string,
packageJson: PackageJson,
token: string,
dry: boolean,
log: Logger,
): Promise<void> {
await client.publish(registryOutputPath, packageJson, dry, log);
const tarData = await pacote.tarball(registryOutputPath);
if (!dry) await libpub.publish(packageJson, tarData, { defaultTag: "next", access: "public", token });
// Sleep for 60 seconds to let NPM update.
if (dry) {
log("(dry) Skipping 60 second sleep...");
Expand All @@ -131,7 +132,7 @@ async function publish(
}
// Don't set it as "latest" until *after* it's been validated.
await validate(log);
await client.tag(packageName, version, "latest", dry, log);
await client.tag(packageJson.name, packageJson.version, "latest", dry, log);
}

async function installForValidate(log: Logger): Promise<void> {
Expand Down Expand Up @@ -209,7 +210,7 @@ function assertJsonNewer(newer: { [s: string]: any }, older: { [s: string]: any
}
}

function generatePackageJson(name: string, version: string, typesPublisherContentHash: string): object {
function generatePackageJson(name: string, version: string, typesPublisherContentHash: string): PackageJson {
const json = {
name,
version,
Expand Down
49 changes: 49 additions & 0 deletions packages/publisher/test/package-publisher.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NotNeededPackage, TypingsData, createMockDT } from "@definitelytyped/definitions-parser";
import * as libpub from "libnpmpublish";
import { publishNotNeededPackage, publishTypingsPackage } from "../src/lib/package-publisher";

jest.mock("libnpmpublish");
jest.mock("pacote", () => ({ async manifest() {}, async tarball() {} }));

const client = { async tag() {} };

const dt = createMockDT();

const latestVersion = {
pkg: new TypingsData(dt.fs, { header: { name: "@types/some-package" } } as never, /*isLatest*/ true),
version: "1.2.3",
};
const oldVersion = {
pkg: new TypingsData(dt.fs, { header: { name: "@types/some-package" } } as never, /*isLatest*/ false),
version: "1.2.3",
};
const notNeeded = new NotNeededPackage("not-needed", "not-needed", "1.2.3");

function log() {}

test("Latest version gets latest tag", async () => {
await publishTypingsPackage(client as never, latestVersion, /*token*/ "", /*dry*/ false, log);
expect(libpub.publish).toHaveBeenLastCalledWith(/*manifest*/ undefined, /*tarData*/ undefined, {
defaultTag: "latest",
access: "public",
token: "",
});
});

test("Publishing old versions doesn't change the latest tag", async () => {
await publishTypingsPackage(client as never, oldVersion, /*token*/ "", /*dry*/ false, log);
expect(libpub.publish).toHaveBeenLastCalledWith(/*manifest*/ undefined, /*tarData*/ undefined, {
defaultTag: "",
access: "public",
token: "",
});
});

test("Not-needed package gets latest tag", async () => {
await publishNotNeededPackage(notNeeded, /*token*/ "", /*dry*/ false, log);
expect(libpub.publish).toHaveBeenLastCalledWith(/*manifest*/ undefined, /*tarData*/ undefined, {
defaultTag: "latest",
access: "public",
token: "",
});
});
19 changes: 1 addition & 18 deletions packages/retag/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,17 @@ async function tag(dry: boolean, definitelyTypedPath: string, name?: string) {
const dt = await getDefinitelyTyped(options, log);
const token = process.env.NPM_TOKEN as string;

const publishClient = await NpmPublishClient.create(token, {});
const publishClient = new NpmPublishClient(token);
if (name) {
const pkg = await AllPackages.readSingle(dt, name);
const version = await getLatestTypingVersion(pkg);
await updateTypeScriptVersionTags(pkg, version, publishClient, consoleLogger.info, dry);
await updateLatestTag(pkg.name, version, publishClient, consoleLogger.info, dry);
} else {
const allPackages = AllPackages.fromFS(dt);
await nAtATime(5, await allPackages.allLatestTypings(), 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.name, version, publishClient, consoleLogger.info, dry);
});
}
// Don't tag notNeeded packages
Expand All @@ -85,21 +83,6 @@ export async function updateTypeScriptVersionTags(
}
}

export async function updateLatestTag(
fullName: string,
version: string,
client: NpmPublishClient,
log: Logger,
dry: boolean,
): Promise<void> {
log(` but tag ${fullName}@${version} as "latest"`);
if (dry) {
log(' (dry) Skip move "latest" back to newest version');
} else {
await client.tag(fullName, version, "latest", dry, log);
}
}

export async function getLatestTypingVersion(pkg: TypingsData): Promise<string> {
return (await fetchTypesPackageVersionInfo(pkg, /*publish*/ false)).version;
}
Expand Down
1 change: 0 additions & 1 deletion packages/utils/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
logs
test/data/pack.tgz
2 changes: 0 additions & 2 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@
"@types/node": "^16.18.61",
"charm": "^1.0.2",
"minimatch": "^9.0.3",
"tar": "^6.2.0",
"tar-stream": "^3.1.6",
"which": "^4.0.0"
},
"devDependencies": {
"@types/charm": "^1.0.6",
"@types/tar": "^6.1.9",
"@types/tar-stream": "^3.1.3",
"@types/which": "^3.0.2",
"@qiwi/npm-types": "^1.0.3"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait, wasn't this PR supposed to get rid of the @qiwi dep?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one step, I have more changes I was building on this PR; probably I'll send a new one instead that includes this one.

Expand Down
Loading