@bifravst/aws-cdk-lambda-helpers 4.0.95 → 5.0.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 (54) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +16 -0
  3. package/package.json +37 -34
  4. package/npm/IoTActionRole.d.ts +0 -10
  5. package/npm/IoTActionRole.js +0 -29
  6. package/npm/LambdaLogGroup.d.ts +0 -6
  7. package/npm/LambdaLogGroup.js +0 -16
  8. package/npm/LambdaSource.d.ts +0 -7
  9. package/npm/LambdaSource.js +0 -12
  10. package/npm/PackedLambdaFn.d.ts +0 -25
  11. package/npm/PackedLambdaFn.js +0 -53
  12. package/npm/cdk.d.ts +0 -4
  13. package/npm/cdk.js +0 -4
  14. package/npm/checkSumOfFiles.spec.d.ts +0 -1
  15. package/npm/checkSumOfFiles.spec.js +0 -15
  16. package/npm/checksumOfFiles.d.ts +0 -5
  17. package/npm/checksumOfFiles.js +0 -44
  18. package/npm/commonParent.d.ts +0 -4
  19. package/npm/commonParent.js +0 -12
  20. package/npm/commonParent.spec.d.ts +0 -1
  21. package/npm/commonParent.spec.js +0 -22
  22. package/npm/findDependencies.d.ts +0 -22
  23. package/npm/findDependencies.js +0 -97
  24. package/npm/findDependencies.spec.d.ts +0 -1
  25. package/npm/findDependencies.spec.js +0 -37
  26. package/npm/isTest.d.ts +0 -2
  27. package/npm/isTest.js +0 -1
  28. package/npm/isTest.spec.d.ts +0 -1
  29. package/npm/isTest.spec.js +0 -16
  30. package/npm/lambda.d.ts +0 -2
  31. package/npm/lambda.js +0 -2
  32. package/npm/layer.d.ts +0 -1
  33. package/npm/layer.js +0 -1
  34. package/npm/module-folder-named-like-handler-bug.spec.d.ts +0 -1
  35. package/npm/module-folder-named-like-handler-bug.spec.js +0 -19
  36. package/npm/packLambda.d.ts +0 -29
  37. package/npm/packLambda.js +0 -104
  38. package/npm/packLambda.spec.d.ts +0 -1
  39. package/npm/packLambda.spec.js +0 -28
  40. package/npm/packLambdaFromPath.d.ts +0 -20
  41. package/npm/packLambdaFromPath.js +0 -33
  42. package/npm/packLayer.d.ts +0 -23
  43. package/npm/packLayer.js +0 -113
  44. package/npm/packLayer.spec.d.ts +0 -1
  45. package/npm/packLayer.spec.js +0 -24
  46. package/npm/test/getFileFromZip.d.ts +0 -1
  47. package/npm/updateImports.d.ts +0 -1
  48. package/npm/updateImports.js +0 -1
  49. package/npm/updateImports.spec.d.ts +0 -1
  50. package/npm/updateImports.spec.js +0 -10
  51. package/npm/updateLambdaCode.d.ts +0 -7
  52. package/npm/updateLambdaCode.js +0 -75
  53. package/npm/util.d.ts +0 -5
  54. package/npm/util.js +0 -5
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  BSD 3-Clause License
2
2
 
3
- Copyright (c) 2024-2025, Nordic Semiconductor ASA | nordicsemi.no
3
+ Copyright (c) 2024-2026, Nordic Semiconductor ASA | nordicsemi.no
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without
package/README.md CHANGED
@@ -28,3 +28,19 @@ See [this example](./cdk/update-lambdas.ts) on how to use it.
28
28
  ## Example migrations to `@bifravst/aws-cdk-lambda-helpers`
29
29
 
