@intentius/chant 0.98.0 → 0.100.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.
Files changed (49) hide show
  1. package/dist/cli/commands/import.d.ts +20 -4
  2. package/dist/cli/commands/import.d.ts.map +1 -1
  3. package/dist/cli/commands/lint.d.ts.map +1 -1
  4. package/dist/deep-observation.d.ts.map +1 -1
  5. package/dist/import/embedded.d.ts +184 -0
  6. package/dist/import/embedded.d.ts.map +1 -0
  7. package/dist/import/generator.d.ts +13 -0
  8. package/dist/import/generator.d.ts.map +1 -1
  9. package/dist/import/parser.d.ts +15 -1
  10. package/dist/import/parser.d.ts.map +1 -1
  11. package/dist/lexicon.d.ts +19 -0
  12. package/dist/lexicon.d.ts.map +1 -1
  13. package/dist/lint/engine.d.ts +6 -1
  14. package/dist/lint/engine.d.ts.map +1 -1
  15. package/dist/lint/rule.d.ts +10 -0
  16. package/dist/lint/rule.d.ts.map +1 -1
  17. package/dist/lint/rules/file-declarable-limit.d.ts.map +1 -1
  18. package/dist/lint/rules/flat-declarations.d.ts.map +1 -1
  19. package/dist/lint/rules/no-unused-declarable.d.ts.map +1 -1
  20. package/dist/lint/rules/property-kind.d.ts +7 -0
  21. package/dist/lint/rules/property-kind.d.ts.map +1 -0
  22. package/dist/workspace/conformance/index.d.ts +9 -0
  23. package/dist/workspace/conformance/index.d.ts.map +1 -1
  24. package/dist/yaml.d.ts +28 -6
  25. package/dist/yaml.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/cli/commands/import-layout.test.ts +142 -0
  28. package/src/cli/commands/import-no-parser.test.ts +1 -1
  29. package/src/cli/commands/import.test.ts +25 -0
  30. package/src/cli/commands/import.ts +158 -36
  31. package/src/cli/commands/lint.ts +21 -8
  32. package/src/deep-observation.test.ts +21 -0
  33. package/src/deep-observation.ts +9 -0
  34. package/src/import/embedded.test.ts +153 -0
  35. package/src/import/embedded.ts +376 -0
  36. package/src/import/generator.ts +14 -0
  37. package/src/import/parser.ts +17 -1
  38. package/src/lexicon.ts +21 -0
  39. package/src/lint/engine.ts +7 -0
  40. package/src/lint/rule.ts +10 -0
  41. package/src/lint/rules/file-declarable-limit.ts +11 -4
  42. package/src/lint/rules/flat-declarations.ts +6 -1
  43. package/src/lint/rules/no-unused-declarable.ts +59 -2
  44. package/src/lint/rules/property-kind.test.ts +99 -0
  45. package/src/lint/rules/property-kind.ts +56 -0
  46. package/src/workspace/conformance/index.mjs +1 -0
  47. package/src/workspace/conformance/index.ts +23 -1
  48. package/src/yaml.test.ts +192 -1
  49. package/src/yaml.ts +404 -250
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/workspace/conformance/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/workspace/conformance/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAYH,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,gGAAiG,CAAC;AACrI,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAQrE,CAAC;AAEF,0GAA0G;AAC1G,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,mBAAmB,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAQhG,CAAC;AAEF,iGAAiG;AACjG,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,EAAE,CAQjE,CAAC;AAEF,uEAAuE;AACvE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,yFAAyF;AACzF,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,IAAI,CAAC,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAChF;AAED,8DAA8D;AAC9D,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,eAAe,CAAC;AAEhF,MAAM,WAAW,iCAAiC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC1C;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,gCAAiC,SAAQ,iCAAiC;IACzF,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,sBAAsB,CAAC;CAChC;AAED,2BAA2B;AAC3B,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,mBAAmB,CAAC;IAC7B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,sGAAsG;AACtG,MAAM,WAAW,gCAAgC;IAC/C,qGAAqG;IACrG,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,4CAA4C;IAC5C,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,wEAAwE;IACxE,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,6FAA6F;IAC7F,YAAY,EAAE,MAAM,CAAC;CACtB;AAMD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAA4B,CAAC;AAYjE,iEAAiE;AACjE,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAsB,GAAG,MAAM,EAAE,CAazE;AAED,0FAA0F;AAC1F,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,SAAS,CAG1D;AAED,KAAK,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAGjE,qGAAqG;AACrG,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG;IAAE,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAUhH;AAED,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAY9D;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAMnG;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,CAc5G;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAkB5I;AAED,uHAAuH;AACvH,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,EAAE,IAAI,GAAE,KAAK,GAAG,KAAa,GAAG;IAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAAC,OAAO,EAAE,mBAAmB,EAAE,CAAA;CAAE,CASzK;AAID,4FAA4F;AAC5F,eAAO,MAAM,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAOvE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG,SAAS,CA0CrH;AAED,sGAAsG;AACtG,MAAM,WAAW,UAAU;IACzB,8FAA8F;IAC9F,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EAAE,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACpF,OAAO,CAAC,UAAU,CAAC,CA4DrB;AAYD,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,oBAAoB,CA0B9H;AAED,qGAAqG;AACrG,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,iCAAsC,GAAG;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,IAAI,IAAI,CAAA;CAAE,CAMpJ;AAsBD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAEzE;AAYD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,SAAS,SAAU,EAAE,IAAI,GAAE,KAAK,GAAG,KAAa;;;;;;IA+C/I,kDAAkD;;EAMrD;AAED,iHAAiH;AACjH,wBAAsB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAWpK;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,6BAA6B,CAAC,MAAM,EAAE,sBAAsB,EAAE,OAAO,GAAE,iCAAsC,GAAG,OAAO,CAAC,gCAAgC,CAAC,CAiB9K"}
package/dist/yaml.d.ts CHANGED
@@ -2,9 +2,11 @@
2
2
  * Lightweight YAML emitter and parser.
