diff --git a/src.ts/_admin/update-version-const.ts b/src.ts/_admin/update-version-const.ts deleted file mode 100644 index 3b1f73350..000000000 --- a/src.ts/_admin/update-version-const.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { atomicWrite } from "./utils/fs.js"; -import { resolve } from "./utils/path.js"; -import { loadJson } from "./utils/json.js"; - -const version = loadJson(resolve("package.json")).version; - -const content = `export const version = "${ version }";\n`; -atomicWrite(resolve("src.ts/_version.ts"), content); diff --git a/src.ts/_admin/update-version.ts b/src.ts/_admin/update-version.ts new file mode 100644 index 000000000..c1fa7dde3 --- /dev/null +++ b/src.ts/_admin/update-version.ts @@ -0,0 +1,74 @@ +import semver from "semver"; + +import { FetchRequest } from "../utils/index.js"; + +import { atomicWrite } from "./utils/fs.js"; +import { getGitTag } from "./utils/git.js"; +import { loadJson, saveJson } from "./utils/json.js"; +import { resolve } from "./utils/path.js"; + + +const cache: Record = { }; + +async function getNpmPackage(name: string): Promise { + if (!cache[name]) { + const resp = await (new FetchRequest("https:/\/registry.npmjs.org/" + name)).send(); + resp.assertOk(); + cache[name] = resp.bodyJson; + } + + return cache[name] || null; +} + +function writeVersion(version: string): void { + const content = `export const version = "${ version }";\n`; + atomicWrite(resolve("src.ts/_version.ts"), content); +} + +(async function() { + // Local pkg + const pkgPath = resolve("package.json"); + const pkgInfo = loadJson(pkgPath); + const tag = pkgInfo.publishConfig.tag; + + // Get the remote version that matches our dist-tag + const remoteInfo = await getNpmPackage(pkgInfo.name); + const remoteVersion = remoteInfo["dist-tags"][tag]; + + // Remote pkg + const remotePkgInfo = remoteInfo.versions[remoteVersion]; + const remoteGitHead = remotePkgInfo.gitHead; + + const gitHead = await getGitTag(resolve(".")); + + // There are new commits, not reflected in the package + // published on npm; update the gitHead and version + if (gitHead !== remoteGitHead) { + + // Bump the version from the remote version + if (tag.indexOf("beta") >= 0) { + // Still a beta branch; advance the beta version + const prerelease = semver.prerelease(remoteVersion); + if (prerelease == null || prerelease.length !== 2) { + throw new Error("no prerelease found"); + } + pkgInfo.version = semver.inc(remoteVersion, "prerelease", String(prerelease[0])); + } else if (semver.minor(remoteVersion) == semver.minor(pkgInfo.version)) { + // If we want to bump the minor version, it was done explicitly in the pkg + pkgInfo.version = semver.inc(remoteVersion, "patch"); + } + + pkgInfo.gitHead = gitHead; + + // Save the package.json + saveJson(pkgPath, pkgInfo, true); + + // Save the src.ts/_version.ts + writeVersion(pkgInfo.version); + } + +})().catch((error) => { + console.log("ERROR"); + console.log(error); + process.exit(1) +}); diff --git a/src.ts/_admin/utils/git.ts b/src.ts/_admin/utils/git.ts new file mode 100644 index 000000000..09d68c187 --- /dev/null +++ b/src.ts/_admin/utils/git.ts @@ -0,0 +1,14 @@ +import { run } from "./run.js"; + +// Returns the most recent git commit hash for a given filename +export async function getGitTag(filename: string): Promise { + const result = await run("git", [ "log", "-n", "1", "--", filename ]); + if (!result.ok) { throw new Error(`git log error`); } + + let log = result.stdout.trim(); + if (!log) { return null; } + + const hashMatch = log.match(/^commit\s+([0-9a-f]{40})\n/i); + if (!hashMatch) { return null; } + return hashMatch[1]; +} diff --git a/src.ts/_admin/utils/npm.ts b/src.ts/_admin/utils/npm.ts new file mode 100644 index 000000000..34d9410bf --- /dev/null +++ b/src.ts/_admin/utils/npm.ts @@ -0,0 +1,76 @@ +/* +import semver from "semver"; + +import { FetchRequest } from "../../utils/index.js"; + +export type PackageInfo = { + dependencies: { [ name: string ]: string }; + devDependencies: { [ name: string ]: string }; + gitHead: string; + name: string; + version: string; + tarballHash: string; + location: "remote" | "local"; + _ethers_nobuild: boolean; +}; + +export class Package { + readonly #info: PackageInfo; + + constructor(info: PackageInfo) { + this.#info = info; + } + + get name(): string { return this.#info.name; } + get version(): string { return this.#info.version; } + + get dependencies(): Record { return this.#info.dependencies; } + get devDependencies(): Record { return this.#info.devDependencies; } + + get gitHead(): string { return this.#info.gitHead; } + get tarballHash(): string { return this.#info.tarballHash; } + +} + + +const cache: Record = { }; + +async function getPackageInfo(name: string): Promise { + if (!cache[name]) { + const resp = await (new FetchRequest("https:/\/registry.npmjs.org/" + name)).send(); + resp.assertOk(); + cache[name] = resp.bodyJson(); + } + + return cache[name] || null; +} + +export async function getPackage(name: string, version?: string): Promise { + const infos = await getPackageInfo(name); + if (infos == null) { return null; } + + if (version == null) { + const versions = Object.keys(infos.versions); + versions.sort(semver.compare); + + // HACK: So v5 continues working while v6 is managed by reticulate + version = "6.0.0"; + while (version.indexOf("beta") >= 0 || semver.gte(version, "6.0.0")) { + version = versions.pop(); + } + } + + const info = infos.versions[version]; + + return new Package({ + dependencies: (info.dependencies || {}), + devDependencies: (info.devDependencies || {}), + gitHead: info.gitHead, + location: "remote", + name: info.name, + tarballHash: info.tarballHash, + version : info.version, + _ethers_nobuild: !!info._ethers_nobuild, + }); +} +*/ diff --git a/src.ts/_admin/utils/run.ts b/src.ts/_admin/utils/run.ts new file mode 100644 index 000000000..780434adb --- /dev/null +++ b/src.ts/_admin/utils/run.ts @@ -0,0 +1,63 @@ +import { spawnSync } from "child_process"; + +export class RunResult { + readonly #cmd: string; + readonly #status: null | number; + readonly #stdout: string | Buffer; + readonly #stderr: string | Buffer; + + constructor(progname: string, args: Array, status: null | number, stdout: string | Buffer, stderr: string | Buffer) { + this.#cmd = `${ progname } ${ args.map((a) => JSON.stringify(a))}`; + this.#status = status; + this.#stdout = stdout; + this.#stderr = stderr; + } + + get cmd(): string { return this.#cmd; } + + get stderr(): string | null { + return this._stderr.toString() || null; + } + + get _stderr(): string | Buffer { + return this.#stderr; + } + + get stdout(): string { + return this._stdout.toString(); + } + + get _stdout(): string | Buffer { + return this.#stdout; + } + + get status(): null | number { return this.#status; } + + get ok(): boolean { + return (this.#stderr.length === 0 && this.#status === 0); + } + + assertOk(message?: string): void { + if (!this.ok) { + throw new Error(message || `failed to run: ${ this.#cmd }`); + } + } +}; + +export function run(progname: string, args?: Array, currentWorkingDirectory?: string): RunResult { + if (args == null) { args = [ ]; } + + const options: any = { }; + if (currentWorkingDirectory) { options.cwd = currentWorkingDirectory; } + const child = spawnSync(progname, args, options); + + const result = new RunResult(progname, args, child.status, child.stdout, child.stderr); + + if (child.error) { + const error = child.error; + (error).result = result; + throw error; + } + + return result; +}