@suss/manifest-aws 0.3.2 → 0.4.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/dist/index.cjs +114 -3
- package/dist/index.d.cts +176 -33
- package/dist/index.d.ts +176 -33
- package/dist/index.js +108 -3
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -39,7 +39,13 @@ __export(index_exports, {
|
|
|
39
39
|
qualifiedLogicalId: () => qualifiedLogicalId,
|
|
40
40
|
readAppSyncResolvers: () => readAppSyncResolvers,
|
|
41
41
|
readServerlessFunctions: () => readServerlessFunctions,
|
|
42
|
+
reduceEventPattern: () => reduceEventPattern,
|
|
42
43
|
refTarget: () => refTarget,
|
|
44
|
+
resolveBucketChannel: () => resolveBucketChannel,
|
|
45
|
+
resolveEventBusToken: () => resolveEventBusToken,
|
|
46
|
+
resolveQueueChannel: () => resolveQueueChannel,
|
|
47
|
+
resolveResourceChannel: () => resolveResourceChannel,
|
|
48
|
+
resolveTopicChannel: () => resolveTopicChannel,
|
|
43
49
|
resourcesWithGlobals: () => resourcesWithGlobals,
|
|
44
50
|
unfollowedStackMessage: () => unfollowedStackMessage
|
|
45
51
|
});
|
|
@@ -63,7 +69,11 @@ var getAttParts = (value) => {
|
|
|
63
69
|
return value.map(String);
|
|
64
70
|
}
|
|
65
71
|
const text = String(value);
|
|
66
|
-
|
|
72
|
+
const dot = text.indexOf(".");
|
|
73
|
+
if (dot === -1) {
|
|
74
|
+
return [text];
|
|
75
|
+
}
|
|
76
|
+
return [text.slice(0, dot), text.slice(dot + 1)];
|
|
67
77
|
};
|
|
68
78
|
var CLOUDFORMATION_YAML_TAGS = [
|
|
69
79
|
...everyNodeKind("!Ref", (value) => ({ Ref: value })),
|
|
@@ -115,8 +125,7 @@ function refTarget(value) {
|
|
|
115
125
|
return getAtt[0];
|
|
116
126
|
}
|
|
117
127
|
if (typeof getAtt === "string") {
|
|
118
|
-
|
|
119
|
-
return dot === -1 ? getAtt : getAtt.slice(0, dot);
|
|
128
|
+
return getAttParts(getAtt)[0] ?? null;
|
|
120
129
|
}
|
|
121
130
|
return null;
|
|
122
131
|
}
|
|
@@ -280,6 +289,102 @@ function stringOf(value) {
|
|
|
280
289
|
return typeof value === "string" ? value : null;
|
|
281
290
|
}
|
|
282
291
|
|
|
292
|
+
// src/arn.ts
|
|
293
|
+
function resolveResourceChannel(value, service) {
|
|
294
|
+
if (value === null || value === void 0) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
if (typeof value === "string") {
|
|
298
|
+
const resource = resolveArnResource(value, service);
|
|
299
|
+
return resource ?? value;
|
|
300
|
+
}
|
|
301
|
+
return refTarget(value);
|
|
302
|
+
}
|
|
303
|
+
function resolveArnResource(value, service) {
|
|
304
|
+
const parts = value.split(":");
|
|
305
|
+
if (parts[0] !== "arn" || parts[2] !== service) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
const region = parts[3];
|
|
309
|
+
const account = parts[4];
|
|
310
|
+
const resource = parts.slice(5).join(":");
|
|
311
|
+
if (service === "s3") {
|
|
312
|
+
if (region !== "" || account !== "" || resource === "") {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
const slash = resource.indexOf("/");
|
|
316
|
+
const bucket = slash === -1 ? resource : resource.slice(0, slash);
|
|
317
|
+
return bucket === "" ? null : bucket;
|
|
318
|
+
}
|
|
319
|
+
if (!region || !account || resource === "") {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return resource;
|
|
323
|
+
}
|
|
324
|
+
function resolveQueueChannel(value) {
|
|
325
|
+
return resolveResourceChannel(value, "sqs");
|
|
326
|
+
}
|
|
327
|
+
function resolveTopicChannel(value) {
|
|
328
|
+
return resolveResourceChannel(value, "sns");
|
|
329
|
+
}
|
|
330
|
+
function resolveBucketChannel(value) {
|
|
331
|
+
return resolveResourceChannel(value, "s3");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/eventPattern.ts
|
|
335
|
+
function reduceEventPattern(pattern) {
|
|
336
|
+
if (pattern === null || typeof pattern !== "object") {
|
|
337
|
+
return {
|
|
338
|
+
kind: "unresolvable",
|
|
339
|
+
reason: "rule declares no EventPattern"
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const detailType = pattern["detail-type"];
|
|
343
|
+
if (detailType === void 0) {
|
|
344
|
+
return {
|
|
345
|
+
kind: "unresolvable",
|
|
346
|
+
reason: "EventPattern has no detail-type; v0 reduces routing to exact detail-type match only"
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (!Array.isArray(detailType)) {
|
|
350
|
+
return {
|
|
351
|
+
kind: "unresolvable",
|
|
352
|
+
reason: "detail-type is not a plain array of string literals"
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const detailTypes = [];
|
|
356
|
+
for (const entry of detailType) {
|
|
357
|
+
if (typeof entry !== "string") {
|
|
358
|
+
return {
|
|
359
|
+
kind: "unresolvable",
|
|
360
|
+
reason: "detail-type contains a content filter (prefix / anything-but / etc.); pattern subsumption is out of v0 scope"
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
detailTypes.push(entry);
|
|
364
|
+
}
|
|
365
|
+
if (detailTypes.length === 0) {
|
|
366
|
+
return { kind: "unresolvable", reason: "detail-type array is empty" };
|
|
367
|
+
}
|
|
368
|
+
return { kind: "exact", detailTypes };
|
|
369
|
+
}
|
|
370
|
+
function resolveEventBusToken(value) {
|
|
371
|
+
if (value === null || value === void 0) {
|
|
372
|
+
return "default";
|
|
373
|
+
}
|
|
374
|
+
if (typeof value === "object") {
|
|
375
|
+
const logicalId = refTarget(value);
|
|
376
|
+
return logicalId ?? "default";
|
|
377
|
+
}
|
|
378
|
+
if (typeof value === "string") {
|
|
379
|
+
const arnMatch = value.match(/:event-bus\/(.+)$/);
|
|
380
|
+
if (arnMatch !== null) {
|
|
381
|
+
return arnMatch[1];
|
|
382
|
+
}
|
|
383
|
+
return value;
|
|
384
|
+
}
|
|
385
|
+
return "default";
|
|
386
|
+
}
|
|
387
|
+
|
|
283
388
|
// src/globals.ts
|
|
284
389
|
var GLOBALS_SECTION_TYPES = {
|
|
285
390
|
Api: "AWS::Serverless::Api",
|
|
@@ -594,7 +699,13 @@ function readServerlessFunctions(template) {
|
|
|
594
699
|
qualifiedLogicalId,
|
|
595
700
|
readAppSyncResolvers,
|
|
596
701
|
readServerlessFunctions,
|
|
702
|
+
reduceEventPattern,
|
|
597
703
|
refTarget,
|
|
704
|
+
resolveBucketChannel,
|
|
705
|
+
resolveEventBusToken,
|
|
706
|
+
resolveQueueChannel,
|
|
707
|
+
resolveResourceChannel,
|
|
708
|
+
resolveTopicChannel,
|
|
598
709
|
resourcesWithGlobals,
|
|
599
710
|
unfollowedStackMessage
|
|
600
711
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import YAML from 'yaml';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* templateLoader.ts parses a CloudFormation or SAM template file into a
|
|
5
|
+
* plain object, resolving the shorthand YAML tags CFN uses for intrinsics.
|
|
6
|
+
*
|
|
7
|
+
* This is the one place that turns a template on disk into data. The
|
|
8
|
+
* summary-generation paths and the code-side handler pairing both read
|
|
9
|
+
* through it, so the YAML and JSON parsing, along with the handling of
|
|
10
|
+
* intrinsic tags, lives here instead of being repeated per consumer.
|
|
11
|
+
*/
|
|
12
|
+
|
|
3
13
|
interface CloudFormationResource {
|
|
4
14
|
Type?: string;
|
|
5
15
|
Properties?: Record<string, unknown>;
|
|
@@ -8,7 +18,7 @@ interface CloudFormationResource {
|
|
|
8
18
|
interface CloudFormationTemplate {
|
|
9
19
|
/**
|
|
10
20
|
* SAM `Globals` block. Section keys (`Function`, `Api`, `HttpApi`)
|
|
11
|
-
*
|
|
21
|
+
* contain defaults that individual resources inherit unless they
|
|
12
22
|
* override the property themselves.
|
|
13
23
|
*/
|
|
14
24
|
Globals?: Record<string, Record<string, unknown>>;
|
|
@@ -16,29 +26,44 @@ interface CloudFormationTemplate {
|
|
|
16
26
|
}
|
|
17
27
|
declare const CLOUDFORMATION_YAML_TAGS: YAML.CollectionTag[];
|
|
18
28
|
/**
|
|
19
|
-
* Load a CloudFormation
|
|
20
|
-
* extension
|
|
21
|
-
* `.yaml
|
|
29
|
+
* Load a CloudFormation or SAM template from disk. The format comes from
|
|
30
|
+
* the extension: `.json` parses as JSON, and everything else (including
|
|
31
|
+
* `.yaml`, `.yml`, and `.template`) goes through the YAML parser with the
|
|
22
32
|
* intrinsic tags registered.
|
|
23
33
|
*
|
|
24
|
-
* Throws when the file is missing or the parsed value isn't an object
|
|
25
|
-
*
|
|
34
|
+
* Throws when the file is missing or the parsed value isn't an object. A
|
|
35
|
+
* malformed manifest is a load-time error rather than an empty result.
|
|
26
36
|
*/
|
|
27
37
|
declare function loadCloudFormationTemplate(templatePath: string): CloudFormationTemplate;
|
|
28
38
|
/**
|
|
29
|
-
* CloudFormation references
|
|
39
|
+
* CloudFormation references come out of the parser in four forms:
|
|
30
40
|
* - { Ref: "LogicalId" }
|
|
31
41
|
* - { "Fn::GetAtt": ["LogicalId", "Attr"] }
|
|
32
42
|
* - { "Fn::GetAtt": "LogicalId.Attr" }, the full-form spelling of the
|
|
33
43
|
* `!GetAtt` short form, which templates written by hand still use
|
|
34
44
|
* - the bare logical id when the parser doesn't recognise the YAML tag
|
|
35
45
|
*
|
|
36
|
-
* Every CloudFormation property that
|
|
37
|
-
*
|
|
38
|
-
*
|
|
46
|
+
* Every CloudFormation property that points at another resource accepts
|
|
47
|
+
* all four, so anything reading such a property should go through here
|
|
48
|
+
* instead of matching a single form.
|
|
39
49
|
*/
|
|
40
50
|
declare function refTarget(value: unknown): string | null;
|
|
41
51
|
|
|
52
|
+
/**
|
|
53
|
+
* appsyncResolvers.ts works out which GraphQL field each Lambda serves.
|
|
54
|
+
*
|
|
55
|
+
* AppSync does not wire a field to a handler in code. The template does it,
|
|
56
|
+
* over three hops: a resolver points at a pipeline function, the function
|
|
57
|
+
* points at a data source, and the data source points at a Lambda. Both ways
|
|
58
|
+
* of writing that are covered, the raw AWS::AppSync::* resources and the SAM
|
|
59
|
+
* AWS::Serverless::GraphQLApi shorthand whose DataSources, Functions, and
|
|
60
|
+
* Resolvers blocks the transform expands into those same resources.
|
|
61
|
+
*
|
|
62
|
+
* Like the rest of this package, this reports only what the template says.
|
|
63
|
+
* Whether a field has an implementation, and whether that implementation
|
|
64
|
+
* matches the schema, is for a reader above to decide.
|
|
65
|
+
*/
|
|
66
|
+
|
|
42
67
|
/** One GraphQL field and the Lambdas behind it. */
|
|
43
68
|
interface AppSyncResolverBinding {
|
|
44
69
|
typeName: string;
|
|
@@ -46,10 +71,10 @@ interface AppSyncResolverBinding {
|
|
|
46
71
|
/**
|
|
47
72
|
* Every Lambda a request for this field runs, in pipeline order.
|
|
48
73
|
* Usually one. A pipeline can chain several, and nothing in the
|
|
49
|
-
* template says which of them
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
74
|
+
* template says which of them produces the value: the first is as
|
|
75
|
+
* often an auth step as it is the resolver, and the last is as often
|
|
76
|
+
* a formatter. So all of them are reported, and a reader that needs
|
|
77
|
+
* to pick one decides for itself.
|
|
53
78
|
*
|
|
54
79
|
* Empty when the field is served by something other than a Lambda.
|
|
55
80
|
*/
|
|
@@ -57,21 +82,125 @@ interface AppSyncResolverBinding {
|
|
|
57
82
|
}
|
|
58
83
|
declare function readAppSyncResolvers(template: CloudFormationTemplate): AppSyncResolverBinding[];
|
|
59
84
|
|
|
85
|
+
/**
|
|
86
|
+
* arn.ts resolves the references AWS manifests use to point at a queue, a
|
|
87
|
+
* topic, or a bucket, down to the channel string suss keys the boundary on.
|
|
88
|
+
*
|
|
89
|
+
* Every manifest language that targets AWS writes a reference one of three
|
|
90
|
+
* ways: as a CFN intrinsic (`!Ref X`, `!GetAtt X.Arn`), as a plain ARN
|
|
91
|
+
* string, or as a bare name. The CloudFormation reader and the Serverless
|
|
92
|
+
* Framework reader both resolve them here, so a queue written two different
|
|
93
|
+
* ways in two manifest languages still ends up on one channel.
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* Resolve a reference (`!Ref X`, `!GetAtt X.Arn`, plain string ARN) to
|
|
97
|
+
* the referenced resource's channel string. `service` is the ARN
|
|
98
|
+
* segment a plain string is checked against (`"sqs"`, `"sns"`, `"s3"`),
|
|
99
|
+
* so a queue ARN, a topic ARN, and a bucket ARN all resolve through the
|
|
100
|
+
* same code.
|
|
101
|
+
*
|
|
102
|
+
* Returns null when the reference is dynamic (a parameter, an import,
|
|
103
|
+
* or an Fn::Join that points at nothing this template declares); those
|
|
104
|
+
* need cross-stack resolution that's out of scope for v0.
|
|
105
|
+
*/
|
|
106
|
+
declare function resolveResourceChannel(value: unknown, service: string): string | null;
|
|
107
|
+
/**
|
|
108
|
+
* Resolve a Queue reference (`!Ref X`, `!GetAtt X.Arn`, plain string
|
|
109
|
+
* ARN) to the queue's channel string.
|
|
110
|
+
*/
|
|
111
|
+
declare function resolveQueueChannel(value: unknown): string | null;
|
|
112
|
+
/**
|
|
113
|
+
* Resolve a Topic reference (`!Ref X`, `!GetAtt X.Arn`, plain string
|
|
114
|
+
* ARN) to the topic's channel string.
|
|
115
|
+
*/
|
|
116
|
+
declare function resolveTopicChannel(value: unknown): string | null;
|
|
117
|
+
/**
|
|
118
|
+
* Resolve a Bucket reference (`!Ref X`, `!GetAtt X.Arn`, plain string
|
|
119
|
+
* ARN) to the bucket's channel string.
|
|
120
|
+
*/
|
|
121
|
+
declare function resolveBucketChannel(value: unknown): string | null;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* eventPattern.ts reads what an EventBridge rule says about the events it
|
|
125
|
+
* matches, and which bus those events arrive on.
|
|
126
|
+
*
|
|
127
|
+
* A rule's EventPattern and its EventBusName are written the same way in a
|
|
128
|
+
* CloudFormation template and in a serverless.yml eventBridge event, so
|
|
129
|
+
* both readers reduce them here.
|
|
130
|
+
*/
|
|
131
|
+
/**
|
|
132
|
+
* How far a rule's EventPattern reduced: either to the exact
|
|
133
|
+
* detail-types it lists, or not at all, along with a reason someone can
|
|
134
|
+
* read.
|
|
135
|
+
*/
|
|
136
|
+
type PatternReduction = {
|
|
137
|
+
kind: "exact";
|
|
138
|
+
detailTypes: string[];
|
|
139
|
+
} | {
|
|
140
|
+
kind: "unresolvable";
|
|
141
|
+
reason: string;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Reduce a rule's EventPattern to the exact set of DetailTypes it
|
|
145
|
+
* matches. v0 handles only a literal `detail-type` array; anything else
|
|
146
|
+
* (absent detail-type, content-filter objects, empty array) is
|
|
147
|
+
* unresolvable, surfaced by the checker, never silently dropped.
|
|
148
|
+
*/
|
|
149
|
+
declare function reduceEventPattern(pattern: unknown): PatternReduction;
|
|
150
|
+
/**
|
|
151
|
+
* Resolve a rule's EventBusName to the channel's bus token: the CFN
|
|
152
|
+
* logical id when it's a Ref/GetAtt, the name segment of an event-bus
|
|
153
|
+
* ARN, the literal string otherwise, or "default" when it's left out
|
|
154
|
+
* (the EventBridge default event bus).
|
|
155
|
+
*/
|
|
156
|
+
declare function resolveEventBusToken(value: unknown): string;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* globals.ts folds the SAM `Globals` section into the resources that
|
|
160
|
+
* inherit from it, so every reader sees one set of properties per resource
|
|
161
|
+
* and none of them has to know that `Globals` exists.
|
|
162
|
+
*
|
|
163
|
+
* SAM applies a section's properties to each resource of the matching type,
|
|
164
|
+
* and the resource's own value wins where both declare one, per
|
|
165
|
+
* https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html
|
|
166
|
+
*
|
|
167
|
+
* That page has the combining rules too. A map is merged key by key, a list
|
|
168
|
+
* becomes the section's entries followed by the resource's, and anything
|
|
169
|
+
* else is replaced outright. Merging keys is what makes a function that sets
|
|
170
|
+
* one environment variable of its own still get the section's others.
|
|
171
|
+
*/
|
|
172
|
+
|
|
60
173
|
/**
|
|
61
174
|
* The template's resources with each `Globals` section already applied
|
|
62
|
-
* to the resources it covers. A resource
|
|
63
|
-
* a template with no `Globals`, comes back untouched.
|
|
175
|
+
* to the resources it covers. A resource whose type no section covers,
|
|
176
|
+
* or a template with no `Globals`, comes back untouched.
|
|
64
177
|
*/
|
|
65
178
|
declare function resourcesWithGlobals(template: CloudFormationTemplate): Record<string, CloudFormationResource>;
|
|
66
179
|
/**
|
|
67
180
|
* Per function logical id, the environment variables the `Function`
|
|
68
181
|
* section supplies that the function does not declare for itself. A
|
|
69
182
|
* consumer that treats a function's environment as its own contract
|
|
70
|
-
* needs the
|
|
71
|
-
*
|
|
183
|
+
* needs the difference, because a variable every function in the
|
|
184
|
+
* document receives tells you about the document, not about one
|
|
185
|
+
* function.
|
|
72
186
|
*/
|
|
73
187
|
declare function inheritedEnvVars(template: CloudFormationTemplate): Record<string, string[]>;
|
|
74
188
|
|
|
189
|
+
/**
|
|
190
|
+
* nestedStacks.ts reads a template and every child template it embeds, as a
|
|
191
|
+
* list of documents.
|
|
192
|
+
*
|
|
193
|
+
* A stack resource points at another template, and CloudFormation deploys that
|
|
194
|
+
* template's resources alongside the parent's. Each document keeps its own
|
|
195
|
+
* namespace, its own SAM `Globals` section, and its own directory, so the tree
|
|
196
|
+
* stays a list of documents. Merging them would apply one document's defaults
|
|
197
|
+
* to another's resources, and let two logical ids spelled the same way collide.
|
|
198
|
+
*
|
|
199
|
+
* A child's reference to one of its own parameters is not followed to what the
|
|
200
|
+
* parent bound, and a `Fn::GetAtt` on a stack resource is not followed to the
|
|
201
|
+
* child's output. Both point at nothing, like any reference we can't resolve.
|
|
202
|
+
*/
|
|
203
|
+
|
|
75
204
|
/**
|
|
76
205
|
* How deep the reader follows children. CloudFormation itself allows
|
|
77
206
|
* deeper nesting; a template chain this long in a repository is far
|
|
@@ -79,7 +208,7 @@ declare function inheritedEnvVars(template: CloudFormationTemplate): Record<stri
|
|
|
79
208
|
* rather than applied silently.
|
|
80
209
|
*/
|
|
81
210
|
declare const MAX_STACK_DEPTH = 10;
|
|
82
|
-
/** One template file, and where it
|
|
211
|
+
/** One template file, and where it lives in the tree that reached it. */
|
|
83
212
|
interface TemplateDocument {
|
|
84
213
|
/** Absolute path of the file this document was read from. */
|
|
85
214
|
path: string;
|
|
@@ -92,8 +221,8 @@ interface TemplateDocument {
|
|
|
92
221
|
}
|
|
93
222
|
/**
|
|
94
223
|
* Why a child template was not read. Every value is a limit on what the
|
|
95
|
-
* reader could reach, so a consumer should report it as
|
|
96
|
-
* stopped rather than treat the child as
|
|
224
|
+
* reader could reach, so a consumer should report it as a read that
|
|
225
|
+
* stopped rather than treat the child as an empty template.
|
|
97
226
|
*/
|
|
98
227
|
type UnfollowedReason = "remoteUrl" | "notALiteralPath" | "fileMissing" | "unreadable" | "cycle" | "depthLimit";
|
|
99
228
|
/** A child template the reader could not open, and what stopped it. */
|
|
@@ -105,7 +234,7 @@ interface UnfollowedStack {
|
|
|
105
234
|
/** The template location as the document writes it, when it is a string. */
|
|
106
235
|
templateUrl: string | null;
|
|
107
236
|
reason: UnfollowedReason;
|
|
108
|
-
/** One phrase
|
|
237
|
+
/** One phrase saying what stopped the read, for a message to a person. */
|
|
109
238
|
detail: string;
|
|
110
239
|
}
|
|
111
240
|
interface TemplateTree {
|
|
@@ -125,14 +254,27 @@ interface TemplateTree {
|
|
|
125
254
|
declare function loadTemplateTree(rootPath: string): TemplateTree;
|
|
126
255
|
/**
|
|
127
256
|
* A logical id qualified by the stack path that reaches it, so two
|
|
128
|
-
* documents that both declare `HandlerFunction`
|
|
257
|
+
* documents that both declare `HandlerFunction` refer to two different
|
|
129
258
|
* deployed things. A resource in the root document keeps its bare id,
|
|
130
259
|
* which is also what CloudFormation shows for it.
|
|
131
260
|
*/
|
|
132
261
|
declare function qualifiedLogicalId(stackPath: string[], logicalId: string): string;
|
|
133
|
-
/** A line
|
|
262
|
+
/** A line saying which child the reader could not open, and why. */
|
|
134
263
|
declare function unfollowedStackMessage(stack: UnfollowedStack): string;
|
|
135
264
|
|
|
265
|
+
/**
|
|
266
|
+
* serverlessFunctions.ts reads AWS::Serverless::Function resources into a
|
|
267
|
+
* code-facing view: which module and export each function points at, and
|
|
268
|
+
* which route and non-route Events it declares.
|
|
269
|
+
*
|
|
270
|
+
* The summary-generation paths expand SAM Events into API Gateway route
|
|
271
|
+
* summaries. This reader covers the other question the code-side pairing
|
|
272
|
+
* needs: given the template, where is each handler's implementation, and
|
|
273
|
+
* what routes is it bound to? The manifest semantics (how a Handler string
|
|
274
|
+
* is laid out, how CodeUri is inherited from Globals, Api versus HttpApi
|
|
275
|
+
* Events) stay here rather than being reconstructed in a framework pack.
|
|
276
|
+
*/
|
|
277
|
+
|
|
136
278
|
/** A route-bearing SAM Event (`Type: Api` or `Type: HttpApi`). */
|
|
137
279
|
interface ServerlessHttpRoute {
|
|
138
280
|
/** Key of the event in the function's `Events` map. */
|
|
@@ -147,9 +289,10 @@ interface ServerlessHttpRoute {
|
|
|
147
289
|
apiId: string | null;
|
|
148
290
|
}
|
|
149
291
|
/**
|
|
150
|
-
* A recognized SAM Event that isn't an HTTP route
|
|
151
|
-
*
|
|
152
|
-
* for handlers it recognizes but deliberately doesn't
|
|
292
|
+
* A recognized SAM Event that isn't an HTTP route (SQS, Schedule, SNS,
|
|
293
|
+
* S3, and so on). These are reported rather than dropped, so the code
|
|
294
|
+
* side can account for handlers it recognizes but deliberately doesn't
|
|
295
|
+
* extract as HTTP.
|
|
153
296
|
*/
|
|
154
297
|
interface ServerlessNonHttpEvent {
|
|
155
298
|
eventId: string;
|
|
@@ -163,7 +306,7 @@ interface ServerlessFunctionInfo {
|
|
|
163
306
|
handler: string;
|
|
164
307
|
/** Module-path portion of the handler (everything before the final dot). */
|
|
165
308
|
modulePath: string;
|
|
166
|
-
/** Exported symbol the handler
|
|
309
|
+
/** Exported symbol the handler points at (everything after the final dot). */
|
|
167
310
|
exportName: string;
|
|
168
311
|
/**
|
|
169
312
|
* CodeUri base directory the handler resolves against: the function's
|
|
@@ -181,16 +324,16 @@ interface ParsedHandler {
|
|
|
181
324
|
* Split a SAM Handler string into its module path and exported symbol.
|
|
182
325
|
* The final dot separates them: `"src/handlers/confirmToken.handler"` →
|
|
183
326
|
* `{ modulePath: "src/handlers/confirmToken", exportName: "handler" }`.
|
|
184
|
-
* Returns null when there's no dot
|
|
327
|
+
* Returns null when there's no dot, since there's no export to bind to.
|
|
185
328
|
*/
|
|
186
329
|
declare function parseHandler(handler: string): ParsedHandler | null;
|
|
187
330
|
/**
|
|
188
331
|
* Read every AWS::Serverless::Function resource in the template into a
|
|
189
332
|
* code-facing `ServerlessFunctionInfo`. Functions without a parseable
|
|
190
|
-
* Handler are skipped
|
|
191
|
-
* Events still appear
|
|
192
|
-
* see them.
|
|
333
|
+
* Handler are skipped, since there's no export to bind. Functions with
|
|
334
|
+
* no Events still appear, with both event lists empty, so a consumer
|
|
335
|
+
* can see them.
|
|
193
336
|
*/
|
|
194
337
|
declare function readServerlessFunctions(template: CloudFormationTemplate): ServerlessFunctionInfo[];
|
|
195
338
|
|
|
196
|
-
export { type AppSyncResolverBinding, CLOUDFORMATION_YAML_TAGS, type CloudFormationResource, type CloudFormationTemplate, MAX_STACK_DEPTH, type ParsedHandler, type ServerlessFunctionInfo, type ServerlessHttpRoute, type ServerlessNonHttpEvent, type TemplateDocument, type TemplateTree, type UnfollowedReason, type UnfollowedStack, inheritedEnvVars, loadCloudFormationTemplate, loadTemplateTree, parseHandler, qualifiedLogicalId, readAppSyncResolvers, readServerlessFunctions, refTarget, resourcesWithGlobals, unfollowedStackMessage };
|
|
339
|
+
export { type AppSyncResolverBinding, CLOUDFORMATION_YAML_TAGS, type CloudFormationResource, type CloudFormationTemplate, MAX_STACK_DEPTH, type ParsedHandler, type PatternReduction, type ServerlessFunctionInfo, type ServerlessHttpRoute, type ServerlessNonHttpEvent, type TemplateDocument, type TemplateTree, type UnfollowedReason, type UnfollowedStack, inheritedEnvVars, loadCloudFormationTemplate, loadTemplateTree, parseHandler, qualifiedLogicalId, readAppSyncResolvers, readServerlessFunctions, reduceEventPattern, refTarget, resolveBucketChannel, resolveEventBusToken, resolveQueueChannel, resolveResourceChannel, resolveTopicChannel, resourcesWithGlobals, unfollowedStackMessage };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import YAML from 'yaml';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* templateLoader.ts parses a CloudFormation or SAM template file into a
|
|
5
|
+
* plain object, resolving the shorthand YAML tags CFN uses for intrinsics.
|
|
6
|
+
*
|
|
7
|
+
* This is the one place that turns a template on disk into data. The
|
|
8
|
+
* summary-generation paths and the code-side handler pairing both read
|
|
9
|
+
* through it, so the YAML and JSON parsing, along with the handling of
|
|
10
|
+
* intrinsic tags, lives here instead of being repeated per consumer.
|
|
11
|
+
*/
|
|
12
|
+
|
|
3
13
|
interface CloudFormationResource {
|
|
4
14
|
Type?: string;
|
|
5
15
|
Properties?: Record<string, unknown>;
|
|
@@ -8,7 +18,7 @@ interface CloudFormationResource {
|
|
|
8
18
|
interface CloudFormationTemplate {
|
|
9
19
|
/**
|
|
10
20
|
* SAM `Globals` block. Section keys (`Function`, `Api`, `HttpApi`)
|
|
11
|
-
*
|
|
21
|
+
* contain defaults that individual resources inherit unless they
|
|
12
22
|
* override the property themselves.
|
|
13
23
|
*/
|
|
14
24
|
Globals?: Record<string, Record<string, unknown>>;
|
|
@@ -16,29 +26,44 @@ interface CloudFormationTemplate {
|
|
|
16
26
|
}
|
|
17
27
|
declare const CLOUDFORMATION_YAML_TAGS: YAML.CollectionTag[];
|
|
18
28
|
/**
|
|
19
|
-
* Load a CloudFormation
|
|
20
|
-
* extension
|
|
21
|
-
* `.yaml
|
|
29
|
+
* Load a CloudFormation or SAM template from disk. The format comes from
|
|
30
|
+
* the extension: `.json` parses as JSON, and everything else (including
|
|
31
|
+
* `.yaml`, `.yml`, and `.template`) goes through the YAML parser with the
|
|
22
32
|
* intrinsic tags registered.
|
|
23
33
|
*
|
|
24
|
-
* Throws when the file is missing or the parsed value isn't an object
|
|
25
|
-
*
|
|
34
|
+
* Throws when the file is missing or the parsed value isn't an object. A
|
|
35
|
+
* malformed manifest is a load-time error rather than an empty result.
|
|
26
36
|
*/
|
|
27
37
|
declare function loadCloudFormationTemplate(templatePath: string): CloudFormationTemplate;
|
|
28
38
|
/**
|
|
29
|
-
* CloudFormation references
|
|
39
|
+
* CloudFormation references come out of the parser in four forms:
|
|
30
40
|
* - { Ref: "LogicalId" }
|
|
31
41
|
* - { "Fn::GetAtt": ["LogicalId", "Attr"] }
|
|
32
42
|
* - { "Fn::GetAtt": "LogicalId.Attr" }, the full-form spelling of the
|
|
33
43
|
* `!GetAtt` short form, which templates written by hand still use
|
|
34
44
|
* - the bare logical id when the parser doesn't recognise the YAML tag
|
|
35
45
|
*
|
|
36
|
-
* Every CloudFormation property that
|
|
37
|
-
*
|
|
38
|
-
*
|
|
46
|
+
* Every CloudFormation property that points at another resource accepts
|
|
47
|
+
* all four, so anything reading such a property should go through here
|
|
48
|
+
* instead of matching a single form.
|
|
39
49
|
*/
|
|
40
50
|
declare function refTarget(value: unknown): string | null;
|
|
41
51
|
|
|
52
|
+
/**
|
|
53
|
+
* appsyncResolvers.ts works out which GraphQL field each Lambda serves.
|
|
54
|
+
*
|
|
55
|
+
* AppSync does not wire a field to a handler in code. The template does it,
|
|
56
|
+
* over three hops: a resolver points at a pipeline function, the function
|
|
57
|
+
* points at a data source, and the data source points at a Lambda. Both ways
|
|
58
|
+
* of writing that are covered, the raw AWS::AppSync::* resources and the SAM
|
|
59
|
+
* AWS::Serverless::GraphQLApi shorthand whose DataSources, Functions, and
|
|
60
|
+
* Resolvers blocks the transform expands into those same resources.
|
|
61
|
+
*
|
|
62
|
+
* Like the rest of this package, this reports only what the template says.
|
|
63
|
+
* Whether a field has an implementation, and whether that implementation
|
|
64
|
+
* matches the schema, is for a reader above to decide.
|
|
65
|
+
*/
|
|
66
|
+
|
|
42
67
|
/** One GraphQL field and the Lambdas behind it. */
|
|
43
68
|
interface AppSyncResolverBinding {
|
|
44
69
|
typeName: string;
|
|
@@ -46,10 +71,10 @@ interface AppSyncResolverBinding {
|
|
|
46
71
|
/**
|
|
47
72
|
* Every Lambda a request for this field runs, in pipeline order.
|
|
48
73
|
* Usually one. A pipeline can chain several, and nothing in the
|
|
49
|
-
* template says which of them
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
74
|
+
* template says which of them produces the value: the first is as
|
|
75
|
+
* often an auth step as it is the resolver, and the last is as often
|
|
76
|
+
* a formatter. So all of them are reported, and a reader that needs
|
|
77
|
+
* to pick one decides for itself.
|
|
53
78
|
*
|
|
54
79
|
* Empty when the field is served by something other than a Lambda.
|
|
55
80
|
*/
|
|
@@ -57,21 +82,125 @@ interface AppSyncResolverBinding {
|
|
|
57
82
|
}
|
|
58
83
|
declare function readAppSyncResolvers(template: CloudFormationTemplate): AppSyncResolverBinding[];
|
|
59
84
|
|
|
85
|
+
/**
|
|
86
|
+
* arn.ts resolves the references AWS manifests use to point at a queue, a
|
|
87
|
+
* topic, or a bucket, down to the channel string suss keys the boundary on.
|
|
88
|
+
*
|
|
89
|
+
* Every manifest language that targets AWS writes a reference one of three
|
|
90
|
+
* ways: as a CFN intrinsic (`!Ref X`, `!GetAtt X.Arn`), as a plain ARN
|
|
91
|
+
* string, or as a bare name. The CloudFormation reader and the Serverless
|
|
92
|
+
* Framework reader both resolve them here, so a queue written two different
|
|
93
|
+
* ways in two manifest languages still ends up on one channel.
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* Resolve a reference (`!Ref X`, `!GetAtt X.Arn`, plain string ARN) to
|
|
97
|
+
* the referenced resource's channel string. `service` is the ARN
|
|
98
|
+
* segment a plain string is checked against (`"sqs"`, `"sns"`, `"s3"`),
|
|
99
|
+
* so a queue ARN, a topic ARN, and a bucket ARN all resolve through the
|
|
100
|
+
* same code.
|
|
101
|
+
*
|
|
102
|
+
* Returns null when the reference is dynamic (a parameter, an import,
|
|
103
|
+
* or an Fn::Join that points at nothing this template declares); those
|
|
104
|
+
* need cross-stack resolution that's out of scope for v0.
|
|
105
|
+
*/
|
|
106
|
+
declare function resolveResourceChannel(value: unknown, service: string): string | null;
|
|
107
|
+
/**
|
|
108
|
+
* Resolve a Queue reference (`!Ref X`, `!GetAtt X.Arn`, plain string
|
|
109
|
+
* ARN) to the queue's channel string.
|
|
110
|
+
*/
|
|
111
|
+
declare function resolveQueueChannel(value: unknown): string | null;
|
|
112
|
+
/**
|
|
113
|
+
* Resolve a Topic reference (`!Ref X`, `!GetAtt X.Arn`, plain string
|
|
114
|
+
* ARN) to the topic's channel string.
|
|
115
|
+
*/
|
|
116
|
+
declare function resolveTopicChannel(value: unknown): string | null;
|
|
117
|
+
/**
|
|
118
|
+
* Resolve a Bucket reference (`!Ref X`, `!GetAtt X.Arn`, plain string
|
|
119
|
+
* ARN) to the bucket's channel string.
|
|
120
|
+
*/
|
|
121
|
+
declare function resolveBucketChannel(value: unknown): string | null;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* eventPattern.ts reads what an EventBridge rule says about the events it
|
|
125
|
+
* matches, and which bus those events arrive on.
|
|
126
|
+
*
|
|
127
|
+
* A rule's EventPattern and its EventBusName are written the same way in a
|
|
128
|
+
* CloudFormation template and in a serverless.yml eventBridge event, so
|
|
129
|
+
* both readers reduce them here.
|
|
130
|
+
*/
|
|
131
|
+
/**
|
|
132
|
+
* How far a rule's EventPattern reduced: either to the exact
|
|
133
|
+
* detail-types it lists, or not at all, along with a reason someone can
|
|
134
|
+
* read.
|
|
135
|
+
*/
|
|
136
|
+
type PatternReduction = {
|
|
137
|
+
kind: "exact";
|
|
138
|
+
detailTypes: string[];
|
|
139
|
+
} | {
|
|
140
|
+
kind: "unresolvable";
|
|
141
|
+
reason: string;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Reduce a rule's EventPattern to the exact set of DetailTypes it
|
|
145
|
+
* matches. v0 handles only a literal `detail-type` array; anything else
|
|
146
|
+
* (absent detail-type, content-filter objects, empty array) is
|
|
147
|
+
* unresolvable, surfaced by the checker, never silently dropped.
|
|
148
|
+
*/
|
|
149
|
+
declare function reduceEventPattern(pattern: unknown): PatternReduction;
|
|
150
|
+
/**
|
|
151
|
+
* Resolve a rule's EventBusName to the channel's bus token: the CFN
|
|
152
|
+
* logical id when it's a Ref/GetAtt, the name segment of an event-bus
|
|
153
|
+
* ARN, the literal string otherwise, or "default" when it's left out
|
|
154
|
+
* (the EventBridge default event bus).
|
|
155
|
+
*/
|
|
156
|
+
declare function resolveEventBusToken(value: unknown): string;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* globals.ts folds the SAM `Globals` section into the resources that
|
|
160
|
+
* inherit from it, so every reader sees one set of properties per resource
|
|
161
|
+
* and none of them has to know that `Globals` exists.
|
|
162
|
+
*
|
|
163
|
+
* SAM applies a section's properties to each resource of the matching type,
|
|
164
|
+
* and the resource's own value wins where both declare one, per
|
|
165
|
+
* https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html
|
|
166
|
+
*
|
|
167
|
+
* That page has the combining rules too. A map is merged key by key, a list
|
|
168
|
+
* becomes the section's entries followed by the resource's, and anything
|
|
169
|
+
* else is replaced outright. Merging keys is what makes a function that sets
|
|
170
|
+
* one environment variable of its own still get the section's others.
|
|
171
|
+
*/
|
|
172
|
+
|
|
60
173
|
/**
|
|
61
174
|
* The template's resources with each `Globals` section already applied
|
|
62
|
-
* to the resources it covers. A resource
|
|
63
|
-
* a template with no `Globals`, comes back untouched.
|
|
175
|
+
* to the resources it covers. A resource whose type no section covers,
|
|
176
|
+
* or a template with no `Globals`, comes back untouched.
|
|
64
177
|
*/
|
|
65
178
|
declare function resourcesWithGlobals(template: CloudFormationTemplate): Record<string, CloudFormationResource>;
|
|
66
179
|
/**
|
|
67
180
|
* Per function logical id, the environment variables the `Function`
|
|
68
181
|
* section supplies that the function does not declare for itself. A
|
|
69
182
|
* consumer that treats a function's environment as its own contract
|
|
70
|
-
* needs the
|
|
71
|
-
*
|
|
183
|
+
* needs the difference, because a variable every function in the
|
|
184
|
+
* document receives tells you about the document, not about one
|
|
185
|
+
* function.
|
|
72
186
|
*/
|
|
73
187
|
declare function inheritedEnvVars(template: CloudFormationTemplate): Record<string, string[]>;
|
|
74
188
|
|
|
189
|
+
/**
|
|
190
|
+
* nestedStacks.ts reads a template and every child template it embeds, as a
|
|
191
|
+
* list of documents.
|
|
192
|
+
*
|
|
193
|
+
* A stack resource points at another template, and CloudFormation deploys that
|
|
194
|
+
* template's resources alongside the parent's. Each document keeps its own
|
|
195
|
+
* namespace, its own SAM `Globals` section, and its own directory, so the tree
|
|
196
|
+
* stays a list of documents. Merging them would apply one document's defaults
|
|
197
|
+
* to another's resources, and let two logical ids spelled the same way collide.
|
|
198
|
+
*
|
|
199
|
+
* A child's reference to one of its own parameters is not followed to what the
|
|
200
|
+
* parent bound, and a `Fn::GetAtt` on a stack resource is not followed to the
|
|
201
|
+
* child's output. Both point at nothing, like any reference we can't resolve.
|
|
202
|
+
*/
|
|
203
|
+
|
|
75
204
|
/**
|
|
76
205
|
* How deep the reader follows children. CloudFormation itself allows
|
|
77
206
|
* deeper nesting; a template chain this long in a repository is far
|
|
@@ -79,7 +208,7 @@ declare function inheritedEnvVars(template: CloudFormationTemplate): Record<stri
|
|
|
79
208
|
* rather than applied silently.
|
|
80
209
|
*/
|
|
81
210
|
declare const MAX_STACK_DEPTH = 10;
|
|
82
|
-
/** One template file, and where it
|
|
211
|
+
/** One template file, and where it lives in the tree that reached it. */
|
|
83
212
|
interface TemplateDocument {
|
|
84
213
|
/** Absolute path of the file this document was read from. */
|
|
85
214
|
path: string;
|
|
@@ -92,8 +221,8 @@ interface TemplateDocument {
|
|
|
92
221
|
}
|
|
93
222
|
/**
|
|
94
223
|
* Why a child template was not read. Every value is a limit on what the
|
|
95
|
-
* reader could reach, so a consumer should report it as
|
|
96
|
-
* stopped rather than treat the child as
|
|
224
|
+
* reader could reach, so a consumer should report it as a read that
|
|
225
|
+
* stopped rather than treat the child as an empty template.
|
|
97
226
|
*/
|
|
98
227
|
type UnfollowedReason = "remoteUrl" | "notALiteralPath" | "fileMissing" | "unreadable" | "cycle" | "depthLimit";
|
|
99
228
|
/** A child template the reader could not open, and what stopped it. */
|
|
@@ -105,7 +234,7 @@ interface UnfollowedStack {
|
|
|
105
234
|
/** The template location as the document writes it, when it is a string. */
|
|
106
235
|
templateUrl: string | null;
|
|
107
236
|
reason: UnfollowedReason;
|
|
108
|
-
/** One phrase
|
|
237
|
+
/** One phrase saying what stopped the read, for a message to a person. */
|
|
109
238
|
detail: string;
|
|
110
239
|
}
|
|
111
240
|
interface TemplateTree {
|
|
@@ -125,14 +254,27 @@ interface TemplateTree {
|
|
|
125
254
|
declare function loadTemplateTree(rootPath: string): TemplateTree;
|
|
126
255
|
/**
|
|
127
256
|
* A logical id qualified by the stack path that reaches it, so two
|
|
128
|
-
* documents that both declare `HandlerFunction`
|
|
257
|
+
* documents that both declare `HandlerFunction` refer to two different
|
|
129
258
|
* deployed things. A resource in the root document keeps its bare id,
|
|
130
259
|
* which is also what CloudFormation shows for it.
|
|
131
260
|
*/
|
|
132
261
|
declare function qualifiedLogicalId(stackPath: string[], logicalId: string): string;
|
|
133
|
-
/** A line
|
|
262
|
+
/** A line saying which child the reader could not open, and why. */
|
|
134
263
|
declare function unfollowedStackMessage(stack: UnfollowedStack): string;
|
|
135
264
|
|
|
265
|
+
/**
|
|
266
|
+
* serverlessFunctions.ts reads AWS::Serverless::Function resources into a
|
|
267
|
+
* code-facing view: which module and export each function points at, and
|
|
268
|
+
* which route and non-route Events it declares.
|
|
269
|
+
*
|
|
270
|
+
* The summary-generation paths expand SAM Events into API Gateway route
|
|
271
|
+
* summaries. This reader covers the other question the code-side pairing
|
|
272
|
+
* needs: given the template, where is each handler's implementation, and
|
|
273
|
+
* what routes is it bound to? The manifest semantics (how a Handler string
|
|
274
|
+
* is laid out, how CodeUri is inherited from Globals, Api versus HttpApi
|
|
275
|
+
* Events) stay here rather than being reconstructed in a framework pack.
|
|
276
|
+
*/
|
|
277
|
+
|
|
136
278
|
/** A route-bearing SAM Event (`Type: Api` or `Type: HttpApi`). */
|
|
137
279
|
interface ServerlessHttpRoute {
|
|
138
280
|
/** Key of the event in the function's `Events` map. */
|
|
@@ -147,9 +289,10 @@ interface ServerlessHttpRoute {
|
|
|
147
289
|
apiId: string | null;
|
|
148
290
|
}
|
|
149
291
|
/**
|
|
150
|
-
* A recognized SAM Event that isn't an HTTP route
|
|
151
|
-
*
|
|
152
|
-
* for handlers it recognizes but deliberately doesn't
|
|
292
|
+
* A recognized SAM Event that isn't an HTTP route (SQS, Schedule, SNS,
|
|
293
|
+
* S3, and so on). These are reported rather than dropped, so the code
|
|
294
|
+
* side can account for handlers it recognizes but deliberately doesn't
|
|
295
|
+
* extract as HTTP.
|
|
153
296
|
*/
|
|
154
297
|
interface ServerlessNonHttpEvent {
|
|
155
298
|
eventId: string;
|
|
@@ -163,7 +306,7 @@ interface ServerlessFunctionInfo {
|
|
|
163
306
|
handler: string;
|
|
164
307
|
/** Module-path portion of the handler (everything before the final dot). */
|
|
165
308
|
modulePath: string;
|
|
166
|
-
/** Exported symbol the handler
|
|
309
|
+
/** Exported symbol the handler points at (everything after the final dot). */
|
|
167
310
|
exportName: string;
|
|
168
311
|
/**
|
|
169
312
|
* CodeUri base directory the handler resolves against: the function's
|
|
@@ -181,16 +324,16 @@ interface ParsedHandler {
|
|
|
181
324
|
* Split a SAM Handler string into its module path and exported symbol.
|
|
182
325
|
* The final dot separates them: `"src/handlers/confirmToken.handler"` →
|
|
183
326
|
* `{ modulePath: "src/handlers/confirmToken", exportName: "handler" }`.
|
|
184
|
-
* Returns null when there's no dot
|
|
327
|
+
* Returns null when there's no dot, since there's no export to bind to.
|
|
185
328
|
*/
|
|
186
329
|
declare function parseHandler(handler: string): ParsedHandler | null;
|
|
187
330
|
/**
|
|
188
331
|
* Read every AWS::Serverless::Function resource in the template into a
|
|
189
332
|
* code-facing `ServerlessFunctionInfo`. Functions without a parseable
|
|
190
|
-
* Handler are skipped
|
|
191
|
-
* Events still appear
|
|
192
|
-
* see them.
|
|
333
|
+
* Handler are skipped, since there's no export to bind. Functions with
|
|
334
|
+
* no Events still appear, with both event lists empty, so a consumer
|
|
335
|
+
* can see them.
|
|
193
336
|
*/
|
|
194
337
|
declare function readServerlessFunctions(template: CloudFormationTemplate): ServerlessFunctionInfo[];
|
|
195
338
|
|
|
196
|
-
export { type AppSyncResolverBinding, CLOUDFORMATION_YAML_TAGS, type CloudFormationResource, type CloudFormationTemplate, MAX_STACK_DEPTH, type ParsedHandler, type ServerlessFunctionInfo, type ServerlessHttpRoute, type ServerlessNonHttpEvent, type TemplateDocument, type TemplateTree, type UnfollowedReason, type UnfollowedStack, inheritedEnvVars, loadCloudFormationTemplate, loadTemplateTree, parseHandler, qualifiedLogicalId, readAppSyncResolvers, readServerlessFunctions, refTarget, resourcesWithGlobals, unfollowedStackMessage };
|
|
339
|
+
export { type AppSyncResolverBinding, CLOUDFORMATION_YAML_TAGS, type CloudFormationResource, type CloudFormationTemplate, MAX_STACK_DEPTH, type ParsedHandler, type PatternReduction, type ServerlessFunctionInfo, type ServerlessHttpRoute, type ServerlessNonHttpEvent, type TemplateDocument, type TemplateTree, type UnfollowedReason, type UnfollowedStack, inheritedEnvVars, loadCloudFormationTemplate, loadTemplateTree, parseHandler, qualifiedLogicalId, readAppSyncResolvers, readServerlessFunctions, reduceEventPattern, refTarget, resolveBucketChannel, resolveEventBusToken, resolveQueueChannel, resolveResourceChannel, resolveTopicChannel, resourcesWithGlobals, unfollowedStackMessage };
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,11 @@ var getAttParts = (value) => {
|
|
|
16
16
|
return value.map(String);
|
|
17
17
|
}
|
|
18
18
|
const text = String(value);
|
|
19
|
-
|
|
19
|
+
const dot = text.indexOf(".");
|
|
20
|
+
if (dot === -1) {
|
|
21
|
+
return [text];
|
|
22
|
+
}
|
|
23
|
+
return [text.slice(0, dot), text.slice(dot + 1)];
|
|
20
24
|
};
|
|
21
25
|
var CLOUDFORMATION_YAML_TAGS = [
|
|
22
26
|
...everyNodeKind("!Ref", (value) => ({ Ref: value })),
|
|
@@ -68,8 +72,7 @@ function refTarget(value) {
|
|
|
68
72
|
return getAtt[0];
|
|
69
73
|
}
|
|
70
74
|
if (typeof getAtt === "string") {
|
|
71
|
-
|
|
72
|
-
return dot === -1 ? getAtt : getAtt.slice(0, dot);
|
|
75
|
+
return getAttParts(getAtt)[0] ?? null;
|
|
73
76
|
}
|
|
74
77
|
return null;
|
|
75
78
|
}
|
|
@@ -233,6 +236,102 @@ function stringOf(value) {
|
|
|
233
236
|
return typeof value === "string" ? value : null;
|
|
234
237
|
}
|
|
235
238
|
|
|
239
|
+
// src/arn.ts
|
|
240
|
+
function resolveResourceChannel(value, service) {
|
|
241
|
+
if (value === null || value === void 0) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
if (typeof value === "string") {
|
|
245
|
+
const resource = resolveArnResource(value, service);
|
|
246
|
+
return resource ?? value;
|
|
247
|
+
}
|
|
248
|
+
return refTarget(value);
|
|
249
|
+
}
|
|
250
|
+
function resolveArnResource(value, service) {
|
|
251
|
+
const parts = value.split(":");
|
|
252
|
+
if (parts[0] !== "arn" || parts[2] !== service) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
const region = parts[3];
|
|
256
|
+
const account = parts[4];
|
|
257
|
+
const resource = parts.slice(5).join(":");
|
|
258
|
+
if (service === "s3") {
|
|
259
|
+
if (region !== "" || account !== "" || resource === "") {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
const slash = resource.indexOf("/");
|
|
263
|
+
const bucket = slash === -1 ? resource : resource.slice(0, slash);
|
|
264
|
+
return bucket === "" ? null : bucket;
|
|
265
|
+
}
|
|
266
|
+
if (!region || !account || resource === "") {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
return resource;
|
|
270
|
+
}
|
|
271
|
+
function resolveQueueChannel(value) {
|
|
272
|
+
return resolveResourceChannel(value, "sqs");
|
|
273
|
+
}
|
|
274
|
+
function resolveTopicChannel(value) {
|
|
275
|
+
return resolveResourceChannel(value, "sns");
|
|
276
|
+
}
|
|
277
|
+
function resolveBucketChannel(value) {
|
|
278
|
+
return resolveResourceChannel(value, "s3");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/eventPattern.ts
|
|
282
|
+
function reduceEventPattern(pattern) {
|
|
283
|
+
if (pattern === null || typeof pattern !== "object") {
|
|
284
|
+
return {
|
|
285
|
+
kind: "unresolvable",
|
|
286
|
+
reason: "rule declares no EventPattern"
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const detailType = pattern["detail-type"];
|
|
290
|
+
if (detailType === void 0) {
|
|
291
|
+
return {
|
|
292
|
+
kind: "unresolvable",
|
|
293
|
+
reason: "EventPattern has no detail-type; v0 reduces routing to exact detail-type match only"
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (!Array.isArray(detailType)) {
|
|
297
|
+
return {
|
|
298
|
+
kind: "unresolvable",
|
|
299
|
+
reason: "detail-type is not a plain array of string literals"
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const detailTypes = [];
|
|
303
|
+
for (const entry of detailType) {
|
|
304
|
+
if (typeof entry !== "string") {
|
|
305
|
+
return {
|
|
306
|
+
kind: "unresolvable",
|
|
307
|
+
reason: "detail-type contains a content filter (prefix / anything-but / etc.); pattern subsumption is out of v0 scope"
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
detailTypes.push(entry);
|
|
311
|
+
}
|
|
312
|
+
if (detailTypes.length === 0) {
|
|
313
|
+
return { kind: "unresolvable", reason: "detail-type array is empty" };
|
|
314
|
+
}
|
|
315
|
+
return { kind: "exact", detailTypes };
|
|
316
|
+
}
|
|
317
|
+
function resolveEventBusToken(value) {
|
|
318
|
+
if (value === null || value === void 0) {
|
|
319
|
+
return "default";
|
|
320
|
+
}
|
|
321
|
+
if (typeof value === "object") {
|
|
322
|
+
const logicalId = refTarget(value);
|
|
323
|
+
return logicalId ?? "default";
|
|
324
|
+
}
|
|
325
|
+
if (typeof value === "string") {
|
|
326
|
+
const arnMatch = value.match(/:event-bus\/(.+)$/);
|
|
327
|
+
if (arnMatch !== null) {
|
|
328
|
+
return arnMatch[1];
|
|
329
|
+
}
|
|
330
|
+
return value;
|
|
331
|
+
}
|
|
332
|
+
return "default";
|
|
333
|
+
}
|
|
334
|
+
|
|
236
335
|
// src/globals.ts
|
|
237
336
|
var GLOBALS_SECTION_TYPES = {
|
|
238
337
|
Api: "AWS::Serverless::Api",
|
|
@@ -546,7 +645,13 @@ export {
|
|
|
546
645
|
qualifiedLogicalId,
|
|
547
646
|
readAppSyncResolvers,
|
|
548
647
|
readServerlessFunctions,
|
|
648
|
+
reduceEventPattern,
|
|
549
649
|
refTarget,
|
|
650
|
+
resolveBucketChannel,
|
|
651
|
+
resolveEventBusToken,
|
|
652
|
+
resolveQueueChannel,
|
|
653
|
+
resolveResourceChannel,
|
|
654
|
+
resolveTopicChannel,
|
|
550
655
|
resourcesWithGlobals,
|
|
551
656
|
unfollowedStackMessage
|
|
552
657
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/manifest-aws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Parse AWS CloudFormation / SAM templates into plain data — the shared facts layer contract readers and manifest-driven framework packs both consume.",
|
|
6
6
|
"keywords": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"typecheck": "tsc --noEmit"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@suss/ir-core": "0.
|
|
48
|
+
"@suss/ir-core": "0.4.0",
|
|
49
49
|
"yaml": "^2.4.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|