@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.
- package/LICENSE +1 -1
- package/README.md +16 -0
- package/package.json +37 -34
- package/npm/IoTActionRole.d.ts +0 -10
- package/npm/IoTActionRole.js +0 -29
- package/npm/LambdaLogGroup.d.ts +0 -6
- package/npm/LambdaLogGroup.js +0 -16
- package/npm/LambdaSource.d.ts +0 -7
- package/npm/LambdaSource.js +0 -12
- package/npm/PackedLambdaFn.d.ts +0 -25
- package/npm/PackedLambdaFn.js +0 -53
- package/npm/cdk.d.ts +0 -4
- package/npm/cdk.js +0 -4
- package/npm/checkSumOfFiles.spec.d.ts +0 -1
- package/npm/checkSumOfFiles.spec.js +0 -15
- package/npm/checksumOfFiles.d.ts +0 -5
- package/npm/checksumOfFiles.js +0 -44
- package/npm/commonParent.d.ts +0 -4
- package/npm/commonParent.js +0 -12
- package/npm/commonParent.spec.d.ts +0 -1
- package/npm/commonParent.spec.js +0 -22
- package/npm/findDependencies.d.ts +0 -22
- package/npm/findDependencies.js +0 -97
- package/npm/findDependencies.spec.d.ts +0 -1
- package/npm/findDependencies.spec.js +0 -37
- package/npm/isTest.d.ts +0 -2
- package/npm/isTest.js +0 -1
- package/npm/isTest.spec.d.ts +0 -1
- package/npm/isTest.spec.js +0 -16
- package/npm/lambda.d.ts +0 -2
- package/npm/lambda.js +0 -2
- package/npm/layer.d.ts +0 -1
- package/npm/layer.js +0 -1
- package/npm/module-folder-named-like-handler-bug.spec.d.ts +0 -1
- package/npm/module-folder-named-like-handler-bug.spec.js +0 -19
- package/npm/packLambda.d.ts +0 -29
- package/npm/packLambda.js +0 -104
- package/npm/packLambda.spec.d.ts +0 -1
- package/npm/packLambda.spec.js +0 -28
- package/npm/packLambdaFromPath.d.ts +0 -20
- package/npm/packLambdaFromPath.js +0 -33
- package/npm/packLayer.d.ts +0 -23
- package/npm/packLayer.js +0 -113
- package/npm/packLayer.spec.d.ts +0 -1
- package/npm/packLayer.spec.js +0 -24
- package/npm/test/getFileFromZip.d.ts +0 -1
- package/npm/updateImports.d.ts +0 -1
- package/npm/updateImports.js +0 -1
- package/npm/updateImports.spec.d.ts +0 -1
- package/npm/updateImports.spec.js +0 -10
- package/npm/updateLambdaCode.d.ts +0 -7
- package/npm/updateLambdaCode.js +0 -75
- package/npm/util.d.ts +0 -5
- package/npm/util.js +0 -5
package/LICENSE
CHANGED
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": "
|
|
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": "./
|
|
9
|
-
"default": "./
|
|
8
|
+
"types": "./lambda.ts",
|
|
9
|
+
"default": "./dist/src/lambda.js"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
"./layer": {
|
|
13
13
|
"import": {
|
|
14
|
-
"types": "./
|
|
15
|
-
"default": "./
|
|
14
|
+
"types": "./layer.ts",
|
|
15
|
+
"default": "./dist/src/layer.js"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"./util": {
|
|
19
19
|
"import": {
|
|
20
|
-
"types": "./
|
|
21
|
-
"default": "./
|
|
20
|
+
"types": "./util.ts",
|
|
21
|
+
"default": "./dist/src/util.js"
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"./cdk": {
|
|
25
25
|
"import": {
|
|
26
|
-
"types": "./
|
|
27
|
-
"default": "./
|
|
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": "
|
|
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.
|
|
57
|
-
"@aws-sdk/client-dynamodb": "3.
|
|
58
|
-
"@bifravst/cloudformation-helpers": "
|
|
59
|
-
"@bifravst/eslint-config-typescript": "
|
|
60
|
-
"@bifravst/from-env": "
|
|
61
|
-
"@bifravst/prettier-config": "
|
|
62
|
-
"@commitlint/config-conventional": "
|
|
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.
|
|
65
|
-
"@types/node": "
|
|
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
|
|
69
|
-
"cdk": "2.
|
|
70
|
-
"
|
|
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": "
|
|
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": ">=
|
|
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.
|
|
119
|
+
"@swc/core": "1.16.0",
|
|
118
120
|
"fp-ts": "2.16.11",
|
|
119
|
-
"p-retry": "
|
|
120
|
-
"typescript": "
|
|
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.
|
|
125
|
-
"@bifravst/aws-ssm-settings-helpers": "
|
|
126
|
-
"aws-cdk-lib": "^2.
|
|
127
|
-
"
|
|
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
|
}
|
package/npm/IoTActionRole.d.ts
DELETED
|
@@ -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
|
-
}
|
package/npm/IoTActionRole.js
DELETED
|
@@ -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
|
-
}
|
package/npm/LambdaLogGroup.d.ts
DELETED
package/npm/LambdaLogGroup.js
DELETED
|
@@ -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
|
-
}
|
package/npm/LambdaSource.d.ts
DELETED
|
@@ -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
|
-
}
|
package/npm/LambdaSource.js
DELETED
|
@@ -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
|
-
}
|
package/npm/PackedLambdaFn.d.ts
DELETED
|
@@ -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
|
-
}
|
package/npm/PackedLambdaFn.js
DELETED
|
@@ -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
package/npm/cdk.js
DELETED
|
@@ -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
|
-
});
|
package/npm/checksumOfFiles.d.ts
DELETED
package/npm/checksumOfFiles.js
DELETED
|
@@ -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
|
-
};
|
package/npm/commonParent.d.ts
DELETED
package/npm/commonParent.js
DELETED
|
@@ -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 {};
|
package/npm/commonParent.spec.js
DELETED
|
@@ -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
|
-
};
|
package/npm/findDependencies.js
DELETED
|
@@ -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
package/npm/isTest.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const isTest = (construct)=>construct.node.tryGetContext('isTest') === true;
|
package/npm/isTest.spec.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/npm/isTest.spec.js
DELETED
|
@@ -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
package/npm/lambda.js
DELETED
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
|
-
});
|
package/npm/packLambda.d.ts
DELETED
|
@@ -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
|
-
}
|
package/npm/packLambda.spec.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/npm/packLambda.spec.js
DELETED
|
@@ -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
|
-
};
|
package/npm/packLayer.d.ts
DELETED
|
@@ -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
|
-
};
|
package/npm/packLayer.spec.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/npm/packLayer.spec.js
DELETED
|
@@ -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>;
|
package/npm/updateImports.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const updateImports: (source: string) => string;
|
package/npm/updateImports.js
DELETED
|
@@ -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>;
|
package/npm/updateLambdaCode.js
DELETED
|
@@ -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