feat: isolated plugin system (#2742)

This commit is contained in:
Himself65
2023-06-09 16:43:46 +08:00
committed by GitHub
parent af6f431c15
commit f2ac2e5b84
51 changed files with 489 additions and 209 deletions

View File

@@ -113,3 +113,6 @@ runs:
shell: bash shell: bash
if: inputs.playwright-install == 'true' && steps.playwright-cache.outputs.cache-hit != 'true' if: inputs.playwright-install == 'true' && steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install --with-deps run: yarn playwright install --with-deps
- name: Build Infra
shell: bash
run: yarn build:infra

View File

@@ -7,6 +7,7 @@ import { registerEvents } from './events';
import { registerHandlers } from './handlers'; import { registerHandlers } from './handlers';
import { logger } from './logger'; import { logger } from './logger';
import { restoreOrCreateWindow } from './main-window'; import { restoreOrCreateWindow } from './main-window';
import { registerPlugin } from './plugin';
import { registerProtocol } from './protocol'; import { registerProtocol } from './protocol';
import { registerUpdater } from './updater'; import { registerUpdater } from './updater';
@@ -60,6 +61,7 @@ app
.then(registerProtocol) .then(registerProtocol)
.then(registerHandlers) .then(registerHandlers)
.then(registerEvents) .then(registerEvents)
.then(registerPlugin)
.then(restoreOrCreateWindow) .then(restoreOrCreateWindow)
.then(createApplicationMenu) .then(createApplicationMenu)
.then(registerUpdater) .then(registerUpdater)

View File

@@ -0,0 +1,57 @@
import { join } from 'node:path';
import { Worker } from 'node:worker_threads';
import { AsyncCall } from 'async-call-rpc';
import { ipcMain } from 'electron';
import { ThreadWorkerChannel } from './utils';
declare global {
// fixme(himself65):
// remove this when bookmark block plugin is migrated to plugin-infra
// eslint-disable-next-line no-var
var asyncCall: Record<string, (...args: any) => PromiseLike<any>>;
}
export async function registerPlugin() {
const pluginWorkerPath = join(__dirname, './workers/plugin.worker.js');
const asyncCall = AsyncCall<
Record<string, (...args: any) => PromiseLike<any>>
>(
{},
{
channel: new ThreadWorkerChannel(new Worker(pluginWorkerPath)),
}
);
globalThis.asyncCall = asyncCall;
await import('@toeverything/plugin-infra/manager').then(
({ rootStore, affinePluginsAtom }) => {
const bookmarkPluginPath = join(
process.env.PLUGIN_DIR ?? '../../plugins',
'./bookmark-block/index.mjs'
);
import(bookmarkPluginPath);
let dispose: () => void = () => {
// noop
};
rootStore.sub(affinePluginsAtom, () => {
dispose();
const plugins = rootStore.get(affinePluginsAtom);
Object.values(plugins).forEach(plugin => {
plugin.definition.commands.forEach(command => {
ipcMain.handle(command, (event, ...args) =>
asyncCall[command](...args)
);
});
});
dispose = () => {
Object.values(plugins).forEach(plugin => {
plugin.definition.commands.forEach(command => {
ipcMain.removeHandler(command);
});
});
};
});
}
);
}

View File

@@ -1,17 +1,9 @@
import { join } from 'node:path';
import { app, BrowserWindow, nativeTheme } from 'electron'; import { app, BrowserWindow, nativeTheme } from 'electron';
import type { NamespaceHandlers } from '../type'; import type { NamespaceHandlers } from '../type';
import { isMacOS } from '../utils'; import { isMacOS } from '../utils';
import { getGoogleOauthCode } from './google-auth'; import { getGoogleOauthCode } from './google-auth';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const handlers = require(join(
process.env.PLUGIN_DIR ?? '../../plugins',
'./bookmark-block/server'
)).default as NamespaceHandlers;
export const uiHandlers = { export const uiHandlers = {
handleThemeChange: async (_, theme: (typeof nativeTheme)['themeSource']) => { handleThemeChange: async (_, theme: (typeof nativeTheme)['themeSource']) => {
nativeTheme.themeSource = theme; nativeTheme.themeSource = theme;
@@ -47,5 +39,12 @@ export const uiHandlers = {
getGoogleOauthCode: async () => { getGoogleOauthCode: async () => {
return getGoogleOauthCode(); return getGoogleOauthCode();
}, },
...handlers, /**
* @deprecated Remove this when bookmark block plugin is migrated to plugin-infra
*/
getBookmarkDataByLink: async (_, link: string) => {
return globalThis.asyncCall[
'com.blocksuite.bookmark-block.get-bookmark-data-by-link'
](link);
},
} satisfies NamespaceHandlers; } satisfies NamespaceHandlers;

View File

@@ -1,3 +1,7 @@
import type { MessagePort, Worker } from 'node:worker_threads';
import type { EventBasedChannel } from 'async-call-rpc';
export function getTime() { export function getTime() {
return new Date().getTime(); return new Date().getTime();
} }
@@ -9,3 +13,33 @@ export const isMacOS = () => {
export const isWindows = () => { export const isWindows = () => {
return process.platform === 'win32'; return process.platform === 'win32';
}; };
export class ThreadWorkerChannel implements EventBasedChannel {
constructor(private worker: Worker) {}
on(listener: (data: unknown) => void) {
this.worker.addListener('message', listener);
return () => {
this.worker.removeListener('message', listener);
};
}
send(data: unknown) {
this.worker.postMessage(data);
}
}
export class MessagePortChannel implements EventBasedChannel {
constructor(private port: MessagePort) {}
on(listener: (data: unknown) => void) {
this.port.addListener('message', listener);
return () => {
this.port.removeListener('message', listener);
};
}
send(data: unknown) {
this.port.postMessage(data);
}
}

View File

@@ -0,0 +1,43 @@
import { join } from 'node:path';
import { parentPort } from 'node:worker_threads';
import { AsyncCall } from 'async-call-rpc';
import { MessagePortChannel } from '../utils';
const commandProxy: Record<string, (...args: any[]) => Promise<any>> = {};
if (!parentPort) {
throw new Error('parentPort is undefined');
}
AsyncCall(commandProxy, {
channel: new MessagePortChannel(parentPort),
});
import('@toeverything/plugin-infra/manager').then(
({ rootStore, affinePluginsAtom }) => {
const bookmarkPluginPath = join(
process.env.PLUGIN_DIR ?? '../../../plugins',
'./bookmark-block/index.mjs'
);
import(bookmarkPluginPath);
rootStore.sub(affinePluginsAtom, () => {
const plugins = rootStore.get(affinePluginsAtom);
Object.values(plugins).forEach(plugin => {
if (plugin.serverAdapter) {
plugin.serverAdapter({
registerCommand: (command, fn) => {
console.log('register command', command);
commandProxy[command] = fn;
},
unregisterCommand: command => {
delete commandProxy[command];
},
});
}
});
});
}
);

View File

@@ -7,36 +7,21 @@ import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('appInfo', affineApis.appInfo); contextBridge.exposeInMainWorld('appInfo', affineApis.appInfo);
// Credit to microsoft/vscode // Credit to microsoft/vscode
function validateIPC(channel: string) {
if (!channel || !channel.startsWith('affine:')) {
throw new Error(`Unsupported event IPC channel '${channel}'`);
}
return true;
}
const globals = { const globals = {
ipcRenderer: { ipcRenderer: {
send(channel: string, ...args: any[]) { send(channel: string, ...args: any[]) {
if (validateIPC(channel)) { ipcRenderer.send(channel, ...args);
ipcRenderer.send(channel, ...args);
}
}, },
invoke(channel: string, ...args: any[]) { invoke(channel: string, ...args: any[]) {
if (validateIPC(channel)) { return ipcRenderer.invoke(channel, ...args);
return ipcRenderer.invoke(channel, ...args);
}
return void 0;
}, },
on( on(
channel: string, channel: string,
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) { ) {
if (validateIPC(channel)) { ipcRenderer.on(channel, listener);
ipcRenderer.on(channel, listener);
}
return this; return this;
}, },
@@ -44,9 +29,7 @@ import { contextBridge, ipcRenderer } from 'electron';
channel: string, channel: string,
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) { ) {
if (validateIPC(channel)) { ipcRenderer.once(channel, listener);
ipcRenderer.once(channel, listener);
}
return this; return this;
}, },
@@ -54,9 +37,7 @@ import { contextBridge, ipcRenderer } from 'electron';
channel: string, channel: string,
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) { ) {
if (validateIPC(channel)) { ipcRenderer.removeListener(channel, listener);
ipcRenderer.removeListener(channel, listener);
}
return this; return this;
}, },
}, },
@@ -65,6 +46,6 @@ import { contextBridge, ipcRenderer } from 'electron';
try { try {
contextBridge.exposeInMainWorld('affine', globals); contextBridge.exposeInMainWorld('affine', globals);
} catch (error) { } catch (error) {
console.error(error); console.error('Failed to expose affine APIs to window object!', error);
} }
})(); })();

