feat: add runtime API registry for dynamic provider switching
This commit is contained in:
@@ -1,95 +0,0 @@
|
|||||||
name: deploy-custom
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [custom]
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: linux-amd64
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
rm -rf $GITHUB_WORKSPACE/repo
|
|
||||||
mkdir -p $GITHUB_WORKSPACE/repo
|
|
||||||
git clone --depth 1 --branch custom gitea@giteassh.buzzbee.dev:BuzzbeeSCD/openchamber.git $GITHUB_WORKSPACE/repo
|
|
||||||
- name: Show revision
|
|
||||||
run: git -C $GITHUB_WORKSPACE/repo rev-parse --short HEAD
|
|
||||||
- name: Stage new build (atomic — live dir untouched)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
SHA=$(git -C $GITHUB_WORKSPACE/repo rev-parse --short HEAD)
|
|
||||||
STAMP=$(date +%Y%m%d-%H%M%S)
|
|
||||||
NEWDIR=/opt/app/deploy/builds/${SHA}-${STAMP}
|
|
||||||
mkdir -p "$NEWDIR"
|
|
||||||
touch "$NEWDIR/.stage-start"
|
|
||||||
tar -C $GITHUB_WORKSPACE/repo --exclude=.git -cf - . | tar -C "$NEWDIR" -xf -
|
|
||||||
echo "$NEWDIR" > $GITHUB_WORKSPACE/newdir
|
|
||||||
echo "staged $NEWDIR"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
NEWDIR=$(cat $GITHUB_WORKSPACE/newdir)
|
|
||||||
cd "$NEWDIR" && bun install --frozen-lockfile
|
|
||||||
- name: Build web
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
NEWDIR=$(cat $GITHUB_WORKSPACE/newdir)
|
|
||||||
cd "$NEWDIR" && bun run build:web
|
|
||||||
- name: Verify dist (fresh, non-empty — stale/partial fails here)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
NEWDIR=$(cat $GITHUB_WORKSPACE/newdir)
|
|
||||||
DIST="$NEWDIR/packages/web/dist/index.html"
|
|
||||||
test -f "$DIST"
|
|
||||||
test -s "$DIST"
|
|
||||||
test "$DIST" -nt "$NEWDIR/.stage-start"
|
|
||||||
rm -f "$NEWDIR/.stage-start"
|
|
||||||
echo "dist verified: $(stat -c '%s bytes, %y' "$DIST")"
|
|
||||||
- name: Swap current symlink (single atomic op)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
NEWDIR=$(cat $GITHUB_WORKSPACE/newdir)
|
|
||||||
readlink /opt/app/deploy/current > $GITHUB_WORKSPACE/prevdir
|
|
||||||
ln -sfn "$NEWDIR" /opt/app/deploy/current
|
|
||||||
echo "swapped: $(cat $GITHUB_WORKSPACE/prevdir) -> $NEWDIR"
|
|
||||||
- name: Restart service
|
|
||||||
run: sudo systemctl restart openchamber-custom
|
|
||||||
- name: Health-check (auto-rollback on failure)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
NEWDIR=$(cat $GITHUB_WORKSPACE/newdir)
|
|
||||||
PREVDIR=$(cat $GITHUB_WORKSPACE/prevdir)
|
|
||||||
SHA=$(git -C $GITHUB_WORKSPACE/repo rev-parse HEAD)
|
|
||||||
ok=0
|
|
||||||
for i in $(seq 1 24); do
|
|
||||||
CODE=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3001/ || true)
|
|
||||||
if [ "$CODE" = "200" ]; then ok=1; break; fi
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
if [ "$ok" = "1" ]; then
|
|
||||||
echo "healthy on $NEWDIR — pruning old builds"
|
|
||||||
ls -dt /opt/app/deploy/builds/*/ 2>/dev/null | tail -n +3 | xargs -r rm -rf
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "HEALTH CHECK FAILED — rolling back to $PREVDIR"
|
|
||||||
ln -sfn "$PREVDIR" /opt/app/deploy/current
|
|
||||||
sudo systemctl restart openchamber-custom
|
|
||||||
sleep 15
|
|
||||||
RCODE=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3001/ || true)
|
|
||||||
MSG="OpenChamber deploy FAILED (health $RCODE after rollback to $PREVDIR)"
|
|
||||||
if [ "$RCODE" = "200" ]; then MSG="$MSG — rollback healthy"; else MSG="$MSG — ROLLBACK ALSO UNHEALTHY, manual intervention needed"; fi
|
|
||||||
/opt/app/.local/bin/openchamber-notify-deploy.sh "$SHA" "$MSG" "deploy-bot"
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Notify on successful deploy
|
|
||||||
run: |
|
|
||||||
SHA=$(git -C $GITHUB_WORKSPACE/repo rev-parse HEAD)
|
|
||||||
MSG=$(git -C $GITHUB_WORKSPACE/repo log -1 --format=%s)
|
|
||||||
AUTHOR=$(git -C $GITHUB_WORKSPACE/repo log -1 --format=%an)
|
|
||||||
/opt/app/.local/bin/openchamber-notify-deploy.sh "$SHA" "$MSG" "$AUTHOR"
|
|
||||||
|
|
||||||
- name: Notify on failed deploy
|
|
||||||
if: failure()
|
|
||||||
run: |
|
|
||||||
SHA=$(git -C $GITHUB_WORKSPACE/repo rev-parse HEAD || echo unknown)
|
|
||||||
/opt/app/.local/bin/openchamber-notify-deploy.sh "$SHA" "OpenChamber deploy FAILED before health-check (see Actions log)" "deploy-bot"
|
|
||||||
@@ -2,10 +2,38 @@ import React, { type JSX, type ReactNode } from 'react';
|
|||||||
import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext';
|
import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext';
|
||||||
import type { FilesAPI, RuntimeAPIs } from '@/lib/api/types';
|
import type { FilesAPI, RuntimeAPIs } from '@/lib/api/types';
|
||||||
import { createContentCachedFiles } from '@/contexts/content-cache-owner';
|
import { createContentCachedFiles } from '@/contexts/content-cache-owner';
|
||||||
|
import {
|
||||||
|
resolveActiveRuntimeAPIs,
|
||||||
|
subscribeRuntimeProviderChanged,
|
||||||
|
} from '@/lib/runtime-api-registry';
|
||||||
|
|
||||||
type ContentCachedFiles = ReturnType<typeof createContentCachedFiles>;
|
type ContentCachedFiles = ReturnType<typeof createContentCachedFiles>;
|
||||||
|
|
||||||
export function RuntimeAPIProvider({ apis, children }: { apis: RuntimeAPIs; children: ReactNode }): JSX.Element {
|
interface RuntimeAPIProviderProps {
|
||||||
|
/** Runtime APIs to provide. Ignored when a registry provider is active. */
|
||||||
|
apis: RuntimeAPIs;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides RuntimeAPIs to the React tree. When a provider is registered in
|
||||||
|
* the runtime API registry, the component automatically re-resolves the APIs
|
||||||
|
* on provider switches. Otherwise it uses the `apis` prop directly (legacy
|
||||||
|
* behaviour, fully backward compatible).
|
||||||
|
*/
|
||||||
|
export function RuntimeAPIProvider({ apis: fallbackApis, children }: RuntimeAPIProviderProps): JSX.Element {
|
||||||
|
const [registryApis, setRegistryApis] = React.useState<RuntimeAPIs | null>(() =>
|
||||||
|
resolveActiveRuntimeAPIs(),
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
return subscribeRuntimeProviderChanged(() => {
|
||||||
|
setRegistryApis(resolveActiveRuntimeAPIs());
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const apis = registryApis ?? fallbackApis;
|
||||||
|
|
||||||
// Effect-owned lifecycle: React Strict Mode dispose+remount must create a fresh
|
// Effect-owned lifecycle: React Strict Mode dispose+remount must create a fresh
|
||||||
// owner. useMemo + dispose reused a dead owner and broke text-file opens
|
// owner. useMemo + dispose reused a dead owner and broke text-file opens
|
||||||
// (binaries skipped the pre-read, so they still appeared to work).
|
// (binaries skipped the pre-read, so they still appeared to work).
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest';
|
||||||
|
import type { RuntimeAPIs } from '@/lib/api/types';
|
||||||
|
import {
|
||||||
|
registerRuntimeProvider,
|
||||||
|
unregisterRuntimeProvider,
|
||||||
|
getRuntimeProvider,
|
||||||
|
getActiveRuntimeProviderName,
|
||||||
|
listRuntimeProviders,
|
||||||
|
setActiveRuntimeProvider,
|
||||||
|
resolveActiveRuntimeAPIs,
|
||||||
|
subscribeRuntimeProviderChanged,
|
||||||
|
} from './runtime-api-registry';
|
||||||
|
|
||||||
|
const stubApis = (platform: string): RuntimeAPIs => ({
|
||||||
|
runtime: { platform: platform as 'web' | 'desktop' | 'vscode', isDesktop: false, isVSCode: false, label: platform },
|
||||||
|
terminal: {} as RuntimeAPIs['terminal'],
|
||||||
|
git: {} as RuntimeAPIs['git'],
|
||||||
|
files: {} as RuntimeAPIs['files'],
|
||||||
|
settings: {} as RuntimeAPIs['settings'],
|
||||||
|
permissions: {} as RuntimeAPIs['permissions'],
|
||||||
|
notifications: {} as RuntimeAPIs['notifications'],
|
||||||
|
tools: {} as RuntimeAPIs['tools'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure window is available for DOM event dispatch in bun test environment
|
||||||
|
const ensureWindow = () => {
|
||||||
|
if (typeof globalThis.window === 'undefined') {
|
||||||
|
(globalThis as Record<string, unknown>).window = globalThis;
|
||||||
|
}
|
||||||
|
if (typeof globalThis.CustomEvent === 'undefined') {
|
||||||
|
(globalThis as Record<string, unknown>).CustomEvent = class CustomEvent<T = unknown> extends Event {
|
||||||
|
detail: T;
|
||||||
|
constructor(type: string, options?: CustomEventInit<T>) {
|
||||||
|
super(type, options);
|
||||||
|
this.detail = options?.detail as T;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('runtime-api-registry', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
ensureWindow();
|
||||||
|
// Clean up all registered providers between tests
|
||||||
|
for (const name of listRuntimeProviders()) {
|
||||||
|
unregisterRuntimeProvider(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registerRuntimeProvider makes a provider retrievable', () => {
|
||||||
|
const factory = () => stubApis('web');
|
||||||
|
registerRuntimeProvider('web', factory);
|
||||||
|
expect(getRuntimeProvider('web')).toBe(factory);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('listRuntimeProviders returns all registered names', () => {
|
||||||
|
registerRuntimeProvider('a', () => stubApis('a'));
|
||||||
|
registerRuntimeProvider('b', () => stubApis('b'));
|
||||||
|
expect(listRuntimeProviders()).toEqual(expect.arrayContaining(['a', 'b']));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unregisterRuntimeProvider removes a provider', () => {
|
||||||
|
registerRuntimeProvider('temp', () => stubApis('temp'));
|
||||||
|
unregisterRuntimeProvider('temp');
|
||||||
|
expect(getRuntimeProvider('temp')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unregisterRuntimeProvider clears active when removing the active provider', () => {
|
||||||
|
registerRuntimeProvider('temp', () => stubApis('temp'));
|
||||||
|
setActiveRuntimeProvider('temp');
|
||||||
|
expect(getActiveRuntimeProviderName()).toBe('temp');
|
||||||
|
unregisterRuntimeProvider('temp');
|
||||||
|
expect(getActiveRuntimeProviderName()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setActiveRuntimeProvider switches the active provider', () => {
|
||||||
|
registerRuntimeProvider('web', () => stubApis('web'));
|
||||||
|
registerRuntimeProvider('vscode', () => stubApis('vscode'));
|
||||||
|
setActiveRuntimeProvider('web');
|
||||||
|
expect(getActiveRuntimeProviderName()).toBe('web');
|
||||||
|
setActiveRuntimeProvider('vscode');
|
||||||
|
expect(getActiveRuntimeProviderName()).toBe('vscode');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setActiveRuntimeProvider throws for unknown provider', () => {
|
||||||
|
expect(() => setActiveRuntimeProvider('nonexistent')).toThrow('not registered');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setActiveRuntimeProvider is idempotent for the same name', () => {
|
||||||
|
registerRuntimeProvider('web', () => stubApis('web'));
|
||||||
|
setActiveRuntimeProvider('web');
|
||||||
|
// Second call with same name should not throw or change anything
|
||||||
|
setActiveRuntimeProvider('web');
|
||||||
|
expect(getActiveRuntimeProviderName()).toBe('web');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolveActiveRuntimeAPIs returns the APIs from the active provider', () => {
|
||||||
|
registerRuntimeProvider('web', () => stubApis('web'));
|
||||||
|
setActiveRuntimeProvider('web');
|
||||||
|
const apis = resolveActiveRuntimeAPIs();
|
||||||
|
expect(apis?.runtime.label).toBe('web');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolveActiveRuntimeAPIs returns null when no provider is active', () => {
|
||||||
|
expect(resolveActiveRuntimeAPIs()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getRuntimeProvider without args returns the active provider', () => {
|
||||||
|
registerRuntimeProvider('web', () => stubApis('web'));
|
||||||
|
setActiveRuntimeProvider('web');
|
||||||
|
expect(getRuntimeProvider()).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('subscribeRuntimeProviderChanged fires on provider switch', () => {
|
||||||
|
registerRuntimeProvider('a', () => stubApis('a'));
|
||||||
|
registerRuntimeProvider('b', () => stubApis('b'));
|
||||||
|
|
||||||
|
const callback = vi.fn();
|
||||||
|
const unsub = subscribeRuntimeProviderChanged(callback);
|
||||||
|
|
||||||
|
setActiveRuntimeProvider('a');
|
||||||
|
expect(callback).toHaveBeenCalledWith({ previous: null, current: 'a' });
|
||||||
|
|
||||||
|
setActiveRuntimeProvider('b');
|
||||||
|
expect(callback).toHaveBeenCalledWith({ previous: 'a', current: 'b' });
|
||||||
|
|
||||||
|
unsub();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('subscribeRuntimeProviderChanged unsubscribes correctly', () => {
|
||||||
|
registerRuntimeProvider('a', () => stubApis('a'));
|
||||||
|
registerRuntimeProvider('b', () => stubApis('b'));
|
||||||
|
|
||||||
|
const callback = vi.fn();
|
||||||
|
const unsub = subscribeRuntimeProviderChanged(callback);
|
||||||
|
|
||||||
|
setActiveRuntimeProvider('a');
|
||||||
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
unsub();
|
||||||
|
setActiveRuntimeProvider('b');
|
||||||
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registerRuntimeProvider overwrites a previous factory with the same name', () => {
|
||||||
|
const factory1 = () => stubApis('web');
|
||||||
|
const factory2 = () => stubApis('desktop');
|
||||||
|
registerRuntimeProvider('web', factory1);
|
||||||
|
registerRuntimeProvider('web', factory2);
|
||||||
|
expect(getRuntimeProvider('web')).toBe(factory2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import type { RuntimeAPIs } from '@/lib/api/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory that creates RuntimeAPIs for a named runtime.
|
||||||
|
*
|
||||||
|
* The factory is called each time the provider becomes active (or the
|
||||||
|
* endpoint changes while it is active), so it must be cheap and side-effect
|
||||||
|
* free beyond returning the APIs object.
|
||||||
|
*/
|
||||||
|
export type RuntimeAPIProviderFactory = () => RuntimeAPIs;
|
||||||
|
|
||||||
|
export type RuntimeAPIProviderChangedDetail = {
|
||||||
|
previous: string | null;
|
||||||
|
current: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const RUNTIME_API_PROVIDER_CHANGED_EVENT = 'openchamber:runtime-api-provider-changed';
|
||||||
|
|
||||||
|
// ---- Provider registry --------------------------------------------------
|
||||||
|
|
||||||
|
const providers = new Map<string, RuntimeAPIProviderFactory>();
|
||||||
|
let activeProviderName: string | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a named provider factory. Overwrites any previous factory with
|
||||||
|
* the same name. Does NOT activate the provider — call
|
||||||
|
* `setActiveRuntimeProvider` separately.
|
||||||
|
*/
|
||||||
|
export const registerRuntimeProvider = (name: string, factory: RuntimeAPIProviderFactory): void => {
|
||||||
|
providers.set(name, factory);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a provider from the registry. If it was the active provider, the
|
||||||
|
* active slot is cleared.
|
||||||
|
*/
|
||||||
|
export const unregisterRuntimeProvider = (name: string): void => {
|
||||||
|
providers.delete(name);
|
||||||
|
if (activeProviderName === name) {
|
||||||
|
activeProviderName = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up a registered provider by name, or the currently active provider
|
||||||
|
* when `name` is omitted.
|
||||||
|
*/
|
||||||
|
export const getRuntimeProvider = (name?: string): RuntimeAPIProviderFactory | undefined => {
|
||||||
|
const key = name ?? activeProviderName;
|
||||||
|
return key != null ? providers.get(key) : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the name of the currently active provider, or null when none is
|
||||||
|
* active.
|
||||||
|
*/
|
||||||
|
export const getActiveRuntimeProviderName = (): string | null => activeProviderName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the names of all registered providers.
|
||||||
|
*/
|
||||||
|
export const listRuntimeProviders = (): string[] => [...providers.keys()];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch the active provider. Throws when the name does not match a
|
||||||
|
* registered provider. Emits a DOM custom event so React subscribers
|
||||||
|
* (RuntimeAPIProvider) can re-resolve the APIs.
|
||||||
|
*/
|
||||||
|
export const setActiveRuntimeProvider = (name: string): void => {
|
||||||
|
if (!providers.has(name)) {
|
||||||
|
throw new Error(`Runtime provider "${name}" is not registered`);
|
||||||
|
}
|
||||||
|
const previous = activeProviderName;
|
||||||
|
if (previous === name) return;
|
||||||
|
activeProviderName = name;
|
||||||
|
emitProviderChanged({ previous, current: name });
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Change subscription ------------------------------------------------
|
||||||
|
|
||||||
|
export const subscribeRuntimeProviderChanged = (
|
||||||
|
callback: (detail: RuntimeAPIProviderChangedDetail) => void,
|
||||||
|
): (() => void) => {
|
||||||
|
if (typeof window === 'undefined') return () => {};
|
||||||
|
const listener = (event: Event) => {
|
||||||
|
callback((event as CustomEvent<RuntimeAPIProviderChangedDetail>).detail);
|
||||||
|
};
|
||||||
|
window.addEventListener(RUNTIME_API_PROVIDER_CHANGED_EVENT, listener);
|
||||||
|
return () => window.removeEventListener(RUNTIME_API_PROVIDER_CHANGED_EVENT, listener);
|
||||||
|
};
|
||||||
|
|
||||||
|
function emitProviderChanged(detail: RuntimeAPIProviderChangedDetail): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent<RuntimeAPIProviderChangedDetail>(RUNTIME_API_PROVIDER_CHANGED_EVENT, { detail }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- High-level helpers -------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the RuntimeAPIs for the currently active provider. Returns null
|
||||||
|
* when no provider is registered or active.
|
||||||
|
*/
|
||||||
|
export const resolveActiveRuntimeAPIs = (): RuntimeAPIs | null => {
|
||||||
|
const factory = getRuntimeProvider();
|
||||||
|
return factory ? factory() : null;
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { RuntimeAPIs, TerminalAPI } from '@openchamber/ui/lib/api/types';
|
import type { RuntimeAPIs, TerminalAPI } from '@openchamber/ui/lib/api/types';
|
||||||
|
import { registerRuntimeProvider } from '@openchamber/ui/lib/runtime-api-registry';
|
||||||
import { createVSCodeFilesAPI } from './files';
|
import { createVSCodeFilesAPI } from './files';
|
||||||
import { createVSCodeSettingsAPI } from './settings';
|
import { createVSCodeSettingsAPI } from './settings';
|
||||||
import { createVSCodePermissionsAPI } from './permissions';
|
import { createVSCodePermissionsAPI } from './permissions';
|
||||||
@@ -38,3 +39,10 @@ export const createVSCodeAPIs = (): RuntimeAPIs => ({
|
|||||||
editor: createVSCodeEditorAPI(),
|
editor: createVSCodeEditorAPI(),
|
||||||
vscode: createVSCodeActionsAPI(),
|
vscode: createVSCodeActionsAPI(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the VS Code provider with the runtime API registry.
|
||||||
|
*/
|
||||||
|
export const registerVSCodeProvider = (): void => {
|
||||||
|
registerRuntimeProvider('vscode', () => createVSCodeAPIs());
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
setRuntimeUrlResolver,
|
setRuntimeUrlResolver,
|
||||||
type RuntimeUrlResolver,
|
type RuntimeUrlResolver,
|
||||||
} from '@openchamber/ui/lib/runtime-url';
|
} from '@openchamber/ui/lib/runtime-url';
|
||||||
|
import { registerRuntimeProvider } from '@openchamber/ui/lib/runtime-api-registry';
|
||||||
import { useDirectoryStore } from '@openchamber/ui/stores/useDirectoryStore';
|
import { useDirectoryStore } from '@openchamber/ui/stores/useDirectoryStore';
|
||||||
import { createWebTerminalAPI } from './terminal';
|
import { createWebTerminalAPI } from './terminal';
|
||||||
import { createWebGitAPI } from './git';
|
import { createWebGitAPI } from './git';
|
||||||
@@ -56,3 +57,11 @@ export const createWebAPIs = (options: WebAPIsOptions = {}): RuntimeAPIs => {
|
|||||||
tools: createWebToolsAPI(),
|
tools: createWebToolsAPI(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the web provider with the runtime API registry. Call once during
|
||||||
|
* app bootstrap (after the URL resolver and auth are configured).
|
||||||
|
*/
|
||||||
|
export const registerWebProvider = (): void => {
|
||||||
|
registerRuntimeProvider('web', () => createWebAPIs());
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user