@egulatee/pulumi-stack-alias 0.2.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/src/alias.ts ADDED
@@ -0,0 +1,204 @@
1
+ import * as pulumi from "@pulumi/pulumi";
2
+ import {
3
+ AliasConfig,
4
+ AliasExports,
5
+ ConditionalAliasConfig,
6
+ } from "./types";
7
+
8
+ /**
9
+ * Creates a stack alias that re-exports outputs from a target stack
10
+ *
11
+ * This is the core function of the Producer-Controlled Proxy pattern.
12
+ * It creates a StackReference to the target stack and re-exports specified outputs.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // In alias stack infrastructure/dev
17
+ * import { createStackAlias } from "@egulatee/pulumi-stack-alias";
18
+ *
19
+ * const alias = createStackAlias({
20
+ * targetProject: "infrastructure",
21
+ * targetStack: "shared",
22
+ * outputs: ["vpcId", "clusterName", "endpoint"]
23
+ * });
24
+ *
25
+ * export const vpcId = alias.vpcId;
26
+ * export const clusterName = alias.clusterName;
27
+ * export const endpoint = alias.endpoint;
28
+ * ```
29
+ *
30
+ * @param config - Alias configuration
31
+ * @returns Record of Pulumi Outputs for each specified output name
32
+ */
33
+ export function createStackAlias(config: AliasConfig): AliasExports {
34
+ const org = config.targetOrg || pulumi.getOrganization();
35
+ const targetStackName = `${org}/${config.targetProject}/${config.targetStack}`;
36
+ const targetStack = new pulumi.StackReference(targetStackName);
37
+
38
+ const exports: AliasExports = {};
39
+ for (const outputName of config.outputs) {
40
+ exports[outputName] = targetStack.requireOutput(outputName);
41
+ }
42
+
43
+ return exports;
44
+ }
45
+
46
+ /**
47
+ * Creates a conditional alias based on pattern matching
48
+ *
49
+ * Evaluates pattern rules in order and uses the first matching target.
50
+ * Falls back to defaultTarget if no pattern matches.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * // In infrastructure/index.ts
55
+ * import { createConditionalAlias } from "@egulatee/pulumi-stack-alias";
56
+ *
57
+ * const alias = createConditionalAlias({
58
+ * targetProject: "infrastructure",
59
+ * patterns: [
60
+ * { pattern: "*\/prod", target: "prod" },
61
+ * { pattern: "*\/staging", target: "shared" },
62
+ * { pattern: "*\/*-ephemeral", target: "shared" }
63
+ * ],
64
+ * defaultTarget: "shared",
65
+ * outputs: ["vpcId", "endpoint"]
66
+ * });
67
+ *
68
+ * export const vpcId = alias.vpcId;
69
+ * export const endpoint = alias.endpoint;
70
+ * ```
71
+ *
72
+ * @param config - Conditional alias configuration
73
+ * @returns Record of Pulumi Outputs for each specified output name
74
+ */
75
+ export function createConditionalAlias(config: ConditionalAliasConfig): AliasExports {
76
+ const currentProject = pulumi.getProject();
77
+ const currentStack = pulumi.getStack();
78
+
79
+ // Find first matching pattern
80
+ let targetStack = config.defaultTarget;
81
+ for (const rule of config.patterns) {
82
+ if (matchesPattern(rule.pattern, currentProject, currentStack)) {
83
+ targetStack = rule.target;
84
+ break;
85
+ }
86
+ }
87
+
88
+ if (!targetStack) {
89
+ throw new Error(
90
+ `No matching pattern found for ${currentProject}/${currentStack} ` +
91
+ `and no defaultTarget specified.`
92
+ );
93
+ }
94
+
95
+ return createStackAlias({
96
+ targetProject: config.targetProject,
97
+ targetStack: targetStack,
98
+ targetOrg: config.targetOrg,
99
+ outputs: config.outputs,
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Creates a simple alias using a simplified API
105
+ *
106
+ * Convenience wrapper around createStackAlias for common use cases.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * import { createSimpleAlias } from "@egulatee/pulumi-stack-alias";
111
+ *
112
+ * const alias = createSimpleAlias("infrastructure", "shared", ["vpcId"]);
113
+ * export const vpcId = alias.vpcId;
114
+ * ```
115
+ *
116
+ * @param targetProject - Target project name
117
+ * @param targetStack - Target stack name
118
+ * @param outputs - List of output names to re-export
119
+ * @returns Record of Pulumi Outputs for each specified output name
120
+ */
121
+ export function createSimpleAlias(
122
+ targetProject: string,
123
+ targetStack: string,
124
+ outputs: string[]
125
+ ): AliasExports {
126
+ return createStackAlias({ targetProject, targetStack, outputs });
127
+ }
128
+
129
+ /**
130
+ * Pattern matching with wildcard support
131
+ *
132
+ * Supports wildcards (*) in project and stack positions, as well as
133
+ * prefix and suffix wildcards.
134
+ *
135
+ * Pattern format: "projectPattern/stackPattern"
136
+ *
137
+ * Wildcard rules:
138
+ * - "*" matches any value
139
+ * - "*-suffix" matches any string ending with "-suffix"
140
+ * - "prefix-*" matches any string starting with "prefix-"
141
+ * - "exact" matches exactly "exact"
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * matchesPattern("myproject/*", "myproject", "dev") // true
146
+ * matchesPattern("*\/dev", "anyproject", "dev") // true
147
+ * matchesPattern("*\/*-dev", "app", "service-dev") // true
148
+ * ```
149
+ *
150
+ * @param pattern - Pattern string (e.g., "myproject/*", "*\/dev")
151
+ * @param project - Current project name to match against
152
+ * @param stack - Current stack name to match against
153
+ * @returns True if pattern matches the project/stack combination
154
+ */
155
+ export function matchesPattern(pattern: string, project: string, stack: string): boolean {
156
+ const [projectPattern, stackPattern] = pattern.split("/");
157
+
158
+ if (!projectPattern || !stackPattern) {
159
+ throw new Error(
160
+ `Invalid pattern format: "${pattern}". Expected "projectPattern/stackPattern".`
161
+ );
162
+ }
163
+
164
+ const projectMatches = matchesWildcard(projectPattern, project);
165
+ const stackMatches = matchesWildcard(stackPattern, stack);
166
+
167
+ return projectMatches && stackMatches;
168
+ }
169
+
170
+ /**
171
+ * Wildcard matching for a single component
172
+ *
173
+ * Supports:
174
+ * - "*" matches anything
175
+ * - "*-suffix" matches strings ending with suffix
176
+ * - "prefix-*" matches strings starting with prefix
177
+ * - "exact" matches exactly
178
+ *
179
+ * @internal
180
+ * @param pattern - Pattern string (may contain wildcards)
181
+ * @param value - Value to match against pattern
182
+ * @returns True if value matches pattern
183
+ */
184
+ function matchesWildcard(pattern: string, value: string): boolean {
185
+ // Exact wildcard - matches anything
186
+ if (pattern === "*") {
187
+ return true;
188
+ }
189
+
190
+ // Suffix wildcard: *-dev matches foo-dev, bar-dev
191
+ if (pattern.startsWith("*")) {
192
+ const suffix = pattern.substring(1);
193
+ return value.endsWith(suffix);
194
+ }
195
+
196
+ // Prefix wildcard: prod-* matches prod-us, prod-eu
197
+ if (pattern.endsWith("*")) {
198
+ const prefix = pattern.substring(0, pattern.length - 1);
199
+ return value.startsWith(prefix);
200
+ }
201
+
202
+ // Exact match
203
+ return pattern === value;
204
+ }
package/src/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @egulatee/pulumi-stack-alias
3
+ *
4
+ * Producer-side stack aliasing for Pulumi using lightweight proxy stacks.
5
+ *
6
+ * Consumers use standard StackReference (no library dependency).
7
+ * Producers use this library to create alias stacks that re-export outputs.
8
+ *
9
+ * ## Producer-Controlled Proxy Pattern
10
+ *
11
+ * Alias stacks re-export outputs from canonical stacks. Consumers use standard
12
+ * Pulumi StackReference without any library dependency. Producers use this
13
+ * library to create proxy stacks that keep outputs in sync.
14
+ *
15
+ * @packageDocumentation
16
+ */
17
+
18
+ export {
19
+ createStackAlias,
20
+ createConditionalAlias,
21
+ createSimpleAlias,
22
+ matchesPattern,
23
+ } from "./alias";
24
+
25
+ export type {
26
+ AliasConfig,
27
+ AliasExports,
28
+ PatternRule,
29
+ ConditionalAliasConfig,
30
+ } from "./types";
31
+
32
+ // Re-export Pulumi types for convenience
33
+ export { Output, StackReference } from "@pulumi/pulumi";
package/src/types.ts ADDED
@@ -0,0 +1,77 @@
1
+ import * as pulumi from "@pulumi/pulumi";
2
+
3
+ /**
4
+ * Configuration for creating a stack alias
5
+ */
6
+ export interface AliasConfig {
7
+ /**
8
+ * Target organization name (defaults to current organization)
9
+ */
10
+ targetOrg?: string;
11
+
12
+ /**
13
+ * Target project name
14
+ */
15
+ targetProject: string;
16
+
17
+ /**
18
+ * Target stack name
19
+ */
20
+ targetStack: string;
21
+
22
+ /**
23
+ * List of output names to re-export from the target stack
24
+ */
25
+ outputs: string[];
26
+ }
27
+
28
+ /**
29
+ * Record of aliased outputs (all are Pulumi Outputs)
30
+ */
31
+ export type AliasExports = Record<string, pulumi.Output<any>>;
32
+
33
+ /**
34
+ * A pattern rule for conditional aliasing
35
+ */
36
+ export interface PatternRule {
37
+ /**
38
+ * Pattern in format "project/stack" with wildcard support
39
+ * Examples: "* /prod", "myproject/ *", "* / *-ephemeral" (without spaces)
40
+ */
41
+ pattern: string;
42
+
43
+ /**
44
+ * Target stack name to use when this pattern matches
45
+ */
46
+ target: string;
47
+ }
48
+
49
+ /**
50
+ * Configuration for creating a conditional alias based on pattern matching
51
+ */
52
+ export interface ConditionalAliasConfig {
53
+ /**
54
+ * Target project name
55
+ */
56
+ targetProject: string;
57
+
58
+ /**
59
+ * Target organization name (defaults to current organization)
60
+ */
61
+ targetOrg?: string;
62
+
63
+ /**
64
+ * List of pattern rules (evaluated in order, first match wins)
65
+ */
66
+ patterns: PatternRule[];
67
+
68
+ /**
69
+ * Default target stack if no pattern matches (optional)
70
+ */
71
+ defaultTarget?: string;
72
+
73
+ /**
74
+ * List of output names to re-export from the target stack
75
+ */
76
+ outputs: string[];
77
+ }