mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
refactor: plugin loading logic (#3448)
This commit is contained in:
44
.github/workflows/build.yml
vendored
44
.github/workflows/build.yml
vendored
@@ -110,6 +110,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Build Plugins
|
||||
run: yarn run build:plugins
|
||||
- name: Build Core
|
||||
run: yarn nx build @affine/core
|
||||
- name: Upload core artifact
|
||||
@@ -207,6 +209,48 @@ jobs:
|
||||
run: |
|
||||
yarn exec concurrently -k -s first -n "SB,TEST" -c "magenta,blue" "yarn exec serve ./storybook-static -l 6006" "yarn exec wait-on tcp:6006 && yarn test"
|
||||
|
||||
e2e-plugin-test:
|
||||
name: E2E Plugin Test
|
||||
runs-on: ubuntu-latest
|
||||
environment: development
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
- name: Download core artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: core
|
||||
path: ./apps/core/dist
|
||||
- name: Run playwright tests
|
||||
run: yarn e2e --forbid-only
|
||||
working-directory: tests/affine-plugin
|
||||
env:
|
||||
COVERAGE: true
|
||||
- name: Collect code coverage report
|
||||
run: yarn exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov
|
||||
|
||||
- name: Upload e2e test coverage results
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./.coverage/lcov.info
|
||||
flags: e2e-plugin-test
|
||||
name: affine
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: test-results-e2e-plugin
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"dependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
"@affine/component": "workspace:*",
|
||||
"@affine/copilot": "workspace:*",
|
||||
"@affine/debug": "workspace:*",
|
||||
"@affine/env": "workspace:*",
|
||||
"@affine/graphql": "workspace:*",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import 'ses';
|
||||
|
||||
import * as AFFiNEComponent from '@affine/component';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { FormatQuickBar } from '@blocksuite/blocks';
|
||||
import * as BlockSuiteBlocksStd from '@blocksuite/blocks/std';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
@@ -40,6 +41,14 @@ if (!process.env.COVERAGE) {
|
||||
});
|
||||
}
|
||||
|
||||
const builtinPluginUrl = new Set([
|
||||
'/plugins/bookmark',
|
||||
'/plugins/copilot',
|
||||
'/plugins/hello-world',
|
||||
]);
|
||||
|
||||
const logger = new DebugLogger('register-plugins');
|
||||
|
||||
const PluginProvider = ({ children }: PropsWithChildren) =>
|
||||
React.createElement(
|
||||
Provider,
|
||||
@@ -142,95 +151,127 @@ const createGlobalThis = () => {
|
||||
};
|
||||
|
||||
const group = new DisposableGroup();
|
||||
const pluginList = (await (
|
||||
await fetch(new URL(`./plugins/plugin-list.json`, window.location.origin))
|
||||
).json()) as { name: string; assets: string[]; release: boolean }[];
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var __pluginPackageJson__: unknown[];
|
||||
}
|
||||
|
||||
globalThis.__pluginPackageJson__ = [];
|
||||
|
||||
interface PluginLoadedEvent extends CustomEvent<{ plugins: unknown[] }> {}
|
||||
// add to window
|
||||
declare global {
|
||||
interface WindowEventMap {
|
||||
'plugin-loaded': PluginLoadedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
pluginList.map(({ name: plugin, release, assets }) => {
|
||||
if (!release && process.env.NODE_ENV !== 'development') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const pluginCompartment = new Compartment(createGlobalThis(), {});
|
||||
const pluginGlobalThis = pluginCompartment.globalThis;
|
||||
const baseURL = new URL(`./plugins/${plugin}/`, window.location.origin);
|
||||
const entryURL = new URL('index.js', baseURL);
|
||||
rootStore.set(registeredPluginAtom, prev => [...prev, plugin]);
|
||||
return fetch(entryURL).then(async res => {
|
||||
if (assets.length > 0) {
|
||||
await Promise.all(
|
||||
assets.map(async asset => {
|
||||
if (asset.endsWith('.css')) {
|
||||
const res = await fetch(new URL(asset, baseURL));
|
||||
if (res.ok) {
|
||||
// todo: how to put css file into sandbox?
|
||||
return res.text().then(text => {
|
||||
const style = document.createElement('style');
|
||||
style.setAttribute('plugin-id', plugin);
|
||||
style.textContent = text;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
const codeText = await res.text();
|
||||
pluginCompartment.evaluate(codeText, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
pluginGlobalThis.__INTERNAL__ENTRY = {
|
||||
register: (part, callback) => {
|
||||
if (part === 'headerItem') {
|
||||
rootStore.set(headerItemsAtom, items => ({
|
||||
...items,
|
||||
[plugin]: callback as CallbackMap['headerItem'],
|
||||
}));
|
||||
} else if (part === 'editor') {
|
||||
rootStore.set(editorItemsAtom, items => ({
|
||||
...items,
|
||||
[plugin]: callback as CallbackMap['editor'],
|
||||
}));
|
||||
} else if (part === 'window') {
|
||||
rootStore.set(windowItemsAtom, items => ({
|
||||
...items,
|
||||
[plugin]: callback as CallbackMap['window'],
|
||||
}));
|
||||
} else if (part === 'setting') {
|
||||
console.log('setting');
|
||||
rootStore.set(settingItemsAtom, items => ({
|
||||
...items,
|
||||
[plugin]: callback as CallbackMap['setting'],
|
||||
}));
|
||||
} else if (part === 'formatBar') {
|
||||
console.log('1');
|
||||
FormatQuickBar.customElements.push((page, getBlockRange) => {
|
||||
console.log('2');
|
||||
const div = document.createElement('div');
|
||||
(callback as CallbackMap['formatBar'])(div, page, getBlockRange);
|
||||
return div;
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unknown part: ${part}`);
|
||||
[...builtinPluginUrl].map(url => {
|
||||
return fetch(`${url}/package.json`)
|
||||
.then(async res => {
|
||||
const packageJson = await res.json();
|
||||
const {
|
||||
name: pluginName,
|
||||
affinePlugin: {
|
||||
release,
|
||||
entry: { core },
|
||||
assets,
|
||||
},
|
||||
} = packageJson;
|
||||
globalThis.__pluginPackageJson__.push(packageJson);
|
||||
logger.debug(`registering plugin ${pluginName}`);
|
||||
logger.debug(`package.json: ${packageJson}`);
|
||||
if (!release) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const pluginCompartment = new Compartment(createGlobalThis(), {});
|
||||
const pluginGlobalThis = pluginCompartment.globalThis;
|
||||
const baseURL = url;
|
||||
const entryURL = `${baseURL}/${core}`;
|
||||
rootStore.set(registeredPluginAtom, prev => [...prev, pluginName]);
|
||||
await fetch(entryURL).then(async res => {
|
||||
if (assets.length > 0) {
|
||||
await Promise.all(
|
||||
assets.map(async (asset: string) => {
|
||||
if (asset.endsWith('.css')) {
|
||||
const res = await fetch(`${baseURL}/${asset}`);
|
||||
if (res.ok) {
|
||||
// todo: how to put css file into sandbox?
|
||||
return res.text().then(text => {
|
||||
const style = document.createElement('style');
|
||||
style.setAttribute('plugin-id', pluginName);
|
||||
style.textContent = text;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
PluginProvider,
|
||||
},
|
||||
} satisfies PluginContext;
|
||||
const dispose = pluginCompartment.evaluate(
|
||||
'exports.entry(__INTERNAL__ENTRY)'
|
||||
);
|
||||
if (typeof dispose !== 'function') {
|
||||
throw new Error('Plugin entry must return a function');
|
||||
}
|
||||
pluginGlobalThis.__INTERNAL__ENTRY = undefined;
|
||||
group.add(dispose);
|
||||
});
|
||||
const codeText = await res.text();
|
||||
pluginCompartment.evaluate(codeText, {
|
||||
__evadeHtmlCommentTest__: true,
|
||||
});
|
||||
pluginGlobalThis.__INTERNAL__ENTRY = {
|
||||
register: (part, callback) => {
|
||||
logger.info(`Registering ${pluginName} to ${part}`);
|
||||
if (part === 'headerItem') {
|
||||
rootStore.set(headerItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['headerItem'],
|
||||
}));
|
||||
} else if (part === 'editor') {
|
||||
rootStore.set(editorItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['editor'],
|
||||
}));
|
||||
} else if (part === 'window') {
|
||||
rootStore.set(windowItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['window'],
|
||||
}));
|
||||
} else if (part === 'setting') {
|
||||
rootStore.set(settingItemsAtom, items => ({
|
||||
...items,
|
||||
[pluginName]: callback as CallbackMap['setting'],
|
||||
}));
|
||||
} else if (part === 'formatBar') {
|
||||
FormatQuickBar.customElements.push((page, getBlockRange) => {
|
||||
const div = document.createElement('div');
|
||||
(callback as CallbackMap['formatBar'])(
|
||||
div,
|
||||
page,
|
||||
getBlockRange
|
||||
);
|
||||
return div;
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unknown part: ${part}`);
|
||||
}
|
||||
},
|
||||
utils: {
|
||||
PluginProvider,
|
||||
},
|
||||
} satisfies PluginContext;
|
||||
const dispose = pluginCompartment.evaluate(
|
||||
'exports.entry(__INTERNAL__ENTRY)'
|
||||
);
|
||||
if (typeof dispose !== 'function') {
|
||||
throw new Error('Plugin entry must return a function');
|
||||
}
|
||||
pluginGlobalThis.__INTERNAL__ENTRY = undefined;
|
||||
group.add(dispose);
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(`error when fetch plugin from ${url}`, e);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
console.log('register plugins finished');
|
||||
).then(() => {
|
||||
console.info('All plugins loaded');
|
||||
});
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"tests/fixtures",
|
||||
"tests/kit",
|
||||
"tests/affine-legacy/*",
|
||||
"tests/affine-local"
|
||||
"tests/affine-local",
|
||||
"tests/affine-plugin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18.16.1 <19.0.0"
|
||||
@@ -20,7 +21,6 @@
|
||||
"scripts": {
|
||||
"dev": "dev-core",
|
||||
"dev:electron": "yarn workspace @affine/electron dev:app",
|
||||
"dev:plugins": "./apps/electron/scripts/plugins/dev-plugins.mjs",
|
||||
"build": "yarn nx build @affine/core",
|
||||
"build:electron": "yarn nx build @affine/electron",
|
||||
"build:storage": "yarn nx run-many -t build -p @affine/storage",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ok } from 'node:assert';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { mkdir, open, readFile } from 'node:fs/promises';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
|
||||
@@ -109,8 +108,6 @@ const serverOutDir = path.resolve(
|
||||
plugin
|
||||
);
|
||||
|
||||
const pluginListJsonPath = path.resolve(outDir, 'plugin-list.json');
|
||||
|
||||
const coreEntry = path.resolve(pluginDir, json.affinePlugin.entry.core);
|
||||
if (json.affinePlugin.entry.server) {
|
||||
const serverEntry = path.resolve(pluginDir, json.affinePlugin.entry.server);
|
||||
@@ -162,41 +159,23 @@ await build({
|
||||
vanillaExtractPlugin(),
|
||||
react(),
|
||||
{
|
||||
name: 'generate-list-json',
|
||||
name: 'generate-package.json',
|
||||
async generateBundle() {
|
||||
if (!existsSync(outDir)) {
|
||||
await mkdir(outDir, { recursive: true });
|
||||
}
|
||||
const file = await open(pluginListJsonPath, 'as+', 0o777);
|
||||
const txt = await file.readFile({
|
||||
encoding: 'utf-8',
|
||||
const packageJson = {
|
||||
name: json.name,
|
||||
affinePlugin: {
|
||||
release: json.affinePlugin.release,
|
||||
entry: {
|
||||
core: 'index.js',
|
||||
},
|
||||
assets: [...metadata.assets],
|
||||
},
|
||||
};
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: 'package.json',
|
||||
source: JSON.stringify(packageJson, null, 2),
|
||||
});
|
||||
if (!txt) {
|
||||
console.log('generate plugin-list.json');
|
||||
await file.write(
|
||||
JSON.stringify([
|
||||
{
|
||||
release: json.affinePlugin.release,
|
||||
name: plugin,
|
||||
assets: [...metadata.assets],
|
||||
},
|
||||
])
|
||||
);
|
||||
} else {
|
||||
console.log('modify plugin-list.json');
|
||||
const list = JSON.parse(txt);
|
||||
const index = list.findIndex((item: any) => item.name === plugin);
|
||||
if (index === -1) {
|
||||
list.push({
|
||||
release: json.affinePlugin.release,
|
||||
name: plugin,
|
||||
assets: [...metadata.assets],
|
||||
});
|
||||
} else {
|
||||
list[index].assets = [...metadata.assets];
|
||||
}
|
||||
await file.write(JSON.stringify(list), 0);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@affine/copilot",
|
||||
"name": "@affine/copilot-plugin",
|
||||
"private": true,
|
||||
"affinePlugin": {
|
||||
"release": false,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IconButton } from '@affine/component';
|
||||
import { SendIcon } from '@blocksuite/icons';
|
||||
import { contentLayoutAtom } from '@toeverything/plugin-infra/atom';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import { Suspense, useCallback, useState } from 'react';
|
||||
@@ -62,11 +61,7 @@ const DetailContentImpl = () => {
|
||||
};
|
||||
|
||||
export const DetailContent = (): ReactElement => {
|
||||
const layout = useAtomValue(contentLayoutAtom);
|
||||
const key = useAtomValue(openAIApiKeyAtom);
|
||||
if (layout === 'editor' || layout.second !== 'copilot') {
|
||||
return <></>;
|
||||
}
|
||||
if (!key) {
|
||||
return <span>Please set OpenAI API Key in the debug panel.</span>;
|
||||
}
|
||||
|
||||
@@ -11,12 +11,13 @@ export const HeaderItem = (): ReactElement => {
|
||||
<IconButton
|
||||
onClick={useCallback(
|
||||
() =>
|
||||
// todo: abstract a context function to open a new tab
|
||||
setLayout(layout => {
|
||||
if (layout === 'editor') {
|
||||
return {
|
||||
direction: 'horizontal',
|
||||
first: 'editor',
|
||||
second: 'copilot',
|
||||
second: '@affine/copilot',
|
||||
splitPercentage: 70,
|
||||
};
|
||||
} else {
|
||||
|
||||
31
tests/affine-plugin/e2e/basic.spec.ts
Normal file
31
tests/affine-plugin/e2e/basic.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { test } from '@affine-test/kit/playwright';
|
||||
import { openHomePage } from '@affine-test/kit/utils/load-page';
|
||||
import { waitEditorLoad } from '@affine-test/kit/utils/page-logic';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
test('plugin should exist', async ({ page }) => {
|
||||
await openHomePage(page);
|
||||
await waitEditorLoad(page);
|
||||
await page.route('**/plugins/**/package.json', route => route.fetch(), {
|
||||
times: 3,
|
||||
});
|
||||
await page.waitForTimeout(50);
|
||||
const packageJson = await page.evaluate(() => {
|
||||
// @ts-expect-error
|
||||
return window.__pluginPackageJson__;
|
||||
});
|
||||
expect(packageJson).toEqual([
|
||||
{
|
||||
name: '@affine/bookmark-plugin',
|
||||
affinePlugin: expect.anything(),
|
||||
},
|
||||
{
|
||||
name: '@affine/copilot-plugin',
|
||||
affinePlugin: expect.anything(),
|
||||
},
|
||||
{
|
||||
name: '@affine/hello-world-plugin',
|
||||
affinePlugin: expect.anything(),
|
||||
},
|
||||
]);
|
||||
});
|
||||
13
tests/affine-plugin/package.json
Normal file
13
tests/affine-plugin/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@affine-test/affine-plugin",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"e2e": "yarn playwright test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@affine-test/fixtures": "workspace:*",
|
||||
"@affine-test/kit": "workspace:*",
|
||||
"@playwright/test": "^1.36.2"
|
||||
},
|
||||
"version": "0.7.0-canary.59"
|
||||
}
|
||||
63
tests/affine-plugin/playwright.config.ts
Normal file
63
tests/affine-plugin/playwright.config.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type {
|
||||
PlaywrightTestConfig,
|
||||
PlaywrightWorkerOptions,
|
||||
} from '@playwright/test';
|
||||
// import { devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: './e2e',
|
||||
fullyParallel: true,
|
||||
timeout: process.env.CI ? 50_000 : 30_000,
|
||||
use: {
|
||||
baseURL: 'http://localhost:8080/',
|
||||
browserName:
|
||||
(process.env.BROWSER as PlaywrightWorkerOptions['browserName']) ??
|
||||
'chromium',
|
||||
permissions: ['clipboard-read', 'clipboard-write'],
|
||||
viewport: { width: 1440, height: 800 },
|
||||
actionTimeout: 5 * 1000,
|
||||
locale: 'en-US',
|
||||
// Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer
|
||||
// You can open traces locally(`npx playwright show-trace trace.zip`)
|
||||
// or in your browser on [Playwright Trace Viewer](https://trace.playwright.dev/).
|
||||
trace: 'on-first-retry',
|
||||
// Record video only when retrying a test for the first time.
|
||||
video: 'on-first-retry',
|
||||
},
|
||||
forbidOnly: !!process.env.CI,
|
||||
workers: 4,
|
||||
retries: 1,
|
||||
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
|
||||
// default 'list' when running locally
|
||||
// See https://playwright.dev/docs/test-reporters#github-actions-annotations
|
||||
reporter: process.env.CI ? 'github' : 'list',
|
||||
|
||||
webServer: [
|
||||
// Intentionally not building the web, reminds you to run it by yourself.
|
||||
{
|
||||
command: 'yarn run start:web-static',
|
||||
port: 8080,
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
env: {
|
||||
COVERAGE: process.env.COVERAGE || 'false',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (process.env.CI) {
|
||||
config.retries = 3;
|
||||
config.workers = '50%';
|
||||
}
|
||||
|
||||
export default config;
|
||||
16
tests/affine-plugin/tsconfig.json
Normal file
16
tests/affine-plugin/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["e2e"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../kit"
|
||||
},
|
||||
{
|
||||
"path": "../fixtures"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -155,6 +155,9 @@
|
||||
{
|
||||
"path": "./tests/affine-local"
|
||||
},
|
||||
{
|
||||
"path": "./tests/affine-plugin"
|
||||
},
|
||||
{
|
||||
"path": "./tests/affine-legacy/0.7.0-canary.18"
|
||||
}
|
||||
|
||||
15
yarn.lock
15
yarn.lock
@@ -48,6 +48,16 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@affine-test/affine-plugin@workspace:tests/affine-plugin":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@affine-test/affine-plugin@workspace:tests/affine-plugin"
|
||||
dependencies:
|
||||
"@affine-test/fixtures": "workspace:*"
|
||||
"@affine-test/kit": "workspace:*"
|
||||
"@playwright/test": ^1.36.2
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@affine-test/fixtures@workspace:*, @affine-test/fixtures@workspace:tests/fixtures":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@affine-test/fixtures@workspace:tests/fixtures"
|
||||
@@ -155,9 +165,9 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@affine/copilot@workspace:*, @affine/copilot@workspace:plugins/copilot":
|
||||
"@affine/copilot-plugin@workspace:plugins/copilot":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@affine/copilot@workspace:plugins/copilot"
|
||||
resolution: "@affine/copilot-plugin@workspace:plugins/copilot"
|
||||
dependencies:
|
||||
"@affine/component": "workspace:*"
|
||||
"@toeverything/plugin-infra": "workspace:*"
|
||||
@@ -183,7 +193,6 @@ __metadata:
|
||||
dependencies:
|
||||
"@affine-test/fixtures": "workspace:*"
|
||||
"@affine/component": "workspace:*"
|
||||
"@affine/copilot": "workspace:*"
|
||||
"@affine/debug": "workspace:*"
|
||||
"@affine/env": "workspace:*"
|
||||
"@affine/graphql": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user