0.3.0: .archify files are YAML and open straight into the diagram

- .archify content is YAML (JSON still accepted); converted to JSON for the renderer
- Archify Diagram custom editor is the default for *.archify; Show Source opens the YAML beside it
- YAML syntax errors and validation problems mapped to YAML lines
- yamlValidation for schema completion with redhat.vscode-yaml
- scripts/to-yaml.mjs converts existing .archify files
This commit is contained in:
2026-09-30 15:21:55 +03:00
parent 7c2624c465
commit d2f52d43ac
18 changed files with 744 additions and 250 deletions
+27 -1
View File
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { hasSourceEvidence, isArchifyHtml, readDiagramSource, stripSourceEvidence, typeFromFilename } from '../src/detect';
import { pointerSegments, rangeForPointer } from '../src/jsonPointer';
import { pointerSegments, rangeForPointer, rangeForPointerYaml, yamlSyntaxProblems } from '../src/jsonPointer';
test('reads diagram_type from content', () => {
const source = readDiagramSource('{"schema_version":1,"diagram_type":"sequence","meta":{}}', 'plain.json');
@@ -58,4 +58,30 @@ test('.archify files are diagram sources typed by their content', async () => {
assert.equal(diagramBaseName('/w/web.architecture.json'), 'web.architecture');
assert.equal(readDiagramSource('{"diagram_type":"lifecycle"}', 'run.archify')?.type, 'lifecycle');
assert.equal(readDiagramSource('{"title":"no type yet"}', 'run.archify'), undefined);
const yaml = readDiagramSource('schema_version: 1\ndiagram_type: sequence\nmeta:\n title: T\n', 'flow.archify');
assert.equal(yaml?.type, 'sequence');
assert.equal(yaml?.format, 'yaml');
assert.deepEqual(JSON.parse(yaml!.json), { schema_version: 1, diagram_type: 'sequence', meta: { title: 'T' } });
assert.equal(readDiagramSource('diagram_type: [unclosed', 'bad.archify'), undefined);
});
test('yaml pointer ranges match the json behaviour', () => {
const text = 'meta:\n title: x\ncomponents:\n - id: a\n label: A\n';
const meta = rangeForPointerYaml(text, '/meta');
assert.equal(text.slice(meta.start, meta.end), 'meta');
const title = rangeForPointerYaml(text, '/meta/title');
assert.equal(text.slice(title.start, title.end), 'x');
const item = rangeForPointerYaml(text, '/components/0');
assert.equal(text.slice(item.start, item.end), 'i', 'first character of the item');
const label = rangeForPointerYaml(text, '/components/0/label');
assert.equal(text.slice(label.start, label.end), 'A');
const missing = rangeForPointerYaml(text, '/components/0/type');
assert.equal(text.slice(missing.start, missing.end), 'i', 'falls back to the nearest existing ancestor');
});
test('yaml syntax problems carry offsets', () => {
assert.deepEqual(yamlSyntaxProblems('a: 1\nb: 2\n'), []);
const problems = yamlSyntaxProblems('a: [1, 2\nb: 3\n');
assert.ok(problems.length > 0);
assert.ok(problems[0].start >= 0 && problems[0].end > problems[0].start);
});