@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
package/src/build.ts
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import type { Declarable } from "./declarable";
|
|
2
|
+
import type { Serializer, SerializerResult } from "./serializer";
|
|
3
|
+
import type { DiscoveryError, BuildError } from "./errors";
|
|
4
|
+
import { BuildError as BuildErrorClass } from "./errors";
|
|
5
|
+
import { LexiconOutput, isLexiconOutput } from "./lexicon-output";
|
|
6
|
+
import { AttrRef } from "./attrref";
|
|
7
|
+
import { isChildProject, type ChildProjectInstance } from "./child-project";
|
|
8
|
+
import { discover } from "./discovery/index";
|
|
9
|
+
import { topologicalSort } from "./sort";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build manifest describing cross-lexicon outputs and deployment order
|
|
14
|
+
*/
|
|
15
|
+
export interface BuildManifest {
|
|
16
|
+
lexicons: string[];
|
|
17
|
+
outputs: Record<
|
|
18
|
+
string,
|
|
19
|
+
{ source: string; entity: string; attribute: string }
|
|
20
|
+
>;
|
|
21
|
+
deployOrder: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Result of the build process
|
|
26
|
+
*/
|
|
27
|
+
export interface BuildResult {
|
|
28
|
+
/** Map of lexicon name to serialized output (string or multi-file result) */
|
|
29
|
+
outputs: Map<string, string | SerializerResult>;
|
|
30
|
+
/** Map of entity name to Declarable entity */
|
|
31
|
+
entities: Map<string, Declarable>;
|
|
32
|
+
/** Array of warnings encountered during the build */
|
|
33
|
+
warnings: string[];
|
|
34
|
+
/** Array of errors encountered during discovery and build */
|
|
35
|
+
errors: Array<DiscoveryError | BuildError>;
|
|
36
|
+
/** Build manifest with cross-lexicon dependency info */
|
|
37
|
+
manifest: BuildManifest;
|
|
38
|
+
/** Number of source files processed */
|
|
39
|
+
sourceFileCount: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Partitions entities by their lexicon field.
|
|
44
|
+
* Property-kind Declarables are included in the same partition as their parent
|
|
45
|
+
* (they get inlined during serialization).
|
|
46
|
+
*
|
|
47
|
+
* @param entities - Map of entity name to Declarable
|
|
48
|
+
* @returns Map of lexicon name to Map of entity name to Declarable
|
|
49
|
+
*/
|
|
50
|
+
export function partitionByLexicon(
|
|
51
|
+
entities: Map<string, Declarable>
|
|
52
|
+
): Map<string, Map<string, Declarable>> {
|
|
53
|
+
const partitions = new Map<string, Map<string, Declarable>>();
|
|
54
|
+
|
|
55
|
+
for (const [name, entity] of entities) {
|
|
56
|
+
const lexicon = entity.lexicon;
|
|
57
|
+
if (!partitions.has(lexicon)) {
|
|
58
|
+
partitions.set(lexicon, new Map());
|
|
59
|
+
}
|
|
60
|
+
partitions.get(lexicon)!.set(name, entity);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return partitions;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Collect LexiconOutput instances from all entity property trees.
|
|
68
|
+
* Walks entity properties recursively to find LexiconOutput values.
|
|
69
|
+
*/
|
|
70
|
+
export function collectLexiconOutputs(
|
|
71
|
+
entities: Map<string, Declarable>
|
|
72
|
+
): LexiconOutput[] {
|
|
73
|
+
const outputs: LexiconOutput[] = [];
|
|
74
|
+
const visited = new Set<unknown>();
|
|
75
|
+
|
|
76
|
+
function walk(value: unknown): void {
|
|
77
|
+
if (value === null || value === undefined || typeof value !== "object") {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (visited.has(value)) return;
|
|
81
|
+
visited.add(value);
|
|
82
|
+
|
|
83
|
+
if (isLexiconOutput(value)) {
|
|
84
|
+
outputs.push(value);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
for (const item of value) {
|
|
90
|
+
walk(item);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const val of Object.values(value as Record<string, unknown>)) {
|
|
96
|
+
walk(val);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const [name, entity] of entities) {
|
|
101
|
+
if (isLexiconOutput(entity as unknown)) {
|
|
102
|
+
const lexiconOutput = entity as unknown as LexiconOutput;
|
|
103
|
+
lexiconOutput._setSourceEntity(name);
|
|
104
|
+
outputs.push(lexiconOutput);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if ("props" in entity && typeof entity.props === "object" && entity.props !== null) {
|
|
109
|
+
// Set source entity name for any LexiconOutputs found in props
|
|
110
|
+
const prevLength = outputs.length;
|
|
111
|
+
walk(entity.props);
|
|
112
|
+
for (let i = prevLength; i < outputs.length; i++) {
|
|
113
|
+
if (!outputs[i].sourceEntity) {
|
|
114
|
+
outputs[i]._setSourceEntity(name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return outputs;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Detect cross-lexicon AttrRefs by walking each entity's property tree.
|
|
125
|
+
* For each AttrRef whose parent entity belongs to a different lexicon than
|
|
126
|
+
* the consuming entity, auto-create a LexiconOutput.
|
|
127
|
+
*
|
|
128
|
+
* @param entities - Map of entity name to Declarable
|
|
129
|
+
* @returns Array of auto-detected LexiconOutput instances
|
|
130
|
+
*/
|
|
131
|
+
export function detectCrossLexiconRefs(
|
|
132
|
+
entities: Map<string, Declarable>
|
|
133
|
+
): LexiconOutput[] {
|
|
134
|
+
const outputs: LexiconOutput[] = [];
|
|
135
|
+
// Track by "sourceEntityName_attribute" to avoid duplicates
|
|
136
|
+
const seen = new Set<string>();
|
|
137
|
+
|
|
138
|
+
// Build a reverse lookup: object identity -> entity name
|
|
139
|
+
const objectToName = new Map<object, string>();
|
|
140
|
+
for (const [name, entity] of entities) {
|
|
141
|
+
objectToName.set(entity as object, name);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function walk(
|
|
145
|
+
value: unknown,
|
|
146
|
+
consumingLexicon: string,
|
|
147
|
+
visited: Set<unknown>
|
|
148
|
+
): void {
|
|
149
|
+
if (value === null || value === undefined || typeof value !== "object") {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (visited.has(value)) return;
|
|
153
|
+
visited.add(value);
|
|
154
|
+
|
|
155
|
+
if (value instanceof AttrRef) {
|
|
156
|
+
const parent = value.parent.deref();
|
|
157
|
+
if (!parent) return;
|
|
158
|
+
|
|
159
|
+
const parentLexicon = (parent as Record<string, unknown>).lexicon;
|
|
160
|
+
if (typeof parentLexicon !== "string") return;
|
|
161
|
+
|
|
162
|
+
if (parentLexicon !== consumingLexicon) {
|
|
163
|
+
// Find the parent's entity name
|
|
164
|
+
const parentName = objectToName.get(parent);
|
|
165
|
+
if (!parentName) return;
|
|
166
|
+
|
|
167
|
+
const key = `${parentName}_${value.attribute}`;
|
|
168
|
+
if (!seen.has(key)) {
|
|
169
|
+
seen.add(key);
|
|
170
|
+
outputs.push(LexiconOutput.auto(value, parentName));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Skip LexiconOutput instances — these are explicit outputs
|
|
177
|
+
if (isLexiconOutput(value)) return;
|
|
178
|
+
|
|
179
|
+
if (Array.isArray(value)) {
|
|
180
|
+
for (const item of value) {
|
|
181
|
+
walk(item, consumingLexicon, visited);
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const val of Object.values(value as Record<string, unknown>)) {
|
|
187
|
+
walk(val, consumingLexicon, visited);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for (const [, entity] of entities) {
|
|
192
|
+
const visited = new Set<unknown>();
|
|
193
|
+
const consumingLexicon = entity.lexicon;
|
|
194
|
+
|
|
195
|
+
// Walk entity-level properties (AttrRefs could be direct properties)
|
|
196
|
+
for (const val of Object.values(entity as unknown as Record<string, unknown>)) {
|
|
197
|
+
walk(val, consumingLexicon, visited);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Walk props if present
|
|
201
|
+
if (
|
|
202
|
+
"props" in entity &&
|
|
203
|
+
typeof entity.props === "object" &&
|
|
204
|
+
entity.props !== null
|
|
205
|
+
) {
|
|
206
|
+
walk(entity.props, consumingLexicon, visited);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return outputs;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Compute deploy order: source lexicons before consuming lexicons.
|
|
215
|
+
*/
|
|
216
|
+
function computeDeployOrder(
|
|
217
|
+
lexiconNames: string[],
|
|
218
|
+
lexiconOutputs: LexiconOutput[]
|
|
219
|
+
): string[] {
|
|
220
|
+
// Build a dependency graph: consuming lexicons depend on source lexicons
|
|
221
|
+
const deps = new Map<string, Set<string>>();
|
|
222
|
+
for (const name of lexiconNames) {
|
|
223
|
+
deps.set(name, new Set());
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for (const output of lexiconOutputs) {
|
|
227
|
+
// All lexicons other than the source lexicon implicitly depend on it
|
|
228
|
+
for (const name of lexiconNames) {
|
|
229
|
+
if (name !== output.sourceLexicon) {
|
|
230
|
+
deps.get(name)?.add(output.sourceLexicon);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Simple topological sort for deploy order
|
|
236
|
+
const sorted: string[] = [];
|
|
237
|
+
const visited = new Set<string>();
|
|
238
|
+
const visiting = new Set<string>();
|
|
239
|
+
|
|
240
|
+
function visit(name: string): void {
|
|
241
|
+
if (visited.has(name)) return;
|
|
242
|
+
if (visiting.has(name)) return; // cycle, just skip
|
|
243
|
+
visiting.add(name);
|
|
244
|
+
for (const dep of deps.get(name) ?? []) {
|
|
245
|
+
visit(dep);
|
|
246
|
+
}
|
|
247
|
+
visiting.delete(name);
|
|
248
|
+
visited.add(name);
|
|
249
|
+
sorted.push(name);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const name of lexiconNames) {
|
|
253
|
+
visit(name);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return sorted;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Generate the build manifest
|
|
261
|
+
*/
|
|
262
|
+
function generateManifest(
|
|
263
|
+
lexiconNames: string[],
|
|
264
|
+
lexiconOutputs: LexiconOutput[]
|
|
265
|
+
): BuildManifest {
|
|
266
|
+
const outputsRecord: Record<
|
|
267
|
+
string,
|
|
268
|
+
{ source: string; entity: string; attribute: string }
|
|
269
|
+
> = {};
|
|
270
|
+
|
|
271
|
+
for (const output of lexiconOutputs) {
|
|
272
|
+
outputsRecord[output.outputName] = {
|
|
273
|
+
source: output.sourceLexicon,
|
|
274
|
+
entity: output.sourceEntity,
|
|
275
|
+
attribute: output.sourceAttribute,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
lexicons: lexiconNames,
|
|
281
|
+
outputs: outputsRecord,
|
|
282
|
+
deployOrder: computeDeployOrder(lexiconNames, lexiconOutputs),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Builds a lexicon specification by discovering entities, sorting them
|
|
288
|
+
* topologically, and serializing them using the lexicon serializers.
|
|
289
|
+
*
|
|
290
|
+
* @param path - The directory path containing the specification files
|
|
291
|
+
* @param serializers - The serializers to use for serialization
|
|
292
|
+
* @returns BuildResult with outputs, entities, warnings, and errors
|
|
293
|
+
*/
|
|
294
|
+
export async function build(
|
|
295
|
+
path: string,
|
|
296
|
+
serializers: Serializer[],
|
|
297
|
+
parentBuildStack?: Set<string>,
|
|
298
|
+
): Promise<BuildResult> {
|
|
299
|
+
const warnings: string[] = [];
|
|
300
|
+
const errors: Array<DiscoveryError | BuildError> = [];
|
|
301
|
+
|
|
302
|
+
// Step 1: Discover entities and dependencies
|
|
303
|
+
const discoveryResult = await discover(path);
|
|
304
|
+
|
|
305
|
+
// Collect discovery errors
|
|
306
|
+
errors.push(...discoveryResult.errors);
|
|
307
|
+
|
|
308
|
+
// Step 2: Convert Map<string, Set<string>> to Record<string, string[]> for topologicalSort
|
|
309
|
+
const dependenciesRecord: Record<string, string[]> = {};
|
|
310
|
+
for (const [entityName, deps] of discoveryResult.dependencies) {
|
|
311
|
+
dependenciesRecord[entityName] = Array.from(deps);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Step 3: Perform topological sort
|
|
315
|
+
try {
|
|
316
|
+
topologicalSort(dependenciesRecord);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
// BuildError from cycle detection
|
|
319
|
+
if (error instanceof Error && error.name === "BuildError") {
|
|
320
|
+
errors.push(error as BuildError);
|
|
321
|
+
} else {
|
|
322
|
+
// Unexpected error
|
|
323
|
+
errors.push(
|
|
324
|
+
new BuildErrorClass(
|
|
325
|
+
"",
|
|
326
|
+
error instanceof Error ? error.message : String(error)
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Step 4: Recursively build child projects
|
|
333
|
+
const resolvedPath = resolve(path);
|
|
334
|
+
const buildStack = parentBuildStack
|
|
335
|
+
? new Set(parentBuildStack)
|
|
336
|
+
: new Set<string>();
|
|
337
|
+
buildStack.add(resolvedPath);
|
|
338
|
+
|
|
339
|
+
for (const [name, entity] of discoveryResult.entities) {
|
|
340
|
+
if (isChildProject(entity)) {
|
|
341
|
+
const childPath = resolve(entity.projectPath);
|
|
342
|
+
if (buildStack.has(childPath)) {
|
|
343
|
+
errors.push(
|
|
344
|
+
new BuildErrorClass(
|
|
345
|
+
childPath,
|
|
346
|
+
`Circular nested stack: ${[...buildStack].join(" → ")} → ${childPath}`,
|
|
347
|
+
),
|
|
348
|
+
);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
const childResult = await build(childPath, serializers, buildStack);
|
|
352
|
+
entity.buildResult = childResult;
|
|
353
|
+
if (childResult.errors.length > 0) {
|
|
354
|
+
for (const err of childResult.errors) {
|
|
355
|
+
errors.push(err);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Step 5: Partition entities by lexicon
|
|
362
|
+
const partitions = partitionByLexicon(discoveryResult.entities);
|
|
363
|
+
|
|
364
|
+
// Build a serializer lookup by name
|
|
365
|
+
const serializersByName = new Map<string, Serializer>();
|
|
366
|
+
for (const serializer of serializers) {
|
|
367
|
+
serializersByName.set(serializer.name, serializer);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Step 6: Collect explicit LexiconOutputs from all entities
|
|
371
|
+
const explicitOutputs = collectLexiconOutputs(discoveryResult.entities);
|
|
372
|
+
|
|
373
|
+
// Step 6b: Auto-detect cross-lexicon AttrRefs
|
|
374
|
+
const autoOutputs = detectCrossLexiconRefs(discoveryResult.entities);
|
|
375
|
+
|
|
376
|
+
// Merge: explicit outputs take precedence over auto-detected ones.
|
|
377
|
+
// Match by parent object identity + attribute to detect collisions.
|
|
378
|
+
const explicitRefs = explicitOutputs.map((o) => ({
|
|
379
|
+
parent: o._sourceParent.deref(),
|
|
380
|
+
attribute: o.sourceAttribute,
|
|
381
|
+
}));
|
|
382
|
+
const lexiconOutputs = [
|
|
383
|
+
...explicitOutputs,
|
|
384
|
+
...autoOutputs.filter((auto) => {
|
|
385
|
+
const autoParent = auto._sourceParent.deref();
|
|
386
|
+
return !explicitRefs.some(
|
|
387
|
+
(e) => e.parent === autoParent && e.attribute === auto.sourceAttribute
|
|
388
|
+
);
|
|
389
|
+
}),
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
// Group outputs by source lexicon
|
|
393
|
+
const outputsByLexicon = new Map<string, LexiconOutput[]>();
|
|
394
|
+
for (const output of lexiconOutputs) {
|
|
395
|
+
if (!outputsByLexicon.has(output.sourceLexicon)) {
|
|
396
|
+
outputsByLexicon.set(output.sourceLexicon, []);
|
|
397
|
+
}
|
|
398
|
+
outputsByLexicon.get(output.sourceLexicon)!.push(output);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Step 7: Serialize each lexicon's entities
|
|
402
|
+
const outputs = new Map<string, string | SerializerResult>();
|
|
403
|
+
for (const [lexiconName, lexiconEntities] of partitions) {
|
|
404
|
+
const serializer = serializersByName.get(lexiconName);
|
|
405
|
+
if (serializer) {
|
|
406
|
+
const lexiconLexiconOutputs = outputsByLexicon.get(lexiconName) ?? [];
|
|
407
|
+
outputs.set(lexiconName, serializer.serialize(lexiconEntities, lexiconLexiconOutputs));
|
|
408
|
+
} else {
|
|
409
|
+
warnings.push(`No serializer found for lexicon "${lexiconName}"`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Step 8: Generate manifest
|
|
414
|
+
const lexiconNames = Array.from(partitions.keys());
|
|
415
|
+
const manifest = generateManifest(lexiconNames, lexiconOutputs);
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
outputs,
|
|
419
|
+
entities: discoveryResult.entities,
|
|
420
|
+
warnings,
|
|
421
|
+
errors,
|
|
422
|
+
manifest,
|
|
423
|
+
sourceFileCount: discoveryResult.sourceFiles.length,
|
|
424
|
+
};
|
|
425
|
+
}
|