Files
orca/src/shared/plugins/authoring/plugin-host-typings.ts
T
Neil 4ee788a725 Build-less TS/TSX plugins, typings and authoring tools (#36918)
* feat(plugins): build-less TS/TSX plugins, typings and authoring tools

- Plugin frames transpile .ts/.tsx/.mts/.jsx on load (sucrase on a shared worker thread with a
  2 s budget, cached in main), resolve relative imports to exact files, and refuse bare
  package imports by name. Components and html panel/editor entries may be .ts/.tsx.
- orca plugin types writes typings for the components orca module, window.orca, worker
  activate(orca), host.call and events into .orca/types, from one host-typings generator.
- orca plugin create --template components; templates omit publisher and target 1.4.246.
- orca plugin release-entry, dev inspect and restart; validate:plugin checks named files.
- Guest console errors reach orca plugin logs; dev workers restart on edits; the dev watcher
  ignores .git, .orca and node_modules; Git and folder installs cannot replace catalog installs.
- examples/plugins/hello-tsx, docs, and the plugin-buildless-tsx e2e spec.

* ci: run the Windows parity probes with one tsgo checker

tsconfig.node peaks near 22 GB with tsgo's default four checkers (each holds its own types)
and about 16 GB with one; the 16 GB Windows runner paged the native probe past its 900 s
timeout once this branch's package.json change triggered the job. One checker gives the
same diagnostics (verified locally with --parity-only); the timing benchmark is unchanged.
2026-11-01 01:28:35 -08:00

128 lines
5.8 KiB
TypeScript

import { PLUGIN_EVENT_PAYLOAD_SCHEMAS, PLUGIN_EVENT_SUMMARIES } from '../events/plugin-events'
import { PLUGIN_ERROR_CODES } from '../frames/plugin-protocol'
import { PLUGIN_EVENT_NAMES } from '../manifest/plugin-manifest'
import { PLUGIN_HOST_API_MAJOR } from '../workers/plugin-host-api-major'
import {
PLUGIN_HOST_API,
PLUGIN_HOST_API_MINOR,
PLUGIN_HOST_METHOD_NAMES,
pluginHostMethodsFor
} from '../workers/plugin-host-api'
import { zodTypeText, type SchemaTypeTextOptions } from '../workers/plugin-schema-type-text'
/** Checked-in output of `pnpm generate:plugin-author-typings`; `orca plugin types` copies it. */
export const PLUGIN_HOST_TYPINGS_FILE = 'resources/plugins/types/orca-host.d.ts'
/** Names the d.ts must declare beside the generated members, since JSON Schema cannot spell them. */
export const HOST_TYPE_TEXT: SchemaTypeTextOptions = {
json: 'JsonValue',
emptyObject: 'Record<string, never>'
}
const version = (minor: number): string => `${PLUGIN_HOST_API_MAJOR}.${minor}`
const union = (values: readonly string[]): string =>
values.map((value) => JSON.stringify(value)).join(' | ')
function docComment(indent: string, text: string): string {
return `${indent}/** ${text.replaceAll('*/', '*\\/')} */\n`
}
/**
* Members of a `HostMethods` type literal, one per host method:
* `{ params; result; surfaces }`, with `params` including `undefined` when it may be omitted.
* The one generator for host-method types: `orca/host` uses every method, and the components
* `orca` module (plugin-declarative-host-typings.ts) embeds a surface's methods.
*/
export function renderPluginHostMethodMembers(
indent = '',
options: {
surface?: Parameters<typeof pluginHostMethodsFor>[0]
/** One line per method, without `surfaces`: for a module already scoped to one surface. */
compact?: boolean
} = {}
): string {
const names =
options.surface === undefined ? PLUGIN_HOST_METHOD_NAMES : pluginHostMethodsFor(options.surface)
return names
.map((name) => {
const spec = PLUGIN_HOST_API[name]
const params = zodTypeText(spec.params, 'input', HOST_TYPE_TEXT)
const optional = spec.params.safeParse(undefined).success
const paramsText = `${params}${optional ? ' | undefined' : ''}`
const result = zodTypeText(spec.result, 'output', HOST_TYPE_TEXT)
if (options.compact) {
return `${docComment(indent, spec.summary)}${indent}${JSON.stringify(name)}: { params: ${paramsText}; result: ${result} }\n`
}
const since = `Since host API ${version(spec.since)}.`
const deprecated =
spec.deprecatedSince === undefined
? ''
: ` @deprecated since ${version(spec.deprecatedSince)}.`
return [
docComment(indent, `${spec.summary} ${since}${deprecated}`),
`${indent}${JSON.stringify(name)}: {\n`,
`${indent} params: ${paramsText}\n`,
`${indent} result: ${result}\n`,
`${indent} surfaces: ${union(spec.surfaces)}\n`,
`${indent}}\n`
].join('')
})
.join('')
}
/** Members of a `PluginEvents` type literal: event name → payload a worker handler receives. */
export function renderPluginEventPayloadMembers(indent = ''): string {
return PLUGIN_EVENT_NAMES.map((name) => {
const payload = zodTypeText(PLUGIN_EVENT_PAYLOAD_SCHEMAS[name], 'output', HOST_TYPE_TEXT)
return `${docComment(indent, PLUGIN_EVENT_SUMMARIES[name])}${indent}${JSON.stringify(name)}: ${payload}\n`
}).join('')
}
/** `declare module 'orca/host'`: the host API, errors and event payloads every surface shares. */
export function renderPluginHostTypings(): string {
return `// Generated by \`pnpm generate:plugin-author-typings\` from src/shared/plugins. Do not edit.
// Host API ${version(PLUGIN_HOST_API_MINOR)} types shared by frames (\`window.orca\`) and workers (\`activate(orca)\`).
declare module 'orca/host' {
export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }
export type Disposable = { dispose(): void }
/** \`orca.api\`: check \`minor\` (or \`host.has\`) before calling a method newer than your oldest Orca. */
export type HostApiVersion = { readonly major: number; readonly minor: number }
/** Open set: treat a code you do not know as \`action_failed\`. */
export type HostErrorCode = ${union(PLUGIN_ERROR_CODES)}
/** What a failed call rejects with; \`retryAfterMs\` is set for \`rate_limited\`. */
export type HostCallError = Error & {
code: HostErrorCode
retryable: boolean
retryAfterMs?: number
}
export type HostCallOptions = { signal?: AbortSignal }
export type HostSurface = 'worker' | 'panel' | 'editor'
export type HostMethods = {
${renderPluginHostMethodMembers(' ')} }
export type HostMethodName = keyof HostMethods
/** Methods a surface may call; any other gets \`not_permitted\`. */
export type HostMethodFor<S extends HostSurface> = {
[M in HostMethodName]: S extends HostMethods[M]['surfaces'] ? M : never
}[HostMethodName]
export type HostParams<M extends HostMethodName> = HostMethods[M]['params']
export type HostResult<M extends HostMethodName> = HostMethods[M]['result']
export type HostCallArgs<M extends HostMethodName> = undefined extends HostParams<M>
? [params?: HostParams<M>, options?: HostCallOptions]
: [params: HostParams<M>, options?: HostCallOptions]
export type HostApi<S extends HostSurface> = {
/** Rejects with a {@link HostCallError}. */
call<M extends HostMethodFor<S>>(method: M, ...args: HostCallArgs<M>): Promise<HostResult<M>>
/** Synchronous: whether this Orca offers \`method\` on this surface. */
has(method: string): boolean
}
/** Worker \`orca.events.on(event, handler)\` payloads. */
export type PluginEvents = {
${renderPluginEventPayloadMembers(' ')} }
export type PluginEventName = keyof PluginEvents
}
`
}