3
3
  *
4
4
  * Covers the subset of YAML used by Chant lexicons (scalars, block arrays,
5
- * nested objects, tagged values). Not a full YAML implementation — use a
6
- * dedicated library if you need anchors, multi-document streams, or
7
- * block scalars.
5
+ * nested objects, block scalars, tagged values, anchors, aliases and merge
6
+ * keys). Not a full YAML implementation: flow collections are read only when
7
+ * they are JSON. `splitYAMLDocuments` splits a multi-document stream. The
8
+ * parser throws a `YAMLParseError` on a line it cannot place instead of
9
+ * dropping it (#2991).
8
10
  */
9
11
  export declare function emitYAML(value: unknown, indent: number): string;
10
12
  /** Result of parsing a YAML block. */
@@ -12,11 +14,23 @@ export interface ParseResult {
12
14
  value: unknown;
13
15
  endIndex: number;
14
16
  }
17
+ /**
18
+ * Input the parser cannot place (#2991). The parser used to skip such a line,
19
+ * or read it as a key of whatever mapping was open at the top level, so a
20
+ * mis-indented or unsupported construct came back as a quietly different
21
+ * document. Now it is an error naming the line.
22
+ */
23
+ export declare class YAMLParseError extends Error {
24
+ /** 1-based line number within the text handed to the parser. */
25
+ readonly line: number;
26
+ constructor(line: number, message: string);
27
+ }
15
28
  /**
16
29
  * Parse a YAML document (or JSON document) into a plain object.
17
30
  *
18
31
  * Tries `JSON.parse` first; falls back to a line-based YAML parser that
19
- * handles the subset of YAML commonly found in CI configuration files.
32
+ * handles the subset of YAML commonly found in CI configuration files. Throws
33
+ * {@link YAMLParseError} on a line it cannot place rather than dropping it.
20
34
  */
21
35
  export declare function parseYAML(content: string): Record<string, unknown>;
22
36
  /**
@@ -36,11 +50,19 @@ export declare function parseYAMLDocument(content: string): unknown;
36
50
  */
37
51
  export declare function splitYAMLDocuments(content: string): string[];
38
52
  /**
39
- * Parse indentation-based YAML lines into a key-value object.
53
+ * Parse indentation-based YAML lines into a key-value object: the mapping
54
+ * whose keys sit at column `baseIndent`, starting at `startIndex`.
55
+ *
56
+ * It ends at the first line that is not one of its keys: a line at another
57
+ * column, a sequence entry, or text that is not `key:`. That line goes back
58
+ * to the caller in `endIndex`; if no caller owns it, `parseYAML` reports it
59
+ * (#2991). This function never skips a content line.
40
60
  */
41
61
  export declare function parseYAMLLines(lines: string[], startIndex: number, baseIndent: number): ParseResult;
42
62
  /**
43
- * Parse a block array (lines starting with `- `).
63
+ * Parse a block array: the `- ` entries at column `baseIndent`, starting at
64
+ * `startIndex`. Like {@link parseYAMLLines} it ends at the first line that is
65
+ * not one of its entries and hands that line back in `endIndex`.
44
66
  */
45
67
  export declare function parseYAMLArray(lines: string[], startIndex: number, baseIndent: number): ParseResult;
46
68
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA0BH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgH/D;AAMD,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAuBD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASlE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAa1D;AAKD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAc5D;AAiFD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,WAAW,CA8Eb;AA0GD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,WAAW,CAqGb;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAwBlD"}
1
+ {"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA0BH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgH/D;AAMD,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAmJD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAalE;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAW1D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAc5D;AAiFD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,WAAW,CAqBb;AA+MD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,WAAW,CA4Db;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAwBlD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.98.0",
3
+ "version": "0.100.0",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -0,0 +1,142 @@
1
+ import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { mkdir, rm, writeFile, readFile } from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import type { LexiconPlugin } from "../../lexicon";
7
+ import type { GeneratedFile, TypeScriptGenerator } from "../../import/generator";
8
+ import type { TemplateIR } from "../../import/parser";
9
+ import { generateOrganizedFiles, importCommand } from "./import";
10
+
11
+ // A generator that writes one module per resource plus a barrel, the shape
12
+ // the otel, prometheus and grafana importers want (#2964).
13
+ function perResourceFiles(ir: TemplateIR): GeneratedFile[] {
14
+ return [
15
+ ...ir.resources.map((r) => ({
16
+ path: `${r.logicalId}.ts`,
17
+ content: `export const ${r.logicalId} = ${JSON.stringify(r.properties)};\n`,
18
+ })),
19
+ { path: "index.ts", content: ir.resources.map((r) => `export * from "./${r.logicalId}";`).join("\n") + "\n" },
20
+ ];
21
+ }
22
+
23
+ const placing: TypeScriptGenerator = { ownsLayout: true, generate: perResourceFiles };
24
+ const defaultLayout: TypeScriptGenerator = { generate: perResourceFiles };
25
+
26
+ // Five resources: over core's three-resource threshold, across two categories.
27
+ const fiveIR: TemplateIR = {
28
+ resources: [
29
+ { logicalId: "receiver", type: "Fake::Receiver", properties: { port: 4317 } },
30
+ { logicalId: "processor", type: "Fake::Processor", properties: {} },
31
+ { logicalId: "exporter", type: "Fake::Exporter", properties: {} },
32
+ { logicalId: "pipeline", type: "Fake::Pipeline", properties: {} },
33
+ { logicalId: "bucket", type: "Fake::Bucket", properties: {} },
34
+ ],
35
+ parameters: [],
36
+ };
37
+
38
+ describe("generateOrganizedFiles (#2964)", () => {
39
+ test("a generator with ownsLayout gets one call and its files are written as returned", () => {
40
+ const generate = vi.fn(perResourceFiles);
41
+ const { files, warnings } = generateOrganizedFiles(fiveIR, { ownsLayout: true, generate });
42
+ expect(generate).toHaveBeenCalledTimes(1);
43
+ expect(generate).toHaveBeenCalledWith(fiveIR);
44
+ expect(files).toEqual(perResourceFiles(fiveIR));
45
+ expect(warnings).toEqual([]);
46
+ });
47
+
48
+ test("ownsLayout writes nothing extra when the generator returns nothing", () => {
49
+ const { files } = generateOrganizedFiles(fiveIR, { ownsLayout: true, generate: () => [] });
50
+ expect(files).toEqual([]);
51
+ });
52
+
53
+ test("the default layout splits by category and names every file it drops", () => {
54
+ const { files, warnings } = generateOrganizedFiles(fiveIR, defaultLayout);
55
+ expect(files.map((f) => f.path)).toEqual(["other.ts", "storage.ts", "index.ts"]);
56
+ expect(warnings).toEqual([
57
+ "The generator returned 5 files for other.ts; only the first was kept, and processor.ts, exporter.ts, " +
58
+ "pipeline.ts, index.ts were not written. A generator that places its own files sets ownsLayout (#2964).",
59
+ "The generator returned 2 files for storage.ts; only the first was kept, and index.ts was not written. " +
60
+ "A generator that places its own files sets ownsLayout (#2964).",
61
+ ]);
62
+ });
63
+
64
+ test("the default layout skips a category the generator returns nothing for, with a warning", () => {
65
+ const { files, warnings } = generateOrganizedFiles(fiveIR, { generate: () => [] });
66
+ expect(files).toEqual([]);
67
+ expect(warnings).toEqual([
68
+ "The generator returned no file for other.ts; it was not written.",
69
+ "The generator returned no file for storage.ts; it was not written.",
70
+ ]);
71
+ });
72
+
73
+ test("the default layout stays silent for a generator that returns one file per call", () => {
74
+ const oneFile: TypeScriptGenerator = { generate: (ir) => [perResourceFiles(ir)[0]] };
75
+ const { files, warnings } = generateOrganizedFiles(fiveIR, oneFile);
76
+ expect(files.map((f) => f.path)).toEqual(["other.ts", "storage.ts", "index.ts"]);
77
+ expect(warnings).toEqual([]);
78
+ });
79
+
80
+ test("up to three resources are one call either way", () => {
81
+ const small: TemplateIR = { resources: fiveIR.resources.slice(0, 3), parameters: [] };
82
+ expect(generateOrganizedFiles(small, defaultLayout)).toEqual({ files: perResourceFiles(small), warnings: [] });
83
+ });
84
+ });
85
+
86
+ // Through the CLI: a fake lexicon whose parser yields fiveIR.
87
+ const { fakes } = vi.hoisted(() => ({ fakes: {} as Record<string, LexiconPlugin> }));
88
+
89
+ vi.mock("../plugins", () => ({
90
+ resolveProjectLexicons: async () => Object.keys(fakes),
91
+ loadPlugins: async (names: string[]) => names.map((n) => fakes[n]),
92
+ loadPlugin: async (n: string) => fakes[n],
93
+ listInstalledLexicons: () => [],
94
+ }));
95
+
96
+ function fakeLexicon(name: string, generator: TypeScriptGenerator): LexiconPlugin {
97
+ return {
98
+ name,
99
+ detectTemplate: (data: unknown) => typeof data === "object" && data !== null && "fakeMarker" in data,
100
+ templateParser: () => ({ parse: () => fiveIR }),
101
+ templateGenerator: () => generator,
102
+ } as unknown as LexiconPlugin;
103
+ }
104
+
105
+ describe("chant import writes a placing generator's files (#2964)", () => {
106
+ let testDir: string;
107
+ let templatePath: string;
108
+ let outputDir: string;
109
+
110
+ beforeEach(async () => {
111
+ testDir = join(tmpdir(), `chant-import-layout-${Date.now()}-${Math.random()}`);
112
+ await mkdir(testDir, { recursive: true });
113
+ templatePath = join(testDir, "config.json");
114
+ outputDir = join(testDir, "out");
115
+ await writeFile(templatePath, JSON.stringify({ fakeMarker: true }));
116
+ fakes.placing = fakeLexicon("placing", placing);
117
+ fakes.splitting = fakeLexicon("splitting", defaultLayout);
118
+ });
119
+
120
+ afterEach(async () => {
121
+ await rm(testDir, { recursive: true, force: true });
122
+ for (const k of Object.keys(fakes)) delete fakes[k];
123
+ });
124
+
125
+ test("ownsLayout: every file the generator returns is written", async () => {
126
+ const result = await importCommand({ templatePath, output: outputDir, lexicon: "placing" });
127
+ expect(result.success).toBe(true);
128
+ expect(result.warnings).toEqual([]);
129
+ expect(result.generatedFiles).toEqual(perResourceFiles(fiveIR).map((f) => f.path));
130
+ expect(await readFile(join(outputDir, "pipeline.ts"), "utf-8")).toBe("export const pipeline = {};\n");
131
+ expect(existsSync(join(outputDir, "other.ts"))).toBe(false);
132
+ });
133
+
134
+ test("default layout: the dropped files are named in the import warnings", async () => {
135
+ const result = await importCommand({ templatePath, output: outputDir, lexicon: "splitting" });
136
+ expect(result.success).toBe(true);
137
+ expect(result.generatedFiles).toEqual(["other.ts", "storage.ts", "index.ts"]);
138
+ expect(result.warnings.some((w) => w.includes("processor.ts, exporter.ts, pipeline.ts, index.ts were not written"))).toBe(
139
+ true,
140
+ );
141
+ });
142
+ });
@@ -7,7 +7,7 @@ import type { LexiconPlugin } from "../../lexicon";
7
7
  import { importCommand, importFromContent } from "./import";
8
8
 
9
9
  // A lexicon that recognizes a template but has no templateParser, as grafana
10
- // does today (#2940). Import must say it cannot import, not throw
10
+ // did until #2945 (#2940). Import must say it cannot import, not throw
11
11
  // "plugin.templateParser is not a function".
12
12
  const { detectOnly } = vi.hoisted(() => ({
13
13
  detectOnly: {
@@ -503,6 +503,31 @@ spec:
503
503
  expect(result.error).toContain("no-such-lexicon");
504
504
  });
505
505
 
506
+ // #2991 — a blank or whitespace-only line after `spec:` (Helm renders one
507
+ // for an empty conditional) no longer ends the pod template's spec.
508
+ test("a blank line after a key keeps its block nested", async () => {
509
+ const templatePath = join(testDir, "manifest.yaml");
510
+ await writeFile(templatePath, deployment.replace(" spec:\n", " spec:\n \n"));
511
+
512
+ const result = await importCommand({ templatePath, output: outputDir, lexicon: "k8s" });
513
+
514
+ expect(result.error).toBeUndefined();
515
+ // The containers stay under template.spec, not hoisted beside `replicas`.
516
+ expect(generated(result.generatedFiles)).toMatch(/template: \{[\s\S]*spec: \{\s*containers: \[/);
517
+ }, 30000);
518
+
519
+ // #2991 — a document the YAML reader cannot place fails the import with
520
+ // its line, where it used to import a different manifest.
521
+ test("a mis-indented document fails the import and names the line", async () => {
522
+ const templatePath = join(testDir, "manifests.yaml");
523
+ await writeFile(templatePath, `${configMap}---\n${deployment.replace(" replicas: 2\n", " replicas: 2\n paused: true\n")}`);
524
+
525
+ const result = await importCommand({ templatePath, output: outputDir });
526
+
527
+ expect(result.success).toBe(false);
528
+ expect(result.error).toContain("YAML line 7");
529
+ }, 30000);
530
+
506
531
  test("content that is neither JSON nor YAML names both formats", async () => {
507
532
  const templatePath = join(testDir, "notes.txt");
508
533
  await writeFile(templatePath, "this is not a template\n{ nor is this");
@@ -6,6 +6,8 @@ import type { GeneratedFile, TypeScriptGenerator } from "../../import/generator"
6
6
  import { listInstalledLexicons, loadPlugin, loadPlugins, resolveProjectLexicons } from "../plugins";
7
7
  import type { LexiconPlugin, ResourceSelector } from "../../lexicon";
8
8
  import { parseYAMLDocument, splitYAMLDocuments } from "../../yaml";
9
+ import { importLexiconPackage } from "../../lexicon-module";
10
+ import { EmbeddedImports, type EmbeddedContent, type RegisteredEmbeddedImporter } from "../../import/embedded";
9
11
 
10
12
  /**
11
13
  * Import command options
@@ -57,9 +59,10 @@ type ResourceCategory = "storage" | "compute" | "network" | "other";
57
59
  * detection sees the same per-document objects the plugin's parser will. A
58
60
  * document whose top level is a list parses as that list (#2965). JSON yields
59
61
  * one document, the parsed value, exactly as before. Returns undefined when
60
- * the content is neither: core's YAML reader is lenient and turns unparseable
61
- * text into an empty mapping, so a file with no non-empty document is
62
- * rejected.
62
+ * the content is neither. A document core's YAML reader cannot parse is
63
+ * skipped here, since detection only needs one it can read; the plugin's
64
+ * parser meets the same document and reports it (#2991). A file with no
65
+ * non-empty document is rejected.
63
66
  */
64
67
  export function parseTemplateDocuments(content: string): unknown[] | undefined {
65
68
  try {
@@ -204,22 +207,56 @@ function organizeByCategory(ir: TemplateIR): Map<ResourceCategory, ResourceIR[]>
204
207
  return categories;
205
208
  }
206
209
 
210
+ /** The files an import writes, and anything core could not keep. */
211
+ export interface OrganizedFiles {
212
+ files: GeneratedFile[];
213
+ warnings: string[];
214
+ }
215
+
216
+ /**
217
+ * The first file of one per-category `generate()` call, which core writes as
218
+ * `<name>.ts`. Any further files that call returned are named in a warning
219
+ * rather than dropped silently; a call that returned nothing is skipped with
220
+ * a warning rather than failing on `generated[0]`.
221
+ */
222
+ function firstFileOf(generated: GeneratedFile[], fileName: string, warnings: string[]): string | undefined {
223
+ if (generated.length === 0) {
224
+ warnings.push(`The generator returned no file for ${fileName}; it was not written.`);
225
+ return undefined;
226
+ }
227
+ if (generated.length > 1) {
228
+ const dropped = generated.slice(1).map((f) => f.path).join(", ");
229
+ warnings.push(
230
+ `The generator returned ${generated.length} files for ${fileName}; only the first was kept, ` +
231
+ `and ${dropped} ${generated.length === 2 ? "was" : "were"} not written. ` +
232
+ "A generator that places its own files sets ownsLayout (#2964).",
233
+ );
234
+ }
235
+ return generated[0].content;
236
+ }
237
+
207
238
  /**
208
- * Generate organized files with separate modules
239
+ * Decide the files an import writes. A generator with `ownsLayout` is called
240
+ * once with the whole IR and its files are written exactly as returned
241
+ * (#2964). Otherwise an IR of up to three resources is generated in one call,
242
+ * and a larger one is split into one file per resource category plus an
243
+ * `index.ts` barrel.
209
244
  */
210
- function generateOrganizedFiles(
245
+ export function generateOrganizedFiles(
211
246
  ir: TemplateIR,
212
247
  generator: TypeScriptGenerator,
213
- ): GeneratedFile[] {
248
+ ): OrganizedFiles {
249
+ const warnings: string[] = [];
250
+
251
+ // The generator places its own files, or everything fits in one call.
252
+ if (generator.ownsLayout === true || ir.resources.length <= 3) {
253
+ return { files: generator.generate(ir), warnings };
254
+ }
255
+
214
256
  const files: GeneratedFile[] = [];
215
257
  const categories = organizeByCategory(ir);
216
258
  const exports: string[] = [];
217
259
 
218
- // If all resources fit in one file, just generate main.ts
219
- if (ir.resources.length <= 3) {
220
- return generator.generate(ir);
221
- }
222
-
223
260
  // Generate files for each category
224
261
  for (const [category, resources] of categories) {
225
262
  if (resources.length === 0) continue;
@@ -229,13 +266,10 @@ function generateOrganizedFiles(
229
266
  resources,
230
267
  };
231
268
 
232
- const generated = generator.generate(categoryIr);
233
269
  const fileName = `${category}.ts`;
234
-
235
- files.push({
236
- path: fileName,
237
- content: generated[0].content,
238
- });
270
+ const content = firstFileOf(generator.generate(categoryIr), fileName, warnings);
271
+ if (content === undefined) continue;
272
+ files.push({ path: fileName, content });
239
273
 
240
274
  // Track exports
241
275
  for (const resource of resources) {
@@ -250,15 +284,13 @@ function generateOrganizedFiles(
250
284
  parameters: ir.parameters,
251
285
  resources: [],
252
286
  };
253
- const generated = generator.generate(paramsIr);
254
- files.push({
255
- path: "parameters.ts",
256
- content: generated[0].content,
257
- });
258
-
259
- for (const param of ir.parameters) {
260
- const varName = param.name.charAt(0).toLowerCase() + param.name.slice(1);
261
- exports.push(`export { ${varName} } from "./parameters";`);
287
+ const content = firstFileOf(generator.generate(paramsIr), "parameters.ts", warnings);
288
+ if (content !== undefined) {
289
+ files.push({ path: "parameters.ts", content });
290
+ for (const param of ir.parameters) {
291
+ const varName = param.name.charAt(0).toLowerCase() + param.name.slice(1);
292
+ exports.push(`export { ${varName} } from "./parameters";`);
293
+ }
262
294
  }
263
295
  }
264
296
 
@@ -270,7 +302,7 @@ function generateOrganizedFiles(
270
302
  });
271
303
  }
272
304
 
273
- return files;
305
+ return { files, warnings };
274
306
  }
275
307
 
276
308
  /**
@@ -351,7 +383,7 @@ export async function importCommand(options: ImportOptions): Promise<ImportResul
351
383
  );
352
384
  }
353
385
 
354
- const result = parseAndWrite(plugin, content, outputDir, options.force, warnings, generatedFiles, plugin.name);
386
+ const result = await parseAndWrite(plugin, content, outputDir, options.force, warnings, generatedFiles, plugin.name, projectDir);
355
387
  return { ...result, detected: true };
356
388
  }
357
389
 
@@ -389,11 +421,93 @@ export async function importFromContent(options: ContentImportOptions): Promise<
389
421
  if (!plugin) {
390
422
  return { success: false, generatedFiles: [], warnings: [], error: `Lexicon "${options.lexicon}" not available.` };
391
423
  }
392
- return parseAndWrite(plugin, options.content, outputDir, options.force, [], [], plugin.name);
424
+ const projectDir = resolve(options.output ? dirname(options.output) : ".");
425
+ return parseAndWrite(plugin, options.content, outputDir, options.force, [], [], plugin.name, projectDir);
426
+ }
427
+
428
+ /**
429
+ * The importers for the content a host's parser offered (#2962): those of the
430
+ * project's lexicons, and of each installed lexicon whose `detectTemplate`
431
+ * recognizes one of the offered documents. The recognizing is done with the
432
+ * lexicon's light `/detect` module, so a lexicon is loaded in full only when
433
+ * it may own something; one that fails to load is skipped.
434
+ */
435
+ async function embeddedImportersFor(
436
+ offered: readonly EmbeddedContent[],
437
+ host: LexiconPlugin,
438
+ projectDir: string,
439
+ ): Promise<RegisteredEmbeddedImporter[]> {
440
+ const documents = offered.map((c) => c.document).filter((d) => typeof d === "object" && d !== null);
441
+ if (documents.length === 0) return [];
442
+
443
+ let projectNames: string[] = [];
444
+ try {
445
+ projectNames = await resolveProjectLexicons(projectDir);
446
+ } catch {
447
+ projectNames = [];
448
+ }
449
+ const candidates: string[] = [...projectNames];
450
+ for (const name of listInstalledLexicons(projectDir)) {
451
+ if (candidates.includes(name)) continue;
452
+ try {
453
+ const detect = await importLexiconPackage(`@intentius/chant-lexicon-${name}/detect`, projectDir);
454
+ const detectTemplate = detect.detectTemplate;
455
+ if (typeof detectTemplate !== "function") continue;
456
+ if (documents.some((d) => {
457
+ try {
458
+ return detectTemplate(d) === true;
459
+ } catch {
460
+ return false;
461
+ }
462
+ })) {
463
+ candidates.push(name);
464
+ }
465
+ } catch {
466
+ // No /detect module, or it failed to load: not a candidate.
467
+ }
468
+ }
469
+
470
+ const registered: RegisteredEmbeddedImporter[] = [];
471
+ for (const name of candidates) {
472
+ let plugin: LexiconPlugin;
473
+ try {
474
+ plugin = name === host.name ? host : await loadPlugin(name);
475
+ } catch {
476
+ continue;
477
+ }
478
+ let importers;
479
+ try {
480
+ importers = plugin.embeddedImporters?.() ?? [];
481
+ } catch {
482
+ continue;
483
+ }
484
+ for (const importer of importers) registered.push({ lexicon: plugin.name, importer });
485
+ }
486
+ return registered;
487
+ }
488
+
489
+ /**
490
+ * Parse, resolving embedded content (#2962). A first parse with a probe
491
+ * collects what the parser offers; when it offers anything, the owners'
492
+ * importers are loaded and the content is parsed again with them. A parser
493
+ * that offers nothing is parsed once, and nothing is loaded.
494
+ */
495
+ async function parseWithEmbedded(
496
+ plugin: LexiconPlugin,
497
+ parser: TemplateParser,
498
+ content: string,
499
+ projectDir: string,
500
+ ): Promise<{ ir: TemplateIR; embedded?: EmbeddedImports }> {
501
+ const probe = new EmbeddedImports([], { quiet: true });
502
+ const ir = parser.parse(content, { embedded: probe });
503
+ if (probe.offered.length === 0) return { ir };
504
+ const importers = await embeddedImportersFor(probe.offered, plugin, projectDir);
505
+ const embedded = new EmbeddedImports(importers);
506
+ return { ir: parser.parse(content, { embedded }), embedded };
393
507
  }
394
508
 
395
509
  /** The shared tail of every template import: parse → generate → write. */
396
- function parseAndWrite(
510
+ async function parseAndWrite(
397
511
  plugin: LexiconPlugin,
398
512
  content: string,
399
513
  outputDir: string,
@@ -401,9 +515,10 @@ function parseAndWrite(
401
515
  warnings: string[],
402
516
  generatedFiles: string[],
403
517
  lexicon: string,
404
- ): ImportResult {
518
+ projectDir: string,
519
+ ): Promise<ImportResult> {
405
520
  // A lexicon can recognize a template (detectTemplate) without being able to
406
- // import it (grafana has no parser). Every path funnels through here, so
521
+ // import it (grafana had no parser until #2945). Every path funnels through here, so
407
522
  // this one check covers detection, --lexicon and content import (#2940).
408
523
  if (!plugin.templateParser || !plugin.templateGenerator) {
409
524
  return {
@@ -417,9 +532,10 @@ function parseAndWrite(
417
532
 
418
533
  // Parse template
419
534
  let ir: TemplateIR;
535
+ let embedded: EmbeddedImports | undefined;
420
536
  try {
421
537
  const parser = plugin.templateParser();
422
- ir = parser.parse(content);
538
+ ({ ir, embedded } = await parseWithEmbedded(plugin, parser, content, projectDir));
423
539
  } catch (err) {
424
540
  return {
425
541
  success: false,
@@ -434,6 +550,8 @@ function parseAndWrite(
434
550
  if (ir.warnings) {
435
551
  warnings.push(...ir.warnings);
436
552
  }
553
+ // Embedded content the owner could not carry, or that no installed lexicon imports.
554
+ if (embedded) warnings.push(...embedded.warnings);
437
555
 
438
556
  const generator = plugin.templateGenerator();
439
557
 
@@ -451,7 +569,10 @@ function parseAndWrite(
451
569
  }
452
570
 
453
571
  // Generate files
454
- const files = generateOrganizedFiles(ir, generator);
572
+ const { files: hostFiles, warnings: layoutWarnings } = generateOrganizedFiles(ir, generator);
573
+ warnings.push(...layoutWarnings);
574
+ // The owners' modules for embedded content, each in a directory of its own.
575
+ const files = [...hostFiles, ...(embedded?.files ?? [])];
455
576
 
456
577
  // Write files
457
578
  for (const file of files) {
@@ -525,7 +646,7 @@ function mergeIR(parts: TemplateIR[]): TemplateIR {
525
646
  * for full-fidelity IR, then generate chant TypeScript from it.
526
647
  *
527
648
  * Unlike file import, the live config may contain secrets — the caller prints a
528
- * warning. Reuses the same by-category output organization as file import.
649
+ * warning. Uses the same file layout as file import (`generateOrganizedFiles`).
529
650
  */
530
651
  export async function importFromLive(options: LiveImportOptions): Promise<ImportResult> {
531
652
  const projectDir = resolve(options.output ? dirname(options.output) : ".");
@@ -640,7 +761,8 @@ export async function liveImportFromPlugins(
640
761
  mkdirSync(outputDir, { recursive: true });
641
762
  }
642
763
 
643
- const files = generateOrganizedFiles(ir, generator);
764
+ const { files, warnings: layoutWarnings } = generateOrganizedFiles(ir, generator);
765
+ warnings.push(...layoutWarnings);
644
766
  const generatedFiles: string[] = [];
645
767
  for (const file of files) {
646
768
  const filePath = join(outputDir, file.path);