@greatcms/conformance 0.0.0-stage → 0.1.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.
Files changed (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -2
  3. package/bin/greatcms-conformance.mjs +119 -0
  4. package/dist/index.d.ts +43 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +169 -0
  7. package/dist/index.js.map +1 -0
  8. package/expected/entries/blog/empty-frontmatter.json +6 -0
  9. package/expected/entries/blog/publish-from-anywhere.json +11 -0
  10. package/expected/entries/blog/yaml-scalars.json +9 -0
  11. package/expected/entries/guides/measuring-latency.json +8 -0
  12. package/expected/regions/about.intro.json +6 -0
  13. package/expected/regions/home.body.json +39 -0
  14. package/expected/regions/home.cards.json +9 -0
  15. package/expected/regions/home.decor.json +6 -0
  16. package/expected/regions/home.featured.json +4 -0
  17. package/expected/regions/home.hero.json +14 -0
  18. package/expected/regions/home.notice.json +5 -0
  19. package/expected/regions/home.seo.json +6 -0
  20. package/expected/regions/home.showcase.json +21 -0
  21. package/fixtures/.cms/config.yaml +12 -0
  22. package/fixtures/.cms/schema.yaml +141 -0
  23. package/fixtures/content/blog/empty-frontmatter.md +4 -0
  24. package/fixtures/content/blog/publish-from-anywhere.md +14 -0
  25. package/fixtures/content/blog/yaml-scalars.md +7 -0
  26. package/fixtures/content/cms/home.body.json +42 -0
  27. package/fixtures/content/cms/home.decor.json +6 -0
  28. package/fixtures/content/cms/home.featured.json +8 -0
  29. package/fixtures/content/cms/home.hero.json +19 -0
  30. package/fixtures/content/cms/home.notice.json +8 -0
  31. package/fixtures/content/cms/home.showcase.json +24 -0
  32. package/fixtures/content/guides/measuring-latency.mdx +14 -0
  33. package/package.json +57 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 UWENAYO Alain Pacifique
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,22 @@
1
- # Temporary Holding Version
1
+ > Built with an AI coding assistant and reviewed by a human. Treat it as you would any new dependency.
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ # @greatcms/conformance
4
+
5
+ What "reads the GreatCMS standard correctly" means, as a fixture site and the
6
+ documents any reader must resolve it to. The fixture deliberately holds the
7
+ awkward cases: a region with no content file, content written before a field
8
+ was added, a block missing a defaulted field, a block that names no variant,
9
+ a decorative empty alt, and a Markdown collection.
10
+
11
+ ```sh
12
+ npx greatcms-conformance fixtures # path to the fixture site
13
+ npx greatcms-conformance verify ./out # compare a reader's output
14
+ npx greatcms-conformance verify --self # verify the reference reader
15
+ ```
16
+
17
+ A reader writes one JSON file per region to a folder, plus
18
+ `entries/<collection>/<slug>.json` if it implements collections, and
19
+ `verify` compares them with `expected/`. The repository's Python reader
20
+ passes it, which is how the format stays language-agnostic.
21
+
22
+ Documentation: https://greatcms.dev/docs/any-language/ · Source: https://github.com/uwenayoallain/thegreatcms
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ /** Language-agnostic conformance verifier. */
3
+
4
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
6
+ import {
7
+ FIXTURES_DIR,
8
+ readFixtureEntries,
9
+ readFixtureRegion,
10
+ runConformance,
11
+ verifyEntries,
12
+ verifyRegions,
13
+ } from "../dist/index.js";
14
+
15
+ const [, , command = "help", ...args] = process.argv;
16
+
17
+ if (command === "fixtures") {
18
+ process.stdout.write(`${FIXTURES_DIR}\n`);
19
+ process.exit(0);
20
+ }
21
+
22
+ if (command !== "verify") {
23
+ process.stdout.write(
24
+ [
25
+ "greatcms-conformance: prove a tool reads the GreatCMS standard",
26
+ "",
27
+ " fixtures print the path to the fixture site",
28
+ " verify <dir> compare a reader's output against the expected documents",
29
+ " verify --self verify the reference reader in @greatcms/conformance",
30
+ "",
31
+ "<dir> holds one JSON file per region, named <region-id>.json, and: if the",
32
+ "reader implements collections: <dir>/entries/<collection>/<slug>.json,",
33
+ 'each `{ "slug": …, "data": … }`. Collections are reported separately, so a',
34
+ "regions-only reader still gets a useful answer.",
35
+ "",
36
+ ].join("\n"),
37
+ );
38
+ process.exit(command === "help" ? 0 : 1);
39
+ }
40
+
41
+ const { regions, entries } = args[0] === "--self" ? selfCheck() : verifyDirectory(args[0]);
42
+
43
+ const parts = [`${regions.checked} regions`];
44
+ if (entries) parts.push(`${entries.checked} entries`);
45
+ else parts.push("collections not implemented");
46
+
47
+ if (regions.ok && (!entries || entries.ok)) {
48
+ process.stdout.write(`conformance: PASS (${parts.join(", ")})\n`);
49
+ process.exit(0);
50
+ }
51
+
52
+ const failures = [...regions.failures, ...(entries?.failures ?? [])];
53
+ const count = `${failures.length} problem${failures.length === 1 ? "" : "s"}`;
54
+ process.stderr.write(`conformance: FAIL (${count} across ${parts.join(", ")})\n\n`);
55
+ for (const failure of failures) {
56
+ process.stderr.write(` ${failure.regionId}: ${failure.reason}\n`);
57
+ if (failure.expected !== undefined) {
58
+ process.stderr.write(` expected: ${JSON.stringify(failure.expected)}\n`);
59
+ process.stderr.write(` actual: ${JSON.stringify(failure.actual)}\n`);
60
+ }
61
+ }
62
+ process.stderr.write("\n");
63
+ process.exit(1);
64
+
65
+ function selfCheck() {
66
+ const found = {};
67
+ for (const name of ["blog", "guides"]) {
68
+ for (const entry of readFixtureEntries(name)) {
69
+ found[`${name}/${entry.slug}`] = entry;
70
+ }
71
+ }
72
+ return { regions: runConformance(readFixtureRegion), entries: verifyEntries(found) };
73
+ }
74
+
75
+ function verifyDirectory(dir) {
76
+ if (!dir) {
77
+ process.stderr.write("verify needs a directory, or --self\n");
78
+ process.exit(2);
79
+ }
80
+ const abs = resolve(dir);
81
+ if (!existsSync(abs) || !statSync(abs).isDirectory()) {
82
+ process.stderr.write(`Not a directory: ${abs}\n`);
83
+ process.exit(2);
84
+ }
85
+
86
+ const foundRegions = {};
87
+ for (const file of readdirSync(abs)) {
88
+ if (!file.endsWith(".json")) continue;
89
+ foundRegions[file.slice(0, -".json".length)] = readJson(join(abs, file));
90
+ }
91
+
92
+ // Collections are optional surface: a reader that only does regions is still
93
+ // worth verifying, and saying "not implemented" beats a misleading failure.
94
+ const entriesDir = join(abs, "entries");
95
+ if (!existsSync(entriesDir)) return { regions: verifyRegions(foundRegions), entries: null };
96
+
97
+ const foundEntries = {};
98
+ for (const collection of readdirSync(entriesDir)) {
99
+ const collectionDir = join(entriesDir, collection);
100
+ if (!statSync(collectionDir).isDirectory()) continue;
101
+ for (const file of readdirSync(collectionDir)) {
102
+ if (!file.endsWith(".json")) continue;
103
+ foundEntries[`${collection}/${file.slice(0, -".json".length)}`] = readJson(
104
+ join(collectionDir, file),
105
+ );
106
+ }
107
+ }
108
+
109
+ return { regions: verifyRegions(foundRegions), entries: verifyEntries(foundEntries) };
110
+ }
111
+
112
+ function readJson(path) {
113
+ try {
114
+ return JSON.parse(readFileSync(path, "utf8"));
115
+ } catch (err) {
116
+ process.stderr.write(`${path}: not valid JSON: ${err.message}\n`);
117
+ process.exit(2);
118
+ }
119
+ }
@@ -0,0 +1,43 @@
1
+ import { CmsConfig, CmsSchema, RegionDocument } from "@greatcms/spec";
2
+ //#region src/index.d.ts
3
+ declare const FIXTURES_DIR: string;
4
+ declare const EXPECTED_DIR: string;
5
+ declare function fixtureSchema(): CmsSchema;
6
+ declare function fixtureConfig(): CmsConfig;
7
+ /** The documents every conforming reader must produce, keyed by region id. */
8
+ declare function expectedRegions(): Record<string, RegionDocument>;
9
+ /** Reference implementation: read the fixture site off disk the way the standard says to. */
10
+ declare function readFixtureRegion(regionId: string): RegionDocument | null;
11
+ /** One collection entry, as every conforming reader must produce it. */
12
+ interface ExpectedEntry {
13
+ slug: string;
14
+ data: Record<string, unknown>;
15
+ }
16
+ /**
17
+ * Entries a reader must produce, keyed `<collection>/<slug>`. Unlike a region,
18
+ * an entry is not layered over schema defaults: what is in the file is what it
19
+ * is. Defaults are an authoring convenience, not a read-time rule.
20
+ */
21
+ declare function expectedEntries(): Record<string, ExpectedEntry>;
22
+ /** Reference implementation for collections, matching the reader's rules. */
23
+ declare function readFixtureEntries(name: string): ExpectedEntry[];
24
+ interface Failure {
25
+ regionId: string;
26
+ reason: string;
27
+ expected?: unknown;
28
+ actual?: unknown;
29
+ }
30
+ interface ConformanceResult {
31
+ ok: boolean;
32
+ checked: number;
33
+ failures: Failure[];
34
+ }
35
+ /** Compares resolved documents against the expected set. */
36
+ declare function verifyRegions(actual: Record<string, unknown>): ConformanceResult;
37
+ /** Compares collection entries against the expected set. */
38
+ declare function verifyEntries(actual: Record<string, unknown>): ConformanceResult;
39
+ /** Runs the whole kit against a reader function. */
40
+ declare function runConformance(readRegion: (regionId: string) => unknown): ConformanceResult;
41
+ //#endregion
42
+ export { ConformanceResult, EXPECTED_DIR, ExpectedEntry, FIXTURES_DIR, Failure, expectedEntries, expectedRegions, fixtureConfig, fixtureSchema, readFixtureEntries, readFixtureRegion, runConformance, verifyEntries, verifyRegions };
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;cAoBa;cACA;iBAEG,iBAAiB;iBAMjB,iBAAiB;;iBAKjB,mBAAmB,eAAe;;iBAWlC,kBAAkB,mBAAmB;;UAepC;EACf;EACA,MAAM;;;;;;;iBAQQ,mBAAmB,eAAe;;iBAelC,mBAAmB,eAAe;UAuBjC;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA,UAAU;;;iBAII,cAAc,QAAQ,0BAA0B;;iBA8BhD,cAAc,QAAQ,0BAA0B;;iBA8BhD,eAAe,aAAa,+BAA+B"}
package/dist/index.js ADDED
@@ -0,0 +1,169 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { entryExtension, parseConfig, parseFrontmatter, parseRegionFile, parseSchema, regionFilePath, resolveRegion } from "@greatcms/spec";
5
+ //#region src/index.ts
6
+ /** The conformance kit. */
7
+ const packageRoot = fileURLToPath(new URL("..", import.meta.url));
8
+ const FIXTURES_DIR = join(packageRoot, "fixtures");
9
+ const EXPECTED_DIR = join(packageRoot, "expected");
10
+ function fixtureSchema() {
11
+ return parseSchema(readFileSync(join(FIXTURES_DIR, ".cms/schema.yaml"), "utf8"), { file: ".cms/schema.yaml" });
12
+ }
13
+ function fixtureConfig() {
14
+ return parseConfig(readFileSync(join(FIXTURES_DIR, ".cms/config.yaml"), "utf8"));
15
+ }
16
+ /** The documents every conforming reader must produce, keyed by region id. */
17
+ function expectedRegions() {
18
+ const dir = join(EXPECTED_DIR, "regions");
19
+ const out = {};
20
+ for (const file of readdirSync(dir).toSorted()) {
21
+ if (!file.endsWith(".json")) continue;
22
+ out[file.slice(0, -5)] = JSON.parse(readFileSync(join(dir, file), "utf8"));
23
+ }
24
+ return out;
25
+ }
26
+ /** Reference implementation: read the fixture site off disk the way the standard says to. */
27
+ function readFixtureRegion(regionId) {
28
+ const schema = fixtureSchema();
29
+ const config = fixtureConfig();
30
+ const path = join(FIXTURES_DIR, regionFilePath(config, regionId));
31
+ let stored = null;
32
+ try {
33
+ stored = parseRegionFile(readFileSync(path, "utf8"), path);
34
+ } catch (err) {
35
+ if (err.code !== "ENOENT" && !/ENOENT/.test(String(err))) throw err;
36
+ }
37
+ return resolveRegion(schema, regionId, stored);
38
+ }
39
+ /**
40
+ * Entries a reader must produce, keyed `<collection>/<slug>`. Unlike a region,
41
+ * an entry is not layered over schema defaults: what is in the file is what it
42
+ * is. Defaults are an authoring convenience, not a read-time rule.
43
+ */
44
+ function expectedEntries() {
45
+ const root = join(EXPECTED_DIR, "entries");
46
+ const out = {};
47
+ for (const collection of readdirSync(root).toSorted()) {
48
+ const dir = join(root, collection);
49
+ for (const file of readdirSync(dir).toSorted()) {
50
+ if (!file.endsWith(".json")) continue;
51
+ const slug = file.slice(0, -5);
52
+ out[`${collection}/${slug}`] = JSON.parse(readFileSync(join(dir, file), "utf8"));
53
+ }
54
+ }
55
+ return out;
56
+ }
57
+ /** Reference implementation for collections, matching the reader's rules. */
58
+ function readFixtureEntries(name) {
59
+ const def = fixtureSchema().collections[name];
60
+ if (!def) throw new Error(`Unknown collection "${name}".`);
61
+ const extension = `.${entryExtension(def.format)}`;
62
+ const dir = join(FIXTURES_DIR, def.path);
63
+ return readdirSync(dir).toSorted().filter((file) => file.endsWith(extension)).map((file) => {
64
+ const source = readFileSync(join(dir, file), "utf8");
65
+ const slug = file.slice(0, -extension.length);
66
+ if (def.format === "json") return {
67
+ slug,
68
+ data: JSON.parse(source)
69
+ };
70
+ const { data, body } = parseFrontmatter(source, file);
71
+ if (def.body) data[def.body] = body;
72
+ return {
73
+ slug,
74
+ data
75
+ };
76
+ });
77
+ }
78
+ /** Compares resolved documents against the expected set. */
79
+ function verifyRegions(actual) {
80
+ const expected = expectedRegions();
81
+ const failures = [];
82
+ for (const [regionId, want] of Object.entries(expected)) {
83
+ if (!(regionId in actual)) {
84
+ failures.push({
85
+ regionId,
86
+ reason: "missing from output"
87
+ });
88
+ continue;
89
+ }
90
+ const got = stripMeta(actual[regionId]);
91
+ if (!deepEqual(stripMeta(want), got)) failures.push({
92
+ regionId,
93
+ reason: "does not match expected document",
94
+ expected: want,
95
+ actual: got
96
+ });
97
+ }
98
+ for (const regionId of Object.keys(actual)) if (!(regionId in expected)) failures.push({
99
+ regionId,
100
+ reason: "unexpected region in output"
101
+ });
102
+ return {
103
+ ok: failures.length === 0,
104
+ checked: Object.keys(expected).length,
105
+ failures
106
+ };
107
+ }
108
+ /** Compares collection entries against the expected set. */
109
+ function verifyEntries(actual) {
110
+ const expected = expectedEntries();
111
+ const failures = [];
112
+ for (const [key, want] of Object.entries(expected)) {
113
+ if (!(key in actual)) {
114
+ failures.push({
115
+ regionId: key,
116
+ reason: "missing from output"
117
+ });
118
+ continue;
119
+ }
120
+ const got = stripMeta(actual[key]);
121
+ if (!deepEqual(stripMeta(want), got)) failures.push({
122
+ regionId: key,
123
+ reason: "does not match expected entry",
124
+ expected: want,
125
+ actual: got
126
+ });
127
+ }
128
+ for (const key of Object.keys(actual)) if (!(key in expected)) failures.push({
129
+ regionId: key,
130
+ reason: "unexpected entry in output"
131
+ });
132
+ return {
133
+ ok: failures.length === 0,
134
+ checked: Object.keys(expected).length,
135
+ failures
136
+ };
137
+ }
138
+ /** Runs the whole kit against a reader function. */
139
+ function runConformance(readRegion) {
140
+ const actual = {};
141
+ for (const regionId of Object.keys(expectedRegions())) actual[regionId] = readRegion(regionId);
142
+ return verifyRegions(actual);
143
+ }
144
+ function stripMeta(value) {
145
+ if (Array.isArray(value)) return value.map(stripMeta);
146
+ if (value === null || typeof value !== "object") return value;
147
+ const out = {};
148
+ for (const [key, v] of Object.entries(value)) {
149
+ if (key === "_meta") continue;
150
+ out[key] = stripMeta(v);
151
+ }
152
+ return out;
153
+ }
154
+ function deepEqual(a, b) {
155
+ if (a === b) return true;
156
+ if (Array.isArray(a) || Array.isArray(b)) {
157
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
158
+ return a.every((item, i) => deepEqual(item, b[i]));
159
+ }
160
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
161
+ const ka = Object.keys(a).toSorted();
162
+ const kb = Object.keys(b).toSorted();
163
+ if (ka.length !== kb.length || !ka.every((k, i) => k === kb[i])) return false;
164
+ return ka.every((k) => deepEqual(a[k], b[k]));
165
+ }
166
+ //#endregion
167
+ export { EXPECTED_DIR, FIXTURES_DIR, expectedEntries, expectedRegions, fixtureConfig, fixtureSchema, readFixtureEntries, readFixtureRegion, runConformance, verifyEntries, verifyRegions };
168
+
169
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/** The conformance kit. */\n\nimport { readFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n type CmsConfig,\n type CmsSchema,\n type RegionDocument,\n entryExtension,\n parseConfig,\n parseFrontmatter,\n parseRegionFile,\n parseSchema,\n regionFilePath,\n resolveRegion,\n} from \"@greatcms/spec\";\n\nconst packageRoot = fileURLToPath(new URL(\"..\", import.meta.url));\n\nexport const FIXTURES_DIR = join(packageRoot, \"fixtures\");\nexport const EXPECTED_DIR = join(packageRoot, \"expected\");\n\nexport function fixtureSchema(): CmsSchema {\n return parseSchema(readFileSync(join(FIXTURES_DIR, \".cms/schema.yaml\"), \"utf8\"), {\n file: \".cms/schema.yaml\",\n });\n}\n\nexport function fixtureConfig(): CmsConfig {\n return parseConfig(readFileSync(join(FIXTURES_DIR, \".cms/config.yaml\"), \"utf8\"));\n}\n\n/** The documents every conforming reader must produce, keyed by region id. */\nexport function expectedRegions(): Record<string, RegionDocument> {\n const dir = join(EXPECTED_DIR, \"regions\");\n const out: Record<string, RegionDocument> = {};\n for (const file of readdirSync(dir).toSorted()) {\n if (!file.endsWith(\".json\")) continue;\n out[file.slice(0, -\".json\".length)] = JSON.parse(readFileSync(join(dir, file), \"utf8\"));\n }\n return out;\n}\n\n/** Reference implementation: read the fixture site off disk the way the standard says to. */\nexport function readFixtureRegion(regionId: string): RegionDocument | null {\n const schema = fixtureSchema();\n const config = fixtureConfig();\n const path = join(FIXTURES_DIR, regionFilePath(config, regionId));\n let stored: RegionDocument | null = null;\n try {\n stored = parseRegionFile(readFileSync(path, \"utf8\"), path);\n } catch (err) {\n // A missing file is the normal case for a region nobody has edited yet.\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\" && !/ENOENT/.test(String(err))) throw err;\n }\n return resolveRegion(schema, regionId, stored);\n}\n\n/** One collection entry, as every conforming reader must produce it. */\nexport interface ExpectedEntry {\n slug: string;\n data: Record<string, unknown>;\n}\n\n/**\n * Entries a reader must produce, keyed `<collection>/<slug>`. Unlike a region,\n * an entry is not layered over schema defaults: what is in the file is what it\n * is. Defaults are an authoring convenience, not a read-time rule.\n */\nexport function expectedEntries(): Record<string, ExpectedEntry> {\n const root = join(EXPECTED_DIR, \"entries\");\n const out: Record<string, ExpectedEntry> = {};\n for (const collection of readdirSync(root).toSorted()) {\n const dir = join(root, collection);\n for (const file of readdirSync(dir).toSorted()) {\n if (!file.endsWith(\".json\")) continue;\n const slug = file.slice(0, -\".json\".length);\n out[`${collection}/${slug}`] = JSON.parse(readFileSync(join(dir, file), \"utf8\"));\n }\n }\n return out;\n}\n\n/** Reference implementation for collections, matching the reader's rules. */\nexport function readFixtureEntries(name: string): ExpectedEntry[] {\n const schema = fixtureSchema();\n const def = schema.collections[name];\n if (!def) throw new Error(`Unknown collection \"${name}\".`);\n\n const extension = `.${entryExtension(def.format)}`;\n const dir = join(FIXTURES_DIR, def.path);\n\n return readdirSync(dir)\n .toSorted()\n .filter((file) => file.endsWith(extension))\n .map((file) => {\n const source = readFileSync(join(dir, file), \"utf8\");\n const slug = file.slice(0, -extension.length);\n if (def.format === \"json\") {\n return { slug, data: JSON.parse(source) as Record<string, unknown> };\n }\n const { data, body } = parseFrontmatter(source, file);\n if (def.body) data[def.body] = body;\n return { slug, data };\n });\n}\n\nexport interface Failure {\n regionId: string;\n reason: string;\n expected?: unknown;\n actual?: unknown;\n}\n\nexport interface ConformanceResult {\n ok: boolean;\n checked: number;\n failures: Failure[];\n}\n\n/** Compares resolved documents against the expected set. */\nexport function verifyRegions(actual: Record<string, unknown>): ConformanceResult {\n const expected = expectedRegions();\n const failures: Failure[] = [];\n\n for (const [regionId, want] of Object.entries(expected)) {\n if (!(regionId in actual)) {\n failures.push({ regionId, reason: \"missing from output\" });\n continue;\n }\n const got = stripMeta(actual[regionId]);\n if (!deepEqual(stripMeta(want), got)) {\n failures.push({\n regionId,\n reason: \"does not match expected document\",\n expected: want,\n actual: got,\n });\n }\n }\n\n for (const regionId of Object.keys(actual)) {\n if (!(regionId in expected)) {\n failures.push({ regionId, reason: \"unexpected region in output\" });\n }\n }\n\n return { ok: failures.length === 0, checked: Object.keys(expected).length, failures };\n}\n\n/** Compares collection entries against the expected set. */\nexport function verifyEntries(actual: Record<string, unknown>): ConformanceResult {\n const expected = expectedEntries();\n const failures: Failure[] = [];\n\n for (const [key, want] of Object.entries(expected)) {\n if (!(key in actual)) {\n failures.push({ regionId: key, reason: \"missing from output\" });\n continue;\n }\n const got = stripMeta(actual[key]);\n if (!deepEqual(stripMeta(want), got)) {\n failures.push({\n regionId: key,\n reason: \"does not match expected entry\",\n expected: want,\n actual: got,\n });\n }\n }\n\n for (const key of Object.keys(actual)) {\n if (!(key in expected)) {\n failures.push({ regionId: key, reason: \"unexpected entry in output\" });\n }\n }\n\n return { ok: failures.length === 0, checked: Object.keys(expected).length, failures };\n}\n\n/** Runs the whole kit against a reader function. */\nexport function runConformance(readRegion: (regionId: string) => unknown): ConformanceResult {\n const actual: Record<string, unknown> = {};\n for (const regionId of Object.keys(expectedRegions())) {\n actual[regionId] = readRegion(regionId);\n }\n return verifyRegions(actual);\n}\n\nfunction stripMeta(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(stripMeta);\n if (value === null || typeof value !== \"object\") return value;\n const out: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(value as Record<string, unknown>)) {\n if (key === \"_meta\") continue;\n out[key] = stripMeta(v);\n }\n return out;\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i]));\n }\n if (a === null || b === null || typeof a !== \"object\" || typeof b !== \"object\") return false;\n const ka = Object.keys(a as object).toSorted();\n const kb = Object.keys(b as object).toSorted();\n if (ka.length !== kb.length || !ka.every((k, i) => k === kb[i])) return false;\n return ka.every((k) =>\n deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]),\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ {
2
+ "slug": "empty-frontmatter",
3
+ "data": {
4
+ "content": "\nAn entry whose frontmatter is empty.\n"
5
+ }
6
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "slug": "publish-from-anywhere",
3
+ "data": {
4
+ "title": "Publish from anywhere",
5
+ "date": "2026-08-12",
6
+ "tags": ["release", "git"],
7
+ "related": ["yaml-scalars"],
8
+ "draft": false,
9
+ "content": "\nContent lives in the repository, so a publish is a commit and a deploy is whatever\nCI you already had.\n\nThe CMS never becomes a runtime dependency of your site.\n"
10
+ }
11
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "slug": "yaml-scalars",
3
+ "data": {
4
+ "title": "Answers, not booleans",
5
+ "date": "2026-10-03",
6
+ "tags": ["no", "on", "off", "y", "1:30"],
7
+ "content": "\nYAML 1.1 reads most of those tags as booleans or numbers. This standard reads YAML 1.2.\n"
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "slug": "measuring-latency",
3
+ "data": {
4
+ "title": "Measuring latency",
5
+ "date": "2026-08-14",
6
+ "content": "\nimport Chart from \"../../components/Chart.astro\";\n\nLatency fell once the reads stopped crossing a network.\n\n<Chart data={[240, 120, 18]} caption=\"p99, in milliseconds\" />\n\nThe prose around the component is ordinary Markdown. A reader stores this body\nbyte for byte: it does not parse the JSX, resolve the import, or care whether\n`Chart` exists.\n"
7
+ }
8
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "body": {
3
+ "html": "<p>We have not written this yet.</p>"
4
+ },
5
+ "heading": "About us"
6
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "blocks": [
3
+ {
4
+ "_type": "hero",
5
+ "heading": "Hybrid by default",
6
+ "image": {
7
+ "alt": "Diagram of a page with one managed section",
8
+ "src": "/media/hybrid.webp",
9
+ "width": 900,
10
+ "height": 600
11
+ }
12
+ },
13
+ {
14
+ "_type": "text",
15
+ "body": {
16
+ "html": "<p>Manage the <strong>hero</strong> and leave the rest of the page as code.</p>"
17
+ }
18
+ },
19
+ {
20
+ "_type": "cta",
21
+ "label": "Read the spec",
22
+ "link": {
23
+ "href": "https://example.com/spec",
24
+ "external": true
25
+ },
26
+ "style": "secondary",
27
+ "_variant": "inline"
28
+ },
29
+ {
30
+ "_type": "cta",
31
+ "label": "Browse the examples",
32
+ "link": {
33
+ "href": "/examples"
34
+ },
35
+ "style": "primary",
36
+ "_variant": "panel"
37
+ }
38
+ ]
39
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "cards": [
3
+ {
4
+ "_type": "card",
5
+ "title": "First card",
6
+ "tone": "plain"
7
+ }
8
+ ]
9
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "image": {
3
+ "alt": "",
4
+ "src": "/media/swirl.svg"
5
+ }
6
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "guides": [],
3
+ "post": "publish-from-anywhere"
4
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "cta": {
3
+ "href": "/get-started",
4
+ "label": "Get started"
5
+ },
6
+ "heading": "Publish from anywhere",
7
+ "image": {
8
+ "alt": "A person editing a page on a laptop",
9
+ "height": 800,
10
+ "src": "/media/hero.webp",
11
+ "width": 1200
12
+ },
13
+ "subtitle": "Content in Git, deploys on commit."
14
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "badge": "New",
3
+ "dismissible": true,
4
+ "message": "We ship on every commit."
5
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "seo": {
3
+ "title": "Home",
4
+ "description": "A site managed by GreatCMS"
5
+ }
6
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "blocks": [
3
+ {
4
+ "heading": "What you get",
5
+ "_type": "grid",
6
+ "cards": [
7
+ {
8
+ "tone": "plain",
9
+ "_type": "card",
10
+ "title": "Content lives in Git",
11
+ "body": "Every publish is a commit, so history and rollback come free."
12
+ },
13
+ {
14
+ "tone": "accent",
15
+ "_type": "card",
16
+ "title": "Nothing at runtime"
17
+ }
18
+ ]
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,12 @@
1
+ version: 1
2
+
3
+ content:
4
+ regions: content/cms
5
+ media: public/media
6
+
7
+ mediaUrlPrefix: /media
8
+
9
+ publish:
10
+ branch: main
11
+ draftBranchPrefix: cms/draft
12
+ commitMessage: "content: update {summary}"
@@ -0,0 +1,141 @@
1
+ # Conformance fixture site.
2
+ #
3
+ # Any tool claiming to read the GreatCMS standard must resolve this repo into
4
+ # the documents under `expected/`. It deliberately exercises the awkward cases:
5
+ # a region with no content file, a region edited before a field was added, a
6
+ # blocks region, and a Markdown collection.
7
+
8
+ version: 1
9
+
10
+ site:
11
+ name: Conformance Fixture
12
+
13
+ blocks:
14
+ hero:
15
+ label: Hero
16
+ fields:
17
+ heading: { type: text, required: true, max: 80 }
18
+ image: { type: image }
19
+
20
+ text:
21
+ label: Text
22
+ fields:
23
+ body: { type: richtext }
24
+
25
+ cta:
26
+ label: Call to action
27
+ variants: [{ value: panel, label: Panel }, { value: inline, label: Inline link }]
28
+ fields:
29
+ label: { type: text, required: true }
30
+ link: { type: link, required: true }
31
+ style: { type: select, options: [primary, secondary], default: primary }
32
+
33
+ card:
34
+ label: Card
35
+ fields:
36
+ title: { type: text, required: true }
37
+ body: { type: text }
38
+ tone: { type: select, options: [plain, accent], default: plain }
39
+
40
+ # A block whose own field is a list of blocks. Nesting is the point: a
41
+ # conforming reader must apply defaults at every depth, not only the top.
42
+ grid:
43
+ label: Card grid
44
+ fields:
45
+ heading: { type: text, default: What you get }
46
+ cards: { type: blocks, allow: [card], max: 4 }
47
+
48
+ regions:
49
+ # Fully edited: content file overrides every default.
50
+ home.hero:
51
+ label: Home hero
52
+ fields:
53
+ heading: { type: text, required: true, max: 80 }
54
+ subtitle: { type: text, default: Default subtitle }
55
+ image: { type: image }
56
+ cta: { type: link }
57
+ default:
58
+ heading: Welcome
59
+
60
+ # Edited before `badge` existed: proves new schema fields still render.
61
+ home.notice:
62
+ fields:
63
+ message: { type: text, required: true }
64
+ badge: { type: text, default: New }
65
+ dismissible: { type: boolean, default: true }
66
+
67
+ # Never edited: proves a site builds before the CMS has touched it.
68
+ about.intro:
69
+ fields:
70
+ heading: { type: text, default: About us }
71
+ body: { type: richtext, default: { html: "<p>We have not written this yet.</p>" } }
72
+
73
+ # Block list with nested groups and lists.
74
+ home.body:
75
+ type: blocks
76
+ allow: [hero, text, cta]
77
+ max: 8
78
+
79
+ # Decorative image: empty alt is valid here and only here.
80
+ home.decor:
81
+ fields:
82
+ image: { type: image, alt: decorative-allowed }
83
+
84
+ # Blocks inside blocks, with a default that has to reach two levels down.
85
+ home.showcase:
86
+ type: blocks
87
+ allow: [grid]
88
+
89
+ # Region default over a group: objects merge deeply, so `description` survives.
90
+ home.seo:
91
+ fields:
92
+ seo:
93
+ type: group
94
+ fields:
95
+ title: { type: text, default: Untitled }
96
+ description: { type: text, default: A site managed by GreatCMS }
97
+ default:
98
+ seo: { title: Home }
99
+
100
+ # Never edited, and its default blocks still need their own field defaults.
101
+ home.cards:
102
+ fields:
103
+ cards:
104
+ type: blocks
105
+ allow: [card]
106
+ default:
107
+ - { _type: card, title: First card }
108
+
109
+ # References store slugs as written; a multiple one nobody filled in is `[]`.
110
+ home.featured:
111
+ fields:
112
+ post: { type: reference, collection: blog }
113
+ guides: { type: reference, collection: guides, multiple: true }
114
+
115
+ collections:
116
+ # MDX: stored exactly like Markdown, read exactly like Markdown. The
117
+ # difference is the extension, and that an editor must not reformat the body.
118
+ guides:
119
+ label: Guides
120
+ path: content/guides
121
+ format: mdx
122
+ body: content
123
+ slug: title
124
+ fields:
125
+ title: { type: text, required: true, max: 120 }
126
+ date: { type: date, required: true }
127
+ content: { type: richtext }
128
+
129
+ blog:
130
+ label: Blog
131
+ path: content/blog
132
+ format: markdown
133
+ body: content
134
+ slug: title
135
+ fields:
136
+ title: { type: text, required: true, max: 120 }
137
+ date: { type: date, required: true }
138
+ tags: { type: list, of: { type: text }, max: 5 }
139
+ related: { type: reference, collection: blog, multiple: true }
140
+ draft: { type: boolean, default: false }
141
+ content: { type: richtext }
@@ -0,0 +1,4 @@
1
+ ---
2
+ ---
3
+
4
+ An entry whose frontmatter is empty.
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: Publish from anywhere
3
+ date: 2026-08-12
4
+ tags:
5
+ - release
6
+ - git
7
+ related: [yaml-scalars]
8
+ draft: false
9
+ ---
10
+
11
+ Content lives in the repository, so a publish is a commit and a deploy is whatever
12
+ CI you already had.
13
+
14
+ The CMS never becomes a runtime dependency of your site.
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: Answers, not booleans
3
+ date: 2026-10-03
4
+ tags: [no, on, off, y, 1:30]
5
+ ---
6
+
7
+ YAML 1.1 reads most of those tags as booleans or numbers. This standard reads YAML 1.2.
@@ -0,0 +1,42 @@
1
+ {
2
+ "blocks": [
3
+ {
4
+ "_type": "hero",
5
+ "heading": "Hybrid by default",
6
+ "image": {
7
+ "alt": "Diagram of a page with one managed section",
8
+ "src": "/media/hybrid.webp",
9
+ "width": 900,
10
+ "height": 600
11
+ }
12
+ },
13
+ {
14
+ "_type": "text",
15
+ "body": {
16
+ "html": "<p>Manage the <strong>hero</strong> and leave the rest of the page as code.</p>"
17
+ }
18
+ },
19
+ {
20
+ "_type": "cta",
21
+ "label": "Read the spec",
22
+ "link": {
23
+ "href": "https://example.com/spec",
24
+ "external": true
25
+ },
26
+ "style": "secondary",
27
+ "_variant": "inline"
28
+ },
29
+ {
30
+ "_type": "cta",
31
+ "label": "Browse the examples",
32
+ "link": {
33
+ "href": "/examples"
34
+ }
35
+ }
36
+ ],
37
+ "_meta": {
38
+ "updatedAt": "2026-08-12T18:05:00.000Z",
39
+ "updatedBy": "conformance-fixture",
40
+ "version": 1
41
+ }
42
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "image": {
3
+ "alt": "",
4
+ "src": "/media/swirl.svg"
5
+ }
6
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "post": "publish-from-anywhere",
3
+ "_meta": {
4
+ "updatedAt": "2026-10-01T09:30:00.000Z",
5
+ "updatedBy": "conformance-fixture",
6
+ "version": 1
7
+ }
8
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "cta": {
3
+ "href": "/get-started",
4
+ "label": "Get started"
5
+ },
6
+ "heading": "Publish from anywhere",
7
+ "image": {
8
+ "alt": "A person editing a page on a laptop",
9
+ "height": 800,
10
+ "src": "/media/hero.webp",
11
+ "width": 1200
12
+ },
13
+ "subtitle": "Content in Git, deploys on commit.",
14
+ "_meta": {
15
+ "updatedAt": "2026-08-12T18:00:00.000Z",
16
+ "updatedBy": "conformance-fixture",
17
+ "version": 1
18
+ }
19
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "message": "We ship on every commit.",
3
+ "_meta": {
4
+ "updatedAt": "2026-07-01T09:30:00.000Z",
5
+ "updatedBy": "conformance-fixture",
6
+ "version": 1
7
+ }
8
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "blocks": [
3
+ {
4
+ "_type": "grid",
5
+ "cards": [
6
+ {
7
+ "_type": "card",
8
+ "title": "Content lives in Git",
9
+ "body": "Every publish is a commit, so history and rollback come free."
10
+ },
11
+ {
12
+ "_type": "card",
13
+ "title": "Nothing at runtime",
14
+ "tone": "accent"
15
+ }
16
+ ]
17
+ }
18
+ ],
19
+ "_meta": {
20
+ "updatedAt": "2026-08-15T09:30:00.000Z",
21
+ "updatedBy": "conformance-fixture",
22
+ "version": 1
23
+ }
24
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: Measuring latency
3
+ date: 2026-08-14
4
+ ---
5
+
6
+ import Chart from "../../components/Chart.astro";
7
+
8
+ Latency fell once the reads stopped crossing a network.
9
+
10
+ <Chart data={[240, 120, 18]} caption="p99, in milliseconds" />
11
+
12
+ The prose around the component is ordinary Markdown. A reader stores this body
13
+ byte for byte: it does not parse the JSX, resolve the import, or care whether
14
+ `Chart` exists.
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@greatcms/conformance",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "description": "Fixtures and a verifier that define what reading the GreatCMS standard correctly means, in any language.",
5
+ "keywords": [
6
+ "cms",
7
+ "conformance",
8
+ "fixtures",
9
+ "testing"
10
+ ],
11
+ "homepage": "https://greatcms.dev",
12
+ "bugs": {
13
+ "url": "https://github.com/uwenayoallain/thegreatcms/issues"
14
+ },
15
+ "license": "MIT",
16
+ "author": "UWENAYO Alain Pacifique",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/uwenayoallain/thegreatcms.git",
20
+ "directory": "packages/conformance"
21
+ },
22
+ "bin": {
23
+ "greatcms-conformance": "./bin/greatcms-conformance.mjs"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "fixtures",
28
+ "expected",
29
+ "bin",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "type": "module",
34
+ "main": "./dist/index.js",
35
+ "types": "./dist/index.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "default": "./dist/index.js"
40
+ },
41
+ "./fixtures/*": "./fixtures/*"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "dependencies": {
47
+ "@greatcms/spec": "0.1.1"
48
+ },
49
+ "devDependencies": {
50
+ "vitest": "^4.1.11"
51
+ },
52
+ "engines": {
53
+ "node": ">=20"
54
+ },
55
+ "scripts": {
56
+ "build": "tsdown",
57
+ "typecheck": "tsc -p tsconfig.json --noEmit",
58
+ "test": "vitest run"
59
+ }
6
60
  }