30
30
  - [world.thingy.rocks backend](https://github.com/NordicPlayground/thingy-rocks-cloud-aws-js/commit/3ca6e267917db4d8cb09ca63ed54384c0e23f163)
31
+
32
+ ## Node & NPM
33
+
34
+ This project requires Node.js `>=24.19.0 <25` and npm `>=12.0.2 <13` (enforced
35
+ via `check-node-version` on `npm install` and `npm ci`).
36
+
37
+ The check is skipped during `npm publish` and `npm pack`, because
38
+ `semantic-release` bundles its own npm (`@semantic-release/npm` depends on
39
+ `npm@^11.6.2`) and runs the publish with that version rather than the one
40
+ installed in CI.
41
+
42
+ ## TypeScript 6 and 7
43
+
44
+ This repo
45
+ [runs TypeScript 6 and 7 side by side](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0),
46
+ [so that eslint works](https://github.com/typescript-eslint/typescript-eslint/issues/10940#issuecomment-4922812181).
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
1
  {
2
2
  "name": "@bifravst/aws-cdk-lambda-helpers",
3
- "version": "4.0.95",
3
+ "version": "5.0.0",
4
4
  "description": "Helper functions which simplify working with TypeScript lambdas for AWS CDK.",
5
5
  "exports": {
6
6
  ".": {
7
7
  "import": {
8
- "types": "./npm/lambda.d.ts",
9
- "default": "./npm/lambda.js"
8
+ "types": "./lambda.ts",
9
+ "default": "./dist/src/lambda.js"
10
10
  }
11
11
  },
12
12
  "./layer": {
13
13
  "import": {
14
- "types": "./npm/layer.d.ts",
15
- "default": "./npm/layer.js"
14
+ "types": "./layer.ts",
15
+ "default": "./dist/src/layer.js"
16
16
  }
17
17
  },
18
18
  "./util": {
19
19
  "import": {
20
- "types": "./npm/util.d.ts",
21
- "default": "./npm/util.js"
20
+ "types": "./util.ts",
21
+ "default": "./dist/src/util.js"
22
22
  }
23
23
  },
24
24
  "./cdk": {
25
25
  "import": {
26
- "types": "./npm/cdk.d.ts",
27
- "default": "./npm/cdk.js"
26
+ "types": "./cdk.ts",
27
+ "default": "./dist/src/cdk.js"
28
28
  }
29
29
  }
30
30
  },
@@ -33,8 +33,8 @@
33
33
  "test": "node --experimental-strip-types --no-warnings --test ./src/*.spec.ts",
34
34
  "test:e2e": "node --experimental-strip-types --test e2e.spec.ts",
35
35
  "test:e2e:updated": "node --experimental-strip-types --test e2e-updated.spec.ts",
36
- "prepare": "husky",
37
- "prepublishOnly": "node --experimental-strip-types .npm/npm-compile.ts && npx tsgo src/*.ts --emitDeclarationOnly --declaration --ignoreConfig --allowImportingTsExtensions --target esnext --module nodenext --moduleResolution nodenext --outDir ./npm && mv npm/src/* npm"
36
+ "prepare": "husky && case \"$npm_command\" in install|ci) check-node-version --package ;; esac",
37
+ "prepublishOnly": "npx tsc --noEmit false -d"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",
@@ -53,25 +53,27 @@
53
53
  "author": "Nordic Semiconductor ASA | nordicsemi.no",
54
54
  "license": "BSD-3-Clause",
55
55
  "devDependencies": {
56
- "@aws-sdk/client-cloudformation": "3.981.0",
57
- "@aws-sdk/client-dynamodb": "3.981.0",
58
- "@bifravst/cloudformation-helpers": "9.1.1",
59
- "@bifravst/eslint-config-typescript": "6.4.4",
60
- "@bifravst/from-env": "3.0.2",
61
- "@bifravst/prettier-config": "1.1.17",
62
- "@commitlint/config-conventional": "20.3.1",
56
+ "@aws-sdk/client-cloudformation": "3.1111.0",
57
+ "@aws-sdk/client-dynamodb": "3.1111.0",
58
+ "@bifravst/cloudformation-helpers": "10.0.0",
59
+ "@bifravst/eslint-config-typescript": "8.0.0",
60
+ "@bifravst/from-env": "4.0.0",
61
+ "@bifravst/prettier-config": "2.0.0",
62
+ "@commitlint/config-conventional": "21.2.2",
63
63
  "@swc/cli": "0.8.1",
64
- "@types/aws-lambda": "8.10.161",
65
- "@types/node": "25.6.0",
64
+ "@types/aws-lambda": "8.10.162",
65
+ "@types/node": "26.2.0",
66
66
  "@types/unzip-stream": "0.3.4",
67
67
  "@types/yazl": "3.3.1",
68
- "@typescript/native-preview": "7.0.0-dev.20260420.1",
69
- "cdk": "2.1118.4",
70
- "commitlint": "20.3.1",
68
+ "@typescript/native": "npm:typescript@7.0.2",
69
+ "cdk": "2.1136.0",
70
+ "check-node-version": "4.2.1",
71
+ "commitlint": "21.2.2",
71
72
  "husky": "9.1.7",
72
73
  "id128": "1.6.6",
73
- "lint-staged": "16.4.0",
74
- "unzip-stream": "0.3.4"
74
+ "lint-staged": "17.3.0",
75
+ "unzip-stream": "0.3.4",
76
+ "typescript": "npm:@typescript/typescript6@6.0.2"
75
77
  },
76
78
  "lint-staged": {
77
79
  "*.ts": [
@@ -83,8 +85,8 @@
83
85
  ]
84
86
  },
85
87
  "engines": {
86
- "node": ">=24",
87
- "npm": ">=11"
88
+ "node": ">=24.19.0 <25",
89
+ "npm": ">=12.0.2 <13"
88
90
  },
89
91
  "release": {
90
92
  "branches": [
@@ -114,16 +116,17 @@
114
116
  ],
115
117
  "prettier": "@bifravst/prettier-config",
116
118
  "dependencies": {
117
- "@swc/core": "1.15.30",
119
+ "@swc/core": "1.16.0",
118
120
  "fp-ts": "2.16.11",
119
- "p-retry": "7.1.1",
120
- "typescript": "5.9.3",
121
+ "p-retry": "8.0.0",
122
+ "typescript": "7.0.2",
121
123
  "yazl": "3.3.1"
122
124
  },
123
125
  "peerDependencies": {
124
- "@aws-sdk/client-lambda": "^3.981.0",
125
- "@bifravst/aws-ssm-settings-helpers": "^1.2.329",
126
- "aws-cdk-lib": "^2.250.0",
127
- "constructs": "^10.6.0"
126
+ "@aws-sdk/client-lambda": "^3.1111.0",
127
+ "@bifravst/aws-ssm-settings-helpers": "2.x",
128
+ "aws-cdk-lib": "^2.265.0",
129
+ "chalk": "6.x",
130
+ "constructs": "^10.8.1"
128
131
  }
129
132
  }
@@ -1,10 +0,0 @@
1
- import { aws_iam as IAM } from 'aws-cdk-lib';
2
- import { Construct } from 'constructs';
3
- /**
4
- * Base role for IoT Actions that allows to publish to the 'errors' topic
5
- */
6
- export declare class IoTActionRole extends Construct {
7
- readonly role: IAM.IRole;
8
- readonly roleArn: string;
9
- constructor(parent: Construct);
10
- }
@@ -1,29 +0,0 @@
1
- import { aws_iam as IAM, Stack } from 'aws-cdk-lib';
2
- import { Construct } from 'constructs';
3
- /**
4
- * Base role for IoT Actions that allows to publish to the 'errors' topic
5
- */ export class IoTActionRole extends Construct {
6
- role;
7
- roleArn;
8
- constructor(parent){
9
- super(parent, 'errorActionRole');
10
- this.role = new IAM.Role(this, 'iot-action-role', {
11
- assumedBy: new IAM.ServicePrincipal('iot.amazonaws.com'),
12
- inlinePolicies: {
13
- rootPermissions: new IAM.PolicyDocument({
14
- statements: [
15
- new IAM.PolicyStatement({
16
- actions: [
17
- 'iot:Publish'
18
- ],
19
- resources: [
20
- `arn:aws:iot:${Stack.of(this).region}:${Stack.of(this).account}:topic/errors`
21
- ]
22
- })
23
- ]
24
- })
25
- }
26
- });
27
- this.roleArn = this.role.roleArn;
28
- }
29
- }
@@ -1,6 +0,0 @@
1
- import { aws_logs as Logs } from 'aws-cdk-lib';
2
- import { Construct } from 'constructs';
3
- export declare class LambdaLogGroup extends Construct {
4
- readonly logGroup: Logs.LogGroup;
5
- constructor(parent: Construct, id: string, retention?: Logs.RetentionDays);
6
- }
@@ -1,16 +0,0 @@
1
- import { aws_logs as Logs, Names, RemovalPolicy, Stack } from 'aws-cdk-lib';
2
- import { Construct } from 'constructs';
3
- import { isTest } from './isTest.js';
4
- export class LambdaLogGroup extends Construct {
5
- logGroup;
6
- constructor(parent, id, // Defaults to 30 days for production, 1 day for test
7
- retention){
8
- super(parent, id);
9
- this.logGroup = new Logs.LogGroup(this, 'logGroup', {
10
- retention: retention ?? (isTest(this) ? Logs.RetentionDays.ONE_DAY : Logs.RetentionDays.ONE_MONTH),
11
- logGroupName: `/${Stack.of(this).stackName}/fn/${id}-${Names.uniqueId(this)}`,
12
- logGroupClass: Logs.LogGroupClass.STANDARD,
13
- removalPolicy: this.node.getContext('isTest') === true ? RemovalPolicy.DESTROY : RemovalPolicy.RETAIN
14
- });
15
- }
16
- }
@@ -1,7 +0,0 @@
1
- import { aws_lambda as Lambda } from 'aws-cdk-lib';
2
- import { Construct } from 'constructs';
3
- import type { PackedLambda } from './packLambda.ts';
4
- export declare class LambdaSource extends Construct {
5
- readonly code: Lambda.AssetCode;
6
- constructor(parent: Construct, packedLambda: Pick<PackedLambda, 'zipFilePath' | 'id' | 'hash'>);
7
- }
@@ -1,12 +0,0 @@
1
- import { AssetHashType, aws_lambda as Lambda } from 'aws-cdk-lib';
2
- import { Construct } from 'constructs';
3
- export class LambdaSource extends Construct {
4
- code;
5
- constructor(parent, packedLambda){
6
- super(parent, `${packedLambda.id}Source`);
7
- this.code = Lambda.Code.fromAsset(packedLambda.zipFilePath, {
8
- assetHash: packedLambda.hash,
9
- assetHashType: AssetHashType.CUSTOM
10
- });
11
- }
12
- }
@@ -1,25 +0,0 @@
1
- import { Function as LambdaFunction, type FunctionProps } from 'aws-cdk-lib/aws-lambda';
2
- import type { ILogGroupRef } from 'aws-cdk-lib/aws-logs';
3
- import { Construct } from 'constructs';
4
- import type { PackedLambda } from './packLambda.ts';
5
- /**
6
- * Creates a Lambda function with useful defaults:
7
- *
8
- * - Code from a PackedLambda
9
- * - Architecture: ARM64
10
- * - Runtime: Node.js 20
11
- * - timeout: 5 seconds
12
- * - memorySize: 1792 MB
13
- * - environment
14
- * VERSION: set from the 'version' context
15
- * NODE_NO_WARNINGS: disabled to get rid of Node.js warnings in the logs
16
- * STACK_NAME: the current stack name
17
- * DISABLE_METRICS: set to '1' of 'isTest'===true in the context
18
- * - a LambdaLogGroup (if not provided)
19
- * - policies that allow to access all SSM parameters below the current stack name
20
- */
21
- export declare class PackedLambdaFn extends Construct {
22
- readonly fn: LambdaFunction;
23
- readonly logGroup: ILogGroupRef;
24
- constructor(parent: Construct, id: string, source: PackedLambda, props: Partial<Omit<FunctionProps, 'code' | 'handler'>>);
25
- }
@@ -1,53 +0,0 @@
1
- import { Permissions as SettingsPermissions } from '@bifravst/aws-ssm-settings-helpers/cdk';
2
- import { Duration, Stack, Tags } from 'aws-cdk-lib';
3
- import { Architecture, Function as LambdaFunction, Runtime } from 'aws-cdk-lib/aws-lambda';
4
- import { Construct } from 'constructs';
5
- import { LambdaLogGroup } from './LambdaLogGroup.js';
6
- import { LambdaSource } from './LambdaSource.js';
7
- /**
8
- * Creates a Lambda function with useful defaults:
9
- *
10
- * - Code from a PackedLambda
11
- * - Architecture: ARM64
12
- * - Runtime: Node.js 20
13
- * - timeout: 5 seconds
14
- * - memorySize: 1792 MB
15
- * - environment
16
- * VERSION: set from the 'version' context
17
- * NODE_NO_WARNINGS: disabled to get rid of Node.js warnings in the logs
18
- * STACK_NAME: the current stack name
19
- * DISABLE_METRICS: set to '1' of 'isTest'===true in the context
20
- * - a LambdaLogGroup (if not provided)
21
- * - policies that allow to access all SSM parameters below the current stack name
22
- */ export class PackedLambdaFn extends Construct {
23
- fn;
24
- logGroup;
25
- constructor(parent, id, source, props){
26
- super(parent, id);
27
- const { environment, initialPolicy, architecture, ...rest } = props;
28
- this.logGroup = props.logGroup ?? new LambdaLogGroup(this, 'fnLogs').logGroup;
29
- this.fn = new LambdaFunction(this, 'fn', {
30
- architecture: architecture ?? Architecture.ARM_64,
31
- runtime: props.runtime ?? Runtime.NODEJS_24_X,
32
- timeout: Duration.seconds(5),
33
- memorySize: 1792,
34
- environment: {
35
- VERSION: this.node.tryGetContext('version'),
36
- NODE_NO_WARNINGS: '1',
37
- STACK_NAME: Stack.of(this).stackName,
38
- DISABLE_METRICS: this.node.tryGetContext('isTest') === true ? '1' : '0',
39
- ...environment
40
- },
41
- initialPolicy: [
42
- ...initialPolicy ?? [],
43
- SettingsPermissions(Stack.of(this))
44
- ],
45
- ...rest,
46
- logGroup: this.logGroup,
47
- handler: source.handler,
48
- code: new LambdaSource(this, source).code
49
- });
50
- Tags.of(this.fn).add('packedLambda:id', source.id);
51
- Tags.of(this.fn).add('packedLambda:hash', source.hash);
52
- }
53
- }
package/npm/cdk.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export * from './IoTActionRole.ts';
2
- export * from './LambdaLogGroup.ts';
3
- export * from './LambdaSource.ts';
4
- export * from './PackedLambdaFn.ts';
package/npm/cdk.js DELETED
@@ -1,4 +0,0 @@
1
- export * from './IoTActionRole.js';
2
- export * from './LambdaLogGroup.js';
3
- export * from './LambdaSource.js';
4
- export * from './PackedLambdaFn.js';
@@ -1 +0,0 @@
1
- export {};
@@ -1,15 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import path from 'node:path';
3
- import { describe, it } from 'node:test';
4
- import { checkSumOfFiles } from './checksumOfFiles.js';
5
- void describe('checkSumOfFiles()', ()=>{
6
- void it('should calculate a checksum of files', async ()=>assert.equal(await checkSumOfFiles([
7
- // sha1sum cdk/helpers/lambdas/test-data/1.txt
8
- // 6ae3f2029d36e029175cc225c2c4cda51a5ac602 cdk/helpers/lambdas/test-data/1.txt
9
- path.join(process.cwd(), 'src', 'test-data', '1.txt'),
10
- // sha1sum cdk/helpers/lambdas/test-data/2.txt
11
- // 6a9c3333d7a3f9ee9fa1ef70224766fafb208fe4 cdk/helpers/lambdas/test-data/2.txt
12
- path.join(process.cwd(), 'src', 'test-data', '2.txt')
13
- ]), // echo -n 6ae3f2029d36e029175cc225c2c4cda51a5ac6026a9c3333d7a3f9ee9fa1ef70224766fafb208fe4 | sha1sum
14
- 'baa003a894945a0d2519b1f4340caa97c462058f'));
15
- });
@@ -1,5 +0,0 @@
1
- /**
2
- * Computes the combined checksum of the given files
3
- */
4
- export declare const checkSumOfFiles: (files: string[]) => Promise<string>;
5
- export declare const checkSumOfStrings: (strings: string[]) => string;
@@ -1,44 +0,0 @@
1
- import * as crypto from 'node:crypto';
2
- import * as fs from 'node:fs';
3
- /**
4
- * Computes the combined checksum of the given files
5
- */ export const checkSumOfFiles = async (files)=>{
6
- const fileChecksums = await checkSum(files);
7
- const checksum = checkSumOfStrings([
8
- ...Object.entries(fileChecksums)
9
- ].map(([, hash])=>hash));
10
- return checksum;
11
- };
12
- export const checkSumOfStrings = (strings)=>{
13
- const hash = crypto.createHash('sha1');
14
- hash.update(strings.join(''));
15
- return hash.digest('hex');
16
- };
17
- const hashCache = {};
18
- const hashFile = async (filePath)=>{
19
- if (hashCache[filePath] === undefined) {
20
- hashCache[filePath] = await new Promise((resolve)=>{
21
- const hash = crypto.createHash('sha1');
22
- hash.setEncoding('hex');
23
- const fileStream = fs.createReadStream(filePath);
24
- fileStream.pipe(hash, {
25
- end: false
26
- });
27
- fileStream.on('end', ()=>{
28
- hash.end();
29
- const h = hash.read().toString();
30
- resolve(h);
31
- });
32
- });
33
- }
34
- return hashCache[filePath];
35
- };
36
- /**
37
- * Computes the checksum for the given files
38
- */ const checkSum = async (files)=>{
39
- const hashes = {};
40
- await files.reduce(async (p, file)=>p.then(async ()=>{
41
- hashes[file] = await hashFile(file);
42
- }), Promise.resolve());
43
- return hashes;
44
- };
@@ -1,4 +0,0 @@
1
- /**
2
- * Returns the common ancestor directory from a list of files
3
- */
4
- export declare const commonParent: (files: string[]) => string;
@@ -1,12 +0,0 @@
1
- import { parse, sep } from 'node:path';
2
- /**
3
- * Returns the common ancestor directory from a list of files
4
- */ export const commonParent = (files)=>{
5
- if (files.length === 1) return parse(files[0] ?? '').dir + sep;
6
- let index = 0;
7
- let prefix = '/';
8
- while(files.filter((f)=>f.startsWith(prefix)).length === files.length){
9
- prefix = files[0]?.slice(0, index++) ?? '';
10
- }
11
- return prefix.slice(0, prefix.lastIndexOf('/') + 1);
12
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,22 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { describe, it } from 'node:test';
3
- import { commonParent } from './commonParent.js';
4
- void describe('commonParent()', ()=>{
5
- void it('should return the common parent directory', ()=>assert.equal(commonParent([
6
- '/some/dir/lambda/onMessage.ts',
7
- '/some/dir/lambda/notifyClients.ts',
8
- '/some/dir/lambda/wirepasPublish.ts',
9
- '/some/dir/wirepas-5g-mesh-gateway/protobuf/ts/data_message.ts'
10
- ]), '/some/dir/'));
11
- void it('should return the entire parent tree for a single file', ()=>assert.equal(commonParent([
12
- '/some/dir/lambda/onMessage.ts'
13
- ]), '/some/dir/lambda/'));
14
- void it('should return "/" if files have no common directory', ()=>assert.equal(commonParent([
15
- '/some/dir/lambda/onMessage.ts',
16
- '/other/dir/lambda/onMessage.ts'
17
- ]), '/'));
18
- void it('should return the common ancestor only up until the directory level', ()=>assert.equal(commonParent([
19
- '/some/dir/lambdas/cors.ts',
20
- '/some/dir/lambdas/corsHeaders.ts'
21
- ]), '/some/dir/lambdas/'));
22
- });
@@ -1,22 +0,0 @@
1
- /**
2
- * Resolve project-level dependencies for the given file using TypeScript compiler API
3
- */
4
- export declare const findDependencies: (args: {
5
- sourceFilePath: string;
6
- imports?: string[];
7
- visited?: string[];
8
- packages?: Set<string>;
9
- tsConfigFilePath?: string;
10
- importsSubpathPatterns?: Record<string, string>;
11
- }) => {
12
- dependencies: string[];
13
- /**
14
- * A map of import subpath patterns to their resolved paths
15
- * @see https://nodejs.org/api/packages.html#subpath-patterns
16
- */
17
- importsSubpathPatterns: Record<string, string>;
18
- /**
19
- * The external packages that the source file depends on
20
- */
21
- packages: Set<string>;
22
- };
@@ -1,97 +0,0 @@
1
- import { readFileSync, statSync } from 'node:fs';
2
- import path from 'node:path';
3
- import ts from 'typescript';
4
- /**
5
- * Resolve project-level dependencies for the given file using TypeScript compiler API
6
- */ export const findDependencies = (args)=>{
7
- const sourceFilePath = args.sourceFilePath;
8
- const visited = args.visited ?? [];
9
- const dependencies = args.imports ?? [];
10
- const packages = args.packages ?? new Set();
11
- const importsSubpathPatterns = args.importsSubpathPatterns ?? {};
12
- if (visited.includes(sourceFilePath)) return {
13
- dependencies,
14
- importsSubpathPatterns,
15
- packages
16
- };
17
- const tsConfigFilePath = args.tsConfigFilePath;
18
- const tsConfig = tsConfigFilePath !== undefined ? JSON.parse(readFileSync(tsConfigFilePath, 'utf-8').toString()) : undefined;
19
- const fileNode = ts.createSourceFile(sourceFilePath, readFileSync(sourceFilePath, 'utf-8').toString(), ts.ScriptTarget.ES2022, /*setParentNodes */ true);
20
- const parseChild = (node)=>{
21
- if (node.kind !== ts.SyntaxKind.ImportDeclaration && node.kind !== ts.SyntaxKind.ExportDeclaration) return;
22
- const moduleSpecifier = node.moduleSpecifier.text;
23
- const { resolvedPath: file } = resolve({
24
- moduleSpecifier,
25
- sourceFilePath,
26
- tsConfigFilePath,
27
- tsConfig,
28
- importsSubpathPatterns
29
- });
30
- try {
31
- const s = statSync(file);
32
- if (!s.isDirectory()) dependencies.push(file);
33
- } catch {
34
- // Module or file not found
35
- visited.push(file);
36
- packages.add(moduleSpecifier);
37
- }
38
- };
39
- ts.forEachChild(fileNode, parseChild);
40
- visited.push(sourceFilePath);
41
- for (const file of dependencies){
42
- findDependencies({
43
- sourceFilePath: file,
44
- imports: dependencies,
45
- visited,
46
- tsConfigFilePath,
47
- importsSubpathPatterns,
48
- packages
49
- });
50
- }
51
- return {
52
- dependencies,
53
- importsSubpathPatterns,
54
- packages: new Set([
55
- ...packages.difference(new Set([
56
- 'aws-lambda'
57
- ]))
58
- ].filter((p)=>!p.startsWith('node:')).filter((p)=>!p.startsWith('@aws-sdk/')).map((d)=>{
59
- if (d.startsWith('@')) {
60
- const [org, packageName] = d.split('/');
61
- return `${org}/${packageName}`;
62
- }
63
- return d.split('/')[0];
64
- }))
65
- };
66
- };
67
- const resolve = ({ moduleSpecifier, sourceFilePath, tsConfigFilePath, tsConfig, importsSubpathPatterns })=>{
68
- if (moduleSpecifier.startsWith('.')) return {
69
- resolvedPath: path.resolve(path.parse(sourceFilePath).dir, moduleSpecifier)
70
- };
71
- if (tsConfigFilePath !== undefined && tsConfig?.compilerOptions?.paths !== undefined) {
72
- for (const [key, value] of Object.entries(tsConfig.compilerOptions.paths)){
73
- const [resolvedPath] = value;
74
- if (resolvedPath === undefined) continue;
75
- // Exact match
76
- if (moduleSpecifier === key) {
77
- const fullResolvedPath = path.join(path.parse(tsConfigFilePath).dir, resolvedPath);
78
- importsSubpathPatterns[key] = resolvedPath;
79
- return {
80
- resolvedPath: fullResolvedPath
81
- };
82
- }
83
- // Wildcard match
84
- if (!key.includes('*')) continue;
85
- const rx = new RegExp(`^${key.replace('*', '(?<wildcard>.*)')}`);
86
- const maybeMatch = rx.exec(moduleSpecifier);
87
- if (maybeMatch?.groups?.wildcard === undefined) continue;
88
- importsSubpathPatterns[key] = resolvedPath;
89
- return {
90
- resolvedPath: path.resolve(path.parse(tsConfigFilePath).dir, resolvedPath.replace('*', maybeMatch.groups.wildcard))
91
- };
92
- }
93
- }
94
- return {
95
- resolvedPath: moduleSpecifier
96
- };
97
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,37 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import path from 'node:path';
3
- import { describe, it } from 'node:test';
4
- import { URL } from 'node:url';
5
- import { findDependencies } from './findDependencies.js';
6
- const __dirname = new URL('.', import.meta.url).pathname;
7
- void describe('findDependencies()', ()=>{
8
- void it('should return a list of external dependencies', ()=>{
9
- const { packages } = findDependencies({
10
- sourceFilePath: path.join(__dirname, '..', 'cdk', 'lambda.ts')
11
- });
12
- assert.equal(packages.has('id128'), true, "Should include the 'id128' package");
13
- assert.equal(packages.has('aws-lambda'), false, "Should not include the type-only 'aws-lambda' package");
14
- assert.equal(packages.has('node:crypto'), false, 'Should not include built-in node dependencies');
15
- assert.equal(packages.has('fp-ts'), true, 'Should include the top-level package only');
16
- assert.equal(packages.has('@aws-sdk/client-dynamodb'), false, 'Should not include AWS SDK packages');
17
- });
18
- void it('should honor tsconfig.json paths', ()=>{
19
- const { dependencies } = findDependencies({
20
- sourceFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'lambda.ts'),
21
- tsConfigFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'tsconfig.json')
22
- });
23
- assert.equal(dependencies.includes(path.join(__dirname, 'test-data', 'resolve-paths', 'foo', 'index.ts')), true, 'Should include the index.ts file');
24
- assert.equal(dependencies.includes(path.join(__dirname, 'test-data', 'resolve-paths', 'foo', '1.ts')), true, 'Should include the module referenced in the index.ts file');
25
- assert.equal(dependencies.includes(path.join(__dirname, 'test-data', 'resolve-paths', 'foo', '2.ts')), true, 'Should include the module file');
26
- });
27
- void it('should return an import map', ()=>{
28
- const { importsSubpathPatterns } = findDependencies({
29
- sourceFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'lambda.ts'),
30
- tsConfigFilePath: path.join(__dirname, 'test-data', 'resolve-paths', 'tsconfig.json')
31
- });
32
- assert.deepEqual(importsSubpathPatterns, {
33
- '#foo': './foo/index.ts',
34
- '#foo/*': './foo/*'
35
- });
36
- });
37
- });
package/npm/isTest.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import type { Construct } from 'constructs';
2
- export declare const isTest: (construct: Construct) => boolean;
package/npm/isTest.js DELETED
@@ -1 +0,0 @@
1
- export const isTest = (construct)=>construct.node.tryGetContext('isTest') === true;
@@ -1 +0,0 @@
1
- export {};
@@ -1,16 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it, mock } from 'node:test';
3
- import { isTest } from './isTest.js';
4
- void describe('isTest()', ()=>{
5
- void it('should return true if the construct is a test', ()=>{
6
- const construct = {
7
- node: {
8
- tryGetContext: mock.fn(()=>true)
9
- }
10
- };
11
- const result = isTest(construct);
12
- assert.equal(result, true);
13
- assert.equal(construct.node.tryGetContext.mock.calls.length, 1);
14
- assert.equal(construct.node.tryGetContext.mock.calls[0]?.arguments[0], 'isTest');
15
- });
16
- });
package/npm/lambda.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './packLambda.ts';
2
- export * from './packLambdaFromPath.ts';
package/npm/lambda.js DELETED
@@ -1,2 +0,0 @@
1
- export * from './packLambda.js';
2
- export * from './packLambdaFromPath.js';
package/npm/layer.d.ts DELETED
@@ -1 +0,0 @@
1
- export * from './packLayer.ts';
package/npm/layer.js DELETED
@@ -1 +0,0 @@
1
- export * from './packLayer.js';
@@ -1 +0,0 @@
1
- export {};
@@ -1,19 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import fs from 'node:fs/promises';
3
- import os from 'node:os';
4
- import path, { dirname } from 'node:path';
5
- import { describe, it } from 'node:test';
6
- import { fileURLToPath } from 'node:url';
7
- import { ImportFromFolderNameError, packLambda } from './packLambda.js';
8
- const tmpDir = os.tmpdir();
9
- void describe('packLambda()', ()=>{
10
- // See https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/issues/93#issuecomment-2042201321
11
- void it('should fail if it imports from a folder on the same level that has the same name as the handler module', async ()=>assert.rejects(async ()=>packLambda({
12
- sourceFilePath: path.join(dirname(fileURLToPath(import.meta.url)), 'test-data', 'module-folder-named-like-handler-bug', 'same-level', 'acme.ts'),
13
- zipFilePath: path.join(await fs.mkdtemp(`${tmpDir}${path.sep}`), 'acme.zip')
14
- }), ImportFromFolderNameError));
15
- void it('should not fail if it a folder with the same name is on a different level', async ()=>assert.doesNotReject(async ()=>packLambda({
16
- sourceFilePath: path.join(dirname(fileURLToPath(import.meta.url)), 'test-data', 'module-folder-named-like-handler-bug', 'different-level', 'lambda', 'acme.ts'),
17
- zipFilePath: path.join(await fs.mkdtemp(`${tmpDir}${path.sep}`), 'acme.zip')
18
- })));
19
- });
@@ -1,29 +0,0 @@
1
- export type PackedLambda = {
2
- id: string;
3
- zipFilePath: string;
4
- handler: string;
5
- hash: string;
6
- };
7
- /**
8
- * In the bundle we only include code that's not in the layer.
9
- */
10
- export declare const packLambda: ({ sourceFilePath, zipFilePath, tsConfigFilePath, debug, progress, }: {
11
- sourceFilePath: string;
12
- zipFilePath: string;
13
- /**
14
- * Pass the path to the tsconfig.json file if you want to use paths from the tsconfig.json file.
15
- */
16
- tsConfigFilePath?: string;
17
- debug?: (label: string, info: string) => void;
18
- progress?: (label: string, info: string) => void;
19
- }) => Promise<{
20
- handler: string;
21
- hash: string;
22
- }>;
23
- /**
24
- * @see https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/issues/93#issuecomment-2042201321
25
- */
26
- export declare class ImportFromFolderNameError extends Error {
27
- readonly folderName: string;
28
- constructor(folderName: string);
29
- }
package/npm/packLambda.js DELETED
@@ -1,104 +0,0 @@
1
- import swc from '@swc/core';
2
- import { createWriteStream } from 'node:fs';
3
- import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { parse } from 'path';
6
- import yazl from 'yazl';
7
- import { checkSumOfFiles } from './checksumOfFiles.js';
8
- import { commonParent } from './commonParent.js';
9
- import { findDependencies } from './findDependencies.js';
10
- import { updateImports } from './updateImports.js';
11
- const removeCommonAncestor = (parentDir)=>(filePath)=>{
12
- const p = parse(filePath);
13
- const jsFileName = [
14
- p.dir.replace(parentDir.slice(0, parentDir.length - 1), ''),
15
- `${p.name}.js`
16
- ].join('/')// Replace leading slash
17
- .replace(/^\//, '');
18
- return jsFileName;
19
- };
20
- /**
21
- * In the bundle we only include code that's not in the layer.
22
- */ export const packLambda = async ({ sourceFilePath, zipFilePath, tsConfigFilePath, debug, progress })=>{
23
- const { dependencies: deps, importsSubpathPatterns, packages } = findDependencies({
24
- sourceFilePath,
25
- tsConfigFilePath
26
- });
27
- debug?.(`${sourceFilePath}: dependencies`, [
28
- ...packages
29
- ].join(', '));
30
- Object.entries(importsSubpathPatterns).forEach(([k, v])=>{
31
- debug?.(`${sourceFilePath}:importsSubpathPattern`, `${k} -> ${v}`);
32
- });
33
- const lambdaFiles = [
34
- sourceFilePath,
35
- ...deps
36
- ];
37
- const zipfile = new yazl.ZipFile();
38
- const stripCommon = removeCommonAncestor(commonParent(lambdaFiles));
39
- const handler = stripCommon(sourceFilePath);
40
- // Make sure that the handler does not import from a folder with the same name in the folder
41
- const handlerInfo = path.parse(handler);
42
- const handlerName = handlerInfo.name;
43
- const handlerDir = handlerInfo.dir;
44
- const handlerDepsFromSameDirectory = deps.map(stripCommon).filter((d)=>handlerDir === '' ? true : d.startsWith(`${handlerDir}${path.sep}`));
45
- const handlerDepsFolderNames = new Set(handlerDepsFromSameDirectory.map((s)=>s.split('/')[0]));
46
- if (handlerDepsFolderNames.has(handlerName)) {
47
- throw new ImportFromFolderNameError(handlerName);
48
- }
49
- // Compile files
50
- for (const file of lambdaFiles){
51
- let compiled = (await swc.transformFile(file, {
52
- jsc: {
53
- target: 'es2022'
54
- }
55
- })).code;
56
- debug?.(`compiled`, compiled);
57
- compiled = updateImports(compiled);
58
- debug?.(`converted imports`, compiled);
59
- const jsFileName = stripCommon(file);
60
- zipfile.addBuffer(Buffer.from(compiled, 'utf-8'), jsFileName);
61
- progress?.(`added`, jsFileName);
62
- }
63
- const hash = await checkSumOfFiles([
64
- ...lambdaFiles,
65
- // Include this script, so artefact is updated if the way it's built is changed
66
- fileURLToPath(import.meta.url)
67
- ]);
68
- // Mark it as ES module
69
- zipfile.addBuffer(Buffer.from(JSON.stringify({
70
- type: 'module',
71
- imports: Object.fromEntries(Object.entries(importsSubpathPatterns).map(([k, v])=>[
72
- k,
73
- v.replace(/\.ts$/, '.js')
74
- ])),
75
- dependencies: Object.fromEntries([
76
- ...packages.values()
77
- ].sort((a, b)=>a.localeCompare(b)).map((pkg)=>[
78
- pkg,
79
- '*'
80
- ]))
81
- }, null, 2), 'utf-8'), 'package.json');
82
- progress?.(`added`, 'package.json');
83
- await new Promise((resolve)=>{
84
- zipfile.outputStream.pipe(createWriteStream(zipFilePath)).on('close', ()=>{
85
- resolve();
86
- });
87
- zipfile.end();
88
- });
89
- progress?.(`written`, zipFilePath);
90
- return {
91
- handler: stripCommon(sourceFilePath),
92
- hash
93
- };
94
- };
95
- /**
96
- * @see https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/issues/93#issuecomment-2042201321
97
- */ export class ImportFromFolderNameError extends Error {
98
- folderName;
99
- constructor(folderName){
100
- super(`Import from folder with same name as handler ("${folderName}") not allowed!`);
101
- this.name = 'ImportFromFolderNameError';
102
- this.folderName = folderName;
103
- }
104
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,28 +0,0 @@
1
- import assert from 'node:assert';
2
- import fs from 'node:fs/promises';
3
- import os from 'node:os';
4
- import path, { dirname } from 'node:path';
5
- import { describe, it } from 'node:test';
6
- import { fileURLToPath } from 'node:url';
7
- import { packLambda } from './packLambda.js';
8
- import { getFileFromZip } from './test/getFileFromZip.js';
9
- const tmpDir = os.tmpdir();
10
- await describe('packLambda()', async ()=>{
11
- await it('should convert .ts imports to .js', async ()=>{
12
- const zipFilePath = path.join(await fs.mkdtemp(`${tmpDir}${path.sep}`), 'resolve-paths.zip');
13
- await packLambda({
14
- sourceFilePath: path.join(dirname(fileURLToPath(import.meta.url)), 'test-data', 'resolve-paths', 'lambda.ts'),
15
- tsConfigFilePath: path.join(dirname(fileURLToPath(import.meta.url)), 'test-data', 'resolve-paths', 'tsconfig.json'),
16
- zipFilePath
17
- });
18
- const handler = await getFileFromZip(zipFilePath, 'lambda.js');
19
- assert.equal(handler.includes(`import { foo2 } from '#foo/2.js'`), true, 'The import should be converted to .js');
20
- await assert.doesNotReject(getFileFromZip(zipFilePath, 'foo/2.js'), 'The ZIP file should contain the imported file');
21
- const pJson = JSON.parse(await getFileFromZip(zipFilePath, 'package.json'));
22
- assert.equal(pJson.type, 'module', 'The package.json should contain "type": "module"');
23
- assert.deepEqual(pJson.imports, {
24
- '#foo': './foo/index.js',
25
- '#foo/*': './foo/*'
26
- }, 'The package.json should contain the imports');
27
- });
28
- });
@@ -1,20 +0,0 @@
1
- import { type PackedLambda } from './packLambda.ts';
2
- export declare const packLambdaFromPath: ({ id, sourceFilePath, handlerFunction: handlerFunctionArg, baseDir: baseDirArg, distDir: distDirArg, tsConfigFilePath, debug, progress, }: {
3
- id: string;
4
- sourceFilePath: string;
5
- handlerFunction?: string;
6
- /**
7
- * @default process.cwd()
8
- */
9
- baseDir?: string;
10
- /**
11
- * @default ${baseDir}/dist/lambdas
12
- */
13
- distDir?: string;
14
- /**
15
- * Pass the path to the tsconfig.json file if you want to use paths from the tsconfig.json file.
16
- */
17
- tsConfigFilePath?: string;
18
- debug?: (label: string, info: string) => void;
19
- progress?: (label: string, info: string) => void;
20
- }) => Promise<PackedLambda>;
@@ -1,33 +0,0 @@
1
- import { mkdir } from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { checkSumOfStrings } from './checksumOfFiles.js';
4
- import { packLambda } from './packLambda.js';
5
- export const packLambdaFromPath = async ({ id, sourceFilePath, handlerFunction: handlerFunctionArg, baseDir: baseDirArg, distDir: distDirArg, tsConfigFilePath, debug, progress })=>{
6
- const distDir = distDirArg ?? path.join(process.cwd(), 'dist', 'lambdas');
7
- const baseDir = baseDirArg ?? process.cwd();
8
- const handlerFunction = handlerFunctionArg ?? 'handler';
9
- try {
10
- await mkdir(distDir, {
11
- recursive: true
12
- });
13
- } catch {
14
- // Directory exists
15
- }
16
- const zipFile = path.join(distDir, `${id}.zip`);
17
- const { handler, hash } = await packLambda({
18
- sourceFilePath: path.join(baseDir, sourceFilePath),
19
- zipFilePath: zipFile,
20
- tsConfigFilePath,
21
- debug,
22
- progress
23
- });
24
- return {
25
- id,
26
- zipFilePath: zipFile,
27
- handler: handler.replace('.js', `.${handlerFunction}`),
28
- hash: checkSumOfStrings([
29
- id,
30
- hash
31
- ])
32
- };
33
- };
@@ -1,23 +0,0 @@
1
- export type PackedLayer = {
2
- layerZipFilePath: string;
3
- hash: string;
4
- };
5
- export declare const packLayer: ({ id, dependencies, baseDir, distDir, installCommand, }: {
6
- id: string;
7
- dependencies: string[];
8
- /**
9
- * @default process.cwd()
10
- */
11
- baseDir?: string;
12
- /**
13
- * @default ${baseDir}/dist/layers
14
- */
15
- distDir?: string;
16
- /**
17
- * Returns the command to run, the first element is the command (e.g. `npm`) and the rest are its arguments.
18
- */
19
- installCommand?: (args: {
20
- packageFilePath: string;
21
- packageLockFilePath: string;
22
- }) => [string, ...Array<string>];
23
- }) => Promise<PackedLayer>;
package/npm/packLayer.js DELETED
@@ -1,113 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import { createWriteStream } from 'fs';
3
- import { copyFile, mkdir, readFile, rm, stat, writeFile } from 'fs/promises';
4
- import { glob } from 'node:fs/promises';
5
- import { fileURLToPath } from 'node:url';
6
- import path from 'path';
7
- import { ZipFile } from 'yazl';
8
- import { checkSumOfFiles, checkSumOfStrings } from './checksumOfFiles.js';
9
- export const packLayer = async ({ id, dependencies, baseDir, distDir, installCommand })=>{
10
- const base = baseDir ?? process.cwd();
11
- const dist = distDir ?? path.join(base, 'dist', 'layers');
12
- const packageJsonFile = path.join(base, 'package.json');
13
- const npmrcFile = path.join(base, '.npmrc');
14
- const packageLockJsonFile = path.join(base, 'package-lock.json');
15
- const { dependencies: deps, devDependencies: devDeps } = JSON.parse(await readFile(packageJsonFile, 'utf-8'));
16
- const layerDir = path.join(dist, id);
17
- const nodejsDir = path.join(layerDir, 'nodejs');
18
- try {
19
- await rm(layerDir, {
20
- recursive: true
21
- });
22
- } catch {
23
- // Folder does not exist.
24
- }
25
- await mkdir(nodejsDir, {
26
- recursive: true
27
- });
28
- const depsToBeInstalled = dependencies.reduce((resolved, dep)=>{
29
- const resolvedDependency = deps[dep] ?? devDeps[dep];
30
- if (resolvedDependency === undefined) throw new Error(`Could not resolve dependency "${dep}" in ${packageJsonFile}!`);
31
- return {
32
- ...resolved,
33
- [dep]: resolvedDependency
34
- };
35
- }, {});
36
- const checkSumFiles = [
37
- // Include this script, so artefact is updated if the way it's built is changed
38
- fileURLToPath(import.meta.url)
39
- ];
40
- const packageJSON = path.join(nodejsDir, 'package.json');
41
- await writeFile(packageJSON, JSON.stringify({
42
- dependencies: depsToBeInstalled
43
- }), 'utf-8');
44
- checkSumFiles.push(packageJSON);
45
- let hasNpmRcFile = true;
46
- try {
47
- // .npmrc may not exist
48
- await stat(npmrcFile);
49
- await copyFile(npmrcFile, path.join(nodejsDir, '.npmrc'));
50
- checkSumFiles.push(npmrcFile);
51
- } catch {
52
- hasNpmRcFile = false;
53
- }
54
- let hasLockFile = true;
55
- try {
56
- // package-lock.json may not exist
57
- await stat(packageLockJsonFile);
58
- const packageLock = path.join(nodejsDir, 'package-lock.json');
59
- await copyFile(packageLockJsonFile, packageLock);
60
- checkSumFiles.push(packageLock);
61
- } catch {
62
- hasLockFile = false;
63
- // pass
64
- }
65
- await new Promise((resolve, reject)=>{
66
- const [cmd, ...args] = installCommand?.({
67
- packageFilePath: packageJSON,
68
- packageLockFilePath: packageLockJsonFile
69
- }) ?? [
70
- 'npm',
71
- hasLockFile ? 'ci' : 'i',
72
- '--ignore-scripts',
73
- '--only=prod',
74
- '--no-audit'
75
- ];
76
- const p = spawn(cmd, args, {
77
- cwd: nodejsDir
78
- });
79
- p.on('close', (code)=>{
80
- if (code !== 0) {
81
- const msg = `${cmd} ${args.join(' ')} in ${nodejsDir} exited with code ${code}.`;
82
- return reject(new Error(msg));
83
- }
84
- return resolve();
85
- });
86
- });
87
- const zipfile = new ZipFile();
88
- for await (const f of glob(`**`, {
89
- cwd: layerDir
90
- })){
91
- if ((await stat(path.join(layerDir, f))).isDirectory()) {
92
- continue;
93
- }
94
- zipfile.addFile(path.join(layerDir, f), f);
95
- }
96
- if (hasNpmRcFile) {
97
- zipfile.addFile(path.join(nodejsDir, '.npmrc'), 'nodejs/.npmrc');
98
- }
99
- const zipFileName = await new Promise((resolve)=>{
100
- const zipFileName = path.join(dist, `${id}.zip`);
101
- zipfile.outputStream.pipe(createWriteStream(zipFileName)).on('close', ()=>{
102
- resolve(zipFileName);
103
- });
104
- zipfile.end();
105
- });
106
- return {
107
- layerZipFilePath: zipFileName,
108
- hash: checkSumOfStrings([
109
- JSON.stringify(dependencies),
110
- await checkSumOfFiles(checkSumFiles)
111
- ])
112
- };
113
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,24 +0,0 @@
1
- import assert from 'node:assert';
2
- import fs from 'node:fs/promises';
3
- import os from 'node:os';
4
- import path, { dirname } from 'node:path';
5
- import { describe, it } from 'node:test';
6
- import { fileURLToPath } from 'node:url';
7
- import { packLayer } from './packLayer.js';
8
- import { getFileFromZip } from './test/getFileFromZip.js';
9
- const tmpDir = os.tmpdir();
10
- await describe('packLayer()', async ()=>{
11
- await it('should include a .npmrc file if present', async ()=>{
12
- const distDir = path.join(await fs.mkdtemp(`${tmpDir}${path.sep}`), 'jsr');
13
- const layer = await packLayer({
14
- baseDir: path.join(dirname(fileURLToPath(import.meta.url)), 'test-data', 'jsr'),
15
- distDir,
16
- id: 'jsr-layer',
17
- dependencies: [
18
- '@nrfcloud/wait-for-it'
19
- ]
20
- });
21
- const npmrc = await getFileFromZip(layer.layerZipFilePath, 'nodejs/.npmrc');
22
- assert.equal(npmrc.includes(`@jsr:registry=https://npm.jsr.io`), true, 'The .npmrc file should contain the JSR registry');
23
- });
24
- });
@@ -1 +0,0 @@
1
- export declare const getFileFromZip: (zipFilePath: string, filename: string) => Promise<string>;
@@ -1 +0,0 @@
1
- export declare const updateImports: (source: string) => string;
@@ -1 +0,0 @@
1
- export const updateImports = (source)=>source.replace(/from ['"](.+?)\.ts['"]/g, "from '$1.js'");
@@ -1 +0,0 @@
1
- export {};
@@ -1,10 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { updateImports } from './updateImports.js';
4
- void describe('updateImports', ()=>{
5
- void it('replaces .ts with .js in relative imports', ()=>{
6
- const input = `import { foo } from './bar.js';`;
7
- const expected = `import { foo } from './bar.js';`;
8
- assert.equal(updateImports(input), expected);
9
- });
10
- });
@@ -1,7 +0,0 @@
1
- import { type CloudFormationClient } from '@aws-sdk/client-cloudformation';
2
- import { type LambdaClient } from '@aws-sdk/client-lambda';
3
- import type { PackedLambda } from './packLambda.ts';
4
- export declare const updateLambdaCode: ({ cf, lambda }: {
5
- cf: CloudFormationClient;
6
- lambda: LambdaClient;
7
- }) => (stackName: string, packedLambdas: Record<string, PackedLambda>, debug?: (...args: Array<any>) => void) => Promise<void>;
@@ -1,75 +0,0 @@
1
- import { paginateListStackResources } from '@aws-sdk/client-cloudformation';
2
- import { GetFunctionCommand, LastUpdateStatus, TagResourceCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand } from '@aws-sdk/client-lambda';
3
- import assert from 'node:assert';
4
- import { readFile } from 'node:fs/promises';
5
- import pRetry from 'p-retry';
6
- const updateLambda = ({ lambda, packs, debug, forceUpdate })=>async (lambdaResource)=>{
7
- const info = await lambda.send(new GetFunctionCommand({
8
- FunctionName: lambdaResource.PhysicalResourceId
9
- }));
10
- const packed = Object.values(packs).find((p)=>p.id === info.Tags?.['packedLambda:id']);
11
- if (packed === undefined) {
12
- debug?.(`[${info.Tags?.['packedLambda:id']}]`, 'No pack found for lambda');
13
- return;
14
- }
15
- debug?.(`[${packed.id}]`, 'found pack');
16
- if ((forceUpdate ?? false) === false) {
17
- if (packed.hash === info.Tags?.['packedLambda:hash']) {
18
- debug?.(`[${packed.id}]`, 'No update needed');
19
- return;
20
- }
21
- }
22
- if (packed.hash !== info.Tags?.['packedLambda:hash']) {
23
- debug?.(`[${packed.id}]`, 'Updating lambda');
24
- }
25
- const updateResult = await lambda.send(new UpdateFunctionCodeCommand({
26
- FunctionName: lambdaResource.PhysicalResourceId,
27
- ZipFile: new Uint8Array(await readFile(packed.zipFilePath))
28
- }));
29
- debug?.(`[${packed.id}]`, 'RevisionId', updateResult.RevisionId);
30
- debug?.(`[${packed.id}]`, 'CodeSha256', updateResult.CodeSha256);
31
- await pRetry(async ()=>{
32
- const updateStatus = (await lambda.send(new GetFunctionCommand({
33
- FunctionName: lambdaResource.PhysicalResourceId
34
- }))).Configuration?.LastUpdateStatus;
35
- assert.equal(updateStatus, LastUpdateStatus.Successful, `Lambda update should have succeeded, got ${updateStatus}`);
36
- }, {
37
- minTimeout: 1000,
38
- maxTimeout: 1000,
39
- retries: 10
40
- });
41
- debug?.(`[${packed.id}]`, `updating config to trigger reload ...`);
42
- await Promise.all([
43
- await lambda.send(new UpdateFunctionConfigurationCommand({
44
- FunctionName: lambdaResource.PhysicalResourceId,
45
- Environment: {
46
- Variables: {
47
- ...info.Configuration?.Environment?.Variables,
48
- PACKED_LAMBDA_HASH: packed.hash
49
- }
50
- }
51
- })),
52
- await lambda.send(new TagResourceCommand({
53
- Resource: info.Configuration.FunctionArn,
54
- Tags: {
55
- 'packedLambda:hash': packed.hash
56
- }
57
- }))
58
- ]);
59
- };
60
- export const updateLambdaCode = ({ cf, lambda })=>async (stackName, packedLambdas, debug)=>{
61
- const u = updateLambda({
62
- lambda,
63
- packs: packedLambdas,
64
- debug
65
- });
66
- const p = [];
67
- for await (const page of paginateListStackResources({
68
- client: cf
69
- }, {
70
- StackName: stackName
71
- })){
72
- p.push(...(page.StackResourceSummaries ?? []).filter((resource)=>resource.ResourceType === 'AWS::Lambda::Function').map(u) ?? []);
73
- }
74
- await Promise.all(p);
75
- };
package/npm/util.d.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from './checksumOfFiles.ts';
2
- export * from './commonParent.ts';
3
- export * from './findDependencies.ts';
4
- export * from './isTest.ts';
5
- export * from './updateLambdaCode.ts';
package/npm/util.js DELETED
@@ -1,5 +0,0 @@
1
- export * from './checksumOfFiles.js';
2
- export * from './commonParent.js';
3
- export * from './findDependencies.js';
4
- export * from './isTest.js';
5
- export * from './updateLambdaCode.js';