[Experimental] typelex cli #3

merged
opened by danabra.mov targeting main from cli
typelex compile xyz.statusphere.*

This command:

  1. Scans lexicons/ for all external lexicons (not matching xyz.statusphere)
  2. Generates typelex/externals.tsp with @external stubs
  3. Compiles typelex/main.tsp to lexicons/ (or custom output via --out)
-5
packages/example/tspconfig.yaml
···
-
emit:
-
- "@typelex/emitter"
-
options:
-
"@typelex/emitter":
-
output-dir: "./lexicons"
+2 -1
package.json
···
"example": "pnpm --filter @typelex/example build",
"playground": "pnpm --filter @typelex/playground dev",
"validate": "pnpm build && pnpm run validate-lexicons && pnpm test",
-
"validate-lexicons": "node scripts/validate-lexicons.js"
+
"validate-lexicons": "node scripts/validate-lexicons.js",
+
"cli": "pnpm --filter @typelex/cli"
},
"repository": {
"type": "git",
+3
packages/cli/.gitignore
···
+
dist
+
node_modules
+
*.log
+66
packages/cli/README.md
···
+
# @typelex/cli
+
+
Experimental CLI for typelex
+
+
## Installation
+
+
```bash
+
pnpm add -D @typelex/cli @typelex/emitter
+
```
+
+
## Usage
+
+
```bash
+
typelex compile xyz.statusphere.*
+
```
+
+
This command:
+
1. Scans `lexicons/` for all external lexicons (not matching `xyz.statusphere`)
+
2. Generates `typelex/externals.tsp` with `@external` stubs
+
3. Compiles `typelex/main.tsp` to `lexicons/` (or custom output via `--out`)
+
+
Fixed paths:
+
- Entry point: `typelex/main.tsp`
+
- Externals: `typelex/externals.tsp`
+
+
## Example
+
+
```typescript
+
// typelex/main.tsp
+
import "@typelex/emitter";
+
import "./externals.tsp";
+
+
namespace xyz.statusphere.defs {
+
model StatusView {
+
@required uri: atUri;
+
@required status: string;
+
@required profile: app.bsky.actor.defs.ProfileView;
+
}
+
}
+
```
+
+
```bash
+
typelex compile 'xyz.statusphere.*'
+
```
+
+
The CLI scans `lexicons/` for external types and auto-generates `typelex/externals.tsp` with stubs
+
+
### Integration
+
+
```json
+
{
+
"scripts": {
+
"build:lexicons": "typelex compile 'xyz.statusphere.*'",
+
"build:codegen": "lex gen-server --yes ./src lexicons/xyz/statusphere/*.json"
+
}
+
}
+
```
+
+
## Options
+
+
- `--out <directory>` - Output directory for generated Lexicon files (default: `./lexicons`)
+
- `--watch` - Watch mode for continuous compilation
+
+
## License
+
+
MIT
+40
packages/cli/package.json
···
+
{
+
"name": "@typelex/cli",
+
"version": "0.2.0",
+
"description": "CLI for typelex - TypeSpec-based IDL for ATProto Lexicons",
+
"main": "dist/index.js",
+
"type": "module",
+
"bin": {
+
"typelex": "dist/cli.js"
+
},
+
"files": [
+
"dist",
+
"src"
+
],
+
"scripts": {
+
"build": "tsc",
+
"clean": "rm -rf dist",
+
"watch": "tsc --watch",
+
"prepublishOnly": "npm run build"
+
},
+
"keywords": [
+
"typespec",
+
"atproto",
+
"lexicon",
+
"cli"
+
],
+
"author": "Dan Abramov <dan.abramov@gmail.com>",
+
"license": "MIT",
+
"dependencies": {
+
"@typespec/compiler": "^1.4.0",
+
"yargs": "^18.0.0"
+
},
+
"devDependencies": {
+
"@types/node": "^20.0.0",
+
"@types/yargs": "^17.0.33",
+
"typescript": "^5.0.0"
+
},
+
"peerDependencies": {
+
"@typelex/emitter": "^0.2.0"
+
}
+
}
+64
packages/cli/src/cli.ts
···
+
#!/usr/bin/env node
+
import yargs from "yargs";
+
import { hideBin } from "yargs/helpers";
+
import { compileCommand } from "./commands/compile.js";
+
+
async function main() {
+
await yargs(hideBin(process.argv))
+
.scriptName("typelex")
+
.usage("$0 compile <namespace>")
+
.command(
+
"compile <namespace>",
+
"Compile TypeSpec files to Lexicon JSON",
+
(yargs) => {
+
return yargs
+
.positional("namespace", {
+
describe: "Primary namespace pattern (e.g., app.bsky.*)",
+
type: "string",
+
demandOption: true,
+
})
+
.option("out", {
+
describe: "Output directory for generated Lexicon files (relative to cwd)",
+
type: "string",
+
default: "./lexicons",
+
});
+
},
+
async (argv) => {
+
const options: Record<string, unknown> = {};
+
if (argv.watch) {
+
options.watch = true;
+
}
+
if (argv.out) {
+
options.out = argv.out;
+
}
+
await compileCommand(argv.namespace, options);
+
}
+
)
+
.option("watch", {
+
describe: "Watch mode",
+
type: "boolean",
+
default: false,
+
})
+
.demandCommand(1, "You must specify a command")
+
.help()
+
.version()
+
.fail((msg, err) => {
+
if (err) {
+
console.error(err);
+
} else {
+
console.error(msg);
+
}
+
process.exit(1);
+
}).argv;
+
}
+
+
process.on("unhandledRejection", (error: unknown) => {
+
console.error("Unhandled promise rejection!");
+
console.error(error);
+
process.exit(1);
+
});
+
+
main().catch((error) => {
+
console.error(error);
+
process.exit(1);
+
});
+68
packages/cli/src/commands/compile.ts
···
+
import { resolve } from "path";
+
import { spawn } from "child_process";
+
import { generateExternalsFile } from "../utils/externals-generator.js";
+
import { ensureMainImports } from "../utils/ensure-imports.js";
+
+
/**
+
* Compile TypeSpec files to Lexicon JSON
+
*
+
* @param namespace - Primary namespace pattern (e.g., "app.bsky.*")
+
* @param options - Additional compiler options
+
*/
+
export async function compileCommand(
+
namespace: string,
+
options: Record<string, unknown> = {}
+
): Promise<void> {
+
const cwd = process.cwd();
+
const outDir = (options.out as string) || "./lexicons";
+
+
// Validate that output directory ends with 'lexicons'
+
const normalizedPath = outDir.replace(/\\/g, '/').replace(/\/+$/, '');
+
if (!normalizedPath.endsWith('/lexicons') && normalizedPath !== 'lexicons' && normalizedPath !== './lexicons') {
+
console.error(`Error: Output directory must end with 'lexicons'`);
+
console.error(`Got: ${outDir}`);
+
console.error(`Valid examples: ./lexicons, ../../lexicons, /path/to/lexicons`);
+
process.exit(1);
+
}
+
+
// Generate externals first (scans the output directory for external lexicons)
+
await generateExternalsFile(namespace, cwd, outDir);
+
+
// Ensure required imports are present in main.tsp
+
await ensureMainImports(cwd);
+
+
// Compile TypeSpec using the TypeSpec CLI
+
const entrypoint = resolve(cwd, "typelex/main.tsp");
+
const args = [
+
"compile",
+
entrypoint,
+
"--emit",
+
"@typelex/emitter",
+
"--option",
+
`@typelex/emitter.emitter-output-dir={project-root}/${outDir}`,
+
];
+
+
if (options.watch) {
+
args.push("--watch");
+
}
+
+
return new Promise((resolve, reject) => {
+
const tsp = spawn("tsp", args, {
+
cwd,
+
stdio: "inherit",
+
});
+
+
tsp.on("close", (code) => {
+
if (code === 0) {
+
resolve();
+
} else {
+
process.exit(code ?? 1);
+
}
+
});
+
+
tsp.on("error", (err) => {
+
console.error("Failed to start TypeSpec compiler:", err);
+
reject(err);
+
});
+
});
+
}
+1
packages/cli/src/index.ts
···
+
export { compileCommand } from "./commands/compile.js";
+38
packages/cli/src/utils/ensure-imports.ts
···
+
import { readFile } from "fs/promises";
+
import { resolve } from "path";
+
+
const REQUIRED_FIRST_LINE = 'import "@typelex/emitter";';
+
const REQUIRED_SECOND_LINE = 'import "./externals.tsp";';
+
+
/**
+
* Validates that main.tsp starts with the required imports.
+
* Fails the build if the first two lines are not exactly as expected.
+
*
+
* @param cwd - Current working directory
+
*/
+
export async function ensureMainImports(cwd: string): Promise<void> {
+
const mainPath = resolve(cwd, "typelex/main.tsp");
+
+
try {
+
const content = await readFile(mainPath, "utf-8");
+
const lines = content.split("\n");
+
+
if (lines[0]?.trim() !== REQUIRED_FIRST_LINE) {
+
console.error(`Error: main.tsp must start with: ${REQUIRED_FIRST_LINE}`);
+
console.error(`Found: ${lines[0] || "(empty line)"}`);
+
process.exit(1);
+
}
+
+
if (lines[1]?.trim() !== REQUIRED_SECOND_LINE) {
+
console.error(`Error: Line 2 of main.tsp must be: ${REQUIRED_SECOND_LINE}`);
+
console.error(`Found: ${lines[1] || "(empty line)"}`);
+
process.exit(1);
+
}
+
} catch (err) {
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
+
console.error("Error: typelex/main.tsp not found");
+
process.exit(1);
+
}
+
throw err;
+
}
+
}
+105
packages/cli/src/utils/externals-generator.ts
···
+
import { resolve } from "path";
+
import { writeFile, mkdir } from "fs/promises";
+
import { findExternalLexicons, LexiconDoc, isTokenDef, isModelDef } from "./lexicon.js";
+
+
/**
+
* Convert camelCase to PascalCase
+
*/
+
function toPascalCase(str: string): string {
+
return str.charAt(0).toUpperCase() + str.slice(1);
+
}
+
+
/**
+
* Extract namespace prefix from pattern (e.g., "app.bsky.*" -> "app.bsky")
+
*/
+
function getNamespacePrefix(pattern: string): string {
+
if (!pattern.endsWith(".*")) {
+
throw new Error(`Namespace pattern must end with .*: ${pattern}`);
+
}
+
return pattern.slice(0, -2);
+
}
+
+
/**
+
* Generate TypeSpec external definitions from lexicon documents
+
*/
+
function generateExternalsCode(lexicons: Map<string, LexiconDoc>): string {
+
const lines: string[] = [];
+
+
lines.push('import "@typelex/emitter";');
+
lines.push("");
+
lines.push("// Generated by typelex");
+
lines.push("// This file is auto-generated. Do not edit manually.");
+
lines.push("");
+
+
// Sort namespaces for consistent output
+
const sortedNamespaces = Array.from(lexicons.entries()).sort(([a], [b]) =>
+
a.localeCompare(b)
+
);
+
+
for (const [nsid, lexicon] of sortedNamespaces) {
+
lines.push("@external");
+
// Escape reserved keywords in namespace (like 'record')
+
const escapedNsid = nsid.replace(/\b(record|union|enum|interface|namespace|model|op|import|using|extends|is|scalar|alias|if|else|return|void|never|unknown|any|true|false|null)\b/g, '`$1`');
+
lines.push(`namespace ${escapedNsid} {`);
+
+
// Sort definitions for consistent output
+
const sortedDefs = Object.entries(lexicon.defs).sort(([a], [b]) =>
+
a.localeCompare(b)
+
);
+
+
for (const [defName, def] of sortedDefs) {
+
if (!isModelDef(def)) {
+
continue;
+
}
+
+
const modelName = toPascalCase(defName);
+
const isToken = isTokenDef(def);
+
+
if (isToken) {
+
lines.push(` @token model ${modelName} { }`);
+
} else {
+
lines.push(` model ${modelName} { }`);
+
}
+
}
+
+
lines.push("}");
+
lines.push("");
+
}
+
+
return lines.join("\n");
+
}
+
+
/**
+
* Generate externals.tsp file for the given namespace pattern
+
*/
+
export async function generateExternalsFile(
+
namespacePattern: string,
+
cwd: string,
+
outDir: string = "./lexicons"
+
): Promise<void> {
+
try {
+
const prefix = getNamespacePrefix(namespacePattern);
+
const lexiconsDir = resolve(cwd, outDir);
+
const outputFile = resolve(cwd, "typelex/externals.tsp");
+
+
const externals = await findExternalLexicons(lexiconsDir, prefix);
+
+
if (externals.size === 0) {
+
// No externals, create empty file
+
await mkdir(resolve(cwd, "typelex"), { recursive: true });
+
await writeFile(
+
outputFile,
+
'import "@typelex/emitter";\n\n// Generated by typelex\n// No external lexicons found\n',
+
"utf-8"
+
);
+
return;
+
}
+
+
const code = generateExternalsCode(externals);
+
await mkdir(resolve(cwd, "typelex"), { recursive: true });
+
await writeFile(outputFile, code, "utf-8");
+
} catch (error) {
+
// Re-throw with better context
+
throw new Error(`Failed to generate externals: ${error instanceof Error ? error.message : String(error)}`);
+
}
+
}
+78
packages/cli/src/utils/lexicon.ts
···
+
import { readFile } from "fs/promises";
+
import { resolve } from "path";
+
import { globby } from "globby";
+
+
export interface LexiconDef {
+
type: string;
+
[key: string]: unknown;
+
}
+
+
export interface LexiconDoc {
+
lexicon: number;
+
id: string;
+
defs: Record<string, LexiconDef>;
+
}
+
+
/**
+
* Read and parse a lexicon JSON file
+
*/
+
export async function readLexicon(path: string): Promise<LexiconDoc> {
+
const content = await readFile(path, "utf-8");
+
return JSON.parse(content);
+
}
+
+
/**
+
* Find all lexicon files in a directory
+
*/
+
export async function findLexicons(dir: string): Promise<string[]> {
+
try {
+
const pattern = resolve(dir, "**/*.json");
+
return await globby(pattern);
+
} catch {
+
// If directory doesn't exist, return empty array
+
return [];
+
}
+
}
+
+
/**
+
* Extract external lexicons that don't match the given namespace
+
*/
+
export async function findExternalLexicons(
+
lexiconsDir: string,
+
primaryNamespace: string
+
): Promise<Map<string, LexiconDoc>> {
+
const files = await findLexicons(lexiconsDir);
+
const externals = new Map<string, LexiconDoc>();
+
+
for (const file of files) {
+
const lexicon = await readLexicon(file);
+
if (!lexicon.id.startsWith(primaryNamespace)) {
+
externals.set(lexicon.id, lexicon);
+
}
+
}
+
+
return externals;
+
}
+
+
/**
+
* Check if a definition is a token type
+
*/
+
export function isTokenDef(def: LexiconDef): boolean {
+
return def.type === "token";
+
}
+
+
/**
+
* Check if a definition should become a model in TypeSpec
+
*/
+
export function isModelDef(def: LexiconDef): boolean {
+
const type = def.type;
+
return (
+
type === "object" ||
+
type === "token" ||
+
type === "record" ||
+
type === "union" ||
+
type === "string" ||
+
type === "bytes" ||
+
type === "cid-link"
+
);
+
}
+20
packages/cli/tsconfig.json
···
+
{
+
"compilerOptions": {
+
"target": "ES2022",
+
"module": "Node16",
+
"moduleResolution": "Node16",
+
"lib": ["ES2022"],
+
"outDir": "dist",
+
"rootDir": "src",
+
"declaration": true,
+
"declarationMap": true,
+
"sourceMap": true,
+
"strict": true,
+
"esModuleInterop": true,
+
"skipLibCheck": true,
+
"forceConsistentCasingInFileNames": true,
+
"resolveJsonModule": true
+
},
+
"include": ["src/**/*"],
+
"exclude": ["node_modules", "dist"]
+
}
+4
packages/cli/typelex/externals.tsp
···
+
import "@typelex/emitter";
+
+
// Generated by typelex
+
// No external lexicons found
+45 -6
packages/example/src/index.ts
···
createServer as createXrpcServer,
} from '@atproto/xrpc-server'
import { schemas } from './lexicons.js'
+
import * as XyzStatusphereGetStatuses from './types/xyz/statusphere/getStatuses.js'
+
import * as XyzStatusphereGetUser from './types/xyz/statusphere/getUser.js'
+
import * as XyzStatusphereSendStatus from './types/xyz/statusphere/sendStatus.js'
export function createServer(options?: XrpcOptions): Server {
return new Server(options)
···
export class Server {
xrpc: XrpcServer
-
app: AppNS
+
xyz: XyzNS
constructor(options?: XrpcOptions) {
this.xrpc = createXrpcServer(schemas, options)
-
this.app = new AppNS(this)
+
this.xyz = new XyzNS(this)
}
}
-
export class AppNS {
+
export class XyzNS {
_server: Server
-
example: AppExampleNS
+
statusphere: XyzStatusphereNS
constructor(server: Server) {
this._server = server
-
this.example = new AppExampleNS(server)
+
this.statusphere = new XyzStatusphereNS(server)
}
}
-
export class AppExampleNS {
+
export class XyzStatusphereNS {
_server: Server
constructor(server: Server) {
this._server = server
}
+
+
getStatuses<A extends Auth = void>(
+
cfg: MethodConfigOrHandler<
+
A,
+
XyzStatusphereGetStatuses.QueryParams,
+
XyzStatusphereGetStatuses.HandlerInput,
+
XyzStatusphereGetStatuses.HandlerOutput
+
>,
+
) {
+
const nsid = 'xyz.statusphere.getStatuses' // @ts-ignore
+
return this._server.xrpc.method(nsid, cfg)
+
}
+
+
getUser<A extends Auth = void>(
+
cfg: MethodConfigOrHandler<
+
A,
+
XyzStatusphereGetUser.QueryParams,
+
XyzStatusphereGetUser.HandlerInput,
+
XyzStatusphereGetUser.HandlerOutput
+
>,
+
) {
+
const nsid = 'xyz.statusphere.getUser' // @ts-ignore
+
return this._server.xrpc.method(nsid, cfg)
+
}
+
+
sendStatus<A extends Auth = void>(
+
cfg: MethodConfigOrHandler<
+
A,
+
XyzStatusphereSendStatus.QueryParams,
+
XyzStatusphereSendStatus.HandlerInput,
+
XyzStatusphereSendStatus.HandlerOutput
+
>,
+
) {
+
const nsid = 'xyz.statusphere.sendStatus' // @ts-ignore
+
return this._server.xrpc.method(nsid, cfg)
+
}
}
+100 -153
packages/example/src/lexicons.ts
···
import { type $Typed, is$typed, maybe$typed } from './util.js'
export const schemaDict = {
-
AppExampleDefs: {
+
XyzStatusphereDefs: {
lexicon: 1,
-
id: 'app.example.defs',
+
id: 'xyz.statusphere.defs',
defs: {
-
postRef: {
+
statusView: {
type: 'object',
properties: {
uri: {
type: 'string',
-
description: 'AT URI of the post',
+
format: 'at-uri',
},
-
cid: {
+
status: {
type: 'string',
-
description: 'CID of the post',
+
maxLength: 32,
+
minLength: 1,
+
maxGraphemes: 1,
},
-
},
-
description: 'Reference to a post',
-
required: ['uri', 'cid'],
-
},
-
replyRef: {
-
type: 'object',
-
properties: {
-
root: {
-
type: 'ref',
-
ref: 'lex:app.example.defs#postRef',
-
description: 'Root post in the thread',
+
createdAt: {
+
type: 'string',
+
format: 'datetime',
},
-
parent: {
+
profile: {
type: 'ref',
-
ref: 'lex:app.example.defs#postRef',
-
description: 'Direct parent post being replied to',
+
ref: 'lex:xyz.statusphere.defs#profileView',
},
},
-
description: 'Reference to a parent post in a reply chain',
-
required: ['root', 'parent'],
+
required: ['uri', 'status', 'createdAt', 'profile'],
},
-
entity: {
+
profileView: {
type: 'object',
properties: {
-
start: {
-
type: 'integer',
-
description: 'Start index in text',
-
},
-
end: {
-
type: 'integer',
-
description: 'End index in text',
-
},
-
type: {
+
did: {
type: 'string',
-
description: 'Entity type',
+
format: 'did',
},
-
value: {
+
handle: {
type: 'string',
-
description: 'Entity value (handle, URL, or tag)',
+
format: 'handle',
},
},
-
description: 'Text entity (mention, link, or tag)',
-
required: ['start', 'end', 'type', 'value'],
-
},
-
notificationType: {
-
type: 'string',
-
knownValues: ['like', 'repost', 'follow', 'mention', 'reply'],
-
description: 'Type of notification',
+
required: ['did', 'handle'],
},
},
},
-
AppExampleFollow: {
+
XyzStatusphereGetStatuses: {
lexicon: 1,
-
id: 'app.example.follow',
+
id: 'xyz.statusphere.getStatuses',
defs: {
main: {
-
type: 'record',
-
key: 'tid',
-
record: {
-
type: 'object',
+
type: 'query',
+
description: 'Get a list of the most recent statuses on the network.',
+
parameters: {
+
type: 'params',
properties: {
-
subject: {
-
type: 'string',
-
description: 'DID of the account being followed',
-
},
-
createdAt: {
-
type: 'string',
-
format: 'datetime',
-
description: 'When the follow was created',
+
limit: {
+
type: 'integer',
+
minimum: 1,
+
maximum: 100,
+
default: 50,
},
},
-
required: ['subject', 'createdAt'],
},
-
description: 'A follow relationship',
-
},
-
},
-
},
-
AppExampleLike: {
-
lexicon: 1,
-
id: 'app.example.like',
-
defs: {
-
main: {
-
type: 'record',
-
key: 'tid',
-
record: {
-
type: 'object',
-
properties: {
-
subject: {
-
type: 'ref',
-
ref: 'lex:app.example.defs#postRef',
-
description: 'Post being liked',
-
},
-
createdAt: {
-
type: 'string',
-
format: 'datetime',
-
description: 'When the like was created',
+
output: {
+
encoding: 'application/json',
+
schema: {
+
type: 'object',
+
properties: {
+
statuses: {
+
type: 'array',
+
items: {
+
type: 'ref',
+
ref: 'lex:xyz.statusphere.defs#statusView',
+
},
+
},
},
+
required: ['statuses'],
},
-
required: ['subject', 'createdAt'],
},
-
description: 'A like on a post',
},
},
},
-
AppExamplePost: {
+
XyzStatusphereGetUser: {
lexicon: 1,
-
id: 'app.example.post',
+
id: 'xyz.statusphere.getUser',
defs: {
main: {
-
type: 'record',
-
key: 'tid',
-
record: {
-
type: 'object',
-
properties: {
-
text: {
-
type: 'string',
-
description: 'Post text content',
-
},
-
createdAt: {
-
type: 'string',
-
format: 'datetime',
-
description: 'Creation timestamp',
-
},
-
langs: {
-
type: 'array',
-
items: {
-
type: 'string',
+
type: 'query',
+
description: "Get the current user's profile and status.",
+
output: {
+
encoding: 'application/json',
+
schema: {
+
type: 'object',
+
properties: {
+
profile: {
+
type: 'ref',
+
ref: 'lex:app.bsky.actor.defs#profileView',
},
-
description: 'Languages the post is written in',
-
},
-
entities: {
-
type: 'array',
-
items: {
+
status: {
type: 'ref',
-
ref: 'lex:app.example.defs#entity',
+
ref: 'lex:xyz.statusphere.defs#statusView',
},
-
description: 'Referenced entities in the post',
-
},
-
reply: {
-
type: 'ref',
-
ref: 'lex:app.example.defs#replyRef',
-
description: 'Post the user is replying to',
},
+
required: ['profile'],
},
-
required: ['text', 'createdAt'],
},
-
description: 'A post in the feed',
},
},
},
-
AppExampleProfile: {
+
XyzStatusphereSendStatus: {
lexicon: 1,
-
id: 'app.example.profile',
+
id: 'xyz.statusphere.sendStatus',
defs: {
main: {
-
type: 'record',
-
key: 'self',
-
record: {
-
type: 'object',
-
properties: {
-
displayName: {
-
type: 'string',
-
description: 'Display name',
-
},
-
description: {
-
type: 'string',
-
description: 'Profile description',
-
},
-
avatar: {
-
type: 'string',
-
description: 'Profile avatar image',
+
type: 'procedure',
+
description: 'Send a status into the ATmosphere.',
+
input: {
+
encoding: 'application/json',
+
schema: {
+
type: 'object',
+
properties: {
+
status: {
+
type: 'string',
+
maxLength: 32,
+
minLength: 1,
+
maxGraphemes: 1,
+
},
},
-
banner: {
-
type: 'string',
-
description: 'Profile banner image',
+
required: ['status'],
+
},
+
},
+
output: {
+
encoding: 'application/json',
+
schema: {
+
type: 'object',
+
properties: {
+
status: {
+
type: 'ref',
+
ref: 'lex:xyz.statusphere.defs#statusView',
+
},
},
+
required: ['status'],
},
},
-
description: 'User profile information',
},
},
},
-
AppExampleRepost: {
+
XyzStatusphereStatus: {
lexicon: 1,
-
id: 'app.example.repost',
+
id: 'xyz.statusphere.status',
defs: {
main: {
type: 'record',
···
record: {
type: 'object',
properties: {
-
subject: {
-
type: 'ref',
-
ref: 'lex:app.example.defs#postRef',
-
description: 'Post being reposted',
+
status: {
+
type: 'string',
+
maxLength: 32,
+
minLength: 1,
+
maxGraphemes: 1,
},
createdAt: {
type: 'string',
format: 'datetime',
-
description: 'When the repost was created',
},
},
-
required: ['subject', 'createdAt'],
+
required: ['status', 'createdAt'],
},
-
description: 'A repost of another post',
},
},
},
···
}
export const ids = {
-
AppExampleDefs: 'app.example.defs',
-
AppExampleFollow: 'app.example.follow',
-
AppExampleLike: 'app.example.like',
-
AppExamplePost: 'app.example.post',
-
AppExampleProfile: 'app.example.profile',
-
AppExampleRepost: 'app.example.repost',
+
XyzStatusphereDefs: 'xyz.statusphere.defs',
+
XyzStatusphereGetStatuses: 'xyz.statusphere.getStatuses',
+
XyzStatusphereGetUser: 'xyz.statusphere.getUser',
+
XyzStatusphereSendStatus: 'xyz.statusphere.sendStatus',
+
XyzStatusphereStatus: 'xyz.statusphere.status',
} as const
-79
packages/example/src/types/app/example/defs.ts
···
-
/**
-
* GENERATED CODE - DO NOT MODIFY
-
*/
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
-
import { CID } from 'multiformats/cid'
-
import { validate as _validate } from '../../../lexicons'
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
-
-
const is$typed = _is$typed,
-
validate = _validate
-
const id = 'app.example.defs'
-
-
/** Reference to a post */
-
export interface PostRef {
-
$type?: 'app.example.defs#postRef'
-
/** AT URI of the post */
-
uri: string
-
/** CID of the post */
-
cid: string
-
}
-
-
const hashPostRef = 'postRef'
-
-
export function isPostRef<V>(v: V) {
-
return is$typed(v, id, hashPostRef)
-
}
-
-
export function validatePostRef<V>(v: V) {
-
return validate<PostRef & V>(v, id, hashPostRef)
-
}
-
-
/** Reference to a parent post in a reply chain */
-
export interface ReplyRef {
-
$type?: 'app.example.defs#replyRef'
-
root: PostRef
-
parent: PostRef
-
}
-
-
const hashReplyRef = 'replyRef'
-
-
export function isReplyRef<V>(v: V) {
-
return is$typed(v, id, hashReplyRef)
-
}
-
-
export function validateReplyRef<V>(v: V) {
-
return validate<ReplyRef & V>(v, id, hashReplyRef)
-
}
-
-
/** Text entity (mention, link, or tag) */
-
export interface Entity {
-
$type?: 'app.example.defs#entity'
-
/** Start index in text */
-
start: number
-
/** End index in text */
-
end: number
-
/** Entity type */
-
type: string
-
/** Entity value (handle, URL, or tag) */
-
value: string
-
}
-
-
const hashEntity = 'entity'
-
-
export function isEntity<V>(v: V) {
-
return is$typed(v, id, hashEntity)
-
}
-
-
export function validateEntity<V>(v: V) {
-
return validate<Entity & V>(v, id, hashEntity)
-
}
-
-
/** Type of notification */
-
export type NotificationType =
-
| 'like'
-
| 'repost'
-
| 'follow'
-
| 'mention'
-
| 'reply'
-
| (string & {})
-30
packages/example/src/types/app/example/like.ts
···
-
/**
-
* GENERATED CODE - DO NOT MODIFY
-
*/
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
-
import { CID } from 'multiformats/cid'
-
import { validate as _validate } from '../../../lexicons'
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
-
import type * as AppExampleDefs from './defs.js'
-
-
const is$typed = _is$typed,
-
validate = _validate
-
const id = 'app.example.like'
-
-
export interface Record {
-
$type: 'app.example.like'
-
subject: AppExampleDefs.PostRef
-
/** When the like was created */
-
createdAt: string
-
[k: string]: unknown
-
}
-
-
const hashRecord = 'main'
-
-
export function isRecord<V>(v: V) {
-
return is$typed(v, id, hashRecord)
-
}
-
-
export function validateRecord<V>(v: V) {
-
return validate<Record & V>(v, id, hashRecord, true)
-
}
-36
packages/example/src/types/app/example/post.ts
···
-
/**
-
* GENERATED CODE - DO NOT MODIFY
-
*/
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
-
import { CID } from 'multiformats/cid'
-
import { validate as _validate } from '../../../lexicons'
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
-
import type * as AppExampleDefs from './defs.js'
-
-
const is$typed = _is$typed,
-
validate = _validate
-
const id = 'app.example.post'
-
-
export interface Record {
-
$type: 'app.example.post'
-
/** Post text content */
-
text: string
-
/** Creation timestamp */
-
createdAt: string
-
/** Languages the post is written in */
-
langs?: string[]
-
/** Referenced entities in the post */
-
entities?: AppExampleDefs.Entity[]
-
reply?: AppExampleDefs.ReplyRef
-
[k: string]: unknown
-
}
-
-
const hashRecord = 'main'
-
-
export function isRecord<V>(v: V) {
-
return is$typed(v, id, hashRecord)
-
}
-
-
export function validateRecord<V>(v: V) {
-
return validate<Record & V>(v, id, hashRecord, true)
-
}
-34
packages/example/src/types/app/example/profile.ts
···
-
/**
-
* GENERATED CODE - DO NOT MODIFY
-
*/
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
-
import { CID } from 'multiformats/cid'
-
import { validate as _validate } from '../../../lexicons'
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
-
-
const is$typed = _is$typed,
-
validate = _validate
-
const id = 'app.example.profile'
-
-
export interface Record {
-
$type: 'app.example.profile'
-
/** Display name */
-
displayName?: string
-
/** Profile description */
-
description?: string
-
/** Profile avatar image */
-
avatar?: string
-
/** Profile banner image */
-
banner?: string
-
[k: string]: unknown
-
}
-
-
const hashRecord = 'main'
-
-
export function isRecord<V>(v: V) {
-
return is$typed(v, id, hashRecord)
-
}
-
-
export function validateRecord<V>(v: V) {
-
return validate<Record & V>(v, id, hashRecord, true)
-
}
-30
packages/example/src/types/app/example/repost.ts
···
-
/**
-
* GENERATED CODE - DO NOT MODIFY
-
*/
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
-
import { CID } from 'multiformats/cid'
-
import { validate as _validate } from '../../../lexicons'
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
-
import type * as AppExampleDefs from './defs.js'
-
-
const is$typed = _is$typed,
-
validate = _validate
-
const id = 'app.example.repost'
-
-
export interface Record {
-
$type: 'app.example.repost'
-
subject: AppExampleDefs.PostRef
-
/** When the repost was created */
-
createdAt: string
-
[k: string]: unknown
-
}
-
-
const hashRecord = 'main'
-
-
export function isRecord<V>(v: V) {
-
return is$typed(v, id, hashRecord)
-
}
-
-
export function validateRecord<V>(v: V) {
-
return validate<Record & V>(v, id, hashRecord, true)
-
}
+45
packages/example/src/types/xyz/statusphere/defs.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { CID } from 'multiformats/cid'
+
import { validate as _validate } from '../../../lexicons'
+
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
+
+
const is$typed = _is$typed,
+
validate = _validate
+
const id = 'xyz.statusphere.defs'
+
+
export interface StatusView {
+
$type?: 'xyz.statusphere.defs#statusView'
+
uri: string
+
status: string
+
createdAt: string
+
profile: ProfileView
+
}
+
+
const hashStatusView = 'statusView'
+
+
export function isStatusView<V>(v: V) {
+
return is$typed(v, id, hashStatusView)
+
}
+
+
export function validateStatusView<V>(v: V) {
+
return validate<StatusView & V>(v, id, hashStatusView)
+
}
+
+
export interface ProfileView {
+
$type?: 'xyz.statusphere.defs#profileView'
+
did: string
+
handle: string
+
}
+
+
const hashProfileView = 'profileView'
+
+
export function isProfileView<V>(v: V) {
+
return is$typed(v, id, hashProfileView)
+
}
+
+
export function validateProfileView<V>(v: V) {
+
return validate<ProfileView & V>(v, id, hashProfileView)
+
}
+36
packages/example/src/types/xyz/statusphere/getStatuses.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { CID } from 'multiformats/cid'
+
import { validate as _validate } from '../../../lexicons'
+
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
+
import type * as XyzStatusphereDefs from './defs.js'
+
+
const is$typed = _is$typed,
+
validate = _validate
+
const id = 'xyz.statusphere.getStatuses'
+
+
export type QueryParams = {
+
limit: number
+
}
+
export type InputSchema = undefined
+
+
export interface OutputSchema {
+
statuses: XyzStatusphereDefs.StatusView[]
+
}
+
+
export type HandlerInput = void
+
+
export interface HandlerSuccess {
+
encoding: 'application/json'
+
body: OutputSchema
+
headers?: { [key: string]: string }
+
}
+
+
export interface HandlerError {
+
status: number
+
message?: string
+
}
+
+
export type HandlerOutput = HandlerError | HandlerSuccess
+36
packages/example/src/types/xyz/statusphere/getUser.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { CID } from 'multiformats/cid'
+
import { validate as _validate } from '../../../lexicons'
+
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
+
import type * as AppBskyActorDefs from '../../app/bsky/actor/defs.js'
+
import type * as XyzStatusphereDefs from './defs.js'
+
+
const is$typed = _is$typed,
+
validate = _validate
+
const id = 'xyz.statusphere.getUser'
+
+
export type QueryParams = {}
+
export type InputSchema = undefined
+
+
export interface OutputSchema {
+
profile: AppBskyActorDefs.ProfileView
+
status?: XyzStatusphereDefs.StatusView
+
}
+
+
export type HandlerInput = void
+
+
export interface HandlerSuccess {
+
encoding: 'application/json'
+
body: OutputSchema
+
headers?: { [key: string]: string }
+
}
+
+
export interface HandlerError {
+
status: number
+
message?: string
+
}
+
+
export type HandlerOutput = HandlerError | HandlerSuccess
+40
packages/example/src/types/xyz/statusphere/sendStatus.ts
···
+
/**
+
* GENERATED CODE - DO NOT MODIFY
+
*/
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
+
import { CID } from 'multiformats/cid'
+
import { validate as _validate } from '../../../lexicons'
+
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
+
import type * as XyzStatusphereDefs from './defs.js'
+
+
const is$typed = _is$typed,
+
validate = _validate
+
const id = 'xyz.statusphere.sendStatus'
+
+
export type QueryParams = {}
+
+
export interface InputSchema {
+
status: string
+
}
+
+
export interface OutputSchema {
+
status: XyzStatusphereDefs.StatusView
+
}
+
+
export interface HandlerInput {
+
encoding: 'application/json'
+
body: InputSchema
+
}
+
+
export interface HandlerSuccess {
+
encoding: 'application/json'
+
body: OutputSchema
+
headers?: { [key: string]: string }
+
}
+
+
export interface HandlerError {
+
status: number
+
message?: string
+
}
+
+
export type HandlerOutput = HandlerError | HandlerSuccess
+3 -5
packages/example/src/types/app/example/follow.ts packages/example/src/types/xyz/statusphere/status.ts
···
const is$typed = _is$typed,
validate = _validate
-
const id = 'app.example.follow'
+
const id = 'xyz.statusphere.status'
export interface Record {
-
$type: 'app.example.follow'
-
/** DID of the account being followed */
-
subject: string
-
/** When the follow was created */
+
$type: 'xyz.statusphere.status'
+
status: string
createdAt: string
[k: string]: unknown
}
+235
packages/example/typelex/externals.tsp
···
+
import "@typelex/emitter";
+
+
// Generated by typelex
+
// This file is auto-generated. Do not edit manually.
+
+
@external
+
namespace app.bsky.actor.defs {
+
model AdultContentPref { }
+
model BskyAppProgressGuide { }
+
model BskyAppStatePref { }
+
model ContentLabelPref { }
+
model FeedViewPref { }
+
model HiddenPostsPref { }
+
model InterestsPref { }
+
model KnownFollowers { }
+
model LabelerPrefItem { }
+
model LabelersPref { }
+
model MutedWord { }
+
model MutedWordsPref { }
+
model MutedWordTarget { }
+
model Nux { }
+
model PersonalDetailsPref { }
+
model PostInteractionSettingsPref { }
+
model ProfileAssociated { }
+
model ProfileAssociatedChat { }
+
model ProfileView { }
+
model ProfileViewBasic { }
+
model ProfileViewDetailed { }
+
model SavedFeed { }
+
model SavedFeedsPref { }
+
model SavedFeedsPrefV2 { }
+
model ThreadViewPref { }
+
model ViewerState { }
+
}
+
+
@external
+
namespace app.bsky.actor.profile {
+
model Main { }
+
}
+
+
@external
+
namespace app.bsky.embed.defs {
+
model AspectRatio { }
+
}
+
+
@external
+
namespace app.bsky.embed.external {
+
model External { }
+
model Main { }
+
model View { }
+
model ViewExternal { }
+
}
+
+
@external
+
namespace app.bsky.embed.images {
+
model Image { }
+
model Main { }
+
model View { }
+
model ViewImage { }
+
}
+
+
@external
+
namespace app.bsky.embed.`record` {
+
model Main { }
+
model View { }
+
model ViewBlocked { }
+
model ViewDetached { }
+
model ViewNotFound { }
+
model ViewRecord { }
+
}
+
+
@external
+
namespace app.bsky.embed.recordWithMedia {
+
model Main { }
+
model View { }
+
}
+
+
@external
+
namespace app.bsky.embed.video {
+
model Caption { }
+
model Main { }
+
model View { }
+
}
+
+
@external
+
namespace app.bsky.feed.defs {
+
model BlockedAuthor { }
+
model BlockedPost { }
+
@token model ClickthroughAuthor { }
+
@token model ClickthroughEmbed { }
+
@token model ClickthroughItem { }
+
@token model ClickthroughReposter { }
+
@token model ContentModeUnspecified { }
+
@token model ContentModeVideo { }
+
model FeedViewPost { }
+
model GeneratorView { }
+
model GeneratorViewerState { }
+
model Interaction { }
+
@token model InteractionLike { }
+
@token model InteractionQuote { }
+
@token model InteractionReply { }
+
@token model InteractionRepost { }
+
@token model InteractionSeen { }
+
@token model InteractionShare { }
+
model NotFoundPost { }
+
model PostView { }
+
model ReasonPin { }
+
model ReasonRepost { }
+
model ReplyRef { }
+
@token model RequestLess { }
+
@token model RequestMore { }
+
model SkeletonFeedPost { }
+
model SkeletonReasonPin { }
+
model SkeletonReasonRepost { }
+
model ThreadContext { }
+
model ThreadgateView { }
+
model ThreadViewPost { }
+
model ViewerState { }
+
}
+
+
@external
+
namespace app.bsky.feed.postgate {
+
model DisableRule { }
+
model Main { }
+
}
+
+
@external
+
namespace app.bsky.feed.threadgate {
+
model FollowerRule { }
+
model FollowingRule { }
+
model ListRule { }
+
model Main { }
+
model MentionRule { }
+
}
+
+
@external
+
namespace app.bsky.graph.defs {
+
@token model Curatelist { }
+
model ListItemView { }
+
model ListPurpose { }
+
model ListView { }
+
model ListViewBasic { }
+
model ListViewerState { }
+
@token model Modlist { }
+
model NotFoundActor { }
+
@token model Referencelist { }
+
model Relationship { }
+
model StarterPackView { }
+
model StarterPackViewBasic { }
+
}
+
+
@external
+
namespace app.bsky.labeler.defs {
+
model LabelerPolicies { }
+
model LabelerView { }
+
model LabelerViewDetailed { }
+
model LabelerViewerState { }
+
}
+
+
@external
+
namespace app.bsky.richtext.facet {
+
model ByteSlice { }
+
model Link { }
+
model Main { }
+
model Mention { }
+
model Tag { }
+
}
+
+
@external
+
namespace com.atproto.label.defs {
+
model Label { }
+
model LabelValue { }
+
model LabelValueDefinition { }
+
model LabelValueDefinitionStrings { }
+
model SelfLabel { }
+
model SelfLabels { }
+
}
+
+
@external
+
namespace com.atproto.repo.applyWrites {
+
model Create { }
+
model CreateResult { }
+
model Delete { }
+
model DeleteResult { }
+
model Update { }
+
model UpdateResult { }
+
}
+
+
@external
+
namespace com.atproto.repo.createRecord {
+
}
+
+
@external
+
namespace com.atproto.repo.defs {
+
model CommitMeta { }
+
}
+
+
@external
+
namespace com.atproto.repo.deleteRecord {
+
}
+
+
@external
+
namespace com.atproto.repo.describeRepo {
+
}
+
+
@external
+
namespace com.atproto.repo.getRecord {
+
}
+
+
@external
+
namespace com.atproto.repo.importRepo {
+
}
+
+
@external
+
namespace com.atproto.repo.listMissingBlobs {
+
model RecordBlob { }
+
}
+
+
@external
+
namespace com.atproto.repo.listRecords {
+
model Record { }
+
}
+
+
@external
+
namespace com.atproto.repo.putRecord {
+
}
+
+
@external
+
namespace com.atproto.repo.strongRef {
+
model Main { }
+
}
+
+
@external
+
namespace com.atproto.repo.uploadBlob {
+
}
+46 -122
packages/example/typelex/main.tsp
···
import "@typelex/emitter";
+
import "./externals.tsp";
-
// Example showing typelex as source of truth for atproto lexicons
+
namespace xyz.statusphere.defs {
+
model StatusView {
+
@required uri: atUri;
-
// ============ Common Types ============
-
-
namespace app.example.defs {
-
@doc("Type of notification")
-
union notificationType {
-
string,
-
-
Like: "like",
-
Repost: "repost",
-
Follow: "follow",
-
Mention: "mention",
-
Reply: "reply",
-
}
-
-
@doc("Reference to a post")
-
model PostRef {
-
@doc("AT URI of the post")
-
@required
-
uri: string;
-
-
@doc("CID of the post")
@required
-
cid: string;
-
}
-
-
@doc("Reference to a parent post in a reply chain")
-
model ReplyRef {
-
@doc("Root post in the thread")
-
@required
-
root: PostRef;
+
@minLength(1)
+
@maxGraphemes(1)
+
@maxLength(32)
+
status: string;
-
@doc("Direct parent post being replied to")
-
@required
-
parent: PostRef;
+
@required createdAt: datetime;
+
@required profile: ProfileView;
}
-
@doc("Text entity (mention, link, or tag)")
-
model Entity {
-
@doc("Start index in text")
-
@required
-
start: int32;
-
-
@doc("End index in text")
-
@required
-
end: int32;
-
-
@doc("Entity type")
-
@required
-
type: string;
-
-
@doc("Entity value (handle, URL, or tag)")
-
@required
-
value: string;
+
model ProfileView {
+
@required did: did;
+
@required handle: handle;
}
}
-
// ============ Records ============
-
-
namespace app.example.post {
+
namespace xyz.statusphere.status {
@rec("tid")
-
@doc("A post in the feed")
model Main {
-
@doc("Post text content")
@required
-
text: string;
+
@minLength(1)
+
@maxGraphemes(1)
+
@maxLength(32)
+
status: string;
-
@doc("Creation timestamp")
-
@required
-
createdAt: datetime;
-
-
@doc("Languages the post is written in")
-
langs?: string[];
-
-
@doc("Referenced entities in the post")
-
entities?: app.example.defs.Entity[];
-
-
@doc("Post the user is replying to")
-
reply?: app.example.defs.ReplyRef;
+
@required createdAt: datetime;
}
}
-
namespace app.example.follow {
-
@rec("tid")
-
@doc("A follow relationship")
-
model Main {
-
@doc("DID of the account being followed")
-
@required
-
subject: string;
-
-
@doc("When the follow was created")
-
@required
-
createdAt: datetime;
-
}
+
namespace xyz.statusphere.sendStatus {
+
@procedure
+
@doc("Send a status into the ATmosphere.")
+
op main(
+
input: {
+
@required
+
@minLength(1)
+
@maxGraphemes(1)
+
@maxLength(32)
+
status: string;
+
},
+
): {
+
@required status: xyz.statusphere.defs.StatusView;
+
};
}
-
namespace app.example.like {
-
@rec("tid")
-
@doc("A like on a post")
-
model Main {
-
@doc("Post being liked")
-
@required
-
subject: app.example.defs.PostRef;
-
-
@doc("When the like was created")
-
@required
-
createdAt: datetime;
-
}
+
namespace xyz.statusphere.getStatuses {
+
@query
+
@doc("Get a list of the most recent statuses on the network.")
+
op main(@minValue(1) @maxValue(100) limit?: integer = 50): {
+
@required statuses: xyz.statusphere.defs.StatusView[];
+
};
}
-
namespace app.example.repost {
-
@rec("tid")
-
@doc("A repost of another post")
-
model Main {
-
@doc("Post being reposted")
-
@required
-
subject: app.example.defs.PostRef;
-
-
@doc("When the repost was created")
-
@required
-
createdAt: datetime;
-
}
-
}
-
-
namespace app.example.profile {
-
@rec("self")
-
@doc("User profile information")
-
model Main {
-
@doc("Display name")
-
displayName?: string;
-
-
@doc("Profile description")
-
description?: string;
-
-
@doc("Profile avatar image")
-
avatar?: string;
-
-
@doc("Profile banner image")
-
banner?: string;
-
}
+
namespace xyz.statusphere.getUser {
+
@query
+
@doc("Get the current user's profile and status.")
+
op main(): {
+
@required profile: app.bsky.actor.defs.ProfileView;
+
status?: xyz.statusphere.defs.StatusView;
+
};
}
+46 -3
pnpm-lock.yaml
···
specifier: ^5.0.0
version: 5.9.3
+
packages/cli:
+
dependencies:
+
'@typelex/emitter':
+
specifier: ^0.2.0
+
version: 0.2.0(@typespec/compiler@1.4.0(@types/node@20.19.19))
+
'@typespec/compiler':
+
specifier: ^1.4.0
+
version: 1.4.0(@types/node@20.19.19)
+
yargs:
+
specifier: ^18.0.0
+
version: 18.0.0
+
devDependencies:
+
'@types/node':
+
specifier: ^20.0.0
+
version: 20.19.19
+
'@types/yargs':
+
specifier: ^17.0.33
+
version: 17.0.33
+
typescript:
+
specifier: ^5.0.0
+
version: 5.9.3
+
packages/emitter:
dependencies:
'@typespec/compiler':
···
'@atproto/xrpc-server':
specifier: ^0.9.5
version: 0.9.5
+
'@typelex/cli':
+
specifier: workspace:*
+
version: link:../cli
'@typelex/emitter':
specifier: workspace:*
version: link:../emitter
-
'@typespec/compiler':
-
specifier: ^1.4.0
-
version: 1.4.0(@types/node@20.19.19)
devDependencies:
typescript:
specifier: ^5.0.0
···
'@ts-morph/common@0.25.0':
resolution: {integrity: sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==}
+
'@typelex/emitter@0.2.0':
+
resolution: {integrity: sha512-4Iw6VAnd9nCFGOkJcu9utWdmu9ZyPeAb1QX/B7KerGBmfc2FuIDqgZZ/mZ6c56atcZd62pb2oYF/3RgSFhEsoQ==}
+
peerDependencies:
+
'@typespec/compiler': ^1.4.0
+
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
···
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+
'@types/yargs-parser@21.0.3':
+
resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
+
+
'@types/yargs@17.0.33':
+
resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==}
+
'@typespec/asset-emitter@0.74.0':
resolution: {integrity: sha512-DWIdlSNhRgBeZ8exfqubfUn0H6mRg4gr0s7zLTdBMUEDHL3Yh0ljnRPkd8AXTZhoW3maTFT69loWTrqx09T5oQ==}
engines: {node: '>=20.0.0'}
···
path-browserify: 1.0.1
tinyglobby: 0.2.15
+
'@typelex/emitter@0.2.0(@typespec/compiler@1.4.0(@types/node@20.19.19))':
+
dependencies:
+
'@typespec/compiler': 1.4.0(@types/node@20.19.19)
+
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.28.4
···
'@types/unist@3.0.3': {}
+
'@types/yargs-parser@21.0.3': {}
+
+
'@types/yargs@17.0.33':
+
dependencies:
+
'@types/yargs-parser': 21.0.3
+
'@typespec/asset-emitter@0.74.0(@typespec/compiler@1.4.0(@types/node@20.19.19))':
dependencies:
'@typespec/compiler': 1.4.0(@types/node@20.19.19)