Skip to content

Commit 2c00276

Browse files
authored
fix(examples): rename function names in SAM template to match integration-test.js script (#312)
*Issue #, if available:* *Description of changes:* This allows us to run the examples with tests against SAM with the `integration-test.js` script directly. Also migrating the generate-sam-template script to TypeScript. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 4091f08 commit 2c00276

File tree

6 files changed

+418
-432
lines changed

6 files changed

+418
-432
lines changed

packages/aws-durable-execution-sdk-js-examples/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"copy-examples": "cp -r dist/examples/*.js dist/examples/*.js.map build/aws-durable-execution-sdk-js-examples",
3737
"copy-source": "mkdir -p build/source && cp -r src/examples/*.ts build/source/",
3838
"copy-catalog": "cp examples-catalog.json build/aws-durable-execution-sdk-js-examples/",
39-
"generate-sam-template": "node scripts/generate-sam-template.js",
39+
"generate-sam-template": "tsx scripts/generate-sam-template.ts",
4040
"test": "npm run unit-test",
4141
"test:integration": "NODE_ENV=integration jest --config jest.config.integration.js",
4242
"pretest-with-sdk-coverage": "node scripts/copy-sdk-source.js",

packages/aws-durable-execution-sdk-js-examples/rollup.config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import nodeResolve from "@rollup/plugin-node-resolve";
77
import json from "@rollup/plugin-json";
88
import commonJs from "@rollup/plugin-commonjs";
99
import path from "path";
10+
import { fileURLToPath } from "url";
1011

1112
const allExamplePaths = examplesCatalog.map((example) =>
1213
path.resolve(example.path),
@@ -43,6 +44,10 @@ export default defineConfig({
4344
typescript({
4445
// Disable incremental build to ensure examples catalog is parsed
4546
incremental: false,
47+
tsconfig: path.resolve(
48+
path.dirname(fileURLToPath(import.meta.url)),
49+
"./tsconfig.build.json",
50+
),
4651
}),
4752
nodeResolve({
4853
preferBuiltins: true,

packages/aws-durable-execution-sdk-js-examples/src/__tests__/generate-sam-template.test.ts renamed to packages/aws-durable-execution-sdk-js-examples/scripts/__tests__/generate-sam-template.test.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
1-
const {
1+
import {
22
toPascalCase,
3-
getExampleFiles,
43
createFunctionResource,
54
generateTemplate,
6-
} = require("../../scripts/generate-sam-template.js");
5+
getExamplesCatalogJson,
6+
} from "../generate-sam-template";
7+
8+
jest.mock("fs", () => ({
9+
existsSync: jest.fn(() => true),
10+
readFileSync: jest.fn(() =>
11+
JSON.stringify([
12+
{
13+
name: "hello-world",
14+
description: "A simple hello world example with no durable operations",
15+
path: "aws-durable-execution-sdk-js/packages/aws-durable-execution-sdk-js-examples/src/examples/hello-world/hello-world.ts",
16+
handler: "hello-world.handler",
17+
durableConfig: {
18+
ExecutionTimeout: 60,
19+
RetentionPeriodInDays: 7,
20+
},
21+
},
22+
{
23+
name: "steps-with-retry",
24+
description: "An example demonstrating retry functionality with steps",
25+
path: "aws-durable-execution-sdk-js/packages/aws-durable-execution-sdk-js-examples/src/examples/step/steps-with-retry/steps-with-retry.ts",
26+
handler: "steps-with-retry.handler",
27+
durableConfig: {
28+
ExecutionTimeout: 60,
29+
RetentionPeriodInDays: 7,
30+
},
31+
},
32+
]),
33+
),
34+
}));
735

836
describe("generate-sam-template", () => {
937
describe("toPascalCase", () => {
@@ -17,10 +45,13 @@ describe("generate-sam-template", () => {
1745

1846
describe("createFunctionResource", () => {
1947
test("creates default function resource", () => {
20-
const resource = createFunctionResource("hello-world");
48+
const resource = createFunctionResource(
49+
"hello-world",
50+
getExamplesCatalogJson()[0],
51+
);
2152

2253
expect(resource.Type).toBe("AWS::Serverless::Function");
23-
expect(resource.Properties.FunctionName).toBe("HelloWorld-TypeScript");
54+
expect(resource.Properties.FunctionName).toBe("hello-world");
2455
expect(resource.Properties.Handler).toBe("hello-world.handler");
2556
expect(resource.Properties.Runtime).toBe("nodejs22.x");
2657
expect(resource.Properties.MemorySize).toBe(128);
@@ -29,11 +60,12 @@ describe("generate-sam-template", () => {
2960
});
3061

3162
test("creates function resource with custom config for steps-with-retry", () => {
32-
const resource = createFunctionResource("steps-with-retry");
33-
34-
expect(resource.Properties.FunctionName).toBe(
35-
"StepsWithRetry-TypeScript",
63+
const resource = createFunctionResource(
64+
"steps-with-retry",
65+
getExamplesCatalogJson()[1],
3666
);
67+
68+
expect(resource.Properties.FunctionName).toBe("steps-with-retry");
3769
expect(resource.Properties.MemorySize).toBe(256);
3870
expect(resource.Properties.Timeout).toBe(300);
3971
expect(resource.Properties.Policies).toEqual([
@@ -46,7 +78,7 @@ describe("generate-sam-template", () => {
4678
});
4779

4880
test("includes required environment variables", () => {
49-
const resource = createFunctionResource("hello-world");
81+
const resource = createFunctionResource("hello-world", {});
5082

5183
expect(resource.Properties.Environment.Variables).toEqual({
5284
AWS_ENDPOINT_URL_LAMBDA: "http://host.docker.internal:5000",

packages/aws-durable-execution-sdk-js-examples/scripts/generate-sam-template.js renamed to packages/aws-durable-execution-sdk-js-examples/scripts/generate-sam-template.ts

Lines changed: 48 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#!/usr/bin/env node
22

3-
const fs = require("fs");
4-
const path = require("path");
5-
const yaml = require("js-yaml");
3+
import fs from "fs";
4+
import path from "path";
5+
import yaml from "js-yaml";
66

77
// Configuration for different examples that need special settings
8-
const EXAMPLE_CONFIGS = {
8+
const EXAMPLE_CONFIGS: Record<string, any> = {
99
"steps-with-retry": {
1010
memorySize: 256,
1111
timeout: 300,
@@ -17,11 +17,6 @@ const EXAMPLE_CONFIGS = {
1717
},
1818
],
1919
},
20-
"wait-for-callback-submitter-retry-success": {
21-
memorySize: 128,
22-
timeout: 120,
23-
policies: [],
24-
},
2520
};
2621

2722
// Default configuration for Lambda functions
@@ -34,96 +29,35 @@ const DEFAULT_CONFIG = {
3429
/**
3530
* Convert kebab-case filename to PascalCase resource name
3631
*/
37-
function toPascalCase(filename) {
32+
function toPascalCase(filename: string) {
3833
return filename
3934
.split("-")
40-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
35+
.map((word: string) => word.charAt(0).toUpperCase() + word.slice(1))
4136
.join("");
4237
}
4338

44-
/**
45-
* Get TypeScript files from src/examples directory
46-
*/
47-
function getExampleFiles() {
48-
const examplesDir = path.join(__dirname, "../src/examples");
49-
50-
if (!fs.existsSync(examplesDir)) {
51-
throw new Error(`Examples directory not found: ${examplesDir}`);
52-
}
53-
54-
const exampleFiles = [];
55-
56-
// Read all directories in examples
57-
const entries = fs.readdirSync(examplesDir, { withFileTypes: true });
58-
59-
for (const entry of entries) {
60-
// Skip non-directories and special directories
61-
if (!entry.isDirectory() || entry.name.startsWith(".")) {
62-
continue;
63-
}
64-
65-
const dirPath = path.join(examplesDir, entry.name);
66-
const subEntries = fs.readdirSync(dirPath, { withFileTypes: true });
67-
68-
// Check if this directory contains TypeScript files directly (standalone examples)
69-
const directTsFiles = subEntries.filter(
70-
(dirent) =>
71-
dirent.isFile() &&
72-
dirent.name.endsWith(".ts") &&
73-
!dirent.name.includes(".test"),
74-
);
75-
76-
if (directTsFiles.length > 0) {
77-
// Standalone example directory
78-
directTsFiles.forEach((file) => {
79-
exampleFiles.push(path.basename(file.name, ".ts"));
80-
});
81-
} else {
82-
// Nested structure - scan subdirectories
83-
const subDirs = subEntries.filter((dirent) => dirent.isDirectory());
84-
85-
for (const subDir of subDirs) {
86-
const subDirPath = path.join(dirPath, subDir.name);
87-
const filesInSubDir = fs.readdirSync(subDirPath);
88-
89-
// Find TypeScript files (excluding test files)
90-
const tsFiles = filesInSubDir.filter(
91-
(file) => file.endsWith(".ts") && !file.includes(".test."),
92-
);
93-
94-
// Add each example file (without .ts extension)
95-
tsFiles.forEach((file) => {
96-
exampleFiles.push(path.basename(file, ".ts"));
97-
});
98-
}
99-
}
100-
}
101-
102-
return exampleFiles.sort(); // Sort for consistent output
103-
}
104-
10539
/**
10640
* Create a Lambda function resource configuration
10741
*/
108-
function createFunctionResource(filename, skipVerboseLogging = false) {
109-
const resourceName = toPascalCase(filename);
110-
const config = EXAMPLE_CONFIGS[filename] || DEFAULT_CONFIG;
111-
112-
const functionResource = {
42+
function createFunctionResource(
43+
resourceName: string,
44+
catalog: any,
45+
skipVerboseLogging = false,
46+
) {
47+
const config = EXAMPLE_CONFIGS[resourceName] || DEFAULT_CONFIG;
48+
49+
const functionResource: Record<string, any> = {
11350
Type: "AWS::Serverless::Function",
11451
Properties: {
115-
FunctionName: `${resourceName}-TypeScript`,
52+
FunctionName: resourceName,
11653
CodeUri: "./dist",
117-
Handler: `${filename}.handler`,
54+
Handler: catalog.handler,
11855
Runtime: "nodejs22.x",
11956
Architectures: ["x86_64"],
12057
MemorySize: config.memorySize,
12158
Timeout: config.timeout,
12259
Role: { "Fn::GetAtt": ["DurableFunctionRole", "Arn"] },
123-
DurableConfig: {
124-
ExecutionTimeout: 3600,
125-
RetentionPeriodInDays: 7,
126-
},
60+
DurableConfig: catalog.durableConfig,
12761
Environment: {
12862
Variables: {
12963
AWS_ENDPOINT_URL_LAMBDA: "http://host.docker.internal:5000",
@@ -145,17 +79,34 @@ function createFunctionResource(filename, skipVerboseLogging = false) {
14579
return functionResource;
14680
}
14781

82+
function getExamplesCatalogJson() {
83+
const examplesCatalogPath = path.join(
84+
__dirname,
85+
"../src/utils/examples-catalog.json",
86+
);
87+
88+
if (!fs.existsSync(examplesCatalogPath)) {
89+
throw new Error(`Examples directory not found: ${examplesCatalogPath}`);
90+
}
91+
92+
const examplesCatalog = JSON.parse(
93+
fs.readFileSync(examplesCatalogPath, "utf8"),
94+
);
95+
96+
if (examplesCatalog.length === 0) {
97+
throw new Error("No TypeScript example files found in src/examples");
98+
}
99+
100+
return examplesCatalog;
101+
}
102+
148103
/**
149104
* Generate the complete CloudFormation template
150105
*/
151106
function generateTemplate(skipVerboseLogging = false) {
152-
const exampleFiles = getExampleFiles();
107+
const examplesCatalog = getExamplesCatalogJson();
153108

154-
if (exampleFiles.length === 0) {
155-
throw new Error("No TypeScript example files found in src/examples");
156-
}
157-
158-
const template = {
109+
const template: Record<string, any> = {
159110
AWSTemplateFormatVersion: "2010-09-09",
160111
Description: "Durable Function examples written in TypeScript.",
161112
Transform: ["AWS::Serverless-2016-10-31"],
@@ -202,12 +153,11 @@ function generateTemplate(skipVerboseLogging = false) {
202153
};
203154

204155
// Generate resources for each example file
205-
exampleFiles.forEach((filename) => {
206-
const resourceName = toPascalCase(filename);
207-
template.Resources[resourceName] = createFunctionResource(
208-
filename,
209-
skipVerboseLogging,
210-
);
156+
examplesCatalog.forEach((catalog: { name: string; handler: string }) => {
157+
const resourceName = catalog.name.replace(/\s/g, "") + `-22x-NodeJS-Local`;
158+
template.Resources[
159+
toPascalCase(catalog.handler.slice(0, -".handler".length))
160+
] = createFunctionResource(resourceName, catalog, skipVerboseLogging);
211161
});
212162

213163
return template;
@@ -252,7 +202,7 @@ function main() {
252202
if (skipVerboseLogging) {
253203
console.log("🔇 Verbose logging disabled");
254204
}
255-
} catch (error) {
205+
} catch (error: any) {
256206
console.error("❌ Error generating template.yml:", error.message);
257207
process.exit(1);
258208
}
@@ -263,9 +213,9 @@ if (require.main === module) {
263213
main();
264214
}
265215

266-
module.exports = {
216+
export {
267217
generateTemplate,
268-
getExampleFiles,
269218
toPascalCase,
270219
createFunctionResource,
220+
getExamplesCatalogJson,
271221
};

0 commit comments

Comments
 (0)