@intentius/chant 0.0.1
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/README.md +365 -0
- package/package.json +22 -0
- package/src/attrref.test.ts +148 -0
- package/src/attrref.ts +50 -0
- package/src/barrel.test.ts +157 -0
- package/src/barrel.ts +101 -0
- package/src/bench.test.ts +227 -0
- package/src/build.test.ts +437 -0
- package/src/build.ts +425 -0
- package/src/builder.test.ts +312 -0
- package/src/builder.ts +56 -0
- package/src/child-project.ts +44 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
- package/src/cli/commands/build.test.ts +149 -0
- package/src/cli/commands/build.ts +344 -0
- package/src/cli/commands/diff.test.ts +148 -0
- package/src/cli/commands/diff.ts +221 -0
- package/src/cli/commands/doctor.test.ts +239 -0
- package/src/cli/commands/doctor.ts +224 -0
- package/src/cli/commands/import.test.ts +379 -0
- package/src/cli/commands/import.ts +335 -0
- package/src/cli/commands/init-lexicon.test.ts +297 -0
- package/src/cli/commands/init-lexicon.ts +993 -0
- package/src/cli/commands/init.test.ts +317 -0
- package/src/cli/commands/init.ts +505 -0
- package/src/cli/commands/licenses.ts +165 -0
- package/src/cli/commands/lint.test.ts +332 -0
- package/src/cli/commands/lint.ts +408 -0
- package/src/cli/commands/list.test.ts +100 -0
- package/src/cli/commands/list.ts +108 -0
- package/src/cli/commands/update.test.ts +38 -0
- package/src/cli/commands/update.ts +207 -0
- package/src/cli/conflict-check.test.ts +255 -0
- package/src/cli/conflict-check.ts +89 -0
- package/src/cli/debug.ts +8 -0
- package/src/cli/format.test.ts +140 -0
- package/src/cli/format.ts +133 -0
- package/src/cli/handlers/build.ts +58 -0
- package/src/cli/handlers/dev.ts +38 -0
- package/src/cli/handlers/init.ts +46 -0
- package/src/cli/handlers/lint.ts +36 -0
- package/src/cli/handlers/misc.ts +57 -0
- package/src/cli/handlers/serve.ts +26 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/lsp/capabilities.ts +46 -0
- package/src/cli/lsp/diagnostics.ts +52 -0
- package/src/cli/lsp/server.test.ts +618 -0
- package/src/cli/lsp/server.ts +393 -0
- package/src/cli/main.test.ts +257 -0
- package/src/cli/main.ts +224 -0
- package/src/cli/mcp/resources/context.ts +59 -0
- package/src/cli/mcp/server.test.ts +747 -0
- package/src/cli/mcp/server.ts +402 -0
- package/src/cli/mcp/tools/build.ts +117 -0
- package/src/cli/mcp/tools/import.ts +48 -0
- package/src/cli/mcp/tools/lint.ts +45 -0
- package/src/cli/plugins.test.ts +31 -0
- package/src/cli/plugins.ts +94 -0
- package/src/cli/registry.ts +73 -0
- package/src/cli/reporters/stylish.test.ts +282 -0
- package/src/cli/reporters/stylish.ts +186 -0
- package/src/cli/watch.test.ts +81 -0
- package/src/cli/watch.ts +101 -0
- package/src/codegen/case.test.ts +30 -0
- package/src/codegen/case.ts +11 -0
- package/src/codegen/coverage.ts +167 -0
- package/src/codegen/docs.ts +634 -0
- package/src/codegen/fetch.test.ts +119 -0
- package/src/codegen/fetch.ts +261 -0
- package/src/codegen/generate-registry.test.ts +118 -0
- package/src/codegen/generate-registry.ts +107 -0
- package/src/codegen/generate-runtime-index.test.ts +81 -0
- package/src/codegen/generate-runtime-index.ts +99 -0
- package/src/codegen/generate-typescript.test.ts +146 -0
- package/src/codegen/generate-typescript.ts +161 -0
- package/src/codegen/generate.ts +206 -0
- package/src/codegen/json-patch.test.ts +113 -0
- package/src/codegen/json-patch.ts +151 -0
- package/src/codegen/json-schema.test.ts +196 -0
- package/src/codegen/json-schema.ts +209 -0
- package/src/codegen/naming.ts +201 -0
- package/src/codegen/package.ts +161 -0
- package/src/codegen/rollback.test.ts +92 -0
- package/src/codegen/rollback.ts +115 -0
- package/src/codegen/topo-sort.test.ts +69 -0
- package/src/codegen/topo-sort.ts +46 -0
- package/src/codegen/typecheck.test.ts +37 -0
- package/src/codegen/typecheck.ts +74 -0
- package/src/codegen/validate.test.ts +86 -0
- package/src/codegen/validate.ts +143 -0
- package/src/composite.test.ts +426 -0
- package/src/composite.ts +243 -0
- package/src/config.test.ts +91 -0
- package/src/config.ts +87 -0
- package/src/declarable.test.ts +160 -0
- package/src/declarable.ts +47 -0
- package/src/detectLexicon.test.ts +236 -0
- package/src/detectLexicon.ts +37 -0
- package/src/discovery/cache.test.ts +78 -0
- package/src/discovery/cache.ts +86 -0
- package/src/discovery/collect.test.ts +269 -0
- package/src/discovery/collect.ts +51 -0
- package/src/discovery/cycles.test.ts +238 -0
- package/src/discovery/cycles.ts +107 -0
- package/src/discovery/files.test.ts +154 -0
- package/src/discovery/files.ts +61 -0
- package/src/discovery/graph.test.ts +476 -0
- package/src/discovery/graph.ts +150 -0
- package/src/discovery/import.test.ts +199 -0
- package/src/discovery/import.ts +20 -0
- package/src/discovery/index.test.ts +272 -0
- package/src/discovery/index.ts +132 -0
- package/src/discovery/resolve.test.ts +267 -0
- package/src/discovery/resolve.ts +54 -0
- package/src/errors.test.ts +138 -0
- package/src/errors.ts +86 -0
- package/src/import/base-parser.test.ts +67 -0
- package/src/import/base-parser.ts +48 -0
- package/src/import/generator.ts +21 -0
- package/src/import/ir-utils.test.ts +103 -0
- package/src/import/ir-utils.ts +87 -0
- package/src/import/parser.ts +41 -0
- package/src/index.ts +60 -0
- package/src/intrinsic-interpolation.test.ts +91 -0
- package/src/intrinsic-interpolation.ts +89 -0
- package/src/intrinsic.test.ts +69 -0
- package/src/intrinsic.ts +43 -0
- package/src/lexicon-integrity.test.ts +94 -0
- package/src/lexicon-integrity.ts +69 -0
- package/src/lexicon-manifest.test.ts +101 -0
- package/src/lexicon-manifest.ts +71 -0
- package/src/lexicon-output.test.ts +182 -0
- package/src/lexicon-output.ts +82 -0
- package/src/lexicon-schema.test.ts +239 -0
- package/src/lexicon-schema.ts +144 -0
- package/src/lexicon.ts +212 -0
- package/src/lint/config-overrides.test.ts +254 -0
- package/src/lint/config.test.ts +644 -0
- package/src/lint/config.ts +375 -0
- package/src/lint/declarative.test.ts +256 -0
- package/src/lint/declarative.ts +187 -0
- package/src/lint/engine.test.ts +465 -0
- package/src/lint/engine.ts +172 -0
- package/src/lint/named-checks.test.ts +37 -0
- package/src/lint/named-checks.ts +33 -0
- package/src/lint/parser.test.ts +129 -0
- package/src/lint/parser.ts +42 -0
- package/src/lint/post-synth.test.ts +113 -0
- package/src/lint/post-synth.ts +76 -0
- package/src/lint/presets/relaxed.json +19 -0
- package/src/lint/presets/strict.json +19 -0
- package/src/lint/rule-loader.test.ts +67 -0
- package/src/lint/rule-loader.ts +67 -0
- package/src/lint/rule-options.test.ts +141 -0
- package/src/lint/rule.test.ts +196 -0
- package/src/lint/rule.ts +98 -0
- package/src/lint/rules/barrel-import-style.test.ts +80 -0
- package/src/lint/rules/barrel-import-style.ts +59 -0
- package/src/lint/rules/composite-scope.ts +55 -0
- package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
- package/src/lint/rules/cor017-composite-name-match.ts +108 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
- package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
- package/src/lint/rules/declarable-naming-convention.ts +70 -0
- package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
- package/src/lint/rules/enforce-barrel-import.ts +81 -0
- package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
- package/src/lint/rules/enforce-barrel-ref.ts +75 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
- package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
- package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
- package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
- package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
- package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
- package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
- package/src/lint/rules/evl004-spread-non-const.ts +111 -0
- package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
- package/src/lint/rules/evl005-resource-block-body.ts +49 -0
- package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
- package/src/lint/rules/evl006-barrel-usage.ts +95 -0
- package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
- package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
- package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
- package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
- package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
- package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
- package/src/lint/rules/export-required.test.ts +213 -0
- package/src/lint/rules/export-required.ts +158 -0
- package/src/lint/rules/file-declarable-limit.test.ts +148 -0
- package/src/lint/rules/file-declarable-limit.ts +96 -0
- package/src/lint/rules/flat-declarations.test.ts +210 -0
- package/src/lint/rules/flat-declarations.ts +70 -0
- package/src/lint/rules/index.ts +99 -0
- package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
- package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
- package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
- package/src/lint/rules/no-redundant-type-import.ts +85 -0
- package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
- package/src/lint/rules/no-redundant-value-cast.ts +46 -0
- package/src/lint/rules/no-string-ref.test.ts +100 -0
- package/src/lint/rules/no-string-ref.ts +66 -0
- package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
- package/src/lint/rules/no-unused-declarable-import.ts +103 -0
- package/src/lint/rules/no-unused-declarable.test.ts +134 -0
- package/src/lint/rules/no-unused-declarable.ts +118 -0
- package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
- package/src/lint/rules/prefer-namespace-import.ts +63 -0
- package/src/lint/rules/single-concern-file.test.ts +156 -0
- package/src/lint/rules/single-concern-file.ts +98 -0
- package/src/lint/rules/stale-barrel-types.ts +60 -0
- package/src/lint/selectors.test.ts +113 -0
- package/src/lint/selectors.ts +188 -0
- package/src/lsp/lexicon-providers.ts +191 -0
- package/src/lsp/types.ts +79 -0
- package/src/mcp/types.ts +22 -0
- package/src/project/scan.test.ts +178 -0
- package/src/project/scan.ts +182 -0
- package/src/project/sync.test.ts +87 -0
- package/src/project/sync.ts +46 -0
- package/src/project-validation.test.ts +64 -0
- package/src/project-validation.ts +79 -0
- package/src/pseudo-parameter.test.ts +39 -0
- package/src/pseudo-parameter.ts +47 -0
- package/src/runtime.ts +68 -0
- package/src/serializer-walker.test.ts +124 -0
- package/src/serializer-walker.ts +83 -0
- package/src/serializer.ts +42 -0
- package/src/sort.test.ts +290 -0
- package/src/sort.ts +58 -0
- package/src/stack-output.ts +82 -0
- package/src/types.test.ts +307 -0
- package/src/types.ts +46 -0
- package/src/utils.test.ts +195 -0
- package/src/utils.ts +46 -0
- package/src/validation.test.ts +308 -0
- package/src/validation.ts +50 -0
|
@@ -0,0 +1,993 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readdirSync } from "fs";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
import { formatSuccess, formatWarning } from "../format";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Init-lexicon command options
|
|
7
|
+
*/
|
|
8
|
+
export interface InitLexiconOptions {
|
|
9
|
+
/** Lexicon name (e.g. "k8s") */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Target directory (defaults to ./lexicons/<name>) */
|
|
12
|
+
path?: string;
|
|
13
|
+
/** Force init even in non-empty directory */
|
|
14
|
+
force?: boolean;
|
|
15
|
+
/** Skip install prompt */
|
|
16
|
+
skipInstall?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Init-lexicon command result
|
|
21
|
+
*/
|
|
22
|
+
export interface InitLexiconResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
createdFiles: string[];
|
|
25
|
+
warnings: string[];
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Name derivation helpers ──────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
function toCamelCase(name: string): string {
|
|
32
|
+
return name.replace(/[-_]+(.)/g, (_, c) => c.toUpperCase());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function deriveNames(name: string) {
|
|
36
|
+
const camel = toCamelCase(name);
|
|
37
|
+
return {
|
|
38
|
+
pluginVarName: `${camel}Plugin`,
|
|
39
|
+
serializerVarName: `${camel}Serializer`,
|
|
40
|
+
rulePrefix: name.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 3),
|
|
41
|
+
packageName: `@intentius/chant-lexicon-${name}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── writeIfNotExists ─────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
function writeIfNotExists(
|
|
48
|
+
filePath: string,
|
|
49
|
+
content: string,
|
|
50
|
+
relativePath: string,
|
|
51
|
+
createdFiles: string[],
|
|
52
|
+
warnings: string[],
|
|
53
|
+
): void {
|
|
54
|
+
if (!existsSync(filePath)) {
|
|
55
|
+
writeFileSync(filePath, content);
|
|
56
|
+
createdFiles.push(relativePath);
|
|
57
|
+
} else {
|
|
58
|
+
warnings.push(`${relativePath} already exists, skipping`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Template generators ──────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function generatePluginTs(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
65
|
+
return `import type { LexiconPlugin } from "@intentius/chant/lexicon";
|
|
66
|
+
import { ${names.serializerVarName} } from "./serializer";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* ${name} lexicon plugin.
|
|
70
|
+
*
|
|
71
|
+
* Implements all required LexiconPlugin lifecycle methods.
|
|
72
|
+
*/
|
|
73
|
+
export const ${names.pluginVarName}: LexiconPlugin = {
|
|
74
|
+
name: "${name}",
|
|
75
|
+
serializer: ${names.serializerVarName},
|
|
76
|
+
|
|
77
|
+
// ── Required lifecycle methods ────────────────────────────────
|
|
78
|
+
|
|
79
|
+
async generate(options?: { verbose?: boolean }): Promise<void> {
|
|
80
|
+
const { generate } = await import("./codegen/generate");
|
|
81
|
+
await generate(options);
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async validate(options?: { verbose?: boolean }): Promise<void> {
|
|
85
|
+
const { validate } = await import("./validate");
|
|
86
|
+
await validate(options);
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void> {
|
|
90
|
+
// TODO: Implement coverage analysis
|
|
91
|
+
console.error("Coverage analysis not yet implemented");
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
|
|
95
|
+
const { packageLexicon } = await import("./codegen/package");
|
|
96
|
+
await packageLexicon(options);
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async rollback(options?: { restore?: string; verbose?: boolean }): Promise<void> {
|
|
100
|
+
const { listSnapshots, restoreSnapshot } = await import("./codegen/rollback");
|
|
101
|
+
const { join, dirname } = await import("path");
|
|
102
|
+
const { fileURLToPath } = await import("url");
|
|
103
|
+
|
|
104
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
105
|
+
const snapshotsDir = join(pkgDir, ".snapshots");
|
|
106
|
+
|
|
107
|
+
if (options?.restore) {
|
|
108
|
+
const generatedDir = join(pkgDir, "src", "generated");
|
|
109
|
+
restoreSnapshot(String(options.restore), generatedDir);
|
|
110
|
+
console.error(\`Restored snapshot: \${options.restore}\`);
|
|
111
|
+
} else {
|
|
112
|
+
const snapshots = listSnapshots(snapshotsDir);
|
|
113
|
+
if (snapshots.length === 0) {
|
|
114
|
+
console.error("No snapshots available.");
|
|
115
|
+
} else {
|
|
116
|
+
console.error(\`Available snapshots (\${snapshots.length}):\`);
|
|
117
|
+
for (const s of snapshots) {
|
|
118
|
+
console.error(\` \${s.timestamp} \${s.resources} resources \${s.path}\`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// ── Optional extensions (uncomment and implement as needed) ───
|
|
125
|
+
|
|
126
|
+
// lintRules(): LintRule[] {
|
|
127
|
+
// return [];
|
|
128
|
+
// },
|
|
129
|
+
|
|
130
|
+
// declarativeRules(): RuleSpec[] {
|
|
131
|
+
// return [];
|
|
132
|
+
// },
|
|
133
|
+
|
|
134
|
+
// postSynthChecks(): PostSynthCheck[] {
|
|
135
|
+
// return [];
|
|
136
|
+
// },
|
|
137
|
+
|
|
138
|
+
// intrinsics(): IntrinsicDef[] {
|
|
139
|
+
// return [];
|
|
140
|
+
// },
|
|
141
|
+
|
|
142
|
+
// pseudoParameters(): string[] {
|
|
143
|
+
// return [];
|
|
144
|
+
// },
|
|
145
|
+
|
|
146
|
+
// detectTemplate(data: unknown): boolean {
|
|
147
|
+
// return false;
|
|
148
|
+
// },
|
|
149
|
+
|
|
150
|
+
// templateParser(): TemplateParser {
|
|
151
|
+
// // return new MyParser();
|
|
152
|
+
// },
|
|
153
|
+
|
|
154
|
+
// templateGenerator(): TypeScriptGenerator {
|
|
155
|
+
// // return new MyGenerator();
|
|
156
|
+
// },
|
|
157
|
+
|
|
158
|
+
// skills(): SkillDefinition[] {
|
|
159
|
+
// return [];
|
|
160
|
+
// },
|
|
161
|
+
|
|
162
|
+
// completionProvider(ctx: CompletionContext): CompletionItem[] {
|
|
163
|
+
// return [];
|
|
164
|
+
// },
|
|
165
|
+
|
|
166
|
+
// hoverProvider(ctx: HoverContext): HoverInfo | undefined {
|
|
167
|
+
// return undefined;
|
|
168
|
+
// },
|
|
169
|
+
|
|
170
|
+
// docs(options?: { verbose?: boolean }): Promise<void> {
|
|
171
|
+
// const { generateDocs } = await import("./codegen/docs");
|
|
172
|
+
// return generateDocs(options);
|
|
173
|
+
// },
|
|
174
|
+
};
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function generateIndexTs(names: ReturnType<typeof deriveNames>): string {
|
|
179
|
+
return `// Plugin
|
|
180
|
+
export { ${names.pluginVarName} } from "./plugin";
|
|
181
|
+
|
|
182
|
+
// Serializer
|
|
183
|
+
export { ${names.serializerVarName} } from "./serializer";
|
|
184
|
+
|
|
185
|
+
// Generated resources — export everything from generated index
|
|
186
|
+
// After running \`chant generate\`, this re-exports all resource classes
|
|
187
|
+
// export * from "./generated/index";
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function generateSerializerTs(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
192
|
+
return `import type { Serializer, Declarable } from "@intentius/chant";
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* ${name} serializer — produces minimal JSON output.
|
|
196
|
+
*
|
|
197
|
+
* TODO: Replace with your lexicon's output format.
|
|
198
|
+
*/
|
|
199
|
+
export const ${names.serializerVarName}: Serializer = {
|
|
200
|
+
name: "${name}",
|
|
201
|
+
rulePrefix: "${names.rulePrefix}",
|
|
202
|
+
|
|
203
|
+
serialize(entities: Map<string, Declarable>): string {
|
|
204
|
+
const resources: Record<string, unknown> = {};
|
|
205
|
+
|
|
206
|
+
for (const [entityName, entity] of entities) {
|
|
207
|
+
resources[entityName] = {
|
|
208
|
+
type: entity.entityType,
|
|
209
|
+
// TODO: Convert entity properties to your output format
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return JSON.stringify({ resources }, null, 2);
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function generateCodegenGenerateTs(): string {
|
|
220
|
+
return `import { generatePipeline, writeGeneratedArtifacts } from "@intentius/chant/codegen/generate";
|
|
221
|
+
import type { GenerateResult } from "@intentius/chant/codegen/generate";
|
|
222
|
+
import { dirname } from "path";
|
|
223
|
+
import { fileURLToPath } from "url";
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Run the code generation pipeline.
|
|
227
|
+
*
|
|
228
|
+
* Each callback has a TODO describing what to implement.
|
|
229
|
+
*/
|
|
230
|
+
export async function generate(options?: { verbose?: boolean }): Promise<GenerateResult> {
|
|
231
|
+
const result = await generatePipeline({
|
|
232
|
+
// Must return Map<typeName, Buffer> — each entry is one schema file.
|
|
233
|
+
// Example: fetch a zip, extract JSON files, key by type name.
|
|
234
|
+
// See lexicons/aws/src/spec/fetch.ts for a working example.
|
|
235
|
+
fetchSchemas: async (opts) => {
|
|
236
|
+
throw new Error("TODO: implement fetchSchemas — download your upstream spec");
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// Must return a ParsedResult (with propertyTypes[] and enums[] at minimum).
|
|
240
|
+
// Return null to skip a schema file.
|
|
241
|
+
// See lexicons/aws/src/spec/parse.ts for a working example.
|
|
242
|
+
parseSchema: (name, data) => {
|
|
243
|
+
throw new Error("TODO: implement parseSchema — parse a single schema file");
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// Must return a NamingStrategy instance.
|
|
247
|
+
// See lexicons/aws/src/codegen/naming.ts and ./naming.ts for setup.
|
|
248
|
+
createNaming: (results) => {
|
|
249
|
+
throw new Error("TODO: implement createNaming — return a NamingStrategy instance");
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
// Must return a string of JSON (the lexicon registry).
|
|
253
|
+
// Use buildRegistry + serializeRegistry from @intentius/chant/codegen/generate-registry.
|
|
254
|
+
// See lexicons/aws/src/codegen/generate.ts for a working example.
|
|
255
|
+
generateRegistry: (results, naming) => {
|
|
256
|
+
throw new Error("TODO: implement generateRegistry — produce lexicon JSON");
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
// Must return a string of TypeScript declarations (.d.ts content).
|
|
260
|
+
// See lexicons/aws/src/codegen/generate.ts for a working example.
|
|
261
|
+
generateTypes: (results, naming) => {
|
|
262
|
+
throw new Error("TODO: implement generateTypes — produce .d.ts content");
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// Must return a string of TypeScript (runtime index with factory exports).
|
|
266
|
+
// Use generateRuntimeIndex from @intentius/chant/codegen/generate-runtime-index.
|
|
267
|
+
// See lexicons/aws/src/codegen/generate.ts for a working example.
|
|
268
|
+
generateRuntimeIndex: (results, naming) => {
|
|
269
|
+
throw new Error("TODO: implement generateRuntimeIndex — produce index.ts content");
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (options?.verbose) {
|
|
274
|
+
console.error(\`Generated \${result.resources} resources, \${result.properties} property types\`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Write generated files to the package directory.
|
|
282
|
+
*/
|
|
283
|
+
export function writeGeneratedFiles(result: GenerateResult, pkgDir?: string): void {
|
|
284
|
+
const dir = pkgDir ?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
285
|
+
writeGeneratedArtifacts({
|
|
286
|
+
baseDir: dir,
|
|
287
|
+
files: {
|
|
288
|
+
"lexicon.json": result.lexiconJSON,
|
|
289
|
+
"index.d.ts": result.typesDTS,
|
|
290
|
+
"index.ts": result.indexTS,
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function generateCodegenGenerateCliTs(): string {
|
|
298
|
+
return `#!/usr/bin/env bun
|
|
299
|
+
import { generate, writeGeneratedFiles } from "./generate";
|
|
300
|
+
import { dirname } from "path";
|
|
301
|
+
import { fileURLToPath } from "url";
|
|
302
|
+
|
|
303
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
304
|
+
const result = await generate({ verbose: true });
|
|
305
|
+
writeGeneratedFiles(result, pkgDir);
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function generateCodegenNamingTs(): string {
|
|
310
|
+
return `import { NamingStrategy, type NamingConfig, type NamingInput } from "@intentius/chant/codegen/naming";
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Naming configuration for this lexicon.
|
|
314
|
+
*
|
|
315
|
+
* TODO: Populate these tables with your provider's naming conventions.
|
|
316
|
+
*/
|
|
317
|
+
export const namingConfig: NamingConfig = {
|
|
318
|
+
// High-priority short names for common resource types
|
|
319
|
+
priorityNames: {},
|
|
320
|
+
|
|
321
|
+
// Aliases for resource types that need alternate names
|
|
322
|
+
priorityAliases: {},
|
|
323
|
+
|
|
324
|
+
// Aliases for property types
|
|
325
|
+
priorityPropertyAliases: {},
|
|
326
|
+
|
|
327
|
+
// Abbreviations for service names (used in collision resolution)
|
|
328
|
+
serviceAbbreviations: {},
|
|
329
|
+
|
|
330
|
+
// Extract the short name from a fully-qualified type string
|
|
331
|
+
shortName: (typeName: string) => typeName.split("::").pop()!,
|
|
332
|
+
|
|
333
|
+
// Extract the service name from a fully-qualified type string
|
|
334
|
+
serviceName: (typeName: string) => typeName.split("::")[1] ?? typeName,
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Create a NamingStrategy instance from parsed results.
|
|
339
|
+
*/
|
|
340
|
+
export function createNaming(inputs: NamingInput[]): NamingStrategy {
|
|
341
|
+
return new NamingStrategy(inputs, namingConfig);
|
|
342
|
+
}
|
|
343
|
+
`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function generateCodegenPackageTs(name: string): string {
|
|
347
|
+
return `import { packagePipeline } from "@intentius/chant/codegen/package";
|
|
348
|
+
import type { PackagePipelineConfig } from "@intentius/chant/codegen/package";
|
|
349
|
+
import { generate } from "./generate";
|
|
350
|
+
import { dirname } from "path";
|
|
351
|
+
import { fileURLToPath } from "url";
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Package the ${name} lexicon for distribution.
|
|
355
|
+
*/
|
|
356
|
+
export async function packageLexicon(options?: { verbose?: boolean; force?: boolean }) {
|
|
357
|
+
const srcDir = dirname(fileURLToPath(import.meta.url));
|
|
358
|
+
|
|
359
|
+
const { spec, stats } = await packagePipeline({
|
|
360
|
+
generate: (opts) => generate({ verbose: opts?.verbose, force: opts?.force }),
|
|
361
|
+
buildManifest: (genResult) => ({
|
|
362
|
+
name: "${name}",
|
|
363
|
+
version: "0.0.1",
|
|
364
|
+
}),
|
|
365
|
+
srcDir,
|
|
366
|
+
collectSkills: () => new Map(),
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
console.error(\`Packaged \${stats.resources} resources, \${stats.ruleCount} rules\`);
|
|
370
|
+
return { spec, stats };
|
|
371
|
+
}
|
|
372
|
+
`;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function generateCodegenRollbackTs(): string {
|
|
376
|
+
return `import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, cpSync } from "fs";
|
|
377
|
+
import { join, basename } from "path";
|
|
378
|
+
|
|
379
|
+
export interface Snapshot {
|
|
380
|
+
timestamp: string;
|
|
381
|
+
resources: number;
|
|
382
|
+
path: string;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* List available generation snapshots.
|
|
387
|
+
*/
|
|
388
|
+
export function listSnapshots(snapshotsDir: string): Snapshot[] {
|
|
389
|
+
if (!existsSync(snapshotsDir)) return [];
|
|
390
|
+
|
|
391
|
+
return readdirSync(snapshotsDir)
|
|
392
|
+
.filter((d) => !d.startsWith("."))
|
|
393
|
+
.sort()
|
|
394
|
+
.reverse()
|
|
395
|
+
.map((dir) => {
|
|
396
|
+
const fullPath = join(snapshotsDir, dir);
|
|
397
|
+
const metaPath = join(fullPath, "meta.json");
|
|
398
|
+
let resources = 0;
|
|
399
|
+
if (existsSync(metaPath)) {
|
|
400
|
+
try {
|
|
401
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
402
|
+
resources = meta.resources ?? 0;
|
|
403
|
+
} catch {}
|
|
404
|
+
}
|
|
405
|
+
return { timestamp: dir, resources, path: fullPath };
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Restore a snapshot to the generated directory.
|
|
411
|
+
*/
|
|
412
|
+
export function restoreSnapshot(timestamp: string, generatedDir: string): void {
|
|
413
|
+
const snapshotsDir = join(generatedDir, "..", "..", ".snapshots");
|
|
414
|
+
const snapshotDir = join(snapshotsDir, timestamp);
|
|
415
|
+
if (!existsSync(snapshotDir)) {
|
|
416
|
+
throw new Error(\`Snapshot not found: \${timestamp}\`);
|
|
417
|
+
}
|
|
418
|
+
mkdirSync(generatedDir, { recursive: true });
|
|
419
|
+
cpSync(snapshotDir, generatedDir, { recursive: true });
|
|
420
|
+
}
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function generateCodegenDocsTs(name: string): string {
|
|
425
|
+
return `import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Generate documentation site for the ${name} lexicon.
|
|
429
|
+
*/
|
|
430
|
+
export async function generateDocs(options?: { verbose?: boolean }): Promise<void> {
|
|
431
|
+
const config = {
|
|
432
|
+
name: "${name}",
|
|
433
|
+
displayName: "${name.charAt(0).toUpperCase() + name.slice(1)}",
|
|
434
|
+
description: "${name} lexicon documentation",
|
|
435
|
+
distDir: "./dist",
|
|
436
|
+
outDir: "./docs",
|
|
437
|
+
// TODO: Implement service grouping for your provider
|
|
438
|
+
serviceFromType: (type: string) => type.split("::")[1] ?? type,
|
|
439
|
+
// TODO: Implement resource documentation URLs
|
|
440
|
+
resourceTypeUrl: (type: string) => \`#\${type}\`,
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const result = docsPipeline(config);
|
|
444
|
+
writeDocsSite(config, result);
|
|
445
|
+
|
|
446
|
+
if (options?.verbose) {
|
|
447
|
+
console.error("Documentation generated");
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function generateSpecFetchTs(): string {
|
|
454
|
+
return `import { fetchWithCache, extractFromZip } from "@intentius/chant/codegen/fetch";
|
|
455
|
+
|
|
456
|
+
// TODO: Set this to your upstream schema source URL
|
|
457
|
+
const SCHEMA_URL = "https://example.com/schemas.zip";
|
|
458
|
+
const CACHE_FILE = ".cache/schemas.zip";
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Fetch upstream schemas with caching.
|
|
462
|
+
*
|
|
463
|
+
* TODO: Point SCHEMA_URL at your real upstream schema source.
|
|
464
|
+
*/
|
|
465
|
+
export async function fetchSchemas(options?: { force?: boolean }): Promise<Map<string, string>> {
|
|
466
|
+
const zipData = await fetchWithCache({
|
|
467
|
+
url: SCHEMA_URL,
|
|
468
|
+
cacheFile: CACHE_FILE,
|
|
469
|
+
force: options?.force,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// TODO: Adjust the filter to match your schema file names
|
|
473
|
+
return extractFromZip(zipData, (name) => name.endsWith(".json"));
|
|
474
|
+
}
|
|
475
|
+
`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function generateSpecParseTs(): string {
|
|
479
|
+
return `/**
|
|
480
|
+
* Parsed schema result for a single schema file.
|
|
481
|
+
*/
|
|
482
|
+
export interface ParseResult {
|
|
483
|
+
typeName: string;
|
|
484
|
+
description?: string;
|
|
485
|
+
properties: Map<string, ParsedProperty>;
|
|
486
|
+
attributes: string[];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export interface ParsedProperty {
|
|
490
|
+
name: string;
|
|
491
|
+
type: string;
|
|
492
|
+
required: boolean;
|
|
493
|
+
description?: string;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Parse a single schema file into a ParseResult.
|
|
498
|
+
*
|
|
499
|
+
* TODO: Implement parsing for your schema format.
|
|
500
|
+
*/
|
|
501
|
+
export function parseSchema(name: string, content: string): ParseResult {
|
|
502
|
+
throw new Error(\`TODO: implement parseSchema for \${name}\`);
|
|
503
|
+
}
|
|
504
|
+
`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function generateSampleRuleTs(names: ReturnType<typeof deriveNames>): string {
|
|
508
|
+
return `import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* ${names.rulePrefix}001: Sample lint rule
|
|
512
|
+
*
|
|
513
|
+
* TODO: Replace with a real lint rule for your lexicon.
|
|
514
|
+
*/
|
|
515
|
+
export const sampleRule: LintRule = {
|
|
516
|
+
id: "${names.rulePrefix}001",
|
|
517
|
+
severity: "warning",
|
|
518
|
+
category: "style",
|
|
519
|
+
description: "Sample lint rule — replace with real checks",
|
|
520
|
+
|
|
521
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
522
|
+
// TODO: Implement rule logic
|
|
523
|
+
return [];
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function generateLintRulesIndexTs(): string {
|
|
530
|
+
return `export { sampleRule } from "./sample";
|
|
531
|
+
`;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function generateLspCompletionsTs(name: string): string {
|
|
535
|
+
return `import type { CompletionContext, CompletionItem } from "@intentius/chant/lsp/types";
|
|
536
|
+
// import { LexiconIndex, lexiconCompletions } from "@intentius/chant/lsp/lexicon-providers";
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Provide LSP completions for ${name} resources.
|
|
540
|
+
*
|
|
541
|
+
* TODO: Build a LexiconIndex from your generated lexicon data
|
|
542
|
+
* and delegate to lexiconCompletions().
|
|
543
|
+
*/
|
|
544
|
+
export function completions(ctx: CompletionContext): CompletionItem[] {
|
|
545
|
+
// const index = new LexiconIndex(lexiconData);
|
|
546
|
+
// return lexiconCompletions(ctx, index, "${name} resource");
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function generateLspHoverTs(name: string): string {
|
|
553
|
+
return `import type { HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
|
|
554
|
+
// import { LexiconIndex, lexiconHover } from "@intentius/chant/lsp/lexicon-providers";
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Provide LSP hover information for ${name} resources.
|
|
558
|
+
*
|
|
559
|
+
* TODO: Build a LexiconIndex from your generated lexicon data
|
|
560
|
+
* and delegate to lexiconHover().
|
|
561
|
+
*/
|
|
562
|
+
export function hover(ctx: HoverContext): HoverInfo | undefined {
|
|
563
|
+
// const index = new LexiconIndex(lexiconData);
|
|
564
|
+
// return lexiconHover(ctx, index, myCustomHoverFormatter);
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
`;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function generateImportParserTs(name: string): string {
|
|
571
|
+
return `import type { TemplateParser } from "@intentius/chant/import/parser";
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Template parser for importing external ${name} templates.
|
|
575
|
+
*
|
|
576
|
+
* TODO: Implement the TemplateParser interface for your format.
|
|
577
|
+
*/
|
|
578
|
+
// export class ${name.charAt(0).toUpperCase() + name.slice(1)}Parser implements TemplateParser {
|
|
579
|
+
// parse(data: unknown): IR { ... }
|
|
580
|
+
// }
|
|
581
|
+
`;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function generateImportGeneratorTs(name: string): string {
|
|
585
|
+
return `import type { TypeScriptGenerator } from "@intentius/chant/import/generator";
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* TypeScript generator for converting imported ${name} templates.
|
|
589
|
+
*
|
|
590
|
+
* TODO: Implement the TypeScriptGenerator interface for your format.
|
|
591
|
+
*/
|
|
592
|
+
// export class ${name.charAt(0).toUpperCase() + name.slice(1)}Generator implements TypeScriptGenerator {
|
|
593
|
+
// generate(ir: IR): string { ... }
|
|
594
|
+
// }
|
|
595
|
+
`;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function generateCoverageTs(name: string): string {
|
|
599
|
+
return `/**
|
|
600
|
+
* Coverage analysis for the ${name} lexicon.
|
|
601
|
+
*
|
|
602
|
+
* TODO: Implement coverage analysis that checks how much of the
|
|
603
|
+
* upstream spec is covered by the generated types.
|
|
604
|
+
*/
|
|
605
|
+
export async function analyzeCoverage(options?: { verbose?: boolean }): Promise<void> {
|
|
606
|
+
console.error("Coverage analysis not yet implemented");
|
|
607
|
+
// TODO: Read generated lexicon JSON, compare against upstream spec,
|
|
608
|
+
// and report coverage metrics.
|
|
609
|
+
}
|
|
610
|
+
`;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function generateValidateTs(name: string): string {
|
|
614
|
+
return `/**
|
|
615
|
+
* Validate generated artifacts for the ${name} lexicon.
|
|
616
|
+
*
|
|
617
|
+
* TODO: Add validation checks for your generated files.
|
|
618
|
+
*/
|
|
619
|
+
export async function validate(options?: { verbose?: boolean }): Promise<void> {
|
|
620
|
+
const checks = [
|
|
621
|
+
// TODO: Add checks — e.g. verify lexicon JSON exists, types compile,
|
|
622
|
+
// registry has expected resources, etc.
|
|
623
|
+
{ name: "placeholder", ok: true, error: undefined as string | undefined },
|
|
624
|
+
];
|
|
625
|
+
|
|
626
|
+
for (const check of checks) {
|
|
627
|
+
const status = check.ok ? "OK" : "FAIL";
|
|
628
|
+
const msg = check.error ? \` — \${check.error}\` : "";
|
|
629
|
+
console.error(\` [\${status}] \${check.name}\${msg}\`);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const failed = checks.filter((c) => !c.ok);
|
|
633
|
+
if (failed.length > 0) {
|
|
634
|
+
throw new Error("Validation failed");
|
|
635
|
+
}
|
|
636
|
+
console.error("All validation checks passed.");
|
|
637
|
+
}
|
|
638
|
+
`;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function generateValidateCliTs(): string {
|
|
642
|
+
return `#!/usr/bin/env bun
|
|
643
|
+
import { validate } from "./validate";
|
|
644
|
+
|
|
645
|
+
await validate({ verbose: true });
|
|
646
|
+
`;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function generatePackageJson(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
650
|
+
const pkg = {
|
|
651
|
+
name: names.packageName,
|
|
652
|
+
version: "0.0.1",
|
|
653
|
+
type: "module",
|
|
654
|
+
private: true,
|
|
655
|
+
files: ["src/", "dist/"],
|
|
656
|
+
exports: {
|
|
657
|
+
".": "./src/index.ts",
|
|
658
|
+
"./*": "./src/*",
|
|
659
|
+
"./manifest": "./dist/manifest.json",
|
|
660
|
+
"./meta": "./dist/meta.json",
|
|
661
|
+
"./types": "./dist/types/index.d.ts",
|
|
662
|
+
},
|
|
663
|
+
scripts: {
|
|
664
|
+
generate: "bun run src/codegen/generate-cli.ts",
|
|
665
|
+
validate: "bun run src/validate-cli.ts",
|
|
666
|
+
docs: "bun src/codegen/docs-cli.ts",
|
|
667
|
+
prepack: "bun run generate && bun run validate",
|
|
668
|
+
},
|
|
669
|
+
dependencies: {
|
|
670
|
+
"@intentius/chant": "workspace:*",
|
|
671
|
+
},
|
|
672
|
+
devDependencies: {
|
|
673
|
+
typescript: "^5.9.3",
|
|
674
|
+
},
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
return JSON.stringify(pkg, null, 2) + "\n";
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function generateTsConfig(): string {
|
|
681
|
+
const config = {
|
|
682
|
+
extends: "../../tsconfig.json",
|
|
683
|
+
compilerOptions: {
|
|
684
|
+
rootDir: "./src",
|
|
685
|
+
outDir: "./dist",
|
|
686
|
+
},
|
|
687
|
+
include: ["src/**/*"],
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function generateJustfile(name: string): string {
|
|
694
|
+
return `# Default recipe - list all available commands
|
|
695
|
+
default:
|
|
696
|
+
@just --list
|
|
697
|
+
|
|
698
|
+
# Generate types and metadata from upstream schemas
|
|
699
|
+
generate:
|
|
700
|
+
bun run src/codegen/generate-cli.ts
|
|
701
|
+
|
|
702
|
+
# Validate generated artifacts
|
|
703
|
+
validate:
|
|
704
|
+
bun run src/validate-cli.ts
|
|
705
|
+
|
|
706
|
+
# Generate docs site, install deps, and start dev server
|
|
707
|
+
docs:
|
|
708
|
+
bun run src/codegen/docs-cli.ts
|
|
709
|
+
bun install --cwd docs
|
|
710
|
+
bun --cwd docs dev
|
|
711
|
+
|
|
712
|
+
# Build docs site for production
|
|
713
|
+
docs-build:
|
|
714
|
+
bun run src/codegen/docs-cli.ts
|
|
715
|
+
bun install --cwd docs
|
|
716
|
+
bun --cwd docs build
|
|
717
|
+
|
|
718
|
+
# Package the lexicon (generate + validate)
|
|
719
|
+
package: generate validate
|
|
720
|
+
`;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function generateGitignore(): string {
|
|
724
|
+
return `.snapshots/
|
|
725
|
+
dist/
|
|
726
|
+
node_modules/
|
|
727
|
+
.cache/
|
|
728
|
+
`;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function generateReadme(name: string, names: ReturnType<typeof deriveNames>): string {
|
|
732
|
+
return `# ${names.packageName}
|
|
733
|
+
|
|
734
|
+
${name} lexicon plugin for [chant](https://github.com/intentius/chant).
|
|
735
|
+
|
|
736
|
+
## Getting started
|
|
737
|
+
|
|
738
|
+
\`\`\`bash
|
|
739
|
+
# Generate types from upstream spec
|
|
740
|
+
just generate
|
|
741
|
+
|
|
742
|
+
# Validate generated artifacts
|
|
743
|
+
just validate
|
|
744
|
+
|
|
745
|
+
# Generate documentation
|
|
746
|
+
just docs
|
|
747
|
+
\`\`\`
|
|
748
|
+
|
|
749
|
+
## Project structure
|
|
750
|
+
|
|
751
|
+
- \`src/plugin.ts\` — LexiconPlugin with all lifecycle methods
|
|
752
|
+
- \`src/serializer.ts\` — Build output serializer
|
|
753
|
+
- \`src/codegen/\` — Code generation pipeline
|
|
754
|
+
- \`src/spec/\` — Upstream schema fetching and parsing
|
|
755
|
+
- \`src/lint/rules/\` — Lint rules
|
|
756
|
+
- \`src/lsp/\` — LSP completions and hover
|
|
757
|
+
- \`src/generated/\` — Generated artifacts (do not edit)
|
|
758
|
+
`;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// ── Docs site skeleton generators ────────────────────────────────────
|
|
762
|
+
|
|
763
|
+
function generateDocsPackageJson(name: string): string {
|
|
764
|
+
return JSON.stringify(
|
|
765
|
+
{
|
|
766
|
+
name: `@intentius/chant-lexicon-${name}-docs`,
|
|
767
|
+
type: "module",
|
|
768
|
+
version: "0.0.1",
|
|
769
|
+
private: true,
|
|
770
|
+
scripts: {
|
|
771
|
+
dev: "astro dev",
|
|
772
|
+
build: "astro build",
|
|
773
|
+
preview: "astro preview",
|
|
774
|
+
},
|
|
775
|
+
dependencies: {
|
|
776
|
+
"@astrojs/starlight": "^0.37.6",
|
|
777
|
+
astro: "^5.6.1",
|
|
778
|
+
sharp: "^0.34.2",
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
null,
|
|
782
|
+
2,
|
|
783
|
+
) + "\n";
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function generateDocsTsConfig(): string {
|
|
787
|
+
return JSON.stringify(
|
|
788
|
+
{
|
|
789
|
+
extends: "astro/tsconfigs/strict",
|
|
790
|
+
include: [".astro/types.d.ts", "**/*"],
|
|
791
|
+
exclude: ["dist"],
|
|
792
|
+
},
|
|
793
|
+
null,
|
|
794
|
+
2,
|
|
795
|
+
) + "\n";
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function generateDocsAstroConfig(name: string): string {
|
|
799
|
+
const displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
800
|
+
return `// @ts-check
|
|
801
|
+
import { defineConfig } from 'astro/config';
|
|
802
|
+
import starlight from '@astrojs/starlight';
|
|
803
|
+
|
|
804
|
+
export default defineConfig({
|
|
805
|
+
integrations: [
|
|
806
|
+
starlight({
|
|
807
|
+
title: '${displayName}',
|
|
808
|
+
sidebar: [
|
|
809
|
+
{ label: 'Overview', slug: '' },
|
|
810
|
+
],
|
|
811
|
+
}),
|
|
812
|
+
],
|
|
813
|
+
});
|
|
814
|
+
`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function generateDocsContentConfig(): string {
|
|
818
|
+
return `import { defineCollection } from 'astro:content';
|
|
819
|
+
import { docsLoader } from '@astrojs/starlight/loaders';
|
|
820
|
+
import { docsSchema } from '@astrojs/starlight/schema';
|
|
821
|
+
|
|
822
|
+
export const collections = {
|
|
823
|
+
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
|
824
|
+
};
|
|
825
|
+
`;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function generateDocsIndexMdx(name: string): string {
|
|
829
|
+
const displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
830
|
+
return `---
|
|
831
|
+
title: Overview
|
|
832
|
+
description: ${displayName} lexicon for chant
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
Welcome to the ${displayName} lexicon documentation.
|
|
836
|
+
|
|
837
|
+
Run \`just generate\` to populate this site with generated reference pages.
|
|
838
|
+
`;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// ── Main command ─────────────────────────────────────────────────────
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Execute the init-lexicon command.
|
|
845
|
+
*/
|
|
846
|
+
export async function initLexiconCommand(options: InitLexiconOptions): Promise<InitLexiconResult> {
|
|
847
|
+
const { name } = options;
|
|
848
|
+
const names = deriveNames(name);
|
|
849
|
+
const targetDir = resolve(options.path ?? join("lexicons", name));
|
|
850
|
+
const createdFiles: string[] = [];
|
|
851
|
+
const warnings: string[] = [];
|
|
852
|
+
|
|
853
|
+
// Check if directory is non-empty
|
|
854
|
+
if (existsSync(targetDir)) {
|
|
855
|
+
const contents = readdirSync(targetDir);
|
|
856
|
+
const nonHiddenFiles = contents.filter((f) => !f.startsWith("."));
|
|
857
|
+
|
|
858
|
+
if (nonHiddenFiles.length > 0 && !options.force) {
|
|
859
|
+
return {
|
|
860
|
+
success: false,
|
|
861
|
+
createdFiles: [],
|
|
862
|
+
warnings: [],
|
|
863
|
+
error: `Directory is not empty: ${targetDir}\nUse --force to initialize anyway.`,
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (nonHiddenFiles.length > 0) {
|
|
868
|
+
warnings.push("Initializing in non-empty directory");
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Create directory structure
|
|
873
|
+
const dirs = [
|
|
874
|
+
"",
|
|
875
|
+
"src",
|
|
876
|
+
"src/codegen",
|
|
877
|
+
"src/spec",
|
|
878
|
+
"src/lint",
|
|
879
|
+
"src/lint/rules",
|
|
880
|
+
"src/lsp",
|
|
881
|
+
"src/import",
|
|
882
|
+
"src/generated",
|
|
883
|
+
"docs",
|
|
884
|
+
"docs/src",
|
|
885
|
+
"docs/src/content",
|
|
886
|
+
"docs/src/content/docs",
|
|
887
|
+
"examples/getting-started",
|
|
888
|
+
".snapshots",
|
|
889
|
+
];
|
|
890
|
+
|
|
891
|
+
for (const dir of dirs) {
|
|
892
|
+
const fullPath = join(targetDir, dir);
|
|
893
|
+
if (!existsSync(fullPath)) {
|
|
894
|
+
mkdirSync(fullPath, { recursive: true });
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// File map: relative path -> content
|
|
899
|
+
const files: Record<string, string> = {
|
|
900
|
+
"src/plugin.ts": generatePluginTs(name, names),
|
|
901
|
+
"src/index.ts": generateIndexTs(names),
|
|
902
|
+
"src/serializer.ts": generateSerializerTs(name, names),
|
|
903
|
+
"src/codegen/generate.ts": generateCodegenGenerateTs(),
|
|
904
|
+
"src/codegen/generate-cli.ts": generateCodegenGenerateCliTs(),
|
|
905
|
+
"src/codegen/naming.ts": generateCodegenNamingTs(),
|
|
906
|
+
"src/codegen/package.ts": generateCodegenPackageTs(name),
|
|
907
|
+
"src/codegen/rollback.ts": generateCodegenRollbackTs(),
|
|
908
|
+
"src/codegen/docs.ts": generateCodegenDocsTs(name),
|
|
909
|
+
"src/spec/fetch.ts": generateSpecFetchTs(),
|
|
910
|
+
"src/spec/parse.ts": generateSpecParseTs(),
|
|
911
|
+
"src/lint/rules/sample.ts": generateSampleRuleTs(names),
|
|
912
|
+
"src/lint/rules/index.ts": generateLintRulesIndexTs(),
|
|
913
|
+
"src/lsp/completions.ts": generateLspCompletionsTs(name),
|
|
914
|
+
"src/lsp/hover.ts": generateLspHoverTs(name),
|
|
915
|
+
"src/import/parser.ts": generateImportParserTs(name),
|
|
916
|
+
"src/import/generator.ts": generateImportGeneratorTs(name),
|
|
917
|
+
"src/coverage.ts": generateCoverageTs(name),
|
|
918
|
+
"src/validate.ts": generateValidateTs(name),
|
|
919
|
+
"src/validate-cli.ts": generateValidateCliTs(),
|
|
920
|
+
"package.json": generatePackageJson(name, names),
|
|
921
|
+
"tsconfig.json": generateTsConfig(),
|
|
922
|
+
"justfile": generateJustfile(name),
|
|
923
|
+
".gitignore": generateGitignore(),
|
|
924
|
+
"README.md": generateReadme(name, names),
|
|
925
|
+
"docs/package.json": generateDocsPackageJson(name),
|
|
926
|
+
"docs/tsconfig.json": generateDocsTsConfig(),
|
|
927
|
+
"docs/astro.config.mjs": generateDocsAstroConfig(name),
|
|
928
|
+
"docs/src/content.config.ts": generateDocsContentConfig(),
|
|
929
|
+
"docs/src/content/docs/index.mdx": generateDocsIndexMdx(name),
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
// Write .gitkeep files
|
|
933
|
+
const gitkeeps = [
|
|
934
|
+
"src/generated/.gitkeep",
|
|
935
|
+
"examples/getting-started/.gitkeep",
|
|
936
|
+
".snapshots/.gitkeep",
|
|
937
|
+
];
|
|
938
|
+
|
|
939
|
+
for (const gk of gitkeeps) {
|
|
940
|
+
const fullPath = join(targetDir, gk);
|
|
941
|
+
if (!existsSync(fullPath)) {
|
|
942
|
+
writeFileSync(fullPath, "");
|
|
943
|
+
createdFiles.push(gk);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Write all template files
|
|
948
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
949
|
+
writeIfNotExists(
|
|
950
|
+
join(targetDir, relativePath),
|
|
951
|
+
content,
|
|
952
|
+
relativePath,
|
|
953
|
+
createdFiles,
|
|
954
|
+
warnings,
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
return {
|
|
959
|
+
success: true,
|
|
960
|
+
createdFiles,
|
|
961
|
+
warnings,
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Print the result of init-lexicon and show next steps.
|
|
967
|
+
*/
|
|
968
|
+
export async function printInitLexiconResult(result: InitLexiconResult): Promise<void> {
|
|
969
|
+
if (!result.success) {
|
|
970
|
+
console.error(result.error);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
for (const warning of result.warnings) {
|
|
975
|
+
console.error(formatWarning({ message: warning }));
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
if (result.createdFiles.length > 0) {
|
|
979
|
+
console.log(formatSuccess("Created:"));
|
|
980
|
+
for (const file of result.createdFiles) {
|
|
981
|
+
console.log(` ${file}`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
console.log("");
|
|
986
|
+
console.log("Next steps:");
|
|
987
|
+
console.log(" 1. cd into the lexicon directory");
|
|
988
|
+
console.log(" 2. bun install");
|
|
989
|
+
console.log(" 3. Edit src/spec/fetch.ts — point at your upstream schema source");
|
|
990
|
+
console.log(" 4. Edit src/spec/parse.ts — parse your schema format");
|
|
991
|
+
console.log(" 5. just generate — generate types from spec");
|
|
992
|
+
console.log(" 6. just validate — check generated artifacts");
|
|
993
|
+
}
|