@checkstack/ui 1.13.1 → 1.14.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.
@@ -1,31 +1,19 @@
1
1
  import type { StorybookConfig } from "@storybook/react-vite";
2
- import { createRequire } from "node:module";
3
2
  import path from "node:path";
4
3
  import { fileURLToPath } from "node:url";
5
4
  import { mergeConfig } from "vite";
5
+ import { monacoViteConfig } from "../src/vite-monaco";
6
6
 
7
7
  const storybookDir = path.dirname(fileURLToPath(import.meta.url));
8
8
  const uiRoot = path.resolve(storybookDir, "..");
9
- const localRequire = createRequire(import.meta.url);
10
9
 
11
- // Resolve the `vscode` npm-alias (= @codingame/monaco-vscode-extension-api) to
12
- // an absolute path so Vite/Rolldown can alias it. This mirrors the same fix in
13
- // core/frontend/vite.config.ts.
14
- //
15
- // `CodeEditor` (via @typefox/monaco-editor-react + monaco-languageclient) pulls
16
- // in the @codingame/monaco-vscode-* stack, whose runtime package does
17
- // `require("vscode")`. Under bun's isolated node_modules the `"vscode":
18
- // "npm:@codingame/monaco-vscode-extension-api"` alias only exists inside the
19
- // typefox / monaco-languageclient scopes, so the Storybook build can't resolve
20
- // a bare `vscode` import and fails with "Rolldown failed to resolve import
21
- // 'vscode'". We resolve the alias *through* @typefox so the path follows bun's
22
- // store layout on any machine/CI rather than being hardcoded.
23
- const typefoxDir = path.dirname(
24
- localRequire.resolve("@typefox/monaco-editor-react", { paths: [uiRoot] }),
25
- );
26
- const vscodeApiDir = path.dirname(
27
- localRequire.resolve("vscode", { paths: [typefoxDir] }),
28
- );
10
+ // Shared Monaco editor Vite settings (ES-module workers + the `vscode` alias),
11
+ // from @checkstack/ui's monacoViteConfig helper the SAME settings the app's
12
+ // vite.config.ts and @checkstack/dev-server use, so the three configs never
13
+ // drift. `CodeEditor` (via @typefox/monaco-editor-react + monaco-languageclient)
14
+ // pulls in the @codingame/monaco-vscode-* stack whose runtime does
15
+ // `require("vscode")`; the alias resolves it under bun's isolated store.
16
+ const monaco = monacoViteConfig({ resolveFrom: [uiRoot] });
29
17
 
