mirror of
https://github.com/denoland/deno.git
synced 2024-12-11 10:07:54 -05:00
228 lines
6 KiB
TypeScript
228 lines
6 KiB
TypeScript
// Copyright 2018-2022 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 * as cargo 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 cargo.getMetadata(DenoWorkspace.rootDirPath),
|
|
);
|
|
}
|
|
|
|
private constructor(metadata: cargo.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.getSerdeV8Crate(),
|
|
this.getCoreCrate(),
|
|
...this.getExtCrates(),
|
|
this.getRuntimeCrate(),
|
|
];
|
|
}
|
|
|
|
getSerdeV8Crate() {
|
|
return this.getCrateByNameOrThrow("serde_v8");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
build() {
|
|
return cargo.build(DenoWorkspace.rootDirPath);
|
|
}
|
|
|
|
updateLockFile() {
|
|
return cargo.check(DenoWorkspace.rootDirPath);
|
|
}
|
|
}
|
|
|
|
export class DenoWorkspaceCrate {
|
|
#workspace: DenoWorkspace;
|
|
#pkg: cargo.CargoPackageMetadata;
|
|
#isUpdatingManifest = false;
|
|
|
|
constructor(workspace: DenoWorkspace, pkg: cargo.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 the crates.io index
|
|
// not being updated yet. Usually it will be resolved after
|
|
// retrying, so try a few times before failing hard.
|
|
return await withRetries({
|
|
action: async () => {
|
|
await cargo.publishCrate(this.directoryPath);
|
|
return true;
|
|
},
|
|
retryCount: 5,
|
|
retryDelaySeconds: 10,
|
|
});
|
|
}
|
|
|
|
build() {
|
|
return cargo.build(this.directoryPath);
|
|
}
|
|
|
|
updateLockFile() {
|
|
return cargo.check(this.directoryPath);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|