0.8.1: New Registry command, creator order #26

Merged
root-at-skic merged 1 commits from feat/new-registry into main 2026-10-20 01:23:03 -07:00
6 changed files with 89 additions and 26 deletions
Showing only changes of commit c66e819abe - Show all commits
+5
View File
@@ -1,5 +1,10 @@
# Changelog # Changelog
## 0.8.1 — 2026-10-20
- **Archify | New Registry…** on a folder (also *File → New File…* and the Command Palette) creates an `.archify-registry` file from the template and opens it.
- The Archify entries in the Explorer menu and *File → New File…* come in the order New Model, New Registry, New Diagram, New Decision.
## 0.8.0 — 2026-10-19 ## 0.8.0 — 2026-10-19
- **Architecture decisions.** A model can hold decisions: one `*.archify-decision` file each, anywhere under the model root, with an id (unique in the model), title, status, date, the registry `elements` and `diagrams` it is about, `supersedes`, and prose for context, decision and consequences. - **Architecture decisions.** A model can hold decisions: one `*.archify-decision` file each, anywhere under the model root, with an id (unique in the model), title, status, date, the registry `elements` and `diagrams` it is about, `supersedes`, and prose for context, decision and consequences.
+1 -1
View File
@@ -73,7 +73,7 @@ The Archify CLI and the AI agents that use it write diagrams as JSON, named `nam
- **Schema help**: completion and hover from Archify's own schemas, through the YAML extension for `.archify` files. - **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. - **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. - **Export**: the diagram's own Export menu (PNG, SVG, share card, …) saves through a VS Code save dialog.
- **Commands**: *Archify | New Diagram…*, *Archify | New Model…*, *Archify | New Decision…*, *Archify | Open Model*, *Archify | Render to HTML File…*, *Archify | Open in Browser*, *Archify | Refresh Preview*, *Archify | Show Source*. - **Commands**: *Archify | New Model…*, *Archify | New Registry…*, *Archify | New Diagram…*, *Archify | New Decision…*, *Archify | Open Model*, *Archify | Render to HTML File…*, *Archify | Open in Browser*, *Archify | Refresh Preview*, *Archify | Show Source*.
## 3D view ## 3D view
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.8.0", "version": "0.8.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"version": "0.8.0", "version": "0.8.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"three": "^0.186.1", "three": "^0.186.1",
+36 -22
View File
@@ -2,7 +2,7 @@
"name": "archify-vscode-ext", "name": "archify-vscode-ext",
"displayName": "Archify Diagram Viewer", "displayName": "Archify Diagram Viewer",
"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, …) and opens rendered Archify HTML.", "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, …) and opens rendered Archify HTML.",
"version": "0.8.0", "version": "0.8.1",
"publisher": "root-at-skic", "publisher": "root-at-skic",
"license": "MIT", "license": "MIT",
"sponsor": { "sponsor": {
@@ -49,16 +49,21 @@
], ],
"contributes": { "contributes": {
"commands": [ "commands": [
{
"command": "archify.newDiagram",
"title": "Archify | New Diagram…",
"icon": "$(new-file)"
},
{ {
"command": "archify.newModel", "command": "archify.newModel",
"title": "Archify | New Model…", "title": "Archify | New Model…",
"icon": "$(new-folder)" "icon": "$(new-folder)"
}, },
{
"command": "archify.newRegistry",
"title": "Archify | New Registry…",
"icon": "$(symbol-field)"
},
{
"command": "archify.newDiagram",
"title": "Archify | New Diagram…",
"icon": "$(new-file)"
},
{ {
"command": "archify.newDecision", "command": "archify.newDecision",
"title": "Archify | New Decision…", "title": "Archify | New Decision…",
@@ -179,39 +184,44 @@
], ],
"explorer/context": [ "explorer/context": [
{ {
"command": "archify.newDiagram", "command": "archify.newModel",
"when": "explorerResourceIsFolder", "when": "explorerResourceIsFolder",
"group": "zzz_archify@1" "group": "zzz_archify@1"
}, },
{ {
"command": "archify.newModel", "command": "archify.newRegistry",
"when": "explorerResourceIsFolder", "when": "explorerResourceIsFolder",
"group": "zzz_archify@2" "group": "zzz_archify@2"
}, },
{
"command": "archify.newDiagram",
"when": "explorerResourceIsFolder",
"group": "zzz_archify@3"
},
{ {
"command": "archify.newDecision", "command": "archify.newDecision",
"when": "explorerResourceIsFolder", "when": "explorerResourceIsFolder",
"group": "zzz_archify@2" "group": "zzz_archify@4"
}, },
{ {
"command": "archify.showPreview", "command": "archify.showPreview",
"when": "resourceFilename =~ /(\\.archify|\\.archify-registry|\\.archify-decision|\\.(architecture|workflow|sequence|dataflow|lifecycle)\\.json)$/", "when": "resourceFilename =~ /(\\.archify|\\.archify-registry|\\.archify-decision|\\.(architecture|workflow|sequence|dataflow|lifecycle)\\.json)$/",
"group": "zzz_archify@3" "group": "zzz_archify@5"
},
{
"command": "archify.renderToHtml",
"when": "resourceFilename =~ /(\\.archify|\\.(architecture|workflow|sequence|dataflow|lifecycle)\\.json)$/",
"group": "zzz_archify@4"
}, },
{ {
"command": "archify.openHtmlViewer", "command": "archify.openHtmlViewer",
"when": "resourceExtname == .html", "when": "resourceExtname == .html",
"group": "zzz_archify@3" "group": "zzz_archify@6"
},
{
"command": "archify.renderToHtml",
"when": "resourceFilename =~ /(\\.archify|\\.(architecture|workflow|sequence|dataflow|lifecycle)\\.json)$/",
"group": "zzz_archify@7"
}, },
{ {
"command": "archify.openModel", "command": "archify.openModel",
"when": "resourceFilename =~ /\\.archify(-registry|-decision)?$/", "when": "resourceFilename =~ /\\.archify(-registry|-decision)?$/",
"group": "zzz_archify@5" "group": "zzz_archify@8"
} }
], ],
"editor/title/context": [ "editor/title/context": [
@@ -223,16 +233,20 @@
], ],
"file/newFile": [ "file/newFile": [
{ {
"command": "archify.newDiagram", "command": "archify.newModel",
"group": "file" "group": "file@1"
}, },
{ {
"command": "archify.newModel", "command": "archify.newRegistry",
"group": "file" "group": "file@2"
},
{
"command": "archify.newDiagram",
"group": "file@3"
}, },
{ {
"command": "archify.newDecision", "command": "archify.newDecision",
"group": "file" "group": "file@4"
} }
] ]
}, },
+21 -1
View File
@@ -2,7 +2,8 @@ import * as vscode from 'vscode';
import { MODEL_EXAMPLES, mainFromExample } from '../language/examples'; import { MODEL_EXAMPLES, mainFromExample } from '../language/examples';
import { diagramName, invalidNameReason } from '../language/templates'; import { diagramName, invalidNameReason } from '../language/templates';
import { DECISION_EXT, slugify } from '../model/decisions'; import { DECISION_EXT, slugify } from '../model/decisions';
import { MAIN_FILE } from '../model/format'; import { MAIN_FILE, REGISTRY_EXT } from '../model/format';
import { REGISTRY_VIEW_TYPE } from '../views/registryView';
import { DECISION_VIEW_TYPE } from '../views/decisionView'; import { DECISION_VIEW_TYPE } from '../views/decisionView';
import { readText } from '../util/files'; import { readText } from '../util/files';
import { DIAGRAM_EDITOR_VIEW_TYPE } from '../views/diagramView'; import { DIAGRAM_EDITOR_VIEW_TYPE } from '../views/diagramView';
@@ -32,6 +33,25 @@ export function modelCommands(ctx: CommandContext): vscode.Disposable[] {
await vscode.commands.executeCommand('vscode.openWith', vscode.Uri.joinPath(root, MAIN_FILE), DIAGRAM_EDITOR_VIEW_TYPE, vscode.ViewColumn.Active); await vscode.commands.executeCommand('vscode.openWith', vscode.Uri.joinPath(root, MAIN_FILE), DIAGRAM_EDITOR_VIEW_TYPE, vscode.ViewColumn.Active);
}), }),
// Creates a registry file in the clicked folder and opens it.
vscode.commands.registerCommand('archify.newRegistry', async (arg?: unknown) => {
const folder = await parentFolder(arg);
if (!folder) return;
const root = await ctx.models.rootOf(vscode.Uri.joinPath(folder, 'x'));
const name = await vscode.window.showInputBox({
title: 'Archify | New Registry',
prompt: `File name in ${vscode.workspace.asRelativePath(folder, true)}${root ? '' : ' (not inside a model: add a main.archify above it so diagrams can refer to its elements)'}`,
value: 'elements',
validateInput: async (value) =>
invalidNameReason(value) ?? ((await exists(vscode.Uri.joinPath(folder, `${diagramName(value).replace(/\.archify-registry$/i, '')}${REGISTRY_EXT}`))) ? `${diagramName(value)}${REGISTRY_EXT} already exists.` : undefined),
});
if (name === undefined) return;
const target = vscode.Uri.joinPath(folder, `${diagramName(name).replace(/\.archify-registry$/i, '')}${REGISTRY_EXT}`);
const template = await readText(vscode.Uri.joinPath(ctx.extension.extensionUri, 'templates', 'registry.archify-registry'));
await vscode.workspace.fs.writeFile(target, Buffer.from(template, 'utf8'));
await vscode.commands.executeCommand('vscode.openWith', target, REGISTRY_VIEW_TYPE);
}),
// Creates a decision file in the clicked folder of a model and opens it. // Creates a decision file in the clicked folder of a model and opens it.
vscode.commands.registerCommand('archify.newDecision', async (arg?: unknown) => { vscode.commands.registerCommand('archify.newDecision', async (arg?: unknown) => {
const folder = await parentFolder(arg); const folder = await parentFolder(arg);
+24
View File
@@ -349,6 +349,30 @@ export async function run(): Promise<void> {
await vscode.workspace.fs.delete(bad); await vscode.workspace.fs.delete(bad);
}); });
await check('Archify | New Registry… creates a registry file and opens the registry view; creators are ordered', async () => {
const commands = vscode.extensions.getExtension(EXTENSION_ID)?.packageJSON.contributes as { commands: { command: string }[]; menus: Record<string, { command: string; group: string }[]> };
const creators = commands.menus['explorer/context'].filter((m) => /^archify\.new/.test(m.command));
assert.deepEqual(creators.map((m) => m.command), ['archify.newModel', 'archify.newRegistry', 'archify.newDiagram', 'archify.newDecision']);
assert.deepEqual(creators.map((m) => m.group), ['zzz_archify@1', 'zzz_archify@2', 'zzz_archify@3', 'zzz_archify@4']);
const folder = workspaceFile('model/registry');
const window = vscode.window as unknown as Record<string, unknown>;
const originalInput = window.showInputBox;
window.showInputBox = async () => 'partners';
try {
await vscode.commands.executeCommand('archify.newRegistry', folder);
} finally {
window.showInputBox = originalInput;
}
const file = vscode.Uri.joinPath(folder, 'partners.archify-registry');
const text = new TextDecoder().decode(await vscode.workspace.fs.readFile(file));
assert.match(text, /^elements:/m);
const tab = await waitFor('registry view tab', () =>
vscode.window.tabGroups.all.flatMap((g) => g.tabs).find((t) => t.isActive && t.input instanceof vscode.TabInputCustom && t.input.viewType === 'archify.registry' && t.input.uri.toString() === file.toString()),
);
assert.ok(tab);
await vscode.workspace.fs.delete(file);
});
await check('Archify | New Diagram… → Model creates main.archify from the folder name', async () => { await check('Archify | New Diagram… → Model creates main.archify from the folder name', async () => {
const folder = workspaceFile('new-shop'); const folder = workspaceFile('new-shop');
await vscode.workspace.fs.createDirectory(folder); await vscode.workspace.fs.createDirectory(folder);