@friggframework/devtools 2.0.0--canary.666.81647c4.0 → 2.0.0--canary.668.8e53aa9.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.
Files changed (26) hide show
  1. package/frigg-cli/README.md +1 -38
  2. package/frigg-cli/__tests__/unit/commands/build.test.js +0 -3
  3. package/frigg-cli/__tests__/unit/commands/deploy.test.js +0 -3
  4. package/frigg-cli/build-command/index.js +0 -6
  5. package/frigg-cli/deploy-command/index.js +0 -6
  6. package/frigg-cli/index.js +1 -13
  7. package/frigg-cli/init-command/backend-first-handler.js +46 -25
  8. package/frigg-cli/start-command/index.js +0 -4
  9. package/frigg-cli/start-command/start-command.test.js +0 -3
  10. package/infrastructure/create-frigg-infrastructure.js +1 -4
  11. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +98 -60
  12. package/package.json +7 -7
  13. package/frigg-cli/__tests__/unit/commands/validate-wiring.test.js +0 -92
  14. package/frigg-cli/validate-command/__tests__/init-validation.test.js +0 -63
  15. package/frigg-cli/validate-command/__tests__/integrations-checks.test.js +0 -133
  16. package/frigg-cli/validate-command/__tests__/validate-app-definition.test.js +0 -326
  17. package/frigg-cli/validate-command/__tests__/validate-command.test.js +0 -241
  18. package/frigg-cli/validate-command/checks/config.js +0 -468
  19. package/frigg-cli/validate-command/checks/integrations.js +0 -274
  20. package/frigg-cli/validate-command/format-report.js +0 -58
  21. package/frigg-cli/validate-command/index.js +0 -107
  22. package/frigg-cli/validate-command/load-app-definition.js +0 -93
  23. package/frigg-cli/validate-command/schema-issues.js +0 -154
  24. package/frigg-cli/validate-command/validate-app-definition.js +0 -60
  25. package/infrastructure/app-definition-schema-contract.test.js +0 -607
  26. package/infrastructure/create-frigg-infrastructure.test.js +0 -51
