@intentius/chant-lexicon-aws 0.124.43 → 0.124.44

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 (57) hide show
  1. package/dist/codegen/generate-lexicon.d.ts +2 -6
  2. package/dist/codegen/generate-lexicon.d.ts.map +1 -1
  3. package/dist/deep-observe.d.ts +6 -2
  4. package/dist/deep-observe.d.ts.map +1 -1
  5. package/dist/deep-observe.js +17 -7
  6. package/dist/import/live-export.d.ts.map +1 -1
  7. package/dist/import/live-export.js +8 -4
  8. package/dist/integrity.json +5 -4
  9. package/dist/lint/audit-catalog.d.ts.map +1 -1
  10. package/dist/lint/audit-catalog.js +1 -0
  11. package/dist/lint/audit-lineage.d.ts.map +1 -1
  12. package/dist/lint/audit-lineage.js +4 -0
  13. package/dist/lint/post-synth/index.d.ts.map +1 -1
  14. package/dist/lint/post-synth/index.js +2 -0
  15. package/dist/lint/post-synth/waw017.d.ts.map +1 -1
  16. package/dist/lint/post-synth/waw017.js +3 -2
  17. package/dist/lint/post-synth/waw071.d.ts +19 -0
  18. package/dist/lint/post-synth/waw071.d.ts.map +1 -0
  19. package/dist/lint/post-synth/waw071.js +76 -0
  20. package/dist/manifest.json +1 -1
  21. package/dist/meta.json +3618 -1206
  22. package/dist/okf/index.md +1 -0
  23. package/dist/okf/rules/WAW071.md +19 -0
  24. package/dist/okf/types/Key.md +1 -0
  25. package/dist/okf/types/Map.md +1 -0
  26. package/dist/okf/types/SsmParameter.md +4 -0
  27. package/dist/okf/types/Type.md +1 -0
  28. package/dist/okf/types/UserPool.md +1 -0
  29. package/dist/rules/waw017.ts +3 -2
  30. package/dist/rules/waw071.ts +85 -0
  31. package/dist/serializer.d.ts +12 -0
  32. package/dist/serializer.d.ts.map +1 -1
  33. package/dist/serializer.js +44 -10
  34. package/dist/spec/parse.d.ts +27 -5
  35. package/dist/spec/parse.d.ts.map +1 -1
  36. package/dist/spec/parse.js +56 -0
  37. package/dist/taggable.d.ts +28 -0
  38. package/dist/taggable.d.ts.map +1 -1
  39. package/dist/taggable.js +60 -9
  40. package/package.json +2 -2
  41. package/src/codegen/generate-lexicon.ts +2 -2
  42. package/src/deep-observe.test.ts +19 -0
  43. package/src/deep-observe.ts +17 -6
  44. package/src/generated/lexicon-aws.json +3618 -1206
  45. package/src/import/live-export.test.ts +18 -0
  46. package/src/import/live-export.ts +8 -4
  47. package/src/lint/audit-catalog.ts +1 -0
  48. package/src/lint/audit-lineage.ts +4 -0
  49. package/src/lint/post-synth/index.ts +2 -0
  50. package/src/lint/post-synth/waw017.ts +3 -2
  51. package/src/lint/post-synth/waw071.test.ts +50 -0
  52. package/src/lint/post-synth/waw071.ts +85 -0
  53. package/src/serializer-tag-shapes.test.ts +83 -0
  54. package/src/serializer.ts +46 -9
  55. package/src/spec/parse.test.ts +39 -0
  56. package/src/spec/parse.ts +83 -1
  57. package/src/taggable.ts +78 -11
package/src/taggable.ts CHANGED
@@ -8,37 +8,104 @@ import { readFileSync } from "fs";
8
8
  import { join } from "path";
9
9
  import { moduleSourceDir } from "@intentius/chant/module-source";
10
10
 
11
+ /** How a type's tags are written: a `[{Key, Value}]` list or a key-to-value map. */
12
+ export type TagShape = "list" | "map";
13
+
14
+ /** The property a type's tags live in, and its shape (#4350). */
15
+ export interface TagPlacement {
16
+ property: string;
17
+ shape: TagShape;
18
+ }
19
+
11
20
  interface LexiconEntry {
12
21
  kind: string;
13
22
  resourceType: string;
14
- tagging?: { taggable: boolean; tagOnCreate: boolean; tagUpdatable: boolean };
23
+ tagging?: {
24
+ taggable: boolean;
25
+ tagOnCreate: boolean;
26
+ tagUpdatable: boolean;
27
+ tagProperty?: string;
28
+ tagShape?: TagShape;
29
+ };
15
30
  [key: string]: unknown;
16
31
  }
