@kensio/yulin 1.20.14 → 1.20.15

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 (60) hide show
  1. package/{dist/config/oxlint/cffjs2.oxlintrc.json → cffjs2.oxlintrc.json} +1 -1
  2. package/dist/config/oxlint/cffjs2.oxlint.config.js +4 -3
  3. package/dist/service/cloudformation/resource/resolve/property/sim-cfn-resource-property-resolver.d.ts +11 -4
  4. package/dist/service/cloudformation/resource/resolve/property/sim-cfn-resource-property-resolver.js +21 -8
  5. package/dist/service/cloudformation/resource/sim-cfn-resource.type.d.ts +9 -0
  6. package/dist/service/cloudformation/template/dynamic/make-sim-cfn-dynamic-references.d.ts +2 -0
  7. package/dist/service/cloudformation/template/dynamic/make-sim-cfn-dynamic-references.js +2 -2
  8. package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-resolvers.d.ts +10 -0
  9. package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-resolvers.js +4 -3
  10. package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-scan.d.ts +11 -0
  11. package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-reference-scan.js +21 -0
  12. package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-references.d.ts +24 -2
  13. package/dist/service/cloudformation/template/dynamic/sim-cfn-dynamic-references.js +50 -11
  14. package/dist/service/cloudformation/template/dynamic/sim-cfn-prefetched-dynamic-references.d.ts +42 -0
  15. package/dist/service/cloudformation/template/dynamic/sim-cfn-prefetched-dynamic-references.js +80 -0
  16. package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-failures.d.ts +16 -0
  17. package/dist/service/eventbridge/delivery/sim-event-bridge-delivery-failures.js +19 -0
  18. package/dist/service/iam/command/policy/create-policy/create-policy-input-resolver.js +4 -1
  19. package/dist/service/iam/command/policy/put-role-policy/put-role-policy.handler.js +5 -1
  20. package/dist/service/iam/command/policy/put-user-policy/put-user-policy.handler.js +5 -1
  21. package/dist/service/iam/policy/parse/sim-iam-document-parser.d.ts +4 -1
  22. package/dist/service/iam/policy/parse/sim-iam-document-parser.js +13 -15
  23. package/dist/service/iam/policy/sim-iam-statement-label.d.ts +28 -0
  24. package/dist/service/iam/policy/sim-iam-statement-label.js +17 -0
  25. package/dist/service/iam/policy/sim-iam-statement-strings.d.ts +11 -0
  26. package/dist/service/iam/policy/sim-iam-statement-strings.js +27 -0
  27. package/dist/service/iam/validate/sim-iam-policy-document-validator.d.ts +9 -2
  28. package/dist/service/iam/validate/sim-iam-policy-document-validator.js +37 -19
  29. package/dist/service/lambda/function/code/sim-lambda-vm-zip-function.factory.d.ts +7 -0
  30. package/dist/service/lambda/function/code/sim-lambda-vm-zip-function.factory.js +1 -0
  31. package/dist/service/lambda/function/code/vm/sdk/sim-lambda-provided-sdk-module.d.ts +19 -0
  32. package/dist/service/lambda/function/code/vm/sdk/sim-lambda-provided-sdk-module.js +78 -0
  33. package/dist/service/lambda/function/code/vm/sdk/sim-lambda-sdk-packages-not-installed.error.d.ts +15 -0
  34. package/dist/service/lambda/function/code/vm/sdk/sim-lambda-sdk-packages-not-installed.error.js +27 -0
  35. package/dist/service/lambda/function/code/vm/sdk/sim-lambda-vm-sdk-module-provider.d.ts +9 -0
  36. package/dist/service/lambda/function/code/vm/sdk/sim-sdk-lambda-vm-module-provider.d.ts +10 -0
  37. package/dist/service/lambda/function/code/vm/sdk/sim-sdk-lambda-vm-module-provider.js +28 -34
  38. package/dist/service/lambda/function/code/vm/sim-lambda-host-modules.d.ts +15 -0
  39. package/dist/service/lambda/function/code/vm/sim-lambda-host-modules.js +31 -0
  40. package/dist/service/lambda/function/code/vm/sim-lambda-vm-module-resolver.d.ts +7 -0
  41. package/dist/service/lambda/function/code/vm/sim-lambda-vm-module-resolver.js +15 -0
  42. package/dist/service/lambda/function/code/vm/sim-lambda-vm-modules.js +5 -4
  43. package/dist/service/s3/command/put-bucket-policy/put-bucket-policy.handler.js +4 -1
  44. package/dist/service/scheduler/delivery/sim-scheduler-delivery-failures.d.ts +16 -0
  45. package/dist/service/scheduler/delivery/sim-scheduler-delivery-failures.js +19 -0
  46. package/dist/service/scheduler/index.d.ts +1 -1
  47. package/dist/service/scheduler/index.js +1 -1
  48. package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-dynamic-reference-resolver.d.ts +13 -15
  49. package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-dynamic-reference-resolver.js +22 -43
  50. package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-stand-in.d.ts +9 -0
  51. package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-stand-in.js +14 -0
  52. package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-value.d.ts +12 -0
  53. package/dist/service/secretsmanager/cfn/dynamic/sim-cfn-secrets-manager-reference-value.js +29 -0
  54. package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-dynamic-reference-resolver.d.ts +22 -6
  55. package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-dynamic-reference-resolver.js +39 -24
  56. package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-secure-dynamic-reference-resolver.d.ts +10 -5
  57. package/dist/service/ssm/cfn/dynamic/sim-cfn-ssm-secure-dynamic-reference-resolver.js +10 -8
  58. package/dist/service/ssm/sim-ssm.d.ts +10 -2
  59. package/dist/service/ssm/sim-ssm.js +12 -4
  60. package/package.json +4 -3
