Skip to content

Commit c3908b7

Browse files
committed
Move skipBadPublishes() -> calculate-versions
1 parent a19278f commit c3908b7

File tree

9 files changed

+1554
-1640
lines changed

9 files changed

+1554
-1640
lines changed

packages/publisher/src/calculate-versions.ts

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "@definitelytyped/utils";
1212
import { fetchTypesPackageVersionInfo } from "@definitelytyped/retag";
1313
import * as pacote from "pacote";
14+
import * as semver from "semver";
1415

1516
if (!module.parent) {
1617
const log = loggerWithErrors()[0];
@@ -31,9 +32,9 @@ async function computeAndSaveChangedPackages(
3132
const cp = await computeChangedPackages(allPackages, log);
3233
const json: ChangedPackagesJson = {
3334
changedTypings: cp.changedTypings.map(
34-
({ pkg: { id }, version, latestVersion }): ChangedTypingJson => ({ id, version, latestVersion })
35+
({ pkg: { name }, version, latestVersion }): ChangedTypingJson => ({ name, version, latestVersion })
3536
),
36-
changedNotNeededPackages: cp.changedNotNeededPackages.map((p) => p.name),
37+
changedNotNeededPackages: cp.changedNotNeededPackages.map(({ pkg: { name }, version }) => ({ name, version })),
3738
};
3839
await writeDataFile(versionsFilename, json);
3940
return cp;
@@ -64,37 +65,55 @@ async function computeChangedPackages(allPackages: AllPackages, log: LoggerWithE
6465
});
6566
log.info("# Computing deprecated packages...");
6667
const changedNotNeededPackages = await mapDefinedAsync(allPackages.allNotNeeded(), async (pkg) => {
67-
if (!(await isAlreadyDeprecated(pkg, log))) {
68+
const incipientVersion = await fetchIncipientStubVersion(pkg, log);
69+
if (incipientVersion) {
6870
await pacote.manifest(pkg.libraryName, { cache: defaultCacheDir }).catch((reason) => {
6971
throw reason.code === "E404"
7072
? new Error(`To deprecate '@types/${pkg.name}', '${pkg.libraryName}' must exist on npm.`, { cause: reason })
7173
: reason;
7274
});
7375
log.info(`To be deprecated: ${pkg.name}`);
74-
return pkg;
76+
return { pkg, version: incipientVersion };
7577
}
7678
return undefined;
7779
});
7880
return { changedTypings, changedNotNeededPackages };
7981
}
8082

81-
async function isAlreadyDeprecated(pkg: NotNeededPackage, log: LoggerWithErrors): Promise<unknown> {
82-
const offline = await pacote
83-
.manifest(pkg.fullNpmName, {
83+
/**
84+
* Return the version of the stub @types package we're about to publish and deprecate, if we haven't already deprecated that package.
85+
* 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.
86+
*/
87+
export async function fetchIncipientStubVersion(pkg: NotNeededPackage, log: LoggerWithErrors): Promise<string | false> {
88+
const packument = await revalidate();
89+
return (
90+
!packument.deprecated &&
91+
String(
92+
semver.maxSatisfying(
93+
[pkg.version, semver.inc(semver.maxSatisfying(Object.keys(packument.versions), "*")!, "patch")!],
94+
"*"
95+
)
96+
)
97+
);
98+
99+
async function revalidate() {
100+
const offline = await pacote
101+
.packument(pkg.fullNpmName, {
102+
cache: defaultCacheDir,
103+
fullMetadata: true,
104+
offline: true,
105+
})
106+
.catch((reason) => {
107+
if (reason.code !== "ENOTCACHED") throw reason;
108+
return undefined;
109+
});
110+
if (offline?.deprecated) return offline;
111+
log.info(`Version info not cached for deprecated package ${pkg.desc}`);
112+
const online = await pacote.packument(pkg.fullNpmName, {
84113
cache: defaultCacheDir,
85114
fullMetadata: true,
86-
offline: true,
87-
})
88-
.catch((reason) => {
89-
if (reason.code !== "ENOTCACHED") throw reason;
90-
return undefined;
115+
preferOnline: true,
91116
});
92-
if (offline?.deprecated) return offline.deprecated;
93-
log.info(`Version info not cached for deprecated package ${pkg.desc}`);
94-
const online = await pacote.manifest(pkg.fullNpmName, {
95-
cache: defaultCacheDir,
96-
fullMetadata: true,
97-
preferOnline: true,
98-
});
99-
return online.deprecated;
117+
return online;
118+
}
100119
}

packages/publisher/src/generate-packages.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
logger,
1616
writeLog,
1717
writeFile,
18-
Logger,
1918
defaultCacheDir,
2019
writeTgz,
2120
} from "@definitelytyped/utils";
@@ -34,7 +33,6 @@ import {
3433
import * as pacote from "pacote";
3534
import { readChangedPackages, ChangedPackages } from "./lib/versions";
3635
import { outputDirectory } from "./util/util";
37-
import { skipBadPublishes } from "./lib/npm";
3836

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

@@ -68,9 +66,9 @@ export default async function generatePackages(
6866
log(` * ${pkg.desc}`);
6967
}
7068
log("## Generating deprecated packages");
71-
for (const pkg of changedPackages.changedNotNeededPackages) {
69+
for (const { pkg, version } of changedPackages.changedNotNeededPackages) {
7270
log(` * ${pkg.libraryName}`);
73-
await generateNotNeededPackage(pkg, log);
71+
await generateNotNeededPackage(pkg, version);
7472
}
7573
await writeLog("package-generator.md", logResult());
7674
}
@@ -92,12 +90,11 @@ async function generateTypingPackage(
9290
);
9391
}
9492

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

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

200-
export function createNotNeededPackageJSON({ libraryName, license, fullNpmName, version }: NotNeededPackage): string {
197+
export function createNotNeededPackageJSON(
198+
{ libraryName, license, fullNpmName }: NotNeededPackage,
199+
version: string
200+
): string {
201201
const out = {
202202
name: fullNpmName,
203-
version: String(version),
203+
version,
204204
typings: null, // tslint:disable-line no-null-keyword
205205
description: `Stub TypeScript definitions entry for ${libraryName}, which provides its own types definitions`,
206206
main: "",

packages/publisher/src/lib/npm.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

packages/publisher/src/lib/package-publisher.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import assert = require("assert");
22
import { Logger, joinPaths, readFileAndWarn, NpmPublishClient } from "@definitelytyped/utils";
3-
import { NotNeededPackage, AnyPackage } from "@definitelytyped/definitions-parser";
3+
import { NotNeededPackage, AnyPackage, TypingsData } from "@definitelytyped/definitions-parser";
44
import { updateTypeScriptVersionTags, updateLatestTag } from "@definitelytyped/retag";
55
import { ChangedTyping } from "./versions";
66
import { outputDirectory } from "../util/util";
77

88
export async function publishTypingsPackage(
99
client: NpmPublishClient,
10-
changedTyping: ChangedTyping,
10+
changedTyping: ChangedTyping<TypingsData>,
1111
dry: boolean,
1212
log: Logger
1313
): Promise<void> {
@@ -28,13 +28,14 @@ export async function publishTypingsPackage(
2828
export async function publishNotNeededPackage(
2929
client: NpmPublishClient,
3030
pkg: NotNeededPackage,
31+
version: string,
3132
dry: boolean,
3233
log: Logger
3334
): Promise<void> {
3435
log(`Deprecating ${pkg.name}`);
3536
await common(client, pkg, log, dry);
3637
// Don't use a newline in the deprecation message because it will be displayed as "\n" and not as a newline.
37-
await deprecateNotNeededPackage(client, pkg, dry, log);
38+
await deprecateNotNeededPackage(client, pkg, version, dry, log);
3839
}
3940

4041
async function common(client: NpmPublishClient, pkg: AnyPackage, log: Logger, dry: boolean): Promise<void> {
@@ -46,14 +47,15 @@ async function common(client: NpmPublishClient, pkg: AnyPackage, log: Logger, dr
4647
export async function deprecateNotNeededPackage(
4748
client: NpmPublishClient,
4849
pkg: NotNeededPackage,
50+
version: string,
4951
dry = false,
5052
log: Logger
5153
): Promise<void> {
5254
const name = pkg.fullNpmName;
5355
if (dry) {
54-
log("(dry) Skip deprecate not needed package " + name + " at " + pkg.version);
56+
log("(dry) Skip deprecate not needed package " + name + " at " + version);
5557
} else {
56-
log(`Deprecating ${name} at ${pkg.version} with message: ${pkg.deprecatedMessage()}.`);
57-
await client.deprecate(name, String(pkg.version), pkg.deprecatedMessage());
58+
log(`Deprecating ${name} at ${version} with message: ${pkg.deprecatedMessage()}.`);
59+
await client.deprecate(name, version, pkg.deprecatedMessage());
5860
}
5961
}
Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,46 @@
11
import { assertDefined } from "@definitelytyped/utils";
2-
import {
3-
AllPackages,
4-
NotNeededPackage,
5-
PackageId,
6-
TypingsData,
7-
readDataFile,
8-
} from "@definitelytyped/definitions-parser";
2+
import { AllPackages, NotNeededPackage, TypingsData, readDataFile } from "@definitelytyped/definitions-parser";
3+
import * as semver from "semver";
94

105
export const versionsFilename = "versions.json";
116

12-
export interface ChangedTyping {
13-
readonly pkg: TypingsData;
7+
export interface ChangedTyping<T> {
8+
readonly pkg: T;
149
/** This is the version to be published, meaning it's the version that doesn't exist yet. */
1510
readonly version: string;
1611
/** 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. */
17-
readonly latestVersion: string | undefined;
12+
readonly latestVersion?: string;
1813
}
1914

2015
export interface ChangedPackagesJson {
2116
readonly changedTypings: readonly ChangedTypingJson[];
22-
readonly changedNotNeededPackages: readonly string[];
17+
readonly changedNotNeededPackages: readonly ChangedTypingJson[];
2318
}
2419

2520
export interface ChangedTypingJson {
26-
readonly id: PackageId;
21+
readonly name: string;
2722
readonly version: string;
2823
readonly latestVersion?: string;
2924
}
3025

3126
export interface ChangedPackages {
32-
readonly changedTypings: readonly ChangedTyping[];
33-
readonly changedNotNeededPackages: readonly NotNeededPackage[];
27+
readonly changedTypings: readonly ChangedTyping<TypingsData>[];
28+
readonly changedNotNeededPackages: readonly ChangedTyping<NotNeededPackage>[];
3429
}
3530

3631
export async function readChangedPackages(allPackages: AllPackages): Promise<ChangedPackages> {
3732
const json = (await readDataFile("calculate-versions", versionsFilename)) as ChangedPackagesJson;
3833
return {
3934
changedTypings: json.changedTypings.map(
40-
({ id, version, latestVersion }): ChangedTyping => ({
41-
pkg: allPackages.getTypingsData(id),
35+
({ name, version, latestVersion }): ChangedTyping<TypingsData> => ({
36+
pkg: allPackages.getTypingsData({ name, version: new semver.SemVer(version) }),
4237
version,
4338
latestVersion,
4439
})
4540
),
46-
changedNotNeededPackages: json.changedNotNeededPackages.map((id) =>
47-
assertDefined(allPackages.getNotNeededPackage(id))
48-
),
41+
changedNotNeededPackages: json.changedNotNeededPackages.map(({ name, version }) => ({
42+
pkg: assertDefined(allPackages.getNotNeededPackage(name)),
43+
version,
44+
})),
4945
};
5046
}

packages/publisher/src/publish-packages.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
NpmPublishClient,
1414
} from "@definitelytyped/utils";
1515
import { readChangedPackages, ChangedPackages } from "./lib/versions";
16-
import { skipBadPublishes } from "./lib/npm";
1716
import { getSecret, Secret } from "./lib/secrets";
1817

1918
if (!module.parent) {
@@ -24,11 +23,12 @@ if (!module.parent) {
2423
if (deprecateName !== undefined) {
2524
// A '--deprecate' command is available in case types-publisher got stuck *while* trying to deprecate a package.
2625
// Normally this should not be needed.
27-
26+
const pkg = AllPackages.readSingleNotNeeded(deprecateName, dt);
2827
const log = logger()[0];
2928
await deprecateNotNeededPackage(
3029
await NpmPublishClient.create(await getSecret(Secret.NPM_TOKEN), undefined),
31-
AllPackages.readSingleNotNeeded(deprecateName, dt),
30+
pkg,
31+
String(pkg.version),
3232
/*dry*/ false,
3333
log
3434
);
@@ -140,9 +140,8 @@ export default async function publishPackages(
140140
}
141141
}
142142

143-
for (const n of changedPackages.changedNotNeededPackages) {
144-
const target = await skipBadPublishes(n, log);
145-
await publishNotNeededPackage(client, target, dry, log);
143+
for (const { pkg, version } of changedPackages.changedNotNeededPackages) {
144+
await publishNotNeededPackage(client, pkg, version, dry, log);
146145
}
147146

148147
await writeLog("publishing.md", logResult());
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as util from "util";
2+
import { NotNeededPackage } from "@definitelytyped/definitions-parser";
3+
import { fetchIncipientStubVersion } from "../src/calculate-versions";
4+
5+
jest.mock("pacote", () => ({
6+
async packument(spec: string) {
7+
switch (spec) {
8+
case "@types/not-needed": // A not-needed @types package about to be deprecated.
9+
return { versions: { "1.0.0": undefined, "1.0.1": undefined } };
10+
case "@types/bad-publish": // A not-needed package after a bad publish.
11+
return { versions: { "1.0.0": undefined, "1.0.1": undefined, "1.2.3": undefined } };
12+
case "@types/already-deprecated": // A successfully deprecated not-needed stub package.
13+
return { deprecated: "Contains built-in declarations." };
14+
}
15+
throw new Error(`Unexpected npm registry fetch: ${util.inspect(spec)}`);
16+
},
17+
}));
18+
19+
const notNeeded = new NotNeededPackage("not-needed", "not-needed", "1.2.3");
20+
const badPublish = new NotNeededPackage("bad-publish", "bad-publish", "1.2.3");
21+
const alreadyDeprecated = new NotNeededPackage("already-deprecated", "already-deprecated", "1.2.3");
22+
23+
const log = { info() {}, error() {} };
24+
25+
test("Returns the not-needed version", () => {
26+
return expect(fetchIncipientStubVersion(notNeeded, log)).resolves.toBe("1.2.3");
27+
});
28+
29+
test("Skips bad publishes", () => {
30+
return expect(fetchIncipientStubVersion(badPublish, log)).resolves.toBe("1.2.4");
31+
});
32+
33+
test("Ignores already-deprecated packages", () => {
34+
return expect(fetchIncipientStubVersion(alreadyDeprecated, log)).resolves.toBe(false);
35+
});

packages/publisher/test/generate-packages.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ testo({
137137
}`);
138138
},
139139
basicNotNeededPackageJson() {
140-
const s = createNotNeededPackageJSON(createUnneededPackage());
140+
const s = createNotNeededPackageJSON(createUnneededPackage(), "1.1.1");
141141
expect(s).toEqual(`{
142142
"name": "@types/absalom",
143143
"version": "1.1.1",
@@ -154,7 +154,7 @@ testo({
154154
},
155155
scopedNotNeededPackageJson() {
156156
const scopedUnneeded = new NotNeededPackage("google-cloud__pubsub", "@google-cloud/chubdub", "0.26.0");
157-
const s = createNotNeededPackageJSON(scopedUnneeded);
157+
const s = createNotNeededPackageJSON(scopedUnneeded, "0.26.0");
158158
expect(s).toEqual(`{
159159
"name": "@types/google-cloud__pubsub",
160160
"version": "0.26.0",

0 commit comments

Comments
 (0)