@checkstack/ui 1.11.0 → 1.13.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.
- package/.storybook/main.ts +43 -0
- package/CHANGELOG.md +326 -0
- package/package.json +23 -18
- package/scripts/generate-stdlib-types.ts +23 -0
- package/src/components/Accordion.tsx +17 -9
- package/src/components/ActionCard.tsx +99 -11
- package/src/components/BrandIcon.tsx +57 -0
- package/src/components/CodeEditor/CodeEditor.tsx +159 -14
- package/src/components/CodeEditor/TypefoxEditor.tsx +537 -168
- package/src/components/CodeEditor/editorTheme.test.ts +41 -0
- package/src/components/CodeEditor/editorTheme.ts +26 -0
- package/src/components/CodeEditor/generated/builtin-modules.json +1 -0
- package/src/components/CodeEditor/importSpecifiers.test.ts +286 -0
- package/src/components/CodeEditor/importSpecifiers.ts +267 -0
- package/src/components/CodeEditor/index.ts +26 -0
- package/src/components/CodeEditor/monacoGuard.ts +76 -0
- package/src/components/CodeEditor/monacoTsService.ts +185 -0
- package/src/components/CodeEditor/popoutTitle.test.ts +37 -0
- package/src/components/CodeEditor/popoutTitle.ts +31 -0
- package/src/components/CodeEditor/scriptContext.test.ts +15 -7
- package/src/components/CodeEditor/scriptContext.ts +12 -18
- package/src/components/CodeEditor/scriptDiagnostics.test.ts +135 -0
- package/src/components/CodeEditor/scriptDiagnostics.ts +172 -0
- package/src/components/CodeEditor/types.ts +79 -0
- package/src/components/CodeEditor/validateScripts.ts +172 -0
- package/src/components/CodeEditor/vscodeServicesSignal.ts +72 -0
- package/src/components/ConfirmationModal.tsx +7 -1
- package/src/components/Dialog.tsx +32 -11
- package/src/components/DurationInput.tsx +121 -0
- package/src/components/DynamicForm/DynamicForm.tsx +119 -47
- package/src/components/DynamicForm/DynamicOptionsField.tsx +19 -14
- package/src/components/DynamicForm/FormField.tsx +183 -15
- package/src/components/DynamicForm/MultiTypeEditorField.tsx +78 -2
- package/src/components/DynamicForm/SecretEnvEditor.tsx +315 -0
- package/src/components/DynamicForm/index.ts +20 -0
- package/src/components/DynamicForm/secretEnv.logic.test.ts +126 -0
- package/src/components/DynamicForm/secretEnv.logic.ts +87 -0
- package/src/components/DynamicForm/types.ts +134 -1
- package/src/components/DynamicForm/utils.test.ts +38 -0
- package/src/components/DynamicForm/utils.ts +54 -0
- package/src/components/DynamicForm/validation.logic.test.ts +255 -0
- package/src/components/DynamicForm/validation.logic.ts +210 -0
- package/src/components/DynamicIcon.tsx +39 -17
- package/src/components/Markdown.tsx +68 -2
- package/src/components/Popover.tsx +6 -1
- package/src/components/ScriptTestPanel.logic.test.ts +139 -0
- package/src/components/ScriptTestPanel.logic.ts +137 -0
- package/src/components/ScriptTestPanel.tsx +394 -0
- package/src/components/Sheet.tsx +21 -6
- package/src/components/Spinner.tsx +56 -0
- package/src/components/StatusBadge.tsx +78 -0
- package/src/components/StrategyConfigCard.tsx +3 -3
- package/src/components/Tabs.tsx +7 -1
- package/src/components/TimeOfDayInput.tsx +116 -0
- package/src/components/UserMenu.logic.test.ts +37 -0
- package/src/components/UserMenu.logic.ts +30 -0
- package/src/components/UserMenu.tsx +40 -12
- package/src/components/comboboxInteraction.ts +39 -0
- package/src/components/iconRegistry.tsx +27 -0
- package/src/components/portalContainer.ts +24 -0
- package/src/index.ts +7 -0
- package/stories/ActionCard.stories.tsx +60 -0
- package/stories/CodeEditor.stories.tsx +47 -2
- package/stories/DurationInput.stories.tsx +59 -0
- package/stories/Introduction.mdx +1 -1
- package/stories/Markdown.stories.tsx +56 -0
- package/stories/ScriptTestPanel.stories.tsx +106 -0
- package/stories/SecretEnvEditor.stories.tsx +80 -0
- package/stories/Spinner.stories.tsx +90 -0
- package/stories/TimeOfDayInput.stories.tsx +34 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
// Guarded accessor for the monaco-vscode editor API. ALL runtime monaco access
|
|
3
|
+
// in this package goes through here; direct value imports of
|
|
4
|
+
// `@codingame/monaco-vscode-editor-api` elsewhere are banned by lint
|
|
5
|
+
// (`no-restricted-imports`) so the guard cannot be bypassed.
|
|
6
|
+
//
|
|
7
|
+
// Why: `monaco.editor.*` / `monaco.languages.*` functions resolve services via
|
|
8
|
+
// `StandaloneServices.get()`, which AUTO-INITIALIZES the monaco-vscode services
|
|
9
|
+
// on first use (CodinGame `standaloneServices.js` — `get()` calls `initialize`
|
|
10
|
+
// when not yet initialized). `@typefox/monaco-editor-react` tracks its OWN,
|
|
11
|
+
// independent init flags; if any `monaco.*` call runs BEFORE the wrapper's init,
|
|
12
|
+
// it trips CodinGame's `servicesInitialized` flag and the wrapper's later
|
|
13
|
+
// `initialize()` throws "Services are already initialized" — the editor then
|
|
14
|
+
// never renders. This is dev-only (production's single mount masked it once),
|
|
15
|
+
// and it is exactly the bug this guard prevents from regressing.
|
|
16
|
+
//
|
|
17
|
+
// In dev, calling any `monaco.editor.*` / `monaco.languages.*` FUNCTION before
|
|
18
|
+
// `areVscodeServicesReady()` throws immediately, at the exact call site, with a
|
|
19
|
+
// clear message. In production the raw API is returned (zero overhead).
|
|
20
|
+
|
|
21
|
+
import * as monacoApi from "@codingame/monaco-vscode-editor-api";
|
|
22
|
+
import { areVscodeServicesReady } from "./vscodeServicesSignal";
|
|
23
|
+
|
|
24
|
+
// The namespaces whose functions auto-initialize the services on first use.
|
|
25
|
+
const GUARDED_NAMESPACES = new Set(["editor", "languages"]);
|
|
26
|
+
|
|
27
|
+
// Wrap a namespace so calling any of its functions before services are ready
|
|
28
|
+
// throws. Non-function members (enums like `MarkerSeverity`, classes) pass
|
|
29
|
+
// through untouched. Returns the same type `T` (the Proxy is invisible to types,
|
|
30
|
+
// so call sites still type-check against the real monaco signatures).
|
|
31
|
+
const guardNamespace = <T extends object>(ns: T, nsName: string): T =>
|
|
32
|
+
new Proxy(ns, {
|
|
33
|
+
get(target, prop, receiver): unknown {
|
|
34
|
+
const value: unknown = Reflect.get(target, prop, receiver);
|
|
35
|
+
if (typeof value !== "function" || typeof prop !== "string") {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
const fn = value;
|
|
39
|
+
return (...args: unknown[]): unknown => {
|
|
40
|
+
if (!areVscodeServicesReady()) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`monaco.${nsName}.${prop}() was called before the monaco-vscode ` +
|
|
43
|
+
`services were initialized. That call auto-initializes the ` +
|
|
44
|
+
`services and breaks the editor's @typefox init ("Services are ` +
|
|
45
|
+
`already initialized"), so the editor never renders. Gate it ` +
|
|
46
|
+
`behind \`apiReady\` (the editor's onEditorStartDone) or ` +
|
|
47
|
+
`\`areVscodeServicesReady()\`. See ` +
|
|
48
|
+
`core/ui/src/components/CodeEditor/monacoGuard.ts.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return Reflect.apply(fn, ns, args);
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const guardedMonaco: typeof monacoApi = import.meta.env.DEV
|
|
57
|
+
? new Proxy(monacoApi, {
|
|
58
|
+
get(target, prop, receiver): unknown {
|
|
59
|
+
const value: unknown = Reflect.get(target, prop, receiver);
|
|
60
|
+
if (
|
|
61
|
+
typeof prop === "string" &&
|
|
62
|
+
GUARDED_NAMESPACES.has(prop) &&
|
|
63
|
+
typeof value === "object" &&
|
|
64
|
+
value !== null
|
|
65
|
+
) {
|
|
66
|
+
return guardNamespace(value, prop);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
: monacoApi;
|
|
72
|
+
|
|
73
|
+
// The guarded runtime value. Consumers import this for runtime monaco access
|
|
74
|
+
// and import the TYPE namespace separately, type-only, from
|
|
75
|
+
// `@codingame/monaco-vscode-editor-api` (which the lint rule allows).
|
|
76
|
+
export { guardedMonaco as monaco };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Shared standalone TypeScript / JavaScript language-service setup.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from `TypefoxEditor` so it can be imported by BOTH the editor and
|
|
4
|
+
// the headless `validateScripts` validator. Both need the same singletons
|
|
5
|
+
// configured (compiler options, ambient stdlib types, the TS worker), and the
|
|
6
|
+
// validator must work even when NO editor is mounted (every script action card
|
|
7
|
+
// collapsed) - so the setup cannot live inside the editor component module's
|
|
8
|
+
// render path.
|
|
9
|
+
//
|
|
10
|
+
// The TS/JS language-service `defaults` are singletons, so the configuration
|
|
11
|
+
// here runs ONCE at module load. The worker factory is registered lazily (and
|
|
12
|
+
// idempotently) via `ensureStandaloneWorkerFactory()` because the editor also
|
|
13
|
+
// registers it during its own init - the guard makes a double-call a no-op.
|
|
14
|
+
|
|
15
|
+
// Side-effect import: registers the standalone language grammars. Imported here
|
|
16
|
+
// too (not only in TypefoxEditor) so the validator gets a fully-registered TS
|
|
17
|
+
// language environment regardless of import order.
|
|
18
|
+
import "@codingame/monaco-vscode-standalone-languages";
|
|
19
|
+
// The named imports below ALSO trigger this package's side-effect registration
|
|
20
|
+
// of the standalone TypeScript language features (defaults + ts.worker).
|
|
21
|
+
import {
|
|
22
|
+
typescriptDefaults,
|
|
23
|
+
javascriptDefaults,
|
|
24
|
+
ScriptTarget,
|
|
25
|
+
ModuleKind,
|
|
26
|
+
ModuleResolutionKind,
|
|
27
|
+
} from "@codingame/monaco-vscode-standalone-typescript-language-features";
|
|
28
|
+
// Worker entry URLs, bundled and resolved by Vite via the `?worker&url` suffix.
|
|
29
|
+
// Imported as URL STRINGS (not Worker constructors) because
|
|
30
|
+
// monaco-languageclient's worker factory consumes `loader().url.toString()`.
|
|
31
|
+
import editorWorkerUrl from "@codingame/monaco-vscode-editor-api/esm/vs/editor/editor.worker.js?worker&url";
|
|
32
|
+
import tsWorkerUrl from "@codingame/monaco-vscode-standalone-typescript-language-features/worker?worker&url";
|
|
33
|
+
import jsonWorkerUrl from "@codingame/monaco-vscode-standalone-json-language-features/worker?worker&url";
|
|
34
|
+
import {
|
|
35
|
+
// `useWorkerFactory` is a plain library registration function, not a React
|
|
36
|
+
// hook. We alias away the `use` prefix so the `react-hooks/rules-of-hooks`
|
|
37
|
+
// lint rule (which keys purely off the identifier name) does not misfire.
|
|
38
|
+
useWorkerFactory as registerWorkerFactory,
|
|
39
|
+
Worker,
|
|
40
|
+
type WorkerFactoryConfig,
|
|
41
|
+
type WorkerLoader,
|
|
42
|
+
} from "monaco-languageclient/workerFactory";
|
|
43
|
+
|
|
44
|
+
// Re-exported so consumers keep a single import surface for the TS services.
|
|
45
|
+
export {
|
|
46
|
+
typescriptDefaults,
|
|
47
|
+
javascriptDefaults,
|
|
48
|
+
ScriptTarget,
|
|
49
|
+
ModuleKind,
|
|
50
|
+
ModuleResolutionKind,
|
|
51
|
+
} from "@codingame/monaco-vscode-standalone-typescript-language-features";
|
|
52
|
+
|
|
53
|
+
// The logger type originates from `@codingame/monaco-vscode-log-service-override`,
|
|
54
|
+
// which is not a direct dependency of this package. We derive it from the
|
|
55
|
+
// `WorkerFactoryConfig` we already import so we never reach for a transitive
|
|
56
|
+
// specifier (and never need an `any`).
|
|
57
|
+
type WorkerFactoryLogger = WorkerFactoryConfig["logger"];
|
|
58
|
+
|
|
59
|
+
const editorWorkerLoader: WorkerLoader = () =>
|
|
60
|
+
new Worker(editorWorkerUrl, { type: "module" });
|
|
61
|
+
|
|
62
|
+
const tsWorkerLoader: WorkerLoader = () =>
|
|
63
|
+
new Worker(tsWorkerUrl, { type: "module" });
|
|
64
|
+
|
|
65
|
+
const jsonWorkerLoader: WorkerLoader = () =>
|
|
66
|
+
new Worker(jsonWorkerUrl, { type: "module" });
|
|
67
|
+
|
|
68
|
+
// The "monaco-vscode services ready" signal (`markVscodeServicesReady` /
|
|
69
|
+
// `areVscodeServicesReady` / `onVscodeServicesReady`) lives in the Monaco-free
|
|
70
|
+
// `vscodeServicesSignal` module so the barrel and the automation editor can
|
|
71
|
+
// observe readiness without importing this Monaco-heavy module. See that file
|
|
72
|
+
// for the full rationale.
|
|
73
|
+
|
|
74
|
+
let workerFactoryRegistered = false;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Register the worker loaders for the standalone (classic) Monaco setup. We
|
|
78
|
+
* only need the generic editor worker plus the TypeScript worker (which also
|
|
79
|
+
* serves JavaScript) and the JSON worker. Mirrors the upstream
|
|
80
|
+
* `defineClassicWorkers` helper.
|
|
81
|
+
*
|
|
82
|
+
* Idempotent: the first caller registers, subsequent calls no-op. Both the
|
|
83
|
+
* editor (via its `monacoWorkerFactory` app-config hook) AND the headless
|
|
84
|
+
* validator call this, so the guard prevents a double registration. The
|
|
85
|
+
* editor's call may pass a `logger`; the first registration wins, so when the
|
|
86
|
+
* validator registers first the editor simply reuses it (logging is optional).
|
|
87
|
+
*/
|
|
88
|
+
export const ensureStandaloneWorkerFactory = (
|
|
89
|
+
logger?: WorkerFactoryLogger,
|
|
90
|
+
): void => {
|
|
91
|
+
if (workerFactoryRegistered) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
workerFactoryRegistered = true;
|
|
95
|
+
registerWorkerFactory({
|
|
96
|
+
workerLoaders: {
|
|
97
|
+
editorWorkerService: editorWorkerLoader,
|
|
98
|
+
// Both must be defined or the worker factory errors (see upstream
|
|
99
|
+
// helper-classic.ts).
|
|
100
|
+
javascript: tsWorkerLoader,
|
|
101
|
+
typescript: tsWorkerLoader,
|
|
102
|
+
json: jsonWorkerLoader,
|
|
103
|
+
},
|
|
104
|
+
logger,
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Base compiler options for the standalone TS + JS services. `types` (node +
|
|
109
|
+
// bun-types) is added only once the stdlib bundle has loaded (see
|
|
110
|
+
// ensureStandaloneStdlib), so the service doesn't transiently error on a
|
|
111
|
+
// missing `node` type while the ~3 MB bundle is still fetching.
|
|
112
|
+
//
|
|
113
|
+
// `baseUrl: "file:///"` anchors NodeJs bare-import resolution at the virtual
|
|
114
|
+
// root so an `import "lodash"` walks `file:///node_modules/...` - the same
|
|
115
|
+
// virtual layout the stdlib bundle AND the lazy-ATA extra-libs are registered
|
|
116
|
+
// under. `typeRoots` lists BOTH the `@types` root (so a package with no own
|
|
117
|
+
// types, e.g. lodash, falls back to `@types/lodash`) and the bare
|
|
118
|
+
// `node_modules` root (so the bundled `bun-types`, which is NOT under
|
|
119
|
+
// `@types`, still resolves as an ambient `types` entry).
|
|
120
|
+
export const BASE_COMPILER_OPTIONS = {
|
|
121
|
+
target: ScriptTarget.ESNext,
|
|
122
|
+
module: ModuleKind.ESNext,
|
|
123
|
+
moduleResolution: ModuleResolutionKind.NodeJs,
|
|
124
|
+
lib: ["esnext"],
|
|
125
|
+
allowNonTsExtensions: false,
|
|
126
|
+
noEmit: true,
|
|
127
|
+
strict: true,
|
|
128
|
+
esModuleInterop: true,
|
|
129
|
+
baseUrl: "file:///",
|
|
130
|
+
typeRoots: ["file:///node_modules/@types", "file:///node_modules"],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Configure the standalone TS + JS language services ONCE at module load.
|
|
135
|
+
* `typescriptDefaults` / `javascriptDefaults` are singletons, so doing this at
|
|
136
|
+
* module scope (not per-mount) guarantees the first editor to mount cannot
|
|
137
|
+
* start the service with stale defaults - the timing race the legacy monaco
|
|
138
|
+
* editor hit.
|
|
139
|
+
*/
|
|
140
|
+
const configureTypeScriptDefaults = (): void => {
|
|
141
|
+
for (const defaults of [typescriptDefaults, javascriptDefaults]) {
|
|
142
|
+
defaults.setCompilerOptions({ ...BASE_COMPILER_OPTIONS });
|
|
143
|
+
// 1108: a top-level `return` is valid because the runtime wraps scripts in
|
|
144
|
+
// an async IIFE (same suppression as the legacy editor).
|
|
145
|
+
defaults.setDiagnosticsOptions({ diagnosticCodesToIgnore: [1108] });
|
|
146
|
+
// Push models to the worker eagerly so diagnostics/completions are ready on
|
|
147
|
+
// the first keystroke.
|
|
148
|
+
defaults.setEagerModelSync(true);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
configureTypeScriptDefaults();
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Lazy-load the bundled `@types/node` + `bun-types` declarations into the
|
|
156
|
+
* standalone TS service so script editors have `console`, `fetch`, `process`,
|
|
157
|
+
* `Bun`, etc. typed. The ~3 MB bundle is code-split into its own chunk and
|
|
158
|
+
* fetched once. Runs at module load; this file is browser-only so the dynamic
|
|
159
|
+
* import is safe here. Returns the in-flight promise so callers (the headless
|
|
160
|
+
* validator) can await stdlib readiness before requesting diagnostics.
|
|
161
|
+
*/
|
|
162
|
+
let stdlibLoad: Promise<void> | undefined;
|
|
163
|
+
export const ensureStandaloneStdlib = (): Promise<void> => {
|
|
164
|
+
if (stdlibLoad) {
|
|
165
|
+
return stdlibLoad;
|
|
166
|
+
}
|
|
167
|
+
stdlibLoad = (async () => {
|
|
168
|
+
const stdlibModule = await import("./generated/stdlib-types.json");
|
|
169
|
+
const bundle = stdlibModule.default;
|
|
170
|
+
for (const defaults of [typescriptDefaults, javascriptDefaults]) {
|
|
171
|
+
for (const [path, content] of Object.entries(bundle)) {
|
|
172
|
+
defaults.addExtraLib(content, `file:///${path}`);
|
|
173
|
+
}
|
|
174
|
+
// The @types/node + bun-types declarations now exist at their node_modules
|
|
175
|
+
// virtual paths, so include them ambiently.
|
|
176
|
+
defaults.setCompilerOptions({
|
|
177
|
+
...BASE_COMPILER_OPTIONS,
|
|
178
|
+
types: ["node", "bun-types"],
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
})();
|
|
182
|
+
return stdlibLoad;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
void ensureStandaloneStdlib();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { popoutTitle } from "./popoutTitle";
|
|
3
|
+
import type { CodeEditorLanguage } from "./types";
|
|
4
|
+
|
|
5
|
+
describe("popoutTitle", () => {
|
|
6
|
+
it("derives a script title for TypeScript", () => {
|
|
7
|
+
expect(popoutTitle({ language: "typescript" })).toBe(
|
|
8
|
+
"Edit script - TypeScript",
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("derives a script title for JavaScript", () => {
|
|
13
|
+
expect(popoutTitle({ language: "javascript" })).toBe(
|
|
14
|
+
"Edit script - JavaScript",
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("derives a script title for Shell", () => {
|
|
19
|
+
expect(popoutTitle({ language: "shell" })).toBe("Edit script - Shell");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("falls back to a generic title for markup/text languages", () => {
|
|
23
|
+
const markupLanguages: CodeEditorLanguage[] = [
|
|
24
|
+
"json",
|
|
25
|
+
"yaml",
|
|
26
|
+
"xml",
|
|
27
|
+
"markdown",
|
|
28
|
+
];
|
|
29
|
+
for (const language of markupLanguages) {
|
|
30
|
+
expect(popoutTitle({ language })).toBe("Edit");
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("uses a normal hyphen, never an em-dash", () => {
|
|
35
|
+
expect(popoutTitle({ language: "typescript" })).not.toContain("—");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Pure helper deriving the popout-dialog title from the editor language.
|
|
2
|
+
// Kept separate (and unit-tested) so the title logic isn't buried in UI glue.
|
|
3
|
+
import type { CodeEditorLanguage } from "./types";
|
|
4
|
+
|
|
5
|
+
// Human-readable display name for the languages that get a script-flavoured
|
|
6
|
+
// title. Markup/text languages fall through to the generic "Edit" title since
|
|
7
|
+
// "script" wouldn't read correctly for them.
|
|
8
|
+
const SCRIPT_LANGUAGE_LABELS: Partial<Record<CodeEditorLanguage, string>> = {
|
|
9
|
+
typescript: "TypeScript",
|
|
10
|
+
javascript: "JavaScript",
|
|
11
|
+
shell: "Shell",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Derive the overlay dialog title from the editor language.
|
|
16
|
+
*
|
|
17
|
+
* - Script languages (`typescript` / `javascript` / `shell`) read as
|
|
18
|
+
* `Edit script - <Language>`.
|
|
19
|
+
* - Everything else (markup/text: json / yaml / xml / markdown) falls back to
|
|
20
|
+
* the generic `Edit`.
|
|
21
|
+
*
|
|
22
|
+
* Uses a normal hyphen (not an em-dash) per the project content style.
|
|
23
|
+
*/
|
|
24
|
+
export const popoutTitle = ({
|
|
25
|
+
language,
|
|
26
|
+
}: {
|
|
27
|
+
language: CodeEditorLanguage;
|
|
28
|
+
}): string => {
|
|
29
|
+
const scriptLabel = SCRIPT_LANGUAGE_LABELS[language];
|
|
30
|
+
return scriptLabel ? `Edit script - ${scriptLabel}` : "Edit";
|
|
31
|
+
};
|
|
@@ -7,13 +7,17 @@ import {
|
|
|
7
7
|
import type { JsonSchemaProperty } from "../DynamicForm/types";
|
|
8
8
|
|
|
9
9
|
describe("healthcheckScriptContext", () => {
|
|
10
|
-
it("emits
|
|
10
|
+
it("emits the global helper + context types, but NOT a bare-name module block", () => {
|
|
11
11
|
const ctx = healthcheckScriptContext({});
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
12
|
+
// The bare-name `@checkstack/healthcheck` module block is REMOVED (plan
|
|
13
|
+
// §6.2 / §6.4): the subpath module `@checkstack/sdk/healthcheck` is now
|
|
14
|
+
// resolved from the injected @checkstack/sdk editor bundle, not from
|
|
15
|
+
// scriptContext. scriptContext must NOT declare ANY package module block.
|
|
16
|
+
expect(ctx.typeDefinitions).not.toContain('declare module "@checkstack/healthcheck"');
|
|
17
|
+
expect(ctx.typeDefinitions).not.toContain('declare module "@checkstack/sdk/healthcheck"');
|
|
18
|
+
expect(ctx.typeDefinitions).not.toContain("declare module");
|
|
15
19
|
expect(ctx.typeDefinitions).toContain("HealthCheckScriptResult");
|
|
16
|
-
//
|
|
20
|
+
// The global declaration stays — it makes Monaco autocomplete `defineHea…`
|
|
17
21
|
// _without_ requiring the user to type the import first (Monaco 0.55
|
|
18
22
|
// doesn't expose `includeCompletionsForModuleExports`).
|
|
19
23
|
expect(ctx.typeDefinitions).toMatch(/declare function defineHealthCheck/);
|
|
@@ -143,9 +147,13 @@ describe("healthcheckScriptContext", () => {
|
|
|
143
147
|
});
|
|
144
148
|
|
|
145
149
|
describe("integrationScriptContext", () => {
|
|
146
|
-
it("emits
|
|
150
|
+
it("emits the global helper + context types, but NOT a bare-name module block", () => {
|
|
147
151
|
const ctx = integrationScriptContext({});
|
|
148
|
-
|
|
152
|
+
// Bare-name `@checkstack/integration` block removed (§6.2/§6.4); the
|
|
153
|
+
// subpath module resolves from the injected @checkstack/sdk editor bundle.
|
|
154
|
+
expect(ctx.typeDefinitions).not.toContain('declare module "@checkstack/integration"');
|
|
155
|
+
expect(ctx.typeDefinitions).not.toContain('declare module "@checkstack/sdk/integration"');
|
|
156
|
+
expect(ctx.typeDefinitions).not.toContain("declare module");
|
|
149
157
|
expect(ctx.typeDefinitions).toContain("IntegrationScriptResult");
|
|
150
158
|
// Global form — analogous to defineHealthCheck.
|
|
151
159
|
expect(ctx.typeDefinitions).toMatch(/declare function defineIntegration/);
|
|
@@ -8,8 +8,9 @@ import { jsonSchemaToTypeScript } from "./generateTypeDefinitions";
|
|
|
8
8
|
*
|
|
9
9
|
* - `healthcheckInlineContext` — inline TS/JS health checks.
|
|
10
10
|
* `context.config` is typed from the collector's own config schema.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* The `@checkstack/sdk/healthcheck` module (resolved from the injected
|
|
12
|
+
* @checkstack/sdk editor bundle) exposes the required return shape so
|
|
13
|
+
* users get a type _error_ when the shape is wrong.
|
|
13
14
|
*
|
|
14
15
|
* - `healthcheckShellContext` — shell health checks. No platform-injected
|
|
15
16
|
* env vars today (the user supplies `env` themselves), so we surface
|
|
@@ -18,7 +19,8 @@ import { jsonSchemaToTypeScript } from "./generateTypeDefinitions";
|
|
|
18
19
|
*
|
|
19
20
|
* - `integrationInlineContext` — TS/JS integration scripts.
|
|
20
21
|
* `context.event.payload` is typed from the event's payload schema.
|
|
21
|
-
*
|
|
22
|
+
* The `@checkstack/sdk/integration` module (resolved from the injected
|
|
23
|
+
* @checkstack/sdk editor bundle) exposes the result shape.
|
|
22
24
|
*
|
|
23
25
|
* - `integrationShellContext` — shell integration scripts. The platform
|
|
24
26
|
* injects `EVENT_ID`, `DELIVERY_ID`, `SUBSCRIPTION_ID`,
|
|
@@ -119,9 +121,10 @@ interface HealthCheckScriptContext {
|
|
|
119
121
|
* to be so mistakes are caught before the script ever runs.
|
|
120
122
|
*
|
|
121
123
|
* Available both as a global (this declaration) and as a named export
|
|
122
|
-
* from \`@checkstack/healthcheck
|
|
123
|
-
*
|
|
124
|
-
*
|
|
124
|
+
* from \`@checkstack/sdk/healthcheck\`. The global form means the editor
|
|
125
|
+
* can autocomplete it without the user typing the import first; the
|
|
126
|
+
* named-export form (resolved from the injected @checkstack/sdk editor
|
|
127
|
+
* bundle, not declared here) is for explicit, IDE-style imports.
|
|
125
128
|
*/
|
|
126
129
|
declare function defineHealthCheck<
|
|
127
130
|
T extends
|
|
@@ -132,11 +135,6 @@ declare function defineHealthCheck<
|
|
|
132
135
|
>(value: T): T;
|
|
133
136
|
|
|
134
137
|
declare const context: HealthCheckScriptContext;
|
|
135
|
-
|
|
136
|
-
declare module "@checkstack/healthcheck" {
|
|
137
|
-
export type { HealthCheckScriptResult, HealthCheckScriptContext };
|
|
138
|
-
export { defineHealthCheck };
|
|
139
|
-
}
|
|
140
138
|
`;
|
|
141
139
|
}
|
|
142
140
|
|
|
@@ -187,7 +185,8 @@ interface IntegrationScriptContext {
|
|
|
187
185
|
* Helper that asserts the return shape of an integration script at the
|
|
188
186
|
* type level. See \`defineHealthCheck\` for the analogous health-check
|
|
189
187
|
* helper. Available both as a global and as a named export from
|
|
190
|
-
* \`@checkstack/integration
|
|
188
|
+
* \`@checkstack/sdk/integration\` (resolved from the injected
|
|
189
|
+
* @checkstack/sdk editor bundle, not declared here).
|
|
191
190
|
*/
|
|
192
191
|
declare function defineIntegration<
|
|
193
192
|
T extends
|
|
@@ -200,11 +199,6 @@ declare function defineIntegration<
|
|
|
200
199
|
>(value: T): T;
|
|
201
200
|
|
|
202
201
|
declare const context: IntegrationScriptContext;
|
|
203
|
-
|
|
204
|
-
declare module "@checkstack/integration" {
|
|
205
|
-
export type { IntegrationScriptResult, IntegrationScriptContext };
|
|
206
|
-
export { defineIntegration };
|
|
207
|
-
}
|
|
208
202
|
`;
|
|
209
203
|
}
|
|
210
204
|
|
|
@@ -221,7 +215,7 @@ declare module "@checkstack/integration" {
|
|
|
221
215
|
* `defineHealthCheck` is available without an import (declared as an
|
|
222
216
|
* ambient global in the editor's type defs and injected onto
|
|
223
217
|
* `globalThis` by the runner). The named-import form
|
|
224
|
-
* `import { defineHealthCheck } from "@checkstack/healthcheck"` also
|
|
218
|
+
* `import { defineHealthCheck } from "@checkstack/sdk/healthcheck"` also
|
|
225
219
|
* works if users prefer it.
|
|
226
220
|
*/
|
|
227
221
|
const HEALTHCHECK_INLINE_TS_STARTER = `import { loadavg } from "node:os";
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
buildValidationSource,
|
|
4
|
+
flattenDiagnosticMessage,
|
|
5
|
+
mapWorkerDiagnostics,
|
|
6
|
+
offsetToPosition,
|
|
7
|
+
type RawTsDiagnostic,
|
|
8
|
+
} from "./scriptDiagnostics";
|
|
9
|
+
|
|
10
|
+
describe("offsetToPosition", () => {
|
|
11
|
+
it("returns 1-based line/column", () => {
|
|
12
|
+
const text = "ab\ncde\nf";
|
|
13
|
+
expect(offsetToPosition(text, 0)).toEqual({ line: 1, column: 1 });
|
|
14
|
+
expect(offsetToPosition(text, 1)).toEqual({ line: 1, column: 2 });
|
|
15
|
+
// offset 3 is the 'c' (first char after the first newline)
|
|
16
|
+
expect(offsetToPosition(text, 3)).toEqual({ line: 2, column: 1 });
|
|
17
|
+
expect(offsetToPosition(text, 7)).toEqual({ line: 3, column: 1 });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("clamps an out-of-range offset to the text length", () => {
|
|
21
|
+
expect(offsetToPosition("ab", 999)).toEqual({ line: 1, column: 3 });
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("flattenDiagnosticMessage", () => {
|
|
26
|
+
it("passes through a plain string", () => {
|
|
27
|
+
expect(flattenDiagnosticMessage("boom")).toBe("boom");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("flattens a nested chain depth-first", () => {
|
|
31
|
+
expect(
|
|
32
|
+
flattenDiagnosticMessage({
|
|
33
|
+
messageText: "top",
|
|
34
|
+
next: [
|
|
35
|
+
{ messageText: "child-a" },
|
|
36
|
+
{ messageText: "child-b", next: [{ messageText: "grandchild" }] },
|
|
37
|
+
],
|
|
38
|
+
}),
|
|
39
|
+
).toBe("top child-a child-b grandchild");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("buildValidationSource", () => {
|
|
44
|
+
it("prepends the type defs and reports the prefix line count", () => {
|
|
45
|
+
const { text, prependedLineCount } = buildValidationSource({
|
|
46
|
+
typeDefinitions: "declare const context: { x: number };", // 1 line
|
|
47
|
+
source: "context.x;",
|
|
48
|
+
});
|
|
49
|
+
expect(prependedLineCount).toBe(1);
|
|
50
|
+
expect(text).toBe("declare const context: { x: number };\ncontext.x;");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("counts multi-line type defs", () => {
|
|
54
|
+
const { prependedLineCount } = buildValidationSource({
|
|
55
|
+
typeDefinitions: "line1\nline2\nline3",
|
|
56
|
+
source: "x",
|
|
57
|
+
});
|
|
58
|
+
expect(prependedLineCount).toBe(3);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("mapWorkerDiagnostics", () => {
|
|
63
|
+
// 2 lines of type defs prepended; user source starts at combined line 3.
|
|
64
|
+
const { text, prependedLineCount } = buildValidationSource({
|
|
65
|
+
typeDefinitions: "declare const context: {\n readonly a: number;\n};",
|
|
66
|
+
source: "const z = context.b;\n", // `b` doesn't exist -> error on user line 1
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const errorOffset = text.indexOf("context.b") + "context.".length; // points at `b`
|
|
70
|
+
|
|
71
|
+
it("shifts a real type error back onto the user's source line", () => {
|
|
72
|
+
const diagnostics: RawTsDiagnostic[] = [
|
|
73
|
+
{
|
|
74
|
+
start: errorOffset,
|
|
75
|
+
length: 1,
|
|
76
|
+
category: 1, // Error
|
|
77
|
+
code: 2339,
|
|
78
|
+
messageText: "Property 'b' does not exist on type",
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
const mapped = mapWorkerDiagnostics({
|
|
82
|
+
diagnostics,
|
|
83
|
+
validationText: text,
|
|
84
|
+
prependedLineCount,
|
|
85
|
+
});
|
|
86
|
+
expect(mapped).toHaveLength(1);
|
|
87
|
+
expect(mapped[0]).toMatchObject({
|
|
88
|
+
severity: "error",
|
|
89
|
+
line: 1,
|
|
90
|
+
message: "Property 'b' does not exist on type",
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("drops ignored codes (lazy-ATA module resolution, top-level return)", () => {
|
|
95
|
+
const diagnostics: RawTsDiagnostic[] = [
|
|
96
|
+
{ start: errorOffset, category: 1, code: 2307, messageText: "Cannot find module 'lodash'" },
|
|
97
|
+
{ start: errorOffset, category: 1, code: 1108, messageText: "A 'return' statement" },
|
|
98
|
+
];
|
|
99
|
+
expect(
|
|
100
|
+
mapWorkerDiagnostics({ diagnostics, validationText: text, prependedLineCount }),
|
|
101
|
+
).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("drops diagnostics that land inside the prepended type-def prefix", () => {
|
|
105
|
+
const diagnostics: RawTsDiagnostic[] = [
|
|
106
|
+
{ start: 0, category: 1, code: 2300, messageText: "noise in generated types" },
|
|
107
|
+
];
|
|
108
|
+
expect(
|
|
109
|
+
mapWorkerDiagnostics({ diagnostics, validationText: text, prependedLineCount }),
|
|
110
|
+
).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("drops non-error/-warning categories and unpositioned diagnostics", () => {
|
|
114
|
+
const diagnostics: RawTsDiagnostic[] = [
|
|
115
|
+
{ start: errorOffset, category: 2, code: 9999, messageText: "suggestion" },
|
|
116
|
+
{ category: 1, code: 2339, messageText: "global, no position" },
|
|
117
|
+
];
|
|
118
|
+
expect(
|
|
119
|
+
mapWorkerDiagnostics({ diagnostics, validationText: text, prependedLineCount }),
|
|
120
|
+
).toEqual([]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("keeps warnings", () => {
|
|
124
|
+
const diagnostics: RawTsDiagnostic[] = [
|
|
125
|
+
{ start: errorOffset, category: 0, code: 6133, messageText: "'z' is declared but never read" },
|
|
126
|
+
];
|
|
127
|
+
const mapped = mapWorkerDiagnostics({
|
|
128
|
+
diagnostics,
|
|
129
|
+
validationText: text,
|
|
130
|
+
prependedLineCount,
|
|
131
|
+
});
|
|
132
|
+
expect(mapped).toHaveLength(1);
|
|
133
|
+
expect(mapped[0]?.severity).toBe("warning");
|
|
134
|
+
});
|
|
135
|
+
});
|