17
32
 
18
- let _cached: Set<string> | undefined;
33
+ let _cached: Map<string, TagPlacement | null> | undefined;
19
34
 
20
35
  /**
21
- * Load taggable resource types from the lexicon JSON.
22
- * Result is cached after first call.
36
+ * Every taggable type, mapped to where its tags go. `null` means the schema
37
+ * puts the tags somewhere chant can't stamp (inside an array, or under a
38
+ * wrapper object), so the type gets no stamped tags.
23
39
  */
24
- export function loadTaggableResources(): Set<string> {
40
+ export function loadTagPlacements(): Map<string, TagPlacement | null> {
25
41
  if (_cached) return _cached;
26
42
 
27
- const set = new Set<string>();
43
+ const map = new Map<string, TagPlacement | null>();
28
44
  try {
29
45
  const lexiconPath = join(moduleSourceDir(import.meta.url), "generated", "lexicon-aws.json");
30
46
  const content = readFileSync(lexiconPath, "utf-8");
31
47
  const data = JSON.parse(content) as Record<string, LexiconEntry>;
32
48
 
33
49
  for (const [_name, entry] of Object.entries(data)) {
34
- if (entry.kind === "resource" && entry.resourceType && entry.tagging?.taggable) {
35
- set.add(entry.resourceType);
36
- }
50
+ if (entry.kind !== "resource" || !entry.resourceType || !entry.tagging?.taggable) continue;
51
+ const { tagProperty, tagShape } = entry.tagging;
52
+ map.set(entry.resourceType, tagProperty && tagShape ? { property: tagProperty, shape: tagShape } : null);
37
53
  }
38
54
  } catch {
39
55
  // Lexicon not available — skip
40
56
  }
41
57
 
42
- _cached = set;
43
- return set;
58
+ _cached = map;
59
+ return map;
60
+ }
61
+
62
+ /**
63
+ * Load taggable resource types from the lexicon JSON.
64
+ * Result is cached after first call.
65
+ */
66
+ export function loadTaggableResources(): Set<string> {
67
+ return new Set(loadTagPlacements().keys());
68
+ }
69
+
70
+ /** Where `type`'s tags go, or undefined when it isn't taggable or can't be placed. */
71
+ export function tagPlacementFor(type: string): TagPlacement | undefined {
72
+ return loadTagPlacements().get(type) ?? undefined;
73
+ }
74
+
75
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
76
+ return typeof value === "object" && value !== null && !Array.isArray(value);
77
+ }
78
+
79
+ /** True for a CloudFormation intrinsic (`Ref`, `Fn::*`), which can't be merged into. */
80
+ export function isTagIntrinsic(value: unknown): boolean {
81
+ if (!isPlainObject(value)) return false;
82
+ const keys = Object.keys(value);
83
+ return keys.length === 1 && (keys[0] === "Ref" || keys[0].startsWith("Fn::"));
84
+ }
85
+
86
+ /**
87
+ * Read a tag value of either shape into a key-to-value map: a `[{Key, Value}]`
88
+ * list or a plain map. An intrinsic or anything else reads as `{}`.
89
+ */
90
+ export function readTagValue(value: unknown): Record<string, unknown> {
91
+ const out: Record<string, unknown> = {};
92
+ if (Array.isArray(value)) {
93
+ for (const t of value) {
94
+ if (isPlainObject(t) && typeof t.Key === "string") out[t.Key] = t.Value;
95
+ }
96
+ return out;
97
+ }
98
+ if (isPlainObject(value) && !isTagIntrinsic(value)) return { ...value };
99
+ return out;
100
+ }
101
+
102
+ /**
103
+ * The tags on a resource's property tree, read through the type's own tag
104
+ * property (`UserPoolTags` on a Cognito user pool), falling back to `Tags`
105
+ * when the type is unknown. Either shape reads (#4350).
106
+ */
107
+ export function readResourceTags(type: string | undefined, properties: Record<string, unknown> | undefined): Record<string, unknown> {
108
+ if (!properties) return {};
109
+ const placement = type ? tagPlacementFor(type) : undefined;
110
+ return readTagValue(properties[placement?.property ?? "Tags"]);
44
111
  }