@cosmicdrift/kumiko-framework 0.48.0 → 0.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.1",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { ConfigScopes } from "../constants";
|
|
3
|
+
import { buildManifestFromRegistry, createRegistry, defineFeature } from "../index";
|
|
4
|
+
|
|
5
|
+
const boolKey = {
|
|
6
|
+
type: "boolean",
|
|
7
|
+
scope: ConfigScopes.system,
|
|
8
|
+
access: { read: ["anonymous"], write: ["anonymous"] },
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
describe("buildManifestFromRegistry — deterministic codepoint sort (#330)", () => {
|
|
12
|
+
// `bZeta` vs `balpha`: localeCompare orders these case-insensitively
|
|
13
|
+
// (balpha < bZeta), but a codepoint sort puts uppercase 'Z' (U+005A) ahead of
|
|
14
|
+
// lowercase 'a' (U+0061) — the two comparators DISAGREE. The manifest is
|
|
15
|
+
// serialized to byte-exact JSON and must not depend on the runner's ICU
|
|
16
|
+
// locale (macOS-dev vs Linux-CI). This assertion fails the instant anyone
|
|
17
|
+
// reverts buildManifestFromRegistry to localeCompare. (Feature names skip the
|
|
18
|
+
// kebab normalization that config/secret short-names go through, so they are
|
|
19
|
+
// the one place a case-based disagreement survives into the sorted output.)
|
|
20
|
+
test("features are ordered by codepoint name, not locale", () => {
|
|
21
|
+
const registry = createRegistry([
|
|
22
|
+
defineFeature("bZeta", () => {}),
|
|
23
|
+
defineFeature("balpha", () => {}),
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const manifest = buildManifestFromRegistry(registry, { source: "test" });
|
|
27
|
+
|
|
28
|
+
expect(manifest.features.map((f) => f.name)).toEqual(["bZeta", "balpha"]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("config keys within a feature come out sorted by qualified name", () => {
|
|
32
|
+
const feature = defineFeature("demo", (r) => {
|
|
33
|
+
r.config({ keys: { "z-flag": boolKey, "a-flag": boolKey } });
|
|
34
|
+
});
|
|
35
|
+
const registry = createRegistry([feature]);
|
|
36
|
+
|
|
37
|
+
const manifest = buildManifestFromRegistry(registry, { source: "test" });
|
|
38
|
+
const demo = manifest.features.find((f) => f.name === "demo");
|
|
39
|
+
|
|
40
|
+
expect(demo?.configKeys.map((k) => k.qualifiedName)).toEqual([
|
|
41
|
+
"demo:config:a-flag",
|
|
42
|
+
"demo:config:z-flag",
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -59,6 +59,13 @@ export type FeatureManifest = {
|
|
|
59
59
|
|
|
60
60
|
const CONFIG_SEGMENT = ":config:";
|
|
61
61
|
|
|
62
|
+
// Codepoint order, not localeCompare: the manifest is serialized to byte-exact
|
|
63
|
+
// JSON, and localeCompare's ordering depends on the runner's ICU locale → drift
|
|
64
|
+
// between macOS-dev and Linux-CI (#330).
|
|
65
|
+
function compareByCodepoint(a: string, b: string): number {
|
|
66
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
62
69
|
export type BuildManifestOptions = {
|
|
63
70
|
/** Herkunfts-Beschreibung fürs Manifest (landet 1:1 im JSON). */
|
|
64
71
|
readonly source: string;
|
|
@@ -111,8 +118,8 @@ export function buildManifestFromRegistry(
|
|
|
111
118
|
});
|
|
112
119
|
}
|
|
113
120
|
|
|
114
|
-
configKeys.sort((a, b) => a.qualifiedName
|
|
115
|
-
secrets.sort((a, b) => a.qualifiedName
|
|
121
|
+
configKeys.sort((a, b) => compareByCodepoint(a.qualifiedName, b.qualifiedName));
|
|
122
|
+
secrets.sort((a, b) => compareByCodepoint(a.qualifiedName, b.qualifiedName));
|
|
116
123
|
|
|
117
124
|
manifestFeatures.push({
|
|
118
125
|
name: feature.name,
|
|
@@ -133,7 +140,7 @@ export function buildManifestFromRegistry(
|
|
|
133
140
|
});
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
manifestFeatures.sort((a, b) => a.name
|
|
143
|
+
manifestFeatures.sort((a, b) => compareByCodepoint(a.name, b.name));
|
|
137
144
|
|
|
138
145
|
return {
|
|
139
146
|
source: options.source,
|