View File

@@ -55,6 +55,8 @@
"zx": "^7.2.2" "zx": "^7.2.2"
}, },
"dependencies": { "dependencies": {
"@toeverything/plugin-infra": "workspace:*",
"async-call-rpc": "^6.3.1",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"electron-updater": "^5.3.0", "electron-updater": "^5.3.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",

View File

@@ -1,9 +1,12 @@
#!/usr/bin/env zx #!/usr/bin/env zx
import 'zx/globals'; import 'zx/globals';
import { resolve } from 'node:path';
import { spawnSync } from 'child_process';
import * as esbuild from 'esbuild'; import * as esbuild from 'esbuild';
import { config } from './common.mjs'; import { config, rootDir } from './common.mjs';
const NODE_ENV = const NODE_ENV =
process.env.NODE_ENV === 'development' ? 'development' : 'production'; process.env.NODE_ENV === 'development' ? 'development' : 'production';
@@ -16,6 +19,11 @@ if (process.platform === 'win32') {
async function buildLayers() { async function buildLayers() {
const common = config(); const common = config();
await esbuild.build(common.preload); await esbuild.build(common.preload);
console.log('Build plugin infra');
spawnSync('yarn', ['build'], {
stdio: 'inherit',
cwd: resolve(rootDir, './packages/plugin-infra'),
});
console.log('Build plugins'); console.log('Build plugins');
await import('./plugins/build-plugins.mjs'); await import('./plugins/build-plugins.mjs');

View File

@@ -41,12 +41,20 @@ export const config = () => {
electronDir, electronDir,
'./layers/main/src/workers/merge-update.worker.ts' './layers/main/src/workers/merge-update.worker.ts'
), ),
resolve(electronDir, './layers/main/src/workers/plugin.worker.ts'),
], ],
outdir: resolve(electronDir, './dist/layers/main'), outdir: resolve(electronDir, './dist/layers/main'),
bundle: true, bundle: true,
target: `node${NODE_MAJOR_VERSION}`, target: `node${NODE_MAJOR_VERSION}`,
platform: 'node', platform: 'node',
external: ['electron', 'yjs', 'electron-updater', '@toeverything/infra'], external: [
'electron',
'yjs',
'better-sqlite3',
'electron-updater',
'@toeverything/plugin-infra',
'async-call-rpc',
],
define: define, define: define,
format: 'cjs', format: 'cjs',
loader: { loader: {

View File

@@ -1,12 +1,12 @@
/* eslint-disable no-async-promise-executor */ /* eslint-disable no-async-promise-executor */
import { spawn } from 'node:child_process'; import { spawn } from 'node:child_process';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import path from 'node:path'; import path, { resolve } from 'node:path';
import electronPath from 'electron'; import electronPath from 'electron';
import * as esbuild from 'esbuild'; import * as esbuild from 'esbuild';
import { config, electronDir } from './common.mjs'; import { config, electronDir, rootDir } from './common.mjs';
// this means we don't spawn electron windows, mainly for testing // this means we don't spawn electron windows, mainly for testing
const watchMode = process.argv.includes('--watch'); const watchMode = process.argv.includes('--watch');
@@ -69,6 +69,10 @@ function spawnOrReloadElectron() {
const common = config(); const common = config();
async function watchPlugins() { async function watchPlugins() {
spawn('yarn', ['dev'], {
stdio: 'inherit',
cwd: resolve(rootDir, './packages/plugin-infra'),
});
await import('./plugins/dev-plugins.mjs'); await import('./plugins/dev-plugins.mjs');
} }

View File

@@ -5,5 +5,16 @@ import { definePluginServerConfig } from './utils.mjs';
await build({ await build({
...definePluginServerConfig('bookmark-block'), ...definePluginServerConfig('bookmark-block'),
external: ['cheerio', 'electron', 'puppeteer', 'foxact'], external: [
// server.ts
'link-preview-js',
// ui.ts
'@toeverything/plugin-infra',
'@affine/component',
'@blocksuite/store',
'@blocksuite/blocks',
'react',
'react-dom',
'foxact',
],
}); });

View File

@@ -5,7 +5,18 @@ import { definePluginServerConfig } from './utils.mjs';
const plugin = await context({ const plugin = await context({
...definePluginServerConfig('bookmark-block'), ...definePluginServerConfig('bookmark-block'),
external: ['cheerio', 'electron', 'puppeteer', 'foxact'], external: [
// server.ts
'link-preview-js',
// ui.ts
'@toeverything/plugin-infra',
'@affine/component',
'@blocksuite/store',
'@blocksuite/blocks',
'react',
'react-dom',
'foxact',
],
}); });
await plugin.watch(); await plugin.watch();

View File

@@ -18,12 +18,17 @@ export const pluginDir = resolve(rootDir, 'plugins');
*/ */
export function definePluginServerConfig(pluginDirName) { export function definePluginServerConfig(pluginDirName) {
const pluginRootDir = resolve(pluginDir, pluginDirName); const pluginRootDir = resolve(pluginDir, pluginDirName);
const serverEntryFile = resolve(pluginRootDir, 'src/server.ts'); const mainEntryFile = resolve(pluginRootDir, 'src/index.ts');
const serverOutputDir = resolve(electronOutputDir, pluginDirName); const serverOutputDir = resolve(electronOutputDir, pluginDirName);
return { return {
entryPoints: [serverEntryFile], entryPoints: [mainEntryFile],
platform: 'node', platform: 'neutral',
format: 'esm',
outExtension: {
'.js': '.mjs',
},
outdir: serverOutputDir, outdir: serverOutputDir,
bundle: true, bundle: true,
splitting: true,
}; };
} }

View File

@@ -17,10 +17,7 @@
"exclude": ["node_modules", "out", "dist"], "exclude": ["node_modules", "out", "dist"],
"references": [ "references": [
{ {
"path": "./tsconfig.node.json" "path": "../../packages/plugin-infra"
},
{
"path": "./tests/tsconfig.json"
}, },
{ {
"path": "../../packages/native" "path": "../../packages/native"
@@ -28,6 +25,14 @@
{ {
"path": "../../packages/infra" "path": "../../packages/infra"
}, },
// Tests
{
"path": "./tsconfig.node.json"
},
{
"path": "./tests/tsconfig.json"
},
{ "path": "../../tests/kit" } { "path": "../../tests/kit" }
], ],
"ts-node": { "ts-node": {

View File

@@ -1,5 +1,5 @@
{ {
"extends": "./tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"target": "ESNext", "target": "ESNext",

View File

@@ -2,10 +2,10 @@ import { Unreachable } from '@affine/env/constant';
import type { AffineLegacyCloudWorkspace } from '@affine/env/workspace'; import type { AffineLegacyCloudWorkspace } from '@affine/env/workspace';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { affineApis } from '@affine/workspace/affine/shared'; import { affineApis } from '@affine/workspace/affine/shared';
import { rootStore } from '@affine/workspace/atom';
import { createAffineProviders } from '@affine/workspace/providers'; import { createAffineProviders } from '@affine/workspace/providers';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils'; import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { assertExists } from '@blocksuite/store'; import { assertExists } from '@blocksuite/store';
import { rootStore } from '@toeverything/plugin-infra/manager';
import { workspacesAtom } from '../../atoms'; import { workspacesAtom } from '../../atoms';

View File

@@ -21,7 +21,7 @@ import {
SignMethod, SignMethod,
} from '@affine/workspace/affine/login'; } from '@affine/workspace/affine/login';
import { affineApis, affineAuth } from '@affine/workspace/affine/shared'; import { affineApis, affineAuth } from '@affine/workspace/affine/shared';
import { rootStore, rootWorkspacesMetadataAtom } from '@affine/workspace/atom'; import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { import {
createAffineProviders, createAffineProviders,
createIndexedDBBackgroundProvider, createIndexedDBBackgroundProvider,
@@ -31,6 +31,7 @@ import {
cleanupWorkspace, cleanupWorkspace,
createEmptyBlockSuiteWorkspace, createEmptyBlockSuiteWorkspace,
} from '@affine/workspace/utils'; } from '@affine/workspace/utils';
import { rootStore } from '@toeverything/plugin-infra/manager';
import { createJSONStorage } from 'jotai/utils'; import { createJSONStorage } from 'jotai/utils';
import type { PropsWithChildren, ReactElement } from 'react'; import type { PropsWithChildren, ReactElement } from 'react';
import { Suspense, useEffect } from 'react'; import { Suspense, useEffect } from 'react';

View File

@@ -1,6 +1,7 @@
import type { AffineLegacyCloudWorkspace } from '@affine/env/workspace'; import type { AffineLegacyCloudWorkspace } from '@affine/env/workspace';
import { affineApis } from '@affine/workspace/affine/shared'; import { affineApis } from '@affine/workspace/affine/shared';
import { rootStore, rootWorkspacesMetadataAtom } from '@affine/workspace/atom'; import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
import { rootStore } from '@toeverything/plugin-infra/manager';
import { useCallback } from 'react'; import { useCallback } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';

View File

@@ -20,7 +20,6 @@ import { createAffineGlobalChannel } from '@affine/workspace/affine/sync';
import { import {
rootCurrentPageIdAtom, rootCurrentPageIdAtom,
rootCurrentWorkspaceIdAtom, rootCurrentWorkspaceIdAtom,
rootStore,
rootWorkspacesMetadataAtom, rootWorkspacesMetadataAtom,
} from '@affine/workspace/atom'; } from '@affine/workspace/atom';
import { assertEquals, assertExists, nanoid } from '@blocksuite/store'; import { assertEquals, assertExists, nanoid } from '@blocksuite/store';
@@ -35,6 +34,7 @@ import {
useSensors, useSensors,
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper'; import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper';
import { rootStore } from '@toeverything/plugin-infra/manager';
import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';

View File

@@ -3,12 +3,12 @@ import '@affine/component/theme/theme.css';
// bootstrap code before everything // bootstrap code before everything
import '../bootstrap'; import '../bootstrap';
import { AffineContext } from '@affine/component/context';
import { WorkspaceFallback } from '@affine/component/workspace'; import { WorkspaceFallback } from '@affine/component/workspace';
import { config } from '@affine/env'; import { config } from '@affine/env';
import { createI18n, I18nextProvider } from '@affine/i18n'; import { createI18n, I18nextProvider } from '@affine/i18n';
import type { EmotionCache } from '@emotion/cache'; import type { EmotionCache } from '@emotion/cache';
import { CacheProvider } from '@emotion/react'; import { CacheProvider } from '@emotion/react';
import { AffinePluginContext } from '@toeverything/plugin-infra/react';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@@ -64,7 +64,7 @@ const App = function App({
<MessageCenter /> <MessageCenter />
<AffineErrorBoundary router={useRouter()}> <AffineErrorBoundary router={useRouter()}>
<Suspense fallback={<WorkspaceFallback key="RootPageLoading" />}> <Suspense fallback={<WorkspaceFallback key="RootPageLoading" />}>
<AffinePluginContext> <AffineContext>
<Head> <Head>
<title>AFFiNE</title> <title>AFFiNE</title>
<meta <meta
@@ -75,7 +75,7 @@ const App = function App({
<DebugProvider> <DebugProvider>
{getLayout(<Component {...pageProps} />)} {getLayout(<Component {...pageProps} />)}
</DebugProvider> </DebugProvider>
</AffinePluginContext> </AffineContext>
</Suspense> </Suspense>
</AffineErrorBoundary> </AffineErrorBoundary>
</I18nextProvider> </I18nextProvider>

View File

@@ -17,8 +17,9 @@
"dev:local": "PORT=8080 API_SERVER_PROFILE=local yarn workspace @affine/web dev", "dev:local": "PORT=8080 API_SERVER_PROFILE=local yarn workspace @affine/web dev",
"dev:electron": "yarn workspace @affine/electron dev:app", "dev:electron": "yarn workspace @affine/electron dev:app",
"dev:plugins": "./apps/electron/scripts/plugins/dev-plugins.mjs", "dev:plugins": "./apps/electron/scripts/plugins/dev-plugins.mjs",
"build": "yarn workspace @affine/web build", "build": "yarn build:infra && yarn workspace @affine/web build",
"build:storybook": "yarn workspace @affine/storybook build-storybook", "build:infra": "yarn workspace @toeverything/plugin-infra build && yarn workspace @toeverything/infra build",
"build:storybook": "yarn build:infra && yarn workspace @affine/storybook build-storybook",
"build:plugins": "./apps/electron/scripts/plugins/build-plugins.mjs", "build:plugins": "./apps/electron/scripts/plugins/build-plugins.mjs",
"bump:nightly": "./scripts/bump-blocksuite.sh", "bump:nightly": "./scripts/bump-blocksuite.sh",
"circular": "madge --circular --ts-config ./tsconfig.json ./apps/web/src/pages/**/*.tsx", "circular": "madge --circular --ts-config ./tsconfig.json ./apps/web/src/pages/**/*.tsx",

View File

@@ -35,6 +35,7 @@
"@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-toast": "^1.1.4", "@radix-ui/react-toast": "^1.1.4",
"@toeverything/hooks": "workspace:*", "@toeverything/hooks": "workspace:*",
"@toeverything/plugin-infra": "workspace:*",
"@toeverything/theme": "^0.6.1", "@toeverything/theme": "^0.6.1",
"@vanilla-extract/dynamic": "^2.0.3", "@vanilla-extract/dynamic": "^2.0.3",
"clsx": "^1.2.1", "clsx": "^1.2.1",

View File

@@ -1,11 +1,11 @@
import { ProviderComposer } from '@affine/component/provider-composer'; import { ProviderComposer } from '@affine/component/provider-composer';
import { ThemeProvider } from '@affine/component/theme-provider'; import { ThemeProvider } from '@affine/component/theme-provider';
import { rootStore } from '@affine/workspace/atom'; import { rootStore } from '@toeverything/plugin-infra/manager';
import { Provider } from 'jotai'; import { Provider } from 'jotai';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
import { useMemo } from 'react'; import { useMemo } from 'react';
export function AffinePluginContext(props: PropsWithChildren) { export function AffineContext(props: PropsWithChildren) {
return ( return (
<ProviderComposer <ProviderComposer
contexts={useMemo( contexts={useMemo(

View File

@@ -20,10 +20,10 @@
{ {
"path": "../hooks" "path": "../hooks"
}, },
{ "path": "../workspace" },
{ {
"path": "../../apps/electron" "path": "../plugin-infra"
}, },
{ "path": "../workspace" },
{ "path": "../../tests/fixtures" } { "path": "../../tests/fixtures" }
] ]
} }

View File

@@ -1,18 +0,0 @@
// to prevent the `@affine/components` contains circular references with `@affine/workspace`
// the include files should be excluded in `./tsconfig.json`
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"noEmit": false,
"outDir": "lib"
},
"include": [
"./src/components/page-list/filter/shared-types.tsx",
"./src/components/page-list/filter/logical/custom-type.ts",
"./src/components/page-list/filter/logical/matcher.ts",
"./src/components/page-list/filter/logical/typesystem.ts"
],
"references": [{ "path": "../env" }],
"exclude": ["lib"]
}

View File

@@ -1,18 +1,23 @@
{ {
"name": "@toeverything/plugin-infra", "name": "@toeverything/plugin-infra",
"private": true, "type": "module",
"scripts": { "scripts": {
"build": "vite build" "build": "vite build",
"dev": "vite build --watch"
}, },
"exports": { "exports": {
"./manager": "./src/manager.ts", "./manager": {
"./type": "./src/type.ts", "type": "./dist/manager.d.ts",
"./react": "./src/react/index.ts" "import": "./dist/manager.js",
"require": "./dist/manager.cjs"
},
"./type": {
"type": "./dist/type.d.ts",
"import": "./dist/type.js",
"require": "./dist/type.cjs"
}
}, },
"dependencies": { "dependencies": {
"@affine/component": "workspace:*",
"@affine/env": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/blocks": "0.0.0-20230607055421-9b20fcaf-nightly",
"@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/editor": "0.0.0-20230607055421-9b20fcaf-nightly",
"@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly", "@blocksuite/global": "0.0.0-20230607055421-9b20fcaf-nightly",
@@ -20,7 +25,9 @@
"@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly" "@blocksuite/store": "0.0.0-20230607055421-9b20fcaf-nightly"
}, },
"devDependencies": { "devDependencies": {
"jotai": "^2.1.1" "jotai": "^2.1.1",
"vite": "^4.3.9",
"vite-plugin-dts": "^2.3.0"
}, },
"peerDependencies": { "peerDependencies": {
"@blocksuite/blocks": "*", "@blocksuite/blocks": "*",

View File

@@ -1,20 +1,23 @@
import { DebugLogger } from '@affine/debug'; import { atom, createStore } from 'jotai/vanilla';
import { rootStore } from '@affine/workspace/atom';
import { atom } from 'jotai';
import type { AffinePlugin, Definition } from './type'; import type { AffinePlugin, Definition, ServerAdapter } from './type';
import type { Loader, PluginUIAdapter } from './type'; import type { Loader, PluginUIAdapter } from './type';
import type { PluginBlockSuiteAdapter } from './type'; import type { PluginBlockSuiteAdapter } from './type';
const isServer = typeof window === 'undefined';
const isClient = typeof window !== 'undefined';
// global store
export const rootStore = createStore();
// todo: for now every plugin is enabled by default // todo: for now every plugin is enabled by default
export const affinePluginsAtom = atom<Record<string, AffinePlugin<string>>>({}); export const affinePluginsAtom = atom<Record<string, AffinePlugin<string>>>({});
const pluginLogger = new DebugLogger('affine:plugin');
export function definePlugin<ID extends string>( export function definePlugin<ID extends string>(
definition: Definition<ID>, definition: Definition<ID>,
uiAdapterLoader?: Loader<Partial<PluginUIAdapter>>, uiAdapterLoader?: Loader<Partial<PluginUIAdapter>>,
blockSuiteAdapter?: Loader<Partial<PluginBlockSuiteAdapter>> blockSuiteAdapter?: Loader<Partial<PluginBlockSuiteAdapter>>,
serverAdapter?: Loader<ServerAdapter>
) { ) {
const basePlugin = { const basePlugin = {
definition, definition,
@@ -27,57 +30,70 @@ export function definePlugin<ID extends string>(
[definition.id]: basePlugin, [definition.id]: basePlugin,
})); }));
if (blockSuiteAdapter) { if (isServer) {
const updateAdapter = (adapter: Partial<PluginBlockSuiteAdapter>) => { if (serverAdapter) {
rootStore.set(affinePluginsAtom, plugins => ({ serverAdapter.load().then(({ default: adapter }) => {
...plugins, rootStore.set(affinePluginsAtom, plugins => ({
[definition.id]: { ...plugins,
...basePlugin, [definition.id]: {
blockSuiteAdapter: adapter, ...basePlugin,
}, serverAdapter: adapter,
})); },
}; }));
blockSuiteAdapter
.load()
.then(({ default: adapter }) => updateAdapter(adapter))
.catch(err => {
pluginLogger.error('[definePlugin] blockSuiteAdapter error', err);
});
if (import.meta.webpackHot) {
blockSuiteAdapter.hotModuleReload(async _ => {
const adapter = (await _).default;
updateAdapter(adapter);
pluginLogger.info('[HMR] Plugin', definition.id, 'hot reloaded.');
}); });
} }
} } else if (isClient) {
if (blockSuiteAdapter) {
const updateAdapter = (adapter: Partial<PluginBlockSuiteAdapter>) => {
rootStore.set(affinePluginsAtom, plugins => ({
...plugins,
[definition.id]: {
...basePlugin,
blockSuiteAdapter: adapter,
},
}));
};
if (uiAdapterLoader) { blockSuiteAdapter
const updateAdapter = (adapter: Partial<PluginUIAdapter>) => { .load()
rootStore.set(affinePluginsAtom, plugins => ({ .then(({ default: adapter }) => updateAdapter(adapter))
...plugins, .catch(err => {
[definition.id]: { console.error('[definePlugin] blockSuiteAdapter error', err);
...basePlugin, });
uiAdapter: adapter,
},
}));
};
uiAdapterLoader if (import.meta.webpackHot) {
.load() blockSuiteAdapter.hotModuleReload(async _ => {
.then(({ default: adapter }) => updateAdapter(adapter)) const adapter = (await _).default;
.catch(err => { updateAdapter(adapter);
pluginLogger.error('[definePlugin] blockSuiteAdapter error', err); console.info('[HMR] Plugin', definition.id, 'hot reloaded.');
}); });
}
}
if (uiAdapterLoader) {
const updateAdapter = (adapter: Partial<PluginUIAdapter>) => {
rootStore.set(affinePluginsAtom, plugins => ({
...plugins,
[definition.id]: {
...basePlugin,
uiAdapter: adapter,
},
}));
};
if (import.meta.webpackHot) { uiAdapterLoader
uiAdapterLoader.hotModuleReload(async _ => { .load()
const adapter = (await _).default; .then(({ default: adapter }) => updateAdapter(adapter))
updateAdapter(adapter); .catch(err => {
pluginLogger.info('[HMR] Plugin', definition.id, 'hot reloaded.'); console.error('[definePlugin] blockSuiteAdapter error', err);
}); });
if (import.meta.webpackHot) {
uiAdapterLoader.hotModuleReload(async _ => {
const adapter = (await _).default;
updateAdapter(adapter);
console.info('[HMR] Plugin', definition.id, 'hot reloaded.');
});
}
} }
} }
} }

View File

@@ -1 +0,0 @@
export * from './context';

View File

@@ -141,6 +141,11 @@ export type Definition<ID extends string> = {
* @example ReleaseStage.PROD * @example ReleaseStage.PROD
*/ */
stage: ReleaseStage; stage: ReleaseStage;
/**
* Registered commands
*/
commands: string[];
}; };
// todo(himself65): support Vue.js // todo(himself65): support Vue.js
@@ -171,12 +176,16 @@ export type PluginBlockSuiteAdapter = {
uiDecorator: (root: EditorContainer) => Cleanup; uiDecorator: (root: EditorContainer) => Cleanup;
}; };
export type PluginAdapterCreator = ( type AFFiNEServer = {
context: AffinePluginContext registerCommand: (command: string, fn: (...args: any[]) => any) => void;
) => PluginUIAdapter; unregisterCommand: (command: string) => void;
};
export type ServerAdapter = (affine: AFFiNEServer) => () => void;
export type AffinePlugin<ID extends string> = { export type AffinePlugin<ID extends string> = {
definition: Definition<ID>; definition: Definition<ID>;
uiAdapter: Partial<PluginUIAdapter>; uiAdapter: Partial<PluginUIAdapter>;
blockSuiteAdapter: Partial<PluginBlockSuiteAdapter>; blockSuiteAdapter: Partial<PluginBlockSuiteAdapter>;
serverAdapter?: ServerAdapter;
}; };

View File

@@ -8,10 +8,7 @@
}, },
"references": [ "references": [
{ {
"path": "../component" "path": "./tsconfig.node.json"
},
{
"path": "../workspace"
} }
] ]
} }

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"outDir": "lib"
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,35 @@
import { resolve } from 'node:path';
import { fileURLToPath } from 'url';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
const root = fileURLToPath(new URL('.', import.meta.url));
export default defineConfig({
build: {
minify: false,
lib: {
entry: {
type: resolve(root, 'src/type.ts'),
manager: resolve(root, 'src/manager.ts'),
},
},
rollupOptions: {
external: [
'jotai',
'jotai/vanilla',
'@blocksuite/blocks',
'@blocksuite/store',
'@blocksuite/global',
'@blocksuite/editor',
'@blocksuite/lit',
],
},
},
plugins: [
dts({
insertTypesEntry: true,
}),
],
});

View File

@@ -22,6 +22,7 @@
"@affine/debug": "workspace:*", "@affine/debug": "workspace:*",
"@affine/env": "workspace:*", "@affine/env": "workspace:*",
"@toeverything/hooks": "workspace:*", "@toeverything/hooks": "workspace:*",
"@toeverything/plugin-infra": "workspace:*",
"@toeverything/y-indexeddb": "workspace:*", "@toeverything/y-indexeddb": "workspace:*",
"firebase": "^9.22.1", "firebase": "^9.22.1",
"jotai": "^2.1.1", "jotai": "^2.1.1",

View File

@@ -1,6 +1,6 @@
import { prefixUrl } from '@affine/env'; import { prefixUrl } from '@affine/env';
import { rootStore } from '@toeverything/plugin-infra/manager';
import { rootStore } from '../atom';
import { createUserApis, createWorkspaceApis } from './api/index'; import { createUserApis, createWorkspaceApis } from './api/index';
import { currentAffineUserAtom } from './atom'; import { currentAffineUserAtom } from './atom';
import type { LoginResponse } from './login'; import type { LoginResponse } from './login';

View File

@@ -8,11 +8,12 @@ import {
} from '@affine/env/workspace/legacy-cloud'; } from '@affine/env/workspace/legacy-cloud';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import type { Disposable } from '@blocksuite/store'; import type { Disposable } from '@blocksuite/store';
import { rootStore } from '@toeverything/plugin-infra/manager';
import { z } from 'zod'; import { z } from 'zod';
import { WebsocketClient } from '../affine/channel'; import { WebsocketClient } from '../affine/channel';
import { storageChangeSlot } from '../affine/login'; import { storageChangeSlot } from '../affine/login';
import { rootStore, rootWorkspacesMetadataAtom } from '../atom'; import { rootWorkspacesMetadataAtom } from '../atom';
const logger = new DebugLogger('affine-sync'); const logger = new DebugLogger('affine-sync');

View File

@@ -1,7 +1,7 @@
import type { WorkspaceFlavour } from '@affine/env/workspace'; import type { WorkspaceFlavour } from '@affine/env/workspace';
import type { EditorContainer } from '@blocksuite/editor'; import type { EditorContainer } from '@blocksuite/editor';
import { atom, createStore } from 'jotai'; import { atom } from 'jotai';
import { atomWithStorage, createJSONStorage } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils';
import Router from 'next/router'; import Router from 'next/router';
export type RootWorkspaceMetadata = { export type RootWorkspaceMetadata = {
@@ -77,13 +77,3 @@ export const rootCurrentEditorAtom = atom<Readonly<EditorContainer> | null>(
null null
); );
//#endregion //#endregion
const getStorage = () => createJSONStorage(() => localStorage);
export const getStoredWorkspaceMeta = () => {
const storage = getStorage();
return storage.getItem('jotai-workspaces', []) as RootWorkspaceMetadata[];
};
// global store
export const rootStore = createStore();

View File

@@ -4,9 +4,10 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models'; import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import type { Generator, StoreOptions } from '@blocksuite/store'; import type { Generator, StoreOptions } from '@blocksuite/store';
import { createIndexeddbStorage, Workspace } from '@blocksuite/store'; import { createIndexeddbStorage, Workspace } from '@blocksuite/store';
import { rootStore } from '@toeverything/plugin-infra/manager';
import type { createWorkspaceApis } from './affine/api'; import type { createWorkspaceApis } from './affine/api';
import { rootStore, rootWorkspacesMetadataAtom } from './atom'; import { rootWorkspacesMetadataAtom } from './atom';
import { createAffineBlobStorage } from './blob'; import { createAffineBlobStorage } from './blob';
import { createSQLiteStorage } from './blob/sqlite-blob-storage'; import { createSQLiteStorage } from './blob/sqlite-blob-storage';

View File

@@ -10,6 +10,7 @@
{ "path": "../y-indexeddb" }, { "path": "../y-indexeddb" },
{ "path": "../env" }, { "path": "../env" },
{ "path": "../debug" }, { "path": "../debug" },
{ "path": "../hooks" } { "path": "../hooks" },
{ "path": "../plugin-infra" }
] ]
} }

View File

@@ -8,6 +8,7 @@
"./server": "./src/server.ts" "./server": "./src/server.ts"
}, },
"dependencies": { "dependencies": {
"@affine/component": "workspace:*",
"@toeverything/plugin-infra": "workspace:*", "@toeverything/plugin-infra": "workspace:*",
"foxact": "^0.2.7", "foxact": "^0.2.7",
"link-preview-js": "^3.0.4" "link-preview-js": "^3.0.4"

View File

@@ -19,6 +19,7 @@ definePlugin(
}, },
stage: ReleaseStage.NIGHTLY, stage: ReleaseStage.NIGHTLY,
version: '0.0.1', version: '0.0.1',
commands: ['com.blocksuite.bookmark-block.get-bookmark-data-by-link'],
}, },
undefined, undefined,
{ {
@@ -28,5 +29,19 @@ definePlugin(
import.meta.webpackHot.accept('./blocksuite', () => import.meta.webpackHot.accept('./blocksuite', () =>
onHot(import('./blocksuite/index')) onHot(import('./blocksuite/index'))
), ),
},
{
load: () =>
import(
/* webpackIgnore: true */
'./server'
),
hotModuleReload: onHot =>
onHot(
import(
/* webpackIgnore: true */
'./server'
)
),
} }
); );

View File

@@ -1,3 +1,4 @@
import type { ServerAdapter } from '@toeverything/plugin-infra/type';
import { getLinkPreview } from 'link-preview-js'; import { getLinkPreview } from 'link-preview-js';
type MetaData = { type MetaData = {
@@ -26,31 +27,41 @@ export interface PreviewType {
favicons: string[]; favicons: string[];
} }
export default { const adapter: ServerAdapter = affine => {
getBookmarkDataByLink: async (_: unknown, url: string): Promise<MetaData> => { affine.registerCommand(
const previewData = (await getLinkPreview(url, { 'com.blocksuite.bookmark-block.get-bookmark-data-by-link',
timeout: 6000, async (url: string): Promise<MetaData> => {
headers: { const previewData = (await getLinkPreview(url, {
'user-agent': 'googlebot', timeout: 6000,
}, headers: {
followRedirects: 'follow', 'user-agent': 'googlebot',
}).catch(() => { },
return { followRedirects: 'follow',
title: '', }).catch(() => {
siteName: '', return {
description: '', title: '',
images: [], siteName: '',
videos: [], description: '',
contentType: `text/html`, images: [],
favicons: [], videos: [],
}; contentType: `text/html`,
})) as PreviewType; favicons: [],
};
})) as PreviewType;
return { return {
title: previewData.title, title: previewData.title,
description: previewData.description, description: previewData.description,
icon: previewData.favicons[0], icon: previewData.favicons[0],
image: previewData.images[0], image: previewData.images[0],
}; };
}, }
);
return () => {
affine.unregisterCommand(
'com.blocksuite.bookmark-block.get-bookmark-data-by-link'
);
};
}; };
export default adapter;

View File

@@ -11,9 +11,6 @@
}, },
{ {
"path": "../../packages/plugin-infra" "path": "../../packages/plugin-infra"
},
{
"path": "../../packages/env"
} }
] ]
} }

