Compare commits

..
10 Commits
Author SHA1 Message Date
root-at-skic 20786f4b5a Merge pull request 'Release 0.5.2: Archify menu section at the bottom, commands named "Archify | …"' (#18) from fix/archify-menu-section into main 2026-10-10 05:04:55 -07:00
root-at-skic 0b536f3e10 Name all commands "Archify | …", keep the menu section last; 0.5.2
All eight commands drop the Archify category and carry the prefix in the
title, so menus, the editor title bar and the Command Palette read the
same. The right-click group is zzz_archify: VS Code has a built-in
z_commands group that would otherwise sort after it.
2026-10-10 15:04:52 +03:00
root-at-skic bc294dc6bd Rename the new-diagram command to Archify | New Diagram… 2026-10-10 13:49:27 +03:00
root-at-skic 1a5482b4bf Put Archify right-click entries in their own section at the bottom
Explorer and editor-tab context menus: the Archify commands used the
built-in navigation group and were mixed with New File, New Folder and
the rest. They now use a z_archify group, which VS Code sorts last and
separates with a divider.
2026-10-10 13:47:57 +03:00
root-at-skic ac9200ccdd Merge pull request 'Release 0.5.1: describe .archify (YAML) as the diagram format everywhere' (#17) from docs/clarify-formats into main 2026-10-07 02:55:37 -07:00
root-at-skic 9871f774f4 Describe .archify (YAML) as the diagram format everywhere (0.5.1)
- Marketplace description, README and messages lead with .archify files
  in YAML; Archify's JSON files and rendered HTML are described as also
  supported, in their own README section and a supported-files table
- Rename the rendered-HTML editor to Archify HTML Viewer (it shared the
  extension's name) and its command to Open in Archify HTML Viewer
- Empty or broken .archify files ask for valid YAML instead of JSON
2026-10-07 12:55:27 +03:00
root-at-skic ca3f08bac9 Merge pull request 'Release 0.5.0' (#16) from release/0.5.0 into main 2026-10-04 12:16:46 -07:00
root-at-skic 86f6d5c668 Release 0.5.0 2026-10-04 22:16:39 +03:00
root-at-skic 789ccb2d5c Merge pull request 'Brand completion and unknown-brand errors on their line' (#15) from feat/brand-completion into main 2026-10-04 12:16:30 -07:00
root-at-skic adc5cdbe97 Suggest brand IDs after brand: and place unknown-brand errors on their line
- Completion for brand values in .archify (YAML) and Archify JSON sources,
  from the bundled brand-marks/catalog.json (107 marks); aliases and
  product names match, so "kafka" offers apache-kafka
- brand/unknown diagnostics carry their JSON pointer only at the start of
  the message; use it when subject.path is missing, so the error lands on
  the brand line instead of the top of the file
- Unit tests for catalog loading, value detection and pointer fallback;
  integration checks for completion and diagnostic placement

Closes #5
2026-10-04 22:05:30 +03:00
12 changed files with 261 additions and 47 deletions

No files matched your search

+16
View File
@@ -1,5 +1,21 @@
# Changelog
## 0.5.2 — 2026-10-10
- **Right-click menus:** the Archify commands (Archify | New Diagram…, Archify | Open Preview, Archify | Render to HTML File…, Archify | Open in HTML Viewer) now sit in their own section at the very bottom of the Explorer and editor-tab menus, instead of between New File, New Folder and the other built-in entries.
- **All commands are named "Archify | …"**, the same in right-click menus, the editor title bar and the Command Palette: Archify | New Diagram… (was *New Diagram Archify…*), Archify | Open Preview, Archify | Open Preview to the Side, Archify | Render to HTML File…, Archify | Open in HTML Viewer, Archify | Open in Browser, Archify | Refresh Preview, Archify | Show Source.
## 0.5.1 — 2026-10-07
- **Clearer descriptions:** the Marketplace description, README and messages now say plainly that diagrams are `.archify` files in YAML. Archify's own JSON files (`*.architecture.json`, …) and rendered HTML are described as also supported, in their own section.
- The rendered-HTML editor is now called **Archify HTML Viewer** (was *Archify Diagram Viewer*, the same name as the extension), and its command **Open in Archify HTML Viewer**.
- Messages: "open a .archify diagram first"; an empty or broken `.archify` file asks for valid YAML, not JSON.
## 0.5.0 — 2026-10-04
- **Brand completion:** after `brand:` in `.archify` files and Archify JSON sources, the editor suggests the 107 built-in brand IDs with their product names. Aliases and product names match too (`kafka` → `apache-kafka`, `postgres` → `postgresql`).
- **Unknown brands are underlined on their own line.** Archify reports these without a location, so they used to appear at the top of the file.
## 0.4.1 — 2026-10-04
- The new-diagram command is now titled **New Diagram Archify…** everywhere, including the Explorer folder menu, where it read *New Diagram…*.
+26 -11
View File
@@ -1,14 +1,24 @@
# Archify Diagram Viewer
View [Archify](https://github.com/tt-a1i/archify) diagrams inside VS Code. Click a `.archify` file (YAML) and the interactive diagram opens straight away; open its source beside it and the diagram follows your edits. Rendered Archify HTML files open in a viewer tab instead of a browser.
Create and view [Archify](https://github.com/tt-a1i/archify) diagrams inside VS Code. You write a diagram as a **`.archify` file in YAML**; clicking it opens the interactive diagram, and the diagram follows your edits as you type.
![An Archify workflow diagram as shown by the preview](docs/preview.png)
This is an unofficial extension. It bundles the Archify renderer (MIT, © tt-a1i) and runs it on VS Code's own Node runtime, so nothing else needs to be installed.
## Supported files
| File | Format | What the extension does |
|---|---|---|
| **`name.archify`** | **YAML** (the main format) | Opens as the interactive diagram. Show Source, live updates, validation, completion, templates. |
| `name.architecture.json`, `name.workflow.json`, `name.sequence.json`, `name.dataflow.json`, `name.lifecycle.json` | JSON (Archify's own CLI format) | Opens as text, with a preview beside it and validation. For files produced by the Archify CLI or AI agents. |
| Rendered Archify `.html` | HTML | Read-only viewer instead of a browser. |
New diagrams should be `.archify` files; everything below describes them unless it says otherwise.
## Quick start
The fastest start: right-click a folder in the Explorer and choose **New Diagram Archify…** (also in *File → New File…* and the Command Palette). Pick a kind of diagram (Architecture, Workflow, Sequence, Data Flow, Lifecycle or Everyday Plan), give it a name, and a small working `.archify` file is created and opened. Change its YAML to fit your case.
The fastest start: right-click a folder in the Explorer and choose **Archify | New Diagram…** (also in *File → New File…* and the Command Palette). Pick a kind of diagram (Architecture, Workflow, Sequence, Data Flow, Lifecycle or Everyday Plan), give it a name, and a small working `.archify` file is created and opened. Change its YAML to fit your case.
To write one by hand:
@@ -36,30 +46,35 @@ The same file is in [examples/hello.archify](examples/hello.archify).
## The `.archify` format
A `.archify` file is an Archify diagram source written in **YAML**. It has the same fields as the JSON the Archify CLI renders, and plain JSON content still works, since JSON is valid YAML. `diagram_type` selects the kind of diagram: `architecture`, `workflow`, `sequence`, `dataflow` or `lifecycle`.
A `.archify` file is an Archify diagram written in **YAML**. Its fields are those of Archify's diagram schema; the extension converts the YAML for the renderer, so you never handle JSON yourself. `diagram_type` selects the kind of diagram: `architecture`, `workflow`, `sequence`, `dataflow` or `lifecycle`.
- **Opening:** the **Archify Diagram** editor is the default for `.archify`. Use *Reopen Editor With… → Text Editor* to edit the YAML full-screen.
- **Completion:** with the [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) (`redhat.vscode-yaml`) installed, `.archify` files get completion and hover from Archify's schemas. The schema follows `diagram_type`.
Archify's own JSON naming, `name.architecture.json`, `name.workflow.json` and so on, works too. Those open as text with a preview button, and get schema help from VS Code's built-in JSON support.
`.archify` files from extension version 0.2.0 were written in JSON. They still open, because JSON is valid YAML, but new files should use YAML.
## Archify's JSON files
The Archify CLI and the AI agents that use it write diagrams as JSON, named `name.architecture.json`, `name.workflow.json` and so on. The extension supports these too: they open as text with a preview button in the editor title (<kbd>Cmd/Ctrl+K V</kbd> to the side, <kbd>Cmd/Ctrl+Shift+V</kbd> in place), get validation in Problems, and get schema completion from VS Code's built-in JSON support. Any other `.json` file that declares an Archify `diagram_type` previews the same way.
## Features
- **New diagram from a template**: *New Diagram Archify…* creates a starter `.archify` file for any of the five diagram kinds, or an everyday plan, in the chosen folder.
- **New diagram from a template**: *Archify | New Diagram…* creates a starter `.archify` file for any of the five diagram kinds, or an everyday plan, in the chosen folder.
- **Diagram editor**: `.archify` files open as the rendered diagram. **Show Source** opens the YAML beside it, and edits re-render live.
- **Live preview**: for Archify's `*.architecture.json`-style files, or any JSON with a `diagram_type`. Use the preview button in the editor title, <kbd>Cmd/Ctrl+K V</kbd> (to the side) or <kbd>Cmd/Ctrl+Shift+V</kbd>. The preview keeps the focus and route you selected (`#focus=…`, `#route=…`) when it re-renders.
- **HTML viewer**: right-click a rendered `.html` file and choose **Open in Archify Viewer**, or use *Reopen Editor With… → Archify Diagram Viewer*. For Archify HTML already open as text, a button appears in the editor title. The viewer reloads when the file changes on disk.
- **Problems**: YAML syntax errors, plus `archify validate`'s schema, layout and evidence problems, appear at the matching line of the source.
- **Schema help**: completion and hover come from Archify's own schemas: built in for the JSON files, and through the YAML extension for `.archify` files.
- **Live updates**: the diagram re-renders as you edit, and keeps the focus and route you selected (`#focus=…`, `#route=…`).
- **HTML viewer**: right-click a rendered `.html` file and choose **Archify | Open in HTML Viewer**, or use *Reopen Editor With… → Archify HTML Viewer*. For Archify HTML already open as text, a button appears in the editor title. The viewer reloads when the file changes on disk.
- **Problems**: YAML syntax errors, plus `archify validate`'s schema, layout and evidence problems, appear at the matching line of the `.archify` file.
- **Brand logos**: after `brand:` the editor suggests Archify's built-in brand IDs (PostgreSQL, Redis, Kafka, Docker, …). Typing an alias or product name finds them too, so `kafka` offers `apache-kafka`. An unknown brand is reported on its own line.
- **Schema help**: completion and hover from Archify's own schemas, through the YAML extension for `.archify` files.
- **Source links**: in source-backed diagrams, clicking a source reference opens that file and line range in the editor. If the file is not in the workspace, the web link opens instead.
- **Export**: the diagram's own Export menu (PNG, SVG, share card, …) saves through a VS Code save dialog.
- **Commands**: *New Diagram Archify…*, *Archify: Render to HTML File…*, *Archify: Open in Browser*, *Archify: Refresh Preview*, *Archify: Show Source*.
- **Commands**: *Archify | New Diagram…*, *Archify | Render to HTML File…*, *Archify | Open in Browser*, *Archify | Refresh Preview*, *Archify | Show Source*.
## Source evidence
A diagram that declares `meta.repository` and `sources` is verified against a local Git checkout whose `origin` and pinned commit match. The extension uses the Git top-level directory of the diagram file, or `archify.repoRoot` when set.
When verification fails, the default (`archify.sourceEvidence: "fallback"`) still shows the diagram without source links, explains why in a notice, and reports the reason as a warning. Set it to `"strict"` to treat this as an error, as the Archify CLI does. *Render to HTML File…* always verifies strictly and asks before rendering without source links.
When verification fails, the default (`archify.sourceEvidence: "fallback"`) still shows the diagram without source links, explains why in a notice, and reports the reason as a warning. Set it to `"strict"` to treat this as an error, as the Archify CLI does. *Archify | Render to HTML File…* always verifies strictly and asks before rendering without source links.
## Settings
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "archify-vscode-ext",
"version": "0.4.1",
"version": "0.5.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "archify-vscode-ext",
"version": "0.4.1",
"version": "0.5.2",
"license": "MIT",
"dependencies": {
"yaml": "^2.9.1"
+17 -23
View File
@@ -1,8 +1,8 @@
{
"name": "archify-vscode-ext",
"displayName": "Archify Diagram Viewer",
"description": "Visualize Archify diagrams: .archify files (YAML) open straight into the interactive diagram; also previews Archify JSON sources and rendered Archify HTML inside VS Code.",
"version": "0.4.1",
"description": "Archify diagrams in VS Code. Write diagrams as .archify files in YAML: they open as the interactive diagram, with live updates, validation, templates and completion. Also previews Archify's own JSON files (*.architecture.json, \u2026) and opens rendered Archify HTML.",
"version": "0.5.2",
"publisher": "root-at-skic",
"license": "MIT",
"icon": "media/icon.png",
@@ -23,6 +23,7 @@
],
"keywords": [
"archify",
"yaml",
"diagram",
"architecture",
"sequence",
@@ -40,49 +41,42 @@
"commands": [
{
"command": "archify.newDiagram",
"title": "New Diagram Archify\u2026",
"title": "Archify | New Diagram\u2026",
"icon": "$(new-file)"
},
{
"command": "archify.showPreview",
"title": "Open Preview",
"category": "Archify",
"title": "Archify | Open Preview",
"icon": "$(open-preview)"
},
{
"command": "archify.showPreviewToSide",
"title": "Open Preview to the Side",
"category": "Archify",
"title": "Archify | Open Preview to the Side",
"icon": "$(open-preview)"
},
{
"command": "archify.openHtmlViewer",
"title": "Open in Archify Viewer",
"category": "Archify",
"title": "Archify | Open in HTML Viewer",
"icon": "$(open-preview)"
},
{
"command": "archify.renderToHtml",
"title": "Render to HTML File\u2026",
"category": "Archify",
"title": "Archify | Render to HTML File\u2026",
"icon": "$(file-code)"
},
{
"command": "archify.openInBrowser",
"title": "Open in Browser",
"category": "Archify",
"title": "Archify | Open in Browser",
"icon": "$(globe)"
},
{
"command": "archify.refreshPreview",
"title": "Refresh Preview",
"category": "Archify",
"title": "Archify | Refresh Preview",
"icon": "$(refresh)"
},
{
"command": "archify.showSource",
"title": "Show Source",
"category": "Archify",
"title": "Archify | Show Source",
"icon": "$(go-to-file)"
}
],
@@ -153,29 +147,29 @@
{
"command": "archify.newDiagram",
"when": "explorerResourceIsFolder",
"group": "navigation@10"
"group": "zzz_archify@1"
},
{
"command": "archify.showPreview",
"when": "resourceFilename =~ /(\\.archify|\\.(architecture|workflow|sequence|dataflow|lifecycle)\\.json)$/",
"group": "navigation@20"
"group": "zzz_archify@2"
},
{
"command": "archify.renderToHtml",
"when": "resourceFilename =~ /(\\.archify|\\.(architecture|workflow|sequence|dataflow|lifecycle)\\.json)$/",
"group": "navigation@21"
"group": "zzz_archify@3"
},
{
"command": "archify.openHtmlViewer",
"when": "resourceExtname == .html",
"group": "navigation@20"
"group": "zzz_archify@2"
}
],
"editor/title/context": [
{
"command": "archify.showPreview",
"when": "resourceFilename =~ /(\\.archify|\\.(architecture|workflow|sequence|dataflow|lifecycle)\\.json)$/",
"group": "1_open"
"group": "zzz_archify@1"
}
],
"file/newFile": [
@@ -212,7 +206,7 @@
},
{
"viewType": "archify.htmlViewer",
"displayName": "Archify Diagram Viewer",
"displayName": "Archify HTML Viewer",
"selector": [
{
"filenamePattern": "*.html"
+48
View File
@@ -0,0 +1,48 @@
import path from 'node:path';
import * as vscode from 'vscode';
import { Brand, brandValueAt, loadBrandCatalog } from './brands';
import { isArchifyFile } from './detect';
import { log } from './runtime';
const JSON_SOURCES = '**/*.{architecture,workflow,sequence,dataflow,lifecycle}.json';
export const BRAND_COMPLETION_SELECTOR: vscode.DocumentSelector = [
{ pattern: '**/*.archify' },
{ language: 'json', pattern: JSON_SOURCES },
{ language: 'jsonc', pattern: JSON_SOURCES },
];
/** Suggests Archify's built-in brand IDs for `brand` values. */
export class BrandCompletionProvider implements vscode.CompletionItemProvider {
private brands: Brand[] | undefined;
constructor(private readonly extensionPath: string) {}
private catalog(): Brand[] {
if (!this.brands) {
try {
this.brands = loadBrandCatalog(path.join(this.extensionPath, 'vendor', 'archify', 'brand-marks', 'catalog.json'));
} catch (error) {
log().error(`Could not read the Archify brand catalog: ${error instanceof Error ? error.message : String(error)}`);
this.brands = [];
}
}
return this.brands;
}
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] | undefined {
const linePrefix = document.lineAt(position.line).text.slice(0, position.character);
const value = brandValueAt(linePrefix, isArchifyFile(document.fileName) ? 'yaml' : 'json');
if (!value) return undefined;
const range = new vscode.Range(position.translate(0, -value.typed.length), position);
return this.catalog().map((brand) => {
const item = new vscode.CompletionItem({ label: brand.id, description: brand.title }, vscode.CompletionItemKind.Value);
item.detail = `${brand.title} · ${brand.category}`;
if (brand.aliases.length) item.documentation = `Also matches: ${brand.aliases.join(', ')}`;
// Typing an alias or the product name finds the brand too.
item.filterText = [brand.id, ...brand.aliases, brand.title].join(' ');
item.range = range;
return item;
});
}
}
+33
View File
@@ -0,0 +1,33 @@
import fs from 'node:fs';
export interface Brand {
id: string;
title: string;
category: string;
aliases: string[];
}
/** Archify's built-in brand marks, read from brand-marks/catalog.json. */
export function loadBrandCatalog(file: string): Brand[] {
const catalog = JSON.parse(fs.readFileSync(file, 'utf8')) as { marks?: Partial<Brand>[] };
return (catalog.marks ?? [])
.filter((mark): mark is Partial<Brand> & { id: string } => typeof mark.id === 'string')
.map((mark) => ({ id: mark.id, title: mark.title ?? mark.id, category: mark.category ?? '', aliases: mark.aliases ?? [] }));
}
export interface BrandValue {
/** What has been typed of the value so far. */
typed: string;
}
/**
* Whether the text before the cursor ends inside a `brand` value:
* `brand: post` or `{ id: db, brand: "post` in YAML, `"brand": "post` in JSON.
*/
export function brandValueAt(linePrefix: string, format: 'yaml' | 'json'): BrandValue | undefined {
const match =
format === 'yaml'
? /(?:^|[\s{,])brand:\s*["']?([\w.-]*)$/.exec(linePrefix)
: /"brand"\s*:\s*"([\w.-]*)$/.exec(linePrefix);
return match ? { typed: match[1] } : undefined;
}
+3 -2
View File
@@ -1,7 +1,7 @@
import { parse, ParseError } from 'jsonc-parser';
import * as vscode from 'vscode';
import { isArchifyFile, readDiagramSource } from './detect';
import { rangeForPointer, rangeForPointerYaml, yamlSyntaxProblems } from './jsonPointer';
import { diagnosticPointer, rangeForPointer, rangeForPointerYaml, yamlSyntaxProblems } from './jsonPointer';
import { ArchifyDiagnostic, isEvidenceError, validate } from './renderer';
import { evidenceMode, log, runtimeFor } from './runtime';
@@ -133,7 +133,8 @@ export class DiagnosticsController implements vscode.Disposable {
this.collection.set(
doc.uri,
result.diagnostics.map((item) => {
const offsets = source.format === 'yaml' ? rangeForPointerYaml(text, item.subject?.path) : rangeForPointer(text, item.subject?.path);
const pointer = diagnosticPointer(item);
const offsets = source.format === 'yaml' ? rangeForPointerYaml(text, pointer) : rangeForPointer(text, pointer);
const range = new vscode.Range(doc.positionAt(offsets.start), doc.positionAt(offsets.end));
const fix = item.supportedFixes?.[0];
const diagnostic = new vscode.Diagnostic(
+12 -5
View File
@@ -1,6 +1,7 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import * as vscode from 'vscode';
import { BRAND_COMPLETION_SELECTOR, BrandCompletionProvider } from './brandCompletion';
import { diagramBaseName, isArchifyFile, isArchifyHtml, isDiagramFileName, readDiagramSource } from './detect';
import { DiagnosticsController } from './diagnostics';
import { HTML_VIEW_TYPE, HtmlViewerProvider } from './htmlViewer';
@@ -18,7 +19,13 @@ function isHtmlUri(uri: vscode.Uri): boolean {
export function activate(context: vscode.ExtensionContext): void {
const previews = new PreviewManager(context);
const htmlViewer = new HtmlViewerProvider();
context.subscriptions.push(previews, htmlViewer, new DiagnosticsController(context), log());
context.subscriptions.push(
previews,
htmlViewer,
new DiagnosticsController(context),
log(),
vscode.languages.registerCompletionItemProvider(BRAND_COMPLETION_SELECTOR, new BrandCompletionProvider(context.extensionPath), ' ', '"'),
);
/** The file a command applies to: explicit argument, focused preview or viewer, then the text editor. */
function targetUri(arg?: unknown): vscode.Uri | undefined {
@@ -28,7 +35,7 @@ export function activate(context: vscode.ExtensionContext): void {
function sourceUri(arg?: unknown): vscode.Uri | undefined {
const uri = targetUri(arg);
if (!uri) vscode.window.showWarningMessage('Archify: open a diagram JSON source first.');
if (!uri) vscode.window.showWarningMessage('Archify: open a .archify diagram first.');
return uri;
}
@@ -112,7 +119,7 @@ export function activate(context: vscode.ExtensionContext): void {
vscode.commands.registerCommand('archify.newDiagram', async (arg?: unknown) => {
const template = await vscode.window.showQuickPick(
TEMPLATES.map((item) => ({ label: item.label, description: item.description, template: item })),
{ title: 'New Diagram Archify', placeHolder: 'Kind of diagram' },
{ title: 'Archify | New Diagram', placeHolder: 'Kind of diagram' },
);
if (!template) return;
const { file, defaultName } = template.template;
@@ -121,7 +128,7 @@ export function activate(context: vscode.ExtensionContext): void {
let target: vscode.Uri | undefined;
if (folder) {
const name = await vscode.window.showInputBox({
title: 'New Diagram Archify',
title: 'Archify | New Diagram',
prompt: `File name in ${vscode.workspace.asRelativePath(folder, true)}`,
value: defaultName,
validateInput: async (value) =>
@@ -133,7 +140,7 @@ export function activate(context: vscode.ExtensionContext): void {
} else {
// No workspace: let the user choose where to save.
target = await vscode.window.showSaveDialog({
title: 'New Diagram Archify',
title: 'Archify | New Diagram',
filters: { 'Archify Diagram': ['archify'] },
saveLabel: 'Create',
});
+8
View File
@@ -6,6 +6,14 @@ export interface OffsetRange {
end: number;
}
/**
* The JSON pointer an Archify diagnostic is about. Most diagnostics carry it
* in subject.path; brand diagnostics only start their message with it.
*/
export function diagnosticPointer(diagnostic: { message: string; subject?: { path?: string } }): string | undefined {
return diagnostic.subject?.path ?? /^(\/[^\s"]+)\s/.exec(diagnostic.message)?.[1];
}
/** Splits an RFC 6901 JSON pointer into path segments. */
export function pointerSegments(pointer: string): (string | number)[] {
if (!pointer || pointer === '/') return [];
+2 -2
View File
@@ -1,6 +1,6 @@
import path from 'node:path';
import * as vscode from 'vscode';
import { readDiagramSource } from './detect';
import { isArchifyFile, readDiagramSource } from './detect';
import { renderDiagram } from './renderer';
import { evidenceMode, log, readText, runtimeFor, viewerTheme } from './runtime';
import { handleWebviewMessage, WebviewMessage } from './webviewMessages';
@@ -100,7 +100,7 @@ class DiagramPreview implements vscode.Disposable {
if (!source) {
this.showMessage(
'Nothing to show yet',
'Set "diagram_type" to architecture, workflow, sequence, dataflow or lifecycle, and make sure the file is valid JSON.',
`Set "diagram_type" to architecture, workflow, sequence, dataflow or lifecycle, and make sure the file is valid ${isArchifyFile(this.uri.path) ? 'YAML' : 'JSON'}.`,
);
return;
}
+44
View File
@@ -0,0 +1,44 @@
import assert from 'node:assert/strict';
import path from 'node:path';
import { test } from 'node:test';
import { brandValueAt, loadBrandCatalog } from '../src/brands';
import { diagnosticPointer } from '../src/jsonPointer';
const root = path.resolve(__dirname, '..', '..');
const catalogFile = path.join(root, 'vendor', 'archify', 'brand-marks', 'catalog.json');
test('loads the bundled brand catalog', () => {
const brands = loadBrandCatalog(catalogFile);
assert.ok(brands.length >= 100, `${brands.length} brands`);
const postgres = brands.find((brand) => brand.id === 'postgresql');
assert.ok(postgres);
assert.ok(postgres.aliases.includes('postgres'));
assert.ok(brands.every((brand) => brand.id && brand.title));
});
test('finds a brand value being typed in YAML', () => {
assert.deepEqual(brandValueAt(' brand: ', 'yaml'), { typed: '' });
assert.deepEqual(brandValueAt(' brand: post', 'yaml'), { typed: 'post' });
assert.deepEqual(brandValueAt(' - brand: apache-ka', 'yaml'), { typed: 'apache-ka' });
assert.deepEqual(brandValueAt(' brand: "red', 'yaml'), { typed: 'red' });
assert.deepEqual(brandValueAt(' - { id: db, brand: post', 'yaml'), { typed: 'post' });
assert.equal(brandValueAt(' label: post', 'yaml'), undefined);
assert.equal(brandValueAt(' subbrand: post', 'yaml'), undefined);
assert.equal(brandValueAt(' brand: postgresql ', 'yaml'), undefined, 'value already finished');
});
test('finds a brand value being typed in JSON', () => {
assert.deepEqual(brandValueAt(' "brand": "', 'json'), { typed: '' });
assert.deepEqual(brandValueAt('{ "id": "db", "brand": "post', 'json'), { typed: 'post' });
assert.equal(brandValueAt(' "brand": ', 'json'), undefined, 'not inside the string yet');
assert.equal(brandValueAt(' "label": "post', 'json'), undefined);
});
test('diagnostic pointer comes from subject.path, else from a leading pointer in the message', () => {
assert.equal(diagnosticPointer({ message: 'x', subject: { path: '/components/0' } }), '/components/0');
assert.equal(
diagnosticPointer({ message: '/components/0/brand "aws" is not a built-in brand; closest IDs: airtable' }),
'/components/0/brand',
);
assert.equal(diagnosticPointer({ message: 'Label "x" is wider than node' }), undefined);
});
+50 -2
View File
@@ -97,11 +97,11 @@ export async function run(): Promise<void> {
assert.ok(diagnostics[0].range.start.line >= 2, `reported on line ${diagnostics[0].range.start.line + 1}`);
});
await check('New Diagram Archify creates a .archify file from a template and opens the diagram', async () => {
await check('Archify | New Diagram creates a .archify file from a template and opens the diagram', async () => {
// Menus show the bare title, so it must name Archify by itself.
const commands = vscode.extensions.getExtension(EXTENSION_ID)?.packageJSON.contributes.commands as { command: string; title: string; category?: string }[];
const newDiagram = commands.find((item) => item.command === 'archify.newDiagram');
assert.equal(newDiagram?.title, 'New Diagram Archify…');
assert.equal(newDiagram?.title, 'Archify | New Diagram…');
assert.equal(newDiagram?.category, undefined);
const folder = workspaceFile('.');
// Stub the template pick and the name prompt; check the name validation on the way.
@@ -131,6 +131,54 @@ export async function run(): Promise<void> {
);
});
await check('suggests brand IDs after brand: in .archify files', async () => {
const target = workspaceFile('brands.archify');
const text = [
'schema_version: 1',
'diagram_type: architecture',
'meta:',
' title: Brands',
'components:',
' - id: db',
' type: database',
' label: DB',
' brand: ',
' - id: bus',
' type: messagebus',
' label: Bus',
' brand: kafka',
'',
].join('\n');
await vscode.workspace.fs.writeFile(target, new TextEncoder().encode(text));
const doc = await vscode.workspace.openTextDocument(target);
const labels = async (line: number) => {
const list = await vscode.commands.executeCommand<vscode.CompletionList>(
'vscode.executeCompletionItemProvider',
doc.uri,
new vscode.Position(line, doc.lineAt(line).text.length),
);
return list.items.map((item) => (typeof item.label === 'string' ? item.label : item.label.label));
};
const empty = await labels(8);
assert.ok(empty.includes('postgresql') && empty.includes('redis'), 'brand IDs offered after "brand: "');
const kafka = await labels(12);
assert.ok(kafka.includes('apache-kafka'), 'the alias "kafka" finds apache-kafka');
assert.equal((await labels(7)).includes('postgresql'), false, 'nothing offered for other fields');
});
await check('an unknown brand is reported on its own line', async () => {
const target = workspaceFile('unknown-brand.archify');
const text = new TextDecoder().decode(await vscode.workspace.fs.readFile(workspaceFile('web-platform.archify')))
.replace(/^( {2}- id: [^\n]+\n)/m, '$1 brand: not-a-brand\n');
await vscode.workspace.fs.writeFile(target, new TextEncoder().encode(text));
const doc = await vscode.workspace.openTextDocument(target);
await vscode.window.showTextDocument(doc);
const diagnostic = await waitFor('brand/unknown diagnostic', () =>
vscode.languages.getDiagnostics(doc.uri).find((d) => d.source === 'archify' && d.code === 'brand/unknown'),
);
assert.match(doc.lineAt(diagnostic.range.start.line).text, /brand: not-a-brand/);
});
await check('opens a live preview beside the source', async () => {
await vscode.commands.executeCommand('archify.showPreviewToSide', workspaceFile('production-deployment.architecture.json'));
const tabs = await waitFor('preview tab', () => {