@intentius/chant 0.0.22 → 0.1.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/package.json +1 -1
- package/src/cli/commands/init-lexicon/templates/codegen.ts +188 -0
- package/src/cli/commands/init-lexicon/templates/docs.ts +81 -0
- package/src/cli/commands/init-lexicon/templates/examples.ts +35 -0
- package/src/cli/commands/init-lexicon/templates/lint.ts +30 -0
- package/src/cli/commands/init-lexicon/templates/lsp.ts +39 -0
- package/src/cli/commands/init-lexicon/templates/plugin.ts +110 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +182 -0
- package/src/cli/commands/init-lexicon/templates/spec.ts +57 -0
- package/src/cli/commands/init-lexicon/templates/tests.ts +70 -0
- package/src/cli/commands/init-lexicon.ts +12 -774
- package/src/cli/conflict-check.test.ts +43 -0
- package/src/cli/main.ts +1 -1
- package/src/cli/mcp/resource-handlers.ts +227 -0
- package/src/cli/mcp/server.ts +20 -409
- package/src/cli/mcp/state-tools.ts +138 -0
- package/src/cli/mcp/types.ts +45 -0
- package/src/codegen/docs-file-markers.ts +69 -0
- package/src/codegen/docs-rule-scanning.ts +159 -0
- package/src/codegen/docs-sections.ts +159 -0
- package/src/codegen/docs-sidebar.ts +56 -0
- package/src/codegen/docs-types.ts +79 -0
- package/src/codegen/docs.ts +9 -495
- package/src/codegen/typecheck.ts +13 -0
- package/src/composite.test.ts +75 -0
- package/src/composite.ts +37 -0
- package/src/discovery/collect.test.ts +34 -0
- package/src/discovery/collect.ts +25 -0
- package/src/lexicon-plugin-helpers.ts +130 -0
- package/src/toml-emit.ts +182 -0
- package/src/toml-parse.ts +370 -0
- package/src/toml-utils.ts +60 -0
- package/src/toml.ts +5 -602
- package/src/yaml.ts +7 -2
|
@@ -2,6 +2,17 @@ import { existsSync, mkdirSync, writeFileSync, readdirSync } from "fs";
|
|
|
2
2
|
import { join, resolve } from "path";
|
|
3
3
|
import { formatSuccess, formatWarning } from "../format";
|
|
4
4
|
|
|
5
|
+
// Template generators
|
|
6
|
+
import { generatePluginTs, generateIndexTs } from "./init-lexicon/templates/plugin";
|
|
7
|
+
import { generateCodegenGenerateTs, generateCodegenGenerateCliTs, generateCodegenNamingTs, generateCodegenPackageTs, generateCodegenDocsTs } from "./init-lexicon/templates/codegen";
|
|
8
|
+
import { generateSpecFetchTs, generateSpecParseTs } from "./init-lexicon/templates/spec";
|
|
9
|
+
import { generateSampleRuleTs, generateLintRulesIndexTs } from "./init-lexicon/templates/lint";
|
|
10
|
+
import { generateLspCompletionsTs, generateLspHoverTs } from "./init-lexicon/templates/lsp";
|
|
11
|
+
import { generatePackageJson, generateTsConfig, generateJustfile, generateGitignore, generateReadme, generateSerializerTs, generateValidateTs, generateValidateCliTs } from "./init-lexicon/templates/project";
|
|
12
|
+
import { generatePluginTestTs, generateSerializerTestTs, generateCompletionsTestTs, generateHoverTestTs } from "./init-lexicon/templates/tests";
|
|
13
|
+
import { generateDocsPackageJson, generateDocsTsConfig, generateDocsAstroConfig, generateDocsContentConfig, generateDocsIndexMdx } from "./init-lexicon/templates/docs";
|
|
14
|
+
import { generateExamplePackageJson, generateExampleInfraTs } from "./init-lexicon/templates/examples";
|
|
15
|
+
|
|
5
16
|
/**
|
|
6
17
|
* Init-lexicon command options
|
|
7
18
|
*/
|
|
@@ -32,7 +43,7 @@ function toCamelCase(name: string): string {
|
|
|
32
43
|
return name.replace(/[-_]+(.)/g, (_, c) => c.toUpperCase());
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
function deriveNames(name: string) {
|
|
46
|
+
export function deriveNames(name: string) {
|
|
36
47
|
const camel = toCamelCase(name);
|
|
37
48
|
return {
|
|
38
49
|
pluginVarName: `${camel}Plugin`,
|
|
@@ -59,779 +70,6 @@ function writeIfNotExists(
|
|
|
59
70
|
}
|
|
60
71
|
}
|
|
61
72
|
|
|
62
|
-
// ── Template generators ──────────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
function generatePluginTs(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
65
|
-
return `import type { LexiconPlugin, SkillDefinition, IntrinsicDef } from "@intentius/chant/lexicon";
|
|
66
|
-
import type { LintRule } from "@intentius/chant/lint/rule";
|
|
67
|
-
import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
|
|
68
|
-
import type { CompletionContext, CompletionItem, HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
|
|
69
|
-
import type { McpToolContribution, McpResourceContribution } from "@intentius/chant/mcp/types";
|
|
70
|
-
import { ${names.serializerVarName} } from "./serializer";
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* ${name} lexicon plugin.
|
|
74
|
-
*
|
|
75
|
-
* Implements all required LexiconPlugin lifecycle methods.
|
|
76
|
-
*/
|
|
77
|
-
export const ${names.pluginVarName}: LexiconPlugin = {
|
|
78
|
-
name: "${name}",
|
|
79
|
-
serializer: ${names.serializerVarName},
|
|
80
|
-
|
|
81
|
-
// ── Required lifecycle methods ────────────────────────────────
|
|
82
|
-
|
|
83
|
-
async generate(options?: { verbose?: boolean }): Promise<void> {
|
|
84
|
-
const { generate } = await import("./codegen/generate");
|
|
85
|
-
await generate(options);
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
async validate(options?: { verbose?: boolean }): Promise<void> {
|
|
89
|
-
const { validate } = await import("./validate");
|
|
90
|
-
const { printValidationResult } = await import("@intentius/chant/codegen/validate");
|
|
91
|
-
const result = await validate();
|
|
92
|
-
printValidationResult(result);
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
async coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void> {
|
|
96
|
-
// TODO: Implement coverage analysis
|
|
97
|
-
console.error("Coverage analysis not yet implemented");
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
|
|
101
|
-
const { packageLexicon } = await import("./codegen/package");
|
|
102
|
-
const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
|
|
103
|
-
const { join, dirname } = await import("path");
|
|
104
|
-
const { fileURLToPath } = await import("url");
|
|
105
|
-
|
|
106
|
-
const { spec, stats } = await packageLexicon(options);
|
|
107
|
-
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
108
|
-
writeBundleSpec(spec, join(pkgDir, "dist"));
|
|
109
|
-
|
|
110
|
-
console.error(\`Packaged \${stats.resources} resources, \${stats.ruleCount} rules, \${stats.skillCount} skills\`);
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
// ── Optional extensions ────────────────────────────────────
|
|
114
|
-
|
|
115
|
-
lintRules() {
|
|
116
|
-
const { rules } = require("./lint/rules");
|
|
117
|
-
return rules;
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
postSynthChecks() {
|
|
121
|
-
return []; // TODO: Add post-synth checks
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
skills() {
|
|
125
|
-
return []; // TODO: Add skills
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
mcpTools() {
|
|
129
|
-
return []; // TODO: Implement MCP tools
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
mcpResources() {
|
|
133
|
-
return []; // TODO: Implement MCP resources
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
detectTemplate(data: unknown) {
|
|
137
|
-
return false; // TODO: Detect if a template belongs to this lexicon
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
completionProvider(ctx: CompletionContext) {
|
|
141
|
-
const { completions } = require("./lsp/completions");
|
|
142
|
-
return completions(ctx);
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
hoverProvider(ctx: HoverContext) {
|
|
146
|
-
const { hover } = require("./lsp/hover");
|
|
147
|
-
return hover(ctx);
|
|
148
|
-
},
|
|
149
|
-
|
|
150
|
-
async docs(options?) {
|
|
151
|
-
const { generateDocs } = await import("./codegen/docs");
|
|
152
|
-
return generateDocs(options);
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function generateIndexTs(names: ReturnType<typeof deriveNames>): string {
|
|
159
|
-
return `// Plugin
|
|
160
|
-
export { ${names.pluginVarName} } from "./plugin";
|
|
161
|
-
|
|
162
|
-
// Serializer
|
|
163
|
-
export { ${names.serializerVarName} } from "./serializer";
|
|
164
|
-
|
|
165
|
-
// Generated resources — export everything from generated index
|
|
166
|
-
// After running \`chant generate\`, this re-exports all resource classes
|
|
167
|
-
// export * from "./generated/index";
|
|
168
|
-
`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function generateSerializerTs(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
172
|
-
return `import type { Serializer, Declarable } from "@intentius/chant";
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* ${name} serializer — produces minimal JSON output.
|
|
176
|
-
*
|
|
177
|
-
* TODO: Replace with your lexicon's output format.
|
|
178
|
-
*/
|
|
179
|
-
export const ${names.serializerVarName}: Serializer = {
|
|
180
|
-
name: "${name}",
|
|
181
|
-
rulePrefix: "${names.rulePrefix}",
|
|
182
|
-
|
|
183
|
-
serialize(entities: Map<string, Declarable>): string {
|
|
184
|
-
const resources: Record<string, unknown> = {};
|
|
185
|
-
|
|
186
|
-
for (const [entityName, entity] of entities) {
|
|
187
|
-
resources[entityName] = {
|
|
188
|
-
type: entity.entityType,
|
|
189
|
-
// TODO: Convert entity properties to your output format
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return JSON.stringify({ resources }, null, 2);
|
|
194
|
-
},
|
|
195
|
-
};
|
|
196
|
-
`;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function generateCodegenGenerateTs(): string {
|
|
200
|
-
return `import { generatePipeline, writeGeneratedArtifacts } from "@intentius/chant/codegen/generate";
|
|
201
|
-
import type { GenerateResult } from "@intentius/chant/codegen/generate";
|
|
202
|
-
import { dirname } from "path";
|
|
203
|
-
import { fileURLToPath } from "url";
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Run the code generation pipeline.
|
|
207
|
-
*
|
|
208
|
-
* Each callback has a TODO describing what to implement.
|
|
209
|
-
*/
|
|
210
|
-
export async function generate(options?: { verbose?: boolean }): Promise<GenerateResult> {
|
|
211
|
-
const result = await generatePipeline({
|
|
212
|
-
// Must return Map<typeName, Buffer> — each entry is one schema file.
|
|
213
|
-
// Example: fetch a zip, extract JSON files, key by type name.
|
|
214
|
-
// See lexicons/aws/src/spec/fetch.ts for a working example.
|
|
215
|
-
fetchSchemas: async (opts) => {
|
|
216
|
-
throw new Error("TODO: implement fetchSchemas — download your upstream spec");
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
// Must return a ParsedResult (with propertyTypes[] and enums[] at minimum).
|
|
220
|
-
// Return null to skip a schema file.
|
|
221
|
-
// See lexicons/aws/src/spec/parse.ts for a working example.
|
|
222
|
-
parseSchema: (name, data) => {
|
|
223
|
-
throw new Error("TODO: implement parseSchema — parse a single schema file");
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
// Must return a NamingStrategy instance.
|
|
227
|
-
// See lexicons/aws/src/codegen/naming.ts and ./naming.ts for setup.
|
|
228
|
-
createNaming: (results) => {
|
|
229
|
-
throw new Error("TODO: implement createNaming — return a NamingStrategy instance");
|
|
230
|
-
},
|
|
231
|
-
|
|
232
|
-
// Must return a string of JSON (the lexicon registry).
|
|
233
|
-
// Use buildRegistry + serializeRegistry from @intentius/chant/codegen/generate-registry.
|
|
234
|
-
// See lexicons/aws/src/codegen/generate.ts for a working example.
|
|
235
|
-
generateRegistry: (results, naming) => {
|
|
236
|
-
throw new Error("TODO: implement generateRegistry — produce lexicon JSON");
|
|
237
|
-
},
|
|
238
|
-
|
|
239
|
-
// Must return a string of TypeScript declarations (.d.ts content).
|
|
240
|
-
// See lexicons/aws/src/codegen/generate.ts for a working example.
|
|
241
|
-
generateTypes: (results, naming) => {
|
|
242
|
-
throw new Error("TODO: implement generateTypes — produce .d.ts content");
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
// Must return a string of TypeScript (runtime index with factory exports).
|
|
246
|
-
// Use generateRuntimeIndex from @intentius/chant/codegen/generate-runtime-index.
|
|
247
|
-
// See lexicons/aws/src/codegen/generate.ts for a working example.
|
|
248
|
-
generateRuntimeIndex: (results, naming) => {
|
|
249
|
-
throw new Error("TODO: implement generateRuntimeIndex — produce index.ts content");
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
if (options?.verbose) {
|
|
254
|
-
console.error(\`Generated \${result.resources} resources, \${result.properties} property types\`);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return result;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Write generated files to the package directory.
|
|
262
|
-
*/
|
|
263
|
-
export function writeGeneratedFiles(result: GenerateResult, pkgDir?: string): void {
|
|
264
|
-
const dir = pkgDir ?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
265
|
-
writeGeneratedArtifacts({
|
|
266
|
-
baseDir: dir,
|
|
267
|
-
files: {
|
|
268
|
-
"lexicon.json": result.lexiconJSON,
|
|
269
|
-
"index.d.ts": result.typesDTS,
|
|
270
|
-
"index.ts": result.indexTS,
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
`;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function generateCodegenGenerateCliTs(): string {
|
|
278
|
-
return `#!/usr/bin/env bun
|
|
279
|
-
import { generate, writeGeneratedFiles } from "./generate";
|
|
280
|
-
import { dirname } from "path";
|
|
281
|
-
import { fileURLToPath } from "url";
|
|
282
|
-
|
|
283
|
-
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
284
|
-
const result = await generate({ verbose: true });
|
|
285
|
-
writeGeneratedFiles(result, pkgDir);
|
|
286
|
-
`;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function generateCodegenNamingTs(): string {
|
|
290
|
-
return `import { NamingStrategy, type NamingConfig, type NamingInput } from "@intentius/chant/codegen/naming";
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Naming configuration for this lexicon.
|
|
294
|
-
*
|
|
295
|
-
* TODO: Populate these tables with your provider's naming conventions.
|
|
296
|
-
*/
|
|
297
|
-
export const namingConfig: NamingConfig = {
|
|
298
|
-
// High-priority short names for common resource types
|
|
299
|
-
priorityNames: {},
|
|
300
|
-
|
|
301
|
-
// Aliases for resource types that need alternate names
|
|
302
|
-
priorityAliases: {},
|
|
303
|
-
|
|
304
|
-
// Aliases for property types
|
|
305
|
-
priorityPropertyAliases: {},
|
|
306
|
-
|
|
307
|
-
// Abbreviations for service names (used in collision resolution)
|
|
308
|
-
serviceAbbreviations: {},
|
|
309
|
-
|
|
310
|
-
// Extract the short name from a fully-qualified type string
|
|
311
|
-
shortName: (typeName: string) => typeName.split("::").pop()!,
|
|
312
|
-
|
|
313
|
-
// Extract the service name from a fully-qualified type string
|
|
314
|
-
serviceName: (typeName: string) => typeName.split("::")[1] ?? typeName,
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Create a NamingStrategy instance from parsed results.
|
|
319
|
-
*/
|
|
320
|
-
export function createNaming(inputs: NamingInput[]): NamingStrategy {
|
|
321
|
-
return new NamingStrategy(inputs, namingConfig);
|
|
322
|
-
}
|
|
323
|
-
`;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function generateCodegenPackageTs(name: string): string {
|
|
327
|
-
return `import { packagePipeline } from "@intentius/chant/codegen/package";
|
|
328
|
-
import type { PackagePipelineConfig } from "@intentius/chant/codegen/package";
|
|
329
|
-
import { generate } from "./generate";
|
|
330
|
-
import { dirname } from "path";
|
|
331
|
-
import { fileURLToPath } from "url";
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Package the ${name} lexicon for distribution.
|
|
335
|
-
*/
|
|
336
|
-
export async function packageLexicon(options?: { verbose?: boolean; force?: boolean }) {
|
|
337
|
-
const srcDir = dirname(fileURLToPath(import.meta.url));
|
|
338
|
-
|
|
339
|
-
const { spec, stats } = await packagePipeline({
|
|
340
|
-
generate: (opts) => generate({ verbose: opts?.verbose, force: opts?.force }),
|
|
341
|
-
buildManifest: (genResult) => ({
|
|
342
|
-
name: "${name}",
|
|
343
|
-
version: "0.0.1",
|
|
344
|
-
}),
|
|
345
|
-
srcDir,
|
|
346
|
-
collectSkills: () => new Map(),
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
console.error(\`Packaged \${stats.resources} resources, \${stats.ruleCount} rules\`);
|
|
350
|
-
return { spec, stats };
|
|
351
|
-
}
|
|
352
|
-
`;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function generateCodegenDocsTs(name: string): string {
|
|
356
|
-
return `import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Generate documentation site for the ${name} lexicon.
|
|
360
|
-
*/
|
|
361
|
-
export async function generateDocs(options?: { verbose?: boolean }): Promise<void> {
|
|
362
|
-
const config = {
|
|
363
|
-
name: "${name}",
|
|
364
|
-
displayName: "${name.charAt(0).toUpperCase() + name.slice(1)}",
|
|
365
|
-
description: "${name} lexicon documentation",
|
|
366
|
-
distDir: "./dist",
|
|
367
|
-
outDir: "./docs",
|
|
368
|
-
// TODO: Implement service grouping for your provider
|
|
369
|
-
serviceFromType: (type: string) => type.split("::")[1] ?? type,
|
|
370
|
-
// TODO: Implement resource documentation URLs
|
|
371
|
-
resourceTypeUrl: (type: string) => \`#\${type}\`,
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
const result = docsPipeline(config);
|
|
375
|
-
writeDocsSite(config, result);
|
|
376
|
-
|
|
377
|
-
if (options?.verbose) {
|
|
378
|
-
console.error("Documentation generated");
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
`;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function generateSpecFetchTs(): string {
|
|
385
|
-
return `import { fetchWithCache, extractFromZip } from "@intentius/chant/codegen/fetch";
|
|
386
|
-
|
|
387
|
-
// TODO: Set this to your upstream schema source URL
|
|
388
|
-
const SCHEMA_URL = "https://example.com/schemas.zip";
|
|
389
|
-
const CACHE_FILE = ".cache/schemas.zip";
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Fetch upstream schemas with caching.
|
|
393
|
-
*
|
|
394
|
-
* TODO: Point SCHEMA_URL at your real upstream schema source.
|
|
395
|
-
*/
|
|
396
|
-
export async function fetchSchemas(options?: { force?: boolean }): Promise<Map<string, string>> {
|
|
397
|
-
const zipData = await fetchWithCache({
|
|
398
|
-
url: SCHEMA_URL,
|
|
399
|
-
cacheFile: CACHE_FILE,
|
|
400
|
-
force: options?.force,
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// TODO: Adjust the filter to match your schema file names
|
|
404
|
-
return extractFromZip(zipData, (name) => name.endsWith(".json"));
|
|
405
|
-
}
|
|
406
|
-
`;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function generateSpecParseTs(): string {
|
|
410
|
-
return `/**
|
|
411
|
-
* Parsed schema result for a single schema file.
|
|
412
|
-
*/
|
|
413
|
-
export interface ParseResult {
|
|
414
|
-
typeName: string;
|
|
415
|
-
description?: string;
|
|
416
|
-
properties: Map<string, ParsedProperty>;
|
|
417
|
-
attributes: string[];
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export interface ParsedProperty {
|
|
421
|
-
name: string;
|
|
422
|
-
type: string;
|
|
423
|
-
required: boolean;
|
|
424
|
-
description?: string;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Parse a single schema file into a ParseResult.
|
|
429
|
-
*
|
|
430
|
-
* TODO: Implement parsing for your schema format.
|
|
431
|
-
*/
|
|
432
|
-
export function parseSchema(name: string, content: string): ParseResult {
|
|
433
|
-
throw new Error(\`TODO: implement parseSchema for \${name}\`);
|
|
434
|
-
}
|
|
435
|
-
`;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function generateSampleRuleTs(names: ReturnType<typeof deriveNames>): string {
|
|
439
|
-
return `import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* ${names.rulePrefix}001: Sample lint rule
|
|
443
|
-
*
|
|
444
|
-
* TODO: Replace with a real lint rule for your lexicon.
|
|
445
|
-
*/
|
|
446
|
-
export const sampleRule: LintRule = {
|
|
447
|
-
id: "${names.rulePrefix}001",
|
|
448
|
-
severity: "warning",
|
|
449
|
-
category: "style",
|
|
450
|
-
description: "Sample lint rule — replace with real checks",
|
|
451
|
-
|
|
452
|
-
check(context: LintContext): LintDiagnostic[] {
|
|
453
|
-
// TODO: Implement rule logic
|
|
454
|
-
return [];
|
|
455
|
-
},
|
|
456
|
-
};
|
|
457
|
-
`;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function generateLintRulesIndexTs(): string {
|
|
461
|
-
return `export { sampleRule } from "./sample";
|
|
462
|
-
`;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function generateLspCompletionsTs(name: string): string {
|
|
466
|
-
return `import type { CompletionContext, CompletionItem } from "@intentius/chant/lsp/types";
|
|
467
|
-
// import { LexiconIndex, lexiconCompletions } from "@intentius/chant/lsp/lexicon-providers";
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Provide LSP completions for ${name} resources.
|
|
471
|
-
*
|
|
472
|
-
* TODO: Build a LexiconIndex from your generated lexicon data
|
|
473
|
-
* and delegate to lexiconCompletions().
|
|
474
|
-
*/
|
|
475
|
-
export function completions(ctx: CompletionContext): CompletionItem[] {
|
|
476
|
-
// const index = new LexiconIndex(lexiconData);
|
|
477
|
-
// return lexiconCompletions(ctx, index, "${name} resource");
|
|
478
|
-
return [];
|
|
479
|
-
}
|
|
480
|
-
`;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function generateLspHoverTs(name: string): string {
|
|
484
|
-
return `import type { HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
|
|
485
|
-
// import { LexiconIndex, lexiconHover } from "@intentius/chant/lsp/lexicon-providers";
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Provide LSP hover information for ${name} resources.
|
|
489
|
-
*
|
|
490
|
-
* TODO: Build a LexiconIndex from your generated lexicon data
|
|
491
|
-
* and delegate to lexiconHover().
|
|
492
|
-
*/
|
|
493
|
-
export function hover(ctx: HoverContext): HoverInfo | undefined {
|
|
494
|
-
// const index = new LexiconIndex(lexiconData);
|
|
495
|
-
// return lexiconHover(ctx, index, myCustomHoverFormatter);
|
|
496
|
-
return undefined;
|
|
497
|
-
}
|
|
498
|
-
`;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function generateValidateTs(name: string): string {
|
|
502
|
-
return `/**
|
|
503
|
-
* Validate generated lexicon-${name} artifacts.
|
|
504
|
-
*
|
|
505
|
-
* Thin wrapper around the core validation framework
|
|
506
|
-
* with ${name}-specific configuration.
|
|
507
|
-
*/
|
|
508
|
-
|
|
509
|
-
import { dirname } from "path";
|
|
510
|
-
import { fileURLToPath } from "url";
|
|
511
|
-
import { validateLexiconArtifacts, type ValidateResult } from "@intentius/chant/codegen/validate";
|
|
512
|
-
|
|
513
|
-
export type { ValidateCheck, ValidateResult } from "@intentius/chant/codegen/validate";
|
|
514
|
-
|
|
515
|
-
// TODO: Add names of required entities for your lexicon
|
|
516
|
-
const REQUIRED_NAMES: string[] = [];
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Validate the generated lexicon-${name} artifacts.
|
|
520
|
-
*/
|
|
521
|
-
export async function validate(opts?: { basePath?: string }): Promise<ValidateResult> {
|
|
522
|
-
const basePath = opts?.basePath ?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
523
|
-
|
|
524
|
-
return validateLexiconArtifacts({
|
|
525
|
-
lexiconJsonFilename: "lexicon-${name}.json",
|
|
526
|
-
requiredNames: REQUIRED_NAMES,
|
|
527
|
-
basePath,
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
`;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function generateValidateCliTs(): string {
|
|
534
|
-
return `#!/usr/bin/env bun
|
|
535
|
-
import { validate } from "./validate";
|
|
536
|
-
|
|
537
|
-
await validate({ verbose: true });
|
|
538
|
-
`;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function generatePackageJson(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
542
|
-
const pkg = {
|
|
543
|
-
name: names.packageName,
|
|
544
|
-
version: "0.0.1",
|
|
545
|
-
type: "module",
|
|
546
|
-
private: true,
|
|
547
|
-
files: ["src/", "dist/"],
|
|
548
|
-
exports: {
|
|
549
|
-
".": "./src/index.ts",
|
|
550
|
-
"./*": "./src/*",
|
|
551
|
-
"./manifest": "./dist/manifest.json",
|
|
552
|
-
"./meta": "./dist/meta.json",
|
|
553
|
-
"./types": "./dist/types/index.d.ts",
|
|
554
|
-
},
|
|
555
|
-
scripts: {
|
|
556
|
-
generate: "bun run src/codegen/generate-cli.ts",
|
|
557
|
-
validate: "bun run src/validate-cli.ts",
|
|
558
|
-
docs: "bun src/codegen/docs-cli.ts",
|
|
559
|
-
prepack: "bun run generate && bun run validate",
|
|
560
|
-
},
|
|
561
|
-
dependencies: {
|
|
562
|
-
"@intentius/chant": "workspace:*",
|
|
563
|
-
},
|
|
564
|
-
devDependencies: {
|
|
565
|
-
typescript: "^5.9.3",
|
|
566
|
-
},
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
return JSON.stringify(pkg, null, 2) + "\n";
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function generateTsConfig(): string {
|
|
573
|
-
const config = {
|
|
574
|
-
extends: "../../tsconfig.json",
|
|
575
|
-
compilerOptions: {
|
|
576
|
-
rootDir: "./src",
|
|
577
|
-
outDir: "./dist",
|
|
578
|
-
},
|
|
579
|
-
include: ["src/**/*"],
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
return JSON.stringify(config, null, 2) + "\n";
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function generateJustfile(name: string): string {
|
|
586
|
-
return `# Default recipe - list all available commands
|
|
587
|
-
default:
|
|
588
|
-
@just --list
|
|
589
|
-
|
|
590
|
-
# Generate types and metadata from upstream schemas
|
|
591
|
-
generate:
|
|
592
|
-
bun run src/codegen/generate-cli.ts
|
|
593
|
-
|
|
594
|
-
# Validate generated artifacts
|
|
595
|
-
validate:
|
|
596
|
-
bun run src/validate-cli.ts
|
|
597
|
-
|
|
598
|
-
# Generate docs site, install deps, and start dev server
|
|
599
|
-
docs:
|
|
600
|
-
bun run src/codegen/docs-cli.ts
|
|
601
|
-
bun install --cwd docs
|
|
602
|
-
bun --cwd docs dev
|
|
603
|
-
|
|
604
|
-
# Build docs site for production
|
|
605
|
-
docs-build:
|
|
606
|
-
bun run src/codegen/docs-cli.ts
|
|
607
|
-
bun install --cwd docs
|
|
608
|
-
bun --cwd docs build
|
|
609
|
-
|
|
610
|
-
# Package the lexicon (generate + validate)
|
|
611
|
-
package: generate validate
|
|
612
|
-
`;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
function generateGitignore(): string {
|
|
616
|
-
return `dist/
|
|
617
|
-
node_modules/
|
|
618
|
-
.cache/
|
|
619
|
-
`;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function generateReadme(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
623
|
-
return `# ${names.packageName}
|
|
624
|
-
|
|
625
|
-
${name} lexicon plugin for [chant](https://github.com/intentius/chant).
|
|
626
|
-
|
|
627
|
-
## Getting started
|
|
628
|
-
|
|
629
|
-
\`\`\`bash
|
|
630
|
-
# Generate types from upstream spec
|
|
631
|
-
just generate
|
|
632
|
-
|
|
633
|
-
# Validate generated artifacts
|
|
634
|
-
just validate
|
|
635
|
-
|
|
636
|
-
# Generate documentation
|
|
637
|
-
just docs
|
|
638
|
-
\`\`\`
|
|
639
|
-
|
|
640
|
-
## Project structure
|
|
641
|
-
|
|
642
|
-
- \`src/plugin.ts\` — LexiconPlugin with all lifecycle methods
|
|
643
|
-
- \`src/serializer.ts\` — Build output serializer
|
|
644
|
-
- \`src/codegen/\` — Code generation pipeline
|
|
645
|
-
- \`src/spec/\` — Upstream schema fetching and parsing
|
|
646
|
-
- \`src/lint/rules/\` — Lint rules
|
|
647
|
-
- \`src/lsp/\` — LSP completions and hover
|
|
648
|
-
- \`src/generated/\` — Generated artifacts (do not edit)
|
|
649
|
-
`;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// ── Test file generators ─────────────────────────────────────────────
|
|
653
|
-
|
|
654
|
-
function generatePluginTestTs(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
655
|
-
return `import { describe, expect, it } from "bun:test";
|
|
656
|
-
import { ${names.pluginVarName} } from "./plugin";
|
|
657
|
-
import { isLexiconPlugin } from "@intentius/chant/lexicon";
|
|
658
|
-
|
|
659
|
-
describe("${name} plugin", () => {
|
|
660
|
-
it("is a valid LexiconPlugin", () => {
|
|
661
|
-
expect(isLexiconPlugin(${names.pluginVarName})).toBe(true);
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
it("has the correct name", () => {
|
|
665
|
-
expect(${names.pluginVarName}.name).toBe("${name}");
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
it("has a serializer", () => {
|
|
669
|
-
expect(${names.pluginVarName}.serializer).toBeDefined();
|
|
670
|
-
});
|
|
671
|
-
});
|
|
672
|
-
`;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function generateSerializerTestTs(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
676
|
-
return `import { describe, expect, it } from "bun:test";
|
|
677
|
-
import { ${names.serializerVarName} } from "./serializer";
|
|
678
|
-
|
|
679
|
-
describe("${name} serializer", () => {
|
|
680
|
-
it("serializes an empty map to valid JSON", () => {
|
|
681
|
-
const result = ${names.serializerVarName}.serialize(new Map());
|
|
682
|
-
expect(typeof result).toBe("string");
|
|
683
|
-
expect(() => JSON.parse(result)).not.toThrow();
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
it("has the correct name", () => {
|
|
687
|
-
expect(${names.serializerVarName}.name).toBe("${name}");
|
|
688
|
-
});
|
|
689
|
-
});
|
|
690
|
-
`;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
function generateCompletionsTestTs(): string {
|
|
694
|
-
return `import { describe, expect, it } from "bun:test";
|
|
695
|
-
import { completions } from "./completions";
|
|
696
|
-
|
|
697
|
-
describe("LSP completions", () => {
|
|
698
|
-
it("returns an array", () => {
|
|
699
|
-
// TODO: Replace with a real CompletionContext
|
|
700
|
-
const result = completions({} as any);
|
|
701
|
-
expect(Array.isArray(result)).toBe(true);
|
|
702
|
-
});
|
|
703
|
-
});
|
|
704
|
-
`;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
function generateHoverTestTs(): string {
|
|
708
|
-
return `import { describe, expect, it } from "bun:test";
|
|
709
|
-
import { hover } from "./hover";
|
|
710
|
-
|
|
711
|
-
describe("LSP hover", () => {
|
|
712
|
-
it("returns undefined for unknown context", () => {
|
|
713
|
-
// TODO: Replace with a real HoverContext
|
|
714
|
-
const result = hover({} as any);
|
|
715
|
-
expect(result).toBeUndefined();
|
|
716
|
-
});
|
|
717
|
-
});
|
|
718
|
-
`;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// ── Example generators ───────────────────────────────────────────────
|
|
722
|
-
|
|
723
|
-
function generateExamplePackageJson(name: string): string {
|
|
724
|
-
return JSON.stringify(
|
|
725
|
-
{
|
|
726
|
-
name: `@intentius/chant-lexicon-${name}-example-getting-started`,
|
|
727
|
-
version: "0.0.1",
|
|
728
|
-
private: true,
|
|
729
|
-
dependencies: {
|
|
730
|
-
[`@intentius/chant-lexicon-${name}`]: "workspace:*",
|
|
731
|
-
"@intentius/chant": "workspace:*",
|
|
732
|
-
},
|
|
733
|
-
},
|
|
734
|
-
null,
|
|
735
|
-
2,
|
|
736
|
-
) + "\n";
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function generateExampleInfraTs(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
740
|
-
return `/**
|
|
741
|
-
* Getting-started example for the ${name} lexicon.
|
|
742
|
-
*
|
|
743
|
-
* TODO: Replace with a real infrastructure definition
|
|
744
|
-
* that uses resources from the ${name} lexicon.
|
|
745
|
-
*/
|
|
746
|
-
|
|
747
|
-
// import { SomeResource } from "${names.packageName}";
|
|
748
|
-
//
|
|
749
|
-
// export const myResource = SomeResource("example", {
|
|
750
|
-
// // properties...
|
|
751
|
-
// });
|
|
752
|
-
`;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// ── Docs site skeleton generators ────────────────────────────────────
|
|
756
|
-
|
|
757
|
-
function generateDocsPackageJson(name: string): string {
|
|
758
|
-
return JSON.stringify(
|
|
759
|
-
{
|
|
760
|
-
name: `@intentius/chant-lexicon-${name}-docs`,
|
|
761
|
-
type: "module",
|
|
762
|
-
version: "0.0.1",
|
|
763
|
-
private: true,
|
|
764
|
-
scripts: {
|
|
765
|
-
dev: "astro dev",
|
|
766
|
-
build: "astro build",
|
|
767
|
-
preview: "astro preview",
|
|
768
|
-
},
|
|
769
|
-
dependencies: {
|
|
770
|
-
"@astrojs/starlight": "^0.37.6",
|
|
771
|
-
astro: "^5.6.1",
|
|
772
|
-
sharp: "^0.34.2",
|
|
773
|
-
},
|
|
774
|
-
},
|
|
775
|
-
null,
|
|
776
|
-
2,
|
|
777
|
-
) + "\n";
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
function generateDocsTsConfig(): string {
|
|
781
|
-
return JSON.stringify(
|
|
782
|
-
{
|
|
783
|
-
extends: "astro/tsconfigs/strict",
|
|
784
|
-
include: [".astro/types.d.ts", "**/*"],
|
|
785
|
-
exclude: ["dist"],
|
|
786
|
-
},
|
|
787
|
-
null,
|
|
788
|
-
2,
|
|
789
|
-
) + "\n";
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
function generateDocsAstroConfig(name: string): string {
|
|
793
|
-
const displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
794
|
-
return `// @ts-check
|
|
795
|
-
import { defineConfig } from 'astro/config';
|
|
796
|
-
import starlight from '@astrojs/starlight';
|
|
797
|
-
|
|
798
|
-
export default defineConfig({
|
|
799
|
-
integrations: [
|
|
800
|
-
starlight({
|
|
801
|
-
title: '${displayName}',
|
|
802
|
-
sidebar: [
|
|
803
|
-
{ label: 'Overview', slug: '' },
|
|
804
|
-
],
|
|
805
|
-
}),
|
|
806
|
-
],
|
|
807
|
-
});
|
|
808
|
-
`;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
function generateDocsContentConfig(): string {
|
|
812
|
-
return `import { defineCollection } from 'astro:content';
|
|
813
|
-
import { docsLoader } from '@astrojs/starlight/loaders';
|
|
814
|
-
import { docsSchema } from '@astrojs/starlight/schema';
|
|
815
|
-
|
|
816
|
-
export const collections = {
|
|
817
|
-
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
|
818
|
-
};
|
|
819
|
-
`;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
function generateDocsIndexMdx(name: string): string {
|
|
823
|
-
const displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
824
|
-
return `---
|
|
825
|
-
title: Overview
|
|
826
|
-
description: ${displayName} lexicon for chant
|
|
827
|
-
---
|
|
828
|
-
|
|
829
|
-
Welcome to the ${displayName} lexicon documentation.
|
|
830
|
-
|
|
831
|
-
Run \`just generate\` to populate this site with generated reference pages.
|
|
832
|
-
`;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
73
|
// ── Main command ─────────────────────────────────────────────────────
|
|
836
74
|
|
|
837
75
|
/**
|