gesso-vite-plugin 0.4.1 → 0.5.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/CHANGELOG.md +31 -0
- package/README.md +4 -0
- package/dist/gesso-channels.mjs +530 -0
- package/dist/gesso-channels.mjs.map +1 -0
- package/dist/index.d.ts +161 -1
- package/dist/index.js +761 -7
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gesso-channels.mjs","names":[],"sources":["../src/contracts.ts","../bin/gesso-channels.ts"],"sourcesContent":["import type * as TsApi from 'typescript/unstable/sync';\n\nimport { blankLiterals, findCalls, importSources } from './source.ts';\n\n/**\n * Channel contracts, read by the type checker and written back as JSON\n * Schema.\n *\n * A contract module already says everything an agent driving the\n * application would need to know: the view's keys and their types, each\n * command's parameters and their types, and, where the author wrote\n * them, what each one is for. All of it is types and comments, and\n * both are gone by the time the module runs. This puts them back, as a\n * `describeChannel(token, schema)` call appended to the module, so the\n * token carries its own description wherever it is imported.\n *\n * The checker rather than the syntax, because a contract names its\n * types where it likes: `ProductRow` from another file, a status union\n * behind an alias, a view written as an object literal whose type is\n * inferred. Only the checker resolves all of those the way the compiler\n * that checked the application did. TypeScript 7's checker runs in a\n * separate process behind an API its package calls unstable, so all of\n * it is in this file, and the rest of the plugin sees one function.\n *\n * What the comments carry:\n *\n * - The JSDoc on the exported token is the channel's description, on\n * a view key is that key's, and on a command is the command's.\n * - `@param name text` describes a parameter.\n * - `@destructive`, `@idempotent`, `@confirm` and `@hidden` on a\n * command are its annotations; `CommandSchema` says what each means.\n *\n * And what it will not describe: a value that is not plain data, which\n * cannot cross a channel anyway. A function, a `Date`, a `Map`, a class\n * instance. Each is reported, by its path in the contract, and\n * described as `{}` so the rest of the schema still stands.\n */\n\n/** The parts of `typescript/unstable/sync` this file uses, values included. */\nexport type TypeScriptApi = typeof TsApi;\n\n/** A channel's description, in the shape `describeChannel` takes. */\nexport interface ChannelSchemaOut {\n description?: string;\n view: JsonSchema;\n commands: Record<string, CommandSchemaOut>;\n}\n\ninterface CommandSchemaOut {\n description?: string;\n parameters: string[];\n rest?: true;\n input: JsonSchema;\n destructive?: true;\n idempotent?: true;\n confirm?: true;\n hidden?: true;\n}\n\ntype JsonSchema = Record<string, unknown>;\n\n/** Every channel a module exports, and what could not be described. */\nexport interface ContractReading {\n /** Export name to schema, in declaration order. */\n readonly channels: ReadonlyMap<string, ChannelSchemaOut>;\n /** One sentence per value that could not be described, naming where it is. */\n readonly warnings: readonly string[];\n}\n\n/** The functions that declare a channel. */\nconst DECLARATIONS = ['channel', 'defineChannel'];\n\n/**\n * Whether a module declares a channel, without asking the checker.\n *\n * The checker is a process round trip per question, and every module\n * the application has passes through `transform`, so this is the gate:\n * a call to `channel` or `defineChannel` imported from\n * `gesso-framework`. A module that re-exports a token someone else\n * declared is not a contract, and the module that declared it is the\n * one described.\n */\nexport function declaresChannel(code: string): boolean {\n if (!code.includes('gesso-framework')) {\n return false;\n }\n const blank = blankLiterals(code);\n const imports = importSources(code, blank);\n // `channel<View, Commands>(...)` has type arguments between the name\n // and the parenthesis, which `findCalls` does not look past, and type\n // arguments are the form `channel` is usually written in.\n return DECLARATIONS.some(\n name =>\n imports.get(name) === 'gesso-framework' &&\n (findCalls(code, name, blank).length > 0 || new RegExp(`(?<![\\\\w$.])${name}\\\\s*<`).test(blank))\n );\n}\n\n/**\n * The code appended to a contract module.\n *\n * An import at the bottom is still hoisted, and appending rather than\n * rewriting leaves every line the author wrote where they wrote it, so\n * the module's source map needs nothing.\n */\nexport function describeCalls(channels: ReadonlyMap<string, ChannelSchemaOut>): string {\n const lines = [\"import { describeChannel as __gessoDescribeChannel } from 'gesso-framework';\"];\n for (const [name, schema] of channels) {\n lines.push(`__gessoDescribeChannel(${name}, ${JSON.stringify(schema)});`);\n }\n return `\\n${lines.join('\\n')}\\n`;\n}\n\n/**\n * A module that describes channels when it is imported, for a bundle\n * this plugin never sees.\n *\n * The plugin describes a contract by appending to it as Vite bundles\n * it. An Electrobun main process is bundled by Electrobun's own build,\n * which takes no plugins, so there the description is written ahead of\n * time instead: `gesso-channels` reads the contracts with the same\n * checker and writes this module, and the main process imports it once.\n * A token is imported from its contract, so the schema lands on the\n * same object the application serves.\n *\n * `outFile` and every `file` are absolute POSIX paths.\n */\nexport function describedModule(\n contracts: readonly { readonly file: string; readonly channels: ReadonlyMap<string, ChannelSchemaOut> }[],\n outFile: string\n): string {\n const lines = [\n '// Written by gesso-channels from the channel contracts it names below.',\n '// Run it again after changing a contract rather than editing this file.',\n \"import { describeChannel } from 'gesso-framework';\"\n ];\n const from = outFile.slice(0, outFile.lastIndexOf('/'));\n for (const contract of contracts) {\n if (contract.channels.size > 0) {\n const names = [...contract.channels.keys()].join(', ');\n lines.push(`import { ${names} } from '${importPath(from, contract.file)}';`);\n }\n }\n for (const contract of contracts) {\n for (const [name, schema] of contract.channels) {\n lines.push('', `describeChannel(${name}, ${JSON.stringify(schema, null, 2)});`);\n }\n }\n return `${lines.join('\\n')}\\n`;\n}\n\n/** `./Counter` from `/app/src/shared` to `/app/src/shared/Counter.ts`, without the extension. */\nfunction importPath(fromDir: string, file: string): string {\n const from = fromDir.split('/').filter(Boolean);\n const to = file\n .replace(/\\.[cm]?[jt]sx?$/, '')\n .split('/')\n .filter(Boolean);\n let shared = 0;\n while (shared < from.length && shared < to.length - 1 && from[shared] === to[shared]) {\n shared++;\n }\n const up = from.length - shared;\n const rest = to.slice(shared).join('/');\n return up === 0 ? `./${rest}` : `${'../'.repeat(up)}${rest}`;\n}\n\n/**\n * The checker, held open across reads.\n *\n * Spawning it and loading a project costs about a hundred\n * milliseconds; a question after that costs well under one. So one\n * reader lives as long as the dev server or the build, is told which\n * files changed between reads, and is closed at the end, because the\n * process it holds would otherwise keep the build from exiting.\n */\nexport class ContractReader {\n private api: TsApi.API | null = null;\n private readonly changed = new Set<string>();\n /** Each contract's tsconfig, found once. */\n private readonly configs = new Map<string, string>();\n\n /**\n * @param fs Callbacks the checker reads through before the disk, for\n * a spec that keeps its project in memory.\n */\n private readonly ts: TypeScriptApi;\n private readonly cwd: string;\n private readonly fs: TsApi.APIOptions['fs'];\n\n // Fields assigned by hand rather than parameter properties: the\n // plugin is loaded by Node's type stripping, which rejects those.\n constructor(ts: TypeScriptApi, cwd: string, fs?: TsApi.APIOptions['fs']) {\n this.ts = ts;\n this.cwd = cwd;\n this.fs = fs;\n }\n\n /** A file changed on disk since the last read. */\n invalidate(file: string): void {\n this.changed.add(file);\n }\n\n /** Every channel `file` exports, described. */\n read(file: string): ContractReading {\n const api = (this.api ??= new this.ts.API({ cwd: this.cwd, ...(this.fs === undefined ? {} : { fs: this.fs }) }));\n this.changed.add(file);\n let snapshot = api.updateSnapshot({ fileChanges: { changed: [...this.changed] } });\n this.changed.clear();\n let config = this.configs.get(file);\n if (config === undefined) {\n // A file opened through the API is an editor's buffer: the server\n // stops reading it from disk, and an edit the next save makes is\n // never seen. So the file is opened only long enough to learn\n // which project it belongs to, and the project is what stays open.\n config = api.updateSnapshot({ openFiles: [file] }).getDefaultProjectForFile(file)?.configFileName;\n snapshot = api.updateSnapshot({\n ...(config === undefined ? {} : { openProjects: [config] }),\n closeFiles: [file]\n });\n if (config !== undefined) {\n this.configs.set(file, config);\n }\n }\n const project = config === undefined ? undefined : snapshot.getProject(config);\n const source = project?.program.getSourceFile(file);\n if (project === undefined || source === undefined) {\n return { channels: new Map(), warnings: [`TypeScript has no project that includes ${file}.`] };\n }\n const checker = project.checker;\n const module = checker.getSymbolAtLocation(source);\n const describer = new Describer(this.ts, checker);\n const channels = new Map<string, ChannelSchemaOut>();\n for (const exported of module === undefined ? [] : checker.getExportsOfModule(module)) {\n const type = checker.getTypeOfSymbol(exported);\n if (type === undefined || type.getSymbol()?.name !== 'ChannelToken') {\n continue;\n }\n const [view, commands] = checker.getTypeArguments(type as TsApi.TypeReference);\n channels.set(exported.name, describer.channel(exported, view, commands));\n }\n return { channels, warnings: describer.warnings };\n }\n\n close(): void {\n this.api?.close();\n this.api = null;\n }\n}\n\n/**\n * Library types that are objects to the checker and not plain data to\n * a channel. They compare by reference, so `provide` rejects them on\n * their first value; describing their methods as properties would be\n * describing something that never arrives.\n */\nconst NOT_PLAIN = new Set([\n 'Date',\n 'Map',\n 'ReadonlyMap',\n 'Set',\n 'ReadonlySet',\n 'WeakMap',\n 'WeakSet',\n 'Promise',\n 'RegExp',\n 'Error',\n 'ArrayBuffer',\n 'SharedArrayBuffer',\n 'DataView',\n 'Blob',\n 'File',\n 'URL',\n 'Int8Array',\n 'Uint8Array',\n 'Uint8ClampedArray',\n 'Int16Array',\n 'Uint16Array',\n 'Int32Array',\n 'Uint32Array',\n 'Float32Array',\n 'Float64Array',\n 'BigInt64Array',\n 'BigUint64Array'\n]);\n\n/**\n * Bytes, which a command may carry and a view key may not.\n *\n * A command's argument is structured-cloned onto the owning thread, and\n * a typed array or an `ArrayBuffer` clones as itself: a file a person\n * dropped crosses as its bytes, with no encoding pass on the render\n * thread. A view key is diffed, and a differ that compares bytes by\n * reference reports a change on every emission, so there they stay\n * unplain. In a command's schema they are a base64 string, which is\n * what an agent can write, tagged with the type the command expects so\n * the agent surface can turn the string back into it.\n */\nexport const BINARY = new Set([\n 'ArrayBuffer',\n 'Int8Array',\n 'Uint8Array',\n 'Uint8ClampedArray',\n 'Int16Array',\n 'Uint16Array',\n 'Int32Array',\n 'Uint32Array',\n 'Float32Array',\n 'Float64Array',\n 'BigInt64Array',\n 'BigUint64Array'\n]);\n\n/** Turns checker types into JSON Schema for one module's channels. */\nclass Describer {\n readonly warnings: string[] = [];\n private readonly flags: TypeScriptApi['TypeFlags'];\n /** Describing a command's parameters, where bytes are allowed; see `BINARY`. */\n private inCommand = false;\n /** Types being described right now, to notice a type that contains itself. */\n private readonly inProgress = new Set<number>();\n /** Types found to contain themselves, which are described once in `$defs`. */\n private readonly recursive = new Set<number>();\n /** `$defs` for the schema being built. Reset per root, so each schema stands alone. */\n private defs = new Map<string, JsonSchema>();\n private readonly defNames = new Map<number, string>();\n\n private readonly ts: TypeScriptApi;\n private readonly checker: TsApi.Checker;\n\n constructor(ts: TypeScriptApi, checker: TsApi.Checker) {\n this.ts = ts;\n this.checker = checker;\n this.flags = ts.TypeFlags;\n }\n\n channel(token: TsApi.Symbol, view: TsApi.Type, commands: TsApi.Type | undefined): ChannelSchemaOut {\n const out: ChannelSchemaOut = { view: this.root(view, `${token.name}.view`), commands: {} };\n const description = this.checker.getDocumentationCommentOfSymbol(token);\n if (description !== '') {\n out.description = description;\n }\n if (commands !== undefined) {\n for (const command of this.checker.getPropertiesOfType(commands)) {\n const described = this.command(command, `${token.name}.commands.${command.name}`);\n if (described !== null) {\n out.commands[command.name] = described;\n }\n }\n }\n return out;\n }\n\n /** A schema that stands alone: its own `$defs`, if anything in it recurs. */\n private root(type: TsApi.Type, path: string): JsonSchema {\n this.defs = new Map();\n const schema = this.describe(type, path);\n if (this.defs.size > 0) {\n return { ...schema, $defs: Object.fromEntries(this.defs) };\n }\n return schema;\n }\n\n private command(symbol: TsApi.Symbol, path: string): CommandSchemaOut | null {\n const type = this.checker.getTypeOfSymbol(symbol);\n const signatures = type === undefined ? [] : this.checker.getSignaturesOfType(type, this.ts.SignatureKind.Call);\n if (signatures.length === 0) {\n this.warnings.push(`${path} is not a function, so it is not a command an agent can send.`);\n return null;\n }\n if (signatures.length > 1) {\n this.warnings.push(`${path} is overloaded; only its first signature is described.`);\n }\n const signature = signatures[0];\n const tags = this.checker.getJsDocTagsOfSymbol(symbol);\n const parameters = signature.getParameters();\n\n this.defs = new Map();\n const properties: Record<string, JsonSchema> = {};\n const required: string[] = [];\n this.inCommand = true;\n parameters.forEach((parameter, index) => {\n const parameterType = this.checker.getTypeOfSymbol(parameter);\n const optional = parameterType !== undefined && this.includesUndefined(parameterType);\n const schema = parameterType === undefined ? {} : this.describe(parameterType, `${path}(${parameter.name})`);\n const description = this.parameterDescription(parameter, tags);\n properties[parameter.name] = description === '' ? schema : { ...schema, description };\n const rest = signature.hasRestParameter && index === parameters.length - 1;\n if (!optional && !rest) {\n required.push(parameter.name);\n }\n });\n this.inCommand = false;\n const input: JsonSchema = { type: 'object', properties, additionalProperties: false };\n if (required.length > 0) {\n input.required = required;\n }\n if (this.defs.size > 0) {\n input.$defs = Object.fromEntries(this.defs);\n }\n\n const out: CommandSchemaOut = { parameters: parameters.map(parameter => parameter.name), input };\n const description = this.checker.getDocumentationCommentOfSymbol(symbol);\n if (description !== '') {\n out.description = description;\n }\n if (signature.hasRestParameter) {\n out.rest = true;\n }\n for (const annotation of ['destructive', 'idempotent', 'confirm', 'hidden'] as const) {\n if (tags.some(tag => tag.name === annotation)) {\n out[annotation] = true;\n }\n }\n return out;\n }\n\n /**\n * A parameter's own comment, or failing that its `@param` line.\n *\n * The checker attaches `@param` text to the parameter of a method\n * signature. A command written as a property, `move: (from: number)\n * => void`, keeps its comment on the property, so the tag is read\n * from there instead.\n */\n private parameterDescription(parameter: TsApi.Symbol, tags: readonly TsApi.JSDocTagInfo[]): string {\n const own = this.checker.getDocumentationCommentOfSymbol(parameter);\n if (own !== '') {\n return own;\n }\n for (const tag of tags) {\n const match = tag.name === 'param' ? /^\\s*(\\S+)\\s*(?:-\\s*)?([\\s\\S]*)$/.exec(tag.text ?? '') : null;\n if (match !== null && match[1] === parameter.name) {\n return match[2].trim();\n }\n }\n return '';\n }\n\n private includesUndefined(type: TsApi.Type): boolean {\n const { Undefined, Void } = this.flags;\n if (type.isUnionType()) {\n return (type.getTypes() ?? []).some(member => (member.flags & (Undefined | Void)) !== 0);\n }\n return (type.flags & (Undefined | Void)) !== 0;\n }\n\n private describe(type: TsApi.Type, path: string): JsonSchema {\n const f = this.flags;\n const flags = type.flags;\n\n if (flags & (f.Any | f.Unknown)) {\n return {};\n }\n if (flags & f.Never) {\n this.warnings.push(\n `${path} is never, which describes no value. An empty array literal is never[] unless it says what it holds: write \\`[] as Row[]\\`.`\n );\n return { not: {} };\n }\n if (flags & (f.String | f.TemplateLiteral | f.StringMapping)) {\n return { type: 'string' };\n }\n if (flags & f.Number) {\n return { type: 'number' };\n }\n if (flags & f.Boolean) {\n return { type: 'boolean' };\n }\n if (flags & f.Null) {\n return { type: 'null' };\n }\n if (flags & (f.Undefined | f.Void)) {\n return {};\n }\n if (flags & f.BooleanLiteral) {\n return { const: (type as TsApi.IntrinsicType).intrinsicName === 'true' };\n }\n if (flags & (f.StringLiteral | f.NumberLiteral)) {\n return { const: (type as TsApi.LiteralType).value };\n }\n if (flags & (f.BigInt | f.BigIntLiteral | f.ESSymbol | f.UniqueESSymbol)) {\n return this.unplain(path, flags & f.BigIntLike ? 'a bigint' : 'a symbol');\n }\n if (type.isUnionType()) {\n return this.union(type, path);\n }\n if (flags & f.TypeParameter) {\n return {};\n }\n if (flags & (f.Object | f.Intersection)) {\n // An array is a container rather than a type that can contain\n // itself: `readonly Row[]` in a view and in `Row.children` is one\n // type, and treating it as named would put the array in `$defs`\n // instead of the row.\n if (this.checker.isArrayType(type)) {\n const [element] = this.checker.getTypeArguments(type as TsApi.TypeReference);\n return { type: 'array', items: element === undefined ? {} : this.describe(element, `${path}[]`) };\n }\n if (this.checker.isTupleType(type)) {\n return this.tuple(type as TsApi.TypeReference, path);\n }\n return this.named(type, path);\n }\n this.warnings.push(`${path} has a type this reader does not describe, so it is left open.`);\n return {};\n }\n\n /**\n * A union, with `undefined` taken out (optionality is said by\n * `required`), `true | false` folded back into `boolean`, and a union\n * of nothing but literals written as an `enum`.\n */\n private union(type: TsApi.UnionType, path: string): JsonSchema {\n const f = this.flags;\n let members = (type.getTypes() ?? []).filter(member => (member.flags & (f.Undefined | f.Void)) === 0);\n const booleans = members.filter(member => member.flags & f.BooleanLiteral);\n const schemas: JsonSchema[] = [];\n if (booleans.length === 2) {\n members = members.filter(member => !(member.flags & f.BooleanLiteral));\n schemas.push({ type: 'boolean' });\n }\n if (\n members.length > 0 &&\n schemas.length === 0 &&\n members.every(member => member.flags & (f.StringLiteral | f.NumberLiteral))\n ) {\n return { enum: members.map(member => (member as TsApi.LiteralType).value) };\n }\n // `null` last, so `string | null` reads as a string that may be absent.\n members.sort((a, b) => Number((a.flags & f.Null) !== 0) - Number((b.flags & f.Null) !== 0));\n schemas.push(...members.map(member => this.describe(member, path)));\n return schemas.length === 1 ? schemas[0] : { anyOf: schemas };\n }\n\n /**\n * An object type, described once however often it appears, and by\n * reference when it contains itself.\n */\n private named(type: TsApi.Type, path: string): JsonSchema {\n const id = type.id;\n if (this.inProgress.has(id)) {\n this.recursive.add(id);\n return { $ref: `#/$defs/${this.defName(type)}` };\n }\n this.inProgress.add(id);\n const schema = this.object(type, path);\n this.inProgress.delete(id);\n if (this.recursive.has(id)) {\n const name = this.defName(type);\n this.defs.set(name, schema);\n return { $ref: `#/$defs/${name}` };\n }\n return schema;\n }\n\n private defName(type: TsApi.Type): string {\n let name = this.defNames.get(type.id);\n if (name === undefined) {\n const base = (type.getAliasSymbol() ?? type.getSymbol())?.name ?? 'Type';\n const taken = new Set(this.defNames.values());\n name = taken.has(base) ? `${base}${type.id}` : base;\n this.defNames.set(type.id, name);\n }\n return name;\n }\n\n private object(type: TsApi.Type, path: string): JsonSchema {\n const checker = this.checker;\n const symbolName = type.getSymbol()?.name;\n if (symbolName !== undefined && this.inCommand && BINARY.has(symbolName)) {\n return { type: 'string', contentEncoding: 'base64', 'x-gesso-binary': symbolName };\n }\n if (symbolName !== undefined && NOT_PLAIN.has(symbolName)) {\n return this.unplain(path, `a ${symbolName}`);\n }\n if ((type as TsApi.ObjectType).objectFlags & this.ts.ObjectFlags.Class) {\n return this.unplain(path, `an instance of ${symbolName ?? 'a class'}`);\n }\n if (checker.getSignaturesOfType(type, this.ts.SignatureKind.Call).length > 0) {\n return this.unplain(path, 'a function');\n }\n\n const properties: Record<string, JsonSchema> = {};\n const required: string[] = [];\n for (const property of checker.getPropertiesOfType(type)) {\n const propertyType = checker.getTypeOfSymbol(property);\n const at = `${path}.${property.name}`;\n if (propertyType === undefined) {\n properties[property.name] = {};\n continue;\n }\n const schema = this.describe(propertyType, at);\n const description = checker.getDocumentationCommentOfSymbol(property);\n properties[property.name] = description === '' ? schema : { ...schema, description };\n const optional = (property.flags & this.ts.SymbolFlags.Optional) !== 0 || this.includesUndefined(propertyType);\n if (!optional) {\n required.push(property.name);\n }\n }\n const schema: JsonSchema = { type: 'object', properties };\n if (required.length > 0) {\n schema.required = required;\n }\n const index = checker.getIndexInfosOfType(type).find(info => info.keyType.flags & this.flags.String);\n if (index !== undefined) {\n schema.additionalProperties = this.describe(index.valueType, `${path}[key]`);\n }\n return schema;\n }\n\n private tuple(type: TsApi.TypeReference, path: string): JsonSchema {\n const elements = this.checker.getTypeArguments(type);\n const elementFlags = (type.getTarget() as TsApi.TupleType).elementFlags ?? [];\n const { Required, Rest } = this.ts.ElementFlags;\n const prefixItems: JsonSchema[] = [];\n let items: JsonSchema | false = false;\n let minItems = 0;\n elements.forEach((element, index) => {\n const schema = this.describe(element, `${path}[${index}]`);\n if (elementFlags[index] & Rest) {\n items = schema;\n } else {\n prefixItems.push(schema);\n if (elementFlags[index] & Required) {\n minItems = index + 1;\n }\n }\n });\n return { type: 'array', prefixItems, items, minItems };\n }\n\n private unplain(path: string, what: string): JsonSchema {\n this.warnings.push(`${path} is ${what}, which is not plain data and cannot cross a channel.`);\n return {};\n }\n}\n","#!/usr/bin/env node\n/**\n * `gesso-channels`: describes channel contracts for a bundle Vite does\n * not build.\n *\n * gesso-channels src/shared/Counter.ts --out src/shared/channels.described.ts\n * gesso-channels src/shared/*.ts --out src/shared/channels.described.ts --check\n *\n * `gesso-vite-plugin` describes a contract as Vite bundles it. An\n * Electrobun main process is bundled by Electrobun's own build, which\n * takes no plugins, so its channels would reach an agent undescribed:\n * no descriptions, and arguments as a positional list. This reads the\n * same contracts with the same checker and writes a module that\n * describes them when imported; the main process imports it once.\n *\n * `--check` writes nothing and fails when the module on disk is not\n * what it would write, for a gate that wants to know a contract\n * changed without the module following.\n *\n * It needs TypeScript 7 in the project, whose checker does the reading.\n */\nimport { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\n\nimport { ContractReader, describedModule, type TypeScriptApi } from '../src/contracts.ts';\n\nconst USAGE = `Usage: gesso-channels <contract.ts>... --out <file> [--check]\n\nWrites a module that attaches a JSON Schema to every channel the\ncontracts export, for a bundle gesso-vite-plugin does not build, such as\nan Electrobun main process. Import the module once from that bundle.\n\n --out <file> Where to write the module.\n --check Write nothing; fail if the module is out of date.\n`;\n\nfunction fail(message: string): never {\n console.error(message);\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const files: string[] = [];\n let out: string | undefined;\n let check = false;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === '--out') {\n out = args[++i];\n } else if (arg === '--check') {\n check = true;\n } else if (arg === '-h' || arg === '--help') {\n console.log(USAGE);\n return;\n } else if (arg.startsWith('-')) {\n fail(`Unknown option ${arg}.\\n\\n${USAGE}`);\n } else {\n files.push(arg);\n }\n }\n if (out === undefined || files.length === 0) {\n fail(USAGE);\n }\n\n let ts: TypeScriptApi;\n try {\n ts = (await import('typescript/unstable/sync')) as TypeScriptApi;\n } catch {\n fail('gesso-channels needs TypeScript 7 or later in the project: npm install --save-dev typescript@^7.');\n }\n\n // Real paths, because the checker knows files by where they really\n // are, and a symlinked temporary directory would otherwise read as a\n // file no project includes.\n const cwd = realpathSync(process.cwd());\n const outFile = resolve(realpathSync(dirname(resolve(cwd, out))), out.split(/[\\\\/]/).pop()!);\n const reader = new ContractReader(ts, cwd);\n const contracts = [];\n let warned = false;\n try {\n for (const file of files) {\n const path = realpathSync(resolve(cwd, file));\n const reading = reader.read(path);\n for (const warning of reading.warnings) {\n console.warn(`gesso-channels: ${warning}`);\n warned = true;\n }\n contracts.push({ file: path, channels: reading.channels });\n }\n } finally {\n reader.close();\n }\n\n const described = contracts.reduce((count, contract) => count + contract.channels.size, 0);\n if (described === 0) {\n fail(`None of ${files.join(', ')} exports a channel.`);\n }\n const module = describedModule(contracts, outFile);\n if (check) {\n const current = existsSync(outFile) ? readFileSync(outFile, 'utf8') : null;\n if (current !== module) {\n fail(`${out} is out of date. Run gesso-channels without --check to write it.`);\n }\n console.log(`${out} describes ${described} channel${described === 1 ? '' : 's'} and is current.`);\n return;\n }\n writeFileSync(outFile, module);\n console.log(\n `Wrote ${out}: ${described} channel${described === 1 ? '' : 's'} described${warned ? ', with warnings above' : ''}.`\n );\n}\n\nvoid main();\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,112 @@
|
|
|
1
1
|
import { Plugin } from "vite";
|
|
2
|
+
import * as TsApi from "typescript/unstable/sync";
|
|
3
|
+
//#region src/contracts.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Channel contracts, read by the type checker and written back as JSON
|
|
6
|
+
* Schema.
|
|
7
|
+
*
|
|
8
|
+
* A contract module already says everything an agent driving the
|
|
9
|
+
* application would need to know: the view's keys and their types, each
|
|
10
|
+
* command's parameters and their types, and, where the author wrote
|
|
11
|
+
* them, what each one is for. All of it is types and comments, and
|
|
12
|
+
* both are gone by the time the module runs. This puts them back, as a
|
|
13
|
+
* `describeChannel(token, schema)` call appended to the module, so the
|
|
14
|
+
* token carries its own description wherever it is imported.
|
|
15
|
+
*
|
|
16
|
+
* The checker rather than the syntax, because a contract names its
|
|
17
|
+
* types where it likes: `ProductRow` from another file, a status union
|
|
18
|
+
* behind an alias, a view written as an object literal whose type is
|
|
19
|
+
* inferred. Only the checker resolves all of those the way the compiler
|
|
20
|
+
* that checked the application did. TypeScript 7's checker runs in a
|
|
21
|
+
* separate process behind an API its package calls unstable, so all of
|
|
22
|
+
* it is in this file, and the rest of the plugin sees one function.
|
|
23
|
+
*
|
|
24
|
+
* What the comments carry:
|
|
25
|
+
*
|
|
26
|
+
* - The JSDoc on the exported token is the channel's description, on
|
|
27
|
+
* a view key is that key's, and on a command is the command's.
|
|
28
|
+
* - `@param name text` describes a parameter.
|
|
29
|
+
* - `@destructive`, `@idempotent`, `@confirm` and `@hidden` on a
|
|
30
|
+
* command are its annotations; `CommandSchema` says what each means.
|
|
31
|
+
*
|
|
32
|
+
* And what it will not describe: a value that is not plain data, which
|
|
33
|
+
* cannot cross a channel anyway. A function, a `Date`, a `Map`, a class
|
|
34
|
+
* instance. Each is reported, by its path in the contract, and
|
|
35
|
+
* described as `{}` so the rest of the schema still stands.
|
|
36
|
+
*/
|
|
37
|
+
/** The parts of `typescript/unstable/sync` this file uses, values included. */
|
|
38
|
+
type TypeScriptApi = typeof TsApi;
|
|
39
|
+
/** A channel's description, in the shape `describeChannel` takes. */
|
|
40
|
+
interface ChannelSchemaOut {
|
|
41
|
+
description?: string;
|
|
42
|
+
view: JsonSchema;
|
|
43
|
+
commands: Record<string, CommandSchemaOut>;
|
|
44
|
+
}
|
|
45
|
+
interface CommandSchemaOut {
|
|
46
|
+
description?: string;
|
|
47
|
+
parameters: string[];
|
|
48
|
+
rest?: true;
|
|
49
|
+
input: JsonSchema;
|
|
50
|
+
destructive?: true;
|
|
51
|
+
idempotent?: true;
|
|
52
|
+
confirm?: true;
|
|
53
|
+
hidden?: true;
|
|
54
|
+
}
|
|
55
|
+
type JsonSchema = Record<string, unknown>;
|
|
56
|
+
/** Every channel a module exports, and what could not be described. */
|
|
57
|
+
interface ContractReading {
|
|
58
|
+
/** Export name to schema, in declaration order. */
|
|
59
|
+
readonly channels: ReadonlyMap<string, ChannelSchemaOut>;
|
|
60
|
+
/** One sentence per value that could not be described, naming where it is. */
|
|
61
|
+
readonly warnings: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A module that describes channels when it is imported, for a bundle
|
|
65
|
+
* this plugin never sees.
|
|
66
|
+
*
|
|
67
|
+
* The plugin describes a contract by appending to it as Vite bundles
|
|
68
|
+
* it. An Electrobun main process is bundled by Electrobun's own build,
|
|
69
|
+
* which takes no plugins, so there the description is written ahead of
|
|
70
|
+
* time instead: `gesso-channels` reads the contracts with the same
|
|
71
|
+
* checker and writes this module, and the main process imports it once.
|
|
72
|
+
* A token is imported from its contract, so the schema lands on the
|
|
73
|
+
* same object the application serves.
|
|
74
|
+
*
|
|
75
|
+
* `outFile` and every `file` are absolute POSIX paths.
|
|
76
|
+
*/
|
|
77
|
+
declare function describedModule(contracts: readonly {
|
|
78
|
+
readonly file: string;
|
|
79
|
+
readonly channels: ReadonlyMap<string, ChannelSchemaOut>;
|
|
80
|
+
}[], outFile: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* The checker, held open across reads.
|
|
83
|
+
*
|
|
84
|
+
* Spawning it and loading a project costs about a hundred
|
|
85
|
+
* milliseconds; a question after that costs well under one. So one
|
|
86
|
+
* reader lives as long as the dev server or the build, is told which
|
|
87
|
+
* files changed between reads, and is closed at the end, because the
|
|
88
|
+
* process it holds would otherwise keep the build from exiting.
|
|
89
|
+
*/
|
|
90
|
+
declare class ContractReader {
|
|
91
|
+
private api;
|
|
92
|
+
private readonly changed;
|
|
93
|
+
/** Each contract's tsconfig, found once. */
|
|
94
|
+
private readonly configs;
|
|
95
|
+
/**
|
|
96
|
+
* @param fs Callbacks the checker reads through before the disk, for
|
|
97
|
+
* a spec that keeps its project in memory.
|
|
98
|
+
*/
|
|
99
|
+
private readonly ts;
|
|
100
|
+
private readonly cwd;
|
|
101
|
+
private readonly fs;
|
|
102
|
+
constructor(ts: TypeScriptApi, cwd: string, fs?: TsApi.APIOptions['fs']);
|
|
103
|
+
/** A file changed on disk since the last read. */
|
|
104
|
+
invalidate(file: string): void;
|
|
105
|
+
/** Every channel `file` exports, described. */
|
|
106
|
+
read(file: string): ContractReading;
|
|
107
|
+
close(): void;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
2
110
|
//#region src/render.d.ts
|
|
3
111
|
/** What the plugin found in a render worker entry, and could wire. */
|
|
4
112
|
interface RenderWiring {
|
|
@@ -148,6 +256,11 @@ interface ShellTransformOptions {
|
|
|
148
256
|
* carries no reference to `gesso-devtools`.
|
|
149
257
|
*/
|
|
150
258
|
readonly overlay: boolean;
|
|
259
|
+
/**
|
|
260
|
+
* Hand the app to the development agent bridge once it is created
|
|
261
|
+
* (default false). A dev server only.
|
|
262
|
+
*/
|
|
263
|
+
readonly agent?: boolean;
|
|
151
264
|
}
|
|
152
265
|
/**
|
|
153
266
|
* The `createApp` call in a module, when the module has one that came
|
|
@@ -177,6 +290,13 @@ declare function findShellCall(code: string, blank?: string): ShellCall | null;
|
|
|
177
290
|
* most modules do not call `createApp`.
|
|
178
291
|
*/
|
|
179
292
|
declare function transformShell(code: string, options: ShellTransformOptions): string | null;
|
|
293
|
+
/**
|
|
294
|
+
* The single-thread shell in a dev server: `createSyncApp(Root)`
|
|
295
|
+
* wrapped so the agent bridge gets the builder, which answers an agent
|
|
296
|
+
* port from the page itself. There are no workers to write and no
|
|
297
|
+
* overlay to wire here, so this is all the plugin does to such a shell.
|
|
298
|
+
*/
|
|
299
|
+
declare function transformSyncShell(code: string): string | null;
|
|
180
300
|
//#endregion
|
|
181
301
|
//#region src/index.d.ts
|
|
182
302
|
/**
|
|
@@ -239,8 +359,48 @@ interface GessoPluginOptions {
|
|
|
239
359
|
* module (default true).
|
|
240
360
|
*/
|
|
241
361
|
readonly diagnostics?: boolean;
|
|
362
|
+
/**
|
|
363
|
+
* Resolve a dependency's `worker` export ahead of its `browser` one
|
|
364
|
+
* (default true). See {@link workerConditions}.
|
|
365
|
+
*/
|
|
366
|
+
readonly workerConditions?: boolean;
|
|
367
|
+
/**
|
|
368
|
+
* Describe every channel the application declares as JSON Schema,
|
|
369
|
+
* read from the contract's types and JSDoc, and attach it to the
|
|
370
|
+
* token with `describeChannel` (default true). It is what lets an AI
|
|
371
|
+
* agent, or anything else that meets the application only at run
|
|
372
|
+
* time, ask a channel what it holds and what its commands take.
|
|
373
|
+
*
|
|
374
|
+
* Needs TypeScript 7, whose checker does the reading. Without it the
|
|
375
|
+
* plugin says so once and carries on undescribed.
|
|
376
|
+
*/
|
|
377
|
+
readonly channelSchemas?: boolean;
|
|
378
|
+
/**
|
|
379
|
+
* Serve MCP at `/__gesso/mcp` while the dev server runs, so an AI
|
|
380
|
+
* agent can read the open page's channels and send their commands
|
|
381
|
+
* (default true). Development only: a build carries none of it.
|
|
382
|
+
*/
|
|
383
|
+
readonly agent?: boolean;
|
|
242
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* The export conditions a Gesso application resolves its dependencies
|
|
387
|
+
* with: `worker` ahead of Vite's defaults.
|
|
388
|
+
*
|
|
389
|
+
* A Gesso application's code runs in workers, and Vite resolves every
|
|
390
|
+
* dependency with the `browser` condition, which is the right call for
|
|
391
|
+
* a page and the wrong one for a worker: a package's browser build may
|
|
392
|
+
* reach for `document`. `decode-named-character-reference`, which every
|
|
393
|
+
* markdown parser built on micromark imports, does exactly that, and a
|
|
394
|
+
* render worker that imported one died on start with "document is not
|
|
395
|
+
* defined". The same package publishes a `worker` build, as packages
|
|
396
|
+
* with a DOM-only browser build tend to. A package's own export order
|
|
397
|
+
* decides between the two, and those list `worker` first.
|
|
398
|
+
*
|
|
399
|
+
* The main thread resolves the same way. A worker build runs in a page
|
|
400
|
+
* as well, and the shell imports little beyond Gesso itself.
|
|
401
|
+
*/
|
|
402
|
+
declare const workerConditions: readonly string[];
|
|
243
403
|
declare function gesso(options?: GessoPluginOptions): Plugin;
|
|
244
404
|
//#endregion
|
|
245
|
-
export { type CallSite, GessoPluginOptions, type RenderWiring, type ShellCall, type ShellTransformOptions, type WorkerEntries, blankLiterals, gesso as default, gesso, findCall, findCalls, findShellCall, firstArgumentName, importSources, transformRenderWorker, transformShell };
|
|
405
|
+
export { type CallSite, ContractReader, type ContractReading, GessoPluginOptions, type RenderWiring, type ShellCall, type ShellTransformOptions, type WorkerEntries, blankLiterals, gesso as default, gesso, describedModule, findCall, findCalls, findShellCall, firstArgumentName, importSources, transformRenderWorker, transformShell, transformSyncShell, workerConditions };
|
|
246
406
|
//# sourceMappingURL=index.d.ts.map
|