+1
packages/cli/package.json
+1
packages/cli/package.json
+4
-30
packages/cli/src/cli.ts
+4
-30
packages/cli/src/cli.ts
···
2
2
import yargs from "yargs";
3
3
import { hideBin } from "yargs/helpers";
4
4
import { compileCommand } from "./commands/compile.js";
5
-
import { initCommand } from "./commands/init.js";
6
5
7
6
async function main() {
8
7
await yargs(hideBin(process.argv))
9
8
.scriptName("typelex")
10
-
.command(
11
-
"init",
12
-
"Initialize a new typelex project",
13
-
() => {},
14
-
async () => {
15
-
await initCommand();
16
-
}
17
-
)
9
+
.usage("$0 compile <namespace>")
18
10
.command(
19
11
"compile <namespace>",
20
12
"Compile TypeSpec files to Lexicon JSON",
21
13
(yargs) => {
22
14
return yargs
23
15
.positional("namespace", {
24
-
describe: "Primary namespace pattern (e.g., com.example.*)",
16
+
describe: "Primary namespace pattern (e.g., app.bsky.*)",
25
17
type: "string",
26
18
demandOption: true,
27
19
})
28
20
.option("out", {
29
-
describe: "Output directory for generated Lexicon files (must end with 'lexicons')",
21
+
describe: "Output directory for generated Lexicon files (relative to cwd)",
30
22
type: "string",
31
23
default: "./lexicons",
32
24
});
33
25
},
34
26
async (argv) => {
35
-
if (!argv.namespace) {
36
-
console.error("Error: namespace is required");
37
-
console.error("Usage: typelex compile <namespace>");
38
-
console.error("Example: typelex compile com.example.*");
39
-
process.exit(1);
40
-
}
41
-
42
-
if (!argv.namespace.endsWith(".*")) {
43
-
console.error("Error: namespace must end with .*");
44
-
console.error(`Got: ${argv.namespace}`);
45
-
console.error("Example: typelex compile com.example.*");
46
-
process.exit(1);
47
-
}
48
-
49
27
const options: Record<string, unknown> = {};
50
28
if (argv.watch) {
51
29
options.watch = true;
···
61
39
type: "boolean",
62
40
default: false,
63
41
})
64
-
.demandCommand(1)
42
+
.demandCommand(1, "You must specify a command")
65
43
.help()
66
44
.version()
67
45
.fail((msg, err) => {
68
46
if (err) {
69
47
console.error(err);
70
-
} else if (msg.includes("Not enough non-option arguments")) {
71
-
console.error("Error: namespace is required");
72
-
console.error("Usage: typelex compile <namespace>");
73
-
console.error("Example: typelex compile com.example.*");
74
48
} else {
75
49
console.error(msg);
76
50
}
-152
packages/cli/src/commands/init.ts
-152
packages/cli/src/commands/init.ts
···
1
-
import { resolve } from "path";
2
-
import { mkdir, writeFile, readFile, access } from "fs/promises";
3
-
import { spawn } from "child_process";
4
-
import { createInterface } from "readline";
5
-
6
-
function createMainTemplate(namespace: string): string {
7
-
return `import "@typelex/emitter";
8
-
import "./externals.tsp";
9
-
10
-
namespace ${namespace}.post {
11
-
@rec("tid")
12
-
model Main {
13
-
@required text: string;
14
-
@required createdAt: datetime;
15
-
}
16
-
}
17
-
`;
18
-
}
19
-
20
-
const EXTERNALS_TSP_TEMPLATE = `import "@typelex/emitter";
21
-
22
-
// Generated by typelex
23
-
// This file is auto-generated. Do not edit manually.
24
-
`;
25
-
26
-
async function promptNamespace(): Promise<string> {
27
-
const rl = createInterface({
28
-
input: process.stdin,
29
-
output: process.stdout,
30
-
});
31
-
32
-
return new Promise((resolve) => {
33
-
rl.question("Namespace (e.g., com.example.*): ", (answer) => {
34
-
rl.close();
35
-
resolve(answer.trim());
36
-
});
37
-
});
38
-
}
39
-
40
-
/**
41
-
* Initialize a new typelex project
42
-
*/
43
-
export async function initCommand(): Promise<void> {
44
-
const cwd = process.cwd();
45
-
const typelexDir = resolve(cwd, "typelex");
46
-
const mainTspPath = resolve(typelexDir, "main.tsp");
47
-
const externalsTspPath = resolve(typelexDir, "externals.tsp");
48
-
49
-
console.log("Initializing typelex project...\n");
50
-
51
-
// Prompt for namespace
52
-
let namespace = await promptNamespace();
53
-
54
-
// Validate namespace format
55
-
while (!namespace.endsWith(".*")) {
56
-
console.error(`Error: namespace must end with .*`);
57
-
console.error(`Got: ${namespace}\n`);
58
-
namespace = await promptNamespace();
59
-
}
60
-
61
-
// Remove the .* suffix for use in template
62
-
const namespacePrefix = namespace.slice(0, -2);
63
-
64
-
// Create typelex directory
65
-
await mkdir(typelexDir, { recursive: true });
66
-
67
-
// Check if main.tsp exists and is non-empty
68
-
let shouldCreateMain = true;
69
-
try {
70
-
await access(mainTspPath);
71
-
const content = await readFile(mainTspPath, "utf-8");
72
-
if (content.trim().length > 0) {
73
-
console.log("✓ typelex/main.tsp already exists, skipping");
74
-
shouldCreateMain = false;
75
-
}
76
-
} catch {
77
-
// File doesn't exist, we'll create it
78
-
}
79
-
80
-
if (shouldCreateMain) {
81
-
await writeFile(mainTspPath, createMainTemplate(namespacePrefix), "utf-8");
82
-
console.log("✓ Created typelex/main.tsp");
83
-
}
84
-
85
-
// Always create/overwrite externals.tsp
86
-
await writeFile(externalsTspPath, EXTERNALS_TSP_TEMPLATE, "utf-8");
87
-
console.log("✓ Created typelex/externals.tsp");
88
-
89
-
// Add build script to package.json
90
-
const packageJsonPath = resolve(cwd, "package.json");
91
-
try {
92
-
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
93
-
if (!packageJson.scripts) {
94
-
packageJson.scripts = {};
95
-
}
96
-
if (!packageJson.scripts["build:typelex"]) {
97
-
packageJson.scripts["build:typelex"] = `typelex compile ${namespace}`;
98
-
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
99
-
console.log("✓ Added build:typelex script to package.json");
100
-
} else {
101
-
console.log("✓ build:typelex script already exists in package.json");
102
-
}
103
-
} catch (err) {
104
-
console.warn("⚠ Could not update package.json:", (err as Error).message);
105
-
}
106
-
107
-
// Install dependencies
108
-
console.log("\nInstalling dependencies...");
109
-
110
-
// Detect package manager
111
-
let packageManager = "npm";
112
-
try {
113
-
await access(resolve(cwd, "pnpm-lock.yaml"));
114
-
packageManager = "pnpm";
115
-
} catch {
116
-
try {
117
-
await access(resolve(cwd, "yarn.lock"));
118
-
packageManager = "yarn";
119
-
} catch {
120
-
// Default to npm
121
-
}
122
-
}
123
-
124
-
return new Promise((resolvePromise, reject) => {
125
-
const args = packageManager === "npm"
126
-
? ["install", "--save-dev", "@typelex/cli", "@typelex/emitter"]
127
-
: ["add", "-D", "@typelex/cli", "@typelex/emitter"];
128
-
129
-
const install = spawn(packageManager, args, {
130
-
cwd,
131
-
stdio: "inherit",
132
-
});
133
-
134
-
install.on("close", (code) => {
135
-
if (code === 0) {
136
-
console.log("\n✓ Installed @typelex/cli and @typelex/emitter");
137
-
console.log("\nTypelex initialized successfully!");
138
-
console.log("\nNext steps:");
139
-
console.log(" 1. Edit typelex/main.tsp to define your lexicons");
140
-
console.log(" 2. Run: npm run build:typelex");
141
-
resolvePromise();
142
-
} else {
143
-
process.exit(code ?? 1);
144
-
}
145
-
});
146
-
147
-
install.on("error", (err) => {
148
-
console.error("Failed to install dependencies:", err);
149
-
reject(err);
150
-
});
151
-
});
152
-
}
+2
-2
packages/example/package.json
+2
-2
packages/example/package.json
···
4
4
"private": true,
5
5
"type": "module",
6
6
"scripts": {
7
-
"build": "pnpm run build:typelex && pnpm run build:codegen",
8
-
"build:typelex": "typelex compile xyz.statusphere.*",
7
+
"build": "pnpm run build:lexicons && pnpm run build:codegen",
8
+
"build:lexicons": "typelex compile xyz.statusphere.*",
9
9
"build:codegen": "lex gen-server --yes ./src lexicons/xyz/statusphere/*.json"
10
10
},
11
11
"dependencies": {