#!/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 each // kind's nodes, 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']; const NODE_COLLECTION_OF = { architecture: 'components', workflow: 'nodes', dataflow: 'nodes', sequence: 'participants', lifecycle: 'states' }; 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; } // The list where each kind keeps its nodes takes `ref` (as the extension's resolver does). const nodes = NODE_COLLECTION_OF[file.replace('.schema.json', '')]; const item = schema.properties[nodes]?.items; if (item?.properties) { 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 its own placement and may override label, sublabel and tag.' }; item.properties.diagram = DIAGRAM_LINK; // A ref brings type and label with it; an inline node must state them. Re-runs keep the original list. const required = item.required ?? item.anyOf?.[1]?.required; if (required) { // Placement (lane/col, stage/row) stays the diagram's own; only id, type and label may come from the ref. const placement = required.filter((key) => !['id', 'type', 'label'].includes(key)); item.anyOf = [{ required: ['ref', ...placement] }, { required }]; 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');