@checkstack/ui 0.5.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/package.json +3 -10
  3. package/src/components/CodeEditor/CodeEditor.tsx +14 -420
  4. package/src/components/CodeEditor/MonacoEditor.tsx +530 -0
  5. package/src/components/CodeEditor/generateTypeDefinitions.ts +169 -0
  6. package/src/components/CodeEditor/index.ts +4 -3
  7. package/src/components/CodeEditor/templateUtils.test.ts +87 -0
  8. package/src/components/CodeEditor/templateUtils.ts +81 -0
  9. package/src/components/DynamicForm/FormField.tsx +13 -7
  10. package/src/components/DynamicForm/MultiTypeEditorField.tsx +33 -0
  11. package/src/components/DynamicForm/utils.ts +3 -0
  12. package/src/components/CodeEditor/languageSupport/enterBehavior.test.ts +0 -173
  13. package/src/components/CodeEditor/languageSupport/enterBehavior.ts +0 -35
  14. package/src/components/CodeEditor/languageSupport/index.ts +0 -22
  15. package/src/components/CodeEditor/languageSupport/json-utils.ts +0 -117
  16. package/src/components/CodeEditor/languageSupport/json.test.ts +0 -274
  17. package/src/components/CodeEditor/languageSupport/json.ts +0 -139
  18. package/src/components/CodeEditor/languageSupport/markdown-utils.ts +0 -65
  19. package/src/components/CodeEditor/languageSupport/markdown.test.ts +0 -245
  20. package/src/components/CodeEditor/languageSupport/markdown.ts +0 -134
  21. package/src/components/CodeEditor/languageSupport/types.ts +0 -48
  22. package/src/components/CodeEditor/languageSupport/xml-utils.ts +0 -94
  23. package/src/components/CodeEditor/languageSupport/xml.test.ts +0 -239
  24. package/src/components/CodeEditor/languageSupport/xml.ts +0 -116
  25. package/src/components/CodeEditor/languageSupport/yaml-utils.ts +0 -101
  26. package/src/components/CodeEditor/languageSupport/yaml.test.ts +0 -203
  27. package/src/components/CodeEditor/languageSupport/yaml.ts +0 -120
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @checkstack/ui
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - f676e11: Add script execution support and migrate CodeEditor to Monaco
8
+
9
+ **Integration providers** (`@checkstack/integration-script-backend`):
10
+
11
+ - **Script** - Execute TypeScript/JavaScript with context object
12
+ - **Bash** - Execute shell scripts with environment variables ($EVENT*ID, $PAYLOAD*\*)
13
+
14
+ **Health check collectors** (`@checkstack/healthcheck-script-backend`):
15
+
16
+ - **InlineScriptCollector** - Run TypeScript directly for health checks
17
+ - **ExecuteCollector** - Bash syntax highlighting for command field
18
+
19
+ **CodeEditor migration to Monaco** (`@checkstack/ui`):
20
+
21
+ - Replaced CodeMirror with Monaco Editor (VS Code's editor)
22
+ - Full TypeScript/JavaScript IntelliSense with custom type definitions
23
+ - Added `generateTypeDefinitions()` for JSON Schema → TypeScript conversion
24
+ - Removed all CodeMirror dependencies
25
+
26
+ **Type updates** (`@checkstack/common`):
27
+
28
+ - Added `javascript`, `typescript`, and `bash` to `EditorType` union
29
+
30
+ ### Patch Changes
31
+
32
+ - Updated dependencies [f676e11]
33
+ - @checkstack/common@0.6.2
34
+ - @checkstack/frontend-api@0.3.5
35
+
3
36
  ## 0.5.3
4
37
 
5
38
  ### Patch Changes
package/package.json CHANGED
@@ -1,31 +1,24 @@
1
1
  {
2
2
  "name": "@checkstack/ui",
3
- "version": "0.5.3",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "dependencies": {
7
7
  "@checkstack/common": "0.6.1",
8
8
  "@checkstack/frontend-api": "0.3.4",
9
- "@codemirror/autocomplete": "^6.20.0",
10
- "@codemirror/lang-json": "^6.0.2",
11
- "@codemirror/lang-markdown": "^6.5.0",
12
- "@codemirror/lang-xml": "^6.1.0",
13
- "@codemirror/lang-yaml": "^6.1.2",
14
- "@codemirror/language": "^6.12.1",
15
- "@codemirror/state": "^6.5.4",
16
- "@codemirror/view": "^6.39.11",
9
+ "@monaco-editor/react": "^4.7.0",
17
10
  "@radix-ui/react-accordion": "^1.2.12",
18
11
  "@radix-ui/react-dialog": "^1.1.15",
19
12
  "@radix-ui/react-popover": "^1.1.15",
20
13
  "@radix-ui/react-select": "^2.2.6",
21
14
  "@radix-ui/react-slot": "^1.2.4",
22
- "@uiw/react-codemirror": "^4.25.4",
23
15
  "ajv": "^8.17.1",
24
16
  "ajv-formats": "^3.0.1",
25
17
  "class-variance-authority": "^0.7.1",
26
18
  "clsx": "^2.1.0",
27
19
  "date-fns": "^4.1.0",
28
20
  "lucide-react": "0.562.0",
21
+ "monaco-editor": "^0.55.1",
29
22
  "react": "^18.2.0",
30
23
  "react-day-picker": "^9.13.0",
31
24
  "react-markdown": "^10.1.0",
@@ -1,420 +1,14 @@
1
- import React from "react";
2
- import CodeMirror from "@uiw/react-codemirror";
3
- import {
4
- EditorView,
5
- ViewPlugin,
6
- Decoration,
7
- keymap,
8
- type ViewUpdate,
9
- type DecorationSet,
10
- } from "@codemirror/view";
11
- import { RangeSetBuilder, Prec } from "@codemirror/state";
12
- import {
13
- autocompletion,
14
- completionStatus,
15
- type CompletionContext,
16
- type CompletionResult,
17
- } from "@codemirror/autocomplete";
18
- import { indentUnit, getIndentUnit, indentString } from "@codemirror/language";
19
- import {
20
- jsonLanguageSupport,
21
- yamlLanguageSupport,
22
- xmlLanguageSupport,
23
- markdownLanguageSupport,
24
- isBetweenBrackets,
25
- type LanguageSupport,
26
- } from "./languageSupport";
27
-
28
- export type CodeEditorLanguage = "json" | "yaml" | "xml" | "markdown";
29
-
30
- /**
31
- * A single payload property available for templating
32
- */
33
- export interface TemplateProperty {
34
- /** Full path to the property, e.g., "payload.incident.title" */
35
- path: string;
36
- /** Type of the property, e.g., "string", "number", "boolean" */
37
- type: string;
38
- /** Optional description of the property */
39
- description?: string;
40
- }
41
-
42
- export interface CodeEditorProps {
43
- /** Unique identifier for the editor */
44
- id?: string;
45
- /** Current value of the editor */
46
- value: string;
47
- /** Callback when the value changes */
48
- onChange: (value: string) => void;
49
- /** Language for syntax highlighting */
50
- language?: CodeEditorLanguage;
51
- /** Minimum height of the editor */
52
- minHeight?: string;
53
- /** Whether the editor is read-only */
54
- readOnly?: boolean;
55
- /** Placeholder text when empty */
56
- placeholder?: string;
57
- /**
58
- * Optional template properties for autocomplete.
59
- * When provided, typing "{{" triggers autocomplete with available template variables.
60
- */
61
- templateProperties?: TemplateProperty[];
62
- }
63
-
64
- // Language support registry - add new languages here
65
- const languageRegistry: Record<CodeEditorLanguage, LanguageSupport> = {
66
- json: jsonLanguageSupport,
67
- yaml: yamlLanguageSupport,
68
- xml: xmlLanguageSupport,
69
- markdown: markdownLanguageSupport,
70
- };
71
-
72
- /**
73
- * Get display type with color info for autocomplete
74
- */
75
- function getTypeInfo(type: string): string {
76
- return type.charAt(0).toUpperCase() + type.slice(1).toLowerCase();
77
- }
78
-
79
- /**
80
- * Create a ViewPlugin for template-aware syntax highlighting.
81
- * Uses the language's buildDecorations function to generate decorations.
82
- */
83
- function createTemplateHighlighter(languageSupport: LanguageSupport) {
84
- return ViewPlugin.fromClass(
85
- class {
86
- decorations: DecorationSet;
87
-
88
- constructor(view: EditorView) {
89
- this.decorations = this.buildDecorations(view);
90
- }
91
-
92
- buildDecorations(view: EditorView): DecorationSet {
93
- const builder = new RangeSetBuilder<Decoration>();
94
- const doc = view.state.doc.toString();
95
- const ranges = languageSupport.buildDecorations(doc);
96
-
97
- for (const range of ranges) {
98
- builder.add(range.from, range.to, range.decoration);
99
- }
100
-
101
- return builder.finish();
102
- }
103
-
104
- update(update: ViewUpdate) {
105
- if (update.docChanged || update.viewportChanged) {
106
- this.decorations = this.buildDecorations(update.view);
107
- }
108
- }
109
- },
110
- {
111
- decorations: (v) => v.decorations,
112
- },
113
- );
114
- }
115
-
116
- /**
117
- * Create a CodeMirror autocomplete extension for template properties.
118
- * Triggers when user types "{{" and offers completions from templateProperties.
119
- */
120
- function createTemplateAutocomplete(
121
- templateProperties: TemplateProperty[],
122
- languageSupport: LanguageSupport,
123
- ): ReturnType<typeof autocompletion> {
124
- return autocompletion({
125
- override: [
126
- (context: CompletionContext): CompletionResult | null => {
127
- // Look for {{ pattern before cursor
128
- const textBefore = context.state.sliceDoc(0, context.pos);
129
- const recentText = context.state.sliceDoc(
130
- Math.max(0, context.pos - 50),
131
- context.pos,
132
- );
133
-
134
- // Find the last {{ that doesn't have a matching }}
135
- const lastOpenBrace = recentText.lastIndexOf("{{");
136
- const lastCloseBrace = recentText.lastIndexOf("}}");
137
-
138
- if (lastOpenBrace === -1 || lastOpenBrace < lastCloseBrace) {
139
- // eslint-disable-next-line unicorn/no-null -- CodeMirror API requires null
140
- return null;
141
- }
142
-
143
- // Validate position based on text BEFORE the {{ started
144
- // We exclude the {{ from validation because it confuses most parsers
145
- const positionOfTemplateStart =
146
- context.pos - recentText.length + lastOpenBrace;
147
- const textBeforeTemplate = textBefore.slice(0, positionOfTemplateStart);
148
- if (!languageSupport.isValidTemplatePosition(textBeforeTemplate)) {
149
- // eslint-disable-next-line unicorn/no-null -- CodeMirror API requires null
150
- return null;
151
- }
152
-
153
- // Calculate the position in the document where {{ starts
154
- const startOffset = context.pos - recentText.length + lastOpenBrace;
155
- const query = recentText.slice(lastOpenBrace + 2).toLowerCase();
156
-
157
- // Check for auto-closed braces after cursor position
158
- // When user types {{ with bracket auto-close, it becomes {{}}
159
- // We need to consume any trailing }} when completing
160
- const textAfter = context.state.sliceDoc(context.pos, context.pos + 4);
161
- let endOffset = context.pos;
162
- if (textAfter.startsWith("}}")) {
163
- endOffset += 2;
164
- } else if (textAfter.startsWith("}")) {
165
- // Just one } from first auto-close
166
- endOffset += 1;
167
- }
168
-
169
- // Filter properties based on query
170
- const filtered = templateProperties.filter((prop) =>
171
- prop.path.toLowerCase().includes(query),
172
- );
173
-
174
- if (filtered.length === 0 && query.length > 0) {
175
- // eslint-disable-next-line unicorn/no-null -- CodeMirror API requires null
176
- return null;
177
- }
178
-
179
- return {
180
- from: startOffset,
181
- to: endOffset,
182
- options: filtered.map((prop) => ({
183
- label: `{{${prop.path}}}`,
184
- displayLabel: prop.path,
185
- type: "variable",
186
- detail: getTypeInfo(prop.type),
187
- info: prop.description,
188
- boost: prop.path.toLowerCase().startsWith(query) ? 1 : 0,
189
- })),
190
- validFor: /^\{\{[\w.]*$/,
191
- };
192
- },
193
- ],
194
- activateOnTyping: true,
195
- icons: false,
196
- });
197
- }
198
-
199
- /**
200
- * A code editor component with syntax highlighting and optional template autocomplete.
201
- * Wraps @uiw/react-codemirror for consistent styling and API across the platform.
202
- */
203
- export const CodeEditor: React.FC<CodeEditorProps> = ({
204
- id,
205
- value,
206
- onChange,
207
- language = "json",
208
- minHeight = "100px",
209
- readOnly = false,
210
- placeholder,
211
- templateProperties,
212
- }) => {
213
- const extensions = React.useMemo(() => {
214
- const languageSupport = languageRegistry[language];
215
- const hasTemplates = templateProperties && templateProperties.length > 0;
216
-
217
- const exts = [
218
- EditorView.lineWrapping,
219
- EditorView.theme({
220
- "&": {
221
- fontSize: "14px",
222
- fontFamily: "ui-monospace, monospace",
223
- backgroundColor: "transparent",
224
- },
225
- ".cm-scroller": {
226
- backgroundColor: "transparent",
227
- overflow: "auto",
228
- },
229
- // minHeight must be on .cm-content and .cm-gutter, not the wrapper (per CM docs)
230
- ".cm-content, .cm-gutter": {
231
- minHeight: minHeight,
232
- },
233
- ".cm-content": {
234
- padding: "10px",
235
- },
236
- // Cursor/caret styling
237
- ".cm-cursor, .cm-dropCursor": {
238
- borderLeftColor: "hsl(var(--foreground))",
239
- borderLeftWidth: "2px",
240
- },
241
- ".cm-line": {
242
- color: "hsl(var(--foreground))",
243
- },
244
- ".cm-gutters": {
245
- backgroundColor: "transparent",
246
- color: "hsl(var(--muted-foreground))",
247
- border: "none",
248
- },
249
- "&.cm-focused": {
250
- outline: "none",
251
- },
252
- // JSON syntax highlighting for dark/light mode
253
- ".cm-string": {
254
- color: "hsl(142.1, 76.2%, 36.3%)",
255
- },
256
- ".cm-number": {
257
- color: "hsl(217.2, 91.2%, 59.8%)",
258
- },
259
- ".cm-propertyName": {
260
- color: "hsl(280, 65%, 60%)",
261
- },
262
- ".cm-keyword": {
263
- color: "hsl(280, 65%, 60%)",
264
- },
265
- ".cm-punctuation": {
266
- color: "hsl(var(--muted-foreground))",
267
- },
268
- // Placeholder styling
269
- ".cm-placeholder": {
270
- color: "hsl(var(--muted-foreground))",
271
- },
272
- // JSON syntax highlighting via decorations (overrides Lezer parser)
273
- ".cm-json-property": {
274
- color: "hsl(280, 65%, 60%)",
275
- },
276
- ".cm-json-string": {
277
- color: "hsl(142.1, 76.2%, 36.3%)",
278
- },
279
- ".cm-json-number": {
280
- color: "hsl(217.2, 91.2%, 59.8%)",
281
- },
282
- ".cm-json-keyword": {
283
- color: "hsl(280, 65%, 60%)",
284
- },
285
- // Template expression highlighting
286
- ".cm-template-expression": {
287
- color: "hsl(190, 70%, 50%)",
288
- fontWeight: "500",
289
- },
290
- // Style for autocomplete popup
291
- ".cm-tooltip.cm-tooltip-autocomplete": {
292
- backgroundColor: "hsl(var(--popover))",
293
- border: "1px solid hsl(var(--border))",
294
- borderRadius: "0.375rem",
295
- boxShadow:
296
- "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
297
- },
298
- ".cm-tooltip-autocomplete ul": {
299
- fontFamily: "ui-monospace, monospace",
300
- fontSize: "0.75rem",
301
- },
302
- ".cm-tooltip-autocomplete ul li": {
303
- padding: "0.25rem 0.5rem",
304
- },
305
- ".cm-tooltip-autocomplete ul li[aria-selected]": {
306
- backgroundColor: "hsl(var(--accent))",
307
- color: "hsl(var(--accent-foreground))",
308
- },
309
- ".cm-completionLabel": {
310
- fontFamily: "ui-monospace, monospace",
311
- },
312
- ".cm-completionDetail": {
313
- marginLeft: "0.5rem",
314
- fontStyle: "normal",
315
- color: "hsl(var(--muted-foreground))",
316
- },
317
- }),
318
- ];
319
-
320
- // Always add language extension for features (indentation, bracket matching, etc.)
321
- if (languageSupport) {
322
- exts.push(languageSupport.extension);
323
-
324
- // Create custom Enter key handler that applies our indentation
325
- // This is needed because the language parsers may get confused by templates
326
- // or may not provide the indentation we want
327
- const customEnterKeymap = keymap.of([
328
- {
329
- key: "Enter",
330
- run: (view) => {
331
- const state = view.state;
332
-
333
- // If autocomplete is active, let it handle Enter for selection
334
- if (completionStatus(state) === "active") {
335
- return false;
336
- }
337
-
338
- const pos = state.selection.main.head;
339
- const textBefore = state.sliceDoc(0, pos);
340
- const textAfter = state.sliceDoc(pos);
341
- const unit = getIndentUnit(state);
342
- const indent = languageSupport.calculateIndentation(
343
- textBefore + "\n",
344
- unit,
345
- );
346
- const indentStr = indentString(state, indent);
347
-
348
- // Check if we're between matching brackets/tags
349
- // This handles cases like: {|}, [|], <tag>|</tag>
350
- if (isBetweenBrackets(textBefore, textAfter)) {
351
- // Split: add newline with indent, then newline with previous indent for closing
352
- const prevIndent = Math.max(0, indent - unit);
353
- const prevIndentStr = indentString(state, prevIndent);
354
-
355
- view.dispatch({
356
- changes: {
357
- from: pos,
358
- to: pos,
359
- insert: "\n" + indentStr + "\n" + prevIndentStr,
360
- },
361
- selection: { anchor: pos + 1 + indentStr.length },
362
- scrollIntoView: true,
363
- userEvent: "input",
364
- });
365
- } else {
366
- // Normal: insert newline with calculated indentation
367
- view.dispatch({
368
- changes: { from: pos, to: pos, insert: "\n" + indentStr },
369
- selection: { anchor: pos + 1 + indentStr.length },
370
- scrollIntoView: true,
371
- userEvent: "input",
372
- });
373
- }
374
- return true;
375
- },
376
- },
377
- ]);
378
-
379
- // Always add indentation support and custom highlighter for consistent behavior
380
- // Prec.highest ensures our colors take precedence over language parser output
381
- exts.push(
382
- indentUnit.of(" "), // Configure 2-space indentation
383
- Prec.highest(customEnterKeymap), // Override default Enter behavior
384
- Prec.highest(createTemplateHighlighter(languageSupport)), // Consistent syntax colors
385
- );
386
-
387
- // Add template autocomplete if properties provided
388
- if (hasTemplates) {
389
- exts.push(
390
- createTemplateAutocomplete(templateProperties, languageSupport),
391
- );
392
- }
393
- }
394
-
395
- return exts;
396
- }, [language, templateProperties, minHeight]);
397
-
398
- return (
399
- <div
400
- id={id}
401
- className="w-full rounded-md border border-input bg-background font-mono text-sm focus-within:ring-2 focus-within:ring-ring focus-within:border-transparent transition-all box-border"
402
- >
403
- <CodeMirror
404
- value={value}
405
- onChange={onChange}
406
- extensions={extensions}
407
- editable={!readOnly}
408
- placeholder={placeholder}
409
- basicSetup={{
410
- lineNumbers: true,
411
- foldGutter: false,
412
- highlightActiveLine: false,
413
- highlightSelectionMatches: false,
414
- autocompletion: false, // We use our own
415
- }}
416
- theme="none"
417
- />
418
- </div>
419
- );
420
- };
1
+ // Monaco-based CodeEditor
2
+ // Re-export all from MonacoEditor as the new CodeEditor implementation
3
+
4
+ export {
5
+ CodeEditor,
6
+ type CodeEditorProps,
7
+ type CodeEditorLanguage,
8
+ type TemplateProperty,
9
+ } from "./MonacoEditor";
10
+
11
+ export {
12
+ generateTypeDefinitions,
13
+ type GenerateTypesOptions,
14
+ } from "./generateTypeDefinitions";