import fs from 'node:fs' import path from 'node:path' import crypto from 'node:crypto' const sourcePath = 'contracts/fields/kbx.fields.json' const source = JSON.parse(fs.readFileSync(sourcePath, 'utf8')) const fields = [...source.fields].sort((a, b) => a.key.localeCompare(b.key)) const hash = crypto.createHash('sha256').update(JSON.stringify(source)).digest('hex') const tsDir = 'packages/kbx-contracts/src/generated' const csDir = 'backend/Shared/Contracts/Generated' fs.mkdirSync(tsDir, { recursive: true }) fs.mkdirSync(csDir, { recursive: true }) fs.mkdirSync('generated', { recursive: true }) const q = value => JSON.stringify(value) const ts = [ '// GENERATED FILE. DO NOT EDIT.', `// Source: ${sourcePath}`, `// Source SHA256: ${hash}`, '', `export const kbxFieldDictionaryVersion = ${q(source.version)} as const`, '', 'export const kbxFieldCatalog = {', ...fields.map(f => ` ${q(f.key)}: ${JSON.stringify(f)},`), '} as const', '', 'export type KbxKnownFieldKey = keyof typeof kbxFieldCatalog', 'export type KbxKnownFieldDefinition = (typeof kbxFieldCatalog)[KbxKnownFieldKey]', '', 'export function getKbxField(key: K): (typeof kbxFieldCatalog)[K] {', ' return kbxFieldCatalog[key]', '}', '', ].join('\n') fs.writeFileSync(path.join(tsDir, 'fieldCatalog.ts'), ts) const csString = value => value == null ? 'null' : `"${String(value).replaceAll('\\','\\\\').replaceAll('"','\\"')}"` const csBool = value => value ? 'true' : 'false' const pascal = s => s[0].toUpperCase() + s.slice(1) const cs = [ '// ', `// Source: ${sourcePath}`, `// Source SHA256: ${hash}`, 'namespace Kbx.Contracts.Generated;', '', 'public sealed record KbxFieldMetadata(', ' string Key, string Label, string DataType, IReadOnlyList Aliases,', ' bool Required, int? MaxLength, int? Precision, int? Scale, string? LookupEntity,', ' bool Readonly, bool Importable, bool Exportable, bool Sensitive, string? Masking);', '', 'public static class KbxFieldKeys', '{', ...fields.map(f => ` public const string ${pascal(f.key)} = "${f.key}";`), '}', '', 'public static class KbxFieldCatalog', '{', ` public const string Version = "${source.version}";`, ' private static readonly IReadOnlyDictionary Items =', ' new Dictionary(StringComparer.Ordinal)', ' {', ...fields.map(f => { const aliases = (f.aliases ?? []).map(csString).join(', ') return ` ["${f.key}"] = new("${f.key}", ${csString(f.label)}, "${f.dataType}", new[] { ${aliases} }, ${csBool(Boolean(f.required))}, ${f.maxLength ?? 'null'}, ${f.precision ?? 'null'}, ${f.scale ?? 'null'}, ${csString(f.lookupEntity)}, ${csBool(Boolean(f.readonly))}, ${csBool(Boolean(f.importable))}, ${csBool(Boolean(f.exportable))}, ${csBool(Boolean(f.sensitive))}, ${csString(f.masking)}),` }), ' };', '', ' public static KbxFieldMetadata Get(string key) => Items.TryGetValue(key, out var value)', ' ? value', ' : throw new KeyNotFoundException($"Unknown KBX field key: {key}");', '', ' public static bool TryGet(string key, out KbxFieldMetadata? value) => Items.TryGetValue(key, out value);', ' public static IReadOnlyCollection All => Items.Values;', '}', '', ].join('\n') fs.writeFileSync(path.join(csDir, 'KbxFieldCatalog.g.cs'), cs) const manifest = { version: source.version, source: sourcePath, sourceSha256: hash, count: fields.length, fields, } fs.writeFileSync('generated/field-manifest.json', JSON.stringify(manifest, null, 2) + '\n') console.log(`field contracts generated: ${fields.length} fields, version ${source.version}`)