Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95c6e60199 | ||
|
|
b54304fef8 | ||
|
|
8407c9a956 |
No files matched your search
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.10.6 — 2026-11-08
|
||||||
|
|
||||||
|
- **Sync sign-in comes back by itself.** The browser now returns the sign-in to a one-off port on this machine (`127.0.0.1`) instead of a `vscode://` link. Browsers open such links only after a click, so when you were already signed in to the identity provider the editor kept waiting. Remote and web editors still use the link.
|
||||||
|
|
||||||
|
## 0.10.5 — 2026-11-08
|
||||||
|
|
||||||
|
- **Sync button fixed.** It is now an icon only (a cloud; green with a broadcast icon once the model is shared), the same size on every page and theme. On a diagram it sits in the toolbar after **Export** instead of over it; in narrow windows and high contrast themes it no longer shrinks or loses its colour.
|
||||||
|
|
||||||
## 0.10.4 — 2026-11-07
|
## 0.10.4 — 2026-11-07
|
||||||
|
|
||||||
- **A real Sync button.** Every Archify page (diagram, model, graph, registry, decision, and in Source) shows a **Sync** button top right. It opens the Sync menu: sign in, share the model, join one, set the server address. On a model that is shared it turns green and reads **Shared**.
|
- **A real Sync button.** Every Archify page (diagram, model, graph, registry, decision, and in Source) shows a **Sync** button top right. It opens the Sync menu: sign in, share the model, join one, set the server address. On a model that is shared it turns green and reads **Shared**.
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ Optional. Without a sync server the extension works exactly as described above a
|
|||||||
|
|
||||||
[Archify Sync](https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync) is a small server you run yourself (one container and PostgreSQL; a Helm chart is included). With it, a model is edited by several people at once, like a shared document, and its files stay ordinary files on each person's disk, so Git, agents and every page of this extension keep working.
|
[Archify Sync](https://gitea.lego-cloud.eu/vscode-extensions/archify-vscode-ext-sync) is a small server you run yourself (one container and PostgreSQL; a Helm chart is included). With it, a model is edited by several people at once, like a shared document, and its files stay ordinary files on each person's disk, so Git, agents and every page of this extension keep working.
|
||||||
|
|
||||||
- **Where.** Every Archify page (diagram, model, graph, registry, decision, also in Source) has a **Sync** button top right. It opens the Sync menu: sign in, share the model, join one, the server address, settings. On a shared model it turns green and reads **Shared**. The same menu is *Archify | Sync…* in the Command Palette.
|
- **Where.** Every Archify page (diagram, model, graph, registry, decision, also in Source) has a Sync button (a cloud) top right; on a diagram it sits after **Export**. It opens the Sync menu: sign in, share the model, join one, the server address, settings. On a shared model it turns green with a broadcast icon. The same menu is *Archify | Sync…* in the Command Palette.
|
||||||
- **Server address.** Set `archify.sync.server` once (Settings › Archify › Sync, or *Archify | Set Sync Server Address…*), for example `https://archify.example.com`. Put it in the workspace settings and everyone in the project uses the same server. Left empty, the address is asked for once and remembered.
|
- **Server address.** Set `archify.sync.server` once (Settings › Archify › Sync, or *Archify | Set Sync Server Address…*), for example `https://archify.example.com`. Put it in the workspace settings and everyone in the project uses the same server. Left empty, the address is asked for once and remembered.
|
||||||
- **Sign in.** *Archify | Sign in to Sync Server…* uses that address (or asks for it) and signs in through the browser; the session is kept in VS Code's secret storage and shows under **Accounts** as *Archify Sync*. A new server is set up first on its `/setup` page with the code from its log.
|
- **Sign in.** *Archify | Sign in to Sync Server…* uses that address (or asks for it) and signs in through the browser; the session is kept in VS Code's secret storage and shows under **Accounts** as *Archify Sync*. A new server is set up first on its `/setup` page with the code from its log.
|
||||||
- **Share.** *Archify | Share Model…* (also on `main.archify` in the Explorer) puts the model on the server and writes a small `.archify-sync` file next to `main.archify`: the server and the model's id, no secrets. Commit it, and everyone who clones the repository and is a member opens the shared model.
|
- **Share.** *Archify | Share Model…* (also on `main.archify` in the Explorer) puts the model on the server and writes a small `.archify-sync` file next to `main.archify`: the server and the model's id, no secrets. Commit it, and everyone who clones the repository and is a member opens the shared model.
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "archify-vscode-ext",
|
"name": "archify-vscode-ext",
|
||||||
"version": "0.10.4",
|
"version": "0.10.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "archify-vscode-ext",
|
"name": "archify-vscode-ext",
|
||||||
"version": "0.10.4",
|
"version": "0.10.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.20.4",
|
"@codemirror/autocomplete": "^6.20.4",
|
||||||
|
|||||||
+1
-1
@@ -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.10.4",
|
"version": "0.10.6",
|
||||||
"publisher": "root-at-skic",
|
"publisher": "root-at-skic",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"sponsor": {
|
"sponsor": {
|
||||||
|
|||||||
+36
-3
@@ -1,4 +1,5 @@
|
|||||||
import * as crypto from 'node:crypto';
|
import * as crypto from 'node:crypto';
|
||||||
|
import * as http from 'node:http';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { SyncApi, SyncApiError } from './api';
|
import { SyncApi, SyncApiError } from './api';
|
||||||
import { Account, TokenResponse } from './protocol';
|
import { Account, TokenResponse } from './protocol';
|
||||||
@@ -100,8 +101,13 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
|
|||||||
const verifier = crypto.randomBytes(32).toString('base64url');
|
const verifier = crypto.randomBytes(32).toString('base64url');
|
||||||
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
|
const challenge = crypto.createHash('sha256').update(verifier).digest('base64url');
|
||||||
const state = crypto.randomBytes(16).toString('base64url');
|
const state = crypto.randomBytes(16).toString('base64url');
|
||||||
const callback = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://${this.context.extension.id}/auth`));
|
// On the desktop the browser comes back to a port on this machine: no
|
||||||
const redirectUri = callback.toString(true);
|
// click needed, and it reaches this window. Remote and web editors use
|
||||||
|
// the extension's URI handler (vscode://…/auth) instead.
|
||||||
|
const loopback = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Desktop ? await this.listen().catch(() => undefined) : undefined;
|
||||||
|
const redirectUri = loopback
|
||||||
|
? `http://127.0.0.1:${loopback.port}/auth`
|
||||||
|
: (await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://${this.context.extension.id}/auth`))).toString(true);
|
||||||
const query = new URLSearchParams({
|
const query = new URLSearchParams({
|
||||||
response_type: 'code',
|
response_type: 'code',
|
||||||
redirect_uri: redirectUri,
|
redirect_uri: redirectUri,
|
||||||
@@ -119,6 +125,7 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
|
|||||||
const done = () => {
|
const done = () => {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
this.pending.delete(state);
|
this.pending.delete(state);
|
||||||
|
loopback?.close();
|
||||||
};
|
};
|
||||||
this.pending.set(state, {
|
this.pending.set(state, {
|
||||||
resolve: (c) => (done(), resolve(c)),
|
resolve: (c) => (done(), resolve(c)),
|
||||||
@@ -137,9 +144,35 @@ export class SyncAuth implements vscode.AuthenticationProvider, vscode.Disposabl
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A one-off HTTP listener on 127.0.0.1 for the browser's way back; closed when the sign-in ends. */
|
||||||
|
private listen(): Promise<{ port: number; close: () => void }> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
||||||
|
const known = url.pathname === '/auth' && this.pending.has(url.searchParams.get('state') ?? '');
|
||||||
|
res.writeHead(known ? 200 : 404, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
|
||||||
|
res.end(
|
||||||
|
known
|
||||||
|
? '<!doctype html><meta charset="utf-8"><title>Archify Sync</title><body style="font:15px system-ui;margin:48px auto;max-width:420px"><h2>Signed in</h2><p>You can close this tab and go back to the editor.</p></body>'
|
||||||
|
: 'Not found',
|
||||||
|
);
|
||||||
|
if (known) this.completeSignIn(url.searchParams);
|
||||||
|
});
|
||||||
|
server.once('error', reject);
|
||||||
|
server.listen(0, '127.0.0.1', () => {
|
||||||
|
const address = server.address();
|
||||||
|
if (!address || typeof address === 'string') return reject(new Error('no port'));
|
||||||
|
resolve({ port: address.port, close: () => server.close() });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** The browser came back: `…/auth?code=&state=`. */
|
/** The browser came back: `…/auth?code=&state=`. */
|
||||||
handleAuthUri(uri: vscode.Uri): void {
|
handleAuthUri(uri: vscode.Uri): void {
|
||||||
const query = new URLSearchParams(uri.query);
|
this.completeSignIn(new URLSearchParams(uri.query));
|
||||||
|
}
|
||||||
|
|
||||||
|
private completeSignIn(query: URLSearchParams): void {
|
||||||
const state = query.get('state') ?? '';
|
const state = query.get('state') ?? '';
|
||||||
const code = query.get('code');
|
const code = query.get('code');
|
||||||
const pending = this.pending.get(state);
|
const pending = this.pending.get(state);
|
||||||
|
|||||||
+29
-13
@@ -888,7 +888,7 @@ function bridgeScript(options: PageOptions): string {
|
|||||||
onReady(describeElements);
|
onReady(describeElements);
|
||||||
onReady(renderModeSwitch);
|
onReady(renderModeSwitch);
|
||||||
|
|
||||||
// Archify Sync: one labelled button, top right, on every page of an Archify
|
// Archify Sync: one icon button, top right, on every page of an Archify
|
||||||
// file (diagram, model, graph, registry, decision). It opens the Sync menu.
|
// file (diagram, model, graph, registry, decision). It opens the Sync menu.
|
||||||
function renderSyncButton() {
|
function renderSyncButton() {
|
||||||
if (!(config.source || config.model || config.registry || config.decision || config.graph)) return;
|
if (!(config.source || config.model || config.registry || config.decision || config.graph)) return;
|
||||||
@@ -901,35 +901,51 @@ function bridgeScript(options: PageOptions): string {
|
|||||||
button.type = 'button';
|
button.type = 'button';
|
||||||
button.title = 'Archify Sync: sign in, share this model with others, or join one';
|
button.title = 'Archify Sync: sign in, share this model with others, or join one';
|
||||||
button.addEventListener('click', function (event) { event.stopPropagation(); post({ type: 'syncMenu' }); });
|
button.addEventListener('click', function (event) { event.stopPropagation(); post({ type: 'syncMenu' }); });
|
||||||
|
// A diagram page has Archify's toolbar at the top right: the button goes
|
||||||
|
// last in it, after Export. Other pages have nothing there; it floats.
|
||||||
|
var toolbar = document.querySelector('body > .toolbar');
|
||||||
|
if (toolbar) {
|
||||||
|
button.classList.add('in-toolbar');
|
||||||
|
toolbar.appendChild(button);
|
||||||
|
} else {
|
||||||
document.body.appendChild(button);
|
document.body.appendChild(button);
|
||||||
|
}
|
||||||
showSyncState(false);
|
showSyncState(false);
|
||||||
post({ type: 'syncReady' });
|
post({ type: 'syncReady' });
|
||||||
}
|
}
|
||||||
// Not shared: a cloud, "Sync". Shared (the model has an .archify-sync file): broadcast waves, "Shared".
|
// Not shared: a cloud. Shared (the model has an .archify-sync file): broadcast waves on green.
|
||||||
var SYNC_ICONS = {
|
var SYNC_ICONS = {
|
||||||
off: '<svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><path fill="currentColor" d="M12.5 13H4.25a3.25 3.25 0 0 1-.6-6.44 4.5 4.5 0 0 1 8.7-.81A3.6 3.6 0 0 1 12.5 13Zm-8.25-1h8.25a2.6 2.6 0 0 0 0-5.2h-.5l-.12-.39a3.5 3.5 0 0 0-6.82.66l-.04.4-.4.05A2.25 2.25 0 0 0 4.25 12Z"/></svg>',
|
off: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M12.5 13H4.25a3.25 3.25 0 0 1-.6-6.44 4.5 4.5 0 0 1 8.7-.81A3.6 3.6 0 0 1 12.5 13Zm-8.25-1h8.25a2.6 2.6 0 0 0 0-5.2h-.5l-.12-.39a3.5 3.5 0 0 0-6.82.66l-.04.4-.4.05A2.25 2.25 0 0 0 4.25 12Z"/></svg>',
|
||||||
on: '<svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true"><circle cx="8" cy="8" r="1.75" fill="currentColor"/><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M5.2 5.2a4 4 0 0 0 0 5.6M10.8 5.2a4 4 0 0 1 0 5.6M3.1 3.1a7 7 0 0 0 0 9.8M12.9 3.1a7 7 0 0 1 0 9.8"/></svg>',
|
on: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="1.75" fill="currentColor"/><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M5.2 5.2a4 4 0 0 0 0 5.6M10.8 5.2a4 4 0 0 1 0 5.6M3.1 3.1a7 7 0 0 0 0 9.8M12.9 3.1a7 7 0 0 1 0 9.8"/></svg>',
|
||||||
};
|
};
|
||||||
function showSyncState(shared) {
|
function showSyncState(shared) {
|
||||||
var button = document.getElementById('archify-sync-button');
|
var button = document.getElementById('archify-sync-button');
|
||||||
if (!button) return;
|
if (!button) return;
|
||||||
button.classList.toggle('is-shared', !!shared);
|
button.classList.toggle('is-shared', !!shared);
|
||||||
button.innerHTML = (shared ? SYNC_ICONS.on : SYNC_ICONS.off) + '<span>' + (shared ? 'Shared' : 'Sync') + '</span>';
|
button.innerHTML = shared ? SYNC_ICONS.on : SYNC_ICONS.off;
|
||||||
button.title = shared
|
button.title = shared
|
||||||
? 'This model is shared with Archify Sync: invite people, publish a version, see versions'
|
? 'Archify Sync: this model is shared. Invite people, publish a version, see versions'
|
||||||
: 'Archify Sync: sign in, share this model with others, or join one';
|
: 'Archify Sync: sign in, share this model with others, or join one';
|
||||||
|
button.setAttribute('aria-label', shared ? 'Archify Sync (shared)' : 'Archify Sync');
|
||||||
}
|
}
|
||||||
window.addEventListener('message', function (event) {
|
window.addEventListener('message', function (event) {
|
||||||
var message = event.data || {};
|
var message = event.data || {};
|
||||||
if (message.type === 'syncState') showSyncState(message.shared);
|
if (message.type === 'syncState') showSyncState(message.shared);
|
||||||
});
|
});
|
||||||
var SYNC_BUTTON_STYLE = '#archify-sync-button { position: fixed; top: 12px; right: 18px; z-index: 1001; display: inline-flex; align-items: center; gap: 6px;'
|
// Every rule is !important: the page's own button styles (Archify's toolbar,
|
||||||
+ ' padding: 6px 12px; border-radius: 8px; cursor: pointer; font: 600 13px/1 var(--vscode-font-family, system-ui, sans-serif);'
|
// VS Code's high contrast themes) must not resize or recolour it.
|
||||||
+ ' color: var(--vscode-button-foreground, #fff); background: var(--vscode-button-background, #0e639c); border: 1px solid var(--vscode-button-border, transparent); }'
|
var SYNC_BUTTON_STYLE = '#archify-sync-button { all: unset; position: fixed !important; top: 12px !important; right: 28px !important; z-index: 1001 !important;'
|
||||||
+ ' #archify-sync-button:hover { background: var(--vscode-button-hoverBackground, #1177bb); }'
|
+ ' box-sizing: border-box !important; width: 30px !important; height: 30px !important; min-width: 0 !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important;'
|
||||||
+ ' #archify-sync-button.is-shared { color: #fff; background: #1f8f5f; }'
|
+ ' display: flex !important; align-items: center !important; justify-content: center !important; border-radius: 8px !important; cursor: pointer !important;'
|
||||||
+ ' #archify-sync-button.is-shared:hover { background: #23a36c; }'
|
+ ' color: #fff !important; background: var(--vscode-button-background, #0e639c) !important; border: 1px solid var(--vscode-contrastBorder, transparent) !important;'
|
||||||
+ ' #archify-sync-button:focus-visible { outline: 2px solid var(--vscode-focusBorder, #007fd4); outline-offset: 2px; }';
|
+ ' box-shadow: 0 1px 4px rgba(0,0,0,.35) !important; }'
|
||||||
|
+ ' #archify-sync-button.in-toolbar { position: relative !important; top: auto !important; right: auto !important; align-self: center !important; flex: none !important; width: 36px !important; height: 36px !important; }'
|
||||||
|
+ ' body.vscode-high-contrast #archify-sync-button { background: #0e639c !important; }'
|
||||||
|
+ ' #archify-sync-button svg { display: block !important; width: 16px !important; height: 16px !important; flex: none !important; }'
|
||||||
|
+ ' #archify-sync-button:hover { background: var(--vscode-button-hoverBackground, #1177bb) !important; }'
|
||||||
|
+ ' #archify-sync-button.is-shared { background: #1f8f5f !important; }'
|
||||||
|
+ ' #archify-sync-button.is-shared:hover { background: #23a36c !important; }'
|
||||||
|
+ ' #archify-sync-button:focus-visible { outline: 2px solid var(--vscode-focusBorder, #007fd4) !important; outline-offset: 2px !important; }';
|
||||||
onReady(renderSyncButton);
|
onReady(renderSyncButton);
|
||||||
if (config.source) {
|
if (config.source) {
|
||||||
// The host answers with the problem count for the Source badge.
|
// The host answers with the problem count for the Source badge.
|
||||||
|
|||||||
Reference in new issue
Block a user