summaryrefslogtreecommitdiff
path: root/tools/release/helpers/deno_workspace.ts
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2021-08-25 09:02:22 -0400
committerGitHub <noreply@github.com>2021-08-25 09:02:22 -0400
commitdce70d32a47801025b3b67a97ec9ebed90dfc8a2 (patch)
treef08176bdad18f931956f24cd7d3b92ba313c4493 /tools/release/helpers/deno_workspace.ts
parentdccf4cbe36d66140f9e35a6ee755c3c440d745f9 (diff)
chore: add scripts for helping with a release (#11832)
Diffstat (limited to 'tools/release/helpers/deno_workspace.ts')
-rw-r--r--tools/release/helpers/deno_workspace.ts210
1 files changed, 210 insertions, 0 deletions
diff --git a/tools/release/helpers/deno_workspace.ts b/tools/release/helpers/deno_workspace.ts
new file mode 100644
index 000000000..f964d24b3
--- /dev/null
+++ b/tools/release/helpers/deno_workspace.ts
@@ -0,0 +1,210 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+import * as path from "https://deno.land/std@0.105.0/path/mod.ts";
+import * as semver from "https://deno.land/x/semver@v1.4.0/mod.ts";
+import {
+ CargoMetadata,
+ CargoPackageMetadata,
+ getMetadata,
+ publishCrate,
+} from "./cargo.ts";
+import { getCratesIoMetadata } from "./crates_io.ts";
+import { withRetries } from "./helpers.ts";
+
+export class DenoWorkspace {
+ #workspaceCrates: readonly DenoWorkspaceCrate[];
+ #workspaceRootDirPath: string;
+
+ static get rootDirPath() {
+ const currentDirPath = path.dirname(path.fromFileUrl(import.meta.url));
+ return path.resolve(currentDirPath, "../../../");
+ }
+
+ static async load(): Promise<DenoWorkspace> {
+ return new DenoWorkspace(await getMetadata(DenoWorkspace.rootDirPath));
+ }
+
+ private constructor(metadata: CargoMetadata) {
+ const crates = [];
+ for (const memberId of metadata.workspace_members) {
+ const pkg = metadata.packages.find((pkg) => pkg.id === memberId);
+ if (!pkg) {
+ throw new Error(`Could not find package with id ${memberId}`);
+ }
+ crates.push(new DenoWorkspaceCrate(this, pkg));
+ }
+
+ this.#workspaceCrates = crates;
+ this.#workspaceRootDirPath = metadata.workspace_root;
+ }
+
+ get crates() {
+ return this.#workspaceCrates;
+ }
+
+ /** Gets the dependency crates used for the first part of the release process. */
+ getDependencyCrates() {
+ return [
+ this.getBenchUtilCrate(),
+ this.getCoreCrate(),
+ ...this.getExtCrates(),
+ this.getRuntimeCrate(),
+ ];
+ }
+
+ getCliCrate() {
+ return this.getCrateByNameOrThrow("deno");
+ }
+
+ getCoreCrate() {
+ return this.getCrateByNameOrThrow("deno_core");
+ }
+
+ getRuntimeCrate() {
+ return this.getCrateByNameOrThrow("deno_runtime");
+ }
+
+ getBenchUtilCrate() {
+ return this.getCrateByNameOrThrow("deno_bench_util");
+ }
+
+ getExtCrates() {
+ const extPath = path.join(this.#workspaceRootDirPath, "ext");
+ return this.#workspaceCrates.filter((c) =>
+ c.manifestPath.startsWith(extPath)
+ );
+ }
+
+ getCrateByNameOrThrow(name: string) {
+ const crate = this.#workspaceCrates.find((c) => c.name === name);
+ if (!crate) {
+ throw new Error(`Could not find crate: ${name}`);
+ }
+ return crate;
+ }
+}
+
+export class DenoWorkspaceCrate {
+ #workspace: DenoWorkspace;
+ #pkg: CargoPackageMetadata;
+ #isUpdatingManifest = false;
+
+ constructor(workspace: DenoWorkspace, pkg: CargoPackageMetadata) {
+ this.#workspace = workspace;
+ this.#pkg = pkg;
+ }
+
+ get manifestPath() {
+ return this.#pkg.manifest_path;
+ }
+
+ get directoryPath() {
+ return path.dirname(this.#pkg.manifest_path);
+ }
+
+ get name() {
+ return this.#pkg.name;
+ }
+
+ get version() {
+ return this.#pkg.version;
+ }
+
+ getDependencies() {
+ const dependencies = [];
+ for (const dependency of this.#pkg.dependencies) {
+ const crate = this.#workspace.crates.find((c) =>
+ c.name === dependency.name
+ );
+ if (crate != null) {
+ dependencies.push(crate);
+ }
+ }
+ return dependencies;
+ }
+
+ async isPublished() {
+ const cratesIoMetadata = await getCratesIoMetadata(this.name);
+ return cratesIoMetadata.versions.some((v) => v.num === this.version);
+ }
+
+ async publish() {
+ if (await this.isPublished()) {
+ console.log(`Already published ${this.name} ${this.version}`);
+ return false;
+ }
+
+ console.log(`Publishing ${this.name} ${this.version}...`);
+
+ // Sometimes a publish may fail due to local caching issues.
+ // Usually it will fix itself after retrying so try a few
+ // times before failing hard.
+ return await withRetries({
+ action: async () => {
+ await publishCrate(this.directoryPath);
+ return true;
+ },
+ retryCount: 3,
+ retryDelaySeconds: 10,
+ });
+ }
+
+ increment(part: "major" | "minor" | "patch") {
+ const newVersion = semver.parse(this.version)!.inc(part).toString();
+ return this.setVersion(newVersion);
+ }
+
+ async setVersion(version: string) {
+ console.log(`Setting ${this.name} to ${version}...`);
+ for (const crate of this.#workspace.crates) {
+ await crate.setDependencyVersion(this.name, version);
+ }
+ await this.#updateManifestVersion(version);
+ }
+
+ async setDependencyVersion(dependencyName: string, version: string) {
+ const dependency = this.#pkg.dependencies.find((d) =>
+ d.name === dependencyName
+ );
+ if (dependency != null) {
+ await this.#updateManifestFile((fileText) => {
+ // simple for now...
+ const findRegex = new RegExp(
+ `^(\\b${dependencyName}\\b\\s.*)"([=\\^])?[0-9]+[^"]+"`,
+ "gm",
+ );
+ return fileText.replace(findRegex, `$1"${version}"`);
+ });
+
+ dependency.req = `^${version}`;
+ }
+ }
+
+ async #updateManifestVersion(version: string) {
+ await this.#updateManifestFile((fileText) => {
+ const findRegex = new RegExp(
+ `^(version\\s*=\\s*)"${this.#pkg.version}"$`,
+ "m",
+ );
+ return fileText.replace(findRegex, `$1"${version}"`);
+ });
+ this.#pkg.version = version;
+ }
+
+ async #updateManifestFile(action: (fileText: string) => string) {
+ if (this.#isUpdatingManifest) {
+ throw new Error("Cannot update manifest while updating manifest.");
+ }
+ this.#isUpdatingManifest = true;
+ try {
+ const originalText = await Deno.readTextFile(this.#pkg.manifest_path);
+ const newText = action(originalText);
+ if (originalText === newText) {
+ throw new Error(`The file didn't change: ${this.manifestPath}`);
+ }
+ await Deno.writeTextFile(this.manifestPath, newText);
+ } finally {
+ this.#isUpdatingManifest = false;
+ }
+ }
+}