#!/usr/bin/env node // Adds the model keys to the editor schemas in schemas/ (the CLI keeps the // strict originals): `model` on main.archify, `ref` and `diagram` on // architecture components, and a schema for *.archify-registry files. // Run after sync-archify.mjs, or on its own after editing this file. import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const schemas = path.join(root, 'schemas'); const read = (file) => JSON.parse(fs.readFileSync(path.join(schemas, file), 'utf8')); const write = (file, value) => fs.writeFileSync(path.join(schemas, file), `${JSON.stringify(value, null, 2)}\n`); const MODEL = { type: 'object', description: 'Only in main.archify: the entry point of a model (a folder of diagrams that belong together).', additionalProperties: false, required: ['id', 'name'], properties: { id: { type: 'string', pattern: '^[a-zA-Z][a-zA-Z0-9_-]*$', description: 'Stable id of the model.' }, name: { type: 'string', minLength: 1, description: 'Name shown on the model page.' }, description: { type: 'string', description: 'What the model covers; shown on the model page.' }, }, }; const DIAGRAM_LINK = { type: 'string', description: 'Diagram that explains this element, relative to the model root (main.archify). Double-click the node to open it.' }; const DECISIONS = { type: 'array', items: { type: 'string', minLength: 1 }, description: 'Ids of architecture decisions (*.archify-decision files of the model) this is about. Shown in the Decisions panel under the diagram.' }; const NODE_COLLECTIONS = ['components', 'nodes', 'participants', 'states']; const EDGE_COLLECTIONS = ['connections', 'edges', 'flows', 'transitions', 'messages']; for (const file of fs.readdirSync(schemas)) { if (!/^(architecture|workflow|sequence|dataflow|lifecycle)\.schema\.json$/.test(file)) continue; const schema = read(file); schema.properties.model = MODEL; schema.properties.decisions = DECISIONS; for (const key of [...NODE_COLLECTIONS, ...EDGE_COLLECTIONS]) { const items = schema.properties[key]?.items; if (items?.properties) items.properties.decisions = DECISIONS; } if (file === 'architecture.schema.json') { const item = schema.properties.components.items; item.properties.ref = { type: 'string', pattern: '^[a-zA-Z][a-zA-Z0-9_-]*$', description: 'A shared element from the model’s registry (*.archify-registry). Its type, label, sublabel, brand and sources come from there; the diagram adds pos/size and may override label, sublabel and tag.' }; item.properties.diagram = DIAGRAM_LINK; // A ref brings type and label with it; an inline component must state them. item.anyOf = [{ required: ['ref'] }, { required: ['id', 'type', 'label'] }]; delete item.required; } write(file, schema); } const common = read('common.schema.json'); const componentType = common.$defs.componentType; const registry = { $schema: 'https://json-schema.org/draft/2020-12/schema', title: 'Archify model registry', description: 'Shared elements of a model: one name for one thing. Diagrams refer to an element with `ref: `.', type: 'object', additionalProperties: false, required: ['elements'], properties: { schema_version: { const: 1 }, elements: { type: 'object', description: 'Element id → element. Ids are unique across all registry files of the model.', propertyNames: { pattern: '^[a-zA-Z][a-zA-Z0-9_-]*$' }, additionalProperties: { type: 'object', additionalProperties: false, required: ['type', 'label'], properties: { type: componentType, label: { type: 'string', minLength: 1 }, sublabel: { type: 'string' }, tag: { type: 'string' }, icon: common.$defs.nodeIcon, brand: common.$defs.brandMark, sources: common.$defs.sourceReferences, description: { type: 'string', description: 'The one description of this thing; shown on hover and on the model page.' }, diagram: DIAGRAM_LINK, }, }, }, }, }; write('archify-registry.schema.json', registry); const decision = { $schema: 'https://json-schema.org/draft/2020-12/schema', title: 'Archify architecture decision', description: 'One architecture decision of a model. Diagrams point at it with `decisions: []`.', type: 'object', additionalProperties: false, required: ['decision'], properties: { schema_version: { const: 1 }, decision: { type: 'object', additionalProperties: false, required: ['id', 'title'], properties: { id: { type: 'string', minLength: 1, description: 'Unique in the model. Any short string.' }, title: { type: 'string', minLength: 1 }, status: { enum: ['proposed', 'accepted', 'superseded', 'rejected'], description: 'A decision another one supersedes shows as superseded whatever this says.' }, date: { type: 'string', description: 'YYYY-MM-DD.' }, elements: { type: 'array', items: { type: 'string', minLength: 1 }, description: 'Registry element ids the decision is about.' }, diagrams: { type: 'array', items: { type: 'string', minLength: 1 }, description: 'Diagram paths (from the model root) the decision names.' }, supersedes: { type: 'array', items: { type: 'string', minLength: 1 }, description: 'Ids of older decisions this one replaces.' }, }, }, context: { type: 'string', description: 'The situation and what forced a decision.' }, decision: { type: 'string', description: 'What was decided.' }, consequences: { type: 'string', description: 'What becomes easier, harder, or must change.' }, }, }; write('archify-decision.schema.json', decision); console.log('Patched editor schemas: model, ref, diagram, decisions; wrote archify-registry.schema.json and archify-decision.schema.json');