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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@
"ts-jest": "^25.2.1",
"tslint": "^6.1.2",
"tslint-microsoft-contrib": "^6.2.0",
"typescript": "^4.7.4"
"typescript": "^4.6.4"
}
}
38 changes: 25 additions & 13 deletions packages/publisher/src/calculate-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "@definitelytyped/utils";
import { fetchTypesPackageVersionInfo } from "@definitelytyped/retag";
import * as pacote from "pacote";
import * as semver from "semver";

if (!module.parent) {
const log = loggerWithErrors()[0];
Expand All @@ -37,9 +38,9 @@ async function computeAndSaveChangedPackages(
const cp = await computeChangedPackages(allPackages, log);
const json: ChangedPackagesJson = {
changedTypings: cp.changedTypings.map(
({ pkg: { id }, version, latestVersion }): ChangedTypingJson => ({ id, version, latestVersion })
({ pkg: { name }, version, latestVersion }): ChangedTypingJson => ({ name, version, latestVersion })
),
changedNotNeededPackages: cp.changedNotNeededPackages.map((p) => p.name),
changedNotNeededPackages: cp.changedNotNeededPackages.map(({ pkg: { name }, version }) => ({ name, version })),
};
await writeDataFile(versionsFilename, json);
return cp;
Expand Down Expand Up @@ -72,7 +73,8 @@ async function computeChangedPackages(allPackages: AllPackages, log: LoggerWithE
});
log.info("# Computing deprecated packages...");
const changedNotNeededPackages = await mapDefinedAsync(allPackages.allNotNeeded(), async (pkg) => {
if (!(await isAlreadyDeprecated(pkg, log))) {
const incipientVersion = await fetchIncipientStubVersion(pkg, log);
if (incipientVersion) {
// Assert that dependencies (i.e. the replacement package) exist on npm.
// Also checked in checkNotNeededPackage().
await pacote.manifest(pkg.libraryName, { cache: cacheDir }).catch((reason) => {
Expand All @@ -81,20 +83,30 @@ async function computeChangedPackages(allPackages: AllPackages, log: LoggerWithE
: reason;
});
log.info(`To be deprecated: ${pkg.name}`);
return pkg;
return { pkg, version: incipientVersion };
}
return undefined;
});
return { changedTypings, changedNotNeededPackages };
}

async function isAlreadyDeprecated(pkg: NotNeededPackage, log: LoggerWithErrors): Promise<unknown> {
const offline = await pacote.manifest(pkg.fullNpmName, { cache: cacheDir, offline: true }).catch((reason) => {
if (reason.code !== "ENOTCACHED") throw reason;
return undefined;
});
if (offline?.deprecated) return offline.deprecated;
log.info(`Version info not cached for deprecated package ${pkg.desc}`);
const online = await pacote.manifest(pkg.fullNpmName, { cache: cacheDir, preferOnline: true });
return online.deprecated;
/**
* Return the version of the stub @types package we're about to publish and deprecate, if we haven't already deprecated that package.
* It's the max of the not-needed version and the max published version + 1, in case we already published a not-needed-versioned stub but failed to deprecate it, for whatever reason.
*/
export async function fetchIncipientStubVersion(pkg: NotNeededPackage, log: LoggerWithErrors) {
const packument = await revalidate();
if (packument.versions[packument["dist-tags"].latest].deprecated) return;
const maxVersion = semver.maxSatisfying(Object.keys(packument.versions), "*")!;
return String(semver.maxSatisfying([pkg.version, semver.inc(maxVersion, "patch")!], "*"));

async function revalidate() {
const offline = await pacote.packument(pkg.fullNpmName, { cache: cacheDir, offline: true }).catch((reason) => {
if (reason.code !== "ENOTCACHED") throw reason;
return undefined;
});
if (offline?.versions[offline["dist-tags"].latest].deprecated) return offline;
log.info(`Version info not cached for deprecated package ${pkg.desc}`);
return pacote.packument(pkg.fullNpmName, { cache: cacheDir, preferOnline: true });
}
}
15 changes: 6 additions & 9 deletions packages/publisher/src/generate-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
logger,
writeLog,
writeFile,
Logger,
cacheDir,
writeTgz,
} from "@definitelytyped/utils";
Expand All @@ -34,7 +33,6 @@ import {
import * as pacote from "pacote";
import { readChangedPackages, ChangedPackages } from "./lib/versions";
import { outputDirectory } from "./util/util";
import { skipBadPublishes } from "./lib/npm";

const mitLicense = readFileSync(joinPaths(__dirname, "..", "LICENSE"), "utf-8");

Expand Down Expand Up @@ -68,9 +66,9 @@ export default async function generatePackages(
log(` * ${pkg.desc}`);
}
log("## Generating deprecated packages");
for (const pkg of changedPackages.changedNotNeededPackages) {
for (const { pkg, version } of changedPackages.changedNotNeededPackages) {
log(` * ${pkg.libraryName}`);
await generateNotNeededPackage(pkg, log);
await generateNotNeededPackage(pkg, version);
}
await writeLog("package-generator.md", logResult());
}
Expand All @@ -92,12 +90,11 @@ async function generateTypingPackage(
);
}

async function generateNotNeededPackage(pkg: NotNeededPackage, log: Logger): Promise<void> {
pkg = await skipBadPublishes(pkg, log);
async function generateNotNeededPackage(pkg: NotNeededPackage, version: string): Promise<void> {
const info = await pacote.manifest(pkg.libraryName, { cache: cacheDir, fullMetadata: true });
const readme = `This is a stub types definition for ${getFullNpmName(pkg.name)} (${info.homepage}).\n
${pkg.libraryName} provides its own type definitions, so you don't need ${getFullNpmName(pkg.name)} installed!`;
await writeCommonOutputs(pkg, createNotNeededPackageJSON(pkg), readme);
await writeCommonOutputs(pkg, createNotNeededPackageJSON(pkg, version), readme);
}

async function writeCommonOutputs(pkg: AnyPackage, packageJson: string, readme: string): Promise<void> {
Expand Down Expand Up @@ -197,10 +194,10 @@ function dependencySemver(dependency: DependencyVersion): string {
return dependency === "*" ? dependency : "^" + formatTypingVersion(dependency);
}

export function createNotNeededPackageJSON(pkg: NotNeededPackage): string {
export function createNotNeededPackageJSON(pkg: NotNeededPackage, version: string): string {
const out = {
name: pkg.fullNpmName,
version: String(pkg.version),
version,
description: `Stub TypeScript definitions entry for ${pkg.libraryName}, which provides its own types definitions`,
main: "",
scripts: {},
Expand Down
21 changes: 0 additions & 21 deletions packages/publisher/src/lib/npm.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/publisher/src/lib/package-publisher.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import assert = require("assert");
import { Logger, joinPaths, readFileAndWarn, NpmPublishClient } from "@definitelytyped/utils";
import { NotNeededPackage, AnyPackage } from "@definitelytyped/definitions-parser";
import { NotNeededPackage, AnyPackage, TypingsData } from "@definitelytyped/definitions-parser";
import { updateTypeScriptVersionTags, updateLatestTag } from "@definitelytyped/retag";
import { ChangedTyping } from "./versions";
import { outputDirectory } from "../util/util";

export async function publishTypingsPackage(
client: NpmPublishClient,
changedTyping: ChangedTyping,
changedTyping: ChangedTyping<TypingsData>,
dry: boolean,
log: Logger
): Promise<void> {
Expand Down
34 changes: 15 additions & 19 deletions packages/publisher/src/lib/versions.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,46 @@
import { assertDefined } from "@definitelytyped/utils";
import {
AllPackages,
NotNeededPackage,
PackageId,
TypingsData,
readDataFile,
} from "@definitelytyped/definitions-parser";
import { AllPackages, NotNeededPackage, TypingsData, readDataFile } from "@definitelytyped/definitions-parser";
import * as semver from "semver";

export const versionsFilename = "versions.json";

export interface ChangedTyping {
readonly pkg: TypingsData;
export interface ChangedTyping<T> {
readonly pkg: T;
/** 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;
readonly latestVersion?: string;
}

export interface ChangedPackagesJson {
readonly changedTypings: readonly ChangedTypingJson[];
readonly changedNotNeededPackages: readonly string[];
readonly changedNotNeededPackages: readonly ChangedTypingJson[];
}

export interface ChangedTypingJson {
readonly id: PackageId;
readonly name: string;
readonly version: string;
readonly latestVersion?: string;
}

export interface ChangedPackages {
readonly changedTypings: readonly ChangedTyping[];
readonly changedNotNeededPackages: readonly NotNeededPackage[];
readonly changedTypings: readonly ChangedTyping<TypingsData>[];
readonly changedNotNeededPackages: readonly ChangedTyping<NotNeededPackage>[];
}

export async function readChangedPackages(allPackages: AllPackages): Promise<ChangedPackages> {
const json = (await readDataFile("calculate-versions", versionsFilename)) as ChangedPackagesJson;
return {
changedTypings: json.changedTypings.map(
({ id, version, latestVersion }): ChangedTyping => ({
pkg: allPackages.getTypingsData(id),
({ name, version, latestVersion }): ChangedTyping<TypingsData> => ({
pkg: allPackages.getTypingsData({ name, version: new semver.SemVer(version) }),
version,
latestVersion,
})
),
changedNotNeededPackages: json.changedNotNeededPackages.map((id) =>
assertDefined(allPackages.getNotNeededPackage(id))
),
changedNotNeededPackages: json.changedNotNeededPackages.map(({ name, version }) => ({
pkg: assertDefined(allPackages.getNotNeededPackage(name)),
version,
})),
};
}
6 changes: 2 additions & 4 deletions packages/publisher/src/publish-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
NpmPublishClient,
} from "@definitelytyped/utils";
import { readChangedPackages, ChangedPackages } from "./lib/versions";
import { skipBadPublishes } from "./lib/npm";
import { getSecret, Secret } from "./lib/secrets";

if (!module.parent) {
Expand Down Expand Up @@ -126,9 +125,8 @@ export default async function publishPackages(
}
}

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

await writeLog("publishing.md", logResult());
Expand Down
44 changes: 44 additions & 0 deletions packages/publisher/test/calculate-versions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as util from "util";
import { NotNeededPackage } from "@definitelytyped/definitions-parser";
import { fetchIncipientStubVersion } from "../src/calculate-versions";

jest.mock("pacote", () => ({
async packument(spec: string) {
switch (spec) {
case "@types/not-needed": // A not-needed @types package about to be deprecated.
return {
"dist-tags": { latest: "1.0.1" },
versions: { "1.0.0": {}, "1.0.1": {} },
};
case "@types/bad-publish": // A not-needed package after a bad publish.
return {
"dist-tags": { latest: "1.2.3" },
versions: { "1.0.0": {}, "1.0.1": {}, "1.2.3": {} },
};
case "@types/already-deprecated": // A successfully deprecated not-needed stub package.
return {
"dist-tags": { latest: "1.2.3" },
versions: { "1.0.0": {}, "1.0.1": {}, "1.2.3": { deprecated: "Contains built-in declarations." } },
};
}
throw new Error(`Unexpected npm registry fetch: ${util.inspect(spec)}`);
},
}));

const notNeeded = new NotNeededPackage("not-needed", "not-needed", "1.2.3");
const badPublish = new NotNeededPackage("bad-publish", "bad-publish", "1.2.3");
const alreadyDeprecated = new NotNeededPackage("already-deprecated", "already-deprecated", "1.2.3");

const log = { info() {}, error() {} };

test("Returns the not-needed version", () => {
return expect(fetchIncipientStubVersion(notNeeded, log)).resolves.toBe("1.2.3");
});

test("Skips bad publishes", () => {
return expect(fetchIncipientStubVersion(badPublish, log)).resolves.toBe("1.2.4");
});

test("Ignores already-deprecated packages", () => {
return expect(fetchIncipientStubVersion(alreadyDeprecated, log)).resolves.toBeUndefined();
});
4 changes: 2 additions & 2 deletions packages/publisher/test/generate-packages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ testo({
}`);
},
basicNotNeededPackageJson() {
const s = createNotNeededPackageJSON(createUnneededPackage());
const s = createNotNeededPackageJSON(createUnneededPackage(), "1.1.1");
expect(s).toEqual(`{
"name": "@types/absalom",
"version": "1.1.1",
Expand All @@ -153,7 +153,7 @@ testo({
},
scopedNotNeededPackageJson() {
const scopedUnneeded = new NotNeededPackage("google-cloud__pubsub", "@google-cloud/chubdub", "0.26.0");
const s = createNotNeededPackageJSON(scopedUnneeded);
const s = createNotNeededPackageJSON(scopedUnneeded, "0.26.0");
expect(s).toEqual(`{
"name": "@types/google-cloud__pubsub",
"version": "0.26.0",
Expand Down
Loading