@davidvornholt/standards 0.10.0 → 0.10.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.
@@ -0,0 +1,67 @@
1
+ // Deterministic block-style YAML for JSON-shaped data. Bun ships a YAML
2
+ // serializer, but it emits flow style, and generated files must stay readable
3
+ // in consumer diffs. Like cli.ts, this module is zero-dependency so `bunx` can
4
+ // execute the published package.
5
+
6
+ import { isRecord } from './github-settings-parse';
7
+
8
+ const isScalar = (value: unknown): boolean =>
9
+ value === null ||
10
+ typeof value === 'string' ||
11
+ typeof value === 'number' ||
12
+ typeof value === 'boolean';
13
+
14
+ const BARE_KEY = /^[A-Za-z0-9_-]+$/u;
15
+
16
+ const emitKey = (key: string): string =>
17
+ BARE_KEY.test(key) ? key : JSON.stringify(key);
18
+
19
+ // Strings are always double-quoted; JSON string syntax is valid YAML.
20
+ const emitScalar = (value: unknown): string =>
21
+ typeof value === 'string' ? JSON.stringify(value) : String(value);
22
+
23
+ const emitEntry = (
24
+ key: string,
25
+ value: unknown,
26
+ indent: string,
27
+ ): Array<string> => {
28
+ if (isScalar(value)) {
29
+ return [`${indent}${key}: ${emitScalar(value)}`];
30
+ }
31
+ if (Array.isArray(value)) {
32
+ return value.length === 0
33
+ ? [`${indent}${key}: []`]
34
+ : [
35
+ `${indent}${key}:`,
36
+ ...value.flatMap((item) => emitItem(item, `${indent} `)),
37
+ ];
38
+ }
39
+ if (isRecord(value)) {
40
+ return Object.keys(value).length === 0
41
+ ? [`${indent}${key}: {}`]
42
+ : [`${indent}${key}:`, ...emitEntries(value, `${indent} `)];
43
+ }
44
+ throw new Error(`Unsupported YAML value under key "${key}"`);
45
+ };
46
+
47
+ const emitEntries = (
48
+ record: Record<string, unknown>,
49
+ indent: string,
50
+ ): Array<string> =>
51
+ Object.entries(record).flatMap(([key, value]) =>
52
+ emitEntry(emitKey(key), value, indent),
53
+ );
54
+
55
+ const emitItem = (item: unknown, indent: string): Array<string> => {
56
+ if (isScalar(item)) {
57
+ return [`${indent}- ${emitScalar(item)}`];
58
+ }
59
+ if (isRecord(item) && Object.keys(item).length > 0) {
60
+ const lines = emitEntries(item, `${indent} `);
61
+ return [`${indent}- ${(lines[0] ?? '').trimStart()}`, ...lines.slice(1)];
62
+ }
63
+ throw new Error('Unsupported YAML sequence item');
64
+ };
65
+
66
+ export const emitYamlDocument = (document: Record<string, unknown>): string =>
67
+ `${emitEntries(document, '').join('\n')}\n`;
@@ -0,0 +1,25 @@
1
+ import { parseDocument } from 'yaml';
2
+
3
+ export const parseYaml = (
4
+ raw: string,
5
+ label: string,
6
+ ): { readonly value: unknown; readonly problem: string | null } => {
7
+ try {
8
+ const unmergedDocument = parseDocument(raw, {
9
+ merge: false,
10
+ uniqueKeys: true,
11
+ });
12
+ if (unmergedDocument.errors.length === 0) {
13
+ const document = parseDocument(raw, { merge: true, uniqueKeys: true });
14
+ if (document.errors.length === 0) {
15
+ return { value: document.toJS() as unknown, problem: null };
16
+ }
17
+ }
18
+ } catch {
19
+ // The caller reports one stable configuration error for parser failures.
20
+ }
21
+ return {
22
+ value: null,
23
+ problem: `${label} must contain valid YAML with unique mapping keys`,
24
+ };
25
+ };