Compare commits

..
13 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
root-at-skic e9da971523 Merge pull request 'Release 0.4.1: title the command New Diagram Archify…' (#14) from fix/new-diagram-title into main 2026-10-04 11:50:00 -07:00
root-at-skic 8d8fc6f65e Title the new-diagram command New Diagram Archify… (0.4.1)
The Explorer folder menu shows a command's title without its category,
so it read just "New Diagram…". The title now names Archify itself and
the category is dropped, so the Command Palette shows the same text.
Integration test asserts the title.
2026-10-04 21:49:54 +03:00
root-at-skic 6f6ffae96f Merge pull request 'Release 0.4.0' (#13) from release/0.4.0 into main 2026-10-04 11:35:06 -07:00
12 changed files with 269 additions and 47 deletions

No files matched your search

+20
View File
@@ -1,5 +1,25 @@
# 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…*.
## 0.4.0 — 2026-10-04
- **New Diagram…** (Explorer folder menu, *File → New File…*, Command Palette): pick Architecture, Workflow, Sequence, Data Flow, Lifecycle or Everyday Plan, enter a name, and a small valid `.archify` (YAML) file is created from a template and opened as a diagram. Title and output file follow the name.
+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 Archify 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.
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**: *Archify: New Diagram…* 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**: *Archify: New Diagram…*, *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.0",
"version": "0.5.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "archify-vscode-ext",
"version": "0.4.0",
"version": "0.5.2",
"license": "MIT",
"dependencies": {
"yaml": "^2.9.1"
+17 -24
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.0",
"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,50 +41,42 @@
"commands": [
{
"command": "archify.newDiagram",
"title": "New Diagram\u2026",
"category": "Archify",
"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)"
}
],
@@ -154,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": [
@@ -213,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 Archify Diagram', 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 Archify Diagram',
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 Archify Diagram',
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);
});
+54 -1
View File
@@ -97,7 +97,12 @@ 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 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, '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.
const window = vscode.window as unknown as Record<string, unknown>;
@@ -126,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', () => {