@@ -1,9 +1,15 @@
1
1
  import { simIamPolicyDocumentStatements } from "../sim-iam-pol-document-statements.js";
2
+ import { simIamStatementLabel } from "../sim-iam-statement-label.js";
3
+ import { simIamStatementStrings } from "../sim-iam-statement-strings.js";
2
4
  /**
3
5
  * Parse raw IAM policy document shapes into a form convenient for evaluation.
4
6
  *
5
7
  * IAM policy JSON commonly allows either a single value or an array. The decision
6
8
  * engine should not need to care which source shape was used.
9
+ *
10
+ * A document stored without validation can hold a value of the wrong type in
11
+ * one of those fields. Parsing it reports a malformed policy naming the
12
+ * statement.
7
13
  */
8
14
  export class SimIamPolicyDocumentParser {
9
15
  /**
@@ -11,30 +17,22 @@ export class SimIamPolicyDocumentParser {
11
17
  */
12
18
  parse(policy) {
13
19
  return {
14
- statements: simIamPolicyDocumentStatements(policy).map((statement) => this.parseStatement(statement)),
20
+ statements: simIamPolicyDocumentStatements(policy).map((statement, index) => this.parseStatement(statement, index)),
15
21
  };
16
22
  }
17
- parseStatement(statement) {
23
+ parseStatement(statement, index) {
24
+ const label = simIamStatementLabel(index);
18
25
  return {
19
26
  source: statement,
20
27
  sid: statement.Sid,
21
28
  effect: statement.Effect,
22
- actions: this.stringList(statement.Action),
23
- notActions: this.stringList(statement.NotAction),
24
- resources: this.stringList(statement.Resource),
25
- notResources: this.stringList(statement.NotResource),
29
+ actions: simIamStatementStrings(statement.Action, label, "Action"),
30
+ notActions: simIamStatementStrings(statement.NotAction, label, "NotAction"),
31
+ resources: simIamStatementStrings(statement.Resource, label, "Resource"),
32
+ notResources: simIamStatementStrings(statement.NotResource, label, "NotResource"),
26
33
  principal: statement.Principal,
27
34
  notPrincipal: statement.NotPrincipal,
28
35
  condition: statement.Condition,
29
36
  };
30
37
  }
31
- stringList(value) {
32
- if (value === undefined) {
33
- return undefined;
34
- }
35
- if (typeof value === "string") {
36
- return [value];
37
- }
38
- return [...value];
39
- }
40
38
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * What a policy document is attached to, for naming it when one of its
3
+ * statements is rejected.
4
+ */
5
+ export interface SimIamPolicyDocumentSubject {
6
+ /**
7
+ * The kind of thing the document is attached to, such as `Role` or `Bucket`.
8
+ */
9
+ readonly attachedTo: string;
10
+ /**
11
+ * The name of the thing the document is attached to.
12
+ */
13
+ readonly name: string;
14
+ /**
15
+ * The policy's own name, for a document stored under one.
16
+ */
17
+ readonly policyName?: string | undefined;
18
+ }
19
+ /**
20
+ * Name one statement of a policy document, for a message about what is wrong
21
+ * with it.
22
+ *
23
+ * A malformed statement usually arrives from a CloudFormation template. The
24
+ * reader chasing one back needs the document it came from as well as the fault
25
+ * in it. Statements carry no names of their own. The position in the document
26
+ * stands in for one.
27
+ */
28
+ export declare function simIamStatementLabel(index: number, subject?: SimIamPolicyDocumentSubject): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Name one statement of a policy document, for a message about what is wrong
3
+ * with it.
4
+ *
5
+ * A malformed statement usually arrives from a CloudFormation template. The
6
+ * reader chasing one back needs the document it came from as well as the fault
7
+ * in it. Statements carry no names of their own. The position in the document
8
+ * stands in for one.
9
+ */
10
+ export function simIamStatementLabel(index, subject) {
11
+ const statement = `statement ${index + 1}`;
12
+ if (subject === undefined) {
13
+ return `IAM policy ${statement}`;
14
+ }
15
+ const policy = subject.policyName === undefined ? "" : ` policy "${subject.policyName}"`;
16
+ return `${subject.attachedTo} "${subject.name}"${policy} ${statement}`;
17
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Read a statement field holding either one string or a list of strings.
3
+ *
4
+ * Action, NotAction, Resource and NotResource each take either shape, and
5
+ * anything else is a malformed document. An unresolved CloudFormation intrinsic
6
+ * is the usual way one arrives. The IAM policy parsers store `PolicyDocument`
7
+ * whole, and a `Ref` or `Fn::GetAtt` nested inside a statement is kept as
8
+ * written. Refusing it here keeps it out of evaluation, where the same value
9
+ * surfaces as a `TypeError` naming no policy.
10
+ */
11
+ export declare function simIamStatementStrings(value: unknown, label: string, field: string): readonly string[] | undefined;
@@ -0,0 +1,27 @@
1
+ import { SimIamMalformedPolicyDocument } from "../error/sim-iam.error.js";
2
+ /**
3
+ * Read a statement field holding either one string or a list of strings.
4
+ *
5
+ * Action, NotAction, Resource and NotResource each take either shape, and
6
+ * anything else is a malformed document. An unresolved CloudFormation intrinsic
7
+ * is the usual way one arrives. The IAM policy parsers store `PolicyDocument`
8
+ * whole, and a `Ref` or `Fn::GetAtt` nested inside a statement is kept as
9
+ * written. Refusing it here keeps it out of evaluation, where the same value
10
+ * surfaces as a `TypeError` naming no policy.
11
+ */
12
+ export function simIamStatementStrings(value, label, field) {
13
+ if (value === undefined) {
14
+ return undefined;
15
+ }
16
+ if (typeof value === "string") {
17
+ return [value];
18
+ }
19
+ if (isStringArray(value)) {
20
+ return [...value];
21
+ }
22
+ throw new SimIamMalformedPolicyDocument(`${label}: ${field} must be a string or an array of strings, but holds ${JSON.stringify(value)}`);
23
+ }
24
+ function isStringArray(value) {
25
+ return (Array.isArray(value) &&
26
+ value.every((entry) => typeof entry === "string"));
27
+ }
@@ -1,17 +1,24 @@
1
1
  import { type JSONString } from "../../../util/type-guard/json.js";
2
2
  import type { SimIamPolicyDocument } from "../policy/sim-iam-policy.js";
3
+ import { type SimIamPolicyDocumentSubject } from "../policy/sim-iam-statement-label.js";
3
4
  /**
4
5
  * Performs basic IAM policy document validation shared by IAM policy commands.
6
+ *
7
+ * A caller can say what the document is attached to, and a rejection names it.
8
+ * Malformed documents usually arrive from a CloudFormation template, and the
9
+ * failure has to carry enough for a reader to find the template that wrote
10
+ * it.
5
11
  */
6
12
  export declare class SimIamPolicyDocumentValidator {
7
13
  /**
8
14
  * Validate a required JSON IAM policy document string.
9
15
  */
10
- validateRequired(policyDocument: string | undefined): asserts policyDocument is JSONString<SimIamPolicyDocument>;
16
+ validateRequired(policyDocument: string | undefined, subject?: SimIamPolicyDocumentSubject): asserts policyDocument is JSONString<SimIamPolicyDocument>;
11
17
  /**
12
18
  * Validate an optional JSON IAM policy document string when present.
13
19
  */
14
- validateOptional(policyDocument: string | undefined): asserts policyDocument is JSONString<SimIamPolicyDocument> | undefined;
20
+ validateOptional(policyDocument: string | undefined, subject?: SimIamPolicyDocumentSubject): asserts policyDocument is JSONString<SimIamPolicyDocument> | undefined;
15
21
  private validate;
16
22
  private validateParsed;
23
+ private validateStatement;
17
24
  }
@@ -1,45 +1,63 @@
1
1
  import { jsonParse } from "../../../util/type-guard/json.js";
2
2
  import { SimIamMalformedPolicyDocument } from "../error/sim-iam.error.js";
3
3
  import { simIamPolicyDocumentStatements } from "../policy/sim-iam-pol-document-statements.js";
4
+ import { simIamStatementLabel, } from "../policy/sim-iam-statement-label.js";
5
+ import { simIamStatementStrings } from "../policy/sim-iam-statement-strings.js";
4
6
  /**
5
7
  * Performs basic IAM policy document validation shared by IAM policy commands.
8
+ *
9
+ * A caller can say what the document is attached to, and a rejection names it.
10
+ * Malformed documents usually arrive from a CloudFormation template, and the
11
+ * failure has to carry enough for a reader to find the template that wrote
12
+ * it.
6
13
  */
7
14
  export class SimIamPolicyDocumentValidator {
8
15
  /**
9
16
  * Validate a required JSON IAM policy document string.
10
17
  */
11
- validateRequired(policyDocument) {
18
+ validateRequired(policyDocument, subject) {
12
19
  if (policyDocument === undefined || policyDocument.length === 0) {
13
20
  throw new Error("PolicyDocument is required");
14
21
  }
15
- this.validate(policyDocument);
22
+ this.validate(policyDocument, subject);
16
23
  }
17
24
  /**
18
25
  * Validate an optional JSON IAM policy document string when present.
19
26
  */
20
- validateOptional(policyDocument) {
27
+ validateOptional(policyDocument, subject) {
21
28
  if (policyDocument === undefined || policyDocument.length === 0) {
22
29
  return;
23
30
  }
24
- this.validate(policyDocument);
31
+ this.validate(policyDocument, subject);
25
32
  }
26
- validate(policyDocument) {
33
+ validate(policyDocument, subject) {
27
34
  const parsedPolicyDocument = jsonParse(policyDocument);
28
- this.validateParsed(parsedPolicyDocument);
35
+ this.validateParsed(parsedPolicyDocument, subject);
29
36
  }
30
- validateParsed(policyDocument) {
31
- for (const statement of simIamPolicyDocumentStatements(policyDocument)) {
32
- // oxlint-disable-next-line typescript/no-unnecessary-condition
33
- if (statement.Effect !== "Allow" && statement.Effect !== "Deny") {
34
- throw new SimIamMalformedPolicyDocument('IAM policy statement Effect must be either "Allow" or "Deny"');
35
- }
36
- if (statement.Action === undefined && statement.NotAction === undefined) {
37
- throw new SimIamMalformedPolicyDocument("IAM policy statement must define either Action or NotAction");
38
- }
39
- if (statement.Resource === undefined &&
40
- statement.NotResource === undefined) {
41
- throw new SimIamMalformedPolicyDocument("IAM policy statement must define either Resource or NotResource");
42
- }
37
+ validateParsed(policyDocument, subject) {
38
+ const statements = simIamPolicyDocumentStatements(policyDocument);
39
+ for (const [index, statement] of statements.entries()) {
40
+ this.validateStatement(statement, simIamStatementLabel(index, subject));
43
41
  }
44
42
  }
43
+ validateStatement(statement, label) {
44
+ // oxlint-disable-next-line typescript/no-unnecessary-condition
45
+ if (statement.Effect !== "Allow" && statement.Effect !== "Deny") {
46
+ throw new SimIamMalformedPolicyDocument(`${label}: Effect must be either "Allow" or "Deny"`);
47
+ }
48
+ if (statement.Action === undefined && statement.NotAction === undefined) {
49
+ throw new SimIamMalformedPolicyDocument(`${label}: must define either Action or NotAction`);
50
+ }
51
+ if (statement.Resource === undefined &&
52
+ statement.NotResource === undefined) {
53
+ throw new SimIamMalformedPolicyDocument(`${label}: must define either Resource or NotResource`);
54
+ }
55
+ // Read for the types alone. A field of the wrong type throws while the
56
+ // document is still beside the call that wrote it. Left to the first
57
+ // authorization, the same fault names no policy at all.
58
+ simIamStatementStrings(statement.Action, label, "Action");
59
+ simIamStatementStrings(statement.NotAction, label, "NotAction");
60
+ simIamStatementStrings(statement.Resource, label, "Resource");
61
+ simIamStatementStrings(statement.NotResource, label, "NotResource");
62
+ }
45
63
  }
@@ -2,6 +2,7 @@ import { MappedFactory } from "@kensio/part-factory";
2
2
  import type { SimAwsAccountRegionScope } from "../../../aws/sim-aws-account-region-scope.js";
3
3
  import { SimLambdaFunction } from "../sim-lambda-function.js";
4
4
  import { type LambdaCodeZipFiles } from "./make-lambda-code-zip.js";
5
+ import type { SimLambdaVmSdkModuleProvider } from "./vm/sdk/sim-lambda-vm-sdk-module-provider.js";
5
6
  /**
6
7
  * What a test asks for when it wants a function backed by zip code running in
7
8
  * a vm sandbox.
@@ -16,6 +17,12 @@ export interface SimLambdaVmZipFunctionInput {
16
17
  readonly code: string | LambdaCodeZipFiles;
17
18
  readonly handlerName: string;
18
19
  readonly memorySizeMb: number;
20
+ /**
21
+ * What provides the modules the archive does not bundle, as the real
22
+ * runtime provides the AWS SDK. Left out, the code has none, and an SDK
23
+ * require is refused for want of one.
24
+ */
25
+ readonly sdkModuleProvider?: SimLambdaVmSdkModuleProvider | undefined;
19
26
  }
20
27
  /**
21
28
  * Builds a simulated Lambda function whose code is a real zip archive run in a
@@ -39,6 +39,7 @@ export const simLambdaVmZipFunctionFactory = new MappedFactory(() => ({
39
39
  archive,
40
40
  handlerName: input.handlerName,
41
41
  environment,
42
+ sdkModuleProvider: input.sdkModuleProvider,
42
43
  }),
43
44
  });
44
45
  });
@@ -0,0 +1,19 @@
1
+ import type { SimZipArchive } from "../../../../../../util/zip/zip-archive.js";
2
+ import type { SimLambdaVmSdkModuleProvider } from "./sim-lambda-vm-sdk-module-provider.js";
3
+ /**
4
+ * What the archive holds and who provides for what it does not. Between them
5
+ * that is enough to tell an absent package from one already accounted for.
6
+ */
7
+ interface SimLambdaMissingSdkPackagesContext {
8
+ readonly archive: SimZipArchive;
9
+ readonly sdkModuleProvider: SimLambdaVmSdkModuleProvider;
10
+ }
11
+ /**
12
+ * The module the runtime provides for a specifier the archive does not
13
+ * bundle, or undefined for a specifier the provider does not serve.
14
+ *
15
+ * A package the project has not installed is refused here naming every other
16
+ * AWS SDK package the function code imports and the project is missing too.
17
+ */
18
+ export declare function provideSdkModule(specifier: string, context: SimLambdaMissingSdkPackagesContext): unknown;
19
+ export {};
@@ -0,0 +1,78 @@
1
+ import { assertDefined } from "../../../../../../util/type-guard/defined.js";
2
+ import { SimLambdaVmModuleResolver } from "../sim-lambda-vm-module-resolver.js";
3
+ import { SimLambdaSdkPackagesNotInstalledError } from "./sim-lambda-sdk-packages-not-installed.error.js";
4
+ /**
5
+ * The archive entries worth reading specifiers out of.
6
+ */
7
+ const javaScriptFilePattern = /\.[cm]?js$/;
8
+ /**
9
+ * A quoted `@aws-sdk/*` specifier, however a file asks for it. A `require`
10
+ * call, a dynamic `import`, and a static import's `from` clause all match.
11
+ */
12
+ const awsSdkImportPattern = /(?:\b(?:require|import)\s*\(|\b(?:from|import)\b)\s*["'](@aws-sdk\/[^"']+)["']/g;
13
+ /**
14
+ * The module the runtime provides for a specifier the archive does not
15
+ * bundle, or undefined for a specifier the provider does not serve.
16
+ *
17
+ * A package the project has not installed is refused here naming every other
18
+ * AWS SDK package the function code imports and the project is missing too.
19
+ */
20
+ export function provideSdkModule(specifier, context) {
21
+ try {
22
+ return context.sdkModuleProvider.provideModule(specifier);
23
+ }
24
+ catch (error) {
25
+ throw everyMissingSdkPackage(error, context);
26
+ }
27
+ }
28
+ /**
29
+ * The refusal to raise for a package the project has not installed, naming
30
+ * every other AWS SDK package the function code imports and the project is
31
+ * missing too. Any other error stands as it is.
32
+ *
33
+ * The runtime provides a package when the code requires it, so on its own a
34
+ * refusal only ever names the one the code reached. A function importing two
35
+ * absent packages would then cost two runs and two installs, and the archive
36
+ * is the only place to read what a third-party function imports. So the
37
+ * archive is read here, once a package has already turned out to be missing,
38
+ * and one install covers the set. An archive that runs is never read.
39
+ */
40
+ function everyMissingSdkPackage(error, context) {
41
+ if (!(error instanceof SimLambdaSdkPackagesNotInstalledError)) {
42
+ return error;
43
+ }
44
+ const resolver = new SimLambdaVmModuleResolver(context.archive);
45
+ const candidates = archiveAwsSdkImports(context.archive).filter((specifier) => !error.specifiers.includes(specifier) &&
46
+ !resolver.bundlesPackage(specifier));
47
+ const missing = context.sdkModuleProvider.unresolvedModules?.(candidates) ?? [];
48
+ if (missing.length === 0) {
49
+ return error;
50
+ }
51
+ return new SimLambdaSdkPackagesNotInstalledError([...error.specifiers, ...missing], { cause: error.cause });
52
+ }
53
+ /**
54
+ * The AWS SDK packages a function code archive's own files import, in the
55
+ * order they appear.
56
+ *
57
+ * Bundled dependencies are left out. What a package under `node_modules/`
58
+ * imports is that package's own business, resolved from the bundle beside it,
59
+ * and reading a vendored tree to name one more install is a poor trade.
60
+ */
61
+ function archiveAwsSdkImports(archive) {
62
+ const specifiers = new Set();
63
+ for (const filePath of archive.filePaths()) {
64
+ if (!isFunctionCodeFile(filePath)) {
65
+ continue;
66
+ }
67
+ const source = archive.file(filePath).toString();
68
+ for (const match of source.matchAll(awsSdkImportPattern)) {
69
+ const specifier = match[1];
70
+ assertDefined(specifier, "Matched import has no specifier");
71
+ specifiers.add(specifier);
72
+ }
73
+ }
74
+ return specifiers.values().toArray();
75
+ }
76
+ function isFunctionCodeFile(filePath) {
77
+ return (javaScriptFilePattern.test(filePath) && !filePath.includes("node_modules/"));
78
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * AWS SDK packages the sim Lambda runtime was asked to provide and the
3
+ * consuming project has not installed.
4
+ *
5
+ * The real Lambda runtime provides these packages from the execution
6
+ * environment rather than from the deployment package, so the simulated
7
+ * runtime provides them from the host project. The fix is the same for every
8
+ * one of them, which is why they are named together: install them, or bundle
9
+ * them into the function code archive.
10
+ */
11
+ export declare class SimLambdaSdkPackagesNotInstalledError extends Error {
12
+ readonly specifiers: readonly string[];
13
+ readonly name = "SimLambdaSdkPackagesNotInstalledError";
14
+ constructor(specifiers: readonly string[], options?: ErrorOptions);
15
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * AWS SDK packages the sim Lambda runtime was asked to provide and the
3
+ * consuming project has not installed.
4
+ *
5
+ * The real Lambda runtime provides these packages from the execution
6
+ * environment rather than from the deployment package, so the simulated
7
+ * runtime provides them from the host project. The fix is the same for every
8
+ * one of them, which is why they are named together: install them, or bundle
9
+ * them into the function code archive.
10
+ */
11
+ export class SimLambdaSdkPackagesNotInstalledError extends Error {
12
+ specifiers;
13
+ name = "SimLambdaSdkPackagesNotInstalledError";
14
+ constructor(specifiers, options) {
15
+ super(notInstalledMessage(specifiers), options);
16
+ this.specifiers = specifiers;
17
+ }
18
+ }
19
+ function notInstalledMessage(specifiers) {
20
+ const [subject, object] = specifiers.length > 1
21
+ ? ["the packages are", "them"]
22
+ : ["the package is", "it"];
23
+ return (`Cannot provide ${specifiers.join(", ")} to sim Lambda function code: ` +
24
+ `${subject} not installed. Install ${object} in your project, as the ` +
25
+ `real Lambda runtime provides ${object}, or bundle ${object} into the ` +
26
+ "function code archive.");
27
+ }
@@ -16,6 +16,15 @@ export declare const awsSdkPackagePrefix = "@aws-sdk/";
16
16
  */
17
17
  export interface SimLambdaVmSdkModuleProvider {
18
18
  provideModule(specifier: string): unknown;
19
+ /**
20
+ * Which of the given specifiers this provider serves and cannot resolve.
21
+ *
22
+ * The module system asks once a package has turned out to be missing. The
23
+ * refusal then names every package the project has to install, where on
24
+ * its own it would name only the one the function code reached first. A
25
+ * provider that does not answer is asked for one specifier at a time.
26
+ */
27
+ unresolvedModules?(specifiers: readonly string[]): readonly string[];
19
28
  }
20
29
  /**
21
30
  * Module provider used when no simulated AWS environment is wired up, such
@@ -46,6 +46,16 @@ export declare class SimSdkLambdaVmModuleProvider implements SimLambdaVmSdkModul
46
46
  * undefined for other specifiers.
47
47
  */
48
48
  provideModule(specifier: string): unknown;
49
+ /**
50
+ * Which of the given specifiers this provider serves and the host cannot
51
+ * resolve.
52
+ *
53
+ * Resolution only. A package that resolves and then throws while it
54
+ * initializes is installed, and that error belongs to the function code
55
+ * requiring it rather than to a report of what is missing.
56
+ */
57
+ unresolvedModules(specifiers: readonly string[]): readonly string[];
58
+ private resolves;
49
59
  private hostModuleExports;
50
60
  }
51
61
  export {};
@@ -1,11 +1,10 @@
1
- import { createRequire } from "node:module";
2
- import path from "node:path";
3
1
  import { SimSdkModuleClientInterceptor } from "../../../../../../sdk/module/sim-sdk-module-client-interceptor.js";
4
2
  import { SimSdkCommandDispatcher } from "../../../../../../sdk/sim-sdk-command-dispatcher.js";
5
3
  import { SimLambdaAwsApiOutbound } from "../../../outbound/sim-lambda-aws-api-outbound.js";
6
4
  import { isSimLambdaHttpModuleSpecifier, makeSimLambdaHttpModule, simLambdaHttpModuleScheme, } from "../../../outbound/sim-lambda-http-module.js";
5
+ import { isModuleNotFoundError, requireHostModule, } from "../sim-lambda-host-modules.js";
6
+ import { SimLambdaSdkPackagesNotInstalledError } from "./sim-lambda-sdk-packages-not-installed.error.js";
7
7
  import { awsSdkPackagePrefix, } from "./sim-lambda-vm-sdk-module-provider.js";
8
- const hostRequire = createRequire(import.meta.url);
9
8
  /**
10
9
  * Provides host-installed AWS SDK v3 packages to sim Lambda vm function
11
10
  * code, with client classes intercepted into the owning simulated AWS
@@ -61,6 +60,29 @@ export class SimSdkLambdaVmModuleProvider {
61
60
  this.providedModules.set(specifier, module);
62
61
  return module;
63
62
  }
63
+ /**
64
+ * Which of the given specifiers this provider serves and the host cannot
65
+ * resolve.
66
+ *
67
+ * Resolution only. A package that resolves and then throws while it
68
+ * initializes is installed, and that error belongs to the function code
69
+ * requiring it rather than to a report of what is missing.
70
+ */
71
+ unresolvedModules(specifiers) {
72
+ return specifiers.filter((specifier) => specifier.startsWith(awsSdkPackagePrefix) && !this.resolves(specifier));
73
+ }
74
+ resolves(specifier) {
75
+ if (this.providedModules.has(specifier)) {
76
+ return true;
77
+ }
78
+ try {
79
+ this.requireModule(specifier);
80
+ return true;
81
+ }
82
+ catch (error) {
83
+ return !isModuleNotFoundError(error);
84
+ }
85
+ }
64
86
  hostModuleExports(specifier) {
65
87
  let moduleExports;
66
88
  try {
@@ -72,10 +94,9 @@ export class SimSdkLambdaVmModuleProvider {
72
94
  if (!isModuleNotFoundError(error)) {
73
95
  throw error;
74
96
  }
75
- throw new Error(`Cannot provide ${specifier} to sim Lambda function code: the ` +
76
- "package is not installed. Install it in your project, as the " +
77
- "real Lambda runtime provides it, or bundle it into the function " +
78
- "code archive.", { cause: error });
97
+ throw new SimLambdaSdkPackagesNotInstalledError([specifier], {
98
+ cause: error,
99
+ });
79
100
  }
80
101
  if (typeof moduleExports !== "object" || moduleExports === null) {
81
102
  throw new Error(`Cannot provide ${specifier} to sim Lambda function code: the host ` +
@@ -84,30 +105,3 @@ export class SimSdkLambdaVmModuleProvider {
84
105
  return moduleExports;
85
106
  }
86
107
  }
87
- /**
88
- * Require an AWS SDK package from the host.
89
- *
90
- * The packages are installed by the consuming project, as they are provided
91
- * by the real Lambda runtime rather than by this package. Requiring from
92
- * this module's context covers hoisted installs; the working-directory
93
- * fallback covers stricter package layouts.
94
- */
95
- function requireHostModule(specifier) {
96
- try {
97
- return hostRequire(specifier);
98
- }
99
- catch {
100
- /* v8 ignore next 4 -- only reachable in consumer package layouts where
101
- this package's own require context cannot see the SDK packages. */
102
- return createRequire(path.join(process.cwd(), "package.json"))(specifier);
103
- }
104
- }
105
- /**
106
- * Whether an error is a Node.js module resolution failure, as opposed to an
107
- * error thrown while a resolved module initializes.
108
- */
109
- function isModuleNotFoundError(error) {
110
- return (error instanceof Error &&
111
- "code" in error &&
112
- (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_MODULE_NOT_FOUND"));
113
- }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Require from the host a module the real Lambda runtime provides without it
3
+ * being bundled: a Node.js built-in, or an AWS SDK package.
4
+ *
5
+ * The SDK packages are installed by the consuming project, as the real
6
+ * runtime provides them rather than this package. Requiring from this
7
+ * module's context covers hoisted installs; the working-directory fallback
8
+ * covers stricter package layouts.
9
+ */
10
+ export declare function requireHostModule(specifier: string): unknown;
11
+ /**
12
+ * Whether an error is a Node.js module resolution failure, as opposed to an
13
+ * error thrown while a resolved module initializes.
14
+ */
15
+ export declare function isModuleNotFoundError(error: unknown): boolean;
@@ -0,0 +1,31 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ const hostRequire = createRequire(import.meta.url);
4
+ /**
5
+ * Require from the host a module the real Lambda runtime provides without it
6
+ * being bundled: a Node.js built-in, or an AWS SDK package.
7
+ *
8
+ * The SDK packages are installed by the consuming project, as the real
9
+ * runtime provides them rather than this package. Requiring from this
10
+ * module's context covers hoisted installs; the working-directory fallback
11
+ * covers stricter package layouts.
12
+ */
13
+ export function requireHostModule(specifier) {
14
+ try {
15
+ return hostRequire(specifier);
16
+ }
17
+ catch {
18
+ /* v8 ignore next 4 -- only reachable in consumer package layouts where
19
+ this package's own require context cannot see the SDK packages. */
20
+ return createRequire(path.join(process.cwd(), "package.json"))(specifier);
21
+ }
22
+ }
23
+ /**
24
+ * Whether an error is a Node.js module resolution failure, as opposed to an
25
+ * error thrown while a resolved module initializes.
26
+ */
27
+ export function isModuleNotFoundError(error) {
28
+ return (error instanceof Error &&
29
+ "code" in error &&
30
+ (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_MODULE_NOT_FOUND"));
31
+ }
@@ -14,6 +14,13 @@ export declare class SimLambdaVmModuleResolver {
14
14
  * Resolve a require specifier to an archive file path.
15
15
  */
16
16
  resolveFilePath(specifier: string, fromDirectory: string): string;
17
+ /**
18
+ * Whether the archive bundles a bare specifier itself.
19
+ *
20
+ * The archive takes precedence over anything the runtime provides, so a
21
+ * specifier it resolves is never asked of the module provider.
22
+ */
23
+ bundlesPackage(specifier: string): boolean;
17
24
  private resolveCandidates;
18
25
  /**
19
26
  * Resolve a bare specifier against node_modules bundled in the archive.
@@ -22,6 +22,21 @@ export class SimLambdaVmModuleResolver {
22
22
  }
23
23
  return this.resolvePackage(specifier);
24
24
  }
25
+ /**
26
+ * Whether the archive bundles a bare specifier itself.
27
+ *
28
+ * The archive takes precedence over anything the runtime provides, so a
29
+ * specifier it resolves is never asked of the module provider.
30
+ */
31
+ bundlesPackage(specifier) {
32
+ try {
33
+ this.resolvePackage(specifier);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
25
40
  resolveCandidates(basePath, specifier) {
26
41
  const candidates = [
27
42
  basePath,