7b3809e8339a

read only canonical 'notes' field in releaseNotesFromMetadata; drop release_notes/changelog fallbacks; v3.4.1

time
2026-06-15 13:44 UTC · 4 months ago
machine
koh2
agent
claude-opus-4-8
session
release notes field standardization
parent
d7e14c6e0d10
3 files VERSION+1 −1build.zig.zon+1 −1src/commands.zig+29 −9
VERSION+1 −1
@@ -1 +1 @@1-3.4.01+3.4.1

view file

build.zig.zon+1 −1
@@ -9,7 +9,7 @@99     .name = .nina,1010     // This is a [Semantic Version](https://semver.org/).1111     // In a future version of Zig it will be used for package deduplication.12-    .version = "3.4.0",12+    .version = "3.4.1",1313     // Together with name, this represents a globally unique package1414     // identifier. This field is generated by the Zig toolchain when the1515     // package is first created, and then *never changes*. This allows

view file

src/commands.zig+29 −9
@@ -520,15 +520,14 @@520520 521521 fn releaseNotesFromMetadata(gpa: std.mem.Allocator, root: std.json.Value) ?[]u8 {522522     if (root != .object) return null;523-    const keys = [_][]const u8{ "release_notes", "changelog", "notes" };524-    for (keys) |key| {525-        const v = root.object.get(key) orelse continue;526-        switch (v) {527-            .string => |s| return gpa.dupe(u8, s) catch null,528-            else => {},529-        }530-    }531-    return null;523+    // `notes` is the one canonical manifest field. The retired `release_notes`524+    // and `changelog` fallbacks are gone — Kepr now emits `notes` everywhere, and525+    // releases that predate `--notes` fall back to archives.json in the caller.526+    const v = root.object.get("notes") orelse return null;527+    return switch (v) {528+        .string => |s| gpa.dupe(u8, s) catch null,529+        else => null,530+    };532531 }533532 534533 fn buildKeprArchiveReleaseNotes(gpa: std.mem.Allocator, io: std.Io, source_url: []const u8, current: []const u8, latest: []const u8) ![]u8 {@@ -684,6 +683,27 @@684683     try std.testing.expect(std.mem.indexOf(u8, notes, "- add streaming deflate path") != null);685684     try std.testing.expect(std.mem.indexOf(u8, notes, "CONTINUITY") == null);686685     try std.testing.expect(std.mem.indexOf(u8, notes, "old release") == null);686+}687+688+test "releaseNotesFromMetadata reads canonical notes and ignores retired keys" {689+    const a = std.testing.allocator;690+691+    // Canonical field is read.692+    {693+        var parsed = try std.json.parseFromSlice(std.json.Value, a, "{\"notes\":\"colored progress bar\"}", .{});694+        defer parsed.deinit();695+        const got = releaseNotesFromMetadata(a, parsed.value).?;696+        defer a.free(got);697+        try std.testing.expectEqualStrings("colored progress bar", got);698+    }699+700+    // Retired field names are no longer honoured — they fall through to null so701+    // the caller drops to the archives.json derivation instead.702+    {703+        var parsed = try std.json.parseFromSlice(std.json.Value, a, "{\"release_notes\":\"x\",\"changelog\":\"y\"}", .{});704+        defer parsed.deinit();705+        try std.testing.expect(releaseNotesFromMetadata(a, parsed.value) == null);706+    }687707 }688708 689709 // nina goodbye — clean uninstall. Three install paths:

view file