Add Archify | New Model… with three C4 examples and Open Model in the Explorer menu

- templates/models: C4 - Standard Web App, C4 - E-Shop, C4 - Event-Based
  Integration; each a complete model (main.archify, registry, context,
  containers, components) that validates
- archify.newModel: pick an example and a folder name, copy it, name the
  model after the folder, open the model page
- Explorer menu: New Model… on folders, Open Model on .archify and
  .archify-registry files; File → New File… lists New Model…
- unit test validates every example end to end; integration test runs the
  command; README, CHANGELOG, docs/model.md
This commit is contained in:
2026-10-19 12:12:49 +03:00
parent 7a2b661afe
commit 57914d435c
29 changed files with 874 additions and 8 deletions
+52 -1
View File
@@ -1,7 +1,21 @@
import * as vscode from 'vscode';
import { MODEL_EXAMPLES, mainFromExample } from '../language/examples';
import { diagramName, invalidNameReason } from '../language/templates';
import { MAIN_FILE } from '../model/format';
import { readText } from '../util/files';
import { DIAGRAM_EDITOR_VIEW_TYPE } from '../views/diagramView';
import { CommandContext, targetUri } from './context';
import { CommandContext, exists, targetUri } from './context';
/** Folder the new model folder goes into: the clicked folder (or a file's folder), else a workspace folder. */
async function parentFolder(arg?: unknown): Promise<vscode.Uri | undefined> {
if (arg instanceof vscode.Uri) {
const stat = await vscode.workspace.fs.stat(arg);
return stat.type & vscode.FileType.Directory ? arg : vscode.Uri.joinPath(arg, '..');
}
const folders = vscode.workspace.workspaceFolders ?? [];
if (folders.length <= 1) return folders[0]?.uri;
return (await vscode.window.showWorkspaceFolderPick({ placeHolder: 'Folder for the new model' }))?.uri;
}
export function modelCommands(ctx: CommandContext): vscode.Disposable[] {
return [
@@ -15,5 +29,42 @@ 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);
}),
// Creates a new model folder from one of the shipped examples and opens its model page.
vscode.commands.registerCommand('archify.newModel', async (arg?: unknown) => {
const picked = await vscode.window.showQuickPick(
MODEL_EXAMPLES.map((example) => ({ label: example.label, description: example.description, example })),
{ title: 'Archify | New Model', placeHolder: 'Example to start from' },
);
if (!picked) return;
const { example } = picked;
let parent = await parentFolder(arg);
if (!parent) {
const chosen = await vscode.window.showOpenDialog({ title: 'Archify | New Model', canSelectFiles: false, canSelectFolders: true, openLabel: 'Create model here' });
parent = chosen?.[0];
if (!parent) return;
}
const name = await vscode.window.showInputBox({
title: 'Archify | New Model',
prompt: `Folder name in ${vscode.workspace.asRelativePath(parent, true)}`,
value: example.folder,
validateInput: async (value) =>
invalidNameReason(value) ?? ((await exists(vscode.Uri.joinPath(parent, diagramName(value)))) ? `${diagramName(value)} already exists.` : undefined),
});
if (name === undefined) return;
const folderName = diagramName(name);
const target = vscode.Uri.joinPath(parent, folderName);
if (await exists(target)) {
vscode.window.showWarningMessage(`Archify: ${vscode.workspace.asRelativePath(target, true)} already exists.`);
return;
}
const source = vscode.Uri.joinPath(ctx.extension.extensionUri, 'templates', 'models', example.folder);
await vscode.workspace.fs.copy(source, target, { overwrite: false });
const main = vscode.Uri.joinPath(target, MAIN_FILE);
await vscode.workspace.fs.writeFile(main, Buffer.from(mainFromExample(await readText(main), folderName, example), 'utf8'));
await vscode.commands.executeCommand('vscode.openWith', main, DIAGRAM_EDITOR_VIEW_TYPE);
}),
];
}