@friggframework/devtools 2.0.0-next.101 → 2.0.0-next.103
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/frigg-cli/__tests__/unit/commands/generate-iam.test.js +97 -0
- package/frigg-cli/deploy-command/index.js +44 -0
- package/frigg-cli/generate-command/index.js +2 -1
- package/frigg-cli/generate-iam-command.js +2 -1
- package/frigg-cli/index.js +16 -1
- package/frigg-cli/ssm-command/index.js +308 -0
- package/frigg-cli/ssm-command/index.test.js +318 -0
- package/infrastructure/__tests__/helpers/test-utils.js +3 -5
- package/infrastructure/__tests__/scoped-environment.test.js +126 -0
- package/infrastructure/__tests__/ssm-preload-node-options.test.js +79 -0
- package/infrastructure/domains/admin-scripts/admin-script-builder.js +17 -3
- package/infrastructure/domains/admin-scripts/admin-script-builder.test.js +45 -0
- package/infrastructure/domains/database/migration-builder.js +46 -9
- package/infrastructure/domains/database/migration-builder.test.js +101 -0
- package/infrastructure/domains/integration/integration-builder.js +65 -9
- package/infrastructure/domains/integration/integration-builder.test.js +95 -0
- package/infrastructure/domains/networking/vpc-builder.js +57 -7
- package/infrastructure/domains/networking/vpc-builder.test.js +41 -0
- package/infrastructure/domains/networking/vpc-discovery.js +9 -2
- package/infrastructure/domains/networking/vpc-discovery.test.js +19 -1
- package/infrastructure/domains/networking/vpc-resolver.js +12 -3
- package/infrastructure/domains/networking/vpc-resolver.test.js +40 -0
- package/infrastructure/domains/parameters/offload-utils.js +190 -0
- package/infrastructure/domains/parameters/offload-utils.test.js +193 -0
- package/infrastructure/domains/parameters/ssm-builder.js +67 -14
- package/infrastructure/domains/parameters/ssm-builder.test.js +158 -1
- package/infrastructure/domains/scheduler/scheduler-builder.js +44 -8
- package/infrastructure/domains/scheduler/scheduler-builder.test.js +118 -0
- package/infrastructure/domains/security/iam-generator.js +36 -1
- package/infrastructure/domains/security/iam-generator.test.js +83 -0
- package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +17 -0
- package/infrastructure/domains/security/templates/iam-policy-full.json +8 -3
- package/infrastructure/domains/shared/builder-orchestrator.js +14 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +45 -0
- package/infrastructure/domains/shared/environment-builder.js +88 -15
- package/infrastructure/domains/shared/environment-builder.test.js +304 -22
- package/infrastructure/domains/shared/function-environments.js +97 -0
- package/infrastructure/domains/shared/function-environments.test.js +146 -0
- package/infrastructure/infrastructure-composer.js +48 -1
- package/infrastructure/infrastructure-composer.test.js +161 -15
- package/infrastructure/integration.test.js +3 -5
- package/package.json +8 -7
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const {
|
|
2
|
+
isScopedEnvironmentActive,
|
|
3
|
+
applyFunctionEnvironments,
|
|
4
|
+
} = require('./function-environments');
|
|
5
|
+
|
|
6
|
+
describe('function-environments', () => {
|
|
7
|
+
const originalSkipDiscovery = process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
if (originalSkipDiscovery === undefined) {
|
|
11
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
12
|
+
} else {
|
|
13
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = originalSkipDiscovery;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('isScopedEnvironmentActive', () => {
|
|
18
|
+
it('is true only when lambda.scopedEnvironment is set', () => {
|
|
19
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
20
|
+
expect(
|
|
21
|
+
isScopedEnvironmentActive({
|
|
22
|
+
lambda: { scopedEnvironment: true },
|
|
23
|
+
})
|
|
24
|
+
).toBe(true);
|
|
25
|
+
expect(isScopedEnvironmentActive({})).toBe(false);
|
|
26
|
+
expect(
|
|
27
|
+
isScopedEnvironmentActive({
|
|
28
|
+
lambda: { scopedEnvironment: false },
|
|
29
|
+
})
|
|
30
|
+
).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('is false in local mode: the serverless-plugin injects LocalStack queue URLs at provider level only', () => {
|
|
34
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = 'true';
|
|
35
|
+
expect(
|
|
36
|
+
isScopedEnvironmentActive({
|
|
37
|
+
lambda: { scopedEnvironment: true },
|
|
38
|
+
})
|
|
39
|
+
).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('getIntegrationFunctionNames', () => {
|
|
44
|
+
const { getIntegrationFunctionNames } = require('./function-environments');
|
|
45
|
+
|
|
46
|
+
it('always includes the router and queue worker', () => {
|
|
47
|
+
expect(
|
|
48
|
+
getIntegrationFunctionNames({ Definition: { name: 'hubspot' } })
|
|
49
|
+
).toEqual(['hubspot', 'hubspotQueueWorker']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('includes the webhook handler when webhooks are enabled', () => {
|
|
53
|
+
expect(
|
|
54
|
+
getIntegrationFunctionNames({
|
|
55
|
+
Definition: { name: 'hubspot', webhooks: true },
|
|
56
|
+
})
|
|
57
|
+
).toContain('hubspotWebhook');
|
|
58
|
+
expect(
|
|
59
|
+
getIntegrationFunctionNames({
|
|
60
|
+
Definition: { name: 'hubspot', webhooks: { enabled: true } },
|
|
61
|
+
})
|
|
62
|
+
).toContain('hubspotWebhook');
|
|
63
|
+
expect(
|
|
64
|
+
getIntegrationFunctionNames({
|
|
65
|
+
Definition: { name: 'hubspot', webhooks: { enabled: false } },
|
|
66
|
+
})
|
|
67
|
+
).not.toContain('hubspotWebhook');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('includes one function per routed extension binding, sanitized', () => {
|
|
71
|
+
const names = getIntegrationFunctionNames({
|
|
72
|
+
Definition: {
|
|
73
|
+
name: 'hubspot',
|
|
74
|
+
extensions: {
|
|
75
|
+
'my-ext': { extension: { routes: [{ path: '/x', method: 'GET' }] } },
|
|
76
|
+
routeless: { extension: { routes: [] } },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
expect(names).toContain('hubspot__myext');
|
|
81
|
+
expect(names).not.toContain('hubspot__routeless');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('getAdminFunctionNames', () => {
|
|
86
|
+
const { getAdminFunctionNames } = require('./function-environments');
|
|
87
|
+
|
|
88
|
+
it('returns the admin functions only when admin scripts are configured', () => {
|
|
89
|
+
expect(
|
|
90
|
+
getAdminFunctionNames({ adminScripts: [{ Definition: { name: 'x' } }] })
|
|
91
|
+
).toEqual(['adminScriptRouter', 'adminScriptExecutor']);
|
|
92
|
+
expect(getAdminFunctionNames({})).toEqual([]);
|
|
93
|
+
expect(getAdminFunctionNames({ adminScripts: [] })).toEqual([]);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('applyFunctionEnvironments', () => {
|
|
98
|
+
const makeFunctions = () => ({
|
|
99
|
+
auth: { handler: 'auth.handler' },
|
|
100
|
+
hubspot: {
|
|
101
|
+
handler: 'hubspot.handler',
|
|
102
|
+
environment: { EXISTING: 'builder-set' },
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('assigns scoped env vars onto the target functions', () => {
|
|
107
|
+
const functions = makeFunctions();
|
|
108
|
+
applyFunctionEnvironments(functions, {
|
|
109
|
+
auth: { HUBSPOT_QUEUE_URL: 'url-1' },
|
|
110
|
+
hubspot: { HUBSPOT_QUEUE_URL: 'url-1' },
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(functions.auth.environment).toEqual({
|
|
114
|
+
HUBSPOT_QUEUE_URL: 'url-1',
|
|
115
|
+
});
|
|
116
|
+
expect(functions.hubspot.environment).toEqual({
|
|
117
|
+
EXISTING: 'builder-set',
|
|
118
|
+
HUBSPOT_QUEUE_URL: 'url-1',
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('never clobbers a key a builder already set directly on the function', () => {
|
|
123
|
+
const functions = makeFunctions();
|
|
124
|
+
applyFunctionEnvironments(functions, {
|
|
125
|
+
hubspot: { EXISTING: 'scoped-value', OTHER: 'x' },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(functions.hubspot.environment.EXISTING).toBe('builder-set');
|
|
129
|
+
expect(functions.hubspot.environment.OTHER).toBe('x');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('throws on an unknown function name instead of silently dropping vars', () => {
|
|
133
|
+
expect(() =>
|
|
134
|
+
applyFunctionEnvironments(makeFunctions(), {
|
|
135
|
+
typoFunction: { A: '1' },
|
|
136
|
+
})
|
|
137
|
+
).toThrow(/typoFunction/);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('is a no-op for an empty map', () => {
|
|
141
|
+
const functions = makeFunctions();
|
|
142
|
+
applyFunctionEnvironments(functions, {});
|
|
143
|
+
expect(functions.auth.environment).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -20,6 +20,44 @@ const { SchedulerBuilder } = require('./domains/scheduler/scheduler-builder');
|
|
|
20
20
|
const { AdminScriptBuilder } = require('./domains/admin-scripts/admin-script-builder');
|
|
21
21
|
|
|
22
22
|
// Utilities
|
|
23
|
+
const { applyFunctionEnvironments } = require('./domains/shared/function-environments');
|
|
24
|
+
const {
|
|
25
|
+
isSsmOffloadActive,
|
|
26
|
+
SSM_PRELOAD_NODE_OPTIONS,
|
|
27
|
+
} = require('./domains/parameters/offload-utils');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load the SSM INIT preload (NODE_OPTIONS=--import) on skipEsbuild handlers
|
|
31
|
+
* only. Those package the full node_modules tree, so the preload .mjs is
|
|
32
|
+
* present at /var/task. esbuild-bundled functions (e.g. defaultWebsocket,
|
|
33
|
+
* adopter custom functions) do NOT ship it — and a missing --import target is a
|
|
34
|
+
* fatal Node startup error — so they are left with the handler-time loader
|
|
35
|
+
* fallback instead.
|
|
36
|
+
*
|
|
37
|
+
* Set at function scope (function env wins over provider env), APPENDED to any
|
|
38
|
+
* NODE_OPTIONS already on the function or provider — so an app's own flags
|
|
39
|
+
* (OTel auto-instrumentation, source maps, memory tuning) survive instead of
|
|
40
|
+
* being clobbered. A function-level assignment shadows provider env in Lambda,
|
|
41
|
+
* so the provider value must be folded in here. Only a value already in the
|
|
42
|
+
* definition is appended — never a synthesized ${env:NODE_OPTIONS}, which would
|
|
43
|
+
* leak the deploy host's shell into every Lambda.
|
|
44
|
+
*/
|
|
45
|
+
function applySsmPreloadNodeOptions(appDefinition, functions, providerEnvironment = {}) {
|
|
46
|
+
if (!isSsmOffloadActive(appDefinition)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const fn of Object.values(functions)) {
|
|
50
|
+
if (!fn.skipEsbuild) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
fn.environment = fn.environment || {};
|
|
54
|
+
const existing =
|
|
55
|
+
fn.environment.NODE_OPTIONS ?? providerEnvironment.NODE_OPTIONS;
|
|
56
|
+
fn.environment.NODE_OPTIONS = existing
|
|
57
|
+
? `${existing} ${SSM_PRELOAD_NODE_OPTIONS}`
|
|
58
|
+
: SSM_PRELOAD_NODE_OPTIONS;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
23
61
|
const { modifyHandlerPaths } = require('./domains/shared/utilities/handler-path-resolver');
|
|
24
62
|
const { createBaseDefinition } = require('./domains/shared/utilities/base-definition-factory');
|
|
25
63
|
const { ensurePrismaLayerExists } = require('./domains/shared/utilities/prisma-layer-manager');
|
|
@@ -75,6 +113,15 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
75
113
|
definition.provider.iamRoleStatements.push(...merged.iamStatements);
|
|
76
114
|
Object.assign(definition.provider.environment, merged.environment);
|
|
77
115
|
Object.assign(definition.functions, merged.functions);
|
|
116
|
+
applyFunctionEnvironments(
|
|
117
|
+
definition.functions,
|
|
118
|
+
merged.functionEnvironments
|
|
119
|
+
);
|
|
120
|
+
applySsmPreloadNodeOptions(
|
|
121
|
+
AppDefinition,
|
|
122
|
+
definition.functions,
|
|
123
|
+
definition.provider.environment
|
|
124
|
+
);
|
|
78
125
|
|
|
79
126
|
if (merged.vpcConfig) {
|
|
80
127
|
definition.provider.vpc = merged.vpcConfig;
|
|
@@ -117,5 +164,5 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
117
164
|
return definition;
|
|
118
165
|
};
|
|
119
166
|
|
|
120
|
-
module.exports = { composeServerlessDefinition };
|
|
167
|
+
module.exports = { composeServerlessDefinition, applySsmPreloadNodeOptions };
|
|
121
168
|
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
composeServerlessDefinition,
|
|
3
|
+
applySsmPreloadNodeOptions,
|
|
4
|
+
} = require('./infrastructure-composer');
|
|
5
|
+
const {
|
|
6
|
+
SSM_PRELOAD_NODE_OPTIONS,
|
|
7
|
+
} = require('./domains/parameters/offload-utils');
|
|
2
8
|
|
|
3
9
|
// Helper to build discovery responses with overridable fields
|
|
4
10
|
const createDiscoveryResponse = (overrides = {}) => ({
|
|
@@ -1062,7 +1068,18 @@ describe('composeServerlessDefinition', () => {
|
|
|
1062
1068
|
});
|
|
1063
1069
|
|
|
1064
1070
|
describe('SSM Configuration', () => {
|
|
1065
|
-
|
|
1071
|
+
const originalSkipDiscovery = process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
1072
|
+
|
|
1073
|
+
afterEach(() => {
|
|
1074
|
+
if (originalSkipDiscovery === undefined) {
|
|
1075
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
1076
|
+
} else {
|
|
1077
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = originalSkipDiscovery;
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
it('should add the broad SSM read grant when ssm.enable is true without offload', async () => {
|
|
1082
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
1066
1083
|
const appDefinition = {
|
|
1067
1084
|
ssm: { enable: true },
|
|
1068
1085
|
integrations: []
|
|
@@ -1070,12 +1087,10 @@ describe('composeServerlessDefinition', () => {
|
|
|
1070
1087
|
|
|
1071
1088
|
const result = await composeServerlessDefinition(appDefinition);
|
|
1072
1089
|
|
|
1073
|
-
//
|
|
1074
|
-
expect(result.provider.layers).
|
|
1075
|
-
'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
|
|
1076
|
-
]);
|
|
1090
|
+
// We deliberately do NOT use the AWS Parameters-and-Secrets extension layer
|
|
1091
|
+
expect(result.provider.layers).toBeUndefined();
|
|
1077
1092
|
|
|
1078
|
-
//
|
|
1093
|
+
// Broad read grant present
|
|
1079
1094
|
const ssmPermission = result.provider.iamRoleStatements.find(
|
|
1080
1095
|
statement => statement.Action.includes('ssm:GetParameter')
|
|
1081
1096
|
);
|
|
@@ -1086,13 +1101,56 @@ describe('composeServerlessDefinition', () => {
|
|
|
1086
1101
|
'ssm:GetParameters',
|
|
1087
1102
|
'ssm:GetParametersByPath'
|
|
1088
1103
|
],
|
|
1089
|
-
Resource:
|
|
1090
|
-
'arn:aws:ssm:${
|
|
1091
|
-
|
|
1104
|
+
Resource: {
|
|
1105
|
+
'Fn::Sub': 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*'
|
|
1106
|
+
}
|
|
1092
1107
|
});
|
|
1093
1108
|
|
|
1094
|
-
//
|
|
1095
|
-
expect(result.provider.environment.SSM_PARAMETER_PREFIX).
|
|
1109
|
+
// No offload markers -> no offload env vars
|
|
1110
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
1111
|
+
expect(result.provider.environment.FRIGG_SSM_OFFLOADED_KEYS).toBeUndefined();
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
it('should offload marked keys and expose prefix + offloaded keys env vars', async () => {
|
|
1115
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
1116
|
+
const appDefinition = {
|
|
1117
|
+
ssm: { enable: true },
|
|
1118
|
+
environment: { FOO: 'ssm', BAR: true },
|
|
1119
|
+
integrations: []
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
1123
|
+
|
|
1124
|
+
// BAR stays in the Lambda env, FOO is offloaded out of it
|
|
1125
|
+
expect(result.provider.environment.BAR).toBe("${env:BAR, ''}");
|
|
1126
|
+
expect(result.provider.environment.FOO).toBeUndefined();
|
|
1127
|
+
|
|
1128
|
+
// Offload env contract
|
|
1129
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBe(
|
|
1130
|
+
'/frigg/${self:service}/${self:provider.stage}'
|
|
1131
|
+
);
|
|
1132
|
+
expect(result.provider.environment.FRIGG_SSM_OFFLOADED_KEYS).toBe('FOO');
|
|
1133
|
+
|
|
1134
|
+
// Prefix-scoped read grant present alongside the broad grant
|
|
1135
|
+
const scoped = result.provider.iamRoleStatements.find(
|
|
1136
|
+
statement => statement.Resource === 'arn:aws:ssm:${self:provider.region}:${aws:accountId}:parameter/frigg/${self:service}/${self:provider.stage}/*'
|
|
1137
|
+
);
|
|
1138
|
+
expect(scoped).toBeDefined();
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
it('should not add offload env vars when ssm.enable is true but nothing is marked', async () => {
|
|
1142
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
1143
|
+
const appDefinition = {
|
|
1144
|
+
ssm: { enable: true },
|
|
1145
|
+
environment: { BAR: true },
|
|
1146
|
+
integrations: []
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
1150
|
+
|
|
1151
|
+
expect(result.provider.environment.BAR).toBe("${env:BAR, ''}");
|
|
1152
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
1153
|
+
expect(result.provider.environment.FRIGG_SSM_OFFLOADED_KEYS).toBeUndefined();
|
|
1096
1154
|
});
|
|
1097
1155
|
|
|
1098
1156
|
it('should not add SSM configuration when ssm.enable is false', async () => {
|
|
@@ -1397,9 +1455,12 @@ describe('composeServerlessDefinition', () => {
|
|
|
1397
1455
|
expect(result.provider.environment.KMS_KEY_ARN).toBeDefined();
|
|
1398
1456
|
expect(result.custom.kmsGrants).toBeDefined();
|
|
1399
1457
|
|
|
1400
|
-
// SSM
|
|
1401
|
-
|
|
1402
|
-
|
|
1458
|
+
// SSM (enabled without offload markers -> broad grant only, no offload env vars)
|
|
1459
|
+
const ssmPermission = result.provider.iamRoleStatements.find(
|
|
1460
|
+
statement => statement.Action && statement.Action.includes('ssm:GetParameter')
|
|
1461
|
+
);
|
|
1462
|
+
expect(ssmPermission).toBeDefined();
|
|
1463
|
+
expect(result.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
1403
1464
|
|
|
1404
1465
|
// Integration
|
|
1405
1466
|
expect(result.functions.testIntegration).toBeDefined();
|
|
@@ -1894,3 +1955,88 @@ describe('composeServerlessDefinition', () => {
|
|
|
1894
1955
|
});
|
|
1895
1956
|
});
|
|
1896
1957
|
});
|
|
1958
|
+
|
|
1959
|
+
describe('applySsmPreloadNodeOptions', () => {
|
|
1960
|
+
const appDefinition = {
|
|
1961
|
+
ssm: { enable: true },
|
|
1962
|
+
environment: { FOO: 'ssm' },
|
|
1963
|
+
};
|
|
1964
|
+
const savedSkip = process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
1965
|
+
|
|
1966
|
+
beforeEach(() => {
|
|
1967
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1970
|
+
afterEach(() => {
|
|
1971
|
+
if (savedSkip === undefined) {
|
|
1972
|
+
delete process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
1973
|
+
} else {
|
|
1974
|
+
process.env.FRIGG_SKIP_AWS_DISCOVERY = savedSkip;
|
|
1975
|
+
}
|
|
1976
|
+
});
|
|
1977
|
+
|
|
1978
|
+
it('sets the preload NODE_OPTIONS on a skipEsbuild function with no existing value', () => {
|
|
1979
|
+
const functions = { auth: { skipEsbuild: true } };
|
|
1980
|
+
applySsmPreloadNodeOptions(appDefinition, functions);
|
|
1981
|
+
expect(functions.auth.environment.NODE_OPTIONS).toBe(
|
|
1982
|
+
SSM_PRELOAD_NODE_OPTIONS
|
|
1983
|
+
);
|
|
1984
|
+
});
|
|
1985
|
+
|
|
1986
|
+
it('appends to an existing function-level NODE_OPTIONS instead of clobbering it', () => {
|
|
1987
|
+
const functions = {
|
|
1988
|
+
auth: {
|
|
1989
|
+
skipEsbuild: true,
|
|
1990
|
+
environment: { NODE_OPTIONS: '--enable-source-maps' },
|
|
1991
|
+
},
|
|
1992
|
+
};
|
|
1993
|
+
applySsmPreloadNodeOptions(appDefinition, functions);
|
|
1994
|
+
expect(functions.auth.environment.NODE_OPTIONS).toBe(
|
|
1995
|
+
`--enable-source-maps ${SSM_PRELOAD_NODE_OPTIONS}`
|
|
1996
|
+
);
|
|
1997
|
+
});
|
|
1998
|
+
|
|
1999
|
+
it('folds in a provider-level NODE_OPTIONS (which the function env would otherwise shadow)', () => {
|
|
2000
|
+
const functions = { auth: { skipEsbuild: true } };
|
|
2001
|
+
applySsmPreloadNodeOptions(appDefinition, functions, {
|
|
2002
|
+
NODE_OPTIONS: '--require ./otel.js',
|
|
2003
|
+
});
|
|
2004
|
+
expect(functions.auth.environment.NODE_OPTIONS).toBe(
|
|
2005
|
+
`--require ./otel.js ${SSM_PRELOAD_NODE_OPTIONS}`
|
|
2006
|
+
);
|
|
2007
|
+
});
|
|
2008
|
+
|
|
2009
|
+
it('prefers a function-level value over the provider-level one', () => {
|
|
2010
|
+
const functions = {
|
|
2011
|
+
auth: {
|
|
2012
|
+
skipEsbuild: true,
|
|
2013
|
+
environment: { NODE_OPTIONS: '--fn-flag' },
|
|
2014
|
+
},
|
|
2015
|
+
};
|
|
2016
|
+
applySsmPreloadNodeOptions(appDefinition, functions, {
|
|
2017
|
+
NODE_OPTIONS: '--provider-flag',
|
|
2018
|
+
});
|
|
2019
|
+
expect(functions.auth.environment.NODE_OPTIONS).toBe(
|
|
2020
|
+
`--fn-flag ${SSM_PRELOAD_NODE_OPTIONS}`
|
|
2021
|
+
);
|
|
2022
|
+
});
|
|
2023
|
+
|
|
2024
|
+
it('never touches esbuild-bundled functions', () => {
|
|
2025
|
+
const functions = {
|
|
2026
|
+
websocket: { environment: { NODE_OPTIONS: '--keep-me' } },
|
|
2027
|
+
};
|
|
2028
|
+
applySsmPreloadNodeOptions(appDefinition, functions, {
|
|
2029
|
+
NODE_OPTIONS: '--provider',
|
|
2030
|
+
});
|
|
2031
|
+
expect(functions.websocket.environment.NODE_OPTIONS).toBe('--keep-me');
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
it('is a no-op when offload is inactive', () => {
|
|
2035
|
+
const functions = { auth: { skipEsbuild: true } };
|
|
2036
|
+
applySsmPreloadNodeOptions(
|
|
2037
|
+
{ ssm: { enable: true } }, // no offloaded keys → inactive
|
|
2038
|
+
functions
|
|
2039
|
+
);
|
|
2040
|
+
expect(functions.auth.environment).toBeUndefined();
|
|
2041
|
+
});
|
|
2042
|
+
});
|
|
@@ -108,11 +108,9 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
108
108
|
);
|
|
109
109
|
expect(kmsPermission).toBeDefined();
|
|
110
110
|
|
|
111
|
-
// Verify SSM configuration
|
|
112
|
-
expect(serverlessConfig.provider.layers).
|
|
113
|
-
|
|
114
|
-
]);
|
|
115
|
-
expect(serverlessConfig.provider.environment.SSM_PARAMETER_PREFIX).toBe('/${self:service}/${self:provider.stage}');
|
|
111
|
+
// Verify SSM configuration (enabled without offload markers -> broad grant only)
|
|
112
|
+
expect(serverlessConfig.provider.layers).toBeUndefined();
|
|
113
|
+
expect(serverlessConfig.provider.environment.SSM_PARAMETER_PREFIX).toBeUndefined();
|
|
116
114
|
|
|
117
115
|
// Verify SSM IAM permissions
|
|
118
116
|
const ssmPermission = serverlessConfig.provider.iamRoleStatements.find(
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0-next.
|
|
4
|
+
"version": "2.0.0-next.103",
|
|
5
5
|
"bin": {
|
|
6
6
|
"frigg": "./frigg-cli/index.js"
|
|
7
7
|
},
|
|
@@ -21,13 +21,14 @@
|
|
|
21
21
|
"@aws-sdk/client-rds": "^3.906.0",
|
|
22
22
|
"@aws-sdk/client-s3": "^3.917.0",
|
|
23
23
|
"@aws-sdk/client-secrets-manager": "^3.906.0",
|
|
24
|
+
"@aws-sdk/client-ssm": "^3.906.0",
|
|
24
25
|
"@aws-sdk/client-sts": "^3.835.0",
|
|
25
26
|
"@babel/eslint-parser": "^7.18.9",
|
|
26
27
|
"@babel/parser": "^7.25.3",
|
|
27
28
|
"@babel/traverse": "^7.25.3",
|
|
28
|
-
"@friggframework/core": "2.0.0-next.
|
|
29
|
-
"@friggframework/schemas": "2.0.0-next.
|
|
30
|
-
"@friggframework/test": "2.0.0-next.
|
|
29
|
+
"@friggframework/core": "2.0.0-next.103",
|
|
30
|
+
"@friggframework/schemas": "2.0.0-next.103",
|
|
31
|
+
"@friggframework/test": "2.0.0-next.103",
|
|
31
32
|
"@hapi/boom": "^10.0.1",
|
|
32
33
|
"@inquirer/prompts": "^5.3.8",
|
|
33
34
|
"axios": "^1.18.0",
|
|
@@ -55,8 +56,8 @@
|
|
|
55
56
|
"validate-npm-package-name": "^5.0.0"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
59
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
59
|
+
"@friggframework/eslint-config": "2.0.0-next.103",
|
|
60
|
+
"@friggframework/prettier-config": "2.0.0-next.103",
|
|
60
61
|
"aws-sdk-client-mock": "^4.1.0",
|
|
61
62
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
62
63
|
"jest": "^30.1.3",
|
|
@@ -88,5 +89,5 @@
|
|
|
88
89
|
"publishConfig": {
|
|
89
90
|
"access": "public"
|
|
90
91
|
},
|
|
91
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "9fc434b0c6e1323bd16be165d311b6880a23de12"
|
|
92
93
|
}
|