View File

@@ -7,6 +7,7 @@
".": "./src/index.ts" ".": "./src/index.ts"
}, },
"dependencies": { "dependencies": {
"@affine/component": "workspace:*",
"@toeverything/plugin-infra": "workspace:*" "@toeverything/plugin-infra": "workspace:*"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,5 +1,5 @@
import { Button, Input } from '@affine/component'; import { Button, Input } from '@affine/component';
import { rootStore } from '@affine/workspace/atom'; import { rootStore } from '@toeverything/plugin-infra/manager';
import type { PluginUIAdapter } from '@toeverything/plugin-infra/type'; import type { PluginUIAdapter } from '@toeverything/plugin-infra/type';
import { Provider, useAtom, useAtomValue, useSetAtom } from 'jotai'; import { Provider, useAtom, useAtomValue, useSetAtom } from 'jotai';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
@@ -10,7 +10,7 @@ import { Conversation } from '../core/components/conversation';
import { Divider } from '../core/components/divider'; import { Divider } from '../core/components/divider';
import { openAIApiKeyAtom, useChatAtoms } from '../core/hooks'; import { openAIApiKeyAtom, useChatAtoms } from '../core/hooks';
if (!environment.isServer) { if (typeof window === 'undefined') {
import('@blocksuite/blocks').then(({ FormatQuickBar }) => { import('@blocksuite/blocks').then(({ FormatQuickBar }) => {
FormatQuickBar.customElements.push((_page, getSelection) => { FormatQuickBar.customElements.push((_page, getSelection) => {
const div = document.createElement('div'); const div = document.createElement('div');

View File

@@ -1,5 +1,3 @@
import '@affine/env/config';
import { definePlugin } from '@toeverything/plugin-infra/manager'; import { definePlugin } from '@toeverything/plugin-infra/manager';
import { ReleaseStage } from '@toeverything/plugin-infra/type'; import { ReleaseStage } from '@toeverything/plugin-infra/type';
@@ -22,6 +20,7 @@ definePlugin(
}, },
stage: ReleaseStage.NIGHTLY, stage: ReleaseStage.NIGHTLY,
version: '0.0.1', version: '0.0.1',
commands: [],
}, },
{ {
load: () => import('./UI/index'), load: () => import('./UI/index'),

View File

@@ -3,7 +3,6 @@
"include": ["./src"], "include": ["./src"],
"compilerOptions": { "compilerOptions": {
"noEmit": false, "noEmit": false,
"composite": true,
"outDir": "lib" "outDir": "lib"
}, },
"references": [ "references": [

View File

@@ -2,7 +2,8 @@
packages=( packages=(
"y-indexeddb" "y-indexeddb"
"infra" "infra",
"plugin-infra"
) )
for package in "${packages[@]}"; do for package in "${packages[@]}"; do

View File

@@ -1,6 +1,18 @@
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'child_process';
import { beforeAll } from 'vitest'; import { beforeAll } from 'vitest';
const rootDir = fileURLToPath(new URL('../../', import.meta.url));
beforeAll(async () => { beforeAll(async () => {
console.log('Build plugin infra');
spawnSync('yarn', ['build'], {
stdio: 'inherit',
cwd: resolve(rootDir, './packages/plugin-infra'),
});
console.log('Build plugins'); console.log('Build plugins');
// @ts-expect-error // @ts-expect-error
await import('../../apps/electron/scripts/plugins/build-plugins.mjs'); await import('../../apps/electron/scripts/plugins/build-plugins.mjs');

View File

@@ -106,6 +106,17 @@
{ {
"path": "./packages/debug" "path": "./packages/debug"
}, },
// Plugins
{
"path": "./packages/plugin-infra"
},
{
"path": "./plugins/bookmark-block"
},
{
"path": "./plugins/copilot"
},
// Tests // Tests
{ {
"path": "./tests" "path": "./tests"
@@ -129,12 +140,7 @@
{ {
"path": "./packages/y-indexeddb" "path": "./packages/y-indexeddb"
}, },
{
"path": "./packages/plugin-infra"
},
{
"path": "./plugins/copilot"
},
{ {
"path": "./tests/fixtures" "path": "./tests/fixtures"
}, },

View File

@@ -34,6 +34,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@affine/bookmark-block@workspace:plugins/bookmark-block" resolution: "@affine/bookmark-block@workspace:plugins/bookmark-block"
dependencies: dependencies:
"@affine/component": "workspace:*"
"@toeverything/plugin-infra": "workspace:*" "@toeverything/plugin-infra": "workspace:*"
foxact: ^0.2.7 foxact: ^0.2.7
link-preview-js: ^3.0.4 link-preview-js: ^3.0.4
@@ -86,6 +87,7 @@ __metadata:
"@radix-ui/react-radio-group": ^1.1.3 "@radix-ui/react-radio-group": ^1.1.3
"@radix-ui/react-toast": ^1.1.4 "@radix-ui/react-toast": ^1.1.4
"@toeverything/hooks": "workspace:*" "@toeverything/hooks": "workspace:*"
"@toeverything/plugin-infra": "workspace:*"
"@toeverything/theme": ^0.6.1 "@toeverything/theme": ^0.6.1
"@types/react": ^18.2.6 "@types/react": ^18.2.6
"@types/react-datepicker": ^4.11.2 "@types/react-datepicker": ^4.11.2
@@ -121,6 +123,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@affine/copilot@workspace:plugins/copilot" resolution: "@affine/copilot@workspace:plugins/copilot"
dependencies: dependencies:
"@affine/component": "workspace:*"
"@toeverything/plugin-infra": "workspace:*" "@toeverything/plugin-infra": "workspace:*"
"@types/marked": ^5.0.0 "@types/marked": ^5.0.0
"@types/react": ^18.2.6 "@types/react": ^18.2.6
@@ -162,8 +165,10 @@ __metadata:
"@electron-forge/shared-types": ^6.1.1 "@electron-forge/shared-types": ^6.1.1
"@electron/remote": 2.0.9 "@electron/remote": 2.0.9
"@toeverything/infra": "workspace:*" "@toeverything/infra": "workspace:*"
"@toeverything/plugin-infra": "workspace:*"
"@types/fs-extra": ^11.0.1 "@types/fs-extra": ^11.0.1
"@types/uuid": ^9.0.1 "@types/uuid": ^9.0.1
async-call-rpc: ^6.3.1
cheerio: ^1.0.0-rc.12 cheerio: ^1.0.0-rc.12
cross-env: 7.0.3 cross-env: 7.0.3
electron: =25.0.1 electron: =25.0.1
@@ -441,6 +446,7 @@ __metadata:
"@affine/debug": "workspace:*" "@affine/debug": "workspace:*"
"@affine/env": "workspace:*" "@affine/env": "workspace:*"
"@toeverything/hooks": "workspace:*" "@toeverything/hooks": "workspace:*"
"@toeverything/plugin-infra": "workspace:*"
"@toeverything/y-indexeddb": "workspace:*" "@toeverything/y-indexeddb": "workspace:*"
"@types/ws": ^8.5.4 "@types/ws": ^8.5.4
firebase: ^9.22.1 firebase: ^9.22.1
@@ -9080,15 +9086,14 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@toeverything/plugin-infra@workspace:packages/plugin-infra" resolution: "@toeverything/plugin-infra@workspace:packages/plugin-infra"
dependencies: dependencies:
"@affine/component": "workspace:*"
"@affine/env": "workspace:*"
"@affine/workspace": "workspace:*"
"@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/blocks": 0.0.0-20230607055421-9b20fcaf-nightly
"@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/editor": 0.0.0-20230607055421-9b20fcaf-nightly
"@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/global": 0.0.0-20230607055421-9b20fcaf-nightly
"@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/lit": 0.0.0-20230607055421-9b20fcaf-nightly
"@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly "@blocksuite/store": 0.0.0-20230607055421-9b20fcaf-nightly
jotai: ^2.1.1 jotai: ^2.1.1
vite: ^4.3.9
vite-plugin-dts: ^2.3.0
peerDependencies: peerDependencies:
"@blocksuite/blocks": "*" "@blocksuite/blocks": "*"
"@blocksuite/editor": "*" "@blocksuite/editor": "*"
@@ -11332,6 +11337,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"async-call-rpc@npm:^6.3.1":
version: 6.3.1
resolution: "async-call-rpc@npm:6.3.1"
checksum: 024e1bab4752b3aa66145977d534e6dc4c316e1d746c11a6a5076b30b16d42fa0ce4793be5a330319644f1335b01cbe13ee4ac28454cded37b1c803608400934
languageName: node
linkType: hard
"async-limiter@npm:~1.0.0": "async-limiter@npm:~1.0.0":
version: 1.0.1 version: 1.0.1
resolution: "async-limiter@npm:1.0.1" resolution: "async-limiter@npm:1.0.1"