@composurecdk/cloudformation 0.5.1 → 0.7.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/README.md +28 -10
- package/dist/apply-builder-tags.d.ts +32 -0
- package/dist/apply-builder-tags.d.ts.map +1 -0
- package/dist/apply-builder-tags.js +65 -0
- package/dist/apply-builder-tags.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/stack-builder.d.ts +25 -47
- package/dist/stack-builder.d.ts.map +1 -1
- package/dist/stack-builder.js +14 -32
- package/dist/stack-builder.js.map +1 -1
- package/dist/strategies.d.ts +41 -12
- package/dist/strategies.d.ts.map +1 -1
- package/dist/strategies.js +43 -16
- package/dist/strategies.js.map +1 -1
- package/dist/tag-validator.d.ts +22 -0
- package/dist/tag-validator.d.ts.map +1 -0
- package/dist/tag-validator.js +63 -0
- package/dist/tag-validator.js.map +1 -0
- package/dist/tagged-builder.d.ts +112 -0
- package/dist/tagged-builder.d.ts.map +1 -0
- package/dist/tagged-builder.js +117 -0
- package/dist/tagged-builder.js.map +1 -0
- package/dist/tags.d.ts +91 -0
- package/dist/tags.d.ts.map +1 -0
- package/dist/tags.js +83 -0
- package/dist/tags.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,24 +28,20 @@ const { stack } = createStackBuilder()
|
|
|
28
28
|
.build(app, "ServiceStack");
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
###
|
|
31
|
+
### Variants and snapshots with `.copy()`
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
`.copy()` returns an independent builder with the same configured state. Use it to derive variants from a shared base, or to snapshot a builder before handing it to a stack strategy that may be invoked after further mutations:
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
const
|
|
37
|
-
.terminationProtection(true)
|
|
38
|
-
.tag("team", "platform")
|
|
39
|
-
.toScopeFactory();
|
|
36
|
+
const baseStack = createStackBuilder().tag("team", "platform");
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.build(app, "MySystem");
|
|
38
|
+
const { stack: us } = baseStack.copy().description("US region").build(app, "UsStack");
|
|
39
|
+
const { stack: eu } = baseStack.copy().description("EU region").build(app, "EuStack");
|
|
44
40
|
```
|
|
45
41
|
|
|
46
42
|
## Stack Strategies
|
|
47
43
|
|
|
48
|
-
Convenience wrappers around `@composurecdk/core`'s strategy primitives
|
|
44
|
+
Convenience wrappers around `@composurecdk/core`'s strategy primitives. Both accept a `Lifecycle<StackBuilderResult>` (typically an `IStackBuilder`) and default to a fresh `createStackBuilder()` per call.
|
|
49
45
|
|
|
50
46
|
### singleStack
|
|
51
47
|
|
|
@@ -59,6 +55,16 @@ compose({ handler, api }, { handler: [], api: ["handler"] })
|
|
|
59
55
|
.build(app, "MySystem");
|
|
60
56
|
```
|
|
61
57
|
|
|
58
|
+
Pass a configured builder to apply tags, description, etc. to the strategy's stack. Use `.copy()` to snapshot the configuration when the original may be mutated later:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const base = createStackBuilder().tag("team", "platform");
|
|
62
|
+
|
|
63
|
+
compose({ ... }, { ... })
|
|
64
|
+
.withStackStrategy(singleStack(base.copy()))
|
|
65
|
+
.build(app, "MySystem");
|
|
66
|
+
```
|
|
67
|
+
|
|
62
68
|
### groupedStacks
|
|
63
69
|
|
|
64
70
|
Groups components into named Stacks by a classifier function:
|
|
@@ -73,6 +79,18 @@ compose({ handler, api, table }, { ... })
|
|
|
73
79
|
.build(app, "MySystem");
|
|
74
80
|
```
|
|
75
81
|
|
|
82
|
+
The same builder is invoked once per group key with `${systemId}-${group}` as the id, so any tags configured on the supplied builder propagate to every stack the strategy creates. As with `singleStack`, pass `builder.copy()` to snapshot the configuration when the original may be mutated after hand-off:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const base = createStackBuilder().tag("team", "platform");
|
|
86
|
+
|
|
87
|
+
compose({ ... }, { ... })
|
|
88
|
+
.withStackStrategy(
|
|
89
|
+
groupedStacks((key) => (key === "table" ? "persistence" : "service"), base.copy()),
|
|
90
|
+
)
|
|
91
|
+
.build(app, "MySystem");
|
|
92
|
+
```
|
|
93
|
+
|
|
76
94
|
## outputs
|
|
77
95
|
|
|
78
96
|
A post-build hook that creates CloudFormation stack outputs from a composed system's build results. Output values can be concrete strings or `Ref`s that resolve against the system's results.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type IConstruct } from "constructs";
|
|
2
|
+
/**
|
|
3
|
+
* Applies every accumulated tag to every {@link IConstruct} reachable in a
|
|
4
|
+
* builder result.
|
|
5
|
+
*
|
|
6
|
+
* Recursively descends through plain-object literals, tagging every
|
|
7
|
+
* `IConstruct` it finds. Stops at:
|
|
8
|
+
*
|
|
9
|
+
* - **Constructs** — tagged via `Tags.of(...).add(...)`. The CDK Aspect
|
|
10
|
+
* schedules tag application across the construct's subtree at
|
|
11
|
+
* synth-prepare time, so the walker does not recurse into the construct's
|
|
12
|
+
* internals.
|
|
13
|
+
* - **Class instances that aren't constructs** (e.g. `PolicyDocument`) —
|
|
14
|
+
* skipped. Plain-object detection requires `Object.prototype` as the
|
|
15
|
+
* prototype, so class instances are opaque to the walker.
|
|
16
|
+
* - **Arrays and primitives** — skipped.
|
|
17
|
+
*
|
|
18
|
+
* The contract this implements: every construct exposed in a builder's
|
|
19
|
+
* result type is a tag target. Wrapper shapes such as
|
|
20
|
+
* `Record<string, { construct: ..., metadata: ... }>` are unwrapped
|
|
21
|
+
* naturally — the walker descends through the plain-object value and tags
|
|
22
|
+
* the construct field. Authors do not need an opt-in marker; if a construct
|
|
23
|
+
* appears in the result, it is tagged.
|
|
24
|
+
*/
|
|
25
|
+
export declare function applyBuilderTags(result: object, tags: ReadonlyMap<string, string>): void;
|
|
26
|
+
/**
|
|
27
|
+
* Applies every entry of `tags` to `target` via `Tags.of(target).add(...)`.
|
|
28
|
+
* Accepts any iterable of `[key, value]` pairs so callers can pass `Map`,
|
|
29
|
+
* `Object.entries(record)`, or other compatible sources without copying.
|
|
30
|
+
*/
|
|
31
|
+
export declare function applyTagsToConstruct(target: IConstruct, tags: Iterable<[string, string]>): void;
|
|
32
|
+
//# sourceMappingURL=apply-builder-tags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-builder-tags.d.ts","sourceRoot":"","sources":["../src/apply-builder-tags.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAMxD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAGxF;AAcD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAK/F"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Tags } from "aws-cdk-lib";
|
|
2
|
+
import { Construct } from "constructs";
|
|
3
|
+
function isConstruct(value) {
|
|
4
|
+
return value !== null && typeof value === "object" && Construct.isConstruct(value);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Applies every accumulated tag to every {@link IConstruct} reachable in a
|
|
8
|
+
* builder result.
|
|
9
|
+
*
|
|
10
|
+
* Recursively descends through plain-object literals, tagging every
|
|
11
|
+
* `IConstruct` it finds. Stops at:
|
|
12
|
+
*
|
|
13
|
+
* - **Constructs** — tagged via `Tags.of(...).add(...)`. The CDK Aspect
|
|
14
|
+
* schedules tag application across the construct's subtree at
|
|
15
|
+
* synth-prepare time, so the walker does not recurse into the construct's
|
|
16
|
+
* internals.
|
|
17
|
+
* - **Class instances that aren't constructs** (e.g. `PolicyDocument`) —
|
|
18
|
+
* skipped. Plain-object detection requires `Object.prototype` as the
|
|
19
|
+
* prototype, so class instances are opaque to the walker.
|
|
20
|
+
* - **Arrays and primitives** — skipped.
|
|
21
|
+
*
|
|
22
|
+
* The contract this implements: every construct exposed in a builder's
|
|
23
|
+
* result type is a tag target. Wrapper shapes such as
|
|
24
|
+
* `Record<string, { construct: ..., metadata: ... }>` are unwrapped
|
|
25
|
+
* naturally — the walker descends through the plain-object value and tags
|
|
26
|
+
* the construct field. Authors do not need an opt-in marker; if a construct
|
|
27
|
+
* appears in the result, it is tagged.
|
|
28
|
+
*/
|
|
29
|
+
export function applyBuilderTags(result, tags) {
|
|
30
|
+
if (tags.size === 0)
|
|
31
|
+
return;
|
|
32
|
+
walkAndTag(result, tags);
|
|
33
|
+
}
|
|
34
|
+
function walkAndTag(value, tags) {
|
|
35
|
+
if (isConstruct(value)) {
|
|
36
|
+
applyTagsToConstruct(value, tags);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (isPlainObject(value)) {
|
|
40
|
+
for (const inner of Object.values(value)) {
|
|
41
|
+
walkAndTag(inner, tags);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Applies every entry of `tags` to `target` via `Tags.of(target).add(...)`.
|
|
47
|
+
* Accepts any iterable of `[key, value]` pairs so callers can pass `Map`,
|
|
48
|
+
* `Object.entries(record)`, or other compatible sources without copying.
|
|
49
|
+
*/
|
|
50
|
+
export function applyTagsToConstruct(target, tags) {
|
|
51
|
+
const t = Tags.of(target);
|
|
52
|
+
for (const [key, value] of tags) {
|
|
53
|
+
t.add(key, value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isPlainObject(value) {
|
|
57
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
58
|
+
return false;
|
|
59
|
+
// Accept both `{...}` literals and `Object.create(null)` dictionaries — the
|
|
60
|
+
// latter is a common idiom for prototype-pollution-safe key/value maps and
|
|
61
|
+
// would otherwise be silently skipped here.
|
|
62
|
+
const proto = Object.getPrototypeOf(value);
|
|
63
|
+
return proto === null || proto === Object.prototype;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=apply-builder-tags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-builder-tags.js","sourceRoot":"","sources":["../src/apply-builder-tags.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,SAAS,EAAmB,MAAM,YAAY,CAAC;AAExD,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AACrF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,IAAiC;IAChF,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAC5B,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,UAAU,CAAC,KAAc,EAAE,IAAiC;IACnE,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAClC,OAAO;IACT,CAAC;IACD,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACzC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAkB,EAAE,IAAgC;IACvF,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;QAChC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtF,4EAA4E;IAC5E,2EAA2E;IAC3E,4CAA4C;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAkB,CAAC;IAC5D,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,CAAC;AACtD,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { createStackBuilder, type IStackBuilder, type StackBuilderResult, } from "./stack-builder.js";
|
|
2
2
|
export { singleStack, groupedStacks } from "./strategies.js";
|
|
3
3
|
export { outputs, type OutputDefinition, type OutputDefinitions } from "./outputs.js";
|
|
4
|
+
export { taggedBuilder, type ITaggedBuilder, TAG_OVERRIDE_WARNING_NAME } from "./tagged-builder.js";
|
|
5
|
+
export { applyBuilderTags } from "./apply-builder-tags.js";
|
|
6
|
+
export { validateTag } from "./tag-validator.js";
|
|
7
|
+
export { tags, type TagDefinitions } from "./tags.js";
|
|
4
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { createStackBuilder, } from "./stack-builder.js";
|
|
2
2
|
export { singleStack, groupedStacks } from "./strategies.js";
|
|
3
3
|
export { outputs } from "./outputs.js";
|
|
4
|
+
export { taggedBuilder, TAG_OVERRIDE_WARNING_NAME } from "./tagged-builder.js";
|
|
5
|
+
export { applyBuilderTags } from "./apply-builder-tags.js";
|
|
6
|
+
export { validateTag } from "./tag-validator.js";
|
|
7
|
+
export { tags } from "./tags.js";
|
|
4
8
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAGnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAiD,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAGnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAiD,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,aAAa,EAAuB,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAuB,MAAM,WAAW,CAAC"}
|
package/dist/stack-builder.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Stack, type StackProps } from "aws-cdk-lib";
|
|
2
2
|
import { type IConstruct } from "constructs";
|
|
3
|
-
import { type
|
|
3
|
+
import { type Lifecycle } from "@composurecdk/core";
|
|
4
|
+
import { type ITaggedBuilder } from "./tagged-builder.js";
|
|
4
5
|
/**
|
|
5
6
|
* The build output of a {@link IStackBuilder}. Contains the CDK Stack
|
|
6
7
|
* created during {@link Lifecycle.build}.
|
|
@@ -16,54 +17,29 @@ export interface StackBuilderResult {
|
|
|
16
17
|
* as an overloaded method: call with a value to set it (returns the builder
|
|
17
18
|
* for chaining), or call with no arguments to read the current value.
|
|
18
19
|
*
|
|
19
|
-
* The builder implements {@link Lifecycle}, so it can be used directly as
|
|
20
|
-
* component in a {@link compose | composed system}
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* The builder implements {@link Lifecycle}, so it can be used directly as
|
|
21
|
+
* a component in a {@link compose | composed system}, or handed to a stack
|
|
22
|
+
* strategy via {@link singleStack} / {@link groupedStacks}. When handing a
|
|
23
|
+
* builder to a strategy that may be invoked after further configuration of
|
|
24
|
+
* the original, pass `builder.copy()` to snapshot the current state.
|
|
25
|
+
*
|
|
26
|
+
* Tags accumulated via {@link ITaggedBuilder.tag | .tag()} or
|
|
27
|
+
* {@link ITaggedBuilder.tags | .tags()} are applied to the resulting Stack.
|
|
28
|
+
* CloudFormation propagates stack-level tags to every resource the stack
|
|
29
|
+
* contains, so a single `.tag()` call on a stack reaches everything inside.
|
|
23
30
|
*
|
|
24
31
|
* @example
|
|
25
32
|
* ```ts
|
|
26
|
-
* const stack = createStackBuilder()
|
|
33
|
+
* const { stack } = createStackBuilder()
|
|
27
34
|
* .description("Network infrastructure")
|
|
28
35
|
* .terminationProtection(true)
|
|
36
|
+
* .tag("Owner", "platform")
|
|
29
37
|
* .build(app, "NetworkStack");
|
|
30
38
|
* ```
|
|
31
39
|
*/
|
|
32
|
-
export type IStackBuilder =
|
|
33
|
-
/**
|
|
34
|
-
* Adds a tag to the Stack. Tags are applied to the Stack and propagate
|
|
35
|
-
* to all resources within it.
|
|
36
|
-
*
|
|
37
|
-
* @param key - The tag key.
|
|
38
|
-
* @param value - The tag value.
|
|
39
|
-
* @returns The builder for chaining.
|
|
40
|
-
*/
|
|
41
|
-
tag(key: string, value: string): IStackBuilder;
|
|
42
|
-
/**
|
|
43
|
-
* Returns a {@link ScopeFactory} that creates Stacks with the builder's
|
|
44
|
-
* configured properties. Use this to integrate with
|
|
45
|
-
* {@link singleStack} or {@link groupedStacks} strategies.
|
|
46
|
-
*
|
|
47
|
-
* @returns A factory function compatible with stack strategies.
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```ts
|
|
51
|
-
* const factory = createStackBuilder()
|
|
52
|
-
* .terminationProtection(true)
|
|
53
|
-
* .toScopeFactory();
|
|
54
|
-
*
|
|
55
|
-
* compose({ ... }, { ... })
|
|
56
|
-
* .withStackStrategy(singleStack(factory))
|
|
57
|
-
* .build(app, "MySystem");
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
toScopeFactory(): ScopeFactory;
|
|
61
|
-
};
|
|
40
|
+
export type IStackBuilder = ITaggedBuilder<StackProps, StackBuilder>;
|
|
62
41
|
declare class StackBuilder implements Lifecycle<StackBuilderResult> {
|
|
63
|
-
#private;
|
|
64
42
|
props: Partial<StackProps>;
|
|
65
|
-
tag(key: string, value: string): this;
|
|
66
|
-
toScopeFactory(): ScopeFactory;
|
|
67
43
|
build(scope: IConstruct, id: string): StackBuilderResult;
|
|
68
44
|
}
|
|
69
45
|
/**
|
|
@@ -71,9 +47,10 @@ declare class StackBuilder implements Lifecycle<StackBuilderResult> {
|
|
|
71
47
|
*
|
|
72
48
|
* This is the entry point for declarative stack configuration. The returned
|
|
73
49
|
* builder exposes every {@link StackProps} property as a fluent setter/getter,
|
|
74
|
-
*
|
|
75
|
-
* {@link
|
|
76
|
-
*
|
|
50
|
+
* `.tag(key, value)` / `.tags({...})` for stack-level tagging, and `.copy()`
|
|
51
|
+
* for variant authoring. It implements {@link Lifecycle}, so it composes
|
|
52
|
+
* naturally and can be passed to {@link singleStack} or
|
|
53
|
+
* {@link groupedStacks}.
|
|
77
54
|
*
|
|
78
55
|
* @returns A fluent builder for a CloudFormation Stack.
|
|
79
56
|
*
|
|
@@ -83,16 +60,17 @@ declare class StackBuilder implements Lifecycle<StackBuilderResult> {
|
|
|
83
60
|
* const { stack } = createStackBuilder()
|
|
84
61
|
* .description("Service layer")
|
|
85
62
|
* .terminationProtection(true)
|
|
63
|
+
* .tag("Owner", "platform")
|
|
86
64
|
* .build(app, "ServiceStack");
|
|
87
65
|
*
|
|
88
|
-
* //
|
|
89
|
-
*
|
|
66
|
+
* // Hand a configured builder to a strategy. Use `.copy()` to snapshot
|
|
67
|
+
* // when the original may be mutated further.
|
|
68
|
+
* const base = createStackBuilder()
|
|
90
69
|
* .terminationProtection(true)
|
|
91
|
-
* .tag("team", "platform")
|
|
92
|
-
* .toScopeFactory();
|
|
70
|
+
* .tag("team", "platform");
|
|
93
71
|
*
|
|
94
72
|
* compose({ ... }, { ... })
|
|
95
|
-
* .withStackStrategy(singleStack(
|
|
73
|
+
* .withStackStrategy(singleStack(base.copy()))
|
|
96
74
|
* .build(app, "MySystem");
|
|
97
75
|
* ```
|
|
98
76
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack-builder.d.ts","sourceRoot":"","sources":["../src/stack-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"stack-builder.d.ts","sourceRoot":"","sources":["../src/stack-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,KAAK,cAAc,EAAiB,MAAM,qBAAqB,CAAC;AAEzE;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAErE,cAAM,YAAa,YAAW,SAAS,CAAC,kBAAkB,CAAC;IACzD,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,CAAM;IAEhC,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,kBAAkB;CAGzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,IAAI,aAAa,CAElD"}
|
package/dist/stack-builder.js
CHANGED
|
@@ -1,29 +1,9 @@
|
|
|
1
|
-
import { Stack
|
|
2
|
-
import {
|
|
1
|
+
import { Stack } from "aws-cdk-lib";
|
|
2
|
+
import { taggedBuilder } from "./tagged-builder.js";
|
|
3
3
|
class StackBuilder {
|
|
4
4
|
props = {};
|
|
5
|
-
#tags = [];
|
|
6
|
-
tag(key, value) {
|
|
7
|
-
this.#tags.push([key, value]);
|
|
8
|
-
return this;
|
|
9
|
-
}
|
|
10
|
-
toScopeFactory() {
|
|
11
|
-
const props = { ...this.props };
|
|
12
|
-
const tags = [...this.#tags];
|
|
13
|
-
return (scope, id) => {
|
|
14
|
-
const stack = new Stack(scope, id, props);
|
|
15
|
-
tags.forEach(([key, value]) => {
|
|
16
|
-
Tags.of(stack).add(key, value);
|
|
17
|
-
});
|
|
18
|
-
return stack;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
5
|
build(scope, id) {
|
|
22
|
-
|
|
23
|
-
this.#tags.forEach(([key, value]) => {
|
|
24
|
-
Tags.of(stack).add(key, value);
|
|
25
|
-
});
|
|
26
|
-
return { stack };
|
|
6
|
+
return { stack: new Stack(scope, id, this.props) };
|
|
27
7
|
}
|
|
28
8
|
}
|
|
29
9
|
/**
|
|
@@ -31,9 +11,10 @@ class StackBuilder {
|
|
|
31
11
|
*
|
|
32
12
|
* This is the entry point for declarative stack configuration. The returned
|
|
33
13
|
* builder exposes every {@link StackProps} property as a fluent setter/getter,
|
|
34
|
-
*
|
|
35
|
-
* {@link
|
|
36
|
-
*
|
|
14
|
+
* `.tag(key, value)` / `.tags({...})` for stack-level tagging, and `.copy()`
|
|
15
|
+
* for variant authoring. It implements {@link Lifecycle}, so it composes
|
|
16
|
+
* naturally and can be passed to {@link singleStack} or
|
|
17
|
+
* {@link groupedStacks}.
|
|
37
18
|
*
|
|
38
19
|
* @returns A fluent builder for a CloudFormation Stack.
|
|
39
20
|
*
|
|
@@ -43,20 +24,21 @@ class StackBuilder {
|
|
|
43
24
|
* const { stack } = createStackBuilder()
|
|
44
25
|
* .description("Service layer")
|
|
45
26
|
* .terminationProtection(true)
|
|
27
|
+
* .tag("Owner", "platform")
|
|
46
28
|
* .build(app, "ServiceStack");
|
|
47
29
|
*
|
|
48
|
-
* //
|
|
49
|
-
*
|
|
30
|
+
* // Hand a configured builder to a strategy. Use `.copy()` to snapshot
|
|
31
|
+
* // when the original may be mutated further.
|
|
32
|
+
* const base = createStackBuilder()
|
|
50
33
|
* .terminationProtection(true)
|
|
51
|
-
* .tag("team", "platform")
|
|
52
|
-
* .toScopeFactory();
|
|
34
|
+
* .tag("team", "platform");
|
|
53
35
|
*
|
|
54
36
|
* compose({ ... }, { ... })
|
|
55
|
-
* .withStackStrategy(singleStack(
|
|
37
|
+
* .withStackStrategy(singleStack(base.copy()))
|
|
56
38
|
* .build(app, "MySystem");
|
|
57
39
|
* ```
|
|
58
40
|
*/
|
|
59
41
|
export function createStackBuilder() {
|
|
60
|
-
return
|
|
42
|
+
return taggedBuilder(StackBuilder);
|
|
61
43
|
}
|
|
62
44
|
//# sourceMappingURL=stack-builder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack-builder.js","sourceRoot":"","sources":["../src/stack-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAmB,
|
|
1
|
+
{"version":3,"file":"stack-builder.js","sourceRoot":"","sources":["../src/stack-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAmB,MAAM,aAAa,CAAC;AAGrD,OAAO,EAAuB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAwCzE,MAAM,YAAY;IAChB,KAAK,GAAwB,EAAE,CAAC;IAEhC,KAAK,CAAC,KAAiB,EAAE,EAAU;QACjC,OAAO,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IACrD,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,aAAa,CAA2B,YAAY,CAAC,CAAC;AAC/D,CAAC"}
|
package/dist/strategies.d.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Lifecycle, type StackStrategy } from "@composurecdk/core";
|
|
2
|
+
import { type StackBuilderResult } from "./stack-builder.js";
|
|
2
3
|
/**
|
|
3
4
|
* Creates a strategy that places all components in a single Stack.
|
|
4
5
|
*
|
|
5
|
-
* The Stack is created lazily on the first call to `resolve` and reused
|
|
6
|
-
* all subsequent components.
|
|
6
|
+
* The Stack is created lazily on the first call to `resolve` and reused
|
|
7
|
+
* for all subsequent components. The supplied `builder` is invoked at that
|
|
8
|
+
* point as `builder.build(scope, id).stack`, so any tags applied via the
|
|
9
|
+
* builder's `.tag()` method land on the resulting Stack.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
11
|
+
* The builder's `build()` is called lazily. If the original may be mutated
|
|
12
|
+
* after this call, pass `builder.copy()` to snapshot the configuration.
|
|
13
|
+
*
|
|
14
|
+
* @param builder - Optional builder for configuring the Stack. Defaults to
|
|
15
|
+
* a fresh {@link createStackBuilder} per call, producing a plain Stack
|
|
16
|
+
* with no extra configuration. For non-Stack scope types, use
|
|
17
|
+
* `singleStack` from `@composurecdk/core` directly with a `ScopeFactory`.
|
|
10
18
|
* @returns A {@link StackStrategy} that groups all components into one Stack.
|
|
11
19
|
*
|
|
12
20
|
* @example
|
|
@@ -14,20 +22,33 @@ import { type ScopeFactory, type StackStrategy } from "@composurecdk/core";
|
|
|
14
22
|
* compose({ handler, api }, { handler: [], api: ["handler"] })
|
|
15
23
|
* .withStackStrategy(singleStack())
|
|
16
24
|
* .build(app, "MySystem");
|
|
25
|
+
*
|
|
26
|
+
* // With a configured builder:
|
|
27
|
+
* const base = createStackBuilder().tag("team", "platform");
|
|
28
|
+
* compose({ ... }, { ... })
|
|
29
|
+
* .withStackStrategy(singleStack(base.copy()))
|
|
30
|
+
* .build(app, "MySystem");
|
|
17
31
|
* ```
|
|
18
32
|
*/
|
|
19
|
-
export declare function singleStack(
|
|
33
|
+
export declare function singleStack(builder?: Lifecycle<StackBuilderResult>): StackStrategy;
|
|
20
34
|
/**
|
|
21
35
|
* Creates a strategy that groups components into named Stacks determined by
|
|
22
36
|
* a classifier function.
|
|
23
37
|
*
|
|
24
|
-
* Components that return the same group key share a Stack. Stacks are
|
|
25
|
-
* lazily as new group keys are encountered.
|
|
38
|
+
* Components that return the same group key share a Stack. Stacks are
|
|
39
|
+
* created lazily as new group keys are encountered. The supplied `builder`
|
|
40
|
+
* is invoked once per group as `builder.build(scope, id).stack` with
|
|
41
|
+
* `id = ${systemId}-${group}`, so any configured tags propagate to every
|
|
42
|
+
* Stack the strategy creates.
|
|
43
|
+
*
|
|
44
|
+
* The builder's `build()` is called lazily. If the original may be mutated
|
|
45
|
+
* after this call, pass `builder.copy()` to snapshot the configuration.
|
|
26
46
|
*
|
|
27
47
|
* @param classify - A function that maps a component key to a group name.
|
|
28
|
-
* @param
|
|
29
|
-
* a
|
|
30
|
-
*
|
|
48
|
+
* @param builder - Optional builder for configuring each Stack. Defaults to
|
|
49
|
+
* a fresh {@link createStackBuilder} per call. For non-Stack scope types,
|
|
50
|
+
* use `groupedStacks` from `@composurecdk/core` directly with a
|
|
51
|
+
* `ScopeFactory`.
|
|
31
52
|
* @returns A {@link StackStrategy} that groups components by classifier output.
|
|
32
53
|
*
|
|
33
54
|
* @example
|
|
@@ -35,7 +56,15 @@ export declare function singleStack(factory?: ScopeFactory): StackStrategy;
|
|
|
35
56
|
* compose({ handler, api, table }, { ... })
|
|
36
57
|
* .withStackStrategy(groupedStacks(key => key === "table" ? "persistence" : "service"))
|
|
37
58
|
* .build(app, "MySystem");
|
|
59
|
+
*
|
|
60
|
+
* // With a configured builder, snapshotted via .copy():
|
|
61
|
+
* const base = createStackBuilder().tag("team", "platform");
|
|
62
|
+
* compose({ ... }, { ... })
|
|
63
|
+
* .withStackStrategy(
|
|
64
|
+
* groupedStacks(key => key === "table" ? "persistence" : "service", base.copy()),
|
|
65
|
+
* )
|
|
66
|
+
* .build(app, "MySystem");
|
|
38
67
|
* ```
|
|
39
68
|
*/
|
|
40
|
-
export declare function groupedStacks(classify: (componentKey: string) => string,
|
|
69
|
+
export declare function groupedStacks(classify: (componentKey: string) => string, builder?: Lifecycle<StackBuilderResult>): StackStrategy;
|
|
41
70
|
//# sourceMappingURL=strategies.d.ts.map
|
package/dist/strategies.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strategies.d.ts","sourceRoot":"","sources":["../src/strategies.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,
|
|
1
|
+
{"version":3,"file":"strategies.d.ts","sourceRoot":"","sources":["../src/strategies.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAGnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,kBAAkB,CAAC,GAAG,aAAa,CAGlF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,EAC1C,OAAO,CAAC,EAAE,SAAS,CAAC,kBAAkB,CAAC,GACtC,aAAa,CAGf"}
|
package/dist/strategies.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { singleStack as coreSingleStack, groupedStacks as coreGroupedStacks, } from "@composurecdk/core";
|
|
2
2
|
import { createStackBuilder } from "./stack-builder.js";
|
|
3
|
-
function defaultFactory() {
|
|
4
|
-
return createStackBuilder().toScopeFactory();
|
|
5
|
-
}
|
|
6
3
|
/**
|
|
7
4
|
* Creates a strategy that places all components in a single Stack.
|
|
8
5
|
*
|
|
9
|
-
* The Stack is created lazily on the first call to `resolve` and reused
|
|
10
|
-
* all subsequent components.
|
|
6
|
+
* The Stack is created lazily on the first call to `resolve` and reused
|
|
7
|
+
* for all subsequent components. The supplied `builder` is invoked at that
|
|
8
|
+
* point as `builder.build(scope, id).stack`, so any tags applied via the
|
|
9
|
+
* builder's `.tag()` method land on the resulting Stack.
|
|
10
|
+
*
|
|
11
|
+
* The builder's `build()` is called lazily. If the original may be mutated
|
|
12
|
+
* after this call, pass `builder.copy()` to snapshot the configuration.
|
|
11
13
|
*
|
|
12
|
-
* @param
|
|
13
|
-
* a
|
|
14
|
+
* @param builder - Optional builder for configuring the Stack. Defaults to
|
|
15
|
+
* a fresh {@link createStackBuilder} per call, producing a plain Stack
|
|
16
|
+
* with no extra configuration. For non-Stack scope types, use
|
|
17
|
+
* `singleStack` from `@composurecdk/core` directly with a `ScopeFactory`.
|
|
14
18
|
* @returns A {@link StackStrategy} that groups all components into one Stack.
|
|
15
19
|
*
|
|
16
20
|
* @example
|
|
@@ -18,22 +22,36 @@ function defaultFactory() {
|
|
|
18
22
|
* compose({ handler, api }, { handler: [], api: ["handler"] })
|
|
19
23
|
* .withStackStrategy(singleStack())
|
|
20
24
|
* .build(app, "MySystem");
|
|
25
|
+
*
|
|
26
|
+
* // With a configured builder:
|
|
27
|
+
* const base = createStackBuilder().tag("team", "platform");
|
|
28
|
+
* compose({ ... }, { ... })
|
|
29
|
+
* .withStackStrategy(singleStack(base.copy()))
|
|
30
|
+
* .build(app, "MySystem");
|
|
21
31
|
* ```
|
|
22
32
|
*/
|
|
23
|
-
export function singleStack(
|
|
24
|
-
|
|
33
|
+
export function singleStack(builder) {
|
|
34
|
+
const stackBuilder = builder ?? createStackBuilder();
|
|
35
|
+
return coreSingleStack((scope, id) => stackBuilder.build(scope, id).stack);
|
|
25
36
|
}
|
|
26
37
|
/**
|
|
27
38
|
* Creates a strategy that groups components into named Stacks determined by
|
|
28
39
|
* a classifier function.
|
|
29
40
|
*
|
|
30
|
-
* Components that return the same group key share a Stack. Stacks are
|
|
31
|
-
* lazily as new group keys are encountered.
|
|
41
|
+
* Components that return the same group key share a Stack. Stacks are
|
|
42
|
+
* created lazily as new group keys are encountered. The supplied `builder`
|
|
43
|
+
* is invoked once per group as `builder.build(scope, id).stack` with
|
|
44
|
+
* `id = ${systemId}-${group}`, so any configured tags propagate to every
|
|
45
|
+
* Stack the strategy creates.
|
|
46
|
+
*
|
|
47
|
+
* The builder's `build()` is called lazily. If the original may be mutated
|
|
48
|
+
* after this call, pass `builder.copy()` to snapshot the configuration.
|
|
32
49
|
*
|
|
33
50
|
* @param classify - A function that maps a component key to a group name.
|
|
34
|
-
* @param
|
|
35
|
-
* a
|
|
36
|
-
*
|
|
51
|
+
* @param builder - Optional builder for configuring each Stack. Defaults to
|
|
52
|
+
* a fresh {@link createStackBuilder} per call. For non-Stack scope types,
|
|
53
|
+
* use `groupedStacks` from `@composurecdk/core` directly with a
|
|
54
|
+
* `ScopeFactory`.
|
|
37
55
|
* @returns A {@link StackStrategy} that groups components by classifier output.
|
|
38
56
|
*
|
|
39
57
|
* @example
|
|
@@ -41,9 +59,18 @@ export function singleStack(factory) {
|
|
|
41
59
|
* compose({ handler, api, table }, { ... })
|
|
42
60
|
* .withStackStrategy(groupedStacks(key => key === "table" ? "persistence" : "service"))
|
|
43
61
|
* .build(app, "MySystem");
|
|
62
|
+
*
|
|
63
|
+
* // With a configured builder, snapshotted via .copy():
|
|
64
|
+
* const base = createStackBuilder().tag("team", "platform");
|
|
65
|
+
* compose({ ... }, { ... })
|
|
66
|
+
* .withStackStrategy(
|
|
67
|
+
* groupedStacks(key => key === "table" ? "persistence" : "service", base.copy()),
|
|
68
|
+
* )
|
|
69
|
+
* .build(app, "MySystem");
|
|
44
70
|
* ```
|
|
45
71
|
*/
|
|
46
|
-
export function groupedStacks(classify,
|
|
47
|
-
|
|
72
|
+
export function groupedStacks(classify, builder) {
|
|
73
|
+
const stackBuilder = builder ?? createStackBuilder();
|
|
74
|
+
return coreGroupedStacks(classify, (scope, id) => stackBuilder.build(scope, id).stack);
|
|
48
75
|
}
|
|
49
76
|
//# sourceMappingURL=strategies.js.map
|
package/dist/strategies.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strategies.js","sourceRoot":"","sources":["../src/strategies.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,WAAW,IAAI,eAAe,EAC9B,aAAa,IAAI,iBAAiB,GACnC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"strategies.js","sourceRoot":"","sources":["../src/strategies.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,WAAW,IAAI,eAAe,EAC9B,aAAa,IAAI,iBAAiB,GACnC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAA2B,MAAM,oBAAoB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,WAAW,CAAC,OAAuC;IACjE,MAAM,YAAY,GAAG,OAAO,IAAI,kBAAkB,EAAE,CAAC;IACrD,OAAO,eAAe,CAAC,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,aAAa,CAC3B,QAA0C,EAC1C,OAAuC;IAEvC,MAAM,YAAY,GAAG,OAAO,IAAI,kBAAkB,EAAE,CAAC;IACrD,OAAO,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;AACzF,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates a single tag key/value pair against AWS tag constraints.
|
|
3
|
+
*
|
|
4
|
+
* Throws synchronously at the call site so authors see the failure where the
|
|
5
|
+
* bad value was written, not at deploy time. Validates:
|
|
6
|
+
*
|
|
7
|
+
* - `key` is non-empty and at most {@link KEY_MAX} characters.
|
|
8
|
+
* - `key` does not start with the reserved `aws:` prefix (case-insensitive).
|
|
9
|
+
* - `value` is at most {@link VALUE_MAX} characters.
|
|
10
|
+
* - both `key` and `value` use only the AWS-permitted character set.
|
|
11
|
+
*
|
|
12
|
+
* The regex matches Unicode letters, digits, and whitespace plus the
|
|
13
|
+
* documented punctuation set, so non-ASCII tags are accepted as AWS
|
|
14
|
+
* supports them.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateTag(key: string, value: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Validates every entry of a record via {@link validateTag}, throwing on the
|
|
19
|
+
* first invalid pair so the failure surfaces at the configuring call site.
|
|
20
|
+
*/
|
|
21
|
+
export declare function validateTagRecord(values: Record<string, string>): void;
|
|
22
|
+
//# sourceMappingURL=tag-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag-validator.d.ts","sourceRoot":"","sources":["../src/tag-validator.ts"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CA2B5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAItE"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS allows letters, digits, whitespace, and the symbols `_ . : / = + - @`
|
|
3
|
+
* in tag keys and values, with non-ASCII letters/digits permitted via
|
|
4
|
+
* Unicode classes. Empty values are permitted by AWS for `Value`, but
|
|
5
|
+
* empty `Key` is not. The character set is validated against this regex.
|
|
6
|
+
*
|
|
7
|
+
* `\p{Z}` matches the full Unicode separator class — slightly more
|
|
8
|
+
* permissive than AWS's documented "white space," but errors on the side
|
|
9
|
+
* of accepting input that the AWS API may yet reject at deploy time. The
|
|
10
|
+
* extra rejections happen later but are reported in the API response.
|
|
11
|
+
*
|
|
12
|
+
* @see https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html
|
|
13
|
+
*/
|
|
14
|
+
const TAG_CHAR_RE = /^[\p{L}\p{Z}\p{N}_.:/=+\-@]*$/u;
|
|
15
|
+
const KEY_MAX = 128;
|
|
16
|
+
const VALUE_MAX = 256;
|
|
17
|
+
/**
|
|
18
|
+
* Validates a single tag key/value pair against AWS tag constraints.
|
|
19
|
+
*
|
|
20
|
+
* Throws synchronously at the call site so authors see the failure where the
|
|
21
|
+
* bad value was written, not at deploy time. Validates:
|
|
22
|
+
*
|
|
23
|
+
* - `key` is non-empty and at most {@link KEY_MAX} characters.
|
|
24
|
+
* - `key` does not start with the reserved `aws:` prefix (case-insensitive).
|
|
25
|
+
* - `value` is at most {@link VALUE_MAX} characters.
|
|
26
|
+
* - both `key` and `value` use only the AWS-permitted character set.
|
|
27
|
+
*
|
|
28
|
+
* The regex matches Unicode letters, digits, and whitespace plus the
|
|
29
|
+
* documented punctuation set, so non-ASCII tags are accepted as AWS
|
|
30
|
+
* supports them.
|
|
31
|
+
*/
|
|
32
|
+
export function validateTag(key, value) {
|
|
33
|
+
if (key.length === 0) {
|
|
34
|
+
throw new Error("Tag key must be non-empty.");
|
|
35
|
+
}
|
|
36
|
+
if (key.length > KEY_MAX) {
|
|
37
|
+
throw new Error(`Tag key "${key}" exceeds ${String(KEY_MAX)}-character limit.`);
|
|
38
|
+
}
|
|
39
|
+
if (key.toLowerCase().startsWith("aws:")) {
|
|
40
|
+
throw new Error(`Tag key "${key}" uses reserved "aws:" prefix; AWS rejects user tags with this prefix.`);
|
|
41
|
+
}
|
|
42
|
+
if (!TAG_CHAR_RE.test(key)) {
|
|
43
|
+
throw new Error(`Tag key "${key}" contains characters outside the AWS tag character set ` +
|
|
44
|
+
"(letters, digits, whitespace, and `_ . : / = + - @`).");
|
|
45
|
+
}
|
|
46
|
+
if (value.length > VALUE_MAX) {
|
|
47
|
+
throw new Error(`Tag value for key "${key}" exceeds ${String(VALUE_MAX)}-character limit.`);
|
|
48
|
+
}
|
|
49
|
+
if (!TAG_CHAR_RE.test(value)) {
|
|
50
|
+
throw new Error(`Tag value for key "${key}" contains characters outside the AWS tag character set ` +
|
|
51
|
+
"(letters, digits, whitespace, and `_ . : / = + - @`).");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validates every entry of a record via {@link validateTag}, throwing on the
|
|
56
|
+
* first invalid pair so the failure surfaces at the configuring call site.
|
|
57
|
+
*/
|
|
58
|
+
export function validateTagRecord(values) {
|
|
59
|
+
for (const [key, value] of Object.entries(values)) {
|
|
60
|
+
validateTag(key, value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=tag-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag-validator.js","sourceRoot":"","sources":["../src/tag-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,GAAG,gCAAgC,CAAC;AAErD,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,KAAa;IACpD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,YAAY,GAAG,aAAa,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,wEAAwE,CACxF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,0DAA0D;YACvE,uDAAuD,CAC1D,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,aAAa,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,sBAAsB,GAAG,0DAA0D;YACjF,uDAAuD,CAC1D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA8B;IAC9D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `name` attached to the `process.emitWarning` call when `.tag(k, v)` /
|
|
3
|
+
* `.tags({...})` overwrites an existing key. Exported so callers with
|
|
4
|
+
* layered configuration (a base builder plus per-environment refinements
|
|
5
|
+
* that intentionally override) can filter via
|
|
6
|
+
* `process.on("warning", w => { if (w.name !== TAG_OVERRIDE_WARNING_NAME) ... })`
|
|
7
|
+
* or the Node 21.3+ `--disable-warning=ComposureCDKTagOverride` CLI flag.
|
|
8
|
+
*/
|
|
9
|
+
export declare const TAG_OVERRIDE_WARNING_NAME = "ComposureCDKTagOverride";
|
|
10
|
+
type Constructor<T> = new () => T;
|
|
11
|
+
interface ObjectWithProps<Props extends object> {
|
|
12
|
+
props: Partial<Props>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A fluent builder extended with tag-accumulating methods. Returned by
|
|
16
|
+
* {@link taggedBuilder}.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors `IBuilder<Props, T>` from `@composurecdk/core` but rewrites every
|
|
19
|
+
* chainable return type to `ITaggedBuilder<Props, T>` so the augmenting
|
|
20
|
+
* `.tag()` / `.tags()` methods stay reachable after any prop setter or
|
|
21
|
+
* chainable method on `T`. {@link ITaggedBuilder.copy | `.copy()`} also
|
|
22
|
+
* returns a tagged builder so the chain survives variant authoring.
|
|
23
|
+
*
|
|
24
|
+
* Tags accumulated via {@link ITaggedBuilder.tag | .tag()} and
|
|
25
|
+
* {@link ITaggedBuilder.tags | .tags()} are applied to every construct in
|
|
26
|
+
* the build result — see {@link applyBuilderTags} for the exact walk.
|
|
27
|
+
*
|
|
28
|
+
* @typeParam Props - The configurable properties.
|
|
29
|
+
* @typeParam T - The target class the builder wraps.
|
|
30
|
+
*/
|
|
31
|
+
export type ITaggedBuilder<Props extends object, T> = {
|
|
32
|
+
[K in keyof Props]-?: ((arg: Props[K]) => ITaggedBuilder<Props, T>) & (() => Props[K]);
|
|
33
|
+
} & {
|
|
34
|
+
[K in keyof T]: T[K] extends (...args: infer A) => T ? (...args: A) => ITaggedBuilder<Props, T> : T[K];
|
|
35
|
+
} & {
|
|
36
|
+
/**
|
|
37
|
+
* Adds a single tag. Validates the key/value at call time and throws on
|
|
38
|
+
* AWS-rejected inputs (empty key, `aws:` prefix, oversize, disallowed
|
|
39
|
+
* characters).
|
|
40
|
+
*
|
|
41
|
+
* Repeated keys overwrite earlier values and emit a process warning so the
|
|
42
|
+
* override is visible at the call site.
|
|
43
|
+
*
|
|
44
|
+
* @param key - The tag key.
|
|
45
|
+
* @param value - The tag value.
|
|
46
|
+
* @returns The builder for chaining.
|
|
47
|
+
*/
|
|
48
|
+
tag(key: string, value: string): ITaggedBuilder<Props, T>;
|
|
49
|
+
/**
|
|
50
|
+
* Adds many tags at once. Each entry is validated independently as if
|
|
51
|
+
* passed to {@link ITaggedBuilder.tag | .tag()}. Existing keys are
|
|
52
|
+
* overwritten with a process warning.
|
|
53
|
+
*
|
|
54
|
+
* @param values - A record of tag keys to values.
|
|
55
|
+
* @returns The builder for chaining.
|
|
56
|
+
*/
|
|
57
|
+
tags(values: Record<string, string>): ITaggedBuilder<Props, T>;
|
|
58
|
+
/**
|
|
59
|
+
* Returns an independent tagged builder with the same configured props
|
|
60
|
+
* and a deep clone of the accumulated tags.
|
|
61
|
+
*
|
|
62
|
+
* Mirrors {@link IBuilder.copy} from `@composurecdk/core` but preserves
|
|
63
|
+
* the tagging surface. Mutations to the returned builder's tags do not
|
|
64
|
+
* affect the original, and vice versa.
|
|
65
|
+
*/
|
|
66
|
+
copy(): ITaggedBuilder<Props, T>;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Wraps {@link Builder} to add the {@link ITaggedBuilder.tag | .tag()} and
|
|
70
|
+
* {@link ITaggedBuilder.tags | .tags()} accumulators and apply the
|
|
71
|
+
* collected tags to every construct in the build result.
|
|
72
|
+
*
|
|
73
|
+
* The wrapper maintains its own outer Proxy around the inner builder Proxy
|
|
74
|
+
* created by `@composurecdk/core`. The outer Proxy:
|
|
75
|
+
*
|
|
76
|
+
* 1. Intercepts `.tag(k, v)` and `.tags({...})` to validate inputs and
|
|
77
|
+
* accumulate them in an insertion-ordered map. Repeated keys overwrite
|
|
78
|
+
* earlier values and emit `process.emitWarning` so the override is
|
|
79
|
+
* visible at the configuring call site.
|
|
80
|
+
* 2. Intercepts `build()` to call {@link applyBuilderTags} on the result
|
|
81
|
+
* after the inner build completes.
|
|
82
|
+
* 3. Intercepts `copy()` so the returned builder is itself a tagged builder
|
|
83
|
+
* with an independent clone of the accumulator. Without this, `.copy()`
|
|
84
|
+
* on a tagged builder would surface the bare inner Proxy and silently
|
|
85
|
+
* drop both the tag methods and the build-time walker.
|
|
86
|
+
* 4. Passes every other access through to the inner Proxy unchanged. Inner
|
|
87
|
+
* methods that return the inner Proxy (chainable setters) are
|
|
88
|
+
* re-wrapped so the chain returns the outer Proxy and the new tag
|
|
89
|
+
* methods stay reachable.
|
|
90
|
+
*
|
|
91
|
+
* Each builder factory in the library opts in by calling `taggedBuilder()`
|
|
92
|
+
* instead of `Builder()`. Custom builders authored outside the library can
|
|
93
|
+
* use plain `Builder()` and forgo tagging.
|
|
94
|
+
*
|
|
95
|
+
* @typeParam Props - The configurable properties.
|
|
96
|
+
* @typeParam T - The target class the builder wraps.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* export function createBucketBuilder(): IBucketBuilder {
|
|
101
|
+
* return taggedBuilder<BucketBuilderProps, BucketBuilder>(BucketBuilder);
|
|
102
|
+
* }
|
|
103
|
+
*
|
|
104
|
+
* createBucketBuilder()
|
|
105
|
+
* .tag("Project", "claude-rig")
|
|
106
|
+
* .tags({ Owner: "platform", Environment: "prod" })
|
|
107
|
+
* .build(stack, "Bucket");
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function taggedBuilder<Props extends object, T extends ObjectWithProps<Props>>(constructor: Constructor<T>): ITaggedBuilder<Props, T>;
|
|
111
|
+
export {};
|
|
112
|
+
//# sourceMappingURL=tagged-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tagged-builder.d.ts","sourceRoot":"","sources":["../src/tagged-builder.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AAEnE,KAAK,WAAW,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC;AAElC,UAAU,eAAe,CAAC,KAAK,SAAS,MAAM;IAC5C,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,IAAI;KACnD,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;CACvF,GAAG;KACD,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAChD,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,GACxC,CAAC,CAAC,CAAC,CAAC;CACT,GAAG;IACF;;;;;;;;;;;OAWG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAE1D;;;;;;;OAOG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAE/D;;;;;;;OAOG;IACH,IAAI,IAAI,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;CAClC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,aAAa,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,SAAS,eAAe,CAAC,KAAK,CAAC,EAClF,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,GAC1B,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAG1B"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Builder } from "@composurecdk/core";
|
|
2
|
+
import { applyBuilderTags } from "./apply-builder-tags.js";
|
|
3
|
+
import { validateTag, validateTagRecord } from "./tag-validator.js";
|
|
4
|
+
/**
|
|
5
|
+
* The `name` attached to the `process.emitWarning` call when `.tag(k, v)` /
|
|
6
|
+
* `.tags({...})` overwrites an existing key. Exported so callers with
|
|
7
|
+
* layered configuration (a base builder plus per-environment refinements
|
|
8
|
+
* that intentionally override) can filter via
|
|
9
|
+
* `process.on("warning", w => { if (w.name !== TAG_OVERRIDE_WARNING_NAME) ... })`
|
|
10
|
+
* or the Node 21.3+ `--disable-warning=ComposureCDKTagOverride` CLI flag.
|
|
11
|
+
*/
|
|
12
|
+
export const TAG_OVERRIDE_WARNING_NAME = "ComposureCDKTagOverride";
|
|
13
|
+
/**
|
|
14
|
+
* Wraps {@link Builder} to add the {@link ITaggedBuilder.tag | .tag()} and
|
|
15
|
+
* {@link ITaggedBuilder.tags | .tags()} accumulators and apply the
|
|
16
|
+
* collected tags to every construct in the build result.
|
|
17
|
+
*
|
|
18
|
+
* The wrapper maintains its own outer Proxy around the inner builder Proxy
|
|
19
|
+
* created by `@composurecdk/core`. The outer Proxy:
|
|
20
|
+
*
|
|
21
|
+
* 1. Intercepts `.tag(k, v)` and `.tags({...})` to validate inputs and
|
|
22
|
+
* accumulate them in an insertion-ordered map. Repeated keys overwrite
|
|
23
|
+
* earlier values and emit `process.emitWarning` so the override is
|
|
24
|
+
* visible at the configuring call site.
|
|
25
|
+
* 2. Intercepts `build()` to call {@link applyBuilderTags} on the result
|
|
26
|
+
* after the inner build completes.
|
|
27
|
+
* 3. Intercepts `copy()` so the returned builder is itself a tagged builder
|
|
28
|
+
* with an independent clone of the accumulator. Without this, `.copy()`
|
|
29
|
+
* on a tagged builder would surface the bare inner Proxy and silently
|
|
30
|
+
* drop both the tag methods and the build-time walker.
|
|
31
|
+
* 4. Passes every other access through to the inner Proxy unchanged. Inner
|
|
32
|
+
* methods that return the inner Proxy (chainable setters) are
|
|
33
|
+
* re-wrapped so the chain returns the outer Proxy and the new tag
|
|
34
|
+
* methods stay reachable.
|
|
35
|
+
*
|
|
36
|
+
* Each builder factory in the library opts in by calling `taggedBuilder()`
|
|
37
|
+
* instead of `Builder()`. Custom builders authored outside the library can
|
|
38
|
+
* use plain `Builder()` and forgo tagging.
|
|
39
|
+
*
|
|
40
|
+
* @typeParam Props - The configurable properties.
|
|
41
|
+
* @typeParam T - The target class the builder wraps.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* export function createBucketBuilder(): IBucketBuilder {
|
|
46
|
+
* return taggedBuilder<BucketBuilderProps, BucketBuilder>(BucketBuilder);
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* createBucketBuilder()
|
|
50
|
+
* .tag("Project", "claude-rig")
|
|
51
|
+
* .tags({ Owner: "platform", Environment: "prod" })
|
|
52
|
+
* .build(stack, "Bucket");
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function taggedBuilder(constructor) {
|
|
56
|
+
const inner = Builder(constructor);
|
|
57
|
+
return wrapTagged(inner, new Map());
|
|
58
|
+
}
|
|
59
|
+
function wrapTagged(inner, accumulator) {
|
|
60
|
+
const recordTag = (key, value) => {
|
|
61
|
+
if (accumulator.has(key)) {
|
|
62
|
+
const previous = accumulator.get(key);
|
|
63
|
+
process.emitWarning(`Tag "${key}" was already set to "${previous ?? ""}" and is being overwritten with "${value}". ` +
|
|
64
|
+
"Last write wins; remove the duplicate to silence this warning.", { type: TAG_OVERRIDE_WARNING_NAME });
|
|
65
|
+
}
|
|
66
|
+
accumulator.set(key, value);
|
|
67
|
+
};
|
|
68
|
+
const buildFn = (...args) => {
|
|
69
|
+
const target = inner;
|
|
70
|
+
const result = target.build(...args);
|
|
71
|
+
applyBuilderTags(result, accumulator);
|
|
72
|
+
return result;
|
|
73
|
+
};
|
|
74
|
+
const copyFn = () => {
|
|
75
|
+
const innerCopy = inner;
|
|
76
|
+
return wrapTagged(innerCopy.copy(), new Map(accumulator));
|
|
77
|
+
};
|
|
78
|
+
// `tagFn` / `tagsFn` and `outer` form a cycle: the interceptors return
|
|
79
|
+
// `outer` for chaining, and `outer`'s `get` trap returns the interceptors.
|
|
80
|
+
// Resolved by Proxy laziness: the `get` trap closes over the names but
|
|
81
|
+
// does not read them until a property is accessed, by which point the
|
|
82
|
+
// `const`s below have initialised.
|
|
83
|
+
const outer = new Proxy(inner, {
|
|
84
|
+
get(target, prop, receiver) {
|
|
85
|
+
if (prop === "tag")
|
|
86
|
+
return tagFn;
|
|
87
|
+
if (prop === "tags")
|
|
88
|
+
return tagsFn;
|
|
89
|
+
if (prop === "build")
|
|
90
|
+
return buildFn;
|
|
91
|
+
if (prop === "copy")
|
|
92
|
+
return copyFn;
|
|
93
|
+
const value = Reflect.get(target, prop, receiver);
|
|
94
|
+
if (typeof value === "function") {
|
|
95
|
+
return (...args) => {
|
|
96
|
+
const ret = value.apply(target, args);
|
|
97
|
+
return ret === inner ? outer : ret;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
const tagFn = (key, value) => {
|
|
104
|
+
validateTag(key, value);
|
|
105
|
+
recordTag(key, value);
|
|
106
|
+
return outer;
|
|
107
|
+
};
|
|
108
|
+
const tagsFn = (values) => {
|
|
109
|
+
validateTagRecord(values);
|
|
110
|
+
for (const [key, value] of Object.entries(values)) {
|
|
111
|
+
recordTag(key, value);
|
|
112
|
+
}
|
|
113
|
+
return outer;
|
|
114
|
+
};
|
|
115
|
+
return outer;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=tagged-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tagged-builder.js","sourceRoot":"","sources":["../src/tagged-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAiB,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEpE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAmEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,UAAU,aAAa,CAC3B,WAA2B;IAE3B,MAAM,KAAK,GAAG,OAAO,CAAW,WAAW,CAAC,CAAC;IAC7C,OAAO,UAAU,CAAW,KAAK,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,UAAU,CACjB,KAAyB,EACzB,WAAgC;IAEhC,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,KAAa,EAAQ,EAAE;QACrD,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACtC,OAAO,CAAC,WAAW,CACjB,QAAQ,GAAG,yBAAyB,QAAQ,IAAI,EAAE,oCAAoC,KAAK,KAAK;gBAC9F,gEAAgE,EAClE,EAAE,IAAI,EAAE,yBAAyB,EAAE,CACpC,CAAC;QACJ,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,GAAG,IAAe,EAAU,EAAE;QAC7C,MAAM,MAAM,GAAG,KAA0D,CAAC;QAC1E,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QACrC,gBAAgB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACtC,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,GAA6B,EAAE;QAC5C,MAAM,SAAS,GAAG,KAAsD,CAAC;QACzE,OAAO,UAAU,CAAW,SAAS,CAAC,IAAI,EAAE,EAAE,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC;IAEF,uEAAuE;IACvE,2EAA2E;IAC3E,uEAAuE;IACvE,sEAAsE;IACtE,mCAAmC;IACnC,MAAM,KAAK,GAA6B,IAAI,KAAK,CAAC,KAAK,EAAE;QACvD,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;YACxB,IAAI,IAAI,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC;YACjC,IAAI,IAAI,KAAK,MAAM;gBAAE,OAAO,MAAM,CAAC;YACnC,IAAI,IAAI,KAAK,OAAO;gBAAE,OAAO,OAAO,CAAC;YACrC,IAAI,IAAI,KAAK,MAAM;gBAAE,OAAO,MAAM,CAAC;YAEnC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAY,CAAC;YAC7D,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;gBAChC,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;oBAC5B,MAAM,GAAG,GAAI,KAAsC,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;oBACxE,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;gBACrC,CAAC,CAAC;YACJ,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAA6B,CAAC;IAE/B,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,KAAa,EAA4B,EAAE;QACrE,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACxB,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,CAAC,MAA8B,EAA4B,EAAE;QAC1E,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACxB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/tags.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type AfterBuildHook } from "@composurecdk/core";
|
|
2
|
+
/**
|
|
3
|
+
* Configures cross-cutting tag application against a composed system.
|
|
4
|
+
*
|
|
5
|
+
* - `system` tags reach every construct under the top-level scope passed
|
|
6
|
+
* to `build(scope, id)` — useful for ownership, environment, and cost
|
|
7
|
+
* allocation tags that should apply to every resource the system creates.
|
|
8
|
+
* - `byComponent` tags reach only the scope a named component was built
|
|
9
|
+
* into. Under {@link ComposedSystem.withStacks} or
|
|
10
|
+
* {@link ComposedSystem.withStackStrategy}, components may live in
|
|
11
|
+
* different stacks — `byComponent` routes the tag to whichever scope
|
|
12
|
+
* that component received.
|
|
13
|
+
*
|
|
14
|
+
* Component keys in `byComponent` are statically typed against the
|
|
15
|
+
* composed system's component keys, matching the pattern `outputs()` uses
|
|
16
|
+
* for its `scope` field, so a typo on a component name is a compile-time
|
|
17
|
+
* error.
|
|
18
|
+
*
|
|
19
|
+
* Builder-level tags applied via `.tag()` / `.tags()` always take
|
|
20
|
+
* precedence on key conflict because they target a closer scope; CDK's
|
|
21
|
+
* native tag priority resolves the collision automatically.
|
|
22
|
+
*
|
|
23
|
+
* @typeParam T - The composed system's build result type. Inferred from
|
|
24
|
+
* the surrounding `compose(...).afterBuild(tags(...))` chain.
|
|
25
|
+
*/
|
|
26
|
+
export interface TagDefinitions<T extends object = object> {
|
|
27
|
+
/**
|
|
28
|
+
* Tags applied to every construct under the top-level scope. Each
|
|
29
|
+
* key/value pair is validated against the AWS tag character set; invalid
|
|
30
|
+
* inputs throw at configuration time.
|
|
31
|
+
*/
|
|
32
|
+
system?: Record<string, string>;
|
|
33
|
+
/**
|
|
34
|
+
* Tags applied only to constructs under a specific component's scope.
|
|
35
|
+
* Component keys are statically checked against the composed system's
|
|
36
|
+
* component keys. Each key/value pair is validated identically to
|
|
37
|
+
* `system`.
|
|
38
|
+
*/
|
|
39
|
+
byComponent?: Partial<Record<keyof T & string, Record<string, string>>>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns an {@link AfterBuildHook} that applies cross-cutting tags to a
|
|
43
|
+
* composed system. Modelled on {@link outputs} — both share the same
|
|
44
|
+
* `(scope, id, results, componentScopes)` shape and live alongside the
|
|
45
|
+
* other CloudFormation-flavoured composition helpers in this package.
|
|
46
|
+
*
|
|
47
|
+
* The hook walks the supplied {@link TagDefinitions} once per build:
|
|
48
|
+
*
|
|
49
|
+
* - `system` entries are applied to the top-level `scope` via
|
|
50
|
+
* `Tags.of(scope).add(...)`. CDK's tag aspect propagates each tag
|
|
51
|
+
* through the construct subtree to every taggable descendant.
|
|
52
|
+
* - `byComponent` entries are applied to `componentScopes[key]` —
|
|
53
|
+
* each component's own scope, which under
|
|
54
|
+
* {@link ComposedSystem.withStacks} or
|
|
55
|
+
* {@link ComposedSystem.withStackStrategy} may be a per-component
|
|
56
|
+
* stack rather than the top-level scope.
|
|
57
|
+
*
|
|
58
|
+
* Tag keys and values are validated synchronously inside the hook before
|
|
59
|
+
* any `Tags.of(...).add(...)` call. Invalid tags throw and surface at the
|
|
60
|
+
* `compose(...).afterBuild(tags({...}))` site rather than at deploy time.
|
|
61
|
+
*
|
|
62
|
+
* Builder-level tags (set via `.tag()` / `.tags()` on individual builders)
|
|
63
|
+
* land on closer-scoped constructs and therefore win on key collision —
|
|
64
|
+
* CDK's tag priority resolves the collision automatically. Use builder
|
|
65
|
+
* tags for selector tags that must match exactly one resource type;
|
|
66
|
+
* use this helper for system-wide concerns like ownership, environment,
|
|
67
|
+
* and cost-allocation dimensions.
|
|
68
|
+
*
|
|
69
|
+
* @param defs - System-wide and per-component tag definitions.
|
|
70
|
+
* @returns An `AfterBuildHook` to pass to {@link ComposedSystem.afterBuild}.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { compose } from "@composurecdk/core";
|
|
75
|
+
* import { tags } from "@composurecdk/cloudformation";
|
|
76
|
+
*
|
|
77
|
+
* compose(
|
|
78
|
+
* { agent: createInstanceBuilder(), bucket: createBucketBuilder() },
|
|
79
|
+
* { agent: [], bucket: [] },
|
|
80
|
+
* )
|
|
81
|
+
* .afterBuild(
|
|
82
|
+
* tags({
|
|
83
|
+
* system: { Owner: "platform", Environment: "prod" },
|
|
84
|
+
* byComponent: { agent: { Project: "claude-rig" } },
|
|
85
|
+
* }),
|
|
86
|
+
* )
|
|
87
|
+
* .build(stack, "MySystem");
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export declare function tags<T extends object = object>(defs: TagDefinitions<T>): AfterBuildHook<T>;
|
|
91
|
+
//# sourceMappingURL=tags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.d.ts","sourceRoot":"","sources":["../src/tags.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIzD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IACvD;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CACzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CA+B1F"}
|
package/dist/tags.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { applyTagsToConstruct } from "./apply-builder-tags.js";
|
|
2
|
+
import { validateTagRecord } from "./tag-validator.js";
|
|
3
|
+
/**
|
|
4
|
+
* Returns an {@link AfterBuildHook} that applies cross-cutting tags to a
|
|
5
|
+
* composed system. Modelled on {@link outputs} — both share the same
|
|
6
|
+
* `(scope, id, results, componentScopes)` shape and live alongside the
|
|
7
|
+
* other CloudFormation-flavoured composition helpers in this package.
|
|
8
|
+
*
|
|
9
|
+
* The hook walks the supplied {@link TagDefinitions} once per build:
|
|
10
|
+
*
|
|
11
|
+
* - `system` entries are applied to the top-level `scope` via
|
|
12
|
+
* `Tags.of(scope).add(...)`. CDK's tag aspect propagates each tag
|
|
13
|
+
* through the construct subtree to every taggable descendant.
|
|
14
|
+
* - `byComponent` entries are applied to `componentScopes[key]` —
|
|
15
|
+
* each component's own scope, which under
|
|
16
|
+
* {@link ComposedSystem.withStacks} or
|
|
17
|
+
* {@link ComposedSystem.withStackStrategy} may be a per-component
|
|
18
|
+
* stack rather than the top-level scope.
|
|
19
|
+
*
|
|
20
|
+
* Tag keys and values are validated synchronously inside the hook before
|
|
21
|
+
* any `Tags.of(...).add(...)` call. Invalid tags throw and surface at the
|
|
22
|
+
* `compose(...).afterBuild(tags({...}))` site rather than at deploy time.
|
|
23
|
+
*
|
|
24
|
+
* Builder-level tags (set via `.tag()` / `.tags()` on individual builders)
|
|
25
|
+
* land on closer-scoped constructs and therefore win on key collision —
|
|
26
|
+
* CDK's tag priority resolves the collision automatically. Use builder
|
|
27
|
+
* tags for selector tags that must match exactly one resource type;
|
|
28
|
+
* use this helper for system-wide concerns like ownership, environment,
|
|
29
|
+
* and cost-allocation dimensions.
|
|
30
|
+
*
|
|
31
|
+
* @param defs - System-wide and per-component tag definitions.
|
|
32
|
+
* @returns An `AfterBuildHook` to pass to {@link ComposedSystem.afterBuild}.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { compose } from "@composurecdk/core";
|
|
37
|
+
* import { tags } from "@composurecdk/cloudformation";
|
|
38
|
+
*
|
|
39
|
+
* compose(
|
|
40
|
+
* { agent: createInstanceBuilder(), bucket: createBucketBuilder() },
|
|
41
|
+
* { agent: [], bucket: [] },
|
|
42
|
+
* )
|
|
43
|
+
* .afterBuild(
|
|
44
|
+
* tags({
|
|
45
|
+
* system: { Owner: "platform", Environment: "prod" },
|
|
46
|
+
* byComponent: { agent: { Project: "claude-rig" } },
|
|
47
|
+
* }),
|
|
48
|
+
* )
|
|
49
|
+
* .build(stack, "MySystem");
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function tags(defs) {
|
|
53
|
+
// Validate eagerly so configuration errors surface at the call site.
|
|
54
|
+
if (defs.system) {
|
|
55
|
+
validateTagRecord(defs.system);
|
|
56
|
+
}
|
|
57
|
+
const byComponent = defs.byComponent;
|
|
58
|
+
if (byComponent) {
|
|
59
|
+
for (const componentTags of Object.values(byComponent)) {
|
|
60
|
+
if (componentTags === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
validateTagRecord(componentTags);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return (scope, _id, _results, componentScopes) => {
|
|
66
|
+
if (defs.system) {
|
|
67
|
+
applyTagsToConstruct(scope, Object.entries(defs.system));
|
|
68
|
+
}
|
|
69
|
+
if (byComponent) {
|
|
70
|
+
const scopesByKey = componentScopes;
|
|
71
|
+
for (const [componentKey, componentTags] of Object.entries(byComponent)) {
|
|
72
|
+
if (componentTags === undefined)
|
|
73
|
+
continue;
|
|
74
|
+
const target = scopesByKey[componentKey];
|
|
75
|
+
if (target === undefined) {
|
|
76
|
+
throw new Error(`tags(): byComponent entry "${componentKey}" is not a known component.`);
|
|
77
|
+
}
|
|
78
|
+
applyTagsToConstruct(target, Object.entries(componentTags));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=tags.js.map
|
package/dist/tags.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.js","sourceRoot":"","sources":["../src/tags.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AA2CvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,UAAU,IAAI,CAA4B,IAAuB;IACrE,qEAAqE;IACrE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,WAEZ,CAAC;IACd,IAAI,WAAW,EAAE,CAAC;QAChB,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;YACvD,IAAI,aAAa,KAAK,SAAS;gBAAE,SAAS;YAC1C,iBAAiB,CAAC,aAAa,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE;QAC/C,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,WAAW,GAAG,eAAmE,CAAC;YACxF,KAAK,MAAM,CAAC,YAAY,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBACxE,IAAI,aAAa,KAAK,SAAS;oBAAE,SAAS;gBAC1C,MAAM,MAAM,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;gBACzC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;oBACzB,MAAM,IAAI,KAAK,CAAC,8BAA8B,YAAY,6BAA6B,CAAC,CAAC;gBAC3F,CAAC;gBACD,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@composurecdk/cloudformation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Composable CloudFormation stack builder and stack assignment strategies",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"type": "module",
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@composurecdk/core": "^0.
|
|
38
|
+
"@composurecdk/core": "^0.7.0",
|
|
39
39
|
"aws-cdk-lib": "^2.0.0",
|
|
40
40
|
"constructs": "^10.0.0"
|
|
41
41
|
},
|