30
18
  const config: StorybookConfig = {
31
19
  stories: [
@@ -46,19 +34,8 @@ const config: StorybookConfig = {
46
34
  },
47
35
  viteFinal: (viteConfig) =>
48
36
  mergeConfig(viteConfig, {
49
- // The @typefox/monaco-editor-react + @codingame/monaco-vscode-* stack
50
- // loads its language services in ES module workers.
51
- worker: {
52
- format: "es",
53
- },
54
- resolve: {
55
- alias: {
56
- // Alias the `vscode` npm-alias to its real package dir so @codingame's
57
- // CJS `require("vscode")` resolves under bun's isolated store instead
58
- // of failing the build.
59
- vscode: vscodeApiDir,
60
- },
61
- },
37
+ worker: monaco.worker,
38
+ resolve: { alias: { ...monaco.resolve.alias } },
62
39
  }),
63
40
  };
64
41
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @checkstack/ui
2
2
 
3
+ ## 1.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ed251b6: Make `@checkstack/ui`'s Monaco `CodeEditor` render in standalone `bun run dev`, and consolidate the Monaco editor Vite settings into one shared helper so they can't drift.
8
+
9
+ - **Shared config (now in `@checkstack/ui`).** `@checkstack/ui` exports a `monacoViteConfig` helper (`@checkstack/ui/src/vite-monaco`) with the editor's Vite settings - `worker.format: "es"` and the `vscode` resolve alias (so `require("vscode")` doesn't leak into the browser). `@checkstack/ui` owns `CodeEditor` and the editor dependencies, so all three consumers now share one source: the app's `vite.config.ts`, `@checkstack/dev-server`, and `@checkstack/ui`'s own Storybook config (each previously hand-rolled its own copy).
10
+ - **Pre-built workers (dev server).** In a standalone plugin, `@checkstack/ui` is a _pre-bundled npm dependency_, and Vite's dependency optimizer can't process the Monaco language workers it imports via `?worker&url` - the dev server used to crash (and serving `@checkstack/ui` as source instead broke the CJS/ESM interop of its other deps). The dev server now pre-builds the three Monaco workers (editor / TypeScript / JSON) into static ES-module bundles, serves them, and redirects the `?worker&url` imports to them via `resolve.alias` (which applies during pre-bundling). `@checkstack/ui` stays pre-bundled and the workers resolve, so the editor renders. Builds are content-addressed and cached under `node_modules/.cache/checkstack-dev-monaco` (concurrency-safe atomic promotion), so only the first run after a dependency change pays the build cost. React is deduped so the editor's hooks share the dev shell's React instance.
11
+
12
+ - 968c12f: Make installed (runtime) frontend plugins actually load, via Module Federation 2.0. Previously a packed external plugin's frontend could not run: the host only shared React/router with runtime plugins, and there was no working way to share the framework/UI singletons (hand-rolled import-map externalisation hit an unsolvable rolldown CJS-interop wall).
13
+
14
+ - **Host (`@checkstack/frontend`)** now uses `@module-federation/vite` as an MF host and loads runtime plugins through the MF runtime (`registerRemotes` + `loadRemote`) instead of a raw `import()`. The shared set (react, react-dom, react-router-dom, @tanstack/react-query, @checkstack/frontend-api) is owned by the host; plugins reuse those exact instances via the share scope. The old hand-rolled vendor build + import map are removed.
15
+ - **`@checkstack/ui`** is bundled per consumer (tree-shaken); its Theme / Toast / Performance React contexts are unified across the host and bundled-in-plugin copies via a registered (globalThis-keyed) context, so a plugin's `useTheme`/`useToast`/`usePerformance` resolve to the host's providers. The ONE exception is the Monaco / VS Code **CodeEditor**, now exposed as the `@checkstack/ui/code-editor` subpath and shared as an MF singleton: the host owns the single editor instance (and builds its `?worker&url` workers), and plugins reuse it. A plugin can now render `<CodeEditor>` (directly or via `ScriptTestPanel` / template/JSON fields) without bundling Monaco.
16
+ - **Scaffold + pack (`@checkstack/scripts`)** build frontend plugins as MF remotes (`vite build` with the federation plugin, exposing `./plugin`, manifest enabled, DTS disabled). The CodeEditor is shared with `import: false` so the plugin is a consume-only participant - it never bundles a local fallback of the editor, keeping the heavy `@codingame/*` / `monaco-languageclient` / `vscode` subtree out of the plugin entirely (so no `vscode` alias or ES-worker config is needed in the plugin build). `plugin-pack` builds frontend packages with `NODE_ENV=production` (the MF plugin skips the remote under `NODE_ENV=test`) and ships only `dist/`. The scaffolded route now declares a `nav` entry so it appears in the sidebar.
17
+ - **Backend (`@checkstack/backend`)** serves a plugin's MF assets under its (possibly scoped) package name (`/assets/plugins/@scope/name/*`), with correct content types, and the SPA catch-all defers those paths so the federation manifest/remoteEntry are not shadowed by `index.html`.
18
+
19
+ Verified end-to-end by the external-plugin install E2E (scaffold → pack → install via the Plugin Manager UI → frontend + backend + co-loaded core plugins all work).
20
+
21
+ ### Patch Changes
22
+
23
+ - @checkstack/common@0.14.1
24
+ - @checkstack/frontend-api@0.7.2
25
+ - @checkstack/template-engine@0.4.2
26
+
27
+ ## 1.13.2
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies [1fee9da]
32
+ - @checkstack/common@0.14.1
33
+ - @checkstack/frontend-api@0.7.2
34
+ - @checkstack/template-engine@0.4.2
35
+
3
36
  ## 1.13.1
4
37
 
5
38
  ### Patch Changes
