Skip to content

Commit d288b70

Browse files
committed
Add tool definitions operations and schemas
1 parent 9b5d06a commit d288b70

File tree

96 files changed

+8823
-94
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+8823
-94
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ coverage
2424
typedoc
2525
storybook-static
2626

27+
# Generated documentation
28+
packages/lexical/docs/
29+
packages/react/docs/
30+
2731
**/*.vsix
2832

2933
*.exclude.*

.prettierignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,7 @@ patches/
5757

5858
# Markdown files with specific formatting
5959
CHANGELOG.md
60+
61+
# Generated documentation
62+
packages/lexical/docs/
63+
packages/react/docs/

eslint.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ module.exports = tseslint.config(
163163
'**/empty-shim.js',
164164
'packages/ipyreactive/**',
165165
'packages/ipyscript/**',
166+
// Generated documentation
167+
'packages/lexical/docs/**',
168+
'packages/react/docs/**',
166169
],
167170
},
168171
);

packages/lexical/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
"access": "public"
3030
},
3131
"scripts": {
32-
"build": "gulp resources-to-lib && tsc && webpack && npm run build:lib",
33-
"build:lib": "tsc",
34-
"build:prod": "gulp resources-to-lib && tsc && jlpm clean && jlpm build:lib",
32+
"build": "npm run validate:tools && gulp resources-to-lib && tsc && webpack && npm run build:lib",
33+
"build:lib": "npm run validate:tools && tsc",
34+
"build:prod": "npm run validate:tools && gulp resources-to-lib && tsc && jlpm clean && jlpm build:lib",
3535
"build:tsc:watch": "run-p 'build:tsc:watch:*'",
3636
"build:tsc:watch:res": "gulp resources-to-lib-watch",
3737
"build:tsc:watch:tsc": "tsc --watch",
@@ -57,12 +57,13 @@
5757
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
5858
"test": "jest --coverage",
5959
"typedoc": "typedoc ./src",
60+
"validate:tools": "node scripts/validate-tools-sync.js",
6061
"watch": "run-p watch:src",
6162
"watch:src": "tsc -w"
6263
},
6364
"dependencies": {
6465
"@datalayer/icons-react": "^1.0.0",
65-
"@datalayer/jupyter-react": "^1.1.5",
66+
"@datalayer/jupyter-react": "^1.1.8",
6667
"@datalayer/primer-addons": "^1.0.4",
6768
"@jupyterlab/application": "^4.0.0",
6869
"@jupyterlab/coreutils": "^6.0.0",
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env node
2+
/*
3+
* Copyright (c) 2021-2023 Datalayer, Inc.
4+
*
5+
* MIT License
6+
*/
7+
8+
/**
9+
* Validation script to ensure tool definitions and operations stay in sync.
10+
*
11+
* Checks:
12+
* 1. Every definition has a corresponding operation
13+
* 2. Every operation has a corresponding definition
14+
* 3. Tool names match between definition and operation
15+
* 4. No orphaned files
16+
*
17+
* Usage: npm run validate:tools
18+
*/
19+
20+
const fs = require('fs');
21+
const path = require('path');
22+
23+
const TOOLS_DIR = path.join(__dirname, '../src/tools');
24+
const DEFINITIONS_DIR = path.join(TOOLS_DIR, 'definitions');
25+
const OPERATIONS_DIR = path.join(TOOLS_DIR, 'operations');
26+
27+
function getToolFiles(dir) {
28+
const files = fs.readdirSync(dir);
29+
return files
30+
.filter(f => f.endsWith('.ts') && f !== 'index.ts')
31+
.map(f => ({
32+
name: f.replace('.ts', ''),
33+
path: path.join(dir, f),
34+
}));
35+
}
36+
37+
function extractToolName(filePath) {
38+
const content = fs.readFileSync(filePath, 'utf-8');
39+
40+
// For definitions: export const xxxTool
41+
const defMatch = content.match(/export const (\w+)Tool:/);
42+
if (defMatch) {
43+
return defMatch[1]; // e.g., "insertBlock" from "insertBlockTool"
44+
}
45+
46+
// For operations: export const xxxOperation
47+
const opMatch = content.match(/export const (\w+)Operation:/);
48+
if (opMatch) {
49+
return opMatch[1]; // e.g., "insertBlock" from "insertBlockOperation"
50+
}
51+
52+
return null;
53+
}
54+
55+
function main() {
56+
console.log(
57+
'🔍 Validating lexical tool definitions and operations sync...\n',
58+
);
59+
60+
const definitions = getToolFiles(DEFINITIONS_DIR);
61+
const operations = getToolFiles(OPERATIONS_DIR);
62+
63+
console.log(`Found ${definitions.length} definitions:`);
64+
definitions.forEach(d => console.log(` - ${d.name}`));
65+
console.log();
66+
67+
console.log(`Found ${operations.length} operations:`);
68+
operations.forEach(o => console.log(` - ${o.name}`));
69+
console.log();
70+
71+
let hasErrors = false;
72+
73+
// Check 1: File name alignment
74+
const defNames = new Set(definitions.map(d => d.name));
75+
const opNames = new Set(operations.map(o => o.name));
76+
77+
// Find definitions without operations
78+
const missingOps = definitions.filter(d => !opNames.has(d.name));
79+
if (missingOps.length > 0) {
80+
console.error('❌ Definitions without matching operations:');
81+
missingOps.forEach(d => console.error(` ${d.name}`));
82+
hasErrors = true;
83+
}
84+
85+
// Find operations without definitions
86+
const missingDefs = operations.filter(o => !defNames.has(o.name));
87+
if (missingDefs.length > 0) {
88+
console.error('❌ Operations without matching definitions:');
89+
missingDefs.forEach(o => console.error(` ${o.name}`));
90+
hasErrors = true;
91+
}
92+
93+
// Check 2: Tool name extraction from file content
94+
console.log('\n📝 Checking exported names...');
95+
for (const def of definitions) {
96+
const toolName = extractToolName(def.path);
97+
if (!toolName) {
98+
console.error(`❌ Could not extract tool name from ${def.name}`);
99+
hasErrors = true;
100+
} else if (toolName !== def.name) {
101+
console.error(
102+
`❌ File name mismatch: ${def.name}.ts exports ${toolName}Tool`,
103+
);
104+
hasErrors = true;
105+
}
106+
}
107+
108+
for (const op of operations) {
109+
const toolName = extractToolName(op.path);
110+
if (!toolName) {
111+
console.error(`❌ Could not extract tool name from ${op.name}`);
112+
hasErrors = true;
113+
} else if (toolName !== op.name) {
114+
console.error(
115+
`❌ File name mismatch: ${op.name}.ts exports ${toolName}Operation`,
116+
);
117+
hasErrors = true;
118+
}
119+
}
120+
121+
// Summary
122+
console.log('\n' + '='.repeat(60));
123+
if (hasErrors) {
124+
console.error('❌ Validation FAILED - lexical tools are out of sync!');
125+
process.exit(1);
126+
} else {
127+
console.log('✅ Validation PASSED - all lexical tools are in sync!');
128+
console.log(`\n ${definitions.length} definition(s)`);
129+
console.log(` ${operations.length} operation(s)`);
130+
console.log(' Perfect 1:1 alignment! 🎉');
131+
}
132+
}
133+
134+
main();
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (c) 2021-2023 Datalayer, Inc.
3+
*
4+
* MIT License
5+
*/
6+
7+
/**
8+
* Context for Lexical configuration (lexicalId).
9+
* Provides the lexicalId to child components and plugins.
10+
*
11+
* @module context/LexicalConfigContext
12+
*/
13+
14+
import { createContext, useContext } from 'react';
15+
16+
/**
17+
* Configuration passed down to Lexical plugins
18+
*/
19+
export interface LexicalConfig {
20+
/** Unique identifier for this Lexical document */
21+
lexicalId: string;
22+
/** Service manager for kernel operations (optional) */
23+
serviceManager?: any;
24+
}
25+
26+
/**
27+
* Context for Lexical configuration
28+
*/
29+
const LexicalConfigContext = createContext<LexicalConfig | undefined>(
30+
undefined,
31+
);
32+
33+
/**
34+
* Hook to access Lexical configuration
35+
*
36+
* @throws {Error} If used outside of LexicalConfigProvider
37+
*/
38+
export function useLexicalConfig(): LexicalConfig {
39+
const config = useContext(LexicalConfigContext);
40+
if (!config) {
41+
throw new Error(
42+
'useLexicalConfig must be used within a LexicalConfigProvider',
43+
);
44+
}
45+
return config;
46+
}
47+
48+
/**
49+
* Provider component for Lexical configuration
50+
*
51+
* @example
52+
* ```tsx
53+
* <LexicalConfigProvider lexicalId="doc-123" serviceManager={sm}>
54+
* <Editor />
55+
* </LexicalConfigProvider>
56+
* ```
57+
*/
58+
export interface LexicalConfigProviderProps {
59+
lexicalId: string;
60+
serviceManager?: any;
61+
children: React.ReactNode;
62+
}
63+
64+
export function LexicalConfigProvider({
65+
lexicalId,
66+
serviceManager,
67+
children,
68+
}: LexicalConfigProviderProps) {
69+
return (
70+
<LexicalConfigContext.Provider value={{ lexicalId, serviceManager }}>
71+
{children}
72+
</LexicalConfigContext.Provider>
73+
);
74+
}

packages/lexical/src/context/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* MIT License
55
*/
66

7+
export * from './LexicalConfigContext';
78
export * from './LexicalContext';
89
export * from './SettingsContext';
910
export * from './ToolbarContext';

packages/lexical/src/editor/Editor.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,14 @@ import { TreeViewPlugin } from '../plugins';
6262
import { OnSessionConnection } from '@datalayer/jupyter-react';
6363
import { ToolbarPlugin } from '../plugins/ToolbarPlugin';
6464
import { ToolbarContext } from '../context/ToolbarContext';
65-
66-
import './../../style/index.css';
65+
import { LexicalConfigProvider } from '../context/LexicalConfigContext';
66+
import { LexicalStatePlugin } from '../plugins/LexicalStatePlugin';
6767

6868
type Props = {
69+
/** Unique identifier for this Lexical document (required for tool operations) */
70+
id?: string;
71+
/** Service manager for kernel operations (required when id is provided) */
72+
serviceManager?: any;
6973
notebook?: INotebookContent;
7074
onSessionConnection?: OnSessionConnection;
7175
};
@@ -115,7 +119,7 @@ const EditorContextPlugin = () => {
115119
};
116120

117121
export function EditorContainer(props: Props) {
118-
const { notebook, onSessionConnection } = props;
122+
const { id, notebook, onSessionConnection } = props;
119123
const { defaultKernel } = useJupyter();
120124
const [editor] = useLexicalComposerContext();
121125
const [activeEditor, setActiveEditor] = useState(editor);
@@ -156,6 +160,7 @@ export function EditorContainer(props: Props) {
156160
<HistoryPlugin />
157161
<TreeViewPlugin />
158162
<AutoFocusPlugin />
163+
{id && <LexicalStatePlugin />}
159164
<TablePlugin />
160165
<ListPlugin />
161166
<CheckListPlugin />
@@ -195,7 +200,10 @@ export function EditorContainer(props: Props) {
195200
}
196201

197202
export function Editor(props: Props) {
198-
return (
203+
const { id, serviceManager } = props;
204+
205+
// Wrap with LexicalConfigProvider if id is provided (for tool operations)
206+
const content = (
199207
<LexicalComposer initialConfig={initialConfig}>
200208
<ToolbarContext>
201209
<div className="editor-shell">
@@ -204,6 +212,15 @@ export function Editor(props: Props) {
204212
</ToolbarContext>
205213
</LexicalComposer>
206214
);
215+
216+
// Only wrap with config provider if id is provided
217+
return id ? (
218+
<LexicalConfigProvider lexicalId={id} serviceManager={serviceManager}>
219+
{content}
220+
</LexicalConfigProvider>
221+
) : (
222+
content
223+
);
207224
}
208225

209226
export default Editor;

packages/lexical/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export * from './hooks';
1212
export * from './nodes';
1313
export * from './plugins';
1414
export * from './themes';
15+
export * from './tools';
1516
export * from './utils';

0 commit comments

Comments
 (0)