@backthread/extractor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/cluster/domain-grouping.d.ts +113 -0
- package/dist/cluster/domain-grouping.d.ts.map +1 -0
- package/dist/cluster/domain-grouping.js +277 -0
- package/dist/cluster/domain-grouping.js.map +1 -0
- package/dist/cluster/grouping-gate.d.ts +54 -0
- package/dist/cluster/grouping-gate.d.ts.map +1 -0
- package/dist/cluster/grouping-gate.js +0 -0
- package/dist/cluster/grouping-gate.js.map +1 -0
- package/dist/cluster/louvain.d.ts +88 -0
- package/dist/cluster/louvain.d.ts.map +1 -0
- package/dist/cluster/louvain.js +674 -0
- package/dist/cluster/louvain.js.map +1 -0
- package/dist/cluster/overrides.d.ts +19 -0
- package/dist/cluster/overrides.d.ts.map +1 -0
- package/dist/cluster/overrides.js +32 -0
- package/dist/cluster/overrides.js.map +1 -0
- package/dist/cluster/subsystem.d.ts +45 -0
- package/dist/cluster/subsystem.d.ts.map +1 -0
- package/dist/cluster/subsystem.js +299 -0
- package/dist/cluster/subsystem.js.map +1 -0
- package/dist/cluster/workspaces.d.ts +64 -0
- package/dist/cluster/workspaces.d.ts.map +1 -0
- package/dist/cluster/workspaces.js +739 -0
- package/dist/cluster/workspaces.js.map +1 -0
- package/dist/extract.d.ts +52 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +48 -0
- package/dist/extract.js.map +1 -0
- package/dist/framework/celery/celery.d.ts +17 -0
- package/dist/framework/celery/celery.d.ts.map +1 -0
- package/dist/framework/celery/celery.js +524 -0
- package/dist/framework/celery/celery.js.map +1 -0
- package/dist/framework/contribute-step.d.ts +38 -0
- package/dist/framework/contribute-step.d.ts.map +1 -0
- package/dist/framework/contribute-step.js +403 -0
- package/dist/framework/contribute-step.js.map +1 -0
- package/dist/framework/detect-step.d.ts +8 -0
- package/dist/framework/detect-step.d.ts.map +1 -0
- package/dist/framework/detect-step.js +54 -0
- package/dist/framework/detect-step.js.map +1 -0
- package/dist/framework/detect-util.d.ts +24 -0
- package/dist/framework/detect-util.d.ts.map +1 -0
- package/dist/framework/detect-util.js +59 -0
- package/dist/framework/detect-util.js.map +1 -0
- package/dist/framework/django/django.d.ts +22 -0
- package/dist/framework/django/django.d.ts.map +1 -0
- package/dist/framework/django/django.js +940 -0
- package/dist/framework/django/django.js.map +1 -0
- package/dist/framework/fastapi/fastapi.d.ts +27 -0
- package/dist/framework/fastapi/fastapi.d.ts.map +1 -0
- package/dist/framework/fastapi/fastapi.js +477 -0
- package/dist/framework/fastapi/fastapi.js.map +1 -0
- package/dist/framework/faststream/faststream.d.ts +19 -0
- package/dist/framework/faststream/faststream.d.ts.map +1 -0
- package/dist/framework/faststream/faststream.js +765 -0
- package/dist/framework/faststream/faststream.js.map +1 -0
- package/dist/framework/flask/flask.d.ts +17 -0
- package/dist/framework/flask/flask.d.ts.map +1 -0
- package/dist/framework/flask/flask.js +507 -0
- package/dist/framework/flask/flask.js.map +1 -0
- package/dist/framework/graphql/graphql.d.ts +17 -0
- package/dist/framework/graphql/graphql.d.ts.map +1 -0
- package/dist/framework/graphql/graphql.js +708 -0
- package/dist/framework/graphql/graphql.js.map +1 -0
- package/dist/framework/grpc/grpc.d.ts +22 -0
- package/dist/framework/grpc/grpc.d.ts.map +1 -0
- package/dist/framework/grpc/grpc.js +618 -0
- package/dist/framework/grpc/grpc.js.map +1 -0
- package/dist/framework/litestar/litestar.d.ts +17 -0
- package/dist/framework/litestar/litestar.d.ts.map +1 -0
- package/dist/framework/litestar/litestar.js +536 -0
- package/dist/framework/litestar/litestar.js.map +1 -0
- package/dist/framework/nest/nest.d.ts +19 -0
- package/dist/framework/nest/nest.d.ts.map +1 -0
- package/dist/framework/nest/nest.js +613 -0
- package/dist/framework/nest/nest.js.map +1 -0
- package/dist/framework/next/next.d.ts +22 -0
- package/dist/framework/next/next.d.ts.map +1 -0
- package/dist/framework/next/next.js +449 -0
- package/dist/framework/next/next.js.map +1 -0
- package/dist/framework/node/node.d.ts +24 -0
- package/dist/framework/node/node.d.ts.map +1 -0
- package/dist/framework/node/node.js +1009 -0
- package/dist/framework/node/node.js.map +1 -0
- package/dist/framework/orchestrator/orchestrator.d.ts +34 -0
- package/dist/framework/orchestrator/orchestrator.d.ts.map +1 -0
- package/dist/framework/orchestrator/orchestrator.js +691 -0
- package/dist/framework/orchestrator/orchestrator.js.map +1 -0
- package/dist/framework/orm/orm.d.ts +61 -0
- package/dist/framework/orm/orm.d.ts.map +1 -0
- package/dist/framework/orm/orm.js +620 -0
- package/dist/framework/orm/orm.js.map +1 -0
- package/dist/framework/python/analyze.d.ts +38 -0
- package/dist/framework/python/analyze.d.ts.map +1 -0
- package/dist/framework/python/analyze.js +98 -0
- package/dist/framework/python/analyze.js.map +1 -0
- package/dist/framework/python/py-ast.d.ts +130 -0
- package/dist/framework/python/py-ast.d.ts.map +1 -0
- package/dist/framework/python/py-ast.js +344 -0
- package/dist/framework/python/py-ast.js.map +1 -0
- package/dist/framework/python-orm/python-orm.d.ts +19 -0
- package/dist/framework/python-orm/python-orm.d.ts.map +1 -0
- package/dist/framework/python-orm/python-orm.js +516 -0
- package/dist/framework/python-orm/python-orm.js.map +1 -0
- package/dist/framework/react-native/react-native.d.ts +35 -0
- package/dist/framework/react-native/react-native.d.ts.map +1 -0
- package/dist/framework/react-native/react-native.js +1181 -0
- package/dist/framework/react-native/react-native.js.map +1 -0
- package/dist/framework/register.d.ts +2 -0
- package/dist/framework/register.d.ts.map +1 -0
- package/dist/framework/register.js +59 -0
- package/dist/framework/register.js.map +1 -0
- package/dist/framework/registry.d.ts +31 -0
- package/dist/framework/registry.d.ts.map +1 -0
- package/dist/framework/registry.js +70 -0
- package/dist/framework/registry.js.map +1 -0
- package/dist/framework/routing/expo-router.d.ts +31 -0
- package/dist/framework/routing/expo-router.d.ts.map +1 -0
- package/dist/framework/routing/expo-router.js +356 -0
- package/dist/framework/routing/expo-router.js.map +1 -0
- package/dist/framework/routing/index.d.ts +4 -0
- package/dist/framework/routing/index.d.ts.map +1 -0
- package/dist/framework/routing/index.js +16 -0
- package/dist/framework/routing/index.js.map +1 -0
- package/dist/framework/routing/next-router.d.ts +45 -0
- package/dist/framework/routing/next-router.d.ts.map +1 -0
- package/dist/framework/routing/next-router.js +378 -0
- package/dist/framework/routing/next-router.js.map +1 -0
- package/dist/framework/routing/route-tree.d.ts +73 -0
- package/dist/framework/routing/route-tree.d.ts.map +1 -0
- package/dist/framework/routing/route-tree.js +120 -0
- package/dist/framework/routing/route-tree.js.map +1 -0
- package/dist/framework/types.d.ts +230 -0
- package/dist/framework/types.d.ts.map +1 -0
- package/dist/framework/types.js +25 -0
- package/dist/framework/types.js.map +1 -0
- package/dist/graph/cross-language.d.ts +11 -0
- package/dist/graph/cross-language.d.ts.map +1 -0
- package/dist/graph/cross-language.js +123 -0
- package/dist/graph/cross-language.js.map +1 -0
- package/dist/graph/extract.d.ts +13 -0
- package/dist/graph/extract.d.ts.map +1 -0
- package/dist/graph/extract.js +79 -0
- package/dist/graph/extract.js.map +1 -0
- package/dist/graph/file-graph.d.ts +186 -0
- package/dist/graph/file-graph.d.ts.map +1 -0
- package/dist/graph/file-graph.js +427 -0
- package/dist/graph/file-graph.js.map +1 -0
- package/dist/graph/http-callsites.d.ts +14 -0
- package/dist/graph/http-callsites.d.ts.map +1 -0
- package/dist/graph/http-callsites.js +107 -0
- package/dist/graph/http-callsites.js.map +1 -0
- package/dist/graph/incremental.d.ts +122 -0
- package/dist/graph/incremental.d.ts.map +1 -0
- package/dist/graph/incremental.js +448 -0
- package/dist/graph/incremental.js.map +1 -0
- package/dist/graph/language.d.ts +31 -0
- package/dist/graph/language.d.ts.map +1 -0
- package/dist/graph/language.js +146 -0
- package/dist/graph/language.js.map +1 -0
- package/dist/graph/noise-filter.d.ts +46 -0
- package/dist/graph/noise-filter.d.ts.map +1 -0
- package/dist/graph/noise-filter.js +199 -0
- package/dist/graph/noise-filter.js.map +1 -0
- package/dist/graph/python-adapter.d.ts +36 -0
- package/dist/graph/python-adapter.d.ts.map +1 -0
- package/dist/graph/python-adapter.js +402 -0
- package/dist/graph/python-adapter.js.map +1 -0
- package/dist/graph/python-manifest.d.ts +22 -0
- package/dist/graph/python-manifest.d.ts.map +1 -0
- package/dist/graph/python-manifest.js +137 -0
- package/dist/graph/python-manifest.js.map +1 -0
- package/dist/graph/python-stdlib.d.ts +2 -0
- package/dist/graph/python-stdlib.d.ts.map +1 -0
- package/dist/graph/python-stdlib.js +53 -0
- package/dist/graph/python-stdlib.js.map +1 -0
- package/dist/graph/ts-morph-adapter.d.ts +65 -0
- package/dist/graph/ts-morph-adapter.d.ts.map +1 -0
- package/dist/graph/ts-morph-adapter.js +265 -0
- package/dist/graph/ts-morph-adapter.js.map +1 -0
- package/dist/graph/types.d.ts +37 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +50 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/infra/aws/aws-parse.d.ts +39 -0
- package/dist/infra/aws/aws-parse.d.ts.map +1 -0
- package/dist/infra/aws/aws-parse.js +149 -0
- package/dist/infra/aws/aws-parse.js.map +1 -0
- package/dist/infra/aws/aws.d.ts +6 -0
- package/dist/infra/aws/aws.d.ts.map +1 -0
- package/dist/infra/aws/aws.js +425 -0
- package/dist/infra/aws/aws.js.map +1 -0
- package/dist/infra/azure/azure-parse.d.ts +73 -0
- package/dist/infra/azure/azure-parse.d.ts.map +1 -0
- package/dist/infra/azure/azure-parse.js +178 -0
- package/dist/infra/azure/azure-parse.js.map +1 -0
- package/dist/infra/azure/azure.d.ts +12 -0
- package/dist/infra/azure/azure.d.ts.map +1 -0
- package/dist/infra/azure/azure.js +673 -0
- package/dist/infra/azure/azure.js.map +1 -0
- package/dist/infra/cloudflare/cloudflare.d.ts +50 -0
- package/dist/infra/cloudflare/cloudflare.d.ts.map +1 -0
- package/dist/infra/cloudflare/cloudflare.js +382 -0
- package/dist/infra/cloudflare/cloudflare.js.map +1 -0
- package/dist/infra/cloudflare/wrangler-parse.d.ts +6 -0
- package/dist/infra/cloudflare/wrangler-parse.d.ts.map +1 -0
- package/dist/infra/cloudflare/wrangler-parse.js +278 -0
- package/dist/infra/cloudflare/wrangler-parse.js.map +1 -0
- package/dist/infra/compose/compose-parse.d.ts +24 -0
- package/dist/infra/compose/compose-parse.d.ts.map +1 -0
- package/dist/infra/compose/compose-parse.js +77 -0
- package/dist/infra/compose/compose-parse.js.map +1 -0
- package/dist/infra/compose/compose.d.ts +12 -0
- package/dist/infra/compose/compose.d.ts.map +1 -0
- package/dist/infra/compose/compose.js +201 -0
- package/dist/infra/compose/compose.js.map +1 -0
- package/dist/infra/convex/convex-parse.d.ts +28 -0
- package/dist/infra/convex/convex-parse.d.ts.map +1 -0
- package/dist/infra/convex/convex-parse.js +74 -0
- package/dist/infra/convex/convex-parse.js.map +1 -0
- package/dist/infra/convex/convex.d.ts +18 -0
- package/dist/infra/convex/convex.d.ts.map +1 -0
- package/dist/infra/convex/convex.js +199 -0
- package/dist/infra/convex/convex.js.map +1 -0
- package/dist/infra/digitalocean/digitalocean.d.ts +33 -0
- package/dist/infra/digitalocean/digitalocean.d.ts.map +1 -0
- package/dist/infra/digitalocean/digitalocean.js +204 -0
- package/dist/infra/digitalocean/digitalocean.js.map +1 -0
- package/dist/infra/firebase/firebase.d.ts +43 -0
- package/dist/infra/firebase/firebase.d.ts.map +1 -0
- package/dist/infra/firebase/firebase.js +271 -0
- package/dist/infra/firebase/firebase.js.map +1 -0
- package/dist/infra/fly/fly-parse.d.ts +52 -0
- package/dist/infra/fly/fly-parse.d.ts.map +1 -0
- package/dist/infra/fly/fly-parse.js +114 -0
- package/dist/infra/fly/fly-parse.js.map +1 -0
- package/dist/infra/fly/fly.d.ts +15 -0
- package/dist/infra/fly/fly.d.ts.map +1 -0
- package/dist/infra/fly/fly.js +265 -0
- package/dist/infra/fly/fly.js.map +1 -0
- package/dist/infra/gcp/gcp-parse.d.ts +24 -0
- package/dist/infra/gcp/gcp-parse.d.ts.map +1 -0
- package/dist/infra/gcp/gcp-parse.js +123 -0
- package/dist/infra/gcp/gcp-parse.js.map +1 -0
- package/dist/infra/gcp/gcp.d.ts +10 -0
- package/dist/infra/gcp/gcp.d.ts.map +1 -0
- package/dist/infra/gcp/gcp.js +502 -0
- package/dist/infra/gcp/gcp.js.map +1 -0
- package/dist/infra/heroku/heroku-parse.d.ts +33 -0
- package/dist/infra/heroku/heroku-parse.d.ts.map +1 -0
- package/dist/infra/heroku/heroku-parse.js +63 -0
- package/dist/infra/heroku/heroku-parse.js.map +1 -0
- package/dist/infra/heroku/heroku.d.ts +24 -0
- package/dist/infra/heroku/heroku.d.ts.map +1 -0
- package/dist/infra/heroku/heroku.js +253 -0
- package/dist/infra/heroku/heroku.js.map +1 -0
- package/dist/infra/image-resolve.d.ts +56 -0
- package/dist/infra/image-resolve.d.ts.map +1 -0
- package/dist/infra/image-resolve.js +229 -0
- package/dist/infra/image-resolve.js.map +1 -0
- package/dist/infra/infra-step.d.ts +39 -0
- package/dist/infra/infra-step.d.ts.map +1 -0
- package/dist/infra/infra-step.js +57 -0
- package/dist/infra/infra-step.js.map +1 -0
- package/dist/infra/k8s/index.d.ts +78 -0
- package/dist/infra/k8s/index.d.ts.map +1 -0
- package/dist/infra/k8s/index.js +251 -0
- package/dist/infra/k8s/index.js.map +1 -0
- package/dist/infra/kamal/kamal-parse.d.ts +23 -0
- package/dist/infra/kamal/kamal-parse.d.ts.map +1 -0
- package/dist/infra/kamal/kamal-parse.js +66 -0
- package/dist/infra/kamal/kamal-parse.js.map +1 -0
- package/dist/infra/kamal/kamal.d.ts +17 -0
- package/dist/infra/kamal/kamal.d.ts.map +1 -0
- package/dist/infra/kamal/kamal.js +162 -0
- package/dist/infra/kamal/kamal.js.map +1 -0
- package/dist/infra/netlify/netlify.d.ts +33 -0
- package/dist/infra/netlify/netlify.d.ts.map +1 -0
- package/dist/infra/netlify/netlify.js +247 -0
- package/dist/infra/netlify/netlify.js.map +1 -0
- package/dist/infra/opentofu/opentofu.d.ts +3 -0
- package/dist/infra/opentofu/opentofu.d.ts.map +1 -0
- package/dist/infra/opentofu/opentofu.js +157 -0
- package/dist/infra/opentofu/opentofu.js.map +1 -0
- package/dist/infra/pulumi/pulumi-parse.d.ts +60 -0
- package/dist/infra/pulumi/pulumi-parse.d.ts.map +1 -0
- package/dist/infra/pulumi/pulumi-parse.js +155 -0
- package/dist/infra/pulumi/pulumi-parse.js.map +1 -0
- package/dist/infra/pulumi/pulumi.d.ts +23 -0
- package/dist/infra/pulumi/pulumi.d.ts.map +1 -0
- package/dist/infra/pulumi/pulumi.js +260 -0
- package/dist/infra/pulumi/pulumi.js.map +1 -0
- package/dist/infra/railway/railway-parse.d.ts +54 -0
- package/dist/infra/railway/railway-parse.d.ts.map +1 -0
- package/dist/infra/railway/railway-parse.js +200 -0
- package/dist/infra/railway/railway-parse.js.map +1 -0
- package/dist/infra/railway/railway.d.ts +19 -0
- package/dist/infra/railway/railway.d.ts.map +1 -0
- package/dist/infra/railway/railway.js +397 -0
- package/dist/infra/railway/railway.js.map +1 -0
- package/dist/infra/register.d.ts +2 -0
- package/dist/infra/register.d.ts.map +1 -0
- package/dist/infra/register.js +81 -0
- package/dist/infra/register.js.map +1 -0
- package/dist/infra/registry.d.ts +64 -0
- package/dist/infra/registry.d.ts.map +1 -0
- package/dist/infra/registry.js +205 -0
- package/dist/infra/registry.js.map +1 -0
- package/dist/infra/relevance.d.ts +16 -0
- package/dist/infra/relevance.d.ts.map +1 -0
- package/dist/infra/relevance.js +132 -0
- package/dist/infra/relevance.js.map +1 -0
- package/dist/infra/render/render-parse.d.ts +76 -0
- package/dist/infra/render/render-parse.d.ts.map +1 -0
- package/dist/infra/render/render-parse.js +155 -0
- package/dist/infra/render/render-parse.js.map +1 -0
- package/dist/infra/render/render.d.ts +11 -0
- package/dist/infra/render/render.d.ts.map +1 -0
- package/dist/infra/render/render.js +303 -0
- package/dist/infra/render/render.js.map +1 -0
- package/dist/infra/sst/sst-parse.d.ts +25 -0
- package/dist/infra/sst/sst-parse.d.ts.map +1 -0
- package/dist/infra/sst/sst-parse.js +45 -0
- package/dist/infra/sst/sst-parse.js.map +1 -0
- package/dist/infra/sst/sst.d.ts +19 -0
- package/dist/infra/sst/sst.d.ts.map +1 -0
- package/dist/infra/sst/sst.js +236 -0
- package/dist/infra/sst/sst.js.map +1 -0
- package/dist/infra/supabase/supabase-parse.d.ts +34 -0
- package/dist/infra/supabase/supabase-parse.d.ts.map +1 -0
- package/dist/infra/supabase/supabase-parse.js +113 -0
- package/dist/infra/supabase/supabase-parse.js.map +1 -0
- package/dist/infra/supabase/supabase.d.ts +15 -0
- package/dist/infra/supabase/supabase.d.ts.map +1 -0
- package/dist/infra/supabase/supabase.js +198 -0
- package/dist/infra/supabase/supabase.js.map +1 -0
- package/dist/infra/terraform/hcl-parse.d.ts +34 -0
- package/dist/infra/terraform/hcl-parse.d.ts.map +1 -0
- package/dist/infra/terraform/hcl-parse.js +203 -0
- package/dist/infra/terraform/hcl-parse.js.map +1 -0
- package/dist/infra/terraform/terraform.d.ts +14 -0
- package/dist/infra/terraform/terraform.d.ts.map +1 -0
- package/dist/infra/terraform/terraform.js +264 -0
- package/dist/infra/terraform/terraform.js.map +1 -0
- package/dist/infra/types.d.ts +151 -0
- package/dist/infra/types.d.ts.map +1 -0
- package/dist/infra/types.js +14 -0
- package/dist/infra/types.js.map +1 -0
- package/dist/infra/vercel/vercel-parse.d.ts +70 -0
- package/dist/infra/vercel/vercel-parse.d.ts.map +1 -0
- package/dist/infra/vercel/vercel-parse.js +194 -0
- package/dist/infra/vercel/vercel-parse.js.map +1 -0
- package/dist/infra/vercel/vercel.d.ts +30 -0
- package/dist/infra/vercel/vercel.d.ts.map +1 -0
- package/dist/infra/vercel/vercel.js +523 -0
- package/dist/infra/vercel/vercel.js.map +1 -0
- package/dist/infra/walk.d.ts +36 -0
- package/dist/infra/walk.d.ts.map +1 -0
- package/dist/infra/walk.js +85 -0
- package/dist/infra/walk.js.map +1 -0
- package/dist/safety.d.ts +13 -0
- package/dist/safety.d.ts.map +1 -0
- package/dist/safety.js +67 -0
- package/dist/safety.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +111 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +35 -0
- package/dist/version.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
// Azure InfraAdapter (v0).
|
|
2
|
+
//
|
|
3
|
+
// Multi-config cluster adapter. Parses:
|
|
4
|
+
// • ARM JSON templates (`$schema` deploymentTemplate or Microsoft.* resources[])
|
|
5
|
+
// • Bicep (light) (regex-only declarations; full DSL evaluation DEFERRED)
|
|
6
|
+
// • Azure Functions (function.json / host.json)
|
|
7
|
+
// • AKS / k8s manifests (Deployment / Service / Ingress YAML)
|
|
8
|
+
//
|
|
9
|
+
// App Service slot config beyond presence is DEFERRED.
|
|
10
|
+
//
|
|
11
|
+
// Node kinds emitted:
|
|
12
|
+
// worker — Microsoft.Web/sites (App), MS Functions, k8s Deployment,
|
|
13
|
+
// Azure Function (from function.json)
|
|
14
|
+
// queue — Microsoft.ServiceBus/*, Microsoft.EventHub/*,
|
|
15
|
+
// Microsoft.EventGrid/*, Storage Queues
|
|
16
|
+
// datastore — Microsoft.Storage/*, Microsoft.Sql/*, Microsoft.DocumentDB/*,
|
|
17
|
+
// Microsoft.Cache/Redis, Microsoft.DBfor*, Microsoft.DBforMySQL/*,
|
|
18
|
+
// Microsoft.DBforPostgreSQL/*
|
|
19
|
+
// secret-store — Microsoft.KeyVault/vaults, Microsoft.AppConfiguration/*
|
|
20
|
+
// container — Microsoft.ContainerService/*, Microsoft.ContainerInstance/*
|
|
21
|
+
// cdn — Microsoft.Cdn/*, Microsoft.FrontDoor/*,
|
|
22
|
+
// Microsoft.Network/frontDoors
|
|
23
|
+
// external-api — Microsoft.CognitiveServices/* (Paid AI)
|
|
24
|
+
// static-site — Microsoft.Web/staticSites
|
|
25
|
+
//
|
|
26
|
+
// classificationsNeeded:
|
|
27
|
+
// One per ARM/Bicep resource. provider `azure/arm` — shares the azure
|
|
28
|
+
// taxonomy with Terraform's `azurerm_*` entries (same LLM classify cache).
|
|
29
|
+
//
|
|
30
|
+
// Edges:
|
|
31
|
+
// calls — ARM rawText cross-references (resourceId / dependsOn /
|
|
32
|
+
// symbolic name), ARM → k8s AKS cluster, Service→Deployment
|
|
33
|
+
// routing, Ingress→Service routing
|
|
34
|
+
// subscribes — Azure Function with queueTrigger / serviceBusTrigger binding
|
|
35
|
+
// writes — Azure Function with queue / blob / cosmosDB output binding
|
|
36
|
+
// stores-in — Azure Function with cosmosDB trigger / blob output
|
|
37
|
+
//
|
|
38
|
+
// v0 scope notes (all DEFERRED):
|
|
39
|
+
// • Full Bicep DSL expression evaluation (parameters, variables, `if()`)
|
|
40
|
+
// • App Service deployment slot config (beyond structural node presence)
|
|
41
|
+
// • Module recursion for Bicep module declarations
|
|
42
|
+
// • Cross-manifest k8s label matching beyond selector name alignment
|
|
43
|
+
// • Azure Policy / Management Group / Monitor resources (emit with heuristic kind)
|
|
44
|
+
import { readFileSync } from 'node:fs';
|
|
45
|
+
import { basename, relative } from 'node:path';
|
|
46
|
+
import { walkRepo } from '../walk.js';
|
|
47
|
+
import { workloadSourceRoots } from '../k8s/index.js';
|
|
48
|
+
import { buildDockerfileIndex, resolveImageToSourceRoots } from '../image-resolve.js';
|
|
49
|
+
import { parseArmTemplate, parseBicep, parseFunctionJson, parseHostJson, parseAksYaml, isArmTemplate, } from './azure-parse.js';
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Filesystem walk
|
|
52
|
+
const AZURE_SKIP_DIRS = [
|
|
53
|
+
'node_modules',
|
|
54
|
+
'.git',
|
|
55
|
+
'dist',
|
|
56
|
+
'.wrangler',
|
|
57
|
+
'.terraform',
|
|
58
|
+
'.next',
|
|
59
|
+
'build',
|
|
60
|
+
'coverage',
|
|
61
|
+
'vendor',
|
|
62
|
+
'__pycache__',
|
|
63
|
+
];
|
|
64
|
+
function findAzureFiles(repoDir) {
|
|
65
|
+
const result = { arm: [], bicep: [], functionJson: [], hostJson: [], aksYaml: [] };
|
|
66
|
+
walkRepo(repoDir, {
|
|
67
|
+
skipDirs: AZURE_SKIP_DIRS,
|
|
68
|
+
onFile: (abs, e) => {
|
|
69
|
+
if (e.name.endsWith('.bicep')) {
|
|
70
|
+
result.bicep.push(abs);
|
|
71
|
+
}
|
|
72
|
+
else if (e.name === 'function.json') {
|
|
73
|
+
result.functionJson.push(abs);
|
|
74
|
+
}
|
|
75
|
+
else if (e.name === 'host.json') {
|
|
76
|
+
result.hostJson.push(abs);
|
|
77
|
+
}
|
|
78
|
+
else if (e.name.endsWith('.yaml') || e.name.endsWith('.yml')) {
|
|
79
|
+
result.aksYaml.push(abs);
|
|
80
|
+
}
|
|
81
|
+
else if (e.name.endsWith('.json') && e.name !== 'function.json' && e.name !== 'host.json') {
|
|
82
|
+
result.arm.push(abs);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
const ARM_KIND_RULES = [
|
|
89
|
+
// Functions / App Service compute
|
|
90
|
+
{ re: /^Microsoft\.Web\/sites\/functions$/i, kind: 'worker' },
|
|
91
|
+
{ re: /^Microsoft\.Web\/sites$/i, kind: 'worker' },
|
|
92
|
+
{ re: /^Microsoft\.Web\/staticSites$/i, kind: 'static-site' },
|
|
93
|
+
{ re: /^Microsoft\.Web\/serverfarms$/i, kind: 'worker' }, // App Service plan — compute host
|
|
94
|
+
// Azure Functions standalone resource type (legacy / early naming)
|
|
95
|
+
{ re: /^Microsoft\.Functions\//i, kind: 'worker' },
|
|
96
|
+
// Messaging / eventing
|
|
97
|
+
{ re: /^Microsoft\.ServiceBus\//i, kind: 'queue' },
|
|
98
|
+
{ re: /^Microsoft\.EventHub\//i, kind: 'queue' },
|
|
99
|
+
{ re: /^Microsoft\.EventGrid\//i, kind: 'queue' },
|
|
100
|
+
// Container services
|
|
101
|
+
{ re: /^Microsoft\.ContainerService\//i, kind: 'container' },
|
|
102
|
+
{ re: /^Microsoft\.ContainerInstance\//i, kind: 'container' },
|
|
103
|
+
{ re: /^Microsoft\.ContainerRegistry\//i, kind: 'container' },
|
|
104
|
+
// Storage / datastores
|
|
105
|
+
{ re: /^Microsoft\.Storage\//i, kind: 'datastore' },
|
|
106
|
+
{ re: /^Microsoft\.Sql\//i, kind: 'datastore' },
|
|
107
|
+
{ re: /^Microsoft\.DocumentDB\//i, kind: 'datastore' }, // Cosmos DB
|
|
108
|
+
{ re: /^Microsoft\.Cache\/Redis/i, kind: 'datastore' },
|
|
109
|
+
{ re: /^Microsoft\.DBforMySQL\//i, kind: 'datastore' },
|
|
110
|
+
{ re: /^Microsoft\.DBforPostgreSQL\//i, kind: 'datastore' },
|
|
111
|
+
{ re: /^Microsoft\.DBforMariaDB\//i, kind: 'datastore' },
|
|
112
|
+
{ re: /^Microsoft\.Synapse\//i, kind: 'datastore' },
|
|
113
|
+
{ re: /^Microsoft\.DataFactory\//i, kind: 'datastore' },
|
|
114
|
+
// Secret / config stores
|
|
115
|
+
{ re: /^Microsoft\.KeyVault\//i, kind: 'secret-store' },
|
|
116
|
+
{ re: /^Microsoft\.AppConfiguration\//i, kind: 'secret-store' },
|
|
117
|
+
// CDN / edge
|
|
118
|
+
{ re: /^Microsoft\.Cdn\//i, kind: 'cdn' },
|
|
119
|
+
{ re: /^Microsoft\.Network\/frontDoors/i, kind: 'cdn' },
|
|
120
|
+
{ re: /^Microsoft\.FrontDoor\//i, kind: 'cdn' },
|
|
121
|
+
// Paid AI services
|
|
122
|
+
{ re: /^Microsoft\.CognitiveServices\//i, kind: 'external-api' },
|
|
123
|
+
{ re: /^Microsoft\.MachineLearning\//i, kind: 'external-api' },
|
|
124
|
+
{ re: /^Microsoft\.MachineLearningServices\//i, kind: 'external-api' },
|
|
125
|
+
// Search
|
|
126
|
+
{ re: /^Microsoft\.Search\//i, kind: 'external-api' },
|
|
127
|
+
];
|
|
128
|
+
function heuristicArmKind(resourceType, armKind) {
|
|
129
|
+
for (const rule of ARM_KIND_RULES) {
|
|
130
|
+
if (rule.re.test(resourceType)) {
|
|
131
|
+
// Microsoft.Web/sites with kind='functionapp' stays 'worker' — same Backthread
|
|
132
|
+
// module kind as a plain App Service site. We don't demote or re-classify;
|
|
133
|
+
// the distinction is surfaced in node metadata (armKind) so the LLM
|
|
134
|
+
// classifier can annotate it further. Both are compute, both are worker.
|
|
135
|
+
void armKind; // acknowledged — no kind override needed for current rules
|
|
136
|
+
return { kind: rule.kind };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { kind: 'datastore' }; // least-bad fallback (see Terraform adapter rationale)
|
|
140
|
+
}
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Node-id helpers (adapter-local; registry prefixes `azure:`)
|
|
143
|
+
const armNodeId = (type, name) => `arm:${type.replace(/\s+/g, '_')}/${name}`;
|
|
144
|
+
const bicepNodeId = (symbolicName) => `bicep:${symbolicName}`;
|
|
145
|
+
const funcNodeId = (fnName) => `func:${fnName}`;
|
|
146
|
+
const k8sDeploymentId = (name) => `k8s:deployment:${name}`;
|
|
147
|
+
const k8sServiceId = (name) => `k8s:service:${name}`;
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Source-root helpers.
|
|
150
|
+
/** Normalize a repo-relative path: backslashes→/, collapse `.`/`./`, strip trailing `/`. */
|
|
151
|
+
function normalizeRoot(p) {
|
|
152
|
+
return p.replace(/\\/g, '/').replace(/^\.(?:\/|$)/, '').replace(/\/+$/, '');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Resolve container image refs out of an ARM/Bicep resource's raw text (ARM
|
|
156
|
+
* `"image": "…"` JSON or Bicep `image: '…'`) via the resolver. Only
|
|
157
|
+
* plain literal refs — an ARM/Bicep expression (`[concat(…)]`, `${…}`,
|
|
158
|
+
* `resourceId(…)`) is skipped (unresolvable → honest "Other", never guess).
|
|
159
|
+
*/
|
|
160
|
+
function azureImageRoots(rawText, index) {
|
|
161
|
+
if (!index)
|
|
162
|
+
return [];
|
|
163
|
+
const roots = new Set();
|
|
164
|
+
const re = /(?:"image"|\bimage)\s*:\s*['"]([^'"]+)['"]/gi;
|
|
165
|
+
let m;
|
|
166
|
+
while ((m = re.exec(rawText)) !== null) {
|
|
167
|
+
const v = m[1];
|
|
168
|
+
if (!v || /[[\]${}()]/.test(v))
|
|
169
|
+
continue; // an expression, not a literal image
|
|
170
|
+
for (const r of resolveImageToSourceRoots(v, index))
|
|
171
|
+
roots.add(r);
|
|
172
|
+
}
|
|
173
|
+
return [...roots].sort();
|
|
174
|
+
}
|
|
175
|
+
export function buildAzureGraph(inputs, root, dockerfileIndex) {
|
|
176
|
+
const nodes = new Map();
|
|
177
|
+
const edges = [];
|
|
178
|
+
const classificationsNeeded = [];
|
|
179
|
+
const seen = new Set(); // dedup edges: `${source}→${target}→${kind}`
|
|
180
|
+
const addNode = (n) => {
|
|
181
|
+
if (!nodes.has(n.id))
|
|
182
|
+
nodes.set(n.id, n);
|
|
183
|
+
};
|
|
184
|
+
const addEdge = (e) => {
|
|
185
|
+
const key = `${e.source}→${e.target}→${e.kind}`;
|
|
186
|
+
if (e.source === e.target)
|
|
187
|
+
return;
|
|
188
|
+
if (seen.has(key))
|
|
189
|
+
return;
|
|
190
|
+
seen.add(key);
|
|
191
|
+
edges.push(e);
|
|
192
|
+
};
|
|
193
|
+
// -------------------------------------------------------------------------
|
|
194
|
+
// 1. ARM resources
|
|
195
|
+
for (const r of inputs.armResources) {
|
|
196
|
+
const id = armNodeId(r.type, r.name);
|
|
197
|
+
const { kind } = heuristicArmKind(r.type, r.armKind);
|
|
198
|
+
// container resources (Container Apps/Instances) reference an
|
|
199
|
+
// image; resolve it to a build context.
|
|
200
|
+
const roots = /container/i.test(r.type) ? azureImageRoots(r.rawText, dockerfileIndex) : [];
|
|
201
|
+
addNode({
|
|
202
|
+
id,
|
|
203
|
+
label: r.name || r.type,
|
|
204
|
+
kind,
|
|
205
|
+
provenance: 'inferred', // upgraded to llm-classified by classify step
|
|
206
|
+
metadata: {
|
|
207
|
+
provider: 'azure/arm',
|
|
208
|
+
resourceType: r.type,
|
|
209
|
+
...(r.armKind !== undefined ? { armKind: r.armKind } : {}),
|
|
210
|
+
},
|
|
211
|
+
...(roots.length ? { sourceRoots: roots } : {}),
|
|
212
|
+
});
|
|
213
|
+
classificationsNeeded.push({
|
|
214
|
+
provider: 'azure/arm',
|
|
215
|
+
resourceType: r.type,
|
|
216
|
+
forNodeId: id,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// ARM cross-resource edges: scan each resource's rawText for references to
|
|
220
|
+
// other resources' names. Uses `calls` (structural dependency, honest verb
|
|
221
|
+
// without knowing the actual runtime relationship — mirrors Terraform adapter).
|
|
222
|
+
for (const src of inputs.armResources) {
|
|
223
|
+
const srcId = armNodeId(src.type, src.name);
|
|
224
|
+
for (const tgt of inputs.armResources) {
|
|
225
|
+
if (src === tgt)
|
|
226
|
+
continue;
|
|
227
|
+
const tgtId = armNodeId(tgt.type, tgt.name);
|
|
228
|
+
if (armTextReferences(src.rawText, tgt)) {
|
|
229
|
+
addEdge({ source: srcId, target: tgtId, kind: 'calls', metadata: { via: 'arm-ref' } });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// -------------------------------------------------------------------------
|
|
234
|
+
// 2. Bicep resources (light regex extraction)
|
|
235
|
+
for (const r of inputs.bicepResources) {
|
|
236
|
+
if (r.isModule) {
|
|
237
|
+
// Module declarations: emit as worker (they wrap compute), provenance inferred.
|
|
238
|
+
// We don't know the type from the path — no classificationsNeeded emitted.
|
|
239
|
+
addNode({
|
|
240
|
+
id: bicepNodeId(r.symbolicName),
|
|
241
|
+
label: r.symbolicName,
|
|
242
|
+
kind: 'worker',
|
|
243
|
+
provenance: 'inferred',
|
|
244
|
+
metadata: { provider: 'azure/bicep', bicepPath: r.type, isModule: true },
|
|
245
|
+
});
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const id = bicepNodeId(r.symbolicName);
|
|
249
|
+
const { kind } = heuristicArmKind(r.type);
|
|
250
|
+
const roots = /container/i.test(r.type) ? azureImageRoots(r.rawText, dockerfileIndex) : [];
|
|
251
|
+
addNode({
|
|
252
|
+
id,
|
|
253
|
+
label: r.symbolicName,
|
|
254
|
+
kind,
|
|
255
|
+
provenance: 'inferred',
|
|
256
|
+
metadata: {
|
|
257
|
+
provider: 'azure/arm', // intentionally azure/arm — same LLM classify cache
|
|
258
|
+
resourceType: r.type,
|
|
259
|
+
apiVersion: r.apiVersion,
|
|
260
|
+
},
|
|
261
|
+
...(roots.length ? { sourceRoots: roots } : {}),
|
|
262
|
+
});
|
|
263
|
+
classificationsNeeded.push({
|
|
264
|
+
provider: 'azure/arm',
|
|
265
|
+
resourceType: r.type,
|
|
266
|
+
forNodeId: id,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// Bicep cross-resource edges (symbolic name references in rawText).
|
|
270
|
+
for (const src of inputs.bicepResources) {
|
|
271
|
+
const srcId = bicepNodeId(src.symbolicName);
|
|
272
|
+
for (const tgt of inputs.bicepResources) {
|
|
273
|
+
if (src === tgt)
|
|
274
|
+
continue;
|
|
275
|
+
const tgtId = bicepNodeId(tgt.symbolicName);
|
|
276
|
+
if (rawTextReferencesSymbol(src.rawText, tgt.symbolicName)) {
|
|
277
|
+
addEdge({ source: srcId, target: tgtId, kind: 'calls', metadata: { via: 'bicep-ref' } });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// -------------------------------------------------------------------------
|
|
282
|
+
// 3. Azure Functions
|
|
283
|
+
for (const fn of inputs.functionJsons) {
|
|
284
|
+
const fnId = funcNodeId(fn.functionName);
|
|
285
|
+
// Every function → worker node (provenance: declared — function.json IS the declaration)
|
|
286
|
+
// the function's folder (holding function.json) is its source root.
|
|
287
|
+
const fnRoots = fn.sourceDir ? [normalizeRoot(fn.sourceDir)].filter((d) => d.length > 0) : [];
|
|
288
|
+
addNode({
|
|
289
|
+
id: fnId,
|
|
290
|
+
label: fn.functionName,
|
|
291
|
+
kind: 'worker',
|
|
292
|
+
provenance: 'declared',
|
|
293
|
+
metadata: { provider: 'azure/functions', source: 'function.json' },
|
|
294
|
+
...(fnRoots.length ? { sourceRoots: fnRoots } : {}),
|
|
295
|
+
});
|
|
296
|
+
// Bindings → edges
|
|
297
|
+
for (const binding of fn.bindings) {
|
|
298
|
+
const bType = binding.type.toLowerCase();
|
|
299
|
+
if (bType === 'httptrigger' || bType === 'http') {
|
|
300
|
+
// httpTrigger = this function IS an HTTP endpoint — no structural edge needed.
|
|
301
|
+
// http output binding = response — also no separate node.
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (bType === 'queuetrigger' || bType === 'servicebustrigger' || bType === 'eventhubstrigger') {
|
|
305
|
+
// This function SUBSCRIBES to a queue/topic.
|
|
306
|
+
const resourceName = binding.queueName ?? binding.topicName ?? binding.connection ?? bType;
|
|
307
|
+
const queueId = `queue:func-queue:${resourceName}`;
|
|
308
|
+
addNode({
|
|
309
|
+
id: queueId,
|
|
310
|
+
label: resourceName,
|
|
311
|
+
kind: 'queue',
|
|
312
|
+
provenance: 'inferred',
|
|
313
|
+
metadata: { provider: 'azure/functions', bindingType: binding.type },
|
|
314
|
+
});
|
|
315
|
+
addEdge({
|
|
316
|
+
source: fnId,
|
|
317
|
+
target: queueId,
|
|
318
|
+
kind: 'subscribes',
|
|
319
|
+
metadata: { binding: binding.name, bindingType: binding.type },
|
|
320
|
+
});
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (bType === 'timertrigger') {
|
|
324
|
+
// Timer trigger: no structural target node — cron schedule.
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (bType === 'blobtrigger') {
|
|
328
|
+
// Blob trigger: the function reads from storage.
|
|
329
|
+
const resourceName = binding.connection ?? 'blob-storage';
|
|
330
|
+
const blobId = `datastore:func-blob:${resourceName}`;
|
|
331
|
+
addNode({
|
|
332
|
+
id: blobId,
|
|
333
|
+
label: resourceName,
|
|
334
|
+
kind: 'datastore',
|
|
335
|
+
provenance: 'inferred',
|
|
336
|
+
metadata: { provider: 'azure/functions', bindingType: 'blobTrigger' },
|
|
337
|
+
});
|
|
338
|
+
addEdge({
|
|
339
|
+
source: fnId,
|
|
340
|
+
target: blobId,
|
|
341
|
+
kind: 'reads',
|
|
342
|
+
metadata: { binding: binding.name, bindingType: binding.type },
|
|
343
|
+
});
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (bType === 'cosmosdbtrigger') {
|
|
347
|
+
// CosmosDB trigger: subscribes to change feed → treat as reads from datastore.
|
|
348
|
+
const resourceName = binding.connection ?? 'cosmos-db';
|
|
349
|
+
const cosmosId = `datastore:func-cosmos:${resourceName}`;
|
|
350
|
+
addNode({
|
|
351
|
+
id: cosmosId,
|
|
352
|
+
label: resourceName,
|
|
353
|
+
kind: 'datastore',
|
|
354
|
+
provenance: 'inferred',
|
|
355
|
+
metadata: { provider: 'azure/functions', bindingType: 'cosmosDBTrigger' },
|
|
356
|
+
});
|
|
357
|
+
addEdge({
|
|
358
|
+
source: fnId,
|
|
359
|
+
target: cosmosId,
|
|
360
|
+
kind: 'reads',
|
|
361
|
+
metadata: { binding: binding.name, bindingType: binding.type },
|
|
362
|
+
});
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
// Output bindings (direction === 'out')
|
|
366
|
+
if (binding.direction === 'out') {
|
|
367
|
+
if (bType === 'queue' || bType === 'servicebus' || bType === 'eventhub') {
|
|
368
|
+
const resourceName = binding.queueName ?? binding.topicName ?? binding.connection ?? bType;
|
|
369
|
+
const queueId = `queue:func-queue:${resourceName}`;
|
|
370
|
+
addNode({
|
|
371
|
+
id: queueId,
|
|
372
|
+
label: resourceName,
|
|
373
|
+
kind: 'queue',
|
|
374
|
+
provenance: 'inferred',
|
|
375
|
+
metadata: { provider: 'azure/functions', bindingType: binding.type },
|
|
376
|
+
});
|
|
377
|
+
addEdge({
|
|
378
|
+
source: fnId,
|
|
379
|
+
target: queueId,
|
|
380
|
+
kind: 'writes',
|
|
381
|
+
metadata: { binding: binding.name, bindingType: binding.type },
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
else if (bType === 'blob' || bType === 'table') {
|
|
385
|
+
const resourceName = binding.connection ?? 'blob-storage';
|
|
386
|
+
const blobId = `datastore:func-blob:${resourceName}`;
|
|
387
|
+
addNode({
|
|
388
|
+
id: blobId,
|
|
389
|
+
label: resourceName,
|
|
390
|
+
kind: 'datastore',
|
|
391
|
+
provenance: 'inferred',
|
|
392
|
+
metadata: { provider: 'azure/functions', bindingType: binding.type },
|
|
393
|
+
});
|
|
394
|
+
addEdge({
|
|
395
|
+
source: fnId,
|
|
396
|
+
target: blobId,
|
|
397
|
+
kind: 'stores-in',
|
|
398
|
+
metadata: { binding: binding.name, bindingType: binding.type },
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
else if (bType === 'cosmosdb') {
|
|
402
|
+
const resourceName = binding.connection ?? 'cosmos-db';
|
|
403
|
+
const cosmosId = `datastore:func-cosmos:${resourceName}`;
|
|
404
|
+
addNode({
|
|
405
|
+
id: cosmosId,
|
|
406
|
+
label: resourceName,
|
|
407
|
+
kind: 'datastore',
|
|
408
|
+
provenance: 'inferred',
|
|
409
|
+
metadata: { provider: 'azure/functions', bindingType: 'cosmosDB' },
|
|
410
|
+
});
|
|
411
|
+
addEdge({
|
|
412
|
+
source: fnId,
|
|
413
|
+
target: cosmosId,
|
|
414
|
+
kind: 'stores-in',
|
|
415
|
+
metadata: { binding: binding.name, bindingType: binding.type },
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// -------------------------------------------------------------------------
|
|
422
|
+
// 4. AKS / k8s manifests
|
|
423
|
+
const k8sDeployments = new Map();
|
|
424
|
+
const k8sServices = new Map();
|
|
425
|
+
for (const manifest of inputs.k8sManifests) {
|
|
426
|
+
const kind = manifest.kind;
|
|
427
|
+
if (kind === 'Deployment' || kind === 'StatefulSet' || kind === 'DaemonSet') {
|
|
428
|
+
const id = k8sDeploymentId(manifest.name);
|
|
429
|
+
// attribute the AKS workload's code via its container image(s).
|
|
430
|
+
const roots = dockerfileIndex ? workloadSourceRoots(manifest.rawObj, dockerfileIndex) : [];
|
|
431
|
+
addNode({
|
|
432
|
+
id,
|
|
433
|
+
label: manifest.name,
|
|
434
|
+
kind: 'container',
|
|
435
|
+
provenance: 'declared',
|
|
436
|
+
metadata: {
|
|
437
|
+
provider: 'azure/aks',
|
|
438
|
+
k8sKind: kind,
|
|
439
|
+
namespace: manifest.namespace,
|
|
440
|
+
},
|
|
441
|
+
...(roots.length ? { sourceRoots: roots } : {}),
|
|
442
|
+
});
|
|
443
|
+
k8sDeployments.set(manifest.name, manifest);
|
|
444
|
+
}
|
|
445
|
+
else if (kind === 'Service') {
|
|
446
|
+
k8sServices.set(manifest.name, manifest);
|
|
447
|
+
// Service itself is routing metadata — not a separate node; becomes edges below.
|
|
448
|
+
}
|
|
449
|
+
else if (kind === 'Ingress') {
|
|
450
|
+
// Ingress → service → deployment chain.
|
|
451
|
+
for (const backend of manifest.ingressBackends ?? []) {
|
|
452
|
+
const svc = k8sServices.get(backend);
|
|
453
|
+
if (svc?.selector) {
|
|
454
|
+
// Find a Deployment that matches the selector (simple name heuristic).
|
|
455
|
+
for (const [depName] of k8sDeployments) {
|
|
456
|
+
if (Object.values(svc.selector).some((v) => v === depName || depName.startsWith(v))) {
|
|
457
|
+
addEdge({
|
|
458
|
+
source: k8sDeploymentId(manifest.name + '-ingress'),
|
|
459
|
+
target: k8sDeploymentId(depName),
|
|
460
|
+
kind: 'calls',
|
|
461
|
+
metadata: { via: 'ingress→service→deployment' },
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// Service routing: a Service with selector → calls the matching Deployment.
|
|
470
|
+
for (const [svcName, svc] of k8sServices) {
|
|
471
|
+
if (!svc.selector)
|
|
472
|
+
continue;
|
|
473
|
+
for (const [depName] of k8sDeployments) {
|
|
474
|
+
// Heuristic: if the deployment name matches the service name or any selector value.
|
|
475
|
+
if (depName === svcName ||
|
|
476
|
+
Object.values(svc.selector).some((v) => v === depName || depName.startsWith(v))) {
|
|
477
|
+
// A Service routes TO a Deployment — express as `calls`.
|
|
478
|
+
addEdge({
|
|
479
|
+
source: k8sServiceId(svcName),
|
|
480
|
+
target: k8sDeploymentId(depName),
|
|
481
|
+
kind: 'calls',
|
|
482
|
+
metadata: { via: 'k8s-service-selector' },
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// ARM → AKS cluster: ARM ContainerService/managedClusters resources `calls`
|
|
488
|
+
// k8s Deployment nodes found in the same repo — the ARM template manages them.
|
|
489
|
+
const armAksNodes = [...nodes.values()].filter((n) => n.kind === 'container' && n.provenance === 'inferred');
|
|
490
|
+
const aksDeploymentNodes = [...nodes.values()].filter((n) => n.kind === 'container' && n.provenance === 'declared');
|
|
491
|
+
if (armAksNodes.length > 0 && aksDeploymentNodes.length > 0) {
|
|
492
|
+
for (const aksArm of armAksNodes) {
|
|
493
|
+
for (const dep of aksDeploymentNodes) {
|
|
494
|
+
addEdge({ source: aksArm.id, target: dep.id, kind: 'calls', metadata: { via: 'aks-manages' } });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
root,
|
|
500
|
+
adapter: 'azure',
|
|
501
|
+
nodes: [...nodes.values()],
|
|
502
|
+
edges,
|
|
503
|
+
classificationsNeeded,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
// ---------------------------------------------------------------------------
|
|
507
|
+
// Reference scanning helpers
|
|
508
|
+
/**
|
|
509
|
+
* Determine whether an ARM resource's rawText references the target — by name,
|
|
510
|
+
* resourceId() call, or dependsOn. Bias: MISS over INVENT (same as Terraform).
|
|
511
|
+
*/
|
|
512
|
+
function armTextReferences(rawText, target) {
|
|
513
|
+
if (!target.name)
|
|
514
|
+
return false;
|
|
515
|
+
// Look for the name outside of description/comments heuristic:
|
|
516
|
+
// In JSON rawText the name always appears as a JSON string value `"<name>"`
|
|
517
|
+
// so we require quote delimiters which greatly reduces prose false positives.
|
|
518
|
+
// Also look for resourceId references that include the type or name.
|
|
519
|
+
const quotedName = `"${target.name}"`;
|
|
520
|
+
const typeRef = target.type.toLowerCase();
|
|
521
|
+
// Only fire if either the quoted name appears somewhere meaningful or
|
|
522
|
+
// a resourceId pattern with the type shows up.
|
|
523
|
+
if (rawText.includes(quotedName)) {
|
|
524
|
+
// Additional guard: the name reference must not only appear as the resource's
|
|
525
|
+
// own `"name"` key value — but we can't easily avoid that without a full JSON
|
|
526
|
+
// walk. Accept the false positive rate as acceptable for v0 (same as TF adapter).
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
// resourceId("Microsoft.X/Y", ...) references.
|
|
530
|
+
if (rawText.toLowerCase().includes(`resourceid("${typeRef}`))
|
|
531
|
+
return true;
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Whether rawText references the Bicep symbolic name as a traversal.
|
|
536
|
+
* Requires the name to appear at a word boundary (not as a substring of
|
|
537
|
+
* another identifier).
|
|
538
|
+
*/
|
|
539
|
+
function rawTextReferencesSymbol(rawText, symbolicName) {
|
|
540
|
+
if (!symbolicName)
|
|
541
|
+
return false;
|
|
542
|
+
// Bicep references look like `symbolicName.property` or `${symbolicName.x}`.
|
|
543
|
+
const re = new RegExp(`(?<![\\w])${symbolicName}(?=\\.)`, '');
|
|
544
|
+
return re.test(rawText);
|
|
545
|
+
}
|
|
546
|
+
// ---------------------------------------------------------------------------
|
|
547
|
+
// Detect helpers: identify Azure-bearing files without reading them
|
|
548
|
+
function looksLikeArmTemplate(text) {
|
|
549
|
+
const parsed = tryParseJson(text);
|
|
550
|
+
if (parsed === null)
|
|
551
|
+
return false;
|
|
552
|
+
return isArmTemplate(parsed);
|
|
553
|
+
}
|
|
554
|
+
function tryParseJson(text) {
|
|
555
|
+
try {
|
|
556
|
+
return JSON.parse(text);
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// ---------------------------------------------------------------------------
|
|
563
|
+
// InfraAdapter
|
|
564
|
+
/** Detect: true if the repo contains at least one recognizable Azure surface. */
|
|
565
|
+
async function detectAzure(repoDir) {
|
|
566
|
+
const files = findAzureFiles(repoDir);
|
|
567
|
+
if (files.bicep.length > 0)
|
|
568
|
+
return true;
|
|
569
|
+
if (files.functionJson.length > 0)
|
|
570
|
+
return true;
|
|
571
|
+
// Check for ARM templates among JSON files (don't read all JSON — check a few).
|
|
572
|
+
for (const file of files.arm.slice(0, 20)) {
|
|
573
|
+
try {
|
|
574
|
+
const text = readFileSync(file, 'utf8');
|
|
575
|
+
if (looksLikeArmTemplate(text))
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// skip unreadable
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// Check for k8s manifests among YAML files (check a few).
|
|
583
|
+
for (const file of files.aksYaml.slice(0, 20)) {
|
|
584
|
+
try {
|
|
585
|
+
const text = readFileSync(file, 'utf8');
|
|
586
|
+
const docs = parseAksYaml(text);
|
|
587
|
+
if (docs.length > 0)
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
// skip
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
/** Extract: walk all Azure-relevant files and build the InfraGraph. */
|
|
597
|
+
async function extractAzure(repoDir) {
|
|
598
|
+
const files = findAzureFiles(repoDir);
|
|
599
|
+
const inputs = {
|
|
600
|
+
armResources: [],
|
|
601
|
+
bicepResources: [],
|
|
602
|
+
functionJsons: [],
|
|
603
|
+
k8sManifests: [],
|
|
604
|
+
};
|
|
605
|
+
// ARM JSON
|
|
606
|
+
for (const file of files.arm) {
|
|
607
|
+
try {
|
|
608
|
+
const text = readFileSync(file, 'utf8');
|
|
609
|
+
const resources = parseArmTemplate(text);
|
|
610
|
+
if (resources.length > 0)
|
|
611
|
+
inputs.armResources.push(...resources);
|
|
612
|
+
}
|
|
613
|
+
catch (err) {
|
|
614
|
+
console.warn(` [azure] skipping unparseable ${relative(repoDir, file)}: ${err.message}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
// Bicep
|
|
618
|
+
for (const file of files.bicep) {
|
|
619
|
+
try {
|
|
620
|
+
const text = readFileSync(file, 'utf8');
|
|
621
|
+
inputs.bicepResources.push(...parseBicep(text));
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
console.warn(` [azure] skipping unparseable ${relative(repoDir, file)}: ${err.message}`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Azure Functions
|
|
628
|
+
for (const file of files.functionJson) {
|
|
629
|
+
try {
|
|
630
|
+
const text = readFileSync(file, 'utf8');
|
|
631
|
+
// Function name = parent directory name.
|
|
632
|
+
const fnDirAbs = file.slice(0, -'/function.json'.length);
|
|
633
|
+
const fnName = basename(fnDirAbs);
|
|
634
|
+
const parsed = parseFunctionJson(text, fnName);
|
|
635
|
+
if (parsed) {
|
|
636
|
+
// the function's folder (repo-relative) is its source root.
|
|
637
|
+
parsed.sourceDir = (relative(repoDir, fnDirAbs) || '').split('\\').join('/');
|
|
638
|
+
inputs.functionJsons.push(parsed);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
catch (err) {
|
|
642
|
+
console.warn(` [azure] skipping unparseable ${relative(repoDir, file)}: ${err.message}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
// host.json presence is a detect signal only; we don't need to parse it for topology.
|
|
646
|
+
for (const file of files.hostJson) {
|
|
647
|
+
try {
|
|
648
|
+
parseHostJson(readFileSync(file, 'utf8'));
|
|
649
|
+
}
|
|
650
|
+
catch (err) {
|
|
651
|
+
console.warn(` [azure] skipping unparseable ${relative(repoDir, file)}: ${err.message}`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// AKS YAML
|
|
655
|
+
for (const file of files.aksYaml) {
|
|
656
|
+
try {
|
|
657
|
+
const text = readFileSync(file, 'utf8');
|
|
658
|
+
const manifests = parseAksYaml(text);
|
|
659
|
+
inputs.k8sManifests.push(...manifests);
|
|
660
|
+
}
|
|
661
|
+
catch (err) {
|
|
662
|
+
console.warn(` [azure] skipping unparseable ${relative(repoDir, file)}: ${err.message}`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
// Dockerfile index for AKS workload image→source attribution.
|
|
666
|
+
return buildAzureGraph(inputs, repoDir, buildDockerfileIndex(repoDir));
|
|
667
|
+
}
|
|
668
|
+
export const azureAdapter = {
|
|
669
|
+
name: 'azure',
|
|
670
|
+
detect: detectAzure,
|
|
671
|
+
extract: extractAzure,
|
|
672
|
+
};
|
|
673
|
+
//# sourceMappingURL=azure.js.map
|