@@ -1,241 +0,0 @@
1
- const fs = require('fs');
2
- const os = require('os');
3
- const path = require('path');
4
-
5
- const { validateCommand, runValidation, preflightValidation } = require('..');
6
-
7
- const GOOD_INDEX = `
8
- class Api { static requesterType = 'apiKey'; }
9
- const definition = {
10
- moduleName: 'acme',
11
- API: Api,
12
- requiredAuthMethods: {
13
- getEntityDetails: async () => {},
14
- getCredentialDetails: async () => {},
15
- testAuthRequest: async () => {},
16
- apiPropertiesToPersist: { credential: ['api_key'], entity: [] },
17
- },
18
- };
19
- class AcmeIntegration {
20
- static Definition = { name: 'acme', version: '1.0.0', modules: { acme: { definition } } };
21
- }
22
- module.exports = {
23
- Definition: {
24
- name: 'frigg-app',
25
- integrations: [AcmeIntegration],
26
- user: { usePassword: true },
27
- encryption: { fieldLevelEncryptionMethod: 'kms', createResourceIfNoneFound: true },
28
- database: { postgres: { enable: true, management: 'use-existing', endpoint: 'db.example.com' } },
29
- environment: { DATABASE_URL: true },
30
- },
31
- };
32
- `;
33
-
34
- const BAD_INDEX = `
35
- module.exports = {
36
- Definition: {
37
- integrations: [{ name: 'not-a-class' }],
38
- database: { postgres: { enable: true, management: 'create-new' } },
39
- encrpytion: { fieldLevelEncryptionMethod: 'kms' },
40
- },
41
- };
42
- `;
43
-
44
- function backend(indexSource) {
45
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'frigg-validate-'));
46
- fs.writeFileSync(path.join(dir, 'package.json'), '{"name":"backend"}');
47
- fs.writeFileSync(path.join(dir, 'index.js'), indexSource);
48
- return dir;
49
- }
50
-
51
- describe('frigg validate', () => {
52
- const dirs = [];
53
- let log;
54
- let warn;
55
- let error;
56
- let exit;
57
-
58
- beforeEach(() => {
59
- log = jest.spyOn(console, 'log').mockImplementation(() => {});
60
- warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
61
- error = jest.spyOn(console, 'error').mockImplementation(() => {});
62
- exit = jest.spyOn(process, 'exit').mockImplementation(() => {});
63
- process.exitCode = undefined;
64
- });
65
-
66
- afterEach(() => {
67
- jest.restoreAllMocks();
68
- process.exitCode = undefined;
69
- for (const dir of dirs.splice(0))
70
- fs.rmSync(dir, { recursive: true, force: true });
71
- });
72
-
73
- const inBackend = (source) => {
74
- const dir = backend(source);
75
- dirs.push(dir);
76
- jest.spyOn(process, 'cwd').mockReturnValue(dir);
77
- return dir;
78
- };
79
-
80
- it('passes an app with integration classes and exits 0', async () => {
81
- inBackend(GOOD_INDEX);
82
-
83
- const report = await validateCommand({ stage: 'prod' });
84
-
85
- expect(report.errors).toEqual([]);
86
- expect(process.exitCode).toBeUndefined();
87
- expect(log.mock.calls.flat().join('\n')).toContain(
88
- 'The app definition is valid'
89
- );
90
- });
91
-
92
- it('prints errors with pointers and hints and exits 1', async () => {
93
- inBackend(BAD_INDEX);
94
-
95
- const report = await validateCommand({});
96
- const output = log.mock.calls.flat().join('\n');
97
-
98
- expect(process.exitCode).toBe(1);
99
- expect(report.errors.map((e) => e.pointer)).toEqual(
100
- expect.arrayContaining([
101
- '/encrpytion',
102
- '/database/postgres/management',
103
- '/integrations/0',
104
- ])
105
- );
106
- expect(output).toContain('/encrpytion');
107
- expect(output).toContain('Did you mean "encryption"?');
108
- expect(output).toContain(
109
- 'Use one of: "discover", "managed", "use-existing"'
110
- );
111
- });
112
-
113
- it('--json prints a machine-readable report', async () => {
114
- inBackend(BAD_INDEX);
115
-
116
- await validateCommand({ json: true, stage: 'prod' });
117
- const json = JSON.parse(log.mock.calls[0][0]);
118
-
119
- expect(json).toEqual(
120
- expect.objectContaining({
121
- valid: false,
122
- stage: 'prod',
123
- errors: expect.arrayContaining([
124
- expect.objectContaining({
125
- pointer: '/encrpytion',
126
- code: 'unknown-key',
127
- severity: 'error',
128
- }),
129
- ]),
130
- warnings: expect.any(Array),
131
- })
132
- );
133
- });
134
-
135
- it('reports a backend that fails to load', () => {
136
- const dir = backend("require('./missing-file');");
137
- dirs.push(dir);
138
-
139
- const report = runValidation({ cwd: dir });
140
-
141
- expect(report.errors).toEqual([
142
- expect.objectContaining({
143
- code: 'load-failed',
144
- message: expect.stringContaining('missing-file'),
145
- }),
146
- ]);
147
- });
148
-
149
- it('reports a directory without a backend', () => {
150
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'frigg-empty-'));
151
- dirs.push(dir);
152
-
153
- expect(runValidation({ cwd: dir }).errors).toEqual([
154
- expect.objectContaining({ code: 'backend-not-found' }),
155
- ]);
156
- });
157
-
158
- it('finds the backend/ directory from the project root', () => {
159
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'frigg-root-'));
160
- dirs.push(root);
161
- fs.mkdirSync(path.join(root, 'backend'));
162
- fs.writeFileSync(path.join(root, 'backend', 'package.json'), '{}');
163
- fs.writeFileSync(path.join(root, 'backend', 'index.js'), GOOD_INDEX);
164
-
165
- expect(runValidation({ cwd: root }).source).toBe(
166
- path.join(root, 'backend', 'index.js')
167
- );
168
- });
169
-
170
- it('reports an index.js without a Definition', () => {
171
- const dir = backend('module.exports = {};');
172
- dirs.push(dir);
173
-
174
- expect(runValidation({ cwd: dir }).errors).toEqual([
175
- expect.objectContaining({ code: 'definition-not-exported' }),
176
- ]);
177
- });
178
-
179
- describe('preflightValidation', () => {
180
- it('stops build/deploy on errors', () => {
181
- inBackend(BAD_INDEX);
182
-
183
- const proceed = preflightValidation({
184
- command: 'deploy',
185
- options: { stage: 'prod' },
186
- failOnErrors: true,
187
- });
188
-
189
- expect(proceed).toBe(false);
190
- expect(exit).toHaveBeenCalledWith(1);
191
- expect(error.mock.calls.flat().join('\n')).toContain(
192
- '--skip-validate'
193
- );
194
- });
195
-
196
- it('lets build/deploy continue on a valid definition', () => {
197
- inBackend(GOOD_INDEX);
198
-
199
- expect(
200
- preflightValidation({
201
- command: 'build',
202
- options: { stage: 'prod' },
203
- failOnErrors: true,
204
- })
205
- ).toBe(true);
206
- expect(exit).not.toHaveBeenCalled();
207
- });
208
-
209
- it('only warns for start', () => {
210
- inBackend(BAD_INDEX);
211
-
212
- expect(
213
- preflightValidation({
214
- command: 'start',
215
- options: {},
216
- failOnErrors: false,
217
- })
218
- ).toBe(true);
219
- expect(exit).not.toHaveBeenCalled();
220
- const output = log.mock.calls.flat().join('\n');
221
- expect(output).toContain('warning');
222
- expect(output).not.toMatch(/^\s+error /m);
223
- });
224
-
225
- it('--skip-validate skips validation, loudly', () => {
226
- inBackend(BAD_INDEX);
227
-
228
- expect(
229
- preflightValidation({
230
- command: 'deploy',
231
- options: { skipValidate: true },
232
- failOnErrors: true,
233
- })
234
- ).toBe(true);
235
- expect(exit).not.toHaveBeenCalled();
236
- expect(warn.mock.calls.flat().join('\n')).toContain(
237
- 'VALIDATION SKIPPED'
238
- );
239
- });
240
- });
241
- });
@@ -1,468 +0,0 @@
1
- /**
2
- * Semantic checks of the app definition: rules JSON Schema cannot express,
3
- * each tied to what a builder or core does with the value (ADR-051).
4
- *
5
- * Every check receives the definition with the schema defaults applied
6
- * (`definition`) and the definition as written (`raw`), so it can tell a
7
- * value the app set from a default.
8
- */
9
-
10
- const { getSchema } = require('@friggframework/schemas');
11
-
12
- const ENCRYPTION_BYPASS_STAGES = ['dev', 'test', 'local'];
13
-
14
- function issue(severity, code, pointer, message, hint) {
15
- return { severity, code, pointer, message, hint };
16
- }
17
-
18
- /** Keys the schema marks `deprecated` (nothing reads them) that the app sets. */
19
- function checkDeprecatedKeys({ raw }) {
20
- const issues = [];
21
- const walk = (schemaNode, data, pointer) => {
22
- if (
23
- !schemaNode?.properties ||
24
- data === null ||
25
- typeof data !== 'object'
26
- )
27
- return;
28
- for (const [key, child] of Object.entries(schemaNode.properties)) {
29
- if (data[key] === undefined) continue;
30
- const childPointer = `${pointer}/${key}`;
31
- if (child.deprecated) {
32
- const alias = key === 'password' && pointer === '/user';
33
- issues.push(
34
- issue(
35
- 'warning',
36
- alias ? 'deprecated-alias' : 'no-effect',
37
- childPointer,
38
- alias
39
- ? 'user.password is the deprecated spelling of user.usePassword.'
40
- : `${childPointer
41
- .slice(1)
42
- .replace(
43
- /\//g,
44
- '.'
45
- )} is not read by Frigg: it has no effect.`,
46
- alias ? 'Rename it to usePassword.' : 'Remove it.'
47
- )
48
- );
49
- continue;
50
- }
51
- walk(child, data[key], childPointer);
52
- }
53
- };
54
- walk(getSchema('app-definition'), raw, '');
55
- return issues;
56
- }
57
-
58
- function checkManagement({ raw }) {
59
- const issues = [];
60
- const mode = raw.managementMode;
61
- const vpc = raw.vpc || {};
62
- const postgres = raw.database?.postgres || {};
63
-
64
- if (mode === 'custom') {
65
- issues.push(
66
- issue(
67
- 'warning',
68
- 'no-effect',
69
- '/managementMode',
70
- "managementMode 'custom' behaves like 'discover'.",
71
- "Use 'discover', or remove the key."
72
- )
73
- );
74
- }
75
- if (mode === 'managed' && raw.vpcIsolation === undefined) {
76
- issues.push(
77
- issue(
78
- 'warning',
79
- 'ambiguous-default',
80
- '/vpcIsolation',
81
- "managementMode 'managed' without vpcIsolation: the builders do not agree on a default (some treat it as 'shared', some as 'isolated').",
82
- "Set vpcIsolation to 'isolated' (resources per stage) or 'shared'."
83
- )
84
- );
85
- }
86
- if (mode === 'managed') {
87
- for (const [key, value] of [
88
- ['management', vpc.management],
89
- ['subnets/management', vpc.subnets?.management],
90
- ['natGateway/management', vpc.natGateway?.management],
91
- ['shareAcrossStages', vpc.shareAcrossStages],
92
- ]) {
93
- if (value !== undefined) {
94
- issues.push(
95
- issue(
96
- 'warning',
97
- 'conflicting-settings',
98
- `/vpc/${key}`,
99
- `managementMode 'managed' and vpc.${key.replace(
100
- '/',
101
- '.'
102
- )} are both set. The build logs that it ignores vpc.${key.replace(
103
- '/',
104
- '.'
105
- )}, but the VPC builder still applies it on top of the managed settings.`,
106
- `Remove vpc.${key.replace(
107
- '/',
108
- '.'
109
- )}, or drop managementMode 'managed'.`
110
- )
111
- );
112
- }
113
- }
114
- }
115
- if (
116
- (mode === 'managed' || mode === 'existing') &&
117
- postgres.management !== undefined &&
118
- postgres.management !== 'external'
119
- ) {
120
- issues.push(
121
- issue(
122
- 'warning',
123
- 'no-effect',
124
- '/database/postgres/management',
125
- `database.postgres.management is ignored when managementMode is '${mode}'.`,
126
- 'Remove it.'
127
- )
128
- );
129
- }
130
-
131
- for (const [pointer, value, useful] of [
132
- [
133
- '/vpc/subnets/management',
134
- vpc.subnets?.management,
135
- "'create' or 'use-existing'",
136
- ],
137
- [
138
- '/vpc/natGateway/management',
139
- vpc.natGateway?.management,
140
- "'createAndManage', or set natGateway.id",
141
- ],
142
- ]) {
143
- if (['discover', 'create-new', 'existing'].includes(value)) {
144
- issues.push(
145
- issue(
146
- 'warning',
147
- 'no-effect',
148
- pointer,
149
- `${pointer
150
- .slice(1)
151
- .replace(
152
- /\//g,
153
- '.'
154
- )} '${value}' has no effect: the VPC builder ignores it.`,
155
- `Use ${useful}, or remove it.`
156
- )
157
- );
158
- }
159
- }
160
-
161
- if (
162
- vpc.enable === true &&
163
- vpc.management === 'use-existing' &&
164
- !vpc.vpcId &&
165
- !vpc.ownership
166
- ) {
167
- issues.push(
168
- issue(
169
- 'error',
170
- 'missing-key',
171
- '/vpc/vpcId',
172
- "vpc.management 'use-existing' needs vpc.vpcId; the VPC builder fails without it.",
173
- 'Set vpc.vpcId (vpc-...).'
174
- )
175
- );
176
- }
177
- if (
178
- vpc.enable === true &&
179
- vpc.subnets?.management === 'use-existing' &&
180
- !(Array.isArray(vpc.subnets.ids) && vpc.subnets.ids.length >= 2)
181
- ) {
182
- issues.push(
183
- issue(
184
- 'error',
185
- 'missing-key',
186
- '/vpc/subnets/ids',
187
- "vpc.subnets.management 'use-existing' needs at least 2 subnet ids; the VPC builder fails without them.",
188
- 'Set vpc.subnets.ids.'
189
- )
190
- );
191
- }
192
- if (
193
- postgres.enable === true &&
194
- postgres.management === 'use-existing' &&
195
- !postgres.endpoint &&
196
- mode !== 'managed' &&
197
- mode !== 'existing'
198
- ) {
199
- issues.push(
200
- issue(
201
- 'error',
202
- 'missing-key',
203
- '/database/postgres/endpoint',
204
- "database.postgres.management 'use-existing' needs database.postgres.endpoint; the Aurora builder fails without it.",
205
- 'Set the endpoint host name.'
206
- )
207
- );
208
- }
209
- const provisionsAurora =
210
- postgres.enable === true &&
211
- postgres.management !== 'use-existing' &&
212
- postgres.management !== 'external';
213
- if (
214
- provisionsAurora &&
215
- vpc.enable !== true &&
216
- postgres.publiclyAccessible !== true
217
- ) {
218
- issues.push(
219
- issue(
220
- 'warning',
221
- 'unreachable-database',
222
- '/vpc/enable',
223
- 'Frigg provisions or discovers a private Aurora cluster, but the Lambda functions run outside a VPC and cannot reach it.',
224
- "Set vpc.enable: true, or use database.postgres.management: 'use-existing' with a reachable endpoint."
225
- )
226
- );
227
- }
228
- return issues;
229
- }
230
-
231
- function checkDatabase({ raw }) {
232
- const database = raw.database;
233
- const enabled = ['postgres', 'mongoDB', 'documentDB'].filter(
234
- (key) => database?.[key]?.enable === true
235
- );
236
- if (enabled.length === 0) {
237
- return [
238
- issue(
239
- 'error',
240
- 'no-database',
241
- '/database',
242
- 'No database is enabled. Core refuses to start without one (database.postgres, database.mongoDB or database.documentDB with enable: true).',
243
- 'Add `database: { postgres: { enable: true } }` (or mongoDB / documentDB).'
244
- ),
245
- ];
246
- }
247
- if (enabled.length > 1) {
248
- return [
249
- issue(
250
- 'warning',
251
- 'several-databases',
252
- '/database',
253
- `Several databases are enabled (${enabled.join(
254
- ', '
255
- )}); core uses ${enabled[0]}.`,
256
- 'Enable only the database the app uses.'
257
- ),
258
- ];
259
- }
260
- return [];
261
- }
262
-
263
- function checkEncryption({ raw, stage, env }) {
264
- const issues = [];
265
- const method = raw.encryption?.fieldLevelEncryptionMethod;
266
- const environment = raw.environment || {};
267
- const deployedStage = stage && !ENCRYPTION_BYPASS_STAGES.includes(stage);
268
-
269
- if (method === 'aes') {
270
- for (const key of ['AES_KEY_ID', 'AES_KEY']) {
271
- if (environment[key] !== true) {
272
- issues.push(
273
- issue(
274
- 'error',
275
- 'missing-encryption-env',
276
- `/environment/${key}`,
277
- `fieldLevelEncryptionMethod 'aes' needs ${key} in the Lambda environment, and only keys listed in \`environment\` (as true) reach it. Without it, deployed stages store sensitive fields unencrypted.`,
278
- `Add \`${key}: true\` to environment (it cannot be offloaded to SSM).`
279
- )
280
- );
281
- } else if (deployedStage && !env[key]) {
282
- issues.push(
283
- issue(
284
- 'warning',
285
- 'missing-env-value',
286
- `/environment/${key}`,
287
- `${key} is not set in this shell; \`frigg deploy --stage ${stage}\` would deploy it empty.`,
288
- `Export ${key} before deploying.`
289
- )
290
- );
291
- }
292
- }
293
- }
294
- if (
295
- method === 'kms' &&
296
- raw.managementMode !== 'managed' &&
297
- raw.encryption?.createResourceIfNoneFound === undefined &&
298
- !raw.encryption?.ownership
299
- ) {
300
- issues.push(
301
- issue(
302
- 'warning',
303
- 'kms-key-source',
304
- '/encryption/createResourceIfNoneFound',
305
- 'With no KMS key found by discovery, KMS_KEY_ARN falls back to the AWS_DISCOVERY_KMS_KEY_ID environment variable.',
306
- "Set createResourceIfNoneFound: true (or managementMode: 'managed') to let Frigg create the key."
307
- )
308
- );
309
- }
310
- if (method === undefined && deployedStage) {
311
- issues.push(
312
- issue(
313
- 'warning',
314
- 'no-field-encryption',
315
- '/encryption/fieldLevelEncryptionMethod',
316
- `No field-level encryption method: stage '${stage}' stores credentials and tokens unencrypted.`,
317
- "Set encryption.fieldLevelEncryptionMethod to 'kms' (Frigg creates the key) or 'aes'."
318
- )
319
- );
320
- }
321
- if (
322
- method !== undefined &&
323
- stage &&
324
- ENCRYPTION_BYPASS_STAGES.includes(stage)
325
- ) {
326
- issues.push(
327
- issue(
328
- 'warning',
329
- 'encryption-bypassed',
330
- '/encryption/fieldLevelEncryptionMethod',
331
- `Core skips field-level encryption when STAGE is ${ENCRYPTION_BYPASS_STAGES.join(
332
- ', '
333
- )}; stage '${stage}' stores sensitive fields unencrypted even when deployed.`,
334
- 'Deploy production data to another stage name (e.g. prod).'
335
- )
336
- );
337
- }
338
- return issues;
339
- }
340
-
341
- function checkUser({ definition, raw }) {
342
- const issues = [];
343
- const user = definition.user || {};
344
- const authModes = user.authModes;
345
-
346
- if (
347
- user.individualUserRequired === false &&
348
- user.organizationUserRequired !== true
349
- ) {
350
- issues.push(
351
- issue(
352
- 'error',
353
- 'no-login',
354
- '/user',
355
- 'Neither individual nor organization users are required: POST /user/login always fails.',
356
- 'Set user.individualUserRequired: true (the default) or user.organizationUserRequired: true.'
357
- )
358
- );
359
- }
360
- if (
361
- raw.user?.organizationUserRequired === true &&
362
- raw.user?.individualUserRequired === undefined
363
- ) {
364
- issues.push(
365
- issue(
366
- 'warning',
367
- 'default-changed',
368
- '/user/individualUserRequired',
369
- 'individualUserRequired is not set, so it defaults to true: login and x-frigg headers look up an individual user first.',
370
- 'For organization-only login, set user.individualUserRequired: false.'
371
- )
372
- );
373
- }
374
- if (authModes) {
375
- const anyMode =
376
- authModes.friggToken !== false ||
377
- authModes.sharedSecret !== false ||
378
- authModes.adopterJwt === true;
379
- if (!anyMode) {
380
- issues.push(
381
- issue(
382
- 'error',
383
- 'no-auth-mode',
384
- '/user/authModes',
385
- 'Every authentication mode is disabled: no request can authenticate.',
386
- 'Enable friggToken, sharedSecret or adopterJwt.'
387
- )
388
- );
389
- }
390
- if (authModes.adopterJwt === true && !user.jwtConfig?.secret) {
391
- issues.push(
392
- issue(
393
- 'error',
394
- 'missing-key',
395
- '/user/jwtConfig/secret',
396
- 'authModes.adopterJwt needs user.jwtConfig.secret to verify tokens.',
397
- 'Set user.jwtConfig: { secret: process.env.JWT_SECRET }.'
398
- )
399
- );
400
- }
401
- if (
402
- raw.user?.authModes?.sharedSecret === true &&
403
- !raw.environment?.FRIGG_API_KEY
404
- ) {
405
- issues.push(
406
- issue(
407
- 'warning',
408
- 'missing-env',
409
- '/environment/FRIGG_API_KEY',
410
- 'authModes.sharedSecret checks x-frigg-api-key against FRIGG_API_KEY, which is not listed in environment.',
411
- "Add FRIGG_API_KEY: true (or 'ssm') to environment."
412
- )
413
- );
414
- }
415
- }
416
- return issues;
417
- }
418
-
419
- function checkEnvironment({ raw }) {
420
- const issues = [];
421
- const environment = raw.environment || {};
422
- if (raw.ssm?.enable !== true) {
423
- for (const [key, value] of Object.entries(environment)) {
424
- if (value === 'ssm') {
425
- issues.push(
426
- issue(
427
- 'warning',
428
- 'ssm-inactive',
429
- `/environment/${key}`,
430
- `${key} is marked 'ssm' but ssm.enable is not true, so it is passed in the Lambda environment instead.`,
431
- 'Set ssm: { enable: true } to offload it.'
432
- )
433
- );
434
- }
435
- }
436
- }
437
- return issues;
438
- }
439
-
440
- const CHECKS = [
441
- checkDeprecatedKeys,
442
- checkManagement,
443
- checkDatabase,
444
- checkEncryption,
445
- checkUser,
446
- checkEnvironment,
447
- ];
448
-
449
- /**
450
- * @param {object} context
451
- * @param {object} context.definition app definition with defaults applied
452
- * @param {object} context.raw app definition as written
453
- * @param {string} [context.stage]
454
- * @param {object} [context.env]
455
- */
456
- function checkConfig({ definition, raw, stage, env = process.env }) {
457
- return CHECKS.flatMap((check) => check({ definition, raw, stage, env }));
458
- }
459
-
460
- module.exports = {
461
- checkConfig,
462
- checkDeprecatedKeys,
463
- checkManagement,
464
- checkDatabase,
465
- checkEncryption,
466
- checkUser,
467
- checkEnvironment,
468
- };