0.10.0: Archify Sync, optional live collaboration on a model

Several people edit one model at once through a server you run yourself
(archify-vscode-ext-sync); the files stay ordinary files on each disk.
Sign-in through the browser, offline-first with later merge, teammates'
cursors in the Source view and text editors, and a Shared section on the
model page with people, versions, Compare and Restore.

Without a server nothing changes: the sync code is a separate bundle that
loads only when a model is shared, after a sign-in, or when a sync command
or invite link is used.
This commit is contained in:
root-at-skic committed 2026-11-06 22:55:01 +02:00
1 parent a9021312b0
commit c99f3e5fe8
27 files changed
+2899 -14

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
import * as path from 'node:path';
import * as vscode from 'vscode';
import type { ModelService } from '../model/service';
import { log } from '../util/log';
import type { SyncFeature } from './index';
import { liveBridge } from './liveBridge';
// Kept in step with SYNC_COMMANDS in ./index (which lives in the other bundle).
const COMMANDS = [
'archify.sync.signIn',
'archify.sync.signOut',
'archify.sync.share',
'archify.sync.invite',
'archify.sync.join',
'archify.sync.publish',
'archify.sync.versions',
'archify.sync.stop',
'archify.sync.menu',
] as const;
/**
* Archify Sync is optional. This registers its commands and the URI handler,
* and loads dist/sync.js (Yjs, the WebSocket) only when it is needed: a model
* in the workspace is shared, someone signed in on this machine, or a sync
* command or link runs. Without that, nothing else happens.
*/
export function registerSync(context: vscode.ExtensionContext, models: ModelService): vscode.Disposable[] {
let feature: Promise<SyncFeature> | undefined;
const load = (): Promise<SyncFeature> =>
(feature ??= (async () => {
// A runtime path, so esbuild leaves the sync bundle out of this one.
const bundle = path.join(__dirname, 'sync.js');
const mod = require(bundle) as typeof import('./index');
const sync = new mod.SyncFeature(context, models, log(), liveBridge);
context.subscriptions.push(sync);
await sync.start();
return sync;
})());
void (async () => {
const used = context.globalState.get<boolean>('archifySync.used') === true;
const shared = used || (await vscode.workspace.findFiles('**/.archify-sync', '{**/node_modules/**,**/.git/**}', 1)).length > 0;
if (shared) load().catch((error) => log().error(`sync: did not start: ${String(error)}`));
})();
return [
liveBridge,
...COMMANDS.map((id) =>
vscode.commands.registerCommand(id, async (...args: unknown[]) => (await load()).run(id, ...args)),
),
vscode.window.registerUriHandler({ handleUri: async (uri) => (await load()).handleUri(uri) }),
];
}