import YAML from 'yaml'; export { ParsedHandler, parseHandler } from '@suss/ir-core'; /** * Parses a CloudFormation or SAM template file into a plain object, * resolving the shorthand YAML tags CFN uses for intrinsics. * * The contract readers and the code-side handler pairing both load * templates through this module, so YAML and JSON parsing and intrinsic * tag handling are written once. */ interface CloudFormationResource { Type?: string; Properties?: Record; Metadata?: Record; } interface CloudFormationTemplate { /** * SAM `Globals` block. Section keys (`Function`, `Api`, `HttpApi`) * contain defaults that individual resources inherit unless they * override the property themselves. */ Globals?: Record>; Resources?: Record; } declare const CLOUDFORMATION_YAML_TAGS: YAML.CollectionTag[]; /** * Load a CloudFormation or SAM template from disk. The format comes from * the extension: `.json` parses as JSON, and everything else (including * `.yaml`, `.yml`, and `.template`) goes through the YAML parser with the * intrinsic tags registered. * * Throws when the file is missing or the parsed value isn't an object. A * malformed manifest is a load-time error rather than an empty result. */ declare function loadCloudFormationTemplate(templatePath: string): CloudFormationTemplate; interface LoadedTemplate { template: CloudFormationTemplate; /** The 1-based line each resource's logical id is written on. */ resourceLines: Map; } /** `loadCloudFormationTemplate`, plus the line each resource starts on. */ declare function loadCloudFormationDocument(templatePath: string): LoadedTemplate; /** * The line each key under `Resources` is written on. JSON is valid YAML, * so one reader covers both formats. A text that does not parse gives * an empty map, and its summaries keep line 1. */ declare function readResourceLines(raw: string): Map; /** * CloudFormation references come out of the parser in four forms: * - { Ref: "LogicalId" } * - { "Fn::GetAtt": ["LogicalId", "Attr"] } * - { "Fn::GetAtt": "LogicalId.Attr" }, the full-form spelling of the * `!GetAtt` short form, which templates written by hand still use * - the bare logical id when the parser doesn't recognise the YAML tag * * Every CloudFormation property that points at another resource accepts * all four, so anything reading such a property should go through here * instead of matching a single form. */ declare function refTarget(value: unknown): string | null; /** * Works out which GraphQL field each Lambda serves. * * AppSync does not wire a field to a handler in code. The template does it, * over three hops: a resolver points at a pipeline function, the function * points at a data source, and the data source points at a Lambda. Both ways * of writing that are read here: the raw AWS::AppSync::* resources, and the * SAM AWS::Serverless::GraphQLApi shorthand whose DataSources, Functions and * Resolvers blocks the transform expands into those same resources. * * Like the rest of this package, it reports only what the template says. * Whether a field has an implementation, and whether that implementation * matches the schema, is left to the reader that consumes these bindings. */ /** One GraphQL field and the Lambdas behind it. */ interface AppSyncResolverBinding { typeName: string; fieldName: string; /** * Every Lambda a request for this field runs, in pipeline order. * Usually one. A pipeline can chain several, and nothing in the * template says which of them produces the value: the first is as * often an auth step as it is the resolver, and the last is as often * a formatter. So all of them are reported, and a reader that needs * to pick one decides for itself. * * Empty when the field is served by something other than a Lambda. */ lambdaFunctionLogicalIds: string[]; } declare function readAppSyncResolvers(template: CloudFormationTemplate): AppSyncResolverBinding[]; /** A name only means something next to the API it was declared in. */ declare function apiScopedKey(apiId: string | null, name: string): string; /** * Resolves the references AWS manifests use to point at a queue, a topic, * or a bucket, down to the channel string suss keys the boundary on. * * Every manifest language that targets AWS writes a reference one of three * ways: as a CFN intrinsic (`!Ref X`, `!GetAtt X.Arn`), as a plain ARN * string, or as a bare name. The CloudFormation reader and the Serverless * Framework reader both resolve them here, so a queue written two different * ways in two manifest languages still ends up on one channel. */ /** * Resolve a reference (`!Ref X`, `!GetAtt X.Arn`, plain string ARN) to * the referenced resource's channel string. `service` is the ARN * segment a plain string is checked against (`"sqs"`, `"sns"`, `"s3"`), * so a queue ARN, a topic ARN, and a bucket ARN all resolve through the * same code. * * Returns null for any other intrinsic, such as `Fn::ImportValue` or * `Fn::Join`, since resolving those needs a stack this package does not * read. A `Ref` to a parameter comes back as the parameter's name. */ declare function resolveResourceChannel(value: unknown, service: string): string | null; /** * Resolve a Queue reference (`!Ref X`, `!GetAtt X.Arn`, plain string * ARN) to the queue's channel string. */ declare function resolveQueueChannel(value: unknown): string | null; /** * Resolve a Topic reference (`!Ref X`, `!GetAtt X.Arn`, plain string * ARN) to the topic's channel string. */ declare function resolveTopicChannel(value: unknown): string | null; /** * Resolve a Bucket reference (`!Ref X`, `!GetAtt X.Arn`, plain string * ARN) to the bucket's channel string. */ declare function resolveBucketChannel(value: unknown): string | null; /** * Reads what an EventBridge rule says about the events it matches, and * which bus those events arrive on. * * A rule's EventPattern and its EventBusName are written the same way in a * CloudFormation template and in a serverless.yml eventBridge event, so * both readers reduce them here. */ /** A pattern field that is not a plain list of strings. */ type UnsettledPatternField = "detail-type" | "source"; /** * What a rule's EventPattern says about the two fields a send states. * A field the pattern leaves out matches every value, so it has no * entry here. A field written as anything but a list of strings, such * as a prefix filter, is listed in `unsettled`. */ interface EventPatternFields { detailTypes?: string[]; sources?: string[]; unsettled: UnsettledPatternField[]; /** * The pattern also filters on a field a send does not state, such as * `detail`, so a send that agrees on source and detail-type still * reaches the rule only for some events. */ filtersOtherFields: boolean; /** * A source starts with `aws.`. Only AWS services send those, and * PutEvents refuses them from anyone else. */ emittedByAws: boolean; } /** Null when the rule declares no EventPattern at all. */ declare function readEventPattern(pattern: unknown): EventPatternFields | null; /** * Resolve a rule's EventBusName to the channel's bus token: the CFN * logical id when it's a Ref/GetAtt, the name segment of an event-bus * ARN, the literal string otherwise, or "default" when it's left out * (the EventBridge default event bus). */ declare function resolveEventBusToken(value: unknown): string; /** * Folds the SAM `Globals` section into the resources that inherit from it, * so every reader sees one set of properties per resource and none of them * has to know that `Globals` exists. * * SAM applies a section's properties to each resource of the matching type, * and the resource's own value wins where both declare one, per * https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html * * That page has the combining rules too. A map is merged key by key, a list * becomes the section's entries followed by the resource's, and anything * else is replaced outright. Because maps merge by key, a function that sets * one environment variable of its own still gets the section's others. */ /** * The template's resources with each `Globals` section already applied * to the resources it covers. A resource whose type no section covers, * or a template with no `Globals`, comes back untouched. */ declare function resourcesWithGlobals(template: CloudFormationTemplate): Record; /** * Per function logical id, the environment variables the `Function` * section supplies that the function does not declare for itself. A * consumer that treats a function's environment as that function's * contract needs the difference, because every function in the document * receives these variables. */ declare function inheritedEnvVars(template: CloudFormationTemplate): Record; /** * Reads a template and every child template it embeds, as a list of * documents. * * A stack resource points at another template, and CloudFormation deploys that * template's resources alongside the parent's. Each document keeps its own * namespace, its own SAM `Globals` section, and its own directory, so the tree * stays a list of documents. Merging them would apply one document's defaults * to another's resources, and let two logical ids spelled the same way collide. * * A child's reference to one of its own parameters is not followed to what the * parent bound, and a `Fn::GetAtt` on a stack resource is not followed to the * child's output. Both point at no resource, like any other unresolved reference. */ /** * How deep the reader follows children. CloudFormation itself allows * deeper nesting; a template chain this long in a repository is far * more likely to be a mistake than a design, and the limit is reported * rather than applied silently. */ declare const MAX_STACK_DEPTH = 10; /** One template file, and where it lives in the tree that reached it. */ interface TemplateDocument { /** Absolute path of the file this document was read from. */ path: string; /** * Logical ids of the stack resources leading from the root document * down to this one. Empty for the root. */ stackPath: string[]; template: CloudFormationTemplate; /** The 1-based line each resource's logical id is written on. */ resourceLines: Map; /** * The resource each of this document's parameters points at, for a * parameter the parent stack sets to a reference. The id is qualified * by its own document's stack path, so a child's `!Ref ApiId` can be * read as the API its parent declares. Empty for the root. */ parameterTargets: Map; } /** * The qualified logical id a reference in this document points at: the * resource the parent passed in as a parameter, or the document's own. * Null for a value only known at deploy time. */ declare function documentRef(document: Pick, value: unknown): string | null; /** * Why a child template was not read. Every value is a limit on what the * reader could reach, so a consumer should report it as a read that * stopped rather than treat the child as an empty template. */ type UnfollowedReason = "remoteUrl" | "notALiteralPath" | "fileMissing" | "unreadable" | "cycle" | "depthLimit"; /** A child template the reader could not open, and what stopped it. */ interface UnfollowedStack { /** Absolute path of the document declaring the stack resource. */ declaredIn: string; /** Stack path of the child, ending in this stack resource's logical id. */ stackPath: string[]; /** The template location as the document writes it, when it is a string. */ templateUrl: string | null; /** * The file the location resolved to, for a location that points into * this repository. Null for a remote URL, or for one nothing static * can resolve. A caller that hashes what it read uses this to notice * the day a missing or unparseable child becomes readable. */ templatePath: string | null; reason: UnfollowedReason; /** One phrase saying what stopped the read, for a message to a person. */ detail: string; } interface TemplateTree { /** The root document first, then every child that could be read. */ documents: TemplateDocument[]; unfollowed: UnfollowedStack[]; } /** * The root template plus every template it embeds, directly or through * another child. * * A missing or malformed root template throws, matching * `loadCloudFormationTemplate`: the caller asked for that file by name. * A child that cannot be read never throws; it lands in `unfollowed` so * the caller can report which one and why. */ declare function loadTemplateTree(rootPath: string): TemplateTree; /** * A logical id qualified by the stack path that reaches it, so two * documents that both declare `HandlerFunction` refer to two different * deployed things. A resource in the root document keeps its bare id, * which is also what CloudFormation shows for it. */ declare function qualifiedLogicalId(stackPath: string[], logicalId: string): string; /** A line saying which child the reader could not open, and why. */ declare function unfollowedStackMessage(stack: UnfollowedStack): string; /** * Reads AWS::Serverless::Function resources into a code-facing view: * which module and export each function points at, and which route and * non-route Events it declares. * * The contract readers expand SAM Events into API Gateway route summaries. * The code-side pairing needs something else from the template: where each * handler's implementation is, and which routes it is bound to. CodeUri * inheritance from Globals and the difference between Api and HttpApi * Events are handled here, so a framework pack does not work them out again. */ /** A route-bearing SAM Event (`Type: Api` or `Type: HttpApi`). */ interface ServerlessHttpRoute { /** Key of the event in the function's `Events` map. */ eventId: string; /** "Api" (REST / API Gateway v1) or "HttpApi" (API Gateway v2). */ eventType: "Api" | "HttpApi"; /** HTTP method, uppercased. `"ANY"` for the catch-all. */ method: string; /** Route path with `{param}` templating, verbatim from the template. */ path: string; /** Logical id of the API resource the event targets, or null when implicit. */ apiId: string | null; } /** * A recognized SAM Event that isn't an HTTP route (SQS, Schedule, SNS, * S3, and so on). These are reported rather than dropped, so the code * side can account for handlers it recognizes but deliberately doesn't * extract as HTTP. */ interface ServerlessNonHttpEvent { eventId: string; /** Event `Type` verbatim (e.g. "SQS", "Schedule", "SNS"). */ eventType: string; } interface ServerlessFunctionInfo { /** Logical id of the AWS::Serverless::Function resource. */ logicalId: string; /** Raw Handler string, e.g. "src/handlers/confirmToken.handler". */ handler: string; /** Module-path portion of the handler, as `parseHandler` splits it. */ modulePath: string; /** The function the handler points at, as `parseHandler` splits it. */ exportName: string; /** * CodeUri base directory the handler resolves against: the function's * own CodeUri, else the `Globals.Function.CodeUri` default, else ".". */ codeUri: string; /** The `Runtime`, from the function or Globals, when the template states one. */ runtime?: string; httpRoutes: ServerlessHttpRoute[]; nonHttpEvents: ServerlessNonHttpEvent[]; } /** * Read every AWS::Serverless::Function resource in the template into a * code-facing `ServerlessFunctionInfo`. Functions without a parseable * Handler are skipped, since there's no export to bind. Functions with * no Events still appear, with both event lists empty, so a consumer * can see them. */ declare function readServerlessFunctions(template: CloudFormationTemplate): ServerlessFunctionInfo[]; export { type AppSyncResolverBinding, CLOUDFORMATION_YAML_TAGS, type CloudFormationResource, type CloudFormationTemplate, type EventPatternFields, type LoadedTemplate, MAX_STACK_DEPTH, type ServerlessFunctionInfo, type ServerlessHttpRoute, type ServerlessNonHttpEvent, type TemplateDocument, type TemplateTree, type UnfollowedReason, type UnfollowedStack, type UnsettledPatternField, apiScopedKey, documentRef, inheritedEnvVars, loadCloudFormationDocument, loadCloudFormationTemplate, loadTemplateTree, qualifiedLogicalId, readAppSyncResolvers, readEventPattern, readResourceLines, readServerlessFunctions, refTarget, resolveBucketChannel, resolveEventBusToken, resolveQueueChannel, resolveResourceChannel, resolveTopicChannel, resourcesWithGlobals, unfollowedStackMessage };