@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.
- package/dist/actions/appservice.js +7 -0
- package/dist/actions/compute.js +9 -0
- package/dist/actions/container.js +14 -0
- package/dist/actions/identity.js +11 -0
- package/dist/actions/index.js +11 -0
- package/dist/actions/keyvault.js +12 -0
- package/dist/actions/network.js +9 -0
- package/dist/actions/sql.js +9 -0
- package/dist/actions/storage.js +15 -0
- package/dist/api/read-client.js +146 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +83 -0
- package/dist/codegen/generate-cli.js +11 -0
- package/dist/codegen/generate-lexicon.js +77 -0
- package/dist/codegen/generate-typescript.js +163 -0
- package/dist/codegen/generate.js +106 -0
- package/dist/codegen/naming.js +139 -0
- package/dist/codegen/package.js +64 -0
- package/dist/composites/aks-cluster.js +68 -0
- package/dist/composites/app-service.js +62 -0
- package/dist/composites/application-gateway.js +107 -0
- package/dist/composites/catalog.js +856 -0
- package/dist/composites/container-instance.js +36 -0
- package/dist/composites/container-registry.js +62 -0
- package/dist/composites/cosmos-database.js +56 -0
- package/dist/composites/from-arm.js +47 -0
- package/dist/composites/function-app.js +57 -0
- package/dist/composites/index.js +16 -0
- package/dist/composites/keyvault.js +69 -0
- package/dist/composites/landing-zone.js +119 -0
- package/dist/composites/private-endpoint.js +61 -0
- package/dist/composites/redis-cache.js +21 -0
- package/dist/composites/service-bus-pipeline.js +54 -0
- package/dist/composites/sql-database.js +62 -0
- package/dist/composites/storage-account.js +59 -0
- package/dist/composites/vm-linux.js +139 -0
- package/dist/composites/vnet-default.js +102 -0
- package/dist/coverage.js +28 -0
- package/dist/deep-observe.js +356 -0
- package/dist/default-tags.js +50 -0
- package/dist/deploy-scopes.d.ts.map +1 -1
- package/dist/deploy-scopes.js +157 -0
- package/dist/describe-resources.js +187 -0
- package/dist/detect.js +17 -0
- package/dist/export-resources.js +108 -0
- package/dist/generated/index.js +16598 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/governance.js +144 -0
- package/dist/import/generator.js +140 -0
- package/dist/import/live-export.js +39 -0
- package/dist/import/parser.js +192 -0
- package/dist/index.js +44 -0
- package/dist/integrity.json +2 -2
- package/dist/intrinsics.js +179 -0
- package/dist/lint/audit-catalog.js +36 -0
- package/dist/lint/audit-lineage.js +71 -0
- package/dist/lint/post-synth/arm-refs.js +94 -0
- package/dist/lint/post-synth/azr010.js +45 -0
- package/dist/lint/post-synth/azr011.js +43 -0
- package/dist/lint/post-synth/azr012.js +41 -0
- package/dist/lint/post-synth/azr013.js +50 -0
- package/dist/lint/post-synth/azr014.js +49 -0
- package/dist/lint/post-synth/azr015.js +66 -0
- package/dist/lint/post-synth/azr016.js +37 -0
- package/dist/lint/post-synth/azr017.js +37 -0
- package/dist/lint/post-synth/azr018.js +42 -0
- package/dist/lint/post-synth/azr019.js +41 -0
- package/dist/lint/post-synth/azr020.js +37 -0
- package/dist/lint/post-synth/azr021.js +36 -0
- package/dist/lint/post-synth/azr022.js +39 -0
- package/dist/lint/post-synth/azr023.js +39 -0
- package/dist/lint/post-synth/azr024.js +39 -0
- package/dist/lint/post-synth/azr025.js +37 -0
- package/dist/lint/post-synth/azr026.js +38 -0
- package/dist/lint/post-synth/azr027.js +37 -0
- package/dist/lint/post-synth/azr028.js +37 -0
- package/dist/lint/post-synth/azr029.js +38 -0
- package/dist/lint/post-synth/azr030.js +44 -0
- package/dist/lint/post-synth/azr031.js +36 -0
- package/dist/lint/post-synth/azr032.js +53 -0
- package/dist/lint/post-synth/azr033.js +35 -0
- package/dist/lint/post-synth/index.js +50 -0
- package/dist/lint/rules/hardcoded-location.js +82 -0
- package/dist/lint/rules/index.js +6 -0
- package/dist/lint/rules/nsg-wildcard.js +44 -0
- package/dist/lint/rules/storage-https.js +62 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +44 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/az-apply.js +563 -0
- package/dist/op/activities/azure.js +48 -0
- package/dist/op/activities/floci-az.js +37 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +70 -0
- package/dist/ownership.js +13 -0
- package/dist/package-cli.js +21 -0
- package/dist/parameter.js +13 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +472 -0
- package/dist/pseudo.js +40 -0
- package/dist/serializer.js +457 -0
- package/dist/spec/api-versions.js +130 -0
- package/dist/spec/fetch.js +196 -0
- package/dist/spec/parse.js +308 -0
- package/dist/taggable.js +34 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +53 -0
- package/package.json +8 -8
- package/src/codegen/package.ts +2 -2
- package/src/deploy-scopes.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/plugin.ts +3 -2
- package/src/serializer.ts +2 -2
- 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
|
+
}
|
package/dist/taggable.js
ADDED
|
@@ -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.");
|
package/dist/validate.js
ADDED
|
@@ -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.
|
|
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": "./
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
36
|
},
|
|
37
37
|
"./*": {
|
|
38
38
|
"development": "./src/*.ts",
|
|
39
39
|
"types": "./dist/*.d.ts",
|
|
40
|
-
"default": "./
|
|
40
|
+
"default": "./dist/*.js"
|
|
41
41
|
},
|
|
42
42
|
"./detect": {
|
|
43
43
|
"development": "./src/detect.ts",
|
|
44
44
|
"types": "./dist/detect.d.ts",
|
|
45
|
-
"default": "./
|
|
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": "./
|
|
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": "./
|
|
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
|
|
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.
|
|
77
|
+
"@intentius/chant": "^0.124.32"
|
|
78
78
|
}
|
|
79
79
|
}
|