package/package.json CHANGED
@@ -1,13 +1,21 @@
1
1
  {
2
2
  "name": "@checkstack/ui",
3
- "version": "1.13.1",
3
+ "version": "1.14.0",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./code-editor": "./src/components/CodeEditor/index.ts",
10
+ "./package.json": "./package.json",
11
+ "./src/vite-monaco": "./src/vite-monaco.ts",
12
+ "./src/components/CodeEditor/generateTypeDefinitions": "./src/components/CodeEditor/generateTypeDefinitions.ts",
13
+ "./src/*": "./src/*"
14
+ },
7
15
  "dependencies": {
8
- "@checkstack/common": "0.13.0",
9
- "@checkstack/frontend-api": "0.7.0",
10
- "@checkstack/template-engine": "0.4.0",
16
+ "@checkstack/common": "0.14.1",
17
+ "@checkstack/frontend-api": "0.7.2",
18
+ "@checkstack/template-engine": "0.4.2",
11
19
  "@codingame/monaco-vscode-editor-api": "25.1.2",
12
20
  "@codingame/monaco-vscode-languages-service-override": "25.1.2",
13
21
  "@codingame/monaco-vscode-standalone-json-language-features": "25.1.2",
@@ -43,7 +51,7 @@
43
51
  "zod": "^4.2.1"
44
52
  },
