@intentius/chant-lexicon-azure 0.124.31 → 0.124.33
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.d.ts.map +1 -1
- package/dist/generated/index.js +16598 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/governance.js +144 -0
- package/dist/health.d.ts +42 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +137 -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 +5 -5
- 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.d.ts +1 -0
- package/dist/lint/post-synth/arm-refs.d.ts.map +1 -1
- package/dist/lint/post-synth/arm-refs.js +96 -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/meta.json +6 -1
- package/dist/okf/types/containerApps.md +5 -0
- 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 +478 -0
- package/dist/pseudo.js +40 -0
- package/dist/rules/arm-refs.ts +5 -3
- package/dist/serializer.d.ts +7 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +543 -0
- package/dist/spec/api-versions.js +130 -0
- package/dist/spec/fetch.js +196 -0
- package/dist/spec/parse.d.ts.map +1 -1
- package/dist/spec/parse.js +318 -0
- package/dist/taggable.js +34 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +53 -0
- package/package.json +8 -8
- package/src/attributes.test.ts +17 -0
- package/src/codegen/generate-typescript.ts +1 -1
- package/src/codegen/generate.ts +1 -1
- package/src/codegen/package.ts +2 -2
- package/src/deploy-scopes.ts +2 -1
- package/src/generated/index.d.ts +5 -0
- package/src/generated/index.ts +1 -1
- package/src/generated/lexicon-azure.json +6 -1
- package/src/health.test.ts +66 -0
- package/src/health.ts +162 -0
- package/src/lint/post-synth/arm-refs.ts +5 -3
- package/src/lint/post-synth/post-synth.test.ts +1 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/plugin.ts +11 -2
- package/src/serializer-names.test.ts +83 -0
- package/src/serializer-outputs.test.ts +65 -0
- package/src/serializer.ts +112 -30
- package/src/spec/parse.test.ts +15 -0
- package/src/spec/parse.ts +10 -0
- package/src/taggable.ts +2 -2
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API version resolution for Azure Resource Manager schemas.
|
|
3
|
+
*
|
|
4
|
+
* ARM schemas are organized by API date:
|
|
5
|
+
* schemas/{date}/Microsoft.{Provider}.json
|
|
6
|
+
*
|
|
7
|
+
* This module picks the latest API version per provider.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Parse an API date string (e.g. "2023-01-01" or "2023-01-01-preview").
|
|
11
|
+
* Returns a numeric value for comparison: date timestamp with preview
|
|
12
|
+
* versions ranked below their GA counterpart.
|
|
13
|
+
*/
|
|
14
|
+
export function parseApiDate(dateStr) {
|
|
15
|
+
const isPreview = dateStr.endsWith("-preview");
|
|
16
|
+
const cleaned = isPreview ? dateStr.replace(/-preview$/, "") : dateStr;
|
|
17
|
+
const ts = new Date(cleaned).getTime();
|
|
18
|
+
// Preview versions sort just below their GA date
|
|
19
|
+
return isPreview ? ts - 1 : ts;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Compare two API date strings. Returns positive if a > b.
|
|
23
|
+
*/
|
|
24
|
+
export function compareApiDates(a, b) {
|
|
25
|
+
return parseApiDate(a) - parseApiDate(b);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extract provider name and API version from a schema file path.
|
|
29
|
+
*
|
|
30
|
+
* Input: "azure-resource-manager-schemas-main/schemas/2023-01-01/Microsoft.Storage.json"
|
|
31
|
+
* Output: { provider: "Microsoft.Storage", apiVersion: "2023-01-01" }
|
|
32
|
+
*/
|
|
33
|
+
export function parseSchemaPath(path) {
|
|
34
|
+
// Match schemas/{date}/Microsoft.{Provider}.json
|
|
35
|
+
const match = path.match(/schemas\/(\d{4}-\d{2}-\d{2}(?:-preview)?)\/(Microsoft\.[^/]+)\.json$/);
|
|
36
|
+
if (!match)
|
|
37
|
+
return null;
|
|
38
|
+
return { apiVersion: match[1], provider: match[2] };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Per-provider API-version pins.
|
|
42
|
+
*
|
|
43
|
+
* `latestVersionPerProvider` picks the single newest version per provider, but
|
|
44
|
+
* Azure spreads resources across versions — a newer version can DROP a resource
|
|
45
|
+
* an older one defined. Microsoft.Authorization's latest preview no longer
|
|
46
|
+
* includes the plain `roleAssignments` / `roleDefinitions` resources; they live
|
|
47
|
+
* in 2022-04-01, the latest stable that still has them. Pin it so those
|
|
48
|
+
* generate (the naming table maps both). See #223.
|
|
49
|
+
*
|
|
50
|
+
* Microsoft.Compute has the same issue (#1144): at the schema commit pinned
|
|
51
|
+
* in ../spec/fetch.ts, the newest dated `Microsoft.Compute.json` (2026-03-02)
|
|
52
|
+
* is a narrow disk-only delta that drops `virtualMachines`,
|
|
53
|
+
* `virtualMachineScaleSets`, and `availabilitySets` entirely — not a rename,
|
|
54
|
+
* an omission, since Azure alternates between a "VM flavor" and a "disk
|
|
55
|
+
* flavor" of this file across dates rather than publishing one cumulative
|
|
56
|
+
* file. 2026-03-01 is the most recent date that still has the VM family
|
|
57
|
+
* (validate.ts's REQUIRED_NAMES and composites/vm-linux.ts depend on it);
|
|
58
|
+
* it drops the disk-only resources (disks, diskAccesses, diskEncryptionSets,
|
|
59
|
+
* snapshots), which nothing here currently references.
|
|
60
|
+
*
|
|
61
|
+
* A pin can list several versions (#1545): Azure publishes some providers as
|
|
62
|
+
* per-family deltas, so no single date carries every resource we need. The
|
|
63
|
+
* files merge in the order listed — the first file that defines a resource
|
|
64
|
+
* name wins, later files only contribute resources not yet seen.
|
|
65
|
+
* Microsoft.Authorization needs three: 2022-04-01 for roleAssignments /
|
|
66
|
+
* roleDefinitions (#223), 2026-06-01 (latest stable policy file) for
|
|
67
|
+
* policyAssignments / policyDefinitions / policySetDefinitions and their
|
|
68
|
+
* _versions children, and 2022-07-01-preview for policyExemptions, which has
|
|
69
|
+
* no GA date and whose newer preview files (2024-12-01-preview and later)
|
|
70
|
+
* drag in policy variables we don't want yet.
|
|
71
|
+
*
|
|
72
|
+
* Microsoft.AzureStackHCI splits the same way (#1795): the newest dated file
|
|
73
|
+
* (2026-05-01-preview) carries only the cluster/edge-device family, while the
|
|
74
|
+
* VM family (virtualMachineInstances, virtualNetworks, networkInterfaces,
|
|
75
|
+
* publicIPAddresses, galleryImages, virtualHardDisks, ...) last appears in
|
|
76
|
+
* 2026-04-01-preview. Pin both, newest first — the cluster/edge family
|
|
77
|
+
* generates from 2026-05-01-preview and the VM family fills in from
|
|
78
|
+
* 2026-04-01-preview. `Microsoft.DataReplication/replicationVaults_
|
|
79
|
+
* privateEndpointConnectionProxies` dropped out in the same re-baseline but
|
|
80
|
+
* is NOT pinnable back: no dated Microsoft.DataReplication.json at the
|
|
81
|
+
* AZURE_SCHEMA_COMMIT pin (2021-02-16-preview, 2024-09-01, 2026-05-01)
|
|
82
|
+
* defines it — upstream removed the resource, so it stays gone.
|
|
83
|
+
*
|
|
84
|
+
* Microsoft.Management and Microsoft.Subscription are pinned to their latest
|
|
85
|
+
* GA dates: the naive latest for both is a preview (2024-02-01-preview adds
|
|
86
|
+
* serviceGroups; 2025-11-01-preview adds changeTenantRequest), and the
|
|
87
|
+
* management-group hierarchy should not author against preview apiVersions.
|
|
88
|
+
*/
|
|
89
|
+
export const PROVIDER_VERSION_OVERRIDES = {
|
|
90
|
+
"Microsoft.Authorization": ["2022-04-01", "2026-06-01", "2022-07-01-preview"],
|
|
91
|
+
"Microsoft.AzureStackHCI": ["2026-05-01-preview", "2026-04-01-preview"],
|
|
92
|
+
"Microsoft.Compute": ["2026-03-01"],
|
|
93
|
+
"Microsoft.Management": ["2023-04-01"],
|
|
94
|
+
"Microsoft.Subscription": ["2021-10-01"],
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Given a set of schema paths, return the schema files to generate from, per
|
|
98
|
+
* provider: the single latest API version, unless
|
|
99
|
+
* {@link PROVIDER_VERSION_OVERRIDES} pins the provider to one or more chosen
|
|
100
|
+
* versions (used where "latest" drops resources we depend on). For pinned
|
|
101
|
+
* providers the array preserves the pin order — callers merge the files
|
|
102
|
+
* first-wins per resource name.
|
|
103
|
+
*
|
|
104
|
+
* Returns a Map of provider → [{ path, apiVersion }, ...].
|
|
105
|
+
*/
|
|
106
|
+
export function latestVersionPerProvider(paths) {
|
|
107
|
+
const best = new Map();
|
|
108
|
+
for (const p of paths) {
|
|
109
|
+
const parsed = parseSchemaPath(p);
|
|
110
|
+
if (!parsed)
|
|
111
|
+
continue;
|
|
112
|
+
const pinned = PROVIDER_VERSION_OVERRIDES[parsed.provider];
|
|
113
|
+
if (pinned) {
|
|
114
|
+
// Only accept the pinned versions for an overridden provider,
|
|
115
|
+
// slotted into pin order.
|
|
116
|
+
if (pinned.includes(parsed.apiVersion)) {
|
|
117
|
+
const files = best.get(parsed.provider) ?? [];
|
|
118
|
+
files.push({ path: p, apiVersion: parsed.apiVersion });
|
|
119
|
+
files.sort((a, b) => pinned.indexOf(a.apiVersion) - pinned.indexOf(b.apiVersion));
|
|
120
|
+
best.set(parsed.provider, files);
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const existing = best.get(parsed.provider);
|
|
125
|
+
if (!existing || compareApiDates(parsed.apiVersion, existing[0].apiVersion) > 0) {
|
|
126
|
+
best.set(parsed.provider, [{ path: p, apiVersion: parsed.apiVersion }]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return best;
|
|
130
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/spec/parse.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/spec/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAiE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC1G,OAAO,EAGL,kBAAkB,IAAI,sBAAsB,EAE5C,KAAK,mBAAmB,EAIzB,MAAM,sCAAsC,CAAC;AAI9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAEhF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7E;;;;OAIG;IACH,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/spec/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAiE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC1G,OAAO,EAGL,kBAAkB,IAAI,sBAAsB,EAE5C,KAAK,mBAAmB,EAIzB,MAAM,sCAAsC,CAAC;AAI9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAEhF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7E;;;;OAIG;IACH,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAyHD;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,aAWrC,CAAC;AAEH;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,oBAAoB,CAwI1E;AA+CD,eAAO,MAAM,kBAAkB,+BAAyB,CAAC;AAMzD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIvD"}
|
|
@@ -0,0 +1,318 @@
|
|
|
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
|
+
// The app's hostname, for DNS in front of it (#4179). Dotted names read a
|
|
108
|
+
// nested field of reference()'s properties, as AWS's Endpoint.Address does.
|
|
109
|
+
"Microsoft.App/containerApps": [
|
|
110
|
+
{ name: "id", tsType: "string" },
|
|
111
|
+
{ name: "configuration.ingress.fqdn", tsType: "string" },
|
|
112
|
+
{ name: "latestRevisionFqdn", tsType: "string" },
|
|
113
|
+
{ name: "latestRevisionName", tsType: "string" },
|
|
114
|
+
{ name: "outboundIpAddresses", tsType: "string[]" },
|
|
115
|
+
{ name: "provisioningState", tsType: "string" },
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Maximum nesting depth of generated property-type interfaces.
|
|
120
|
+
*
|
|
121
|
+
* ARM schemas embed a named definition for every nested object, and fully
|
|
122
|
+
* expanding all of them per-resource produces a ~273MB declaration that is not
|
|
123
|
+
* shippable as `.d.ts`. We instead emit only the property types reachable from
|
|
124
|
+
* a resource's top-level properties within this depth; references that are
|
|
125
|
+
* deeper, or to definitions no resource property reaches, are loosened to
|
|
126
|
+
* `Record<string, unknown>`. Depth 1 keeps top-level props + their immediate
|
|
127
|
+
* nested object shapes typed; deeper ARM config trees become loose. (#438)
|
|
128
|
+
*/
|
|
129
|
+
const MAX_PROPERTY_TYPE_DEPTH = 1;
|
|
130
|
+
/**
|
|
131
|
+
* Property-type definition names that are always emitted as typed interfaces,
|
|
132
|
+
* regardless of reachability/depth — common nested ARM shapes worth typing for
|
|
133
|
+
* authoring ergonomics. Kept in sync with the azure `validate` required-names
|
|
134
|
+
* check (it asserts these survive code generation). (#438)
|
|
135
|
+
*/
|
|
136
|
+
export const CURATED_PROPERTY_TYPE_DEFS = new Set([
|
|
137
|
+
"Sku",
|
|
138
|
+
"Identity",
|
|
139
|
+
"NetworkProfile",
|
|
140
|
+
"StorageProfile",
|
|
141
|
+
"OsDisk",
|
|
142
|
+
"ImageReference",
|
|
143
|
+
"IpConfiguration",
|
|
144
|
+
"SecurityRule",
|
|
145
|
+
"SubnetProperties",
|
|
146
|
+
"SiteConfig",
|
|
147
|
+
]);
|
|
148
|
+
/**
|
|
149
|
+
* Parse an ARM schema (exploded per-resource format) into typed structures.
|
|
150
|
+
*/
|
|
151
|
+
export function parseArmSchema(data) {
|
|
152
|
+
const raw = JSON.parse(typeof data === "string" ? data : data.toString("utf-8"));
|
|
153
|
+
const resourceType = raw.resourceType;
|
|
154
|
+
const apiVersion = raw.apiVersion;
|
|
155
|
+
const resourceDef = raw.resourceDefinition;
|
|
156
|
+
const definitions = raw.definitions ?? {};
|
|
157
|
+
// Deployment scopes stamped by the fetch explode (#1545). Only carried
|
|
158
|
+
// through when the resource exists somewhere other than plain
|
|
159
|
+
// resource-group scope, so the common case stays field-free.
|
|
160
|
+
const rawScopes = raw.deployScopes;
|
|
161
|
+
const deployScopes = rawScopes && !(rawScopes.length === 1 && rawScopes[0] === "resourceGroup")
|
|
162
|
+
? rawScopes
|
|
163
|
+
: undefined;
|
|
164
|
+
const shortName = armShortName(resourceType);
|
|
165
|
+
const fakeSchema = { definitions };
|
|
166
|
+
// Parse resource-level fields and nested properties
|
|
167
|
+
const props = [];
|
|
168
|
+
const resourceLevelFields = [];
|
|
169
|
+
if (resourceDef.properties) {
|
|
170
|
+
for (const [name, prop] of Object.entries(resourceDef.properties)) {
|
|
171
|
+
if (name === "type" || name === "apiVersion")
|
|
172
|
+
continue; // These are injected
|
|
173
|
+
const unwrapped = unwrapExpressionOneOf(prop);
|
|
174
|
+
if (RESOURCE_LEVEL_FIELDS.has(name)) {
|
|
175
|
+
resourceLevelFields.push(name);
|
|
176
|
+
}
|
|
177
|
+
// If this is the "properties" container, expand its sub-properties
|
|
178
|
+
if (name === "properties" && unwrapped.properties) {
|
|
179
|
+
const innerRequired = new Set(unwrapped.required ?? []);
|
|
180
|
+
for (const [innerName, innerProp] of Object.entries(unwrapped.properties)) {
|
|
181
|
+
const innerUnwrapped = unwrapExpressionOneOf(innerProp);
|
|
182
|
+
const tsType = resolvePropertyType(innerUnwrapped, definitions, shortName);
|
|
183
|
+
props.push({
|
|
184
|
+
name: innerName,
|
|
185
|
+
tsType,
|
|
186
|
+
required: innerRequired.has(innerName),
|
|
187
|
+
description: innerUnwrapped.description,
|
|
188
|
+
enum: innerUnwrapped.enum,
|
|
189
|
+
constraints: extractConstraints(innerUnwrapped),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const tsType = resolvePropertyType(unwrapped, definitions, shortName);
|
|
195
|
+
const requiredSet = new Set(resourceDef.required ?? []);
|
|
196
|
+
props.push({
|
|
197
|
+
name,
|
|
198
|
+
tsType,
|
|
199
|
+
required: requiredSet.has(name),
|
|
200
|
+
description: unwrapped.description,
|
|
201
|
+
enum: unwrapped.enum,
|
|
202
|
+
constraints: extractConstraints(unwrapped),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Parse definitions into property types and enums
|
|
207
|
+
const propertyTypes = [];
|
|
208
|
+
const enums = [];
|
|
209
|
+
for (const [defName, def] of Object.entries(definitions)) {
|
|
210
|
+
// Skip expression-related definitions
|
|
211
|
+
if (defName === "expression" || defName.startsWith("http"))
|
|
212
|
+
continue;
|
|
213
|
+
if (isEnumDefinition(def)) {
|
|
214
|
+
enums.push({
|
|
215
|
+
name: `${shortName}_${defName}`,
|
|
216
|
+
values: def.enum,
|
|
217
|
+
});
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (def.properties) {
|
|
221
|
+
const defRequired = new Set(def.required ?? []);
|
|
222
|
+
const defProps = [];
|
|
223
|
+
for (const [propName, prop] of Object.entries(def.properties)) {
|
|
224
|
+
const unwrapped = unwrapExpressionOneOf(prop);
|
|
225
|
+
const tsType = resolvePropertyType(unwrapped, definitions, shortName);
|
|
226
|
+
defProps.push({
|
|
227
|
+
name: propName,
|
|
228
|
+
tsType,
|
|
229
|
+
required: defRequired.has(propName),
|
|
230
|
+
description: unwrapped.description,
|
|
231
|
+
enum: unwrapped.enum,
|
|
232
|
+
constraints: extractConstraints(unwrapped),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
propertyTypes.push({
|
|
236
|
+
name: `${shortName}_${defName}`,
|
|
237
|
+
specType: defName,
|
|
238
|
+
properties: defProps,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Attributes from curated map
|
|
243
|
+
const attrs = CURATED_ATTRIBUTES[resourceType] ?? [
|
|
244
|
+
{ name: "id", tsType: "string" },
|
|
245
|
+
];
|
|
246
|
+
// Bound the emitted property types to keep the generated declaration shippable (#438).
|
|
247
|
+
const boundedPropertyTypes = boundPropertyTypes(shortName, props, propertyTypes, new Set(enums.map((e) => e.name)), { maxDepth: MAX_PROPERTY_TYPE_DEPTH, curatedDefs: CURATED_PROPERTY_TYPE_DEFS });
|
|
248
|
+
// Detect tagging support (if tags field exists)
|
|
249
|
+
const hasTags = resourceLevelFields.includes("tags");
|
|
250
|
+
const tagging = hasTags
|
|
251
|
+
? { taggable: true, tagOnCreate: true, tagUpdatable: true }
|
|
252
|
+
: undefined;
|
|
253
|
+
return {
|
|
254
|
+
resource: {
|
|
255
|
+
typeName: resourceType,
|
|
256
|
+
apiVersion,
|
|
257
|
+
properties: props,
|
|
258
|
+
attributes: attrs,
|
|
259
|
+
resourceLevelFields,
|
|
260
|
+
...(tagging && { tagging }),
|
|
261
|
+
...(deployScopes && { deployScopes }),
|
|
262
|
+
},
|
|
263
|
+
propertyTypes: boundedPropertyTypes,
|
|
264
|
+
enums,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
// --- Unwrap oneOf expression wrappers ---
|
|
268
|
+
/**
|
|
269
|
+
* ARM schemas wrap real types in oneOf with an expression $ref.
|
|
270
|
+
* Detect this pattern and return the real type.
|
|
271
|
+
*/
|
|
272
|
+
function unwrapExpressionOneOf(prop) {
|
|
273
|
+
if (!prop.oneOf || prop.oneOf.length < 2)
|
|
274
|
+
return prop;
|
|
275
|
+
const nonExpr = prop.oneOf.filter((item) => {
|
|
276
|
+
if (item.$ref && EXPRESSION_REF_PATTERN.test(item.$ref))
|
|
277
|
+
return false;
|
|
278
|
+
return true;
|
|
279
|
+
});
|
|
280
|
+
if (nonExpr.length === 1) {
|
|
281
|
+
// Merge description from parent if present
|
|
282
|
+
const result = { ...nonExpr[0] };
|
|
283
|
+
if (prop.description && !result.description) {
|
|
284
|
+
result.description = prop.description;
|
|
285
|
+
}
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
return prop;
|
|
289
|
+
}
|
|
290
|
+
// --- Type resolution (delegated to core) ---
|
|
291
|
+
function resolvePropertyType(prop, definitions, shortName) {
|
|
292
|
+
const fakeDoc = { definitions };
|
|
293
|
+
return coreResolvePropertyType(prop, fakeDoc, (defName) => `${shortName}_${defName}`);
|
|
294
|
+
}
|
|
295
|
+
function extractConstraints(prop) {
|
|
296
|
+
return coreExtractConstraints(prop);
|
|
297
|
+
}
|
|
298
|
+
export const constraintsIsEmpty = coreConstraintsIsEmpty;
|
|
299
|
+
function isEnumDefinition(def) {
|
|
300
|
+
return coreIsEnumDefinition(def);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Extract short resource name:
|
|
304
|
+
* "Microsoft.Storage/storageAccounts" → "storageAccounts"
|
|
305
|
+
*/
|
|
306
|
+
export function armShortName(typeName) {
|
|
307
|
+
const slashIdx = typeName.lastIndexOf("/");
|
|
308
|
+
return slashIdx >= 0 ? typeName.slice(slashIdx + 1) : typeName;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Extract service name:
|
|
312
|
+
* "Microsoft.Storage/storageAccounts" → "Storage"
|
|
313
|
+
*/
|
|
314
|
+
export function armServiceName(typeName) {
|
|
315
|
+
const match = typeName.match(/^Microsoft\.([^/]+)/);
|
|
316
|
+
// Strip dots from hierarchical providers (e.g. "App.ContainerApps" → "AppContainerApps")
|
|
317
|
+
return match ? match[1].replace(/\./g, "") : typeName;
|
|
318
|
+
}
|