@mettlecast/domain-cdk-packer 0.2.110 → 0.2.111

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.
@@ -89,8 +89,12 @@ export interface DomainStackProps extends cdk.StackProps {
89
89
  * Injected into this domain's subscriber/action Lambdas (via the
90
90
  * `TIB_ACTION_LAMBDA_ARNS` env var) so `ctx.actions.otherDomain.someAction()`
91
91
  * dispatches cross-domain through the runtime's Lambda invoke envelope
92
- * (#5226). ARNs use the deterministic action-Lambda function name
93
- * `${projectId}-${envCode}-domain-${domainId}-action`.
92
+ * (#5226). ARNs MUST be built with `domainActionLambdaArn(projectId,
93
+ * envCode, domainId)` — the single canonical source that matches the
94
+ * deterministic physical name this stack assigns to the grouped internal
95
+ * action Lambda (`${projectId}-${envCode}-${domainId}-action`, lowercased).
96
+ * Hand-assembled ARN strings drift from that name and make every
97
+ * cross-domain invoke target a phantom function (#5381).
94
98
  */
95
99
  crossDomainActionArns?: Record<string, string>;
96
100
  /**
@@ -20,7 +20,7 @@ import { CmkConstruct } from './constructs/cmk-construct.js';
20
20
  import { WafConstruct } from './constructs/waf-construct.js';
21
21
  import { AlarmConstruct } from './constructs/alarm-construct.js';
22
22
  import { CanaryConstruct } from './constructs/canary-construct.js';
23
- import { domainResourceName } from './naming.js';
23
+ import { domainResourceName, domainActionLambdaName } from './naming.js';
24
24
  import { SecurityAssertionAspect } from './aspects/security-assertion-aspect.js';
25
25
  import { validateRegistry } from './validate-registry.js';
26
26
  /** L1 helper: adds a CfnRoute + CfnIntegration to an existing HTTP API. */
@@ -259,7 +259,9 @@ export class DomainStack extends cdk.Stack {
259
259
  }
260
260
  // Deterministic physical name for THIS domain's internal-action grouped
261
261
  // Lambda so other stacks can construct its ARN for cross-domain calls.
262
- const actionLambdaName = `${resourceBaseName}-action`;
262
+ // Canonical deterministic name shared with every cross-domain ARN
263
+ // builder (naming.ts) — never hand-assemble this pattern elsewhere.
264
+ const actionLambdaName = domainActionLambdaName(projectId, envCode, domainId);
263
265
  const resolveSubnetSelection = (access) => {
264
266
  if (access === 'internet')
265
267
  return internetSubnetSelection ?? internalSubnetSelection;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * #5381 — the cross-domain action ARN map MUST resolve to the physical name of
3
+ * the internal-action grouped Lambda that DomainStack actually deploys.
4
+ *
5
+ * Production evidence (run 33969906049): auth's `on-member-invited`
6
+ * subscriber called `ctx.actions.email['send-template-email']` and the email
7
+ * send never started (no email_send_audit row). The map consumed by the
8
+ * subscriber Lambda had never been reconciled against the deployed function
9
+ * name: the generated app hand-assembled
10
+ * `${projectId}-${envCode}-domain-${other}-action` while DomainStack names
11
+ * the grouped internal-action Lambda
12
+ * `${projectId}-${envCode}-${domainId}-action` (naming.ts, lowercased). Every
13
+ * cross-domain invoke — and the IAM grant backing it — pointed at a phantom
14
+ * function. The existing cross-domain-wiring.test.ts could not catch this
15
+ * because it injected a synthetic ARN prop and asserted the SAME string came
16
+ * back out; it never compared the map against the deployed FunctionName.
17
+ *
18
+ * These tests close that loop with the REAL constructs:
19
+ * 1. synthesize an email DomainStack and read the physical FunctionName of
20
+ * its grouped internal-action Lambda out of the template;
21
+ * 2. synthesize an auth DomainStack whose crossDomainActionArns map is built
22
+ * exactly the way the generated deploy entries build it — through the
23
+ * shared `domainActionLambdaArn` helper — and assert the subscriber's
24
+ * TIB_ACTION_LAMBDA_ARNS value resolves to that same function name;
25
+ * 3. assert the subscriber's lambda:InvokeFunction grant targets the same
26
+ * function resource (a valid map with a missing grant is equally
27
+ * undeliverable).
28
+ */
29
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
30
+ import * as cdk from 'aws-cdk-lib';
31
+ import { Template } from 'aws-cdk-lib/assertions';
32
+ import fs from 'node:fs';
33
+ import path from 'node:path';
34
+ import { DomainStack } from '../DomainStack.js';
35
+ import { domainActionLambdaName, domainActionLambdaArn } from '../naming.js';
36
+ const REPO_CWD = process.cwd();
37
+ const EVENT_BUS_ARN = 'arn:aws:events:eu-north-1:123456789012:event-bus/test-bus';
38
+ const PROJECT_ID = 'MTC';
39
+ const ENV_CODE = 'Dev';
40
+ function fixtureRegistry(domainId, opts) {
41
+ const root = path.join(REPO_CWD, `.test-arn-naming-${domainId}`);
42
+ return {
43
+ schemaVersion: '1',
44
+ domainRoot: root,
45
+ domain: { id: domainId, kind: 'domain', name: domainId, tenancy: 'none' },
46
+ webhooks: [],
47
+ subscribers: opts.subscribers
48
+ ? [
49
+ {
50
+ id: 'on-member-invited',
51
+ kind: 'subscriber',
52
+ handlerFile: 'subscribers/on-member-invited.ts',
53
+ event: 'auth.member.invited',
54
+ semverRange: '^1',
55
+ outboundAccess: 'internal',
56
+ },
57
+ ]
58
+ : [],
59
+ schedules: [],
60
+ jobs: [],
61
+ actions: opts.internalActions.map((id) => ({
62
+ id,
63
+ kind: 'action',
64
+ handlerFile: `actions/${id}.ts`,
65
+ backendAccess: 'domain',
66
+ exposure: { type: 'internal' },
67
+ idempotent: false,
68
+ inputSchema: { type: 'object', properties: {}, required: [] },
69
+ outputSchema: { type: 'object', properties: {}, required: [] },
70
+ })),
71
+ integrations: [],
72
+ events: [],
73
+ };
74
+ }
75
+ /** Create the handler stubs + grouped-Lambda asset dirs DomainStack expects. */
76
+ function materializeFixture(domainId, registry) {
77
+ const root = registry.domainRoot;
78
+ fs.mkdirSync(path.join(root, 'subscribers'), { recursive: true });
79
+ fs.mkdirSync(path.join(root, 'actions'), { recursive: true });
80
+ for (const sub of registry.subscribers) {
81
+ fs.writeFileSync(path.join(root, sub.handlerFile), `export const onMemberInvited = { id: "${sub.id}", event: "${sub.event}", semverRange: "^1", handler: async () => {} };\n`);
82
+ }
83
+ for (const action of registry.actions) {
84
+ const exportName = action.id.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
85
+ fs.writeFileSync(path.join(root, action.handlerFile), `export const ${exportName} = { id: "${action.id}", backendAccess: "domain", exposure: { type: "internal" }, input: { parse: (x) => x }, output: { parse: (x) => x }, idempotent: false, handler: async () => ({ ok: true }) };\n`);
86
+ }
87
+ for (const prim of ['subscriber', 'action']) {
88
+ fs.mkdirSync(path.join(REPO_CWD, 'dist', 'domains', domainId, prim), { recursive: true });
89
+ }
90
+ }
91
+ /**
92
+ * Resolve a (possibly tokenized) CFN value to a plain string, replacing
93
+ * pseudo-parameter refs with stable placeholders. The JSON map injected as
94
+ * TIB_ACTION_LAMBDA_ARNS synthesizes to an Fn::Join of literal segments and
95
+ * {Ref: AWS::Region}/{Ref: AWS::AccountId} tokens; the region/account do not
96
+ * affect the function-name segment we assert on.
97
+ */
98
+ function flattenCfn(value) {
99
+ if (typeof value === 'string')
100
+ return value;
101
+ if (value && typeof value === 'object') {
102
+ const rec = value;
103
+ if (Array.isArray(rec['Fn::Join'])) {
104
+ const [delim, parts] = rec['Fn::Join'];
105
+ return parts.map(flattenCfn).join(delim);
106
+ }
107
+ if (typeof rec['Ref'] === 'string')
108
+ return `__${rec['Ref']}__`;
109
+ if (Array.isArray(value))
110
+ return value.map(flattenCfn).join('');
111
+ }
112
+ return String(value);
113
+ }
114
+ function findDomainLambdas(template) {
115
+ return template.findResources('AWS::Lambda::Function');
116
+ }
117
+ beforeAll(() => {
118
+ materializeFixture('email', fixtureRegistry('email', { subscribers: false, internalActions: ['send-template-email'] }));
119
+ materializeFixture('auth', fixtureRegistry('auth', { subscribers: true, internalActions: ['verify-token'] }));
120
+ });
121
+ afterAll(() => {
122
+ for (const d of ['email', 'auth']) {
123
+ fs.rmSync(path.join(REPO_CWD, `.test-arn-naming-${d}`), { recursive: true, force: true });
124
+ fs.rmSync(path.join(REPO_CWD, 'dist', 'domains', d), { recursive: true, force: true });
125
+ }
126
+ });
127
+ describe('cross-domain action ARN ⇔ deployed Lambda name (#5381)', () => {
128
+ it('the grouped internal-action Lambda carries the helper-derived physical name', () => {
129
+ const app = new cdk.App();
130
+ const registry = fixtureRegistry('email', { subscribers: false, internalActions: ['send-template-email'] });
131
+ const stack = new DomainStack(app, 'MTC-Dev-domain-email', {
132
+ registry,
133
+ eventBusArn: EVENT_BUS_ARN,
134
+ projectId: PROJECT_ID,
135
+ envCode: ENV_CODE,
136
+ });
137
+ const template = Template.fromStack(stack);
138
+ const lambdas = findDomainLambdas(template);
139
+ const physical = Object.values(lambdas).map((l) => l.Properties.FunctionName).filter(Boolean);
140
+ // The deployed name has NO 'domain' segment — this exact fact is what the
141
+ // old hand-assembled app.ts ARN got wrong.
142
+ expect(physical).toContain('mtc-dev-email-action');
143
+ });
144
+ it('a subscriber map built via domainActionLambdaArn resolves to the deployed email action function name', () => {
145
+ // Build the map the way BOTH generated deploy entries now build it.
146
+ const crossDomainActionArns = {
147
+ email: domainActionLambdaArn(PROJECT_ID, ENV_CODE, 'email'),
148
+ };
149
+ const app = new cdk.App();
150
+ const registry = fixtureRegistry('auth', { subscribers: true, internalActions: ['verify-token'] });
151
+ const stack = new DomainStack(app, 'MTC-Dev-domain-auth', {
152
+ registry,
153
+ eventBusArn: EVENT_BUS_ARN,
154
+ projectId: PROJECT_ID,
155
+ envCode: ENV_CODE,
156
+ crossDomainActionArns,
157
+ });
158
+ const template = Template.fromStack(stack);
159
+ // 1) Locate the grouped subscriber Lambda by its env-injected map.
160
+ const lambdas = findDomainLambdas(template);
161
+ const subscriberEntries = Object.entries(lambdas).filter(([id, l]) => /subscriber/i.test(id) && l.Properties.Environment?.Variables?.TIB_ACTION_LAMBDA_ARNS);
162
+ expect(subscriberEntries.length).toBeGreaterThanOrEqual(1);
163
+ for (const [, l] of subscriberEntries) {
164
+ const raw = l.Properties.Environment.Variables.TIB_ACTION_LAMBDA_ARNS;
165
+ const arns = JSON.parse(flattenCfn(raw));
166
+ const functionSegment = arns.email.split(':function:')[1];
167
+ // 2) THE invariant that was broken in production: the map's function
168
+ // segment must equal the physical name of the deployed email
169
+ // internal-action Lambda, not a hand-assembled approximation.
170
+ expect(functionSegment).toBe(domainActionLambdaName(PROJECT_ID, ENV_CODE, 'email'));
171
+ expect(functionSegment).toBe('mtc-dev-email-action');
172
+ }
173
+ // 3) The subscriber role must actually be granted invoke on THAT function
174
+ // (env correct but IAM phantom = still undeliverable).
175
+ const policies = Object.values(template.findResources('AWS::IAM::Policy'));
176
+ const hasInvokeOnRealName = policies.some((p) => p.Properties.PolicyDocument.Statement.some((s) => s.Action?.includes('lambda:InvokeFunction') &&
177
+ flattenCfn(s.Resource).includes(`:function:${domainActionLambdaName(PROJECT_ID, ENV_CODE, 'email')}`)));
178
+ expect(hasInvokeOnRealName).toBe(true);
179
+ });
180
+ });
@@ -0,0 +1,130 @@
1
+ /**
2
+ * #5385 — the COMMITTED generated deploy entries (infra/modules/app.ts and
3
+ * app-domain.ts) run in CI against the PUBLISHED @mettlecast/domain-cdk-packer:
4
+ * the deploy jobs `npm ci` in infra/modules and `npx tsx` loads the installed
5
+ * ESM tarball. Template v172 (#5384) imported `domainActionLambdaArn` — a
6
+ * member that exists only in this package's SOURCE — so every domain deploy
7
+ * died at link time with
8
+ * `SyntaxError: The requested module '@mettlecast/domain-cdk-packer' does
9
+ * not provide an export named 'domainActionLambdaArn'`
10
+ * (TIB Deploy run 34000848210) before the #5381 ARN repair could ship.
11
+ * Template v173 defines the ARN builder LOCALLY in the generated entries, on
12
+ * top of the published `domainResourceName` primitive.
13
+ *
14
+ * This test pins both halves of that contract:
15
+ * 1. the committed deploy entries value-import ONLY members verified to
16
+ * exist on the published packer export surface (npm tarballs 0.2.96 and
17
+ * 0.2.110 were checked: `packDomain`, `domainResourceName`);
18
+ * 2. the emitted local helper — extracted and evaluated with the REAL
19
+ * `domainResourceName` — produces the exact function name this package's
20
+ * canonical `domainActionLambdaName` assigns the deployed grouped
21
+ * internal-action Lambda. The local mirror must not fork away from
22
+ * naming.ts; if it drifts, this fails. (Region/account halves are CDK
23
+ * pseudo-parameters — asserted structurally, not by token identity.)
24
+ */
25
+ import { describe, it, expect } from 'vitest';
26
+ import { readFileSync } from 'node:fs';
27
+ import { join, dirname } from 'node:path';
28
+ import { fileURLToPath } from 'node:url';
29
+ import { domainResourceName, domainActionLambdaName } from '../naming.js';
30
+ const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..');
31
+ /** Deploy entries the TIB Deploy jobs execute via `npx tsx`. */
32
+ const DEPLOY_ENTRIES = ['infra/modules/app.ts', 'infra/modules/app-domain.ts'];
33
+ /**
34
+ * Value exports VERIFIED present on every published packer the generated
35
+ * package.json range (^0.2.96) can resolve, including the lockfile-pinned
36
+ * 0.2.110. Widening this set requires first proving the member ships on the
37
+ * published tarball — that proof is exactly what #5385 lacked.
38
+ */
39
+ const PUBLISHED_VALUE_EXPORTS = new Set(['packDomain', 'domainResourceName']);
40
+ /** Stub pseudo-parameter values for evaluating the emitted helper offline. */
41
+ const STUB_REGION = '__AWS_REGION__';
42
+ const STUB_ACCOUNT = '__AWS_ACCOUNT_ID__';
43
+ /**
44
+ * Extract the local `domainActionLambdaArn` function from a deploy entry and
45
+ * return it as a callable, with `cdk.Aws.*` stubbed and the REAL published
46
+ * `domainResourceName` primitive injected.
47
+ */
48
+ function extractLocalArnHelper(src, label) {
49
+ const match = src.match(/function domainActionLambdaArn\([^)]*\)\s*:\s*string\s*\{[\s\S]*?\n\}/);
50
+ expect(match, `${label}: must define the local domainActionLambdaArn helper (template v173, #5385)`).toBeTruthy();
51
+ const stubCdk = { Aws: { REGION: STUB_REGION, ACCOUNT_ID: STUB_ACCOUNT } };
52
+ // The emitted helper is TypeScript; `new Function` parses plain JS. The
53
+ // function body contains no `: string` sequences, so stripping the four
54
+ // signature annotations is safe and keeps the executed logic verbatim.
55
+ const jsSource = match[0].replace(/: string/g, '');
56
+ // eslint-disable-next-line no-new-func -- executing the generated mirror IS the parity check
57
+ const factory = new Function('cdk', 'domainResourceName', `${jsSource}\nreturn domainActionLambdaArn;`);
58
+ return factory(stubCdk, domainResourceName);
59
+ }
60
+ /** Named value imports from '@mettlecast/domain-cdk-packer' (type imports erased). */
61
+ function packerValueImports(src) {
62
+ const names = [];
63
+ for (const line of src.split('\n')) {
64
+ const t = line.trim();
65
+ if (!t.startsWith('import') || !t.includes('@mettlecast/domain-cdk-packer'))
66
+ continue;
67
+ if (t.startsWith('import type'))
68
+ continue;
69
+ const braceOpen = t.indexOf('{');
70
+ const braceClose = t.indexOf('}');
71
+ if (braceOpen === -1 || braceClose === -1)
72
+ continue;
73
+ for (const raw of t.slice(braceOpen + 1, braceClose).split(',')) {
74
+ const name = raw.trim().split(/\s+as\s+/)[0].trim();
75
+ if (name)
76
+ names.push(name);
77
+ }
78
+ }
79
+ return names;
80
+ }
81
+ describe('deploy entries import only published packer members (#5385)', () => {
82
+ for (const rel of DEPLOY_ENTRIES) {
83
+ it(`${rel} value-imports nothing the installed packer lacks`, () => {
84
+ const src = readFileSync(join(repoRoot, rel), 'utf8');
85
+ const imported = packerValueImports(src);
86
+ // The regression itself: an unexported member crashes `npx tsx` at link
87
+ // time, so every deploy stack fails BEFORE synthesis. Fail loudly here.
88
+ expect(imported.length, `${rel} must import packDomain from the packer`).toBeGreaterThan(0);
89
+ for (const name of imported) {
90
+ expect(PUBLISHED_VALUE_EXPORTS.has(name), `${rel} imports { ${name} } from @mettlecast/domain-cdk-packer, but that member is not on the published export surface (SyntaxError at deploy time — #5385)`).toBe(true);
91
+ }
92
+ expect(imported).not.toContain('domainActionLambdaArn');
93
+ expect(imported).not.toContain('domainActionLambdaName');
94
+ });
95
+ }
96
+ });
97
+ describe('local ARN mirror ≡ canonical naming.ts (#5381/#5385)', () => {
98
+ it('naming.ts itself derives the action name from domainResourceName + "-action"', () => {
99
+ // The local mirror builds its ARN from the SAME published primitive; this
100
+ // pins the canonical contract the mirror is allowed to reproduce.
101
+ expect(domainActionLambdaName('MTC', 'Dev', 'email')).toBe(`${domainResourceName('MTC', 'Dev', 'email')}-action`);
102
+ expect(domainActionLambdaName('mtc', 'dev', 'email')).toBe('mtc-dev-email-action');
103
+ });
104
+ const fixtures = [
105
+ ['MTC', 'Dev', 'email'],
106
+ ['MTC', 'prod', 'data-management'],
107
+ ['MyProj', 'Staging', 'billing'],
108
+ ['MTC', 'Dev', 'auth'],
109
+ ];
110
+ for (const rel of DEPLOY_ENTRIES) {
111
+ const src = readFileSync(join(repoRoot, rel), 'utf8');
112
+ const local = extractLocalArnHelper(src, rel);
113
+ for (const [projectId, envCode, domainId] of fixtures) {
114
+ it(`${rel}: domainActionLambdaArn('${projectId}', '${envCode}', '${domainId}') resolves to the deployed function name`, () => {
115
+ const generatedArn = local(projectId, envCode, domainId);
116
+ // Region/account halves are CFN pseudo-parameters (asserted by shape);
117
+ // the FUNCTION NAME is what drifted in production and what this guards.
118
+ expect(generatedArn.startsWith(`arn:aws:lambda:${STUB_REGION}:${STUB_ACCOUNT}:function:`)).toBe(true);
119
+ const functionSegment = generatedArn.split(':function:')[1];
120
+ expect(functionSegment).toBe(domainActionLambdaName(projectId, envCode, domainId));
121
+ // No phantom 'domain' segment (#5381's original bug shape).
122
+ expect(functionSegment).not.toContain('-domain-');
123
+ });
124
+ }
125
+ it(`${rel}: helper exists exactly once (no forked duplicates)`, () => {
126
+ const defs = src.match(/function domainActionLambdaArn\(/g) ?? [];
127
+ expect(defs.length).toBe(1);
128
+ });
129
+ }
130
+ });
package/dist/index.d.ts CHANGED
@@ -36,4 +36,4 @@ export type { DomainActionFlowNode, StepFunctionsTaskState } from './step-functi
36
36
  export type { FlowRegistry, FlowRegistryEntry, SerialFlowStep, SerialDomainActionStep, SerialDomainApiStep, SerialDomainEventStep, SerialDomainQueryStep, SerialAwsServiceStep, SerialFlowControlStep } from './flow-registry.js';
37
37
  export { FlowsStack, packFlows } from './pack-flows.js';
38
38
  export type { PackFlowsOptions } from './pack-flows.js';
39
- export { domainResourceName } from './naming.js';
39
+ export { domainResourceName, domainActionLambdaName, domainActionLambdaArn } from './naming.js';
package/dist/index.js CHANGED
@@ -16,4 +16,4 @@ export { packDomain } from './pack-domain.js';
16
16
  export { validateRegistry } from './validate-registry.js';
17
17
  export { StepFunctionsCodegen } from './step-functions-codegen.js';
18
18
  export { FlowsStack, packFlows } from './pack-flows.js';
19
- export { domainResourceName } from './naming.js';
19
+ export { domainResourceName, domainActionLambdaName, domainActionLambdaArn } from './naming.js';
package/dist/naming.d.ts CHANGED
@@ -9,3 +9,21 @@
9
9
  * (pack-flows domain-query grants) so the two can never drift.
10
10
  */
11
11
  export declare function domainResourceName(projectId: string, envCode: string, domainId: string): string;
12
+ /**
13
+ * Deterministic physical name of a domain's internal-action grouped Lambda —
14
+ * the invoke target for cross-domain `ctx.actions.<domain>.<action>()`
15
+ * dispatch (#5226). DomainStack assigns this exact name via `functionName`,
16
+ * and callers building cross-domain ARNs MUST derive them from here rather
17
+ * than hand-assembling the pattern: the generated app constructed
18
+ * `…-domain-<id>-action`, which never matched the deployed `…-<id>-action`,
19
+ * so every subscriber cross-domain invoke targeted a phantom function
20
+ * (bootstrap invitation email dispatch, #5381).
21
+ */
22
+ export declare function domainActionLambdaName(projectId: string, envCode: string, domainId: string): string;
23
+ /**
24
+ * Cross-domain ARN for a domain's internal-action grouped Lambda, using
25
+ * account/region pseudo-parameters so it can be synthesized before the
26
+ * target stack exists. This is the canonical way to populate
27
+ * `PackDomainOptions.crossDomainActionArns`.
28
+ */
29
+ export declare function domainActionLambdaArn(projectId: string, envCode: string, domainId: string): string;
package/dist/naming.js CHANGED
@@ -1,3 +1,4 @@
1
+ import * as cdk from 'aws-cdk-lib';
1
2
  /**
2
3
  * Canonical physical-name builder for per-domain AWS resources.
3
4
  *
@@ -11,3 +12,25 @@
11
12
  export function domainResourceName(projectId, envCode, domainId) {
12
13
  return `${projectId}-${envCode}-${domainId}`.toLowerCase();
13
14
  }
15
+ /**
16
+ * Deterministic physical name of a domain's internal-action grouped Lambda —
17
+ * the invoke target for cross-domain `ctx.actions.<domain>.<action>()`
18
+ * dispatch (#5226). DomainStack assigns this exact name via `functionName`,
19
+ * and callers building cross-domain ARNs MUST derive them from here rather
20
+ * than hand-assembling the pattern: the generated app constructed
21
+ * `…-domain-<id>-action`, which never matched the deployed `…-<id>-action`,
22
+ * so every subscriber cross-domain invoke targeted a phantom function
23
+ * (bootstrap invitation email dispatch, #5381).
24
+ */
25
+ export function domainActionLambdaName(projectId, envCode, domainId) {
26
+ return `${domainResourceName(projectId, envCode, domainId)}-action`;
27
+ }
28
+ /**
29
+ * Cross-domain ARN for a domain's internal-action grouped Lambda, using
30
+ * account/region pseudo-parameters so it can be synthesized before the
31
+ * target stack exists. This is the canonical way to populate
32
+ * `PackDomainOptions.crossDomainActionArns`.
33
+ */
34
+ export function domainActionLambdaArn(projectId, envCode, domainId) {
35
+ return `arn:aws:lambda:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:function:${domainActionLambdaName(projectId, envCode, domainId)}`;
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mettlecast/domain-cdk-packer",
3
- "version": "0.2.110",
3
+ "version": "0.2.111",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",