45
53
  "devDependencies": {
46
- "@checkstack/scripts": "0.4.0",
54
+ "@checkstack/scripts": "0.5.0",
47
55
  "@checkstack/test-utils-frontend": "0.1.0",
48
56
  "@checkstack/tsconfig": "0.0.7",
49
57
  "@storybook/addon-a11y": "^10.4.1",
@@ -2,7 +2,9 @@ import React from "react";
2
2
  import Ajv from "ajv";
3
3
  import addFormats from "ajv-formats";
4
4
 
5
- import { CodeEditor } from "../CodeEditor";
5
+ // Deep package specifier (not "../CodeEditor"): routes through the Module
6
+ // Federation shared singleton so this never bundles its own Monaco copy.
7
+ import { CodeEditor } from "@checkstack/ui/code-editor";
6
8
  import type { JsonFieldProps } from "./types";
7
9
 
8
10
  const ajv = new Ajv({ allErrors: true, strict: false });
@@ -1,12 +1,14 @@
1
1
  import React from "react";
2
2
  import { Label } from "../Label";
3
3
  import { Textarea } from "../Textarea";
4
+ // Deep package specifier (not "../CodeEditor"): routes the CodeEditor value
5
+ // through the Module Federation shared singleton (see core/ui/src/index.ts).
4
6
  import {
5
7
  CodeEditor,
6
8
  type TemplateProperty,
7
9
  type AcquireTypes,
8
10
  type AcquiredTypeFile,
9
- } from "../CodeEditor";
11
+ } from "@checkstack/ui/code-editor";
10
12
  import {
11
13
  Select,
12
14
  SelectContent,
@@ -1,5 +1,6 @@
1
- import React, { createContext, useContext, useEffect, useState } from "react";
1
+ import React, { useContext, useEffect, useState } from "react";
2
2
  import { useToast } from "./ToastProvider";
3
+ import { createRegisteredContext } from "../utils/registered-context";
3
4
 
4
5
  const STORAGE_KEY = "checkstack-low-power";
5
6
 
@@ -17,12 +18,17 @@ interface PerformanceContextValue {
17
18
  toggleManualLowPower: () => void;
18
19
  }
19
20
 
20
- const PerformanceContext = createContext<PerformanceContextValue>({
21
- isLowPower: false,
22
- isLoaded: false,
23
- manualLowPower: false,
24
- toggleManualLowPower: () => {},
25
- });
21
+ // Registered (singleton) context: @checkstack/ui is bundled per consumer, but
22
+ // the host's provider and a plugin's usePerformance() must share one context.
23
+ const PerformanceContext = createRegisteredContext<PerformanceContextValue>(
24
+ "checkstack.ui.performance",
25
+ {
26
+ isLowPower: false,
27
+ isLoaded: false,
28
+ manualLowPower: false,
29
+ toggleManualLowPower: () => {},
30
+ },
31
+ );
26
32
 
27
33
  /**
28
34
  * usePerformance - Hook to access the global hardware performance state.
@@ -11,7 +11,9 @@ import { Button } from "./Button";
11
11
  import { Badge } from "./Badge";
12
12
  import { Input } from "./Input";
13
13
  import { Label } from "./Label";
14
- import { CodeEditor } from "./CodeEditor";
14
+ // Deep package specifier (not "./CodeEditor"): routes through the Module
15
+ // Federation shared singleton so this never bundles its own Monaco copy.
16
+ import { CodeEditor } from "@checkstack/ui/code-editor";
15
17
  import { cn } from "../utils";
16
18
  import { usePerformance } from "./PerformanceProvider";
17
19
  import {
@@ -1,5 +1,10 @@
1
1
  import React from "react";
2
- import { CodeEditor, type TemplateProperty } from "./CodeEditor";
2
+ // Deep package specifier (not "./CodeEditor"): routes the CodeEditor value
3
+ // through the Module Federation shared singleton (see core/ui/src/index.ts).
4
+ import {
5
+ CodeEditor,
6
+ type TemplateProperty,
7
+ } from "@checkstack/ui/code-editor";
3
8
  import { TemplateValueInput } from "./TemplateValueInput";
4
9
 
5
10
  /**
@@ -1,4 +1,5 @@
1
- import React, { createContext, useContext, useEffect, useState } from "react";
1
+ import React, { useContext, useEffect, useState } from "react";
2
+ import { createRegisteredContext } from "../utils/registered-context";
2
3
 
3
4
  type Theme = "light" | "dark" | "system";
4
5
 
@@ -29,7 +30,12 @@ const initialState: ThemeProviderState = {
29
30
  },
30
31
  };
31
32
 
32
- const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
33
+ // Registered (singleton) context: shared between the host's provider and a
34
+ // plugin's useTheme() even though @checkstack/ui is bundled per consumer.
35
+ const ThemeProviderContext = createRegisteredContext<ThemeProviderState>(
36
+ "checkstack.ui.theme",
37
+ initialState,
38
+ );
33
39
 
34
40
  export const ThemeProvider: React.FC<ThemeProviderProps> = ({
35
41
  children,
@@ -1,5 +1,6 @@
1
- import React, { createContext, useContext, useState, useCallback } from "react";
1
+ import React, { useContext, useState, useCallback } from "react";
2
2
  import { Toast } from "./Toast";
3
+ import { createRegisteredContext } from "../utils/registered-context";
3
4
 
4
5
  type ToastVariant = "default" | "success" | "error" | "warning" | "info";
5
6
 
@@ -26,7 +27,11 @@ interface ToastContextValue {
26
27
  info: (message: string, duration?: number) => void;
27
28
  }
28
29
 
29
- const ToastContext = createContext<ToastContextValue | undefined>(undefined);
30
+ // Registered (singleton) context: shared between the host's provider and a
31
+ // plugin's useToast() even though @checkstack/ui is bundled per consumer.
32
+ const ToastContext = createRegisteredContext<ToastContextValue | undefined>(
33
+ "checkstack.ui.toast",
34
+ );
30
35
 
31
36
  export const useToast = (): ToastContextValue => {
32
37
  const context = useContext(ToastContext);
package/src/index.ts CHANGED
@@ -60,7 +60,13 @@ export * from "./components/CommandPalette";
60
60
  export * from "./components/TerminalFeed";
61
61
  export * from "./components/PerformanceProvider";
62
62
  export * from "./components/AmbientBackground";
63
- export * from "./components/CodeEditor";
63
+ // The CodeEditor (Monaco / VS Code) stack is re-exported via the package's own
64
+ // deep specifier - NOT the relative "./components/CodeEditor" - so Module
65
+ // Federation can treat it as a shared singleton. The host owns the one editor
66
+ // instance (and builds its workers); runtime plugins reuse it via the share
67
+ // scope instead of bundling Monaco. The rest of @checkstack/ui stays bundled
68
+ // per consumer. See core/frontend/vite.config.ts `shared`.
69
+ export * from "@checkstack/ui/code-editor";
64
70
  export * from "./components/TemplateValueInput";
65
71
  export * from "./components/VariablePicker";
66
72
  export * from "./components/TemplateInput";
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { createRegisteredContext } from "./registered-context";
3
+
4
+ describe("createRegisteredContext", () => {
5
+ it("returns the SAME context instance for a repeated key (singleton)", () => {
6
+ // Simulates two bundled copies of @checkstack/ui registering the same
7
+ // context: the second call must reuse the first's instance.
8
+ const first = createRegisteredContext<number>("test.same", 1);
9
+ const second = createRegisteredContext<number>("test.same", 999);
10
+ expect(second).toBe(first);
11
+ });
12
+
13
+ it("returns distinct contexts for distinct keys", () => {
14
+ const a = createRegisteredContext<number>("test.a", 0);
15
+ const b = createRegisteredContext<number>("test.b", 0);
16
+ expect(a).not.toBe(b);
17
+ });
18
+
19
+ it("supports an omitted default for an undefined-capable T", () => {
20
+ const ctx = createRegisteredContext<string | undefined>("test.optional");
21
+ expect(ctx).toBeDefined();
22
+ // Re-registration under the same key still returns the same instance.
23
+ expect(createRegisteredContext<string | undefined>("test.optional")).toBe(
24
+ ctx,
25
+ );
26
+ });
27
+ });
@@ -0,0 +1,68 @@
1
+ import { createContext, type Context } from "react";
2
+
3
+ /**
4
+ * A `React.createContext` replacement whose context object is registered in a
5
+ * process-global registry keyed by a stable string.
6
+ *
7
+ * ## Why this exists
8
+ *
9
+ * `@checkstack/ui` is NOT a shared (vendored / import-mapped) singleton — it is
10
+ * bundled into every consumer (the host app AND each runtime plugin) so it can
11
+ * be tree-shaken (a plugin using a couple of components ships a couple of KB,
12
+ * not the whole 2 MB kit incl. Monaco/recharts). See the runtime
13
+ * frontend-plugin sharing design.
14
+ *
15
+ * But a few of its values MUST behave as singletons: the Theme / Toast /
16
+ * Performance React contexts. The host mounts the providers; a plugin's
17
+ * components call the matching hooks. With `@checkstack/ui` duplicated per
18
+ * bundle, a plain `createContext()` would create a DIFFERENT context object in
19
+ * each copy, so a plugin's `usePerformance()` would read its own empty context
20
+ * instead of the host's provider value.
21
+ *
22
+ * Registering the context on `globalThis` by key fixes that: whichever copy
23
+ * loads first (always the host, which boots before any plugin) creates and
24
+ * registers the context; every later copy returns that same instance. React
25
+ * itself is the shared singleton (resolved via the import map), so the context
26
+ * object is usable across the bundles. This is the well-established
27
+ * "singleton context" pattern for shared React context from a bundled-per-MFE
28
+ * library (cf. `@indeedeng/react-singleton-context`).
29
+ *
30
+ * Only contexts that are genuinely shared between host and plugins need this;
31
+ * purely internal component state should keep using plain `createContext`.
32
+ */
33
+
34
+ const REGISTRY_KEY = "__checkstackUiContextRegistry__";
35
+
36
+ // The registry stores contexts for heterogeneous value types keyed by string,
37
+ // so it cannot be typed per key; values are stored as `Context<unknown>` and
38
+ // narrowed back by the caller's `T` on read. This single, contained cast is
39
+ // the reason the registry is wrapped in this helper instead of inlined.
40
+ type ContextRegistry = Map<string, Context<unknown>>;
41
+
42
+ function getRegistry(): ContextRegistry {
43
+ const holder = globalThis as typeof globalThis & {
44
+ [REGISTRY_KEY]?: ContextRegistry;
45
+ };
46
+ holder[REGISTRY_KEY] ??= new Map<string, Context<unknown>>();
47
+ return holder[REGISTRY_KEY];
48
+ }
49
+
50
+ export function createRegisteredContext<T>(
51
+ key: string,
52
+ // Optional so callers whose `T` includes `undefined` (a context that is
53
+ // absent until a provider mounts) can omit it rather than pass a literal
54
+ // `undefined` argument.
55
+ ...defaultValue: [T] | []
56
+ ): Context<T> {
57
+ const registry = getRegistry();
58
+ const existing = registry.get(key);
59
+ if (existing) {
60
+ // Safe by construction: a given `key` is always created with the same `T`.
61
+ return existing as Context<T>;
62
+ }
63
+ // `defaultValue[0]` is `undefined` only when the caller omitted it, in which
64
+ // case `T` includes `undefined`; otherwise it is the provided `T`.
65
+ const context = createContext<T>(defaultValue[0] as T);
66
+ registry.set(key, context as Context<unknown>);
67
+ return context;
68
+ }
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { monacoViteConfig } from "./vite-monaco";
5
+
6
+ // `@typefox/monaco-editor-react` + the `vscode` npm-alias live in
7
+ // `@checkstack/ui`'s dependencies (this package's root).
8
+ const UI_DIR = path.resolve(import.meta.dir, "..");
9
+
10
+ describe("monacoViteConfig", () => {
11
+ it("returns the ES-module worker format the @codingame workers require", () => {
12
+ const config = monacoViteConfig({ resolveFrom: [UI_DIR] });
13
+ expect(config.worker.format).toBe("es");
14
+ });
15
+
16
+ it("resolves the `vscode` alias to the real @codingame package dir", () => {
17
+ const config = monacoViteConfig({ resolveFrom: [UI_DIR] });
18
+ const vscodeDir = config.resolve.alias.vscode;
19
+ // The alias must point at @codingame/monaco-vscode-extension-api on disk
20
+ // (the package `require("vscode")` actually resolves to), not the bare
21
+ // `vscode` specifier that leaks a runtime require into the browser.
22
+ expect(vscodeDir).toContain("monaco-vscode");
23
+ expect(path.isAbsolute(vscodeDir)).toBe(true);
24
+ expect(existsSync(vscodeDir)).toBe(true);
25
+ });
26
+ });
@@ -0,0 +1,60 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+
4
+ /**
5
+ * Shared Vite settings required to bundle the Monaco / VS Code editor stack
6
+ * that `@checkstack/ui`'s `CodeEditor` pulls in (`@typefox/monaco-editor-react`
7
+ * + `monaco-languageclient` + `@codingame/monaco-vscode-*`).
8
+ *
9
+ * Lives here in `@checkstack/ui` (which owns `CodeEditor` and the editor
10
+ * dependencies) so every consumer can share one source instead of drifting:
11
+ * `@checkstack/frontend`'s `vite.config.ts`, `@checkstack/dev-server`'s
12
+ * standalone-plugin dev config, and `@checkstack/ui`'s own Storybook config.
13
+ *
14
+ * Two settings are required:
15
+ *
16
+ * - `worker.format: "es"` - the `@codingame` language-feature workers are ES
17
+ * modules; the default (iife) worker format cannot bundle them.
18
+ * - `resolve.alias.vscode` - `@typefox/monaco-editor-react` and
19
+ * `monaco-languageclient` declare `"vscode": "npm:@codingame/monaco-vscode-extension-api"`,
20
+ * but the package that actually does `require("vscode")` at runtime
21
+ * (`@codingame/monaco-vscode-api`) has no `vscode` in its own scope under
22
+ * bun's isolated `node_modules`. Aliasing every `vscode` specifier to the
23
+ * real package dir resolves it (otherwise a runtime `require` leaks into the
24
+ * browser).
25
+ */
26
+ export interface MonacoViteConfig {
27
+ worker: { format: "es" };
28
+ resolve: { alias: { vscode: string } };
29
+ }
30
+
31
+ /**
32
+ * Build the Monaco-related Vite settings, resolving the `vscode` npm-alias to
33
+ * an absolute path so Vite can alias it.
34
+ *
35
+ * `resolveFrom` are candidate base dirs from which
36
+ * `@typefox/monaco-editor-react` (and, through it, the `vscode` alias) can be
37
+ * resolved: `core/ui` in the monorepo, or the plugin's installed
38
+ * `@checkstack/ui` location in a standalone scaffold. The path follows bun's
39
+ * store layout on any machine rather than being hardcoded.
40
+ *
41
+ * Throws if the editor stack cannot be resolved (e.g. `@checkstack/ui` is not
42
+ * installed). Callers that must degrade gracefully should wrap the call.
43
+ */
44
+ export function monacoViteConfig({
45
+ resolveFrom,
46
+ }: {
47
+ resolveFrom: string[];
48
+ }): MonacoViteConfig {
49
+ const req = createRequire(import.meta.url);
50
+ const typefoxDir = path.dirname(
51
+ req.resolve("@typefox/monaco-editor-react", { paths: resolveFrom }),
52
+ );
53
+ const vscodeApiDir = path.dirname(
54
+ req.resolve("vscode", { paths: [typefoxDir] }),
55
+ );
56
+ return {
57
+ worker: { format: "es" },
58
+ resolve: { alias: { vscode: vscodeApiDir } },
59
+ };
60
+ }