From df3c84a8d212f36fbc465c47bda0fa8d750db7fa Mon Sep 17 00:00:00 2001 From: Alberto Ricart Date: Thu, 25 Jul 2024 17:31:57 -0500 Subject: [PATCH] [cli] added script to sync bundle versions between the different modules --- bin/check-bundle-version.ts | 142 ++------------- bin/check-dep-versions.ts | 355 ++++++++++++++++++++++++++++-------- jetstream/deno.json | 10 +- jetstream/import_map.json | 2 +- jetstream/package.json | 2 +- kv/deno.json | 10 +- kv/import_map.json | 6 +- kv/package.json | 2 +- obj/deno.json | 10 +- obj/import_map.json | 6 +- obj/package.json | 2 +- services/deno.json | 10 +- services/import_map.json | 4 +- services/package.json | 2 +- transport-node/package.json | 2 +- 15 files changed, 342 insertions(+), 223 deletions(-) diff --git a/bin/check-bundle-version.ts b/bin/check-bundle-version.ts index 1650f325..ff73bce1 100644 --- a/bin/check-bundle-version.ts +++ b/bin/check-bundle-version.ts @@ -1,129 +1,27 @@ #!/usr/bin/env -S deno run -A +/* + * Copyright 2021-2024 The NATS Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { parseArgs } from "jsr:@std/cli/parse-args"; import { join } from "jsr:@std/path"; +import { loadVersions, SemVer } from "./lib/bundle_versions.ts"; -class ModuleVersions { - file?: SemVer; - deno?: SemVer; - node?: SemVer; - - constructor() { - } - - max(): SemVer | null { - const vers = this.versions(); - let vv: SemVer | null = null; - vers.forEach((v) => { - vv = vv == null ? v : vv.max(v); - }); - return vv; - } - - versions(): SemVer[] { - const vers = []; - if (this.file) { - vers.push(this.file); - } - if (this.deno) { - vers.push(this.deno); - } - if (this.node) { - vers.push(this.node); - } - return vers; - } - - check() { - const m = this.max(); - if (m !== null) { - this.versions().forEach((v) => { - if (m.compare(v) !== 0) { - throw new Error("different versions found"); - } - }); - } - } -} - -class SemVer { - major: number; - minor: number; - micro: number; - qualifier: string; - - constructor(v: string) { - const m = v.match(/(\d+).(\d+).(\d+)(-{1}(.+))?/); - if (m) { - this.major = parseInt(m[1]); - this.minor = parseInt(m[2]); - this.micro = parseInt(m[3]); - this.qualifier = m[5] ? m[5] : ""; - } else { - throw new Error(`'${v}' is not a semver value`); - } - } - - compare(b: SemVer): number { - if (this.major < b.major) return -1; - if (this.major > b.major) return 1; - if (this.minor < b.minor) return -1; - if (this.minor > b.minor) return 1; - if (this.micro < b.micro) return -1; - if (this.micro > b.micro) return 1; - if (this.qualifier === "") return 1; - if (b.qualifier === "") return -1; - return this.qualifier.localeCompare(b.qualifier); - } - - max(b: SemVer): SemVer { - return this.compare(b) > 0 ? this : b; - } - - string(): string { - return `${this.major}.${this.minor}.${this.micro}` + - (this.qualifier ? `-${this.qualifier}` : ""); - } -} - -async function loadVersionFile(module: string): Promise { - const { version } = await import( - join(Deno.cwd(), module, "src", "version.ts") - ).catch(() => { - return ""; - }); - return version; -} - -async function loadPackageFile(fp: string): Promise<{ version: string }> { - const src = await Deno.readTextFile(fp) - .catch(() => { - return JSON.stringify({ version: "" }); - }); - return JSON.parse(src); -} - -async function loadVersions(module: string): Promise { - const v = new ModuleVersions(); - const file = await loadVersionFile(module); - if (file) { - v.file = new SemVer(file); - } - - const { version: deno } = await loadPackageFile( - join(Deno.cwd(), module, "deno.json"), - ); - if (deno) { - v.deno = new SemVer(deno); - } - - const { version: node } = await loadPackageFile( - join(Deno.cwd(), module, "package.json"), - ); - if (node) { - v.node = new SemVer(node); - } - return v; -} +// Check that a particular bundle has consistent versions across +// deno.json, package.json, version.ts, and build tag +// the option --fix will pick the best (newest version and use that everywhere) +// --tag (version set by CI in a tag) +// deno run -A check-bundle-versions.ts --module [core|jetstream|kv|obj|transport-node|transport-deno|services] [--tag 1.2.3] [--fix] async function fixPackageVersion(fp: string, version: SemVer): Promise { const d = JSON.parse(await Deno.readTextFile(fp)); diff --git a/bin/check-dep-versions.ts b/bin/check-dep-versions.ts index f6e101e9..45a2b927 100644 --- a/bin/check-dep-versions.ts +++ b/bin/check-dep-versions.ts @@ -14,103 +14,308 @@ * limitations under the License. */ -import { parseArgs } from "jsr:@std/cli/parse-args"; -import { join } from "jsr:@std/path"; +// this command checks that bundles dependencies have a minimum floor dependency +// matching the current version of a bundle. This way the next release +// raises the minimum version of the dependency to currently released versions +// of the dependency. -async function load(fp: string): Promise { - const src = await Deno.readTextFile(fp); - return JSON.parse(src); -} +import { join } from "jsr:@std/path"; +import { loadPackageFile, SemVer } from "./lib/bundle_versions.ts"; -type denoImports = { +type PackageJSON = { + name: string; + version: string; + dependencies: Record; +}; +type DenoJSON = { + name: string; + version: string; imports: Record; }; - -type nodeDependencies = { - dependencies: Record; +type Imports = { + imports: Record; }; -const argv = parseArgs( - Deno.args, - { - alias: { - "m": ["module"], - "v": ["verbose"], - "f": ["fix"], - }, - string: ["module"], - boolean: ["v", "f"], - }, -); - -const module = argv.module || null; - -if (module === null) { - console.error( - `[ERROR] --module is required`, - ); - Deno.exit(1); +class ImportMap { + data: Imports; + changed: boolean; + + constructor(data: Imports) { + this.data = data; + this.changed = false; + } + static async load(dir: string): Promise { + const data = await loadPackageFile( + join(dir, "import_map.json"), + ); + + if (data.imports) { + return new ImportMap(data); + } + + return null; + } + + has(module: string): SemVer | null { + if (this.data.imports) { + const v = this.data.imports[module]; + // we only update them when they have a jsr - otherwise it is local file + if (v?.startsWith("jsr:")) { + return DenoModule.parseVersion( + this.data.imports[module], + ); + } + } + return null; + } + + update(module: string, version: SemVer): boolean { + let changed = false; + if (this.data.imports) { + const have = this.has(module); + if (have && version.compare(have) !== 0) { + this.data.imports[module] = `jsr:${module}@~${version.string()}`; + changed = true; + } + const internalModule = `${module}/internal`; + const haveInternal = this.has(internalModule); + if (haveInternal && version.compare(haveInternal) !== 0) { + this.data.imports[internalModule] = + `jsr:${module}@~${version.string()}/internal`; + changed = true; + } + return changed; + } + return false; + } + + store(dir: string): Promise { + this.changed = false; + return Deno.writeTextFile( + join(dir, "import_map.json"), + JSON.stringify(this.data, null, 2), + ); + } +} + +class BaseModule { + name: string; + version: SemVer; + + constructor(name: string, version: SemVer) { + this.name = name; + this.version = version; + } } -const di = await load(join(module, "deno.json")); -const nd = await load(join(module, "package.json")); +class DenoModule extends BaseModule { + data: DenoJSON; + changed: boolean; + + constructor(name: string, version: SemVer, data: DenoJSON) { + super(name, version); + this.data = data; + this.changed = false; + } + + static async load(dir: string): Promise { + const data = await loadPackageFile( + join(dir, "deno.json"), + ); -// drive the dependencies from the deno.json, as npm may contain additional ones -let failed = false; -let fixed = false; + if (data.version) { + return new DenoModule(data.name, new SemVer(data.version), data); + } + + return null; + } -for (const lib in di.imports) { - let deno = di.imports[lib]; - const prefix = `jsr:${lib}@`; - if (deno.startsWith(prefix)) { - deno = deno.substring(prefix.length); + store(dir: string): Promise { + this.changed = false; + return Deno.writeTextFile( + join(dir, "deno.json"), + JSON.stringify(this.data, null, 2), + ); } - if (argv.f) { - if (nd.dependencies[lib] !== deno) { - console.log( - `changed in package.json ${lib} from ${ - nd.dependencies[lib] - } to ${deno}`, + has(module: string): SemVer | null { + if (this.data.imports) { + return DenoModule.parseVersion( + this.data.imports[module], ); - nd.dependencies[lib] = deno; - fixed = true; } - continue; + return null; } - if (argv.v === true) { - const node = nd.dependencies[lib]; - console.log({ lib, deno, node }); + update(module: string, version: SemVer): boolean { + if (this.data.imports) { + const have = this.has(module); + if (have && version.compare(have) !== 0) { + this.data.imports[module] = `jsr:${module}@~${version.string()}`; + this.changed = true; + return true; + } + } + return false; + } + + static parseVersion(v: string): SemVer | null { + // jsr:@nats-io/something@[^|~]0.0.0-something + if (v) { + v = v.substring(v.lastIndexOf("@")); + return v.startsWith("^") || v.startsWith("~") + ? new SemVer(v.substring(1)) + : new SemVer(v); + } + return null; } - if (!nd.dependencies[lib]) { - failed = true; - console.log( - `[ERROR] module ${module} package.json dependencies is missing: ${lib}`, +} + +class NodeModule extends BaseModule { + data: PackageJSON; + changed: boolean; + + constructor(name: string, version: SemVer) { + super(name, version); + this.data = {} as PackageJSON; + this.changed = false; + } + + static async load(dir: string): Promise { + const packageJSON = await loadPackageFile( + join(dir, "package.json"), ); - continue; - } - if (deno !== nd.dependencies[lib]) { - failed = true; - console.log( - `[ERROR] module ${module} package.json dependencies ${lib}: ${ - nd.dependencies[lib] - } - but should be ${deno}`, + + if (packageJSON.version) { + const m = new NodeModule( + packageJSON.name, + new SemVer(packageJSON.version), + ); + m.data = packageJSON; + return m; + } + + return null; + } + + has(module: string): SemVer | null { + if (this.data.dependencies) { + return NodeModule.parseVersion( + this.data.dependencies[module], + ); + } + return null; + } + + static parseVersion(v: string): SemVer | null { + if (v) { + return v.startsWith("^") || v.startsWith("~") + ? new SemVer(v.substring(1)) + : new SemVer(v); + } + return null; + } + + update(module: string, version: SemVer): boolean { + if (this.data.dependencies) { + const have = this.has(module); + if (have && version.compare(have) !== 0) { + this.data.dependencies[module] = `~${version.string()}`; + this.changed = true; + return true; + } + } + return false; + } + + store(dir: string): Promise { + this.changed = false; + return Deno.writeTextFile( + join(dir, "package.json"), + JSON.stringify(this.data, null, 2), ); } } -if (argv.f && fixed) { - await Deno.writeTextFile( - join(module, "package.json"), - JSON.stringify(nd, undefined, " "), - ); - console.log(`[OK] module ${module} updated package.json`); - Deno.exit(0); +const dirs = [ + "core", + "services", + "jetstream", + "kv", + "obj", + "transport-deno", + "transport-node", +]; + +let nuid = new SemVer("0.0.0"); +let nkeys = new SemVer("0.0.0"); + +for (const dir of dirs) { + const dm = await DenoModule.load(dir); + const nm = await NodeModule.load(dir); + + if (dm && nm) { + if (dm.version.compare(nm.version) !== 0) { + throw new Error( + `expected package.json and deno.json to match for ${dir}`, + ); + } + } + + const v = dm ? dm.version : nm!.version; + const moduleName = dm ? dm.name : nm!.name; + + const other = dirs.filter((m) => { + return m !== dir; + }); + + for (const d of other) { + const dmm = await DenoModule.load(d); + if (dmm) { + if (dmm.has(moduleName)) { + dmm.update(moduleName, v); + await dmm.store(d); + } + } + const nmm = await NodeModule.load(d); + if (nmm) { + if (nmm.has(moduleName)) { + nmm.update(moduleName, v); + await nmm.store(d); + } + const onuid = nmm.has("@nats-io/nuid"); + if (onuid) { + nuid = nuid.max(onuid); + } + const onkeys = nmm.has("@nats-io/nkeys"); + if (onkeys) { + nkeys = nkeys.max(onkeys); + } + } + + const map = await ImportMap.load(d); + if (map) { + if (map.has(moduleName)) { + map.update(moduleName, v); + await map.store(d); + } + } + } } -if (failed) { - Deno.exit(1); +for (const d of dirs) { + const dmm = await DenoModule.load(d); + if (dmm) { + if (dmm.has("@nats-io/nuid")) { + dmm.update("@nats-io/nuid", nuid); + await dmm.store(d); + } + } + const nmm = await NodeModule.load(d); + if (nmm) { + if (nmm.has("@nats-io/nkeys")) { + nmm.update("@nats-io/nkeys", nkeys); + await nmm.store(d); + } + } } -console.log(`[OK] module ${module}`); -Deno.exit(0); diff --git a/jetstream/deno.json b/jetstream/deno.json index 03704c7c..a5ab14ba 100644 --- a/jetstream/deno.json +++ b/jetstream/deno.json @@ -19,15 +19,19 @@ ] }, "lint": { - "exclude": ["lib/"] + "exclude": [ + "lib/" + ] }, "fmt": { - "exclude": ["lib/"] + "exclude": [ + "lib/" + ] }, "tasks": { "test": "deno test -A --parallel --reload --quiet tests/ --import-map=import_map.json" }, "imports": { - "@nats-io/nats-core": "jsr:@nats-io/nats-core@3.0.0-17" + "@nats-io/nats-core": "jsr:@nats-io/nats-core@~3.0.0-18" } } diff --git a/jetstream/import_map.json b/jetstream/import_map.json index 93bfe3bc..c24bbb93 100644 --- a/jetstream/import_map.json +++ b/jetstream/import_map.json @@ -2,7 +2,7 @@ "imports": { "@nats-io/nkeys": "jsr:@nats-io/nkeys@1.2.0-4", "@nats-io/nuid": "jsr:@nats-io/nuid@2.0.1-2", - "@nats-io/nats-core": "jsr:@nats-io/nats-core@3.0.0-17", + "@nats-io/nats-core": "jsr:@nats-io/nats-core@~3.0.0-18", "@nats-io/jetstream/internal": "./src/internal_mod.ts", "test_helpers": "../test_helpers/mod.ts", "@std/io": "jsr:@std/io@0.224.0" diff --git a/jetstream/package.json b/jetstream/package.json index 0c7c5407..8c14d5e9 100644 --- a/jetstream/package.json +++ b/jetstream/package.json @@ -26,7 +26,7 @@ }, "description": "jetstream library - this library implements all the base functionality for NATS JetStream for javascript clients", "dependencies": { - "@nats-io/nats-core": "3.0.0-17" + "@nats-io/nats-core": "~3.0.0-18" }, "devDependencies": { "@types/node": "^20.11.30", diff --git a/kv/deno.json b/kv/deno.json index a3610dff..42cca84b 100644 --- a/kv/deno.json +++ b/kv/deno.json @@ -15,16 +15,20 @@ ] }, "lint": { - "exclude": ["lib/"] + "exclude": [ + "lib/" + ] }, "fmt": { - "exclude": ["lib/"] + "exclude": [ + "lib/" + ] }, "tasks": { "test": "deno test -A --parallel --reload --quiet tests/ --import-map=import_map.json" }, "imports": { - "@nats-io/nats-core": "jsr:@nats-io/nats-core@3.0.0-17", + "@nats-io/nats-core": "jsr:@nats-io/nats-core@~3.0.0-18", "@nats-io/jetstream": "jsr:@nats-io/jetstream@3.0.0-3" } } diff --git a/kv/import_map.json b/kv/import_map.json index 7d49bdf0..685ea97c 100644 --- a/kv/import_map.json +++ b/kv/import_map.json @@ -1,9 +1,9 @@ { "imports": { - "@nats-io/nats-core": "jsr:@nats-io/nats-core@3.0.0-17", - "@nats-io/nats-core/internal": "jsr:@nats-io/nats-core@3.0.0-17/internal", + "@nats-io/nats-core": "jsr:@nats-io/nats-core@~3.0.0-18", + "@nats-io/nats-core/internal": "jsr:@nats-io/nats-core@~3.0.0-18/internal", "@nats-io/jetstream": "jsr:@nats-io/jetstream@3.0.0-3", - "@nats-io/jetstream/internal": "jsr:@nats-io/jetstream@3.0.0-3/internal", + "@nats-io/jetstream/internal": "jsr:@nats-io/jetstream@~3.0.0-3/internal", "test_helpers": "../test_helpers/mod.ts", "@nats-io/nkeys": "jsr:@nats-io/nkeys@1.2.0-4", "@nats-io/nuid": "jsr:@nats-io/nuid@2.0.1-2", diff --git a/kv/package.json b/kv/package.json index 8f3ffde3..ab3ffffd 100644 --- a/kv/package.json +++ b/kv/package.json @@ -26,7 +26,7 @@ }, "description": "kv library - this library implements all the base functionality for NATS KV javascript clients", "dependencies": { - "@nats-io/nats-core": "3.0.0-17", + "@nats-io/nats-core": "~3.0.0-18", "@nats-io/jetstream": "3.0.0-3" }, "devDependencies": { diff --git a/obj/deno.json b/obj/deno.json index 032a0623..ddca4c02 100644 --- a/obj/deno.json +++ b/obj/deno.json @@ -14,16 +14,20 @@ ] }, "lint": { - "exclude": ["lib/"] + "exclude": [ + "lib/" + ] }, "fmt": { - "exclude": ["lib/"] + "exclude": [ + "lib/" + ] }, "tasks": { "test": "deno test -A --parallel --reload --quiet tests/ --import-map=import_map.json" }, "imports": { - "@nats-io/nats-core": "jsr:@nats-io/nats-core@3.0.0-17", + "@nats-io/nats-core": "jsr:@nats-io/nats-core@~3.0.0-18", "@nats-io/jetstream": "jsr:@nats-io/jetstream@3.0.0-3" } } diff --git a/obj/import_map.json b/obj/import_map.json index 7d49bdf0..685ea97c 100644 --- a/obj/import_map.json +++ b/obj/import_map.json @@ -1,9 +1,9 @@ { "imports": { - "@nats-io/nats-core": "jsr:@nats-io/nats-core@3.0.0-17", - "@nats-io/nats-core/internal": "jsr:@nats-io/nats-core@3.0.0-17/internal", + "@nats-io/nats-core": "jsr:@nats-io/nats-core@~3.0.0-18", + "@nats-io/nats-core/internal": "jsr:@nats-io/nats-core@~3.0.0-18/internal", "@nats-io/jetstream": "jsr:@nats-io/jetstream@3.0.0-3", - "@nats-io/jetstream/internal": "jsr:@nats-io/jetstream@3.0.0-3/internal", + "@nats-io/jetstream/internal": "jsr:@nats-io/jetstream@~3.0.0-3/internal", "test_helpers": "../test_helpers/mod.ts", "@nats-io/nkeys": "jsr:@nats-io/nkeys@1.2.0-4", "@nats-io/nuid": "jsr:@nats-io/nuid@2.0.1-2", diff --git a/obj/package.json b/obj/package.json index 3e6264cd..e9b760ef 100644 --- a/obj/package.json +++ b/obj/package.json @@ -26,7 +26,7 @@ }, "description": "obj library - this library implements all the base functionality for NATS objectstore for javascript clients", "dependencies": { - "@nats-io/nats-core": "3.0.0-17", + "@nats-io/nats-core": "~3.0.0-18", "@nats-io/jetstream": "3.0.0-3" }, "devDependencies": { diff --git a/services/deno.json b/services/deno.json index 482eafd6..0a9641f3 100644 --- a/services/deno.json +++ b/services/deno.json @@ -14,15 +14,19 @@ ] }, "lint": { - "exclude": ["lib/"] + "exclude": [ + "lib/" + ] }, "fmt": { - "exclude": ["lib/"] + "exclude": [ + "lib/" + ] }, "tasks": { "test": "deno test -A --parallel --reload --quiet tests/ --import-map=import_map.json" }, "imports": { - "@nats-io/nats-core": "jsr:@nats-io/nats-core@3.0.0-17" + "@nats-io/nats-core": "jsr:@nats-io/nats-core@~3.0.0-18" } } diff --git a/services/import_map.json b/services/import_map.json index ba1cd488..7a4a450c 100644 --- a/services/import_map.json +++ b/services/import_map.json @@ -1,7 +1,7 @@ { "imports": { - "@nats-io/nats-core": "jsr:@nats-io/nats-core@3.0.0-17", - "@nats-io/nats-core/internal": "jsr:@nats-io/nats-core@3.0.0-17/internal", + "@nats-io/nats-core": "jsr:@nats-io/nats-core@~3.0.0-18", + "@nats-io/nats-core/internal": "jsr:@nats-io/nats-core@~3.0.0-18/internal", "test_helpers": "../test_helpers/mod.ts", "@nats-io/nkeys": "jsr:@nats-io/nkeys@1.2.0-4", "@nats-io/nuid": "jsr:@nats-io/nuid@2.0.1-2", diff --git a/services/package.json b/services/package.json index 46f0e7f1..eb41364e 100644 --- a/services/package.json +++ b/services/package.json @@ -26,7 +26,7 @@ }, "description": "services library - this library implements all the base functionality for NATS services for javascript clients", "dependencies": { - "@nats-io/nats-core": "3.0.0-17" + "@nats-io/nats-core": "~3.0.0-18" }, "devDependencies": { "@types/node": "^20.11.30", diff --git a/transport-node/package.json b/transport-node/package.json index c60567ce..663861ad 100644 --- a/transport-node/package.json +++ b/transport-node/package.json @@ -57,7 +57,7 @@ "node": ">= 18.0.0" }, "dependencies": { - "@nats-io/nats-core": "^3.0.0-17", + "@nats-io/nats-core": "~3.0.0-18", "@nats-io/nkeys": "^1.2.0-4", "@nats-io/nuid": "^2.0.1-2" },