@intentius/chant-lexicon-azure 0.124.30 → 0.124.32

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 (117) hide show
  1. package/dist/actions/appservice.js +7 -0
  2. package/dist/actions/compute.js +9 -0
  3. package/dist/actions/container.js +14 -0
  4. package/dist/actions/identity.js +11 -0
  5. package/dist/actions/index.js +11 -0
  6. package/dist/actions/keyvault.js +12 -0
  7. package/dist/actions/network.js +9 -0
  8. package/dist/actions/sql.js +9 -0
  9. package/dist/actions/storage.js +15 -0
  10. package/dist/api/read-client.js +146 -0
  11. package/dist/codegen/docs-cli.js +2 -0
  12. package/dist/codegen/docs.js +83 -0
  13. package/dist/codegen/generate-cli.js +11 -0
  14. package/dist/codegen/generate-lexicon.js +77 -0
  15. package/dist/codegen/generate-typescript.js +163 -0
  16. package/dist/codegen/generate.js +106 -0
  17. package/dist/codegen/naming.js +139 -0
  18. package/dist/codegen/package.js +64 -0
  19. package/dist/composites/aks-cluster.js +68 -0
  20. package/dist/composites/app-service.js +62 -0
  21. package/dist/composites/application-gateway.js +107 -0
  22. package/dist/composites/catalog.js +856 -0
  23. package/dist/composites/container-instance.js +36 -0
  24. package/dist/composites/container-registry.js +62 -0
  25. package/dist/composites/cosmos-database.js +56 -0
  26. package/dist/composites/from-arm.js +47 -0
  27. package/dist/composites/function-app.js +57 -0
  28. package/dist/composites/index.js +16 -0
  29. package/dist/composites/keyvault.js +69 -0
  30. package/dist/composites/landing-zone.js +119 -0
  31. package/dist/composites/private-endpoint.js +61 -0
  32. package/dist/composites/redis-cache.js +21 -0
  33. package/dist/composites/service-bus-pipeline.js +54 -0
  34. package/dist/composites/sql-database.js +62 -0
  35. package/dist/composites/storage-account.js +59 -0
  36. package/dist/composites/vm-linux.js +139 -0
  37. package/dist/composites/vnet-default.js +102 -0
  38. package/dist/coverage.js +28 -0
  39. package/dist/deep-observe.js +356 -0
  40. package/dist/default-tags.js +50 -0
  41. package/dist/deploy-scopes.d.ts.map +1 -1
  42. package/dist/deploy-scopes.js +157 -0
  43. package/dist/describe-resources.js +187 -0
  44. package/dist/detect.js +17 -0
  45. package/dist/export-resources.js +108 -0
  46. package/dist/generated/index.js +16598 -0
  47. package/dist/generated/runtime.js +4 -0
  48. package/dist/governance.js +144 -0
  49. package/dist/import/generator.js +140 -0
  50. package/dist/import/live-export.js +39 -0
  51. package/dist/import/parser.js +192 -0
  52. package/dist/index.js +44 -0
  53. package/dist/integrity.json +2 -2
  54. package/dist/intrinsics.js +179 -0
  55. package/dist/lint/audit-catalog.js +36 -0
  56. package/dist/lint/audit-lineage.js +71 -0
  57. package/dist/lint/post-synth/arm-refs.js +94 -0
  58. package/dist/lint/post-synth/azr010.js +45 -0
  59. package/dist/lint/post-synth/azr011.js +43 -0
  60. package/dist/lint/post-synth/azr012.js +41 -0
  61. package/dist/lint/post-synth/azr013.js +50 -0
  62. package/dist/lint/post-synth/azr014.js +49 -0
  63. package/dist/lint/post-synth/azr015.js +66 -0
  64. package/dist/lint/post-synth/azr016.js +37 -0
  65. package/dist/lint/post-synth/azr017.js +37 -0
  66. package/dist/lint/post-synth/azr018.js +42 -0
  67. package/dist/lint/post-synth/azr019.js +41 -0
  68. package/dist/lint/post-synth/azr020.js +37 -0
  69. package/dist/lint/post-synth/azr021.js +36 -0
  70. package/dist/lint/post-synth/azr022.js +39 -0
  71. package/dist/lint/post-synth/azr023.js +39 -0
  72. package/dist/lint/post-synth/azr024.js +39 -0
  73. package/dist/lint/post-synth/azr025.js +37 -0
  74. package/dist/lint/post-synth/azr026.js +38 -0
  75. package/dist/lint/post-synth/azr027.js +37 -0
  76. package/dist/lint/post-synth/azr028.js +37 -0
  77. package/dist/lint/post-synth/azr029.js +38 -0
  78. package/dist/lint/post-synth/azr030.js +44 -0
  79. package/dist/lint/post-synth/azr031.js +36 -0
  80. package/dist/lint/post-synth/azr032.js +53 -0
  81. package/dist/lint/post-synth/azr033.js +35 -0
  82. package/dist/lint/post-synth/index.js +50 -0
  83. package/dist/lint/rules/hardcoded-location.js +82 -0
  84. package/dist/lint/rules/index.js +6 -0
  85. package/dist/lint/rules/nsg-wildcard.js +44 -0
  86. package/dist/lint/rules/storage-https.js +62 -0
  87. package/dist/lsp/completions.d.ts.map +1 -1
  88. package/dist/lsp/completions.js +18 -0
  89. package/dist/lsp/hover.d.ts.map +1 -1
  90. package/dist/lsp/hover.js +44 -0
  91. package/dist/manifest.json +1 -1
  92. package/dist/op/activities/az-apply.js +563 -0
  93. package/dist/op/activities/azure.js +48 -0
  94. package/dist/op/activities/floci-az.js +37 -0
  95. package/dist/op/activities/index.js +10 -0
  96. package/dist/op/builders.js +70 -0
  97. package/dist/ownership.js +13 -0
  98. package/dist/package-cli.js +21 -0
  99. package/dist/parameter.js +13 -0
  100. package/dist/plugin.d.ts.map +1 -1
  101. package/dist/plugin.js +472 -0
  102. package/dist/pseudo.js +40 -0
  103. package/dist/serializer.js +457 -0
  104. package/dist/spec/api-versions.js +130 -0
  105. package/dist/spec/fetch.js +196 -0
  106. package/dist/spec/parse.js +308 -0
  107. package/dist/taggable.js +34 -0
  108. package/dist/validate-cli.js +16 -0
  109. package/dist/validate.js +53 -0
  110. package/package.json +8 -8
  111. package/src/codegen/package.ts +2 -2
  112. package/src/deploy-scopes.ts +2 -1
  113. package/src/lsp/completions.ts +2 -1
  114. package/src/lsp/hover.ts +2 -1
  115. package/src/plugin.ts +3 -2
  116. package/src/serializer.ts +2 -2
  117. package/src/taggable.ts +2 -2
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Azure Resource Manager schema fetching.
3
+ *
4
+ * Downloads the azure-resource-manager-schemas repo tarball,
5
+ * extracts provider JSON schemas, deduplicates by latest API version,
6
+ * and "explodes" multi-resource files into one entry per resource.
7
+ */
8
+ import { homedir } from "os";
9
+ import { join } from "path";
10
+ import { fetchWithCache, extractFromTar, clearCacheFile } from "@intentius/chant/codegen/fetch";
11
+ import { latestVersionPerProvider } from "./api-versions.js";
12
+ /** Per-attempt download timeout for a multi-megabyte archive. */
13
+ const ARCHIVE_ATTEMPT_TIMEOUT_MS = 120000;
14
+ /**
15
+ * Pinned commit of Azure/azure-resource-manager-schemas (#1144).
16
+ *
17
+ * The repo has no tagged releases, so unlike gcp (`KCC_VERSION`) or k8s
18
+ * (`K8S_SCHEMA_VERSION`) there is no version tag to pin against — codegen
19
+ * used to fetch `refs/heads/main` directly. That meant a cold cache picked
20
+ * up whatever upstream state had landed since the cache last expired, while
21
+ * a warm cache kept serving whatever was current when it was last filled.
22
+ * Azure republishes per-provider schema files under new dates constantly,
23
+ * and a later date can drop resources an earlier one defined (see
24
+ * PROVIDER_VERSION_OVERRIDES in ./api-versions.ts) — so which resources
25
+ * even exist to generate depends on exactly when you fetch, not just what
26
+ * the fetch URL happens to be. That broke composites/vm-linux.ts at tsc
27
+ * nondeterministically, depending on which runner's cache was warm.
28
+ *
29
+ * Pinning to a commit SHA makes generation reproducible regardless of cache
30
+ * state. Bump policy: #523's scheduled lexicon-upgrade cron (rolling-spec
31
+ * bucket, sub-issue #526) is meant to propose bumps to this constant as a
32
+ * reviewable PR once built. Until then, bump by hand: take the current
33
+ * `main` HEAD sha from
34
+ * https://github.com/Azure/azure-resource-manager-schemas/commits/main,
35
+ * regenerate, fix up any composite left referencing a renamed or vanished
36
+ * export (adding a PROVIDER_VERSION_OVERRIDES entry if a resource dropped
37
+ * out of the naive "latest by date" pick), and update this constant +
38
+ * comment.
39
+ */
40
+ export const AZURE_SCHEMA_COMMIT = "0085cb6f3a98e735359807143bcb1667aeec930f";
41
+ const TARBALL_URL = `https://github.com/Azure/azure-resource-manager-schemas/archive/${AZURE_SCHEMA_COMMIT}.tar.gz`;
42
+ const CACHE_DIR = join(homedir(), ".chant");
43
+ // The cache filename embeds the pin so a stale tarball from before a bump
44
+ // can never mask the bump: restoring an old ~/.chant (e.g. a CI cache
45
+ // partial-key fallback, or a developer's pre-existing cache) simply misses
46
+ // this filename and re-downloads, instead of silently serving pre-bump
47
+ // schemas under a name that still matches (#1144).
48
+ const CACHE_FILE = join(CACHE_DIR, `azure-resource-manager-schemas-${AZURE_SCHEMA_COMMIT}.tar.gz`);
49
+ /** Paths to skip (common-types, non-provider files). */
50
+ function isProviderSchema(path) {
51
+ if (path.includes("common-types/"))
52
+ return false;
53
+ if (path.includes("test/"))
54
+ return false;
55
+ if (!path.includes("/schemas/"))
56
+ return false;
57
+ // Must match schemas/{date}/Microsoft.*.json
58
+ return /schemas\/\d{4}-\d{2}-\d{2}(?:-preview)?\/Microsoft\.[^/]+\.json$/.test(path);
59
+ }
60
+ /**
61
+ * Normalize schema-file provider name to canonical ARM provider.
62
+ * "Microsoft.Network.NRP" → "Microsoft.Network"
63
+ * "Microsoft.Sql.Legacy" → "Microsoft.Sql"
64
+ * "Microsoft.Compute" → "Microsoft.Compute" (unchanged)
65
+ */
66
+ function normalizeProvider(fileProvider) {
67
+ const parts = fileProvider.split(".");
68
+ // Microsoft.X.Y... → Microsoft.X
69
+ return parts.length > 2 ? `${parts[0]}.${parts[1]}` : fileProvider;
70
+ }
71
+ /**
72
+ * Providers whose scope-specific resource definition sections are read in
73
+ * addition to `resourceDefinitions` (#1545).
74
+ *
75
+ * Deliberately an allowlist: reading the scoped sections for every provider
76
+ * would pull hundreds of subscription/tenant-scope resources into the
77
+ * generated surface at once (and back into tsc time — see the #438 bounding
78
+ * story). Only the org-hierarchy and policy providers need them today; grow
79
+ * this set intentionally, checking generated size and azure tsc time after.
80
+ */
81
+ const SCOPED_SECTION_PROVIDERS = new Set([
82
+ "Microsoft.Management",
83
+ "Microsoft.Subscription",
84
+ "Microsoft.Authorization",
85
+ ]);
86
+ /** Scope sections of a provider file, in definition-priority order. */
87
+ const SCOPE_SECTIONS = [
88
+ { key: "resourceDefinitions", scope: "resourceGroup" },
89
+ { key: "subscription_resourceDefinitions", scope: "subscription" },
90
+ { key: "managementGroup_resourceDefinitions", scope: "managementGroup" },
91
+ { key: "tenant_resourceDefinitions", scope: "tenant" },
92
+ ];
93
+ /**
94
+ * Explode one provider schema file into per-resource schema entries,
95
+ * writing into `out` keyed by resource type. Resource types already present
96
+ * in `out` are left untouched (first file wins across a multi-version pin).
97
+ *
98
+ * For providers in {@link SCOPED_SECTION_PROVIDERS} all deployment-scope
99
+ * sections are read; the definition comes from the highest-priority section
100
+ * that has it, and `deployScopes` unions every section that defines it.
101
+ */
102
+ export function explodeProviderSchema(provider, apiVersion, providerSchema, out) {
103
+ const definitions = providerSchema.definitions ?? {};
104
+ const sections = SCOPED_SECTION_PROVIDERS.has(normalizeProvider(provider))
105
+ ? SCOPE_SECTIONS
106
+ : SCOPE_SECTIONS.slice(0, 1);
107
+ // Gather each resource name's definition (highest-priority section wins)
108
+ // and the union of scopes that define it.
109
+ const byName = new Map();
110
+ for (const { key, scope } of sections) {
111
+ const defs = providerSchema[key];
112
+ if (!defs)
113
+ continue;
114
+ for (const [resourceName, resourceDef] of Object.entries(defs)) {
115
+ const existing = byName.get(resourceName);
116
+ if (existing) {
117
+ existing.scopes.push(scope);
118
+ }
119
+ else {
120
+ byName.set(resourceName, { def: resourceDef, scopes: [scope] });
121
+ }
122
+ }
123
+ }
124
+ for (const [resourceName, { def, scopes }] of byName) {
125
+ const resourceType = `${normalizeProvider(provider)}/${resourceName}`;
126
+ if (out.has(resourceType))
127
+ continue;
128
+ // Build a per-resource schema that includes the resource def + shared definitions + apiVersion
129
+ const perResourceSchema = {
130
+ resourceType,
131
+ apiVersion,
132
+ provider,
133
+ resourceName,
134
+ deployScopes: scopes,
135
+ resourceDefinition: def,
136
+ definitions,
137
+ };
138
+ out.set(resourceType, Buffer.from(JSON.stringify(perResourceSchema)));
139
+ }
140
+ }
141
+ /**
142
+ * Fetch ARM schemas and return a Map keyed by resource type
143
+ * (e.g. "Microsoft.Storage/storageAccounts") to raw JSON bytes.
144
+ *
145
+ * Each provider file is "exploded" so that every resourceDefinition
146
+ * becomes its own entry with the shared definitions included.
147
+ */
148
+ export async function fetchArmSchemas(force = false) {
149
+ const tarGz = await fetchWithCache({ url: TARBALL_URL, cacheFile: CACHE_FILE, attemptTimeoutMs: ARCHIVE_ATTEMPT_TIMEOUT_MS }, force);
150
+ // Gunzip
151
+ const { gunzipSync } = await import("fflate");
152
+ const tarData = gunzipSync(new Uint8Array(tarGz));
153
+ // Extract all provider schema files
154
+ const allFiles = extractFromTar(tarData, isProviderSchema);
155
+ // Deduplicate: keep only the latest API version per provider, or the pinned
156
+ // versions (in pin order) for providers in PROVIDER_VERSION_OVERRIDES.
157
+ const paths = [...allFiles.keys()];
158
+ const selected = latestVersionPerProvider(paths);
159
+ // Explode: one provider file has N resourceDefinitions → emit N entries.
160
+ // With a multi-version pin the first file that defines a resource wins.
161
+ const schemas = new Map();
162
+ for (const [provider, files] of selected) {
163
+ // First-wins applies within one provider's pin list; across providers the
164
+ // pre-#1545 behavior is kept (a later provider file may overwrite a
165
+ // normalized-name collision, e.g. Microsoft.Sql.Legacy vs Microsoft.Sql).
166
+ const providerSchemas = new Map();
167
+ for (const { path, apiVersion } of files) {
168
+ const data = allFiles.get(path);
169
+ if (!data)
170
+ continue;
171
+ try {
172
+ const providerSchema = JSON.parse(data.toString("utf-8"));
173
+ explodeProviderSchema(provider, apiVersion, providerSchema, providerSchemas);
174
+ }
175
+ catch {
176
+ // Skip files that can't be parsed
177
+ }
178
+ }
179
+ for (const [resourceType, buf] of providerSchemas) {
180
+ schemas.set(resourceType, buf);
181
+ }
182
+ }
183
+ return schemas;
184
+ }
185
+ /**
186
+ * Get the cache file path (for testing).
187
+ */
188
+ export function getCachePath() {
189
+ return CACHE_FILE;
190
+ }
191
+ /**
192
+ * Clear the cache (for testing).
193
+ */
194
+ export function clearCache() {
195
+ clearCacheFile(CACHE_FILE);
196
+ }
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Azure Resource Manager JSON Schema parser.
3
+ *
4
+ * Parses each exploded ARM schema into typed structures suitable for code generation:
5
+ * resources with properties and attributes, property types from definitions,
6
+ * and enum types from string enum definitions.
7
+ *
8
+ * Key differences from CloudFormation parsing:
9
+ * - ARM has resource-level fields (name, type, apiVersion, location, sku, kind,
10
+ * identity, tags, zones, plan) separate from properties.properties
11
+ * - ARM schemas use oneOf wrappers with expression refs that must be unwrapped
12
+ * - ARM schemas lack readOnlyProperties — attributes are curated
13
+ */
14
+ import { resolvePropertyType as coreResolvePropertyType, extractConstraints as coreExtractConstraints, constraintsIsEmpty as coreConstraintsIsEmpty, isEnumDefinition as coreIsEnumDefinition, } from "@intentius/chant/codegen/json-schema";
15
+ import { boundPropertyTypes } from "@intentius/chant/codegen/bound-property-types";
16
+ /** ARM resource-level fields that live outside properties.properties. */
17
+ const RESOURCE_LEVEL_FIELDS = new Set([
18
+ "name",
19
+ "type",
20
+ "apiVersion",
21
+ "location",
22
+ "sku",
23
+ "kind",
24
+ "identity",
25
+ "tags",
26
+ "zones",
27
+ "plan",
28
+ ]);
29
+ /** Expression reference pattern in ARM oneOf wrappers. */
30
+ const EXPRESSION_REF_PATTERN = /definitions\.json#\/definitions\/expression|common\/definitions\.json/;
31
+ /**
32
+ * Curated attributes for common ARM resources.
33
+ * ARM schemas lack readOnlyProperties, so we maintain this list.
34
+ */
35
+ const CURATED_ATTRIBUTES = {
36
+ "Microsoft.Storage/storageAccounts": [
37
+ { name: "id", tsType: "string" },
38
+ { name: "primaryEndpoints", tsType: "string" },
39
+ { name: "primaryLocation", tsType: "string" },
40
+ { name: "provisioningState", tsType: "string" },
41
+ ],
42
+ "Microsoft.Compute/virtualMachines": [
43
+ { name: "id", tsType: "string" },
44
+ { name: "vmId", tsType: "string" },
45
+ { name: "provisioningState", tsType: "string" },
46
+ ],
47
+ "Microsoft.Network/virtualNetworks": [
48
+ { name: "id", tsType: "string" },
49
+ { name: "provisioningState", tsType: "string" },
50
+ ],
51
+ "Microsoft.Network/networkSecurityGroups": [
52
+ { name: "id", tsType: "string" },
53
+ { name: "provisioningState", tsType: "string" },
54
+ ],
55
+ "Microsoft.Network/publicIPAddresses": [
56
+ { name: "id", tsType: "string" },
57
+ { name: "ipAddress", tsType: "string" },
58
+ { name: "provisioningState", tsType: "string" },
59
+ ],
60
+ "Microsoft.Network/networkInterfaces": [
61
+ { name: "id", tsType: "string" },
62
+ { name: "provisioningState", tsType: "string" },
63
+ ],
64
+ "Microsoft.Web/serverfarms": [
65
+ { name: "id", tsType: "string" },
66
+ { name: "provisioningState", tsType: "string" },
67
+ ],
68
+ "Microsoft.Web/sites": [
69
+ { name: "id", tsType: "string" },
70
+ { name: "defaultHostName", tsType: "string" },
71
+ { name: "provisioningState", tsType: "string" },
72
+ ],
73
+ "Microsoft.ContainerService/managedClusters": [
74
+ { name: "id", tsType: "string" },
75
+ { name: "fqdn", tsType: "string" },
76
+ { name: "provisioningState", tsType: "string" },
77
+ { name: "nodeResourceGroup", tsType: "string" },
78
+ ],
79
+ "Microsoft.Sql/servers": [
80
+ { name: "id", tsType: "string" },
81
+ { name: "fullyQualifiedDomainName", tsType: "string" },
82
+ { name: "provisioningState", tsType: "string" },
83
+ ],
84
+ "Microsoft.Sql/servers_databases": [
85
+ { name: "id", tsType: "string" },
86
+ { name: "provisioningState", tsType: "string" },
87
+ ],
88
+ "Microsoft.KeyVault/vaults": [
89
+ { name: "id", tsType: "string" },
90
+ { name: "vaultUri", tsType: "string" },
91
+ { name: "provisioningState", tsType: "string" },
92
+ ],
93
+ "Microsoft.ContainerRegistry/registries": [
94
+ { name: "id", tsType: "string" },
95
+ { name: "loginServer", tsType: "string" },
96
+ { name: "provisioningState", tsType: "string" },
97
+ ],
98
+ "Microsoft.DocumentDB/databaseAccounts": [
99
+ { name: "id", tsType: "string" },
100
+ { name: "documentEndpoint", tsType: "string" },
101
+ { name: "provisioningState", tsType: "string" },
102
+ ],
103
+ "Microsoft.Network/loadBalancers": [
104
+ { name: "id", tsType: "string" },
105
+ { name: "provisioningState", tsType: "string" },
106
+ ],
107
+ };
108
+ /**
109
+ * Maximum nesting depth of generated property-type interfaces.
110
+ *
111
+ * ARM schemas embed a named definition for every nested object, and fully
112
+ * expanding all of them per-resource produces a ~273MB declaration that is not
113
+ * shippable as `.d.ts`. We instead emit only the property types reachable from
114
+ * a resource's top-level properties within this depth; references that are
115
+ * deeper, or to definitions no resource property reaches, are loosened to
116
+ * `Record<string, unknown>`. Depth 1 keeps top-level props + their immediate
117
+ * nested object shapes typed; deeper ARM config trees become loose. (#438)
118
+ */
119
+ const MAX_PROPERTY_TYPE_DEPTH = 1;
120
+ /**
121
+ * Property-type definition names that are always emitted as typed interfaces,
122
+ * regardless of reachability/depth — common nested ARM shapes worth typing for
123
+ * authoring ergonomics. Kept in sync with the azure `validate` required-names
124
+ * check (it asserts these survive code generation). (#438)
125
+ */
126
+ export const CURATED_PROPERTY_TYPE_DEFS = new Set([
127
+ "Sku",
128
+ "Identity",
129
+ "NetworkProfile",
130
+ "StorageProfile",
131
+ "OsDisk",
132
+ "ImageReference",
133
+ "IpConfiguration",
134
+ "SecurityRule",
135
+ "SubnetProperties",
136
+ "SiteConfig",
137
+ ]);
138
+ /**
139
+ * Parse an ARM schema (exploded per-resource format) into typed structures.
140
+ */
141
+ export function parseArmSchema(data) {
142
+ const raw = JSON.parse(typeof data === "string" ? data : data.toString("utf-8"));
143
+ const resourceType = raw.resourceType;
144
+ const apiVersion = raw.apiVersion;
145
+ const resourceDef = raw.resourceDefinition;
146
+ const definitions = raw.definitions ?? {};
147
+ // Deployment scopes stamped by the fetch explode (#1545). Only carried
148
+ // through when the resource exists somewhere other than plain
149
+ // resource-group scope, so the common case stays field-free.
150
+ const rawScopes = raw.deployScopes;
151
+ const deployScopes = rawScopes && !(rawScopes.length === 1 && rawScopes[0] === "resourceGroup")
152
+ ? rawScopes
153
+ : undefined;
154
+ const shortName = armShortName(resourceType);
155
+ const fakeSchema = { definitions };
156
+ // Parse resource-level fields and nested properties
157
+ const props = [];
158
+ const resourceLevelFields = [];
159
+ if (resourceDef.properties) {
160
+ for (const [name, prop] of Object.entries(resourceDef.properties)) {
161
+ if (name === "type" || name === "apiVersion")
162
+ continue; // These are injected
163
+ const unwrapped = unwrapExpressionOneOf(prop);
164
+ if (RESOURCE_LEVEL_FIELDS.has(name)) {
165
+ resourceLevelFields.push(name);
166
+ }
167
+ // If this is the "properties" container, expand its sub-properties
168
+ if (name === "properties" && unwrapped.properties) {
169
+ const innerRequired = new Set(unwrapped.required ?? []);
170
+ for (const [innerName, innerProp] of Object.entries(unwrapped.properties)) {
171
+ const innerUnwrapped = unwrapExpressionOneOf(innerProp);
172
+ const tsType = resolvePropertyType(innerUnwrapped, definitions, shortName);
173
+ props.push({
174
+ name: innerName,
175
+ tsType,
176
+ required: innerRequired.has(innerName),
177
+ description: innerUnwrapped.description,
178
+ enum: innerUnwrapped.enum,
179
+ constraints: extractConstraints(innerUnwrapped),
180
+ });
181
+ }
182
+ continue;
183
+ }
184
+ const tsType = resolvePropertyType(unwrapped, definitions, shortName);
185
+ const requiredSet = new Set(resourceDef.required ?? []);
186
+ props.push({
187
+ name,
188
+ tsType,
189
+ required: requiredSet.has(name),
190
+ description: unwrapped.description,
191
+ enum: unwrapped.enum,
192
+ constraints: extractConstraints(unwrapped),
193
+ });
194
+ }
195
+ }
196
+ // Parse definitions into property types and enums
197
+ const propertyTypes = [];
198
+ const enums = [];
199
+ for (const [defName, def] of Object.entries(definitions)) {
200
+ // Skip expression-related definitions
201
+ if (defName === "expression" || defName.startsWith("http"))
202
+ continue;
203
+ if (isEnumDefinition(def)) {
204
+ enums.push({
205
+ name: `${shortName}_${defName}`,
206
+ values: def.enum,
207
+ });
208
+ continue;
209
+ }
210
+ if (def.properties) {
211
+ const defRequired = new Set(def.required ?? []);
212
+ const defProps = [];
213
+ for (const [propName, prop] of Object.entries(def.properties)) {
214
+ const unwrapped = unwrapExpressionOneOf(prop);
215
+ const tsType = resolvePropertyType(unwrapped, definitions, shortName);
216
+ defProps.push({
217
+ name: propName,
218
+ tsType,
219
+ required: defRequired.has(propName),
220
+ description: unwrapped.description,
221
+ enum: unwrapped.enum,
222
+ constraints: extractConstraints(unwrapped),
223
+ });
224
+ }
225
+ propertyTypes.push({
226
+ name: `${shortName}_${defName}`,
227
+ specType: defName,
228
+ properties: defProps,
229
+ });
230
+ }
231
+ }
232
+ // Attributes from curated map
233
+ const attrs = CURATED_ATTRIBUTES[resourceType] ?? [
234
+ { name: "id", tsType: "string" },
235
+ ];
236
+ // Bound the emitted property types to keep the generated declaration shippable (#438).
237
+ const boundedPropertyTypes = boundPropertyTypes(shortName, props, propertyTypes, new Set(enums.map((e) => e.name)), { maxDepth: MAX_PROPERTY_TYPE_DEPTH, curatedDefs: CURATED_PROPERTY_TYPE_DEFS });
238
+ // Detect tagging support (if tags field exists)
239
+ const hasTags = resourceLevelFields.includes("tags");
240
+ const tagging = hasTags
241
+ ? { taggable: true, tagOnCreate: true, tagUpdatable: true }
242
+ : undefined;
243
+ return {
244
+ resource: {
245
+ typeName: resourceType,
246
+ apiVersion,
247
+ properties: props,
248
+ attributes: attrs,
249
+ resourceLevelFields,
250
+ ...(tagging && { tagging }),
251
+ ...(deployScopes && { deployScopes }),
252
+ },
253
+ propertyTypes: boundedPropertyTypes,
254
+ enums,
255
+ };
256
+ }
257
+ // --- Unwrap oneOf expression wrappers ---
258
+ /**
259
+ * ARM schemas wrap real types in oneOf with an expression $ref.
260
+ * Detect this pattern and return the real type.
261
+ */
262
+ function unwrapExpressionOneOf(prop) {
263
+ if (!prop.oneOf || prop.oneOf.length < 2)
264
+ return prop;
265
+ const nonExpr = prop.oneOf.filter((item) => {
266
+ if (item.$ref && EXPRESSION_REF_PATTERN.test(item.$ref))
267
+ return false;
268
+ return true;
269
+ });
270
+ if (nonExpr.length === 1) {
271
+ // Merge description from parent if present
272
+ const result = { ...nonExpr[0] };
273
+ if (prop.description && !result.description) {
274
+ result.description = prop.description;
275
+ }
276
+ return result;
277
+ }
278
+ return prop;
279
+ }
280
+ // --- Type resolution (delegated to core) ---
281
+ function resolvePropertyType(prop, definitions, shortName) {
282
+ const fakeDoc = { definitions };
283
+ return coreResolvePropertyType(prop, fakeDoc, (defName) => `${shortName}_${defName}`);
284
+ }
285
+ function extractConstraints(prop) {
286
+ return coreExtractConstraints(prop);
287
+ }
288
+ export const constraintsIsEmpty = coreConstraintsIsEmpty;
289
+ function isEnumDefinition(def) {
290
+ return coreIsEnumDefinition(def);
291
+ }
292
+ /**
293
+ * Extract short resource name:
294
+ * "Microsoft.Storage/storageAccounts" → "storageAccounts"
295
+ */
296
+ export function armShortName(typeName) {
297
+ const slashIdx = typeName.lastIndexOf("/");
298
+ return slashIdx >= 0 ? typeName.slice(slashIdx + 1) : typeName;
299
+ }
300
+ /**
301
+ * Extract service name:
302
+ * "Microsoft.Storage/storageAccounts" → "Storage"
303
+ */
304
+ export function armServiceName(typeName) {
305
+ const match = typeName.match(/^Microsoft\.([^/]+)/);
306
+ // Strip dots from hierarchical providers (e.g. "App.ContainerApps" → "AppContainerApps")
307
+ return match ? match[1].replace(/\./g, "") : typeName;
308
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shared taggable resource lookup — loads from the generated lexicon JSON.
3
+ *
4
+ * Lazy-loaded and cached for the lifetime of the process.
5
+ */
6
+ import { readFileSync } from "fs";
7
+ import { join } from "path";
8
+ import { moduleSourceDir } from "@intentius/chant/module-source";
9
+ let _cached;
10
+ const __dirname_ = moduleSourceDir(import.meta.url);
11
+ /**
12
+ * Load taggable resource types from the lexicon JSON.
13
+ * Result is cached after first call.
14
+ */
15
+ export function loadTaggableResources() {
16
+ if (_cached)
17
+ return _cached;
18
+ const set = new Set();
19
+ try {
20
+ const lexiconPath = join(__dirname_, "generated", "lexicon-azure.json");
21
+ const content = readFileSync(lexiconPath, "utf-8");
22
+ const data = JSON.parse(content);
23
+ for (const [_name, entry] of Object.entries(data)) {
24
+ if (entry.kind === "resource" && entry.resourceType && entry.tagging?.taggable) {
25
+ set.add(entry.resourceType);
26
+ }
27
+ }
28
+ }
29
+ catch {
30
+ // Lexicon not available — skip
31
+ }
32
+ _cached = set;
33
+ return set;
34
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Thin entry point for `npm run validate` in lexicon-azure.
4
+ */
5
+ import { validate } from "./validate.js";
6
+ const result = await validate();
7
+ for (const check of result.checks) {
8
+ const status = check.ok ? "OK" : "FAIL";
9
+ const msg = check.error ? ` — ${check.error}` : "";
10
+ console.error(` [${status}] ${check.name}${msg}`);
11
+ }
12
+ if (!result.success) {
13
+ console.error("Validation failed");
14
+ process.exit(1);
15
+ }
16
+ console.error("All validation checks passed.");
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Validate generated lexicon-azure artifacts.
3
+ */
4
+ import { dirname } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { validateLexiconArtifacts } from "@intentius/chant/codegen/validate";
7
+ import { CURATED_PROPERTY_TYPE_DEFS } from "./spec/parse.js";
8
+ const REQUIRED_NAMES = [
9
+ // Core compute resources
10
+ "VirtualMachine", "VirtualMachineScaleSet", "AvailabilitySet",
11
+ // Networking
12
+ "VirtualNetwork", "Subnet", "NetworkInterface",
13
+ "NetworkSecurityGroup", "PublicIPAddress", "LoadBalancer",
14
+ // Storage
15
+ "StorageAccount", "BlobContainer",
16
+ // Web & App
17
+ "WebApp", "AppServicePlan",
18
+ // Containers
19
+ "ManagedCluster", "ContainerRegistry",
20
+ // Databases
21
+ "SqlServer", "SqlDatabase",
22
+ // Security & Identity
23
+ "KeyVault", "ManagedIdentity",
24
+ // Governance — org hierarchy + policy primitives (#1545). once claimed
25
+ // these existed when they did not; this gate keeps them from vanishing again.
26
+ "ManagementGroup", "SubscriptionAlias", "PolicyDefinition", "PolicyAssignment",
27
+ // DNS & Traffic
28
+ "DnsZone", "TrafficManagerProfile",
29
+ // Curated property types kept typed by the codegen (#438) — single source of truth.
30
+ ...CURATED_PROPERTY_TYPE_DEFS,
31
+ ];
32
+ export async function validate(opts) {
33
+ const basePath = opts?.basePath ?? dirname(dirname(fileURLToPath(import.meta.url)));
34
+ return validateLexiconArtifacts({
35
+ lexiconJsonFilename: "lexicon-azure.json",
36
+ requiredNames: REQUIRED_NAMES,
37
+ // Curated property types are emitted under resource-prefixed/variant names
38
+ // after bounding (#438), so match required names by substring.
39
+ requiredNamesMatchSubstring: true,
40
+ basePath,
41
+ // chant #1475 — azure generates from a commit sha of
42
+ // azure-resource-manager-schemas (#1144), so a fresh generate is
43
+ // deterministic. Gate the surface on every validate: the #1144 pin left
44
+ // the snapshot 483 entries behind until #1572 re-baselined it, because
45
+ // nothing compared the two on a PR.
46
+ checkSurfaceSnapshot: "always",
47
+ coverageThresholds: {
48
+ minPropertyPct: 1,
49
+ minLifecyclePct: 1,
50
+ minAttrPct: 1,
51
+ },
52
+ });
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-azure",
3
- "version": "0.124.30",
3
+ "version": "0.124.32",
4
4
  "description": "Azure lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -32,27 +32,27 @@
32
32
  ".": {
33
33
  "development": "./src/index.ts",
34
34
  "types": "./dist/index.d.ts",
35
- "default": "./src/index.ts"
35
+ "default": "./dist/index.js"
36
36
  },
37
37
  "./*": {
38
38
  "development": "./src/*.ts",
39
39
  "types": "./dist/*.d.ts",
40
- "default": "./src/*.ts"
40
+ "default": "./dist/*.js"
41
41
  },
42
42
  "./detect": {
43
43
  "development": "./src/detect.ts",
44
44
  "types": "./dist/detect.d.ts",
45
- "default": "./src/detect.ts"
45
+ "default": "./dist/detect.js"
46
46
  },
47
47
  "./op/activities": {
48
48
  "development": "./src/op/activities/index.ts",
49
49
  "types": "./dist/op/activities/index.d.ts",
50
- "default": "./src/op/activities/index.ts"
50
+ "default": "./dist/op/activities/index.js"
51
51
  },
52
52
  "./lint/post-synth": {
53
53
  "development": "./src/lint/post-synth/index.ts",
54
54
  "types": "./dist/lint/post-synth/index.d.ts",
55
- "default": "./src/lint/post-synth/index.ts"
55
+ "default": "./dist/lint/post-synth/index.js"
56
56
  },
57
57
  "./manifest": "./dist/manifest.json",
58
58
  "./meta": "./dist/meta.json",
@@ -64,7 +64,7 @@
64
64
  "validate": "tsx src/validate-cli.ts",
65
65
  "docs": "tsx src/codegen/docs-cli.ts",
66
66
  "prepack": "npm run generate && npm run bundle && npm run validate && npm run build",
67
- "build": "../../scripts/tsc7.sh -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && find dist -type f \\( -name \"*.js\" -o -name \"*.js.map\" \\) -delete"
67
+ "build": "../../scripts/tsc7.sh -p tsconfig.build.json --sourceMap false && tsc-alias -p tsconfig.build.json"
68
68
  },
69
69
  "dependencies": {
70
70
  "fflate": "^0.8.2"
@@ -74,6 +74,6 @@
74
74
  "typescript": "^5.9.3"
75
75
  },
76
76
  "peerDependencies": {
77
- "@intentius/chant": "^0.124.30"
77
+ "@intentius/chant": "^0.124.32"
78
78
  }
79
79
  }