4 Commits

Author SHA1 Message Date
614b1858cb chore(release): v1.2.2 - bump from v1.2.1 2026-03-11 15:51:40 +01:00
45aae46925 Do not overwrite tags 2026-03-11 15:50:47 +01:00
d47007d905 Release script 2026-03-11 15:48:45 +01:00
6daadc97fc Treat transport codes and path as their own segments 2026-03-11 15:42:00 +01:00
3 changed files with 169 additions and 34 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@hamradio/meshcore",
"version": "1.2.1",
"version": "1.2.2",
"description": "MeshCore protocol support for Typescript",
"keywords": [
"MeshCore",

115
scripts/release.js Executable file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env node
// Minimal safe release script.
// Usage: node scripts/release.js [major|minor|patch|<version>]
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const root = path.resolve(__dirname, "..");
const pkgPath = path.join(root, "package.json");
function run(cmd, opts = {}) {
return execSync(cmd, { stdio: "inherit", cwd: root, ...opts });
}
function runOutput(cmd) {
return execSync(cmd, { cwd: root }).toString().trim();
}
function bumpSemver(current, spec) {
if (["major","minor","patch"].includes(spec)) {
const [maj, min, patch] = current.split(".").map(n=>parseInt(n,10));
if (spec==="major") return `${maj+1}.0.0`;
if (spec==="minor") return `${maj}.${min+1}.0`;
return `${maj}.${min}.${patch+1}`;
}
if (!/^\d+\.\d+\.\d+$/.test(spec)) throw new Error("Invalid version spec");
return spec;
}
(async () => {
const arg = process.argv[2] || "patch";
const pkgRaw = fs.readFileSync(pkgPath, "utf8");
const pkg = JSON.parse(pkgRaw);
const oldVersion = pkg.version;
const newVersion = bumpSemver(oldVersion, arg);
let committed = false;
let tagged = false;
let pushedTags = false;
try {
// refuse to run if there are unstaged/uncommitted changes
const status = runOutput("git status --porcelain");
if (status) throw new Error("Repository has uncommitted changes; please commit or stash before releasing.");
console.log("Running tests...");
run("npm run test:ci");
console.log("Building...");
run("npm run build");
// write new version
pkg.version = newVersion;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
console.log(`Bumped version: ${oldVersion} -> ${newVersion}`);
// commit
run(`git add ${pkgPath}`);
run(`git commit -m "chore(release): v${newVersion} - bump from v${oldVersion}"`);
committed = true;
// ensure tag doesn't already exist locally
let localTagExists = false;
try {
runOutput(`git rev-parse --verify refs/tags/v${newVersion}`);
localTagExists = true;
} catch (_) {
localTagExists = false;
}
if (localTagExists) throw new Error(`Tag v${newVersion} already exists locally — aborting to avoid overwrite.`);
// ensure tag doesn't exist on remote
const remoteTagInfo = (() => {
try { return runOutput(`git ls-remote --tags origin v${newVersion}`); } catch (_) { return ""; }
})();
if (remoteTagInfo) throw new Error(`Tag v${newVersion} already exists on remote — aborting to avoid overwrite.`);
// tag
run(`git tag -a v${newVersion} -m "Release v${newVersion}"`);
tagged = true;
// push commit and tags
run("git push");
run("git push --tags");
pushedTags = true;
// publish
console.log("Publishing to npm...");
const publishCmd = pkg.name && pkg.name.startsWith("@") ? "npm publish --access public" : "npm publish";
run(publishCmd);
console.log(`Release v${newVersion} succeeded.`);
process.exit(0);
} catch (err) {
console.error("Release failed:", err.message || err);
try {
// delete local tag
if (tagged) {
try { run(`git tag -d v${newVersion}`); } catch {}
if (pushedTags) {
try { run(`git push origin :refs/tags/v${newVersion}`); } catch {}
}
}
// undo commit if made
if (committed) {
try { run("git reset --hard HEAD~1"); } catch {
// fallback: restore package.json content
fs.writeFileSync(pkgPath, pkgRaw, "utf8");
}
} else {
// restore package.json
fs.writeFileSync(pkgPath, pkgRaw, "utf8");
}
} catch (rbErr) {
console.error("Rollback error:", rbErr.message || rbErr);
}
process.exit(1);
}
})();

View File

@@ -119,7 +119,10 @@ export class Packet implements IPacket {
this.structure = [
/* Header segment */
{ name: "header", data: new Uint8Array([this.header, this.pathLength, ...this.path]), fields: [
{
name: "header",
data: new Uint8Array([this.header]),
fields: [
/* Header flags */
{
name: "flags",
@@ -131,21 +134,37 @@ export class Packet implements IPacket {
{ name: "route type", size: 2 },
]
},
]
},
/* Transport codes */
...(Packet.hasTransportCodes(this.routeType) ? [
...(Packet.hasTransportCodes(this.routeType) ? [{
name: "transport codes",
data: new Uint8Array([
(this.transport![0] >> 8) & 0xff, this.transport![0] & 0xff,
(this.transport![1] >> 8) & 0xff, this.transport![1] & 0xff
]),
fields: [
{
name: "transport code 1",
type: FieldType.UINT16_BE,
size: 2
size: 2,
value: this.transport![0]
},
{
name: "transport code 2",
type: FieldType.UINT16_BE,
size: 2
size: 2,
value: this.transport![1]
},
] : []),
]
}] : []),
/* Path length and hashes */
{
name: "path",
data: new Uint8Array([this.pathLength, ...this.path]),
fields: [
{
name: "path length",
type: FieldType.UINT8,
@@ -160,7 +179,8 @@ export class Packet implements IPacket {
type: pathHashType,
size: this.path.length
}
]},
]
},
]
}