From 5509e799e7c3c361c8fe79cd9335db7e2e0c2418 Mon Sep 17 00:00:00 2001 From: Haresh Nasit Date: Mon, 21 Oct 2024 15:34:01 -0700 Subject: [PATCH 01/28] feat: Add nodejs22.x support --- .github/workflows/build_test_invoke.yml | 6 + manifest-v2.json | 116 ++++++ nodejs22.x/cw-event/.gitignore | 229 ++++++++++ nodejs22.x/cw-event/README.md | 20 + nodejs22.x/cw-event/cookiecutter.json | 10 + nodejs22.x/cw-event/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 1 + .../{{cookiecutter.project_name}}/README.md | 140 +++++++ .../handlers/scheduled-event-logger.test.mjs | 31 ++ .../buildspec.yml | 23 ++ .../events/event-cloudwatch-event.json | 12 + .../package.json | 24 ++ .../src/handlers/scheduled-event-logger.mjs | 8 + .../template.yaml | 41 ++ nodejs22.x/full-stack/.gitignore | 229 ++++++++++ nodejs22.x/full-stack/README.md | 20 + nodejs22.x/full-stack/cookiecutter.json | 11 + nodejs22.x/full-stack/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 390 ++++++++++++++++++ .../12 - Full Stack.png | Bin 0 -> 49390 bytes .../3-Serverless-API.png | Bin 0 -> 30785 bytes .../{{cookiecutter.project_name}}/README.md | 246 +++++++++++ .../unit/handlers/get-all-items.test.mjs | 43 ++ .../unit/handlers/get-by-id.test.mjs | 48 +++ .../__tests__/unit/handlers/put-item.test.mjs | 45 ++ .../backend/package.json | 30 ++ .../backend/src/handlers/get-all-items.mjs | 66 +++ .../backend/src/handlers/get-by-id.mjs | 69 ++++ .../backend/src/handlers/put-item.mjs | 71 ++++ .../deploy_frontend.ps1 | 49 +++ .../deploy_frontend.sh | 49 +++ .../{{cookiecutter.project_name}}/env.json | 14 + .../events/event-get-all-items.json | 3 + .../events/event-get-by-id.json | 6 + .../events/event-post-item.json | 4 + .../frontend/.gitignore | 24 ++ .../frontend/LICENSE | 14 + .../frontend/README.md | 36 ++ .../frontend/babel.config.js | 5 + .../frontend/package.json | 55 +++ .../frontend/public/favicon.ico | Bin 0 -> 15406 bytes .../frontend/public/index.html | 17 + .../frontend/public/logo.png | Bin 0 -> 110984 bytes .../frontend/src/App.vue | 71 ++++ .../frontend/src/components/CreateItem.vue | 54 +++ .../frontend/src/components/GetItemById.vue | 54 +++ .../frontend/src/components/GetItems.vue | 42 ++ .../frontend/src/main.js | 4 + .../frontend/test/CreateItem.test.js | 36 ++ .../frontend/test/GetItemById.test.js | 28 ++ .../frontend/test/GetItems.test.js | 20 + .../frontend/vite.config.js | 15 + .../frontend/vue.config.js | 3 + .../template.yaml | 244 +++++++++++ nodejs22.x/hello-gql/.gitignore | 208 ++++++++++ nodejs22.x/hello-gql/README.md | 69 ++++ nodejs22.x/hello-gql/cookiecutter.json | 10 + nodejs22.x/hello-gql/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 208 ++++++++++ .../gql/createPostItem.js | 14 + .../gql/getPostFromTable.js | 19 + .../gql/greet.js | 11 + .../gql/preprocessPostItem.js | 14 + .../gql/schema.graphql | 24 ++ .../greeter/.npmignore | 1 + .../greeter/app.mjs | 15 + .../greeter/package.json | 16 + .../greeter/tests/events/appsync.json | 69 ++++ .../greeter/tests/unit/test-handler.mjs | 17 + .../template.yaml | 105 +++++ nodejs22.x/hello-img/.gitignore | 229 ++++++++++ nodejs22.x/hello-img/README.md | 20 + nodejs22.x/hello-img/cookiecutter.json | 10 + nodejs22.x/hello-img/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 207 ++++++++++ .../{{cookiecutter.project_name}}/README.md | 116 ++++++ .../events/event.json | 62 +++ .../hello-world/.npmignore | 1 + .../hello-world/Dockerfile | 10 + .../hello-world/app.mjs | 23 ++ .../hello-world/package.json | 19 + .../hello-world/tests/unit/test-handler.mjs | 20 + .../template.yaml | 47 +++ nodejs22.x/hello-ts-pt/.gitignore | 229 ++++++++++ nodejs22.x/hello-ts-pt/README.md | 37 ++ nodejs22.x/hello-ts-pt/cookiecutter.json | 14 + nodejs22.x/hello-ts-pt/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 207 ++++++++++ .../{{cookiecutter.project_name}}/README.md | 182 ++++++++ .../events/event.json | 62 +++ .../hello-world/.eslintignore | 2 + .../hello-world/.eslintrc.js | 15 + .../hello-world/.npmignore | 1 + .../hello-world/.prettierrc.js | 7 + .../hello-world/app.ts | 135 ++++++ .../hello-world/jest.config.ts | 15 + .../hello-world/package.json | 37 ++ .../tests/unit/test-handler.test.ts | 87 ++++ .../hello-world/tsconfig.json | 15 + .../template.yaml | 70 ++++ nodejs22.x/hello-ts/.gitignore | 229 ++++++++++ nodejs22.x/hello-ts/README.md | 20 + nodejs22.x/hello-ts/cookiecutter.json | 10 + nodejs22.x/hello-ts/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 207 ++++++++++ .../{{cookiecutter.project_name}}/README.md | 127 ++++++ .../events/event.json | 62 +++ .../hello-world/.eslintignore | 2 + .../hello-world/.eslintrc.js | 15 + .../hello-world/.npmignore | 1 + .../hello-world/.prettierrc.js | 7 + .../hello-world/app.ts | 30 ++ .../hello-world/jest.config.ts | 15 + .../hello-world/package.json | 34 ++ .../tests/unit/test-handler.test.ts | 65 +++ .../hello-world/tsconfig.json | 15 + .../template.yaml | 53 +++ nodejs22.x/hello/.gitignore | 229 ++++++++++ nodejs22.x/hello/README.md | 20 + nodejs22.x/hello/cookiecutter.json | 10 + nodejs22.x/hello/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 207 ++++++++++ .../{{cookiecutter.project_name}}/README.md | 127 ++++++ .../events/event.json | 62 +++ .../hello-world/.npmignore | 1 + .../hello-world/app.mjs | 24 ++ .../hello-world/package.json | 19 + .../hello-world/tests/unit/test-handler.mjs | 20 + .../template.yaml | 45 ++ nodejs22.x/response-streaming/.gitignore | 208 ++++++++++ nodejs22.x/response-streaming/README.md | 20 + .../response-streaming/cookiecutter.json | 10 + nodejs22.x/response-streaming/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 208 ++++++++++ .../{{cookiecutter.project_name}}/README.md | 102 +++++ .../__tests__/unit/index.test.js | 41 ++ .../package.json | 24 ++ .../src/index.js | 33 ++ .../template.yaml | 33 ++ nodejs22.x/s3/.gitignore | 229 ++++++++++ nodejs22.x/s3/README.md | 20 + nodejs22.x/s3/cookiecutter.json | 10 + nodejs22.x/s3/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 2 + .../{{cookiecutter.project_name}}/README.md | 151 +++++++ .../handlers/s3-json-logger-handler.test.mjs | 52 +++ .../buildspec.yml | 22 + .../events/event-s3.json | 38 ++ .../package.json | 29 ++ .../src/handlers/s3-json-logger.mjs | 39 ++ .../template.yaml | 47 +++ nodejs22.x/scratch/.gitignore | 229 ++++++++++ nodejs22.x/scratch/README.md | 20 + nodejs22.x/scratch/cookiecutter.json | 10 + nodejs22.x/scratch/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 1 + .../{{cookiecutter.project_name}}/README.md | 140 +++++++ .../unit/handlers/hello-from-lambda.test.mjs | 20 + .../buildspec.yml | 25 ++ .../package.json | 24 ++ .../src/handlers/hello-from-lambda.mjs | 12 + .../template.yaml | 38 ++ nodejs22.x/sns/.gitignore | 229 ++++++++++ nodejs22.x/sns/README.md | 20 + nodejs22.x/sns/cookiecutter.json | 10 + nodejs22.x/sns/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 1 + .../{{cookiecutter.project_name}}/README.md | 146 +++++++ .../unit/handlers/sns-payload-logger.test.mjs | 24 ++ .../buildspec.yml | 25 ++ .../events/event-sns.json | 5 + .../package.json | 24 ++ .../src/handlers/sns-payload-logger.mjs | 8 + .../template.yaml | 48 +++ nodejs22.x/sqs/.gitignore | 229 ++++++++++ nodejs22.x/sqs/README.md | 20 + nodejs22.x/sqs/cookiecutter.json | 10 + nodejs22.x/sqs/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 1 + .../{{cookiecutter.project_name}}/README.md | 141 +++++++ .../unit/handlers/sqs-payload-logger.test.mjs | 30 ++ .../buildspec.yml | 23 ++ .../events/event-sqs.json | 11 + .../package.json | 24 ++ .../src/handlers/sqs-payload-logger.mjs | 8 + .../template.yaml | 48 +++ nodejs22.x/step-func/.gitignore | 229 ++++++++++ nodejs22.x/step-func/README.md | 23 ++ nodejs22.x/step-func/cookiecutter.json | 10 + nodejs22.x/step-func/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 207 ++++++++++ .../{{cookiecutter.project_name}}/README.md | 137 ++++++ .../functions/stock-buyer/.npmignore | 1 + .../functions/stock-buyer/app.mjs | 30 ++ .../functions/stock-buyer/package.json | 17 + .../stock-buyer/tests/unit/test-handler.mjs | 21 + .../functions/stock-checker/.npmignore | 1 + .../functions/stock-checker/app.mjs | 19 + .../functions/stock-checker/package.json | 17 + .../stock-checker/tests/unit/test-handler.mjs | 16 + .../functions/stock-seller/.npmignore | 1 + .../functions/stock-seller/app.mjs | 30 ++ .../functions/stock-seller/package.json | 17 + .../stock-seller/tests/unit/test-handler.mjs | 21 + .../{{cookiecutter.project_name}}/makefile | 71 ++++ .../statemachine/stock_trader.asl.json | 97 +++++ .../statemachine/test/MockConfigFile.json | 142 +++++++ .../template.yaml | 94 +++++ nodejs22.x/web/.gitignore | 229 ++++++++++ nodejs22.x/web/README.md | 20 + nodejs22.x/web/cookiecutter.json | 10 + nodejs22.x/web/setup.cfg | 2 + .../{{cookiecutter.project_name}}/.gitignore | 1 + .../{{cookiecutter.project_name}}/README.md | 160 +++++++ .../unit/handlers/get-all-items.test.mjs | 38 ++ .../unit/handlers/get-by-id.test.mjs | 43 ++ .../__tests__/unit/handlers/put-item.test.mjs | 40 ++ .../buildspec.yml | 22 + .../{{cookiecutter.project_name}}/env.json | 11 + .../events/event-get-all-items.json | 3 + .../events/event-get-by-id.json | 6 + .../events/event-post-item.json | 4 + .../package.json | 30 ++ .../src/handlers/get-all-items.mjs | 44 ++ .../src/handlers/get-by-id.mjs | 47 +++ .../src/handlers/put-item.mjs | 49 +++ .../template.yaml | 131 ++++++ .../build_invoke/node/test_node_22.py | 80 ++++ .../unit_test/test_unit_test_nodejs22_x.py | 75 ++++ 229 files changed, 12305 insertions(+) create mode 100644 nodejs22.x/cw-event/.gitignore create mode 100644 nodejs22.x/cw-event/README.md create mode 100644 nodejs22.x/cw-event/cookiecutter.json create mode 100644 nodejs22.x/cw-event/setup.cfg create mode 100755 nodejs22.x/cw-event/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/cw-event/{{cookiecutter.project_name}}/__tests__/unit/handlers/scheduled-event-logger.test.mjs create mode 100755 nodejs22.x/cw-event/{{cookiecutter.project_name}}/buildspec.yml create mode 100644 nodejs22.x/cw-event/{{cookiecutter.project_name}}/events/event-cloudwatch-event.json create mode 100755 nodejs22.x/cw-event/{{cookiecutter.project_name}}/package.json create mode 100755 nodejs22.x/cw-event/{{cookiecutter.project_name}}/src/handlers/scheduled-event-logger.mjs create mode 100755 nodejs22.x/cw-event/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/full-stack/.gitignore create mode 100644 nodejs22.x/full-stack/README.md create mode 100644 nodejs22.x/full-stack/cookiecutter.json create mode 100644 nodejs22.x/full-stack/setup.cfg create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/12 - Full Stack.png create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/3-Serverless-API.png create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/get-all-items.test.mjs create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/get-by-id.test.mjs create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/put-item.test.mjs create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/package.json create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/get-all-items.mjs create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/get-by-id.mjs create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/put-item.mjs create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/deploy_frontend.ps1 create mode 100755 nodejs22.x/full-stack/{{cookiecutter.project_name}}/deploy_frontend.sh create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/env.json create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-get-all-items.json create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-get-by-id.json create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-post-item.json create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/.gitignore create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/LICENSE create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/README.md create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/babel.config.js create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/favicon.ico create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/index.html create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/logo.png create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/App.vue create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/CreateItem.vue create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/GetItemById.vue create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/GetItems.vue create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/main.js create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/CreateItem.test.js create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/GetItemById.test.js create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/GetItems.test.js create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/vite.config.js create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/vue.config.js create mode 100644 nodejs22.x/full-stack/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/hello-gql/.gitignore create mode 100644 nodejs22.x/hello-gql/README.md create mode 100644 nodejs22.x/hello-gql/cookiecutter.json create mode 100644 nodejs22.x/hello-gql/setup.cfg create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/createPostItem.js create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/getPostFromTable.js create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/greet.js create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/preprocessPostItem.js create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/schema.graphql create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/.npmignore create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/app.mjs create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/package.json create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/events/appsync.json create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/unit/test-handler.mjs create mode 100644 nodejs22.x/hello-gql/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/hello-img/.gitignore create mode 100644 nodejs22.x/hello-img/README.md create mode 100644 nodejs22.x/hello-img/cookiecutter.json create mode 100644 nodejs22.x/hello-img/setup.cfg create mode 100644 nodejs22.x/hello-img/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/hello-img/{{cookiecutter.project_name}}/events/event.json create mode 100644 nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/.npmignore create mode 100644 nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/Dockerfile create mode 100644 nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/app.mjs create mode 100644 nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/package.json create mode 100644 nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.mjs create mode 100644 nodejs22.x/hello-img/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/hello-ts-pt/.gitignore create mode 100644 nodejs22.x/hello-ts-pt/README.md create mode 100644 nodejs22.x/hello-ts-pt/cookiecutter.json create mode 100644 nodejs22.x/hello-ts-pt/setup.cfg create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/events/event.json create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.eslintignore create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.eslintrc.js create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.npmignore create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.prettierrc.js create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/app.ts create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/jest.config.ts create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/package.json create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.test.ts create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/tsconfig.json create mode 100644 nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/hello-ts/.gitignore create mode 100644 nodejs22.x/hello-ts/README.md create mode 100644 nodejs22.x/hello-ts/cookiecutter.json create mode 100644 nodejs22.x/hello-ts/setup.cfg create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/events/event.json create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.eslintignore create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.eslintrc.js create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.npmignore create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.prettierrc.js create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/app.ts create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/jest.config.ts create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/package.json create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.test.ts create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/tsconfig.json create mode 100644 nodejs22.x/hello-ts/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/hello/.gitignore create mode 100644 nodejs22.x/hello/README.md create mode 100644 nodejs22.x/hello/cookiecutter.json create mode 100644 nodejs22.x/hello/setup.cfg create mode 100644 nodejs22.x/hello/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/hello/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/hello/{{cookiecutter.project_name}}/events/event.json create mode 100644 nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/.npmignore create mode 100644 nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/app.mjs create mode 100644 nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/package.json create mode 100644 nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.mjs create mode 100644 nodejs22.x/hello/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/response-streaming/.gitignore create mode 100644 nodejs22.x/response-streaming/README.md create mode 100644 nodejs22.x/response-streaming/cookiecutter.json create mode 100644 nodejs22.x/response-streaming/setup.cfg create mode 100644 nodejs22.x/response-streaming/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/response-streaming/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/response-streaming/{{cookiecutter.project_name}}/__tests__/unit/index.test.js create mode 100644 nodejs22.x/response-streaming/{{cookiecutter.project_name}}/package.json create mode 100644 nodejs22.x/response-streaming/{{cookiecutter.project_name}}/src/index.js create mode 100644 nodejs22.x/response-streaming/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/s3/.gitignore create mode 100644 nodejs22.x/s3/README.md create mode 100644 nodejs22.x/s3/cookiecutter.json create mode 100644 nodejs22.x/s3/setup.cfg create mode 100644 nodejs22.x/s3/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/s3/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/s3/{{cookiecutter.project_name}}/__tests__/unit/handlers/s3-json-logger-handler.test.mjs create mode 100644 nodejs22.x/s3/{{cookiecutter.project_name}}/buildspec.yml create mode 100644 nodejs22.x/s3/{{cookiecutter.project_name}}/events/event-s3.json create mode 100644 nodejs22.x/s3/{{cookiecutter.project_name}}/package.json create mode 100644 nodejs22.x/s3/{{cookiecutter.project_name}}/src/handlers/s3-json-logger.mjs create mode 100644 nodejs22.x/s3/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/scratch/.gitignore create mode 100644 nodejs22.x/scratch/README.md create mode 100644 nodejs22.x/scratch/cookiecutter.json create mode 100644 nodejs22.x/scratch/setup.cfg create mode 100755 nodejs22.x/scratch/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/scratch/{{cookiecutter.project_name}}/__tests__/unit/handlers/hello-from-lambda.test.mjs create mode 100755 nodejs22.x/scratch/{{cookiecutter.project_name}}/buildspec.yml create mode 100755 nodejs22.x/scratch/{{cookiecutter.project_name}}/package.json create mode 100755 nodejs22.x/scratch/{{cookiecutter.project_name}}/src/handlers/hello-from-lambda.mjs create mode 100755 nodejs22.x/scratch/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/sns/.gitignore create mode 100644 nodejs22.x/sns/README.md create mode 100644 nodejs22.x/sns/cookiecutter.json create mode 100644 nodejs22.x/sns/setup.cfg create mode 100755 nodejs22.x/sns/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/sns/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/sns/{{cookiecutter.project_name}}/__tests__/unit/handlers/sns-payload-logger.test.mjs create mode 100755 nodejs22.x/sns/{{cookiecutter.project_name}}/buildspec.yml create mode 100644 nodejs22.x/sns/{{cookiecutter.project_name}}/events/event-sns.json create mode 100755 nodejs22.x/sns/{{cookiecutter.project_name}}/package.json create mode 100755 nodejs22.x/sns/{{cookiecutter.project_name}}/src/handlers/sns-payload-logger.mjs create mode 100755 nodejs22.x/sns/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/sqs/.gitignore create mode 100644 nodejs22.x/sqs/README.md create mode 100644 nodejs22.x/sqs/cookiecutter.json create mode 100644 nodejs22.x/sqs/setup.cfg create mode 100755 nodejs22.x/sqs/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/sqs/{{cookiecutter.project_name}}/__tests__/unit/handlers/sqs-payload-logger.test.mjs create mode 100755 nodejs22.x/sqs/{{cookiecutter.project_name}}/buildspec.yml create mode 100644 nodejs22.x/sqs/{{cookiecutter.project_name}}/events/event-sqs.json create mode 100755 nodejs22.x/sqs/{{cookiecutter.project_name}}/package.json create mode 100755 nodejs22.x/sqs/{{cookiecutter.project_name}}/src/handlers/sqs-payload-logger.mjs create mode 100755 nodejs22.x/sqs/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/step-func/.gitignore create mode 100644 nodejs22.x/step-func/README.md create mode 100644 nodejs22.x/step-func/cookiecutter.json create mode 100644 nodejs22.x/step-func/setup.cfg create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/.npmignore create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/app.mjs create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/package.json create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/tests/unit/test-handler.mjs create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/.npmignore create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/app.mjs create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/package.json create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/tests/unit/test-handler.mjs create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/.npmignore create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/app.mjs create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/package.json create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/tests/unit/test-handler.mjs create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/makefile create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/statemachine/test/MockConfigFile.json create mode 100644 nodejs22.x/step-func/{{cookiecutter.project_name}}/template.yaml create mode 100644 nodejs22.x/web/.gitignore create mode 100644 nodejs22.x/web/README.md create mode 100644 nodejs22.x/web/cookiecutter.json create mode 100644 nodejs22.x/web/setup.cfg create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/.gitignore create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/README.md create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/get-all-items.test.mjs create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/get-by-id.test.mjs create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/put-item.test.mjs create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/buildspec.yml create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/env.json create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/events/event-get-all-items.json create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/events/event-get-by-id.json create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/events/event-post-item.json create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/package.json create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/get-all-items.mjs create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/get-by-id.mjs create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/put-item.mjs create mode 100644 nodejs22.x/web/{{cookiecutter.project_name}}/template.yaml create mode 100644 tests/integration/build_invoke/node/test_node_22.py create mode 100644 tests/integration/unit_test/test_unit_test_nodejs22_x.py diff --git a/.github/workflows/build_test_invoke.yml b/.github/workflows/build_test_invoke.yml index 61a94601f..9f33b2546 100644 --- a/.github/workflows/build_test_invoke.yml +++ b/.github/workflows/build_test_invoke.yml @@ -202,6 +202,12 @@ jobs: - version: '20' type: 'Invoke' file: 'tests/integration/build_invoke/node/test_node_20.py' + - version: '22' + type: 'Test' + file: 'tests/integration/unit_test/test_unit_test_nodejs22_x.py' + - version: '22' + type: 'Invoke' + file: 'tests/integration/build_invoke/node/test_node_22.py' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/manifest-v2.json b/manifest-v2.json index b5396efe3..3d00e1a0f 100644 --- a/manifest-v2.json +++ b/manifest-v2.json @@ -904,6 +904,122 @@ "useCaseName": "Hello World Example" } ], + "nodejs22.x": [ + { + "directory": "nodejs22.x/hello", + "displayName": "Hello World Example", + "dependencyManager": "npm", + "appTemplate": "hello-world", + "packageType": "Zip", + "useCaseName": "Hello World Example" + }, + { + "directory": "nodejs22.x/hello-gql", + "displayName": "GraphQLApi Hello World Example", + "dependencyManager": "npm", + "appTemplate": "graphql-api-sample-app", + "packageType": "Zip", + "useCaseName": "GraphQLApi Hello World Example" + }, + { + "directory": "nodejs22.x/hello-ts", + "displayName": "Hello World Example TypeScript", + "dependencyManager": "npm", + "appTemplate": "hello-world-typescript", + "packageType": "Zip", + "useCaseName": "Hello World Example" + }, + { + "directory": "nodejs22.x/hello-ts-pt", + "displayName": "Hello World Example TypeScript With Powertools for AWS Lambda", + "dependencyManager": "npm", + "appTemplate": "hello-world-powertools-typescript", + "packageType": "Zip", + "useCaseName": "Hello World Example with Powertools for AWS Lambda" + }, + { + "directory": "nodejs22.x/step-func", + "displayName": "Step Functions Sample App (Stock Trader)", + "dependencyManager": "npm", + "appTemplate": "step-functions-sample-app", + "packageType": "Zip", + "useCaseName": "Multi-step workflow" + }, + { + "directory": "nodejs22.x/scratch", + "displayName": "Quick Start: From Scratch", + "dependencyManager": "npm", + "appTemplate": "quick-start-from-scratch", + "packageType": "Zip", + "useCaseName": "Standalone function" + }, + { + "directory": "nodejs22.x/cw-event", + "displayName": "Quick Start: Scheduled Events", + "dependencyManager": "npm", + "appTemplate": "quick-start-cloudwatch-events", + "packageType": "Zip", + "useCaseName": "Scheduled task" + }, + { + "directory": "nodejs22.x/s3", + "displayName": "Quick Start: S3", + "dependencyManager": "npm", + "appTemplate": "quick-start-s3", + "packageType": "Zip", + "useCaseName": "Data processing" + }, + { + "directory": "nodejs22.x/sns", + "displayName": "Quick Start: SNS", + "dependencyManager": "npm", + "appTemplate": "quick-start-sns", + "packageType": "Zip", + "useCaseName": "Data processing" + }, + { + "directory": "nodejs22.x/sqs", + "displayName": "Quick Start: SQS", + "dependencyManager": "npm", + "appTemplate": "quick-start-sqs", + "packageType": "Zip", + "useCaseName": "Data processing" + }, + { + "directory": "nodejs22.x/web", + "displayName": "Quick Start: Web Backend", + "dependencyManager": "npm", + "appTemplate": "quick-start-web", + "packageType": "Zip", + "useCaseName": "Serverless API" + }, + { + "directory": "nodejs22.x/full-stack", + "displayName": "Quick Start: Full Stack Application", + "dependencyManager": "npm", + "appTemplate": "quick-start-full-stack", + "packageType": "Zip", + "useCaseName": "Full Stack" + }, + { + "directory": "nodejs22.x/response-streaming", + "displayName": "Lambda function using Response Streaming", + "dependencyManager": "npm", + "appTemplate": "response-streaming", + "packageType": "Zip", + "useCaseName": "Lambda Response Streaming" + } + ], + "amazon/nodejs22.x-base": [ + { + "directory": "nodejs22.x/hello-img", + "displayName": "Hello World Image Example", + "dependencyManager": "npm", + "appTemplate": "hello-world-lambda-image", + "packageType": "Image", + "useCaseName": "Hello World Example" + } + ], "python3.8": [ { "directory": "python3.8/hello", diff --git a/nodejs22.x/cw-event/.gitignore b/nodejs22.x/cw-event/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/cw-event/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/cw-event/README.md b/nodejs22.x/cw-event/README.md new file mode 100644 index 000000000..024e2272e --- /dev/null +++ b/nodejs22.x/cw-event/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS CloudWatch Events Quick Start Application + +A cookiecutter template to create a NodeJS CloudWatch Events Quick Start Application using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-cloudwatch-events --name cwe-app` + +> **NOTE**: ``--name`` allows you to specify a different project folder name + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/cw-event/cookiecutter.json b/nodejs22.x/cw-event/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/cw-event/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/cw-event/setup.cfg b/nodejs22.x/cw-event/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/cw-event/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/.gitignore new file mode 100755 index 000000000..b512c09d4 --- /dev/null +++ b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..0cf5f61f2 --- /dev/null +++ b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,140 @@ +# {{cookiecutter.project_name}} + +This project contains source code and supporting files for a serverless application that you can deploy with the AWS Serverless Application Model (AWS SAM) command line interface (CLI). It includes the following files and folders: + +- `src` - Code for the application's Lambda function. +- `events` - Invocation events that you can use to invoke the function. +- `__tests__` - Unit tests for the application code. +- `template.yaml` - A template that defines the application's AWS resources. + +Resources for this project are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Use the AWS SAM CLI to build and test locally + +Build your application by using the `sam build` command. + +```bash +my-application$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +my-application$ sam local invoke ScheduledEventLogger --event events/event-cloudwatch-event.json +``` + +## Add a resource to your application + +The application template uses AWS SAM to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources, such as functions, triggers, and APIs. For resources that aren't included in the [AWS SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use the standard [AWS CloudFormation resource types](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html). + +Update `template.yaml` to add a dead-letter queue to your application. In the **Resources** section, add a resource named **MyQueue** with the type **AWS::SQS::Queue**. Then add a property to the **AWS::Serverless::Function** resource named **DeadLetterQueue** that targets the queue's Amazon Resource Name (ARN), and a policy that grants the function permission to access the queue. + +``` +Resources: + MyQueue: + Type: AWS::SQS::Queue + ScheduledEventLogger: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/scheduled-event-logger.scheduledEventLogger + Runtime: nodejs22.x + DeadLetterQueue: + Type: SQS + TargetArn: !GetAtt MyQueue.Arn + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt MyQueue.QueueName +``` + +The dead-letter queue is a location for Lambda to send events that could not be processed. It's only used if you invoke your function asynchronously, but it's useful here to show how you can modify your application's resources and function configuration. + +Deploy the updated application. + +```bash +my-application$ sam deploy +``` + +Open the [**Applications**](https://console.aws.amazon.com/lambda/home#/applications) page of the Lambda console, and choose your application. When the deployment completes, view the application resources on the **Overview** tab to see the new resource. Then, choose the function to see the updated configuration that specifies the dead-letter queue. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, the AWS SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs that are generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +**NOTE:** This command works for all Lambda functions, not just the ones you deploy using AWS SAM. + +```bash +my-application$ sam logs -n ScheduledEventLogger --stack-name sam-app --tail +``` + +**NOTE:** This uses the logical name of the function within the stack. This is the correct name to use when searching logs inside an AWS Lambda function within a CloudFormation stack, even if the deployed function name varies due to CloudFormation's unique resource name generation. + +You can find more information and examples about filtering Lambda function logs in the [AWS SAM CLI documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `__tests__` folder in this project. Use `npm` to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +my-application$ npm install +my-application$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +For an introduction to the AWS SAM specification, the AWS SAM CLI, and serverless application concepts, see the [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). + +Next, you can use the AWS Serverless Application Repository to deploy ready-to-use apps that go beyond Hello World samples and learn how authors developed their applications. For more information, see the [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) and the [AWS Serverless Application Repository Developer Guide](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). diff --git a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/__tests__/unit/handlers/scheduled-event-logger.test.mjs b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/__tests__/unit/handlers/scheduled-event-logger.test.mjs new file mode 100644 index 000000000..a51fba2c1 --- /dev/null +++ b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/__tests__/unit/handlers/scheduled-event-logger.test.mjs @@ -0,0 +1,31 @@ +// Import scheduledEventLoggerHandler function from scheduled-event-logger.mjs +import { scheduledEventLoggerHandler } from '../../../src/handlers/scheduled-event-logger.mjs'; +import { jest } from '@jest/globals'; + +describe('Test for sqs-payload-logger', function () { + // This test invokes the scheduled-event-logger Lambda function and verifies that the received payload is logged + it('Verifies the payload is logged', async () => { + // Mock console.log statements so we can verify them. For more information, see + // https://jestjs.io/docs/en/mock-functions.html + console.info = jest.fn() + + // Create a sample payload with CloudWatch scheduled event message format + var payload = { + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "arn:aws:events:us-west-2:123456789012:rule/ExampleRule" + ], + "detail": {} + } + + await scheduledEventLoggerHandler(payload, null) + + // Verify that console.info has been called with the expected payload + expect(console.info).toHaveBeenCalledWith(JSON.stringify(payload)) + }); +}); diff --git a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/buildspec.yml b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/buildspec.yml new file mode 100755 index 000000000..adb0a1019 --- /dev/null +++ b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/buildspec.yml @@ -0,0 +1,23 @@ +version: 0.2 + +phases: + install: + commands: + # Install all dependencies (including dependencies for running tests) + - npm install + pre_build: + commands: + # Discover and run unit tests in the '__tests__' directory + - npm run test + # Remove all unit tests to reduce the size of the package that will be ultimately uploaded to Lambda + - rm -rf ./__tests__ + # Remove all dependencies not needed for the Lambda deployment package (the packages from devDependencies in package.json) + - npm prune --production + build: + commands: + # Use AWS SAM to package the application by using AWS CloudFormation + - aws cloudformation package --template template.yaml --s3-bucket $S3_BUCKET --output-template template-export.yml +artifacts: + type: zip + files: + - template-export.yml diff --git a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/events/event-cloudwatch-event.json b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/events/event-cloudwatch-event.json new file mode 100644 index 000000000..6c3cb7e3c --- /dev/null +++ b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/events/event-cloudwatch-event.json @@ -0,0 +1,12 @@ +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "arn:aws:events:us-west-2:123456789012:rule/ExampleRule" + ], + "detail": {} +} diff --git a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/package.json b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/package.json new file mode 100755 index 000000000..64d1cf5eb --- /dev/null +++ b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/package.json @@ -0,0 +1,24 @@ +{ + "name": "replaced-by-user-input", + "description": "replaced-by-user-input", + "version": "0.0.1", + "private": true, + "devDependencies": { + "jest": "^29.2.1" + }, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" + }, + "jest": { + "testMatch": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)", + "**/__tests__/**/*.mjs?(x)", + "**/?(*.)+(spec|test).mjs?(x)" + ], + "moduleFileExtensions": [ + "mjs", + "js" + ] + } +} diff --git a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/src/handlers/scheduled-event-logger.mjs b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/src/handlers/scheduled-event-logger.mjs new file mode 100755 index 000000000..af6443ecc --- /dev/null +++ b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/src/handlers/scheduled-event-logger.mjs @@ -0,0 +1,8 @@ +/** + * A Lambda function that logs the payload received from a CloudWatch scheduled event. + */ +export const scheduledEventLoggerHandler = async (event, context) => { + // All log statements are written to CloudWatch by default. For more information, see + // https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-logging.html + console.info(JSON.stringify(event)); +} diff --git a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/template.yaml new file mode 100755 index 000000000..612d6305b --- /dev/null +++ b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,41 @@ +# This is the SAM template that represents the architecture of your serverless application +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html + +# The AWSTemplateFormatVersion identifies the capabilities of the template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + {{cookiecutter.project_name}} + +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: +- AWS::Serverless-2016-10-31 + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + # This is the Lambda function definition associated with the source code: sqs-payload-logger.js. For all available properties, see + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + ScheduledEventLogger: + Type: AWS::Serverless::Function + Properties: + Description: A Lambda function that logs the payload of messages sent to an associated SQS queue. + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Handler: src/handlers/scheduled-event-logger.scheduledEventLoggerHandler + # This property associates this Lambda function with a scheduled CloudWatch Event. For all available properties, see + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule + # This example runs every hour. + Events: + CloudWatchEvent: + Type: Schedule + Properties: + Schedule: cron(0 * * * ? *) + MemorySize: 128 + Timeout: 100 diff --git a/nodejs22.x/full-stack/.gitignore b/nodejs22.x/full-stack/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/full-stack/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/full-stack/README.md b/nodejs22.x/full-stack/README.md new file mode 100644 index 000000000..cebaeea0a --- /dev/null +++ b/nodejs22.x/full-stack/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS Quick Start Web Application + +A cookiecutter template to create a NodeJS Quick Start Web Application using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-full-stack --name full-stack-app` + +> **NOTE**: ``--name`` allows you to specify a different project folder name + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/full-stack/cookiecutter.json b/nodejs22.x/full-stack/cookiecutter.json new file mode 100644 index 000000000..c77e45adf --- /dev/null +++ b/nodejs22.x/full-stack/cookiecutter.json @@ -0,0 +1,11 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore", + "*.vue" + ] +} \ No newline at end of file diff --git a/nodejs22.x/full-stack/setup.cfg b/nodejs22.x/full-stack/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/full-stack/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..e65dcb1ff --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,390 @@ +# Created by https://www.toptal.com/developers/gitignore/api/osx,sam,linux,python,windows,node +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,sam,linux,python,windows,node + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,sam,linux,python,windows,node \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/12 - Full Stack.png b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/12 - Full Stack.png new file mode 100644 index 0000000000000000000000000000000000000000..b2ab6142903be5e1c938fd47e3dc82951306da10 GIT binary patch literal 49390 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 z%0D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 _pq6K; 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 literal 0 HcmV?d00001 diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..c76375afe --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,246 @@ +# {{cookiecutter.project_name}} + +This project contains source code and supporting files for a serverless application that you can deploy with the AWS Serverless Application Model (AWS SAM) command line interface (CLI). It includes the following files and folders: + +- `src` - Code for the application's Lambda function. +- `events` - Invocation events that you can use to invoke the function. +- `__tests__` - Unit tests for the application code. +- `template.yaml` - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions, an API Gateway API, an S3 Bucket with a CloudFront Distribution and Amazon DynamoDB tables. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +This template addresses the challenge developers can face when adding a front-end to a standard serverless backend. See below for a diagram that represents a standard serverless backend: + +![Standard](./3-Serverless-API.png) + +This template adds the following components to the standard serverless backend which provides the foundation for further development of a single page app frontend to allow your users to interact with your api: + +![Full-Stack](./12%20-%20Full%20Stack.png) + +How does it work? When the full stack is deployed, the end result is a single page web application hosted on S3/CloudFront that allows users to interact with the serverless API that is created by this project. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +The following outputs will be displayed in the outputs when the deployment is complete: +* API Gateway endpoint API +* CloudFront Distribution ID +* CloudFront domain name +* S3 Bucket for Front End source files + +## Deploy the Front End +For convenience, the included deploy_frontend.sh bash script can be run to automatically deploy your front end website to your AWS account. Run using the following command: +```bash +./deploy_frontend.sh +``` + +## Regarding CORS +For security, it is recommended to restrict the Allowed Origin value to restrict HTTP requests that are initiated from scripts running in the browser. See here for more information: +* Configuring CORS for an HTTP API - [Configuring CORS for an HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html). + +## Use the AWS SAM CLI to build and test locally + +Build your application by using the `sam build` command. + +```bash +my-application$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +my-application$ sam local invoke putItemFunction --event events/event-post-item.json +my-application$ sam local invoke getAllItemsFunction --event events/event-get-all-items.json +``` + +The AWS SAM CLI can also emulate your application's API. Use the `sam local start-api` command to run the API locally on port 3000. + +```bash +my-application$ sam local start-api +my-application$ curl http://localhost:3000/ +``` + +The AWS SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. It also includes a reference to the API Gateway that is also deployed as part of this application. + +```yaml + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET + RestApiId: + Ref: ApiGatewayApi +``` + +## Test locally with dynamodb: +1. Start DynamoDB Local in a Docker container (this example works on codespace) +``` +docker run --rm -p 8000:8000 -v /tmp:/data amazon/dynamodb-local +``` +2. Create the DynamoDB table (sample command below): +``` +aws dynamodb create-table --table-name SampleTable --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --billing-mode PAY_PER_REQUEST --endpoint-url http://127.0.0.1:8000 +``` +3. Retrieve the ip address of your docker container running dynamodb local: +``` +docker inspect -f {% raw %} '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' {% endraw %} +``` +4. Update env.json with the IP of your docker container for the endpoint override - see here for example: +``` +{ + "getByIdFunction": { + "ENDPOINT_OVERRIDE": "http://172.17.0.2:8000", + "SAMPLE_TABLE": "SampleTable" + }, + "putItemFunction": { + "ENDPOINT_OVERRIDE": "http://172.17.0.2:8000", + "SAMPLE_TABLE": "SampleTable" + } +} +``` +5. run the following commands to start the sam local api: +``` +sam local start-api --env-vars env.json --host 0.0.0.0 --debug +``` +6. For testing - you can put an item into dynamodb local +``` +aws dynamodb put-item \ + --table-name SampleTable \ + --item '{"id": {"S": "A1234"}, "name": {"S": "randeepx"}}' \ + --endpoint-url http://127.0.0.1:8000 +``` +7. How to scan your table for items +``` +aws dynamodb scan --table-name SampleTable --endpoint-url http://127.0.0.1:8000 +``` +8. To run frontend application locally: +Go to your `frontend` code directory + ``` +cd frontend +``` +Make backend API endpoint accessible as an environment variable. For local, create a `.env` file, Here is an example: +``` +VUE_APP_API_ENDPOINT=http://127.0.0.1:3000/ +``` +9. run following command to compile and run (with hot-reloads) for development +``` +npm run serve +``` +10. to execute frontend unit test +``` +npm run test +``` + + +## Add a resource to your application +The application template uses AWS SAM to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources, such as functions, triggers, and APIs. For resources that aren't included in the [AWS SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use the standard [AWS CloudFormation resource types](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html). + +Update `template.yaml` to add a dead-letter queue to your application. In the **Resources** section, add a resource named **MyQueue** with the type **AWS::SQS::Queue**. Then add a property to the **AWS::Serverless::Function** resource named **DeadLetterQueue** that targets the queue's Amazon Resource Name (ARN), and a policy that grants the function permission to access the queue. + +``` +Resources: + MyQueue: + Type: AWS::SQS::Queue + getAllItemsFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get-all-items.getAllItemsHandler + Runtime: nodejs22.x + DeadLetterQueue: + Type: SQS + TargetArn: !GetAtt MyQueue.Arn + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt MyQueue.QueueName +``` + +The dead-letter queue is a location for Lambda to send events that could not be processed. It's only used if you invoke your function asynchronously, but it's useful here to show how you can modify your application's resources and function configuration. + +Deploy the updated application. + +```bash +my-application$ sam deploy +``` + +Open the [**Applications**](https://console.aws.amazon.com/lambda/home#/applications) page of the Lambda console, and choose your application. When the deployment completes, view the application resources on the **Overview** tab to see the new resource. Then, choose the function to see the updated configuration that specifies the dead-letter queue. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, the AWS SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs that are generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +**NOTE:** This command works for all Lambda functions, not just the ones you deploy using AWS SAM. + +```bash +my-application$ sam logs -n putItemFunction --stack-name sam-app --tail +``` + +**NOTE:** This uses the logical name of the function within the stack. This is the correct name to use when searching logs inside an AWS Lambda function within a CloudFormation stack, even if the deployed function name varies due to CloudFormation's unique resource name generation. + +You can find more information and examples about filtering Lambda function logs in the [AWS SAM CLI documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `__tests__` folder in this project. Use `npm` to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +my-application$ npm install +my-application$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +For an introduction to the AWS SAM specification, the AWS SAM CLI, and serverless application concepts, see the [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). + +Next, you can use the AWS Serverless Application Repository to deploy ready-to-use apps that go beyond Hello World samples and learn how authors developed their applications. For more information, see the [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) and the [AWS Serverless Application Repository Developer Guide](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/get-all-items.test.mjs b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/get-all-items.test.mjs new file mode 100644 index 000000000..063c71b71 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/get-all-items.test.mjs @@ -0,0 +1,43 @@ +// Import getAllItemsHandler function from get-all-items.mjs +import { getAllItemsHandler } from '../../../src/handlers/get-all-items.mjs'; +// Import dynamodb from aws-sdk +import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb'; +import { mockClient } from "aws-sdk-client-mock"; + +// This includes all tests for getAllItemsHandler() +describe('Test getAllItemsHandler', () => { + const ddbMock = mockClient(DynamoDBDocumentClient); + + beforeEach(() => { + ddbMock.reset(); + }); + + it('should return ids', async () => { + const items = [{ id: 'id1' }, { id: 'id2' }]; + + // Return the specified value whenever the spied scan function is called + ddbMock.on(ScanCommand).resolves({ + Items: items, + }); + + const event = { + httpMethod: 'GET' + }; + + // Invoke helloFromLambdaHandler() + const result = await getAllItemsHandler(event); + + const expectedResult = { + statusCode: 200, + headers: { + "Access-Control-Allow-Headers" : "Content-Type", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "OPTIONS,POST,GET" + }, + body: JSON.stringify(items) + }; + + // Compare the result with the expected result + expect(result).toEqual(expectedResult); + }); +}); diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/get-by-id.test.mjs b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/get-by-id.test.mjs new file mode 100644 index 000000000..1b2d1e4b8 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/get-by-id.test.mjs @@ -0,0 +1,48 @@ +// Import getByIdHandler function from get-by-id.mjs +import { getByIdHandler } from '../../../src/handlers/get-by-id.mjs'; +// Import dynamodb from aws-sdk +import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb'; +import { mockClient } from "aws-sdk-client-mock"; + +// This includes all tests for getByIdHandler() +describe('Test getByIdHandler', () => { + const ddbMock = mockClient(DynamoDBDocumentClient); + + beforeEach(() => { + ddbMock.reset(); + }); + + // This test invokes getByIdHandler() and compare the result + it('should get item by id', async () => { + const item = { id: 'id1' }; + + // Return the specified value whenever the spied get function is called + ddbMock.on(GetCommand).resolves({ + Item: item, + }); + + const event = { + httpMethod: 'GET', + pathParameters: { + id: 'id1' + } + }; + + // Invoke getByIdHandler() + const result = await getByIdHandler(event); + + const expectedResult = { + statusCode: 200, + headers: { + "Access-Control-Allow-Headers" : "Content-Type", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "OPTIONS,POST,GET" + }, + body: JSON.stringify(item) + }; + + // Compare the result with the expected result + expect(result).toEqual(expectedResult); + }); +}); + \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/put-item.test.mjs b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/put-item.test.mjs new file mode 100644 index 000000000..03acd5288 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/__tests__/unit/handlers/put-item.test.mjs @@ -0,0 +1,45 @@ +// Import putItemHandler function from put-item.mjs +import { putItemHandler } from '../../../src/handlers/put-item.mjs'; +// Import dynamodb from aws-sdk +import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb'; +import { mockClient } from "aws-sdk-client-mock"; +// This includes all tests for putItemHandler() +describe('Test putItemHandler', function () { + const ddbMock = mockClient(DynamoDBDocumentClient); + + beforeEach(() => { + ddbMock.reset(); + }); + + // This test invokes putItemHandler() and compare the result + it('should add id to the table', async () => { + const returnedItem = { id: 'id1', name: 'name1' }; + + // Return the specified value whenever the spied put function is called + ddbMock.on(PutCommand).resolves({ + returnedItem + }); + + const event = { + httpMethod: 'POST', + body: '{"id": "id1","name": "name1"}' + }; + + // Invoke putItemHandler() + const result = await putItemHandler(event); + + const expectedResult = { + statusCode: 200, + headers: { + "Access-Control-Allow-Headers" : "Content-Type", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "OPTIONS,POST,GET" + }, + body: JSON.stringify(returnedItem) + }; + + // Compare the result with the expected result + expect(result).toEqual(expectedResult); + }); +}); + \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/package.json b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/package.json new file mode 100644 index 000000000..11e7ea689 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/package.json @@ -0,0 +1,30 @@ +{ + "name": "delete-test-01", + "description": "delete-test-01-description", + "version": "0.0.1", + "private": true, + "dependencies": { + "@aws-sdk/client-dynamodb": "^3.398.0", + "@aws-sdk/lib-dynamodb": "^3.398.0" + }, + "devDependencies": { + "aws-sdk-client-mock": "^2.0.0", + "jest": "^29.2.1" + }, + "scripts": { + "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js" + }, + "jest": { + "testMatch": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)", + "**/__tests__/**/*.mjs?(x)", + "**/?(*.)+(spec|test).mjs?(x)" + ], + "moduleFileExtensions": [ + "mjs", + "js" + ] + } +} + diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/get-all-items.mjs b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/get-all-items.mjs new file mode 100644 index 000000000..3ff565ce9 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/get-all-items.mjs @@ -0,0 +1,66 @@ +// Create clients and set shared const values outside of the handler. + +// Create a DocumentClient that represents the query to add an item +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb'; + +//DynamoDB Endpoint +const ENDPOINT_OVERRIDE = process.env.ENDPOINT_OVERRIDE; +let ddbClient = undefined; + +if (ENDPOINT_OVERRIDE) { + ddbClient = new DynamoDBClient({ endpoint: ENDPOINT_OVERRIDE }); +} +else { + ddbClient = new DynamoDBClient({}); // Use default values for DynamoDB endpoint + console.warn("No value for ENDPOINT_OVERRIDE provided for DynamoDB, using default"); +} + +const ddbDocClient = DynamoDBDocumentClient.from(ddbClient); + +// Get the DynamoDB table name from environment variables +const tableName = process.env.SAMPLE_TABLE; + +/** + * A simple example includes a HTTP get method to get all items from a DynamoDB table. + */ +export const getAllItemsHandler = async (event) => { + if (event.httpMethod !== 'GET') { + throw new Error(`getAllItems only accept GET method, you tried: ${event.httpMethod}`); + } + // All log statements are written to CloudWatch + console.info('received:', event); + + // get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data) + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property + // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html + var params = { + TableName : tableName + }; + + try { + const data = await ddbDocClient.send(new ScanCommand(params)); + var items = data.Items; + } catch (err) { + console.error("Error retrieving all items:", err.message); + console.error("Error code:", err.code); + console.error("Error name:", err.name); + console.error("Error stack:", err.stack); + + throw err; + } + + const response = { + statusCode: 200, + headers: { + "Access-Control-Allow-Headers" : "Content-Type", + "Access-Control-Allow-Origin": "*", //DO NOT USE THIS VALUE IN PRODUCTION - https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html + "Access-Control-Allow-Methods": "OPTIONS,POST,GET" + }, + body: JSON.stringify(items) + }; + + // All log statements are written to CloudWatch + console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); + return response; +} diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/get-by-id.mjs b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/get-by-id.mjs new file mode 100644 index 000000000..8c97ccfbf --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/get-by-id.mjs @@ -0,0 +1,69 @@ +// Create clients and set shared const values outside of the handler. + +// Create a DocumentClient that represents the query to add an item +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb'; + +//DynamoDB Endpoint +const ENDPOINT_OVERRIDE = process.env.ENDPOINT_OVERRIDE; +let ddbClient = undefined; + +if (ENDPOINT_OVERRIDE) { + ddbClient = new DynamoDBClient({ endpoint: ENDPOINT_OVERRIDE }); +} +else{ + ddbClient = new DynamoDBClient({}); // Use default values for DynamoDB endpoint + console.warn("No value for ENDPOINT_OVERRIDE provided for DynamoDB, using default"); +} + +const ddbDocClient = DynamoDBDocumentClient.from(ddbClient); + +// Get the DynamoDB table name from environment variables +const tableName = process.env.SAMPLE_TABLE; + +/** + * A simple example includes a HTTP get method to get one item by id from a DynamoDB table. + */ +export const getByIdHandler = async (event) => { + if (event.httpMethod !== 'GET') { + throw new Error(`getMethod only accept GET method, you tried: ${event.httpMethod}`); + } + // All log statements are written to CloudWatch + console.info('received:', event); + + // Get id from pathParameters from APIGateway because of `/{id}` at template.yaml + const id = event.pathParameters.id; + + // Get the item from the table + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property + var params = { + TableName : tableName, + Key: { id: id }, + }; + + try { + const data = await ddbDocClient.send(new GetCommand(params)); + var item = data.Item; + } catch (err) { + console.error("Error retrieving item:", err.message); + console.error("Error code:", err.code); + console.error("Error name:", err.name); + console.error("Error stack:", err.stack); + + throw err; + } + + const response = { + statusCode: 200, + headers: { + "Access-Control-Allow-Headers" : "Content-Type", + "Access-Control-Allow-Origin": "*", //DO NOT USE THIS VALUE IN PRODUCTION - https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html + "Access-Control-Allow-Methods": "OPTIONS,POST,GET" + }, + body: JSON.stringify(item) + }; + + // All log statements are written to CloudWatch + console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); + return response; +} diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/put-item.mjs b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/put-item.mjs new file mode 100644 index 000000000..f92d38950 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/backend/src/handlers/put-item.mjs @@ -0,0 +1,71 @@ +// Create clients and set shared const values outside of the handler. + +// Create a DocumentClient that represents the query to add an item +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb'; + +//DynamoDB Endpoint +const ENDPOINT_OVERRIDE = process.env.ENDPOINT_OVERRIDE; +let ddbClient = undefined; + +if (ENDPOINT_OVERRIDE) { + ddbClient = new DynamoDBClient({ endpoint: ENDPOINT_OVERRIDE }); +} +else { + ddbClient = new DynamoDBClient({}); // Use default values for DynamoDB endpoint + console.warn("No value for ENDPOINT_OVERRIDE provided for DynamoDB, using default"); +} + +const ddbDocClient = DynamoDBDocumentClient.from(ddbClient); + +// Get the DynamoDB table name from environment variables +const tableName = process.env.SAMPLE_TABLE; + +/** + * A simple example includes a HTTP post method to add one item to a DynamoDB table. + */ +export const putItemHandler = async (event) => { + if (event.httpMethod !== 'POST') { + throw new Error(`postMethod only accepts POST method, you tried: ${event.httpMethod} method.`); + } + // All log statements are written to CloudWatch + console.info('received:', event); + + // Get id and name from the body of the request + const body = JSON.parse(event.body); + const id = body.id; + const name = body.name; + + // Creates a new item, or replaces an old item with a new item + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property + var params = { + TableName : tableName, + Item: { id : id, name: name } + }; + + try { + const data = await ddbDocClient.send(new PutCommand(params)); + console.log("Success - item added or updated", data); + } catch (err) { + console.error("Error adding or updating item:", err.message); + console.error("Error code:", err.code); + console.error("Error name:", err.name); + console.error("Error stack:", err.stack); + + throw err; + } + + const response = { + statusCode: 200, + headers: { + "Access-Control-Allow-Headers" : "Content-Type", + "Access-Control-Allow-Origin": "*", //DO NOT USE THIS VALUE IN PRODUCTION - https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html + "Access-Control-Allow-Methods": "OPTIONS,POST,GET" + }, + body: JSON.stringify(body) + }; + + // All log statements are written to CloudWatch + console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); + return response; +}; diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/deploy_frontend.ps1 b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/deploy_frontend.ps1 new file mode 100644 index 000000000..52d39cdff --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/deploy_frontend.ps1 @@ -0,0 +1,49 @@ +# Get user input for stack name +$stack_name = Read-Host "Enter the name of the CloudFormation stack:" + +# Get the API Gateway URL from the stack +$api_gateway_endpoint = aws cloudformation describe-stacks --stack-name $stack_name --query "Stacks[0].Outputs[?OutputKey=='APIGatewayEndpoint'].OutputValue" --output text + +# Get the CloudFront Distribution ID from the stack +$cloudfront_distribution_id = aws cloudformation describe-stacks --stack-name $stack_name --query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue" --output text + +# Get the S3 Bucket Name from the stack +$s3_bucket_name = aws cloudformation describe-stacks --stack-name $stack_name --query "Stacks[0].Outputs[?OutputKey=='WebS3BucketName'].OutputValue" --output text + +# Output the results +Write-Host "API Gateway URL: $api_gateway_endpoint" +Write-Host "CloudFront Distribution ID: $cloudfront_distribution_id" +Write-Host "S3 Bucket Name: $s3_bucket_name" + +# Move to frontend and install +cd frontend/ +npm install + +# Create .env file for building distribution with API Gateway Endpoint defined +New-Item -ItemType File -Path ".env" + +# Add the API Gateway endpoint to the .env file +"`nVUE_APP_API_ENDPOINT=$api_gateway_endpoint" | Out-File -Append ".env" + +# Confirm that the endpoint has been added to the .env file +Write-Host "The API Gateway endpoint has been added to the .env file:" +Get-Content ".env" + +# Create distribution for deployment +npm run build +cd dist/ + +# Sync distribution with S3 +aws s3 sync . "s3://$s3_bucket_name/" + +# Create cloudfront invalidation and capture id for next step +$invalidation_output = aws cloudfront create-invalidation --distribution-id $cloudfront_distribution_id --paths "/*" +$invalidation_id = $invalidation_output | Select-String -Pattern '(?<=Id": ")[^"]+' | ForEach-Object { $_.Matches.Value } + +# Wait for cloudfront invalidation to complete +aws cloudfront wait invalidation-completed --distribution-id $cloudfront_distribution_id --id $invalidation_id + +# Get cloudfront domain name and validate +$cloudfront_domain_name = aws cloudfront list-distributions --query "DistributionList.Items[?Id=='$cloudfront_distribution_id'].DomainName" --output text + +Write-Host "The invalidation is now complete - please visit your cloudfront URL to test: $cloudfront_domain_name" \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/deploy_frontend.sh b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/deploy_frontend.sh new file mode 100755 index 000000000..e50c37941 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/deploy_frontend.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Get user input for stack name +read -p "Enter the name of the CloudFormation stack: " stack_name + +# Get the API Gateway URL from the stack +api_gateway_endpoint=$(aws cloudformation describe-stacks --stack-name "$stack_name" --query "Stacks[0].Outputs[?OutputKey=='APIGatewayEndpoint'].OutputValue" --output text) + +# Get the CloudFront Distribution ID from the stack +cloudfront_distribution_id=$(aws cloudformation describe-stacks --stack-name "$stack_name" --query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue" --output text) + +# Get the S3 Bucket Name from the stack +s3_bucket_name=$(aws cloudformation describe-stacks --stack-name "$stack_name" --query "Stacks[0].Outputs[?OutputKey=='WebS3BucketName'].OutputValue" --output text) + +# Output the results +echo "API Gateway URL: $api_gateway_endpoint" +echo "CloudFront Distribution ID: $cloudfront_distribution_id" +echo "S3 Bucket Name: $s3_bucket_name" + +# Move to frontend and install +cd frontend/ && npm install + +# Create .env file for building distribtuion with API Gateway Endpoint defined +touch .env + +# Add the API Gateway endpoint to the .env file +echo "VUE_APP_API_ENDPOINT=$api_gateway_endpoint" >> .env + +# Confirm that the endpoint has been added to the .env file +echo "The API Gateway endpoint has been added to the .env file:" +cat .env + +# Create distribution for deployment +npm run build && cd dist/ + +# Sync distribution with S3 +aws s3 sync . s3://$s3_bucket_name/ + +# Create cloudfront invalidation and capture id for next step +invalidation_output=$(aws cloudfront create-invalidation --distribution-id $cloudfront_distribution_id --paths "/*") +invalidation_id=$(echo "$invalidation_output" | grep -oP '(?<="Id": ")[^"]+') + +# Wait for cloudfront invalidation to complete +aws cloudfront wait invalidation-completed --distribution-id $cloudfront_distribution_id --id $invalidation_id + +# Get cloudfront domain name and validate +cloudfront_domain_name=$(aws cloudfront list-distributions --query "DistributionList.Items[?Id=='$cloudfront_distribution_id'].DomainName" --output text) + +echo "The invalidation is now complete - please visit your cloudfront URL to test: $cloudfront_domain_name" diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/env.json b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/env.json new file mode 100644 index 000000000..947de1f76 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/env.json @@ -0,0 +1,14 @@ +{ + "getAllItemsFunction": { + "ENDPOINT_OVERRIDE": "", + "SAMPLE_TABLE": "" + }, + "getByIdFunction": { + "ENDPOINT_OVERRIDE": "", + "SAMPLE_TABLE": "" + }, + "putItemFunction": { + "ENDPOINT_OVERRIDE": "", + "SAMPLE_TABLE": "" + } + } \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-get-all-items.json b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-get-all-items.json new file mode 100644 index 000000000..3a0cb5f77 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-get-all-items.json @@ -0,0 +1,3 @@ +{ + "httpMethod": "GET" +} \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-get-by-id.json b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-get-by-id.json new file mode 100644 index 000000000..63a64fb45 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-get-by-id.json @@ -0,0 +1,6 @@ +{ + "httpMethod": "GET", + "pathParameters": { + "id": "id1" + } +} \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-post-item.json b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-post-item.json new file mode 100644 index 000000000..6367003e5 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/events/event-post-item.json @@ -0,0 +1,4 @@ +{ + "httpMethod": "POST", + "body": "{\"id\": \"id1\",\"name\": \"name1\"}" +} \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/.gitignore b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/.gitignore new file mode 100644 index 000000000..20cffc7ca --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/.gitignore @@ -0,0 +1,24 @@ +.DS_Store +node_modules +/dist +/test/__snapshots__ +package-lock.json + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/LICENSE b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/LICENSE new file mode 100644 index 000000000..7c41feeec --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/LICENSE @@ -0,0 +1,14 @@ +Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/README.md b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/README.md new file mode 100644 index 000000000..de3b2cf12 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/README.md @@ -0,0 +1,36 @@ +# SAM starter Front End Application + +## Project setup +``` +npm install +``` + +### Compiles and hot-reloads for development +``` +npm run serve +``` + +### Compiles and minifies for production +``` +npm run build +``` + +### Lints and fixes files +``` +npm run lint +``` + +### Runs unit tests +``` +npm run test +``` + +### Connect to Backend + +Make backend API endpoint accessible as an environment variable. For local, create a `.env` file, Here is an example: +``` +VUE_APP_API_ENDPOINT=http://127.0.0.1:3000/ +``` + +### Customize configuration +See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/babel.config.js b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/babel.config.js new file mode 100644 index 000000000..e9558405f --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/cli-plugin-babel/preset' + ] +} diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json new file mode 100644 index 000000000..ddc58ceda --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json @@ -0,0 +1,55 @@ +{ + "name": "sam-starter-fontend", + "version": "1.0.0", + "description": "Frontend app for the Innovator Island workshop.", + "author": "Randeep Singh , Ben Peterson ", + "private": true, + "scripts": { + "serve": "vue-cli-service serve", + "build": "vue-cli-service build", + "lint": "vue-cli-service lint", + "test": "vitest" + }, + "dependencies": { + "axios": "^1.6.0", + "core-js": "^3.18.1", + "vue": "^3.0.0" + }, + "devDependencies": { + "@testing-library/vue": "^7.0.0", + "@vue/cli-plugin-babel": "^5.0.8", + "@vue/cli-plugin-eslint": "^5.0.8", + "@vitejs/plugin-vue": "^4.2.3", + "@vue/cli-service": "^5.0.8", + "@vue/compiler-sfc": "^3.0.0", + "babel-eslint": "^10.1.0", + "eslint": "^7.32.0", + "eslint-plugin-vue": "^7.18.0", + "happy-dom": "^9.20.3", + "vitest": "^0.31.1", + "vue-template-compiler": "^2.6.14" + }, + "eslintConfig": { + "root": true, + "env": { + "node": true + }, + "extends": [ + "plugin:vue/vue3-essential", + "eslint:recommended" + ], + "parserOptions": { + "parser": "babel-eslint" + }, + "rules": {} + }, + "prettier": { + "semi": false, + "singleQuote": true + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not dead" + ] +} diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/favicon.ico b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b7fdc77dc80947012d0a5fce7999dde34429a47e GIT binary patch literal 15406 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 H!ZYxHCQpmj literal 0 HcmV?d00001 diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/index.html b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/index.html new file mode 100644 index 000000000..3e5a13962 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + <%= htmlWebpackPlugin.options.title %> + + + +
+ + + diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/logo.png b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..56a3af614bf81d70f280cb36c576aa82b684b8f8 GIT binary patch literal 110984 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 M07*qoM6N<$f|_ +
+ +
+

Hi, I Am SAM

+
+

Create User

+ +
+

Get User By ID

+ +
+

Get All Users

+ + + + + + diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/CreateItem.vue b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/CreateItem.vue new file mode 100644 index 000000000..17507de2b --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/CreateItem.vue @@ -0,0 +1,54 @@ + + + + + \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/GetItemById.vue b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/GetItemById.vue new file mode 100644 index 000000000..6f67b7c72 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/GetItemById.vue @@ -0,0 +1,54 @@ + + + + + \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/GetItems.vue b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/GetItems.vue new file mode 100644 index 000000000..bdc9f11e6 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/components/GetItems.vue @@ -0,0 +1,42 @@ + + + + + \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/main.js b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/main.js new file mode 100644 index 000000000..01433bca2 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/src/main.js @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/CreateItem.test.js b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/CreateItem.test.js new file mode 100644 index 000000000..f5bbbfa4b --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/CreateItem.test.js @@ -0,0 +1,36 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import CreateItem from '../src/components/CreateItem.vue' + +describe('CreateItem', () => { + + it('has proper input fileds', () => { + const wrapper = mount(CreateItem) + + const userId = wrapper.find('#userId') + expect(userId.element.id).toBe('userId') + + const userName = wrapper.find('#userName') + expect(userName.element.id).toBe('userName') + + const button = wrapper.find('button') + expect(button.exists()).toBe(true) + }) + + it('accepts input values', () => { + const wrapper = mount(CreateItem) + + const userId = wrapper.find('#userId') + userId.setValue('A123') + + const userName = wrapper.find('#userName') + userName.setValue('John Doe') + + + expect(userId.element.value).toBe('A123') + expect(userName.element.value).toBe('John Doe') + + // const button = wrapper.find('button') + // button.trigger('click') + }) +}) \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/GetItemById.test.js b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/GetItemById.test.js new file mode 100644 index 000000000..bb101c96b --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/GetItemById.test.js @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import GetItemById from '../src/components/GetItemById.vue' + +describe('GetItemById', () => { + + it('has proper input fileds', () => { + const wrapper = mount(GetItemById) + + const userId = wrapper.find('#userId') + expect(userId.element.id).toBe('userId') + + const button = wrapper.find('button') + expect(button.exists()).toBe(true) + }) + + it('accepts input values', () => { + const wrapper = mount(GetItemById) + + const userId = wrapper.find('#userId') + userId.setValue('A123') + + expect(userId.element.value).toBe('A123') + + // const button = wrapper.find('button') + // button.trigger('click') + }) +}) \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/GetItems.test.js b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/GetItems.test.js new file mode 100644 index 000000000..1005e4252 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/test/GetItems.test.js @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import GetItems from '../src/components/GetItems.vue' + +describe('GetItems', () => { + + it('has proper input fileds', () => { + const wrapper = mount(GetItems) + + const button = wrapper.find('button') + expect(button.exists()).toBe(true) + }) + + it('accepts input values', () => { + const wrapper = mount(GetItems) + + // const button = wrapper.find('button') + // button.trigger('click') + }) +}) \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/vite.config.js b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/vite.config.js new file mode 100644 index 000000000..c74213279 --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig ({ + plugins: [ + vue(), + ], + // ... + test: { + // enable jest-like global test APIs + globals: true, + // simulate DOM with happy-dom + environment: 'happy-dom' + } +}) diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/vue.config.js b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/vue.config.js new file mode 100644 index 000000000..1c5377cfe --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/vue.config.js @@ -0,0 +1,3 @@ +module.exports = { + runtimeCompiler: true +} \ No newline at end of file diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..3a711301b --- /dev/null +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,244 @@ +# This is the SAM template that represents the architecture of your serverless application +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html + +# The AWSTemplateFormatVersion identifies the capabilities of the template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + {{cookiecutter.project_name}} +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: +- AWS::Serverless-2016-10-31 + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is an API gateway associated with the getByIdFunction and putItemFunctions + ApiGatewayApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Cors: + AllowMethods: "'OPTIONS, POST, GET'" + AllowHeaders: "'Content-Type'" + AllowOrigin: "'*'" #DO NOT USE THIS VALUE IN PRODUCTION - https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html + + # This is a Lambda function config associated with the source code: get-by-id.js + getAllItemsFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: backend/ + Handler: src/handlers/get-all-items.getAllItemsHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP get method to get all items by id from a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + # Make DynamoDB endpoint accessible as environment variable from function code during execution + ENDPOINT_OVERRIDE: "" + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET + RestApiId: + Ref: ApiGatewayApi + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: get-by-id.js + getByIdFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: backend/ + Handler: src/handlers/get-by-id.getByIdHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP get method to get one item by id from a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + # Make DynamoDB endpoint accessible as environment variable from function code during execution + ENDPOINT_OVERRIDE: "" + Events: + Api: + Type: Api + Properties: + Path: /{id} + Method: GET + RestApiId: + Ref: ApiGatewayApi + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: put-item.js + putItemFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: backend/ + Handler: src/handlers/put-item.putItemHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP post method to add one item to a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Environment: + Variables: + SAMPLE_TABLE: !Ref SampleTable + # Make DynamoDB endpoint accessible as environment variable from function code during execution + ENDPOINT_OVERRIDE: "" + Events: + Api: + Type: Api + Properties: + Path: / + Method: POST + RestApiId: + Ref: ApiGatewayApi + # Simple syntax to create a DynamoDB table with a single attribute primary key, more in + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + + # DynamoDB table to store item: {id: <ID>, name: <NAME>} + SampleTable: + Type: AWS::Serverless::SimpleTable + Properties: + PrimaryKey: + Name: id + Type: String + ProvisionedThroughput: + ReadCapacityUnits: 2 + WriteCapacityUnits: 2 + + # S3 Bucket to host single page app website + WebSiteBucket: + Type: "AWS::S3::Bucket" + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - BucketKeyEnabled: true + VersioningConfiguration: + Status: Enabled + WebSiteBucketPolicy: + Type: "AWS::S3::BucketPolicy" + Properties: + Bucket: !Ref WebSiteBucket + PolicyDocument: + Version: "2012-10-17" + Id: "PolicyForCloudFrontPrivateContent" + Statement: + - Sid: "AllowCloudFrontServicePrincipal" + Effect: "Allow" + Principal: + Service: "cloudfront.amazonaws.com" + Action: "s3:GetObject" + Resource: !Join [ "", [ "arn:aws:s3:::", !Ref WebSiteBucket, "/*" ] ] + Condition: + StringEquals: + "AWS:SourceArn": !Join [ "", [ "arn:aws:cloudfront::", !Ref "AWS::AccountId", ":distribution/", !Ref CloudFrontDistribution ] ] + + # CloudFront Distribution for hosting the single page app website + CloudFrontDistribution: + Type: "AWS::CloudFront::Distribution" + Properties: + DistributionConfig: + Origins: + - DomainName: !GetAtt WebSiteBucket.RegionalDomainName + Id: "myS3Origin" + OriginAccessControlId: !GetAtt CloudFrontOriginAccessControl.Id + S3OriginConfig: + OriginAccessIdentity: "" + Enabled: true + DefaultRootObject: "index.html" + HttpVersion: "http2" + DefaultCacheBehavior: + AllowedMethods: + - "DELETE" + - "GET" + - "HEAD" + - "OPTIONS" + - "PATCH" + - "POST" + - "PUT" + CachedMethods: + - "GET" + - "HEAD" + TargetOriginId: "myS3Origin" + ForwardedValues: + QueryString: false + Cookies: + Forward: "none" + ViewerProtocolPolicy: "allow-all" + MinTTL: 0 + DefaultTTL: 3600 + MaxTTL: 86400 + PriceClass: "PriceClass_200" + Restrictions: + GeoRestriction: + RestrictionType: "whitelist" + Locations: + - "US" + - "CA" + - "GB" + - "DE" + ViewerCertificate: + CloudFrontDefaultCertificate: true + CloudFrontOriginAccessControl: + Type: "AWS::CloudFront::OriginAccessControl" + Properties: + OriginAccessControlConfig: + Name: !Sub "${WebSiteBucket} OAC" + OriginAccessControlOriginType: "s3" + SigningBehavior: "always" + SigningProtocol: "sigv4" +Outputs: + APIGatewayEndpoint: + Description: "API Gateway endpoint URL for Prod stage" + Value: !Sub "https://${ApiGatewayApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" + CloudFrontDistributionId: + Description: "CloudFront Distribution ID for hosting web front end" + Value: !Ref CloudFrontDistribution + CloudFrontDistributionDomainName: + Description: "CloudFront Distribution Domain Name for accessing web front end" + Value: !GetAtt CloudFrontDistribution.DomainName + WebS3BucketName: + Description: "S3 Bucket for hosting web frontend" + Value: !Ref WebSiteBucket diff --git a/nodejs22.x/hello-gql/.gitignore b/nodejs22.x/hello-gql/.gitignore new file mode 100644 index 000000000..9fb28e9f2 --- /dev/null +++ b/nodejs22.x/hello-gql/.gitignore @@ -0,0 +1,208 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +**/node_modules/ +**/jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/nodejs22.x/hello-gql/README.md b/nodejs22.x/hello-gql/README.md new file mode 100644 index 000000000..e7c3f12d8 --- /dev/null +++ b/nodejs22.x/hello-gql/README.md @@ -0,0 +1,69 @@ +# {{ cookiecutter.project_name }} + +This is a sample template for {{ cookiecutter.project_name }} - Below is a brief explanation of what we have generated for you: + +```bash +. +├── README.md <-- This instructions file +├── gql <-- Source code for schema and pipeline functions +│ ├── createPostItem.js <-- Pipeline function code +│ ├── getPostFromTable.js <-- Pipeline function code +│ ├── greet.js <-- Pipeline function code +│ ├── preprocessPostItem.js <-- Pipeline function code +│ └── schema.graphql <-- Schema definition +├── greeter <-- Source code for lambda function +│ ├── tests <-- Tests for lambda function +│ │ ├──events <-- Event stubs +│ │ │ └── appsync.json <-- Sample event for tests +│ │ └──unit <-- Unit tests +│ │ └── test-handler.mjs <-- Source file with tests +│ ├── app.mjs <-- Lambda function source code +│ └── package.json <-- List of Lambda dependencies and npm scripts +└── template.yaml <-- SAM template +``` + +## Requirements + +* AWS CLI already configured with Administrator permission +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) + +## Deploy the sample application + +To deploy your application for the first time, run the following in your shell: + +```bash +sam deploy --guided +``` + +This command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your AppSync GraphQL Endpoint URL and API key, which is required to access your API, in the output values displayed after deployment. + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name "{{ cookiecutter.__stack_name }}" +``` + +## Test lambda function locally + +1. Open terminal in `{{ cookiecutter.__stack_name }}/greeter` directory +2. Run `npm i` +3. Run `npm run test` + +To modify the lambda function event, edit `{{ cookiecutter.__stack_name }}/greeter/tests/events/appsync.json` file. + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. diff --git a/nodejs22.x/hello-gql/cookiecutter.json b/nodejs22.x/hello-gql/cookiecutter.json new file mode 100644 index 000000000..aa29c9e4f --- /dev/null +++ b/nodejs22.x/hello-gql/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} diff --git a/nodejs22.x/hello-gql/setup.cfg b/nodejs22.x/hello-gql/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/hello-gql/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..9fb28e9f2 --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,208 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +**/node_modules/ +**/jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/createPostItem.js b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/createPostItem.js new file mode 100644 index 000000000..97f20d313 --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/createPostItem.js @@ -0,0 +1,14 @@ +import { util } from "@aws-appsync/utils"; + +export function request(ctx) { + const { key, values } = ctx.prev.result; + return { + operation: "PutItem", + key: util.dynamodb.toMapValues(key), + attributeValues: util.dynamodb.toMapValues(values), + }; +} + +export function response(ctx) { + return ctx.result; +} diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/getPostFromTable.js b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/getPostFromTable.js new file mode 100644 index 000000000..ca55c8bc7 --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/getPostFromTable.js @@ -0,0 +1,19 @@ +import { util } from "@aws-appsync/utils"; + +export function request(ctx) { + return dynamoDBGetItemRequest({ id: ctx.args.id }); +} + +export function response(ctx) { + return ctx.result; +} + +/** + * A helper function to get a DynamoDB item + */ +function dynamoDBGetItemRequest(key) { + return { + operation: "GetItem", + key: util.dynamodb.toMapValues(key), + }; +} diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/greet.js b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/greet.js new file mode 100644 index 000000000..05fb0dcca --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/greet.js @@ -0,0 +1,11 @@ +export function request(ctx) { + const { source, args } = ctx; + return { + operation: "Invoke", + payload: { field: ctx.info.fieldName, arguments: args, source }, + }; +} + +export function response(ctx) { + return ctx.result; +} diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/preprocessPostItem.js b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/preprocessPostItem.js new file mode 100644 index 000000000..a1b4c839d --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/preprocessPostItem.js @@ -0,0 +1,14 @@ +import { util } from "@aws-appsync/utils"; + +export function request(ctx) { + const id = util.autoId(); + const { ...values } = ctx.args; + values.ups = 1; + values.downs = 0; + values.version = 1; + return { payload: { key: { id }, values: values } }; +} + +export function response(ctx) { + return ctx.result; +} diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/schema.graphql b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/schema.graphql new file mode 100644 index 000000000..d6a62ddee --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/gql/schema.graphql @@ -0,0 +1,24 @@ +schema { + query: Query + mutation: Mutation +} + +type Query { + getPost(id: String!): Post + sayHello(name: String): String + sayGoodbye(name: String): String +} + +type Mutation { + addPost(author: String!, title: String!, content: String!): Post! +} + +type Post { + id: String! + author: String + title: String + content: String + ups: Int! + downs: Int! + version: Int! +} diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/.npmignore b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/.npmignore new file mode 100644 index 000000000..e7e1fb04f --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/app.mjs b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/app.mjs new file mode 100644 index 000000000..11b4e6124 --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/app.mjs @@ -0,0 +1,15 @@ +export const lambdaHandler = async (event, _) => { + console.log("Got an Invoke Request."); + console.log(`EVENT: ${JSON.stringify(event, 2)}`); + + const name = !event.arguments?.name ? "world" : event.arguments.name; + + switch (event.field) { + case "sayHello": + return `Hello, ${name}`; + case "sayGoodbye": + return `Bye, ${name}`; + default: + throw new Error("Unknown field, unable to resolve " + event.field); + } +}; diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/package.json b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/package.json new file mode 100644 index 000000000..d48887bd1 --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/package.json @@ -0,0 +1,16 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "hello world sample for NodeJS", + "main": "app.js", + "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", + "author": "SAM CLI", + "license": "MIT", + "scripts": { + "test": "mocha tests/unit/" + }, + "devDependencies": { + "chai": "^4.3.6", + "mocha": "^10.1.0" + } +} diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/events/appsync.json b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/events/appsync.json new file mode 100644 index 000000000..95a0a0d0e --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/events/appsync.json @@ -0,0 +1,69 @@ +{ + "field": "sayHello", + "arguments": { + "name": "User" + }, + "identity": { + "claims": { + "sub": "192879fc-a240-4bf1-ab5a-d6a00f3063f9", + "email_verified": true, + "iss": "https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx", + "phone_number_verified": false, + "cognito:username": "jdoe", + "aud": "7471s60os7h0uu77i1tk27sp9n", + "event_id": "bc334ed8-a938-4474-b644-9547e304e606", + "token_use": "id", + "auth_time": 1599154213, + "phone_number": "+19999999999", + "exp": 1599157813, + "iat": 1599154213, + "email": "jdoe@email.com" + }, + "defaultAuthStrategy": "ALLOW", + "groups": null, + "issuer": "https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx", + "sourceIp": ["1.1.1.1"], + "sub": "192879fc-a240-4bf1-ab5a-d6a00f3063f9", + "username": "jdoe" + }, + "source": null, + "request": { + "headers": { + "x-forwarded-for": "1.1.1.1, 2.2.2.2", + "cloudfront-viewer-country": "US", + "cloudfront-is-tablet-viewer": "false", + "via": "2.0 xxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)", + "cloudfront-forwarded-proto": "https", + "origin": "https://us-west-1.console.aws.amazon.com", + "content-length": "217", + "accept-language": "en-US,en;q=0.9", + "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-1.amazonaws.com", + "x-forwarded-proto": "https", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", + "accept": "*/*", + "cloudfront-is-mobile-viewer": "false", + "cloudfront-is-smarttv-viewer": "false", + "accept-encoding": "gzip, deflate, br", + "referer": "https://us-west-1.console.aws.amazon.com/appsync/home?region=us-west-1", + "content-type": "application/json", + "sec-fetch-mode": "cors", + "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==", + "x-amzn-trace-id": "Root=1-5f512f51-fac632066c5e848ae714", + "authorization": "eyJraWQiOiJScWFCSlJqYVJlM0hrSnBTUFpIcVRXazNOW...", + "sec-fetch-dest": "empty", + "x-amz-user-agent": "AWS-Console-AppSync/", + "cloudfront-is-desktop-viewer": "true", + "sec-fetch-site": "cross-site", + "x-forwarded-port": "443" + } + }, + "prev": null, + "info": { + "selectionSetList": ["id", "field1", "field2"], + "selectionSetGraphQL": "{\n id\n field1\n field2\n}", + "parentTypeName": "Mutation", + "fieldName": "createSomething", + "variables": {} + }, + "stash": {} +} diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/unit/test-handler.mjs b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/unit/test-handler.mjs new file mode 100644 index 000000000..c59093c6f --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/unit/test-handler.mjs @@ -0,0 +1,17 @@ +"use strict"; + +import { lambdaHandler } from "../../app.mjs"; +import { expect } from "chai"; + +import event from "../events/appsync.json" assert { type: "json" }; + +const context = {}; + +describe("Tests Lambda handler", function () { + it("verifies successful response", async () => { + const result = await lambdaHandler(event, context); + + expect(result).to.be.an("string"); + expect(result).to.be.equal("Hello, User"); + }); +}); diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..3f91172a9 --- /dev/null +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,105 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + + +Resources: + PostsTable: + Type: AWS::Serverless::SimpleTable + + Greeter: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: greeter/ + Handler: app.lambdaHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + + HelloWorldGraphQLApi: + Type: AWS::Serverless::GraphQLApi + Properties: + SchemaUri: ./gql/schema.graphql + Auth: + Type: API_KEY + ApiKeys: + MyApiKey: + Description: my api key + DataSources: + DynamoDb: + Posts: + TableName: !Ref PostsTable + TableArn: !GetAtt PostsTable.Arn + Lambda: + Greeter: + FunctionArn: !GetAtt Greeter.Arn + Functions: + preprocessPostItem: + Runtime: + Name: APPSYNC_JS + Version: 1.0.0 + DataSource: NONE + CodeUri: ./gql/preprocessPostItem.js + createPostItem: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + DataSource: Posts + CodeUri: ./gql/createPostItem.js + getPostFromTable: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + DataSource: Posts + CodeUri: ./gql/getPostFromTable.js + greet: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + DataSource: Greeter + CodeUri: ./gql/greet.js + Resolvers: + Mutation: + addPost: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + Pipeline: + - preprocessPostItem + - createPostItem + Query: + getPost: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + Pipeline: + - getPostFromTable + sayHello: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + Pipeline: + - greet + sayGoodbye: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + Pipeline: + - greet + + +Outputs: + HelloWorldGraphQLApi: + Description: HelloWorldGraphQLApi endpoint URL for Prod environment + Value: !GetAtt HelloWorldGraphQLApi.GraphQLUrl + HelloWorldGraphQLApiMyApiKey: + Description: API Key for HelloWorldGraphQLApi + Value: !GetAtt HelloWorldGraphQLApiMyApiKey.ApiKey + diff --git a/nodejs22.x/hello-img/.gitignore b/nodejs22.x/hello-img/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/hello-img/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/hello-img/README.md b/nodejs22.x/hello-img/README.md new file mode 100644 index 000000000..478057812 --- /dev/null +++ b/nodejs22.x/hello-img/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS Hello-world for SAM based Serverless App + +A cookiecutter template to create a NodeJS Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/hello-img/cookiecutter.json b/nodejs22.x/hello-img/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/hello-img/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/hello-img/setup.cfg b/nodejs22.x/hello-img/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/hello-img/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..5854f05ec --- /dev/null +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,207 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..00031caee --- /dev/null +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,116 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello-world - Code for the application's Lambda function and Project Dockerfile. +- events - Invocation events that you can use to invoke the function. +- hello-world/tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) + +You may need the following for local testing. + +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build a docker image from a Dockerfile and then the source of your application inside the Docker image. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI builds a docker image from a Dockerfile and then installs dependencies defined in `hello-world/package.json` inside the docker image. The processed template file is saved in the `.aws-sam/build` folder. +* **Note**: The Dockerfile included in this sample application uses `npm install` by default. If you are building your code for production, you can modify it to use `npm ci` instead. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `hello-world/tests` folder in this project. Use NPM to install the [Mocha test framework](https://mochajs.org/) and run unit tests from your local machine. + +```bash +{{ cookiecutter.project_name }}$ cd hello-world +hello-world$ npm install +hello-world$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/events/event.json b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..070ad8e01 --- /dev/null +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/.npmignore b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/.npmignore new file mode 100644 index 000000000..e7e1fb04f --- /dev/null +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/Dockerfile b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/Dockerfile new file mode 100644 index 000000000..f03a62a5f --- /dev/null +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/Dockerfile @@ -0,0 +1,10 @@ +FROM public.ecr.aws/lambda/nodejs:22 + +COPY app.mjs package*.json ./ + +RUN npm install +# If you are building your code for production, instead include a package-lock.json file on this directory and use: +# RUN npm ci --production + +# Command can be overwritten by providing a different command in the template directly. +CMD ["app.lambdaHandler"] diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/app.mjs b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/app.mjs new file mode 100644 index 000000000..9cef2aca5 --- /dev/null +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/app.mjs @@ -0,0 +1,23 @@ +/** + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {Object} event - API Gateway Lambda Proxy Input Format + * + * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html + * @param {Object} context + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {Object} object - API Gateway Lambda Proxy Output Format + * + */ + +export const lambdaHandler = async (event, context) => { + const response = { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world', + }) + }; + + return response; + }; diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/package.json b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/package.json new file mode 100644 index 000000000..0164adcf5 --- /dev/null +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/package.json @@ -0,0 +1,19 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "hello world sample for NodeJS", + "main": "app.js", + "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "axios": ">=1.6.0" + }, + "scripts": { + "test": "mocha tests/unit/" + }, + "devDependencies": { + "chai": "^4.3.6", + "mocha": "^10.2.0" + } +} diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.mjs b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.mjs new file mode 100644 index 000000000..02a66db27 --- /dev/null +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.mjs @@ -0,0 +1,20 @@ +'use strict'; + +import { lambdaHandler } from '../../app.mjs'; +import { expect } from 'chai'; +var event, context; + +describe('Tests index', function () { + it('verifies successful response', async () => { + const result = await lambdaHandler(event, context) + + expect(result).to.be.an('object'); + expect(result.statusCode).to.equal(200); + expect(result.body).to.be.an('string'); + + let response = JSON.parse(result.body); + + expect(response).to.be.an('object'); + expect(response.message).to.be.equal("hello world"); + }); +}); diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..2e2bb0ee3 --- /dev/null +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,47 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + PackageType: Image + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get + Metadata: + DockerTag: {{cookiecutter.runtime}}-v1 + DockerContext: ./hello-world + Dockerfile: Dockerfile + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/nodejs22.x/hello-ts-pt/.gitignore b/nodejs22.x/hello-ts-pt/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/hello-ts-pt/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/hello-ts-pt/README.md b/nodejs22.x/hello-ts-pt/README.md new file mode 100644 index 000000000..5f5436fcd --- /dev/null +++ b/nodejs22.x/hello-ts-pt/README.md @@ -0,0 +1,37 @@ +# AWS SAM cookiecutter for NodeJS TypeScript Lambda functions with Powertools for AWS Lambda (TypeScript) + +**Please note, you should not try to `git clone` this project.** Instead, use `cookiecutter` CLI instead as ``{{cookiecutter.project_name}}`` will be rendered based on your input and therefore all variables and files will be rendered properly. + +## Cookiecutter requirements + +Install `cookiecutter` command line: + +**Pip users**: + +* `pip install cookiecutter` + +**Homebrew users**: + +* `brew install cookiecutter` + +**Windows or Pipenv users**: + +* `pipenv install cookiecutter` + +**NOTE**: [`Pipenv`](https://github.com/pypa/pipenv) is the new and recommended Python packaging tool that works across multiple platforms and makes Windows a first-class citizen. + +### Usage + +Generate a new SAM based Serverless App: `sam init --runtime nodejs22.x`. + +You'll be prompted a few questions to help this cookiecutter template to scaffold this project and after its completed you should see a new folder at your current path with the name of the project you gave as input. + +**NOTE**: After you understand how cookiecutter works (cookiecutter.json, mainly), you can fork this repo and apply your own mechanisms to accelerate your development process and this can be followed for any programming language and OS. + +### Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + +### License + +This project is licensed under the terms of the [MIT License with no attribution](/LICENSE) diff --git a/nodejs22.x/hello-ts-pt/cookiecutter.json b/nodejs22.x/hello-ts-pt/cookiecutter.json new file mode 100644 index 000000000..7dfa5e4fb --- /dev/null +++ b/nodejs22.x/hello-ts-pt/cookiecutter.json @@ -0,0 +1,14 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "Powertools for AWS Lambda (TypeScript) Tracing": ["enabled","disabled"], + "Powertools for AWS Lambda (TypeScript) Metrics": ["enabled","disabled"], + "Powertools for AWS Lambda (TypeScript) Logging": ["enabled","disabled"], + "_copy_without_render": [ + ".gitignore" + ] + +} \ No newline at end of file diff --git a/nodejs22.x/hello-ts-pt/setup.cfg b/nodejs22.x/hello-ts-pt/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/hello-ts-pt/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..5854f05ec --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,207 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..fecf4d780 --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,182 @@ +# {{ cookiecutter.project_name }} + +Congratulations, you have just created a Serverless "Hello World" application using the AWS Serverless Application Model (AWS SAM) for the `nodejs22.x` runtime, and options to bootstrap it with [**Powertools for AWS Lambda (TypeScript)**](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/) (Lambda Powertools) utilities for Logging, Tracing and Metrics. + +Powertools for AWS Lambda (TypeScript) is a developer toolkit to implement Serverless best practices and increase developer velocity. + +## Powertools for AWS Lambda (TypeScript) features + +Powertools for AWS Lambda (TypeScript) provides three core utilities: + +* **[Tracer](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/tracer/)** - Utilities to trace Lambda function handlers, and both synchronous and asynchronous functions +* **[Logger](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/logger/)** - Structured logging made easier, and a middleware to enrich log items with key details of the Lambda context +* **[Metrics](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics/)** - Custom Metrics created asynchronously via CloudWatch Embedded Metric Format (EMF) + +Find the complete project's [documentation here](https://awslabs.github.io/aws-lambda-powertools-typescript). + +### Installing Powertools for AWS Lambda (TypeScript) + +You have 2 ways of consuming those utilities: + +* NPM modules +* Lambda Layer + +#### Lambda layers + +The Powertools for AWS Lambda (TypeScript) utilities is packaged as a single [AWS Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-concepts.html#gettingstarted-concepts-layer) + +👉 [Installation guide for the **Powertools for AWS Lambda (TypeScript)** layer](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/#lambda-layer) + +#### NPM modules + +The Powertools for AWS Lambda (TypeScript) utilities follow a modular approach, similar to the official [AWS SDK v3 for JavaScript](https://github.com/aws/aws-sdk-js-v3). + +Each TypeScript utility is installed as standalone NPM package. + +Install all three core utilities at once with this single command: + +```shell +npm install @aws-lambda-powertools/logger @aws-lambda-powertools/tracer @aws-lambda-powertools/metrics +``` + +Or refer to the installation guide of each utility: + +👉 [Installation guide for the **Tracer** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/tracer#getting-started) + +👉 [Installation guide for the **Logger** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/logger#getting-started) + +👉 [Installation guide for the **Metrics** utility](https://awslabs.github.io/aws-lambda-powertools-typescript/latest/core/metrics#getting-started) + +### Powertools for AWS Lambda (TypeScript) Examples + +* [CDK](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/cdk) +* [SAM](https://github.com/awslabs/aws-lambda-powertools-typescript/tree/main/examples/sam) + +## Working with this project + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +* hello-world - Code for the application's Lambda function written in TypeScript. +* events - Invocation events that you can use to invoke the function. +* hello-world/tests - Unit tests for the application code. +* template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +### Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +### Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI installs dependencies defined in `hello-world/package.json`, compiles TypeScript with esbuild, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +### Add a resource to your application + +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +### Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +### Unit tests + +Tests are defined in the `test` folder in this project. + +```bash +{{ cookiecutter.project_name }}$ cd hello-world +hello-world$ npm install +hello-world$ npm run test +``` + +### Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/events/event.json b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..070ad8e01 --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.eslintignore b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.eslintignore new file mode 100644 index 000000000..512d4cb8b --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.eslintignore @@ -0,0 +1,2 @@ +node_modules +.aws-sam \ No newline at end of file diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.eslintrc.js b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.eslintrc.js new file mode 100644 index 000000000..5da871fc4 --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: "module" + }, + extends: [ + "plugin:@typescript-eslint/recommended", // recommended rules from the @typescript-eslint/eslint-plugin + "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. + ], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + // e.g. "@typescript-eslint/explicit-function-return-type": "off", + } + }; \ No newline at end of file diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.npmignore b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.npmignore new file mode 100644 index 000000000..e7e1fb04f --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.prettierrc.js b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.prettierrc.js new file mode 100644 index 000000000..4c2c6c78b --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + trailingComma: "all", + singleQuote: true, + printWidth: 120, + tabWidth: 4 + }; \ No newline at end of file diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/app.ts b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/app.ts new file mode 100644 index 000000000..b6976fecc --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/app.ts @@ -0,0 +1,135 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +{%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Metrics"] == "enabled"%} +import { Metrics } from '@aws-lambda-powertools/metrics'; +{%- endif %} +{%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Logging"] == "enabled"%} +import { Logger } from '@aws-lambda-powertools/logger'; +{%- endif %} +{%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled"%} +import { Tracer } from '@aws-lambda-powertools/tracer'; +{%- endif %} + + +{%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Metrics"] == "enabled"%} +const metrics = new Metrics(); +{%- endif %} +{%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Logging"] == "enabled"%} +const logger = new Logger(); +{%- endif %} +{%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled"%} +const tracer = new Tracer(); +{%- endif %} + +/** + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {APIGatewayProxyEvent} event - API Gateway Lambda Proxy Input Format + * @param {Context} object - API Gateway Lambda $context variable + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {APIGatewayProxyResult} object - API Gateway Lambda Proxy Output Format + * + */ + +export const lambdaHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { + let response: APIGatewayProxyResult; + + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Logging"] == "enabled"%} + + // Log the incoming event + logger.info('Lambda invocation event', { event }); + + // Append awsRequestId to each log statement + logger.appendKeys({ + awsRequestId: context.awsRequestId, + }); + + {%- endif %} + + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled"%} + // Get facade segment created by AWS Lambda + const segment = tracer.getSegment(); + + if (!segment) { + response = { + statusCode: 500, + body: "Failed to get segment" + } + return response; + } + + // Create subsegment for the function & set it as active + const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`); + tracer.setSegment(handlerSegment); + + // Annotate the subsegment with the cold start & serviceName + tracer.annotateColdStart(); + tracer.addServiceNameAnnotation(); + + // Add annotation for the awsRequestId + tracer.putAnnotation('awsRequestId', context.awsRequestId); + + {%- endif %} + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Metrics"] == "enabled" %} + // Capture cold start metrics + metrics.captureColdStartMetric(); + + {%- endif %} + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled"%} + // Create another subsegment & set it as active + const subsegment = handlerSegment.addNewSubsegment('### MySubSegment'); + tracer.setSegment(subsegment); + {%- endif %} + + try { + // hello world code + response = { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world', + }), + }; + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Logging"] == "enabled"%} + logger.info(`Successful response from API enpoint: ${event.path}`, response.body); + {%- else %} + console.log('sending HTTP 200 - hello world response') + {%- endif %} + } catch (err) { + // Error handling + response = { + statusCode: 500, + body: JSON.stringify({ + message: 'some error happened', + }), + }; + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled"%} + tracer.addErrorAsMetadata(err as Error); + {%- endif %} + + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Logging"] == "enabled"%} + logger.error(`Error response from API enpoint: ${err}`, response.body); + {%- else %} + console.log('sending HTTP 500 - some error happened response') + {%- endif %} + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Metrics"] == "enabled" or cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled"%} + } finally { + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled"%} + // Close subsegments (the AWS Lambda one is closed automatically) + subsegment.close(); // (### MySubSegment) + handlerSegment.close(); // (## index.handler) + + // Set the facade segment as active again (the one created by AWS Lambda) + tracer.setSegment(segment); + + {%- endif %} + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Metrics"] == "enabled"%} + // Publish all stored metrics + metrics.publishStoredMetrics(); + + {%- endif %} + {%- endif %} + } + + return response; + +}; \ No newline at end of file diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/jest.config.ts b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/jest.config.ts new file mode 100644 index 000000000..3f8eb55bd --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/jest.config.ts @@ -0,0 +1,15 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +export default { + transform: { + '^.+\\.ts?$': 'ts-jest', + }, + clearMocks: true, + collectCoverage: true, + coverageDirectory: 'coverage', + coverageProvider: 'v8', + testMatch: ['**/tests/unit/*.test.ts'], +}; diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/package.json b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/package.json new file mode 100644 index 000000000..f1e2400bd --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/package.json @@ -0,0 +1,37 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "hello world sample for NodeJS", + "main": "app.js", + "repository": "https://github.com/aws/aws-sam-cli-app-templates/tree/master/nodejs22.x/hello-ts-pt", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "@aws-lambda-powertools/logger": "^2.0.3", + "@aws-lambda-powertools/metrics": "^2.0.3", + "@aws-lambda-powertools/tracer": "^2.0.3", + "esbuild": "^0.17.6" + }, + "scripts": { + "unit": "jest", + "lint": "eslint '*.ts' --quiet --fix", + "compile": "tsc", + "test": "npm run compile && npm run unit" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.109", + "@types/jest": "^29.4.0", + "@jest/globals": "^29.4.0", + "@types/node": "^20.5.7", + "@typescript-eslint/eslint-plugin": "^5.46.1", + "@typescript-eslint/parser": "^5.46.1", + "eslint": "^8.30.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "jest": "^29.3.1", + "prettier": "^2.5.1", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "typescript": "^4.9.4" + } +} diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.test.ts b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.test.ts new file mode 100644 index 000000000..da70ae4da --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.test.ts @@ -0,0 +1,87 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { lambdaHandler } from '../../app'; +import { expect, describe, it } from '@jest/globals'; + +describe('Unit test for app handler', function () { + it('verifies successful response', async () => { + const event: APIGatewayProxyEvent = { + httpMethod: 'get', + body: '', + headers: {}, + isBase64Encoded: false, + multiValueHeaders: {}, + multiValueQueryStringParameters: {}, + path: '/hello', + pathParameters: {}, + queryStringParameters: {}, + requestContext: { + accountId: '123456789012', + apiId: '1234', + authorizer: {}, + httpMethod: 'get', + identity: { + accessKey: '', + accountId: '', + apiKey: '', + apiKeyId: '', + caller: '', + clientCert: { + clientCertPem: '', + issuerDN: '', + serialNumber: '', + subjectDN: '', + validity: { notAfter: '', notBefore: '' }, + }, + cognitoAuthenticationProvider: '', + cognitoAuthenticationType: '', + cognitoIdentityId: '', + cognitoIdentityPoolId: '', + principalOrgId: '', + sourceIp: '', + user: '', + userAgent: '', + userArn: '', + }, + path: '/hello', + protocol: 'HTTP/1.1', + requestId: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef', + requestTimeEpoch: 1428582896000, + resourceId: '123456', + resourcePath: '/hello', + stage: 'dev', + }, + resource: '', + stageVariables: {}, + }; + const context: Context = { + callbackWaitsForEmptyEventLoop: false, + functionName: 'lambdaHandler', + functionVersion: '1.0', + invokedFunctionArn: 'arn:1234567890:lambda:lambdaHandler', + memoryLimitInMB: '128', + awsRequestId: '1234567890', + logGroupName: 'lambdaHandlerLogGroup', + logStreamName: 'c6a789dff9326bc178', + getRemainingTimeInMillis: function (): number { + throw new Error('Function not implemented.'); + }, + done: function (error?: Error, result?: any): void { + throw new Error('Function not implemented.'); + }, + fail: function (error: string | Error): void { + throw new Error('Function not implemented.'); + }, + succeed: function (messageOrObject: any): void { + throw new Error('Function not implemented.'); + } + }; + const result: APIGatewayProxyResult = await lambdaHandler(event,context); + + expect(result.statusCode).toEqual(200); + expect(result.body).toEqual( + JSON.stringify({ + message: 'hello world', + }), + ); + }); +}); diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/tsconfig.json b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/tsconfig.json new file mode 100644 index 000000000..93fab7255 --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/hello-world/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2020", + "strict": true, + "preserveConstEnums": true, + "noEmit": true, + "sourceMap": false, + "module": "es2015", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "exclude": ["node_modules"], +} \ No newline at end of file diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..324c53fb9 --- /dev/null +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,70 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + MemorySize: 128 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: hello-world/ + Handler: app.lambdaHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled"%} + Tracing: Active + {%- endif %} + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled" or cookiecutter["Powertools for AWS Lambda (TypeScript) Metrics"] == "enabled" or cookiecutter["Powertools for AWS Lambda (TypeScript) Logging"] == "enabled" %} + Environment: + Variables: + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Tracing"] == "enabled" or cookiecutter["Powertools for AWS Lambda (TypeScript) Metrics"] == "enabled"%} + POWERTOOLS_SERVICE_NAME: helloWorld + {%- endif %} + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Metrics"] == "enabled"%} + POWERTOOLS_METRICS_NAMESPACE: {{ cookiecutter.project_name|lower|replace(' ', '-') }} + {%- endif %} + {%- if cookiecutter["Powertools for AWS Lambda (TypeScript) Logging"] == "enabled"%} + LOG_LEVEL: INFO + {%- endif %} + {%- endif %} + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + Metadata: # Manage esbuild properties + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: "es2020" + # Sourcemap: true # Enabling source maps will create the required NODE_OPTIONS environment variables on your lambda function during sam build + EntryPoints: + - app.ts + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/nodejs22.x/hello-ts/.gitignore b/nodejs22.x/hello-ts/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/hello-ts/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/hello-ts/README.md b/nodejs22.x/hello-ts/README.md new file mode 100644 index 000000000..9eada2829 --- /dev/null +++ b/nodejs22.x/hello-ts/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS TypeScript Hello-world for SAM based Serverless App + +A cookiecutter template to create a NodeJS TypeScript Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/hello-ts/cookiecutter.json b/nodejs22.x/hello-ts/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/hello-ts/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/hello-ts/setup.cfg b/nodejs22.x/hello-ts/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/hello-ts/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..5854f05ec --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,207 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..26cd83248 --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,127 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello-world - Code for the application's Lambda function written in TypeScript. +- events - Invocation events that you can use to invoke the function. +- hello-world/tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI installs dependencies defined in `hello-world/package.json`, compiles TypeScript with esbuild, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `hello-world/tests` folder in this project. Use NPM to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +{{ cookiecutter.project_name }}$ cd hello-world +hello-world$ npm install +hello-world$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/events/event.json b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..070ad8e01 --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.eslintignore b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.eslintignore new file mode 100644 index 000000000..512d4cb8b --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.eslintignore @@ -0,0 +1,2 @@ +node_modules +.aws-sam \ No newline at end of file diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.eslintrc.js b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.eslintrc.js new file mode 100644 index 000000000..5da871fc4 --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: "module" + }, + extends: [ + "plugin:@typescript-eslint/recommended", // recommended rules from the @typescript-eslint/eslint-plugin + "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. + ], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + // e.g. "@typescript-eslint/explicit-function-return-type": "off", + } + }; \ No newline at end of file diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.npmignore b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.npmignore new file mode 100644 index 000000000..e7e1fb04f --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.prettierrc.js b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.prettierrc.js new file mode 100644 index 000000000..4c2c6c78b --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + trailingComma: "all", + singleQuote: true, + printWidth: 120, + tabWidth: 4 + }; \ No newline at end of file diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/app.ts b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/app.ts new file mode 100644 index 000000000..09939f747 --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/app.ts @@ -0,0 +1,30 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; + +/** + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {Object} event - API Gateway Lambda Proxy Input Format + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {Object} object - API Gateway Lambda Proxy Output Format + * + */ + +export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise => { + try { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world', + }), + }; + } catch (err) { + console.log(err); + return { + statusCode: 500, + body: JSON.stringify({ + message: 'some error happened', + }), + }; + } +}; diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/jest.config.ts b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/jest.config.ts new file mode 100644 index 000000000..3f8eb55bd --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/jest.config.ts @@ -0,0 +1,15 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +export default { + transform: { + '^.+\\.ts?$': 'ts-jest', + }, + clearMocks: true, + collectCoverage: true, + coverageDirectory: 'coverage', + coverageProvider: 'v8', + testMatch: ['**/tests/unit/*.test.ts'], +}; diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/package.json b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/package.json new file mode 100644 index 000000000..85f260ee5 --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/package.json @@ -0,0 +1,34 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "hello world sample for NodeJS", + "main": "app.js", + "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", + "author": "SAM CLI", + "license": "MIT", + "scripts": { + "unit": "jest", + "lint": "eslint '*.ts' --quiet --fix", + "compile": "tsc", + "test": "npm run compile && npm run unit" + }, + "dependencies": { + "esbuild": "^0.14.14" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92", + "@types/jest": "^29.2.0", + "@jest/globals": "^29.2.0", + "@types/node": "^20.5.7", + "@typescript-eslint/eslint-plugin": "^5.10.2", + "@typescript-eslint/parser": "^5.10.2", + "eslint": "^8.8.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "jest": "^29.2.1", + "prettier": "^2.5.1", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "typescript": "^4.8.4" + } +} diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.test.ts b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.test.ts new file mode 100644 index 000000000..f4501a905 --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.test.ts @@ -0,0 +1,65 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { lambdaHandler } from '../../app'; +import { expect, describe, it } from '@jest/globals'; + +describe('Unit test for app handler', function () { + it('verifies successful response', async () => { + const event: APIGatewayProxyEvent = { + httpMethod: 'get', + body: '', + headers: {}, + isBase64Encoded: false, + multiValueHeaders: {}, + multiValueQueryStringParameters: {}, + path: '/hello', + pathParameters: {}, + queryStringParameters: {}, + requestContext: { + accountId: '123456789012', + apiId: '1234', + authorizer: {}, + httpMethod: 'get', + identity: { + accessKey: '', + accountId: '', + apiKey: '', + apiKeyId: '', + caller: '', + clientCert: { + clientCertPem: '', + issuerDN: '', + serialNumber: '', + subjectDN: '', + validity: { notAfter: '', notBefore: '' }, + }, + cognitoAuthenticationProvider: '', + cognitoAuthenticationType: '', + cognitoIdentityId: '', + cognitoIdentityPoolId: '', + principalOrgId: '', + sourceIp: '', + user: '', + userAgent: '', + userArn: '', + }, + path: '/hello', + protocol: 'HTTP/1.1', + requestId: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef', + requestTimeEpoch: 1428582896000, + resourceId: '123456', + resourcePath: '/hello', + stage: 'dev', + }, + resource: '', + stageVariables: {}, + }; + const result: APIGatewayProxyResult = await lambdaHandler(event); + + expect(result.statusCode).toEqual(200); + expect(result.body).toEqual( + JSON.stringify({ + message: 'hello world', + }), + ); + }); +}); diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/tsconfig.json b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/tsconfig.json new file mode 100644 index 000000000..ffaf193ea --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/hello-world/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2020", + "strict": true, + "preserveConstEnums": true, + "noEmit": true, + "sourceMap": false, + "module":"es2015", + "moduleResolution":"node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + }, + "exclude": ["node_modules", "**/*.test.ts"] + } \ No newline at end of file diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..447d4e83a --- /dev/null +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,53 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: hello-world/ + Handler: app.lambdaHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + Metadata: # Manage esbuild properties + BuildMethod: esbuild + BuildProperties: + Minify: true + Target: "es2020" + Sourcemap: true + EntryPoints: + - app.ts + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/nodejs22.x/hello/.gitignore b/nodejs22.x/hello/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/hello/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/hello/README.md b/nodejs22.x/hello/README.md new file mode 100644 index 000000000..478057812 --- /dev/null +++ b/nodejs22.x/hello/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS Hello-world for SAM based Serverless App + +A cookiecutter template to create a NodeJS Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/hello/cookiecutter.json b/nodejs22.x/hello/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/hello/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/hello/setup.cfg b/nodejs22.x/hello/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/hello/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/hello/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/hello/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..5854f05ec --- /dev/null +++ b/nodejs22.x/hello/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,207 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/nodejs22.x/hello/{{cookiecutter.project_name}}/README.md b/nodejs22.x/hello/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..9b53d9910 --- /dev/null +++ b/nodejs22.x/hello/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,127 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. + +- hello-world - Code for the application's Lambda function. +- events - Invocation events that you can use to invoke the function. +- hello-world/tests - Unit tests for the application code. +- template.yaml - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the SAM CLI, you need the following tools. + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI installs dependencies defined in `hello-world/package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +{{ cookiecutter.project_name }}$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +{{ cookiecutter.project_name }}$ sam local start-api +{{ cookiecutter.project_name }}$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `hello-world/tests` folder in this project. Use NPM to install the [Mocha test framework](https://mochajs.org/) and run unit tests. + +```bash +{{ cookiecutter.project_name }}$ cd hello-world +hello-world$ npm install +hello-world$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/nodejs22.x/hello/{{cookiecutter.project_name}}/events/event.json b/nodejs22.x/hello/{{cookiecutter.project_name}}/events/event.json new file mode 100644 index 000000000..070ad8e01 --- /dev/null +++ b/nodejs22.x/hello/{{cookiecutter.project_name}}/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/.npmignore b/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/.npmignore new file mode 100644 index 000000000..e7e1fb04f --- /dev/null +++ b/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/app.mjs b/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/app.mjs new file mode 100644 index 000000000..a9de03111 --- /dev/null +++ b/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/app.mjs @@ -0,0 +1,24 @@ +/** + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {Object} event - API Gateway Lambda Proxy Input Format + * + * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html + * @param {Object} context + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {Object} object - API Gateway Lambda Proxy Output Format + * + */ + +export const lambdaHandler = async (event, context) => { + const response = { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world', + }) + }; + + return response; + }; + \ No newline at end of file diff --git a/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/package.json b/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/package.json new file mode 100644 index 000000000..0164adcf5 --- /dev/null +++ b/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/package.json @@ -0,0 +1,19 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "hello world sample for NodeJS", + "main": "app.js", + "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "axios": ">=1.6.0" + }, + "scripts": { + "test": "mocha tests/unit/" + }, + "devDependencies": { + "chai": "^4.3.6", + "mocha": "^10.2.0" + } +} diff --git a/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.mjs b/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.mjs new file mode 100644 index 000000000..02a66db27 --- /dev/null +++ b/nodejs22.x/hello/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.mjs @@ -0,0 +1,20 @@ +'use strict'; + +import { lambdaHandler } from '../../app.mjs'; +import { expect } from 'chai'; +var event, context; + +describe('Tests index', function () { + it('verifies successful response', async () => { + const result = await lambdaHandler(event, context) + + expect(result).to.be.an('object'); + expect(result.statusCode).to.equal(200); + expect(result.body).to.be.an('string'); + + let response = JSON.parse(result.body); + + expect(response).to.be.an('object'); + expect(response.message).to.be.equal("hello world"); + }); +}); diff --git a/nodejs22.x/hello/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/hello/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..3cbd2fc7b --- /dev/null +++ b/nodejs22.x/hello/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,45 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: hello-world/ + Handler: app.lambdaHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/nodejs22.x/response-streaming/.gitignore b/nodejs22.x/response-streaming/.gitignore new file mode 100644 index 000000000..41b1156d3 --- /dev/null +++ b/nodejs22.x/response-streaming/.gitignore @@ -0,0 +1,208 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/nodejs22.x/response-streaming/README.md b/nodejs22.x/response-streaming/README.md new file mode 100644 index 000000000..478057812 --- /dev/null +++ b/nodejs22.x/response-streaming/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS Hello-world for SAM based Serverless App + +A cookiecutter template to create a NodeJS Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/response-streaming/cookiecutter.json b/nodejs22.x/response-streaming/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/response-streaming/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/response-streaming/setup.cfg b/nodejs22.x/response-streaming/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/response-streaming/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..41b1156d3 --- /dev/null +++ b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,208 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/README.md b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..b6a5a687d --- /dev/null +++ b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,102 @@ +# {{cookiecutter.project_name}} + +This project contains source code and supporting files for a serverless application that you can deploy with the AWS Serverless Application Model (AWS SAM) command line interface (CLI). It includes the following files and folders: + +- `src` - Code for the application's Lambda function. +- `template.yaml` - A template that defines the application's AWS resources. +- `__tests__` - Unit tests for the application code. + +Resources for this project are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Building and testing + +Build your application by using the `sam build` command. + +```bash +my-application$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +my-application$ sam local invoke StreamingFunction --no-event +``` + +The streaming capabilities of the function are present only when invoked through a Function URL, so the response won't be streamed when invoking locally. To learn more, see the [Lambda response streaming documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html) + +There are also unit tests inside the `__tests__` directory, that can be run using `npm run test`. + + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, the AWS SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs that are generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +**NOTE:** This command works for all Lambda functions, not just the ones you deploy using AWS SAM. + +```bash +my-application$ sam logs -n StreamingFunction --stack-name sam-app --tail +``` + +**NOTE:** This uses the logical name of the function within the stack. This is the correct name to use when searching logs inside an AWS Lambda function within a CloudFormation stack, even if the deployed function name varies due to CloudFormation's unique resource name generation. + +You can find more information and examples about filtering Lambda function logs in the [AWS SAM CLI documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete +``` + +## Resources + +For an introduction to the AWS SAM specification, the AWS SAM CLI, and serverless application concepts, see the [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). + +Next, you can use the AWS Serverless Application Repository to deploy ready-to-use apps that go beyond Hello World samples and learn how authors developed their applications. For more information, see the [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) and the [AWS Serverless Application Repository Developer Guide](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). diff --git a/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/__tests__/unit/index.test.js b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/__tests__/unit/index.test.js new file mode 100644 index 000000000..134a63ca0 --- /dev/null +++ b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/__tests__/unit/index.test.js @@ -0,0 +1,41 @@ +// `awslambda` object doesn't exist outside of the Lambda runtime, so we mock its definitions here +awslambda = { + streamifyResponse: _streamifyResponseMock, + HttpResponseStream: { from: _responseStreamMock }, +}; + +const { handler } = require('../../src/index.js'); + +// This includes all tests for handler() +describe('Test for streaming handler', function () { + // This test invokes handler() and compare the values streamed + it('Verifies successful response', async () => { + const responseStream = { + setContentType: jest.fn(), + write: jest.fn(), + end: jest.fn(), + } + // Invoke handler() + await handler({}, responseStream); + + // Check some of the text that was streamed from your Lambda function. + const expectedResults = ['', '

First write!

', '']; + // Compare the result with the expected result + expect(responseStream.write).toHaveBeenCalledWith(expectedResults[0]); + expect(responseStream.write).toHaveBeenCalledWith(expectedResults[1]); + expect(responseStream.write).toHaveBeenLastCalledWith(expectedResults[2]); + expect(responseStream.end).toHaveBeenCalledTimes(1); + }); +}); + +function _streamifyResponseMock(lambdaHandler) { + return lambdaHandler; +} + +function _responseStreamMock(responseStream, httpResponseMetadata){ + responseStream.setContentType("vnd.awslambda.http-integration-response"); + responseStream.write(httpResponseMetadata); + // Separator between metadata and the body + responseStream.write("\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"); + return responseStream; +} \ No newline at end of file diff --git a/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/package.json b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/package.json new file mode 100644 index 000000000..e8ce5fb99 --- /dev/null +++ b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/package.json @@ -0,0 +1,24 @@ +{ + "name": "streaming-function", + "description": "Sample project for Lambda streaming invoke", + "version": "0.0.1", + "private": true, + "devDependencies": { + "jest": "^29.2.1" + }, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" + }, + "jest": { + "testMatch": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)", + "**/__tests__/**/*.mjs?(x)", + "**/?(*.)+(spec|test).mjs?(x)" + ], + "moduleFileExtensions": [ + "mjs", + "js" + ] + } +} diff --git a/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/src/index.js b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/src/index.js new file mode 100644 index 000000000..71ae27051 --- /dev/null +++ b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/src/index.js @@ -0,0 +1,33 @@ +exports.handler = awslambda.streamifyResponse( + async (event, responseStream, context) => { + const httpResponseMetadata = { + statusCode: 200, + headers: { + "Content-Type": "text/html", + "X-Custom-Header": "Example-Custom-Header" + } + }; + + responseStream = awslambda.HttpResponseStream.from(responseStream, httpResponseMetadata); + // It's recommended to use a `pipeline` over the `write` method for more complex use cases. + // Learn more: https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html + responseStream.write(""); + responseStream.write("

First write!

"); + + responseStream.write("

Streaming h1

"); + await new Promise(r => setTimeout(r, 1000)); + responseStream.write("

Streaming h2

"); + await new Promise(r => setTimeout(r, 1000)); + responseStream.write("

Streaming h3

"); + await new Promise(r => setTimeout(r, 1000)); + + // Long strings will be streamed + const loremIpsum1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque vitae mi tincidunt tellus ultricies dignissim id et diam. Morbi pharetra eu nisi et finibus. Vivamus diam nulla, vulputate et nisl cursus, pellentesque vehicula libero. Cras imperdiet lorem ante, non posuere dolor sollicitudin a. Vestibulum ipsum lacus, blandit nec augue id, lobortis dictum urna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Morbi auctor orci eget tellus aliquam, non maximus massa porta. In diam ante, pulvinar aliquam nisl non, elementum hendrerit sapien. Vestibulum massa nunc, mattis non congue vitae, placerat in quam. Nam vulputate lectus metus, et dignissim erat varius a."; + responseStream.write(`

${loremIpsum1}

`); + await new Promise(r => setTimeout(r, 1000)); + + responseStream.write("

DONE!

"); + responseStream.write(""); + responseStream.end(); + } +); diff --git a/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..2da419c02 --- /dev/null +++ b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,33 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Description: > + Sample SAM Template for {{ cookiecutter.project_name }} + +Resources: + StreamingFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: src/ + Handler: index.handler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Timeout: 10 + FunctionUrlConfig: + AuthType: AWS_IAM + InvokeMode: RESPONSE_STREAM + +Outputs: + StreamingFunction: + Description: "Streaming Lambda Function ARN" + Value: !GetAtt StreamingFunction.Arn + StreamingFunctionURL: + Description: "Streaming Lambda Function URL" + Value: !GetAtt StreamingFunctionUrl.FunctionUrl + + diff --git a/nodejs22.x/s3/.gitignore b/nodejs22.x/s3/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/s3/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/s3/README.md b/nodejs22.x/s3/README.md new file mode 100644 index 000000000..255dbbee9 --- /dev/null +++ b/nodejs22.x/s3/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS S3 Quick Start Application + +A cookiecutter template to create a NodeJS S3 Quick Start Application using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-s3 --name s3-app` + +> **NOTE**: ``--name`` allows you to specify a different project folder name + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/s3/cookiecutter.json b/nodejs22.x/s3/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/s3/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/s3/setup.cfg b/nodejs22.x/s3/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/s3/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/s3/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/s3/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..2d980704f --- /dev/null +++ b/nodejs22.x/s3/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,2 @@ +.aws-sam +node_modules \ No newline at end of file diff --git a/nodejs22.x/s3/{{cookiecutter.project_name}}/README.md b/nodejs22.x/s3/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..c91f8acba --- /dev/null +++ b/nodejs22.x/s3/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,151 @@ +# {{cookiecutter.project_name}} + +This project contains source code and supporting files for a serverless application that you can deploy with the AWS Serverless Application Model (AWS SAM) command line interface (CLI). It includes the following files and folders: + +- `src` - Code for the application's Lambda function. +- `events` - Invocation events that you can use to invoke the function. +- `__tests__` - Unit tests for the application code. +- `template.yaml` - A template that defines the application's AWS resources. + +Resources for this project are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Parameter AppBucketName**: This template includes a parameter to name the S3 bucket you will create as a part of the new application. This name needs to be globally unique. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Use the AWS SAM CLI to build and test locally + +Build your application by using the `sam build` command. + +```bash +my-application$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +my-application$ sam local invoke S3JsonLoggerFunction --event events/event-s3.json +``` + +## Add a resource to your application + +The application template uses AWS SAM to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources, such as functions, triggers, and APIs. For resources that aren't included in the [AWS SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use the standard [AWS CloudFormation resource types](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html). + +Update `template.yaml` to add a dead-letter queue to your application. In the **Resources** section, add a resource named **MyQueue** with the type **AWS::SQS::Queue**. Then add a property to the **AWS::Serverless::Function** resource named **DeadLetterQueue** that targets the queue's Amazon Resource Name (ARN), and a policy that grants the function permission to access the queue. + +``` +Resources: + MyQueue: + Type: AWS::SQS::Queue + S3JsonLoggerFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/s3-json-logger.s3JsonLoggerHandler + Runtime: nodejs22.x + DeadLetterQueue: + Type: SQS + TargetArn: !GetAtt MyQueue.Arn + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt MyQueue.QueueName + - S3NewObjectEvent: + Type: S3 + Properties: + Bucket: !Ref AppBucket + Events: s3:ObjectCreated:* + Filter: + S3Key: + Rules: + - Name: suffix + Value: ".json" +``` + +The dead-letter queue is a location for Lambda to send events that could not be processed. It's only used if you invoke your function asynchronously, but it's useful here to show how you can modify your application's resources and function configuration. + +Deploy the updated application. + +```bash +my-application$ sam deploy +``` + +Open the [**Applications**](https://console.aws.amazon.com/lambda/home#/applications) page of the Lambda console, and choose your application. When the deployment completes, view the application resources on the **Overview** tab to see the new resource. Then, choose the function to see the updated configuration that specifies the dead-letter queue. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, the AWS SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs that are generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +**NOTE:** This command works for all Lambda functions, not just the ones you deploy using AWS SAM. + +```bash +my-application$ sam logs -n S3JsonLoggerFunction --stack-name sam-app --tail +``` + +**NOTE:** This uses the logical name of the function within the stack. This is the correct name to use when searching logs inside an AWS Lambda function within a CloudFormation stack, even if the deployed function name varies due to CloudFormation's unique resource name generation. + +You can find more information and examples about filtering Lambda function logs in the [AWS SAM CLI documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `__tests__` folder in this project. Use `npm` to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +my-application$ npm install +my-application$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +For an introduction to the AWS SAM specification, the AWS SAM CLI, and serverless application concepts, see the [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). + +Next, you can use the AWS Serverless Application Repository to deploy ready-to-use apps that go beyond Hello World samples and learn how authors developed their applications. For more information, see the [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) and the [AWS Serverless Application Repository Developer Guide](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). diff --git a/nodejs22.x/s3/{{cookiecutter.project_name}}/__tests__/unit/handlers/s3-json-logger-handler.test.mjs b/nodejs22.x/s3/{{cookiecutter.project_name}}/__tests__/unit/handlers/s3-json-logger-handler.test.mjs new file mode 100644 index 000000000..4590eaf34 --- /dev/null +++ b/nodejs22.x/s3/{{cookiecutter.project_name}}/__tests__/unit/handlers/s3-json-logger-handler.test.mjs @@ -0,0 +1,52 @@ +import { mockClient } from 'aws-sdk-client-mock'; +import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; +import { s3JsonLoggerHandler } from '../../../src/handlers/s3-json-logger.mjs'; +import { jest } from '@jest/globals'; +import { Readable } from 'stream'; + +describe('Test s3JsonLoggerHandler', () => { + const s3Mock = mockClient(S3Client); + + beforeEach(() => { + s3Mock.reset(); + }); + + it('should read and log S3 objects', async () => { + const objectBody = '{"Test": "PASS"}'; + const getObjectResp = { + data: objectBody + }; + + const s3ResponseStream = new Readable({ + read() {} + }); + + s3ResponseStream.push(objectBody); + s3ResponseStream.push(null); + + const event = { + Records: [ + { + s3: { + bucket: { + name: "test-bucket" + }, + object: { + key: "test-key" + } + } + } + ] + } + + s3Mock.on(GetObjectCommand).resolves({ + Body: s3ResponseStream + }); + + console.info = jest.fn() + + await s3JsonLoggerHandler(event, null); + + expect(console.info).toHaveBeenCalledWith(objectBody); + }); +}); diff --git a/nodejs22.x/s3/{{cookiecutter.project_name}}/buildspec.yml b/nodejs22.x/s3/{{cookiecutter.project_name}}/buildspec.yml new file mode 100644 index 000000000..7c1dae85e --- /dev/null +++ b/nodejs22.x/s3/{{cookiecutter.project_name}}/buildspec.yml @@ -0,0 +1,22 @@ +version: 0.2 +phases: + install: + commands: + # Install all dependencies (including dependencies for running tests) + - npm install + pre_build: + commands: + # Discover and run unit tests in the '__tests__' directory + - npm run test + # Remove all unit tests to reduce the size of the package that will be ultimately uploaded to Lambda + - rm -rf ./__tests__ + # Remove all dependencies not needed for the Lambda deployment package (the packages from devDependencies in package.json) + - npm prune --production + build: + commands: + # Use AWS SAM to package the application by using AWS CloudFormation + - aws cloudformation package --template template.yaml --s3-bucket $S3_BUCKET --output-template template-export.yml +artifacts: + type: zip + files: + - template-export.yml diff --git a/nodejs22.x/s3/{{cookiecutter.project_name}}/events/event-s3.json b/nodejs22.x/s3/{{cookiecutter.project_name}}/events/event-s3.json new file mode 100644 index 000000000..b13b216d2 --- /dev/null +++ b/nodejs22.x/s3/{{cookiecutter.project_name}}/events/event-s3.json @@ -0,0 +1,38 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "1970-01-01T00:00:00.000Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "EXAMPLE123456789", + "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "example-bucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::example-bucket" + }, + "object": { + "key": "test/key", + "size": 1024, + "eTag": "0123456789abcdef0123456789abcdef", + "sequencer": "0A1B2C3D4E5F678901" + } + } + } + ] +} diff --git a/nodejs22.x/s3/{{cookiecutter.project_name}}/package.json b/nodejs22.x/s3/{{cookiecutter.project_name}}/package.json new file mode 100644 index 000000000..52d84b4e0 --- /dev/null +++ b/nodejs22.x/s3/{{cookiecutter.project_name}}/package.json @@ -0,0 +1,29 @@ +{ + "name": "replaced-by-user-input", + "description": "replaced-by-user-input", + "version": "0.0.1", + "jest": { + "testMatch": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)", + "**/__tests__/**/*.mjs?(x)", + "**/?(*.)+(spec|test).mjs?(x)" + ], + "moduleFileExtensions": [ + "mjs", + "js" + ] + }, + "private": true, + "dependencies": { + "@aws-sdk/client-s3": "^3.400.0", + "@aws-sdk/util-stream-node": "^3.374.0" + }, + "devDependencies": { + "aws-sdk-client-mock": "^2.0.0", + "jest": "^29.2.1" + }, + "scripts": { + "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js" + } +} diff --git a/nodejs22.x/s3/{{cookiecutter.project_name}}/src/handlers/s3-json-logger.mjs b/nodejs22.x/s3/{{cookiecutter.project_name}}/src/handlers/s3-json-logger.mjs new file mode 100644 index 000000000..a30815631 --- /dev/null +++ b/nodejs22.x/s3/{{cookiecutter.project_name}}/src/handlers/s3-json-logger.mjs @@ -0,0 +1,39 @@ +import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; +import { sdkStreamMixin } from '@aws-sdk/util-stream-node'; + +const s3 = new S3Client({ }); + +/** + * A Lambda function that logs the payload received from S3. + */ +export const s3JsonLoggerHandler = async (event, context) => { + // All log statements are written to CloudWatch by default. For more information, see + // https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-logging.html + const getObjectRequests = event.Records.map(record => { + const params = { + Bucket: record.s3.bucket.name, + Key: record.s3.object.key + }; + + const getObject = new GetObjectCommand(params); + + return s3.send(getObject).then((data) => + sdkStreamMixin(data.Body).transformToString().then((objectString) => { + console.info(objectString); + return Promise.resolve(objectString); + }) + .catch((err) => { + console.error("Error consuming response stream:", err); + return Promise.reject(err); + }) + ) + .catch((err) => { + console.error("Error calling S3 getObject:", err); + return Promise.reject(err); + }) + }); + + return Promise.all(getObjectRequests).then(() => { + console.debug('Complete!'); + }); +}; \ No newline at end of file diff --git a/nodejs22.x/s3/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/s3/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..bef1b9f84 --- /dev/null +++ b/nodejs22.x/s3/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,47 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{cookiecutter.project_name}} + +Parameters: + AppBucketName: + Type: String + Description: "REQUIRED: Unique S3 bucket name to use for the app." + +Resources: + S3JsonLoggerFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/s3-json-logger.s3JsonLoggerHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + MemorySize: 128 + Timeout: 60 + Policies: + S3ReadPolicy: + BucketName: !Ref AppBucketName + Events: + S3NewObjectEvent: + Type: S3 + Properties: + Bucket: !Ref AppBucket + Events: s3:ObjectCreated:* + Filter: + S3Key: + Rules: + - Name: suffix + Value: ".json" + AppBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Ref AppBucketName + BucketEncryption: + ServerSideEncryptionConfiguration: + - BucketKeyEnabled: true + VersioningConfiguration: + Status: Enabled diff --git a/nodejs22.x/scratch/.gitignore b/nodejs22.x/scratch/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/scratch/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/scratch/README.md b/nodejs22.x/scratch/README.md new file mode 100644 index 000000000..f6caedd36 --- /dev/null +++ b/nodejs22.x/scratch/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS "From Scratch" Quick Start Application + +A cookiecutter template to create a NodeJS Basic Quick Start Application using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-from-scratch --name sam-app` + +> **NOTE**: ``--name`` allows you to specify a different project folder name + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/scratch/cookiecutter.json b/nodejs22.x/scratch/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/scratch/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/scratch/setup.cfg b/nodejs22.x/scratch/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/scratch/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/scratch/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/scratch/{{cookiecutter.project_name}}/.gitignore new file mode 100755 index 000000000..b512c09d4 --- /dev/null +++ b/nodejs22.x/scratch/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md b/nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..d6bd097b3 --- /dev/null +++ b/nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,140 @@ +# {{cookiecutter.project_name}} + +This project contains source code and supporting files for a serverless application that you can deploy with the AWS Serverless Application Model (AWS SAM) command line interface (CLI). It includes the following files and folders: + +- `src` - Code for the application's Lambda function. +- `events` - Invocation events that you can use to invoke the function. +- `__tests__` - Unit tests for the application code. +- `template.yaml` - A template that defines the application's AWS resources. + +Resources for this project are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Use the AWS SAM CLI to build and test locally + +Build your application by using the `sam build` command. + +```bash +my-application$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +my-application$ sam local invoke helloFromLambdaFunction --no-event +``` + +## Add a resource to your application + +The application template uses AWS SAM to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources, such as functions, triggers, and APIs. For resources that aren't included in the [AWS SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use the standard [AWS CloudFormation resource types](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html). + +Update `template.yaml` to add a dead-letter queue to your application. In the **Resources** section, add a resource named **MyQueue** with the type **AWS::SQS::Queue**. Then add a property to the **AWS::Serverless::Function** resource named **DeadLetterQueue** that targets the queue's Amazon Resource Name (ARN), and a policy that grants the function permission to access the queue. + +``` +Resources: + MyQueue: + Type: AWS::SQS::Queue + helloFromLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/hello-from-lambda.helloFromLambdaHandler + Runtime: nodejs22.x + DeadLetterQueue: + Type: SQS + TargetArn: !GetAtt MyQueue.Arn + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt MyQueue.QueueName +``` + +The dead-letter queue is a location for Lambda to send events that could not be processed. It's only used if you invoke your function asynchronously, but it's useful here to show how you can modify your application's resources and function configuration. + +Deploy the updated application. + +```bash +my-application$ sam deploy +``` + +Open the [**Applications**](https://console.aws.amazon.com/lambda/home#/applications) page of the Lambda console, and choose your application. When the deployment completes, view the application resources on the **Overview** tab to see the new resource. Then, choose the function to see the updated configuration that specifies the dead-letter queue. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, the AWS SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs that are generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +**NOTE:** This command works for all Lambda functions, not just the ones you deploy using AWS SAM. + +```bash +my-application$ sam logs -n helloFromLambdaFunction --stack-name sam-app --tail +``` + +**NOTE:** This uses the logical name of the function within the stack. This is the correct name to use when searching logs inside an AWS Lambda function within a CloudFormation stack, even if the deployed function name varies due to CloudFormation's unique resource name generation. + +You can find more information and examples about filtering Lambda function logs in the [AWS SAM CLI documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `__tests__` folder in this project. Use `npm` to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +my-application$ npm install +my-application$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +For an introduction to the AWS SAM specification, the AWS SAM CLI, and serverless application concepts, see the [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). + +Next, you can use the AWS Serverless Application Repository to deploy ready-to-use apps that go beyond Hello World samples and learn how authors developed their applications. For more information, see the [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) and the [AWS Serverless Application Repository Developer Guide](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). diff --git a/nodejs22.x/scratch/{{cookiecutter.project_name}}/__tests__/unit/handlers/hello-from-lambda.test.mjs b/nodejs22.x/scratch/{{cookiecutter.project_name}}/__tests__/unit/handlers/hello-from-lambda.test.mjs new file mode 100644 index 000000000..76d12cc51 --- /dev/null +++ b/nodejs22.x/scratch/{{cookiecutter.project_name}}/__tests__/unit/handlers/hello-from-lambda.test.mjs @@ -0,0 +1,20 @@ +// Import helloFromLambdaHandler function from hello-from-lambda.mjs +import { helloFromLambdaHandler } from '../../../src/handlers/hello-from-lambda.mjs'; + +// This includes all tests for helloFromLambdaHandler() +describe('Test for hello-from-lambda', function () { + // This test invokes helloFromLambdaHandler() and compare the result + it('Verifies successful response', async () => { + // Invoke helloFromLambdaHandler() + const result = await helloFromLambdaHandler(); + /* + The expected result should match the return from your Lambda function. + e.g. + if you change from `const message = 'Hello from Lambda!';` to `const message = 'Hello World!';` in hello-from-lambda.mjs + you should change the following line to `const expectedResult = 'Hello World!';` + */ + const expectedResult = 'Hello from Lambda!'; + // Compare the result with the expected result + expect(result).toEqual(expectedResult); + }); +}); diff --git a/nodejs22.x/scratch/{{cookiecutter.project_name}}/buildspec.yml b/nodejs22.x/scratch/{{cookiecutter.project_name}}/buildspec.yml new file mode 100755 index 000000000..86e7a9ebf --- /dev/null +++ b/nodejs22.x/scratch/{{cookiecutter.project_name}}/buildspec.yml @@ -0,0 +1,25 @@ +version: 0.2 + +phases: + install: + commands: + # Install all dependencies (including dependencies for running tests) + - npm install + pre_build: + commands: + # Discover and run unit tests in the '__tests__' directory + - npm run test + # Remove all unit tests to reduce the size of the package that will be ultimately uploaded to Lambda + - rm -rf ./__tests__ + # Remove all dependencies not needed for the Lambda deployment package (the packages from devDependencies in package.json) + - npm prune --production + build: + commands: + # Use AWS SAM to package the application by using AWS CloudFormation + - aws cloudformation package --template template.yaml --s3-bucket $S3_BUCKET --output-template template-export.yml +artifacts: + type: zip + files: + - template-export.yml + + diff --git a/nodejs22.x/scratch/{{cookiecutter.project_name}}/package.json b/nodejs22.x/scratch/{{cookiecutter.project_name}}/package.json new file mode 100755 index 000000000..64d1cf5eb --- /dev/null +++ b/nodejs22.x/scratch/{{cookiecutter.project_name}}/package.json @@ -0,0 +1,24 @@ +{ + "name": "replaced-by-user-input", + "description": "replaced-by-user-input", + "version": "0.0.1", + "private": true, + "devDependencies": { + "jest": "^29.2.1" + }, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" + }, + "jest": { + "testMatch": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)", + "**/__tests__/**/*.mjs?(x)", + "**/?(*.)+(spec|test).mjs?(x)" + ], + "moduleFileExtensions": [ + "mjs", + "js" + ] + } +} diff --git a/nodejs22.x/scratch/{{cookiecutter.project_name}}/src/handlers/hello-from-lambda.mjs b/nodejs22.x/scratch/{{cookiecutter.project_name}}/src/handlers/hello-from-lambda.mjs new file mode 100755 index 000000000..0fb1c6b40 --- /dev/null +++ b/nodejs22.x/scratch/{{cookiecutter.project_name}}/src/handlers/hello-from-lambda.mjs @@ -0,0 +1,12 @@ +/** + * A Lambda function that returns a static string + */ +export const helloFromLambdaHandler = async () => { + // If you change this message, you will need to change hello-from-lambda.test.mjs + const message = 'Hello from Lambda!'; + + // All log statements are written to CloudWatch + console.info(`${message}`); + + return message; +} diff --git a/nodejs22.x/scratch/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/scratch/{{cookiecutter.project_name}}/template.yaml new file mode 100755 index 000000000..d12efbca5 --- /dev/null +++ b/nodejs22.x/scratch/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,38 @@ +# This is the SAM template that represents the architecture of your serverless application +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html + +# The AWSTemplateFormatVersion identifies the capabilities of the template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + {{cookiecutter.project_name}} + +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: +- AWS::Serverless-2016-10-31 + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: hello-from-lambda.js + helloFromLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/hello-from-lambda.helloFromLambdaHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + MemorySize: 128 + Timeout: 100 + Description: A Lambda function that returns a static string. + Policies: + # Give Lambda basic execution Permission to the helloFromLambda + - AWSLambdaBasicExecutionRole diff --git a/nodejs22.x/sns/.gitignore b/nodejs22.x/sns/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/sns/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/sns/README.md b/nodejs22.x/sns/README.md new file mode 100644 index 000000000..249b36007 --- /dev/null +++ b/nodejs22.x/sns/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS SNS Quick Start Application + +A cookiecutter template to create a NodeJS SNS Quick Start Application using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-sns --name sns-app` + +> **NOTE**: ``--name`` allows you to specify a different project folder name + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/sns/cookiecutter.json b/nodejs22.x/sns/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/sns/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/sns/setup.cfg b/nodejs22.x/sns/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/sns/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/sns/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/sns/{{cookiecutter.project_name}}/.gitignore new file mode 100755 index 000000000..b512c09d4 --- /dev/null +++ b/nodejs22.x/sns/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/nodejs22.x/sns/{{cookiecutter.project_name}}/README.md b/nodejs22.x/sns/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..3424d48b9 --- /dev/null +++ b/nodejs22.x/sns/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,146 @@ +# {{cookiecutter.project_name}} + +This project contains source code and supporting files for a serverless application that you can deploy with the AWS Serverless Application Model (AWS SAM) command line interface (CLI). It includes the following files and folders: + +- `src` - Code for the application's Lambda function. +- `events` - Invocation events that you can use to invoke the function. +- `__tests__` - Unit tests for the application code. +- `template.yaml` - A template that defines the application's AWS resources. + +Resources for this project are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Use the AWS SAM CLI to build and test locally + +Build your application by using the `sam build` command. + +```bash +my-application$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +my-application$ sam local invoke SNSPayloadLogger --event events/event-sns.json +``` + +## Add a resource to your application + +The application template uses AWS SAM to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources, such as functions, triggers, and APIs. For resources that aren't included in the [AWS SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use the standard [AWS CloudFormation resource types](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html). + +Update `template.yaml` to add a dead-letter queue to your application. In the **Resources** section, add a resource named **MyQueue** with the type **AWS::SQS::Queue**. Then add a property to the **AWS::Serverless::Function** resource named **DeadLetterQueue** that targets the queue's Amazon Resource Name (ARN), and a policy that grants the function permission to access the queue. + +``` +Resources: + MyQueue: + Type: AWS::SQS::Queue + SNSPayloadLogger: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/sns-payload-logger.snsPayloadLoggerHandler + Runtime: nodejs22.x + DeadLetterQueue: + Type: SQS + TargetArn: !GetAtt MyQueue.Arn + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt MyQueue.QueueName +``` + +The dead-letter queue is a location for Lambda to send events that could not be processed. It's only used if you invoke your function asynchronously, but it's useful here to show how you can modify your application's resources and function configuration. + +Deploy the updated application. + +```bash +my-application$ sam package \ + --output-template-file packaged.yaml \ + --s3-bucket BUCKET_NAME +my-application$ sam deploy \ + --template-file packaged.yaml \ + --stack-name sam-app \ + --capabilities CAPABILITY_IAM +``` + +Open the [**Applications**](https://console.aws.amazon.com/lambda/home#/applications) page of the Lambda console, and choose your application. When the deployment completes, view the application resources on the **Overview** tab to see the new resource. Then, choose the function to see the updated configuration that specifies the dead-letter queue. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, the AWS SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs that are generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +**NOTE:** This command works for all Lambda functions, not just the ones you deploy using AWS SAM. + +```bash +my-application$ sam logs -n SNSPayloadLogger --stack-name sam-app --tail +``` + +**NOTE:** This uses the logical name of the function within the stack. This is the correct name to use when searching logs inside an AWS Lambda function within a CloudFormation stack, even if the deployed function name varies due to CloudFormation's unique resource name generation. + +You can find more information and examples about filtering Lambda function logs in the [AWS SAM CLI documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `__tests__` folder in this project. Use `npm` to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +my-application$ npm install +my-application$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +For an introduction to the AWS SAM specification, the AWS SAM CLI, and serverless application concepts, see the [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). + +Next, you can use the AWS Serverless Application Repository to deploy ready-to-use apps that go beyond Hello World samples and learn how authors developed their applications. For more information, see the [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) and the [AWS Serverless Application Repository Developer Guide](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). diff --git a/nodejs22.x/sns/{{cookiecutter.project_name}}/__tests__/unit/handlers/sns-payload-logger.test.mjs b/nodejs22.x/sns/{{cookiecutter.project_name}}/__tests__/unit/handlers/sns-payload-logger.test.mjs new file mode 100644 index 000000000..2d864ae5d --- /dev/null +++ b/nodejs22.x/sns/{{cookiecutter.project_name}}/__tests__/unit/handlers/sns-payload-logger.test.mjs @@ -0,0 +1,24 @@ +// Import snsPayloadLoggerHandler function from sns-payload-logger.mjs +import { snsPayloadLoggerHandler } from '../../../src/handlers/sns-payload-logger.mjs'; +import { jest } from '@jest/globals' + +describe('Test for sns-payload-logger', function () { + // This test invokes the sns-payload-logger Lambda function and verifies that the received payload is logged + it('Verifies the payload is logged', async() => { + // Mock console.log statements so we can verify them. For more information, see + // https://jestjs.io/docs/en/mock-functions.html + console.info = jest.fn() + + // Create a sample payload with SNS message format + var payload = { + TopicArn: "arn:aws:sns:us-west-2:123456789012:SimpleTopic", + Message: "This is a notification from SNS", + Subject: "SNS Notification" + } + + await snsPayloadLoggerHandler(payload, null) + + // Verify that console.info has been called with the expected payload + expect(console.info).toHaveBeenCalledWith(payload) + }); +}); diff --git a/nodejs22.x/sns/{{cookiecutter.project_name}}/buildspec.yml b/nodejs22.x/sns/{{cookiecutter.project_name}}/buildspec.yml new file mode 100755 index 000000000..86e7a9ebf --- /dev/null +++ b/nodejs22.x/sns/{{cookiecutter.project_name}}/buildspec.yml @@ -0,0 +1,25 @@ +version: 0.2 + +phases: + install: + commands: + # Install all dependencies (including dependencies for running tests) + - npm install + pre_build: + commands: + # Discover and run unit tests in the '__tests__' directory + - npm run test + # Remove all unit tests to reduce the size of the package that will be ultimately uploaded to Lambda + - rm -rf ./__tests__ + # Remove all dependencies not needed for the Lambda deployment package (the packages from devDependencies in package.json) + - npm prune --production + build: + commands: + # Use AWS SAM to package the application by using AWS CloudFormation + - aws cloudformation package --template template.yaml --s3-bucket $S3_BUCKET --output-template template-export.yml +artifacts: + type: zip + files: + - template-export.yml + + diff --git a/nodejs22.x/sns/{{cookiecutter.project_name}}/events/event-sns.json b/nodejs22.x/sns/{{cookiecutter.project_name}}/events/event-sns.json new file mode 100644 index 000000000..baa02e78b --- /dev/null +++ b/nodejs22.x/sns/{{cookiecutter.project_name}}/events/event-sns.json @@ -0,0 +1,5 @@ +{ + "TopicArn": "arn:aws:sns:us-west-2:123456789012:SimpleTopic", + "Message": "This is a notification from SNS", + "Subject": "SNS Notification" +} \ No newline at end of file diff --git a/nodejs22.x/sns/{{cookiecutter.project_name}}/package.json b/nodejs22.x/sns/{{cookiecutter.project_name}}/package.json new file mode 100755 index 000000000..64d1cf5eb --- /dev/null +++ b/nodejs22.x/sns/{{cookiecutter.project_name}}/package.json @@ -0,0 +1,24 @@ +{ + "name": "replaced-by-user-input", + "description": "replaced-by-user-input", + "version": "0.0.1", + "private": true, + "devDependencies": { + "jest": "^29.2.1" + }, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" + }, + "jest": { + "testMatch": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)", + "**/__tests__/**/*.mjs?(x)", + "**/?(*.)+(spec|test).mjs?(x)" + ], + "moduleFileExtensions": [ + "mjs", + "js" + ] + } +} diff --git a/nodejs22.x/sns/{{cookiecutter.project_name}}/src/handlers/sns-payload-logger.mjs b/nodejs22.x/sns/{{cookiecutter.project_name}}/src/handlers/sns-payload-logger.mjs new file mode 100755 index 000000000..fe1bfbb55 --- /dev/null +++ b/nodejs22.x/sns/{{cookiecutter.project_name}}/src/handlers/sns-payload-logger.mjs @@ -0,0 +1,8 @@ +/** + * A Lambda function that logs the payload received from SNS. + */ +export const snsPayloadLoggerHandler = async (event, context) => { + // All log statements are written to CloudWatch by default. For more information, see + // https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-logging.html + console.info(event); +} diff --git a/nodejs22.x/sns/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/sns/{{cookiecutter.project_name}}/template.yaml new file mode 100755 index 000000000..210103834 --- /dev/null +++ b/nodejs22.x/sns/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,48 @@ +# This is the SAM template that represents the architecture of your serverless application +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html + +# The AWSTemplateFormatVersion identifies the capabilities of the template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + {{cookiecutter.project_name}} + +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: +- AWS::Serverless-2016-10-31 + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + # This is an SNS Topic with all default configuration properties. To learn more about the available options, see + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html + SimpleTopic: + Type: AWS::SNS::Topic + + # This is the Lambda function definition associated with the source code: sns-payload-logger.js. For all available properties, see + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + SNSPayloadLogger: + Type: AWS::Serverless::Function + Properties: + Description: A Lambda function that logs the payload of messages sent to an associated SNS topic. + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Handler: src/handlers/sns-payload-logger.snsPayloadLoggerHandler + # This property associates this Lambda function with the SNS topic defined above, so that whenever the topic + # receives a message, the Lambda function is invoked + Events: + SNSTopicEvent: + Type: SNS + Properties: + Topic: !Ref SimpleTopic + MemorySize: 128 + Timeout: 100 + Policies: + # Give Lambda basic execution Permission to the helloFromLambda + - AWSLambdaBasicExecutionRole diff --git a/nodejs22.x/sqs/.gitignore b/nodejs22.x/sqs/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/sqs/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/sqs/README.md b/nodejs22.x/sqs/README.md new file mode 100644 index 000000000..890e00190 --- /dev/null +++ b/nodejs22.x/sqs/README.md @@ -0,0 +1,20 @@ +# {{cookiecutter.project_name}} + +A cookiecutter template to create a NodeJS SQS Quick Start Application using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-sqs --name sqs-app` + +> **NOTE**: ``--name`` allows you to specify a different project folder name + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/sqs/cookiecutter.json b/nodejs22.x/sqs/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/sqs/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/sqs/setup.cfg b/nodejs22.x/sqs/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/sqs/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/sqs/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/sqs/{{cookiecutter.project_name}}/.gitignore new file mode 100755 index 000000000..b512c09d4 --- /dev/null +++ b/nodejs22.x/sqs/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md b/nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..0d9374a96 --- /dev/null +++ b/nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,141 @@ +# {{cookiecutter.project_name}} + +This project contains source code and supporting files for a serverless application that you can deploy with the AWS Serverless Application Model (AWS SAM) command line interface (CLI). It includes the following files and folders: + +- `src` - Code for the application's Lambda function. +- `events` - Invocation events that you can use to invoke the function. +- `__tests__` - Unit tests for the application code. +- `template.yaml` - A template that defines the application's AWS resources. + +Resources for this project are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +## Use the AWS SAM CLI to build and test locally + +Build your application by using the `sam build` command. + +```bash +my-application$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +my-application$ sam local invoke SQSPayloadLogger --event events/event-sqs.json +``` + +## Add a resource to your application + +The application template uses AWS SAM to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources, such as functions, triggers, and APIs. For resources that aren't included in the [AWS SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use the standard [AWS CloudFormation resource types](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html). + +Update `template.yaml` to add a dead-letter queue to your application. In the **Resources** section, add a resource named **MyQueue** with the type **AWS::SQS::Queue**. Then add a property to the **AWS::Serverless::Function** resource named **DeadLetterQueue** that targets the queue's Amazon Resource Name (ARN), and a policy that grants the function permission to access the queue. + +``` +Resources: + MyQueue: + Type: AWS::SQS::Queue + SQSPayloadLogger: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/sqs-payload-logger.sqsPayloadLoggerHandler + Runtime: nodejs22.x + DeadLetterQueue: + Type: SQS + TargetArn: !GetAtt MyQueue.Arn + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt MyQueue.QueueName +``` + +The dead-letter queue is a location for Lambda to send events that could not be processed. It's only used if you invoke your function asynchronously, but it's useful here to show how you can modify your application's resources and function configuration. + +Deploy the updated application. + +```bash +my-application$ sam deploy +``` + +Open the [**Applications**](https://console.aws.amazon.com/lambda/home#/applications) page of the Lambda console, and choose your application. When the deployment completes, view the application resources on the **Overview** tab to see the new resource. Then, choose the function to see the updated configuration that specifies the dead-letter queue. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, the AWS SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs that are generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +**NOTE:** This command works for all Lambda functions, not just the ones you deploy using AWS SAM. + +```bash +my-application$ sam logs -n SQSPayloadLogger --stack-name sam-app --tail +``` + +**NOTE:** This uses the logical name of the function within the stack. This is the correct name to use when searching logs inside an AWS Lambda function within a CloudFormation stack, even if the deployed function name varies due to CloudFormation's unique resource name generation. + +You can find more information and examples about filtering Lambda function logs in the [AWS SAM CLI documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `__tests__` folder in this project. Use `npm` to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +my-application$ npm install +my-application$ npm run test +``` + +## Cleanup + +To delete the sample application and the bucket that you created, use the AWS CLI. + +```bash +my-application$ sam delete --stack-name sam-app +my-application$ aws s3 rb s3://BUCKET_NAME +``` + +## Resources + +For an introduction to the AWS SAM specification, the AWS SAM CLI, and serverless application concepts, see the [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). + +Next, you can use the AWS Serverless Application Repository to deploy ready-to-use apps that go beyond Hello World samples and learn how authors developed their applications. For more information, see the [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) and the [AWS Serverless Application Repository Developer Guide](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). diff --git a/nodejs22.x/sqs/{{cookiecutter.project_name}}/__tests__/unit/handlers/sqs-payload-logger.test.mjs b/nodejs22.x/sqs/{{cookiecutter.project_name}}/__tests__/unit/handlers/sqs-payload-logger.test.mjs new file mode 100644 index 000000000..de00cb6c6 --- /dev/null +++ b/nodejs22.x/sqs/{{cookiecutter.project_name}}/__tests__/unit/handlers/sqs-payload-logger.test.mjs @@ -0,0 +1,30 @@ +// Import sqsPayloadLoggerHandler function from sqs-payload-logger.mjs +import { sqsPayloadLoggerHandler } from '../../../src/handlers/sqs-payload-logger.mjs'; +import { jest } from '@jest/globals'; + +describe('Test for sqs-payload-logger', function () { + // This test invokes the sqs-payload-logger Lambda function and verifies that the received payload is logged + it('Verifies the payload is logged', async () => { + // Mock console.log statements so we can verify them. For more information, see + // https://jestjs.io/docs/en/mock-functions.html + console.info = jest.fn() + + // Create a sample payload with SQS message format + var payload = { + DelaySeconds: 10, + MessageAttributes: { + "Sender": { + DataType: "String", + StringValue: "sqs-payload-logger" + } + }, + MessageBody: "This message was sent by the sqs-payload-logger Lambda function", + QueueUrl: "SQS_QUEUE_URL" + } + + await sqsPayloadLoggerHandler(payload, null) + + // Verify that console.info has been called with the expected payload + expect(console.info).toHaveBeenCalledWith(JSON.stringify(payload)) + }); +}); diff --git a/nodejs22.x/sqs/{{cookiecutter.project_name}}/buildspec.yml b/nodejs22.x/sqs/{{cookiecutter.project_name}}/buildspec.yml new file mode 100755 index 000000000..adb0a1019 --- /dev/null +++ b/nodejs22.x/sqs/{{cookiecutter.project_name}}/buildspec.yml @@ -0,0 +1,23 @@ +version: 0.2 + +phases: + install: + commands: + # Install all dependencies (including dependencies for running tests) + - npm install + pre_build: + commands: + # Discover and run unit tests in the '__tests__' directory + - npm run test + # Remove all unit tests to reduce the size of the package that will be ultimately uploaded to Lambda + - rm -rf ./__tests__ + # Remove all dependencies not needed for the Lambda deployment package (the packages from devDependencies in package.json) + - npm prune --production + build: + commands: + # Use AWS SAM to package the application by using AWS CloudFormation + - aws cloudformation package --template template.yaml --s3-bucket $S3_BUCKET --output-template template-export.yml +artifacts: + type: zip + files: + - template-export.yml diff --git a/nodejs22.x/sqs/{{cookiecutter.project_name}}/events/event-sqs.json b/nodejs22.x/sqs/{{cookiecutter.project_name}}/events/event-sqs.json new file mode 100644 index 000000000..f691c8780 --- /dev/null +++ b/nodejs22.x/sqs/{{cookiecutter.project_name}}/events/event-sqs.json @@ -0,0 +1,11 @@ +{ + "DelaySeconds": 10, + "MessageAttributes": { + "Sender": { + "DataType": "String", + "StringValue": "sqs-payload-logger" + } + }, + "MessageBody": "This message was sent by the sqs-payload-logger Lambda function", + "QueueUrl": "SQS_QUEUE_URL" +} diff --git a/nodejs22.x/sqs/{{cookiecutter.project_name}}/package.json b/nodejs22.x/sqs/{{cookiecutter.project_name}}/package.json new file mode 100755 index 000000000..64d1cf5eb --- /dev/null +++ b/nodejs22.x/sqs/{{cookiecutter.project_name}}/package.json @@ -0,0 +1,24 @@ +{ + "name": "replaced-by-user-input", + "description": "replaced-by-user-input", + "version": "0.0.1", + "private": true, + "devDependencies": { + "jest": "^29.2.1" + }, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" + }, + "jest": { + "testMatch": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)", + "**/__tests__/**/*.mjs?(x)", + "**/?(*.)+(spec|test).mjs?(x)" + ], + "moduleFileExtensions": [ + "mjs", + "js" + ] + } +} diff --git a/nodejs22.x/sqs/{{cookiecutter.project_name}}/src/handlers/sqs-payload-logger.mjs b/nodejs22.x/sqs/{{cookiecutter.project_name}}/src/handlers/sqs-payload-logger.mjs new file mode 100755 index 000000000..1f1c4e2c5 --- /dev/null +++ b/nodejs22.x/sqs/{{cookiecutter.project_name}}/src/handlers/sqs-payload-logger.mjs @@ -0,0 +1,8 @@ +/** + * A Lambda function that logs the payload received from SQS. + */ +export const sqsPayloadLoggerHandler = async (event, context) => { + // All log statements are written to CloudWatch by default. For more information, see + // https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-logging.html + console.info(JSON.stringify(event)); +} diff --git a/nodejs22.x/sqs/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/sqs/{{cookiecutter.project_name}}/template.yaml new file mode 100755 index 000000000..6cb57492d --- /dev/null +++ b/nodejs22.x/sqs/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,48 @@ +# This is the SAM template that represents the architecture of your serverless application +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html + +# The AWSTemplateFormatVersion identifies the capabilities of the template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + {{cookiecutter.project_name}} + +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: +- AWS::Serverless-2016-10-31 + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + # This is an SQS queue with all default configuration properties. To learn more about the available options, see + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html + SimpleQueue: + Type: AWS::SQS::Queue + + # This is the Lambda function definition associated with the source code: sqs-payload-logger.js. For all available properties, see + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + SQSPayloadLogger: + Type: AWS::Serverless::Function + Properties: + Description: A Lambda function that logs the payload of messages sent to an associated SQS queue. + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + Handler: src/handlers/sqs-payload-logger.sqsPayloadLoggerHandler + # This property associates this Lambda function with the SQS queue defined above, so that whenever the queue + # receives a message, the Lambda function is invoked + Events: + SQSQueueEvent: + Type: SQS + Properties: + Queue: !GetAtt SimpleQueue.Arn + MemorySize: 128 + Timeout: 25 # Chosen to be less than the default SQS Visibility Timeout of 30 seconds + Policies: + # Give Lambda basic execution Permission to the helloFromLambda + - AWSLambdaBasicExecutionRole diff --git a/nodejs22.x/step-func/.gitignore b/nodejs22.x/step-func/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/step-func/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/step-func/README.md b/nodejs22.x/step-func/README.md new file mode 100644 index 000000000..33cc327c9 --- /dev/null +++ b/nodejs22.x/step-func/README.md @@ -0,0 +1,23 @@ +# Cookiecutter NodeJS Step Functions Sample App (Stock Trader) for SAM based Serverless App + +A cookiecutter template to create a NodeJS Step Functions Sample App (Stock Trader) boilerplate using +[Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +This application creates a mock stock trading workflow which runs on a pre-defined schedule. It demonstrates the power of Step Functions to orchestrate Lambda functions and other AWS resources to form complex and robust workflows, coupled with event-driven development using Amazon EventBridge. + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/step-func/cookiecutter.json b/nodejs22.x/step-func/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/step-func/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/step-func/setup.cfg b/nodejs22.x/step-func/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/step-func/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/step-func/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..5854f05ec --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,207 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out +storybook-static + +# rollup.js default build output +dist/ + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md b/nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..b298e4784 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,137 @@ +# {{ cookiecutter.project_name }} + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders: + +- functions - Code for the application's Lambda functions to check the value of, buy, or sell shares of a stock. +- statemachines - Definition for the state machine that orchestrates the stock trading workflow. +- template.yaml - A template that defines the application's AWS resources. + +This application creates a mock stock trading workflow which runs on a pre-defined schedule (note that the schedule is disabled by default to avoid incurring charges). It demonstrates the power of Step Functions to orchestrate Lambda functions and other AWS resources to form complex and robust workflows, coupled with event-driven development using Amazon EventBridge. + +AWS Step Functions lets you coordinate multiple AWS services into serverless workflows so you can build and update apps quickly. Using Step Functions, you can design and run workflows that stitch together services, such as AWS Lambda, AWS Fargate, and Amazon SageMaker, into feature-rich applications. + +The application uses several AWS resources, including Step Functions state machines, Lambda functions and an EventBridge rule trigger. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test the Lambda functions within your application, you can use the AWS Toolkit. The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +The AWS Toolkit for VS Code includes full support for state machine visualization, enabling you to visualize your state machine in real time as you build. The AWS Toolkit for VS Code includes a language server for Amazon States Language, which lints your state machine definition to highlight common errors, provides auto-complete support, and code snippets for each state, enabling you to build state machines faster. + +## Deploy the sample application + +The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. + +To use the SAM CLI, you need the following tools: + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your State Machine ARN in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build the Lambda functions in your application with the `sam build --use-container` command. + +```bash +{{ cookiecutter.project_name }}$ sam build +``` + +The SAM CLI installs dependencies defined in `functions/*/package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +{{ cookiecutter.project_name }}$ sam logs -n StockCheckerFunction --stack-name {{ cookiecutter.project_name }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `functions/*/tests` folder in this project. Use NPM to install the [Mocha test framework](https://mochajs.org/) and run unit tests. + +```bash +{{ cookiecutter.project_name }}$ cd functions/stock-checker +stock-checker$ npm install +stock-checker$ npm run test +``` + +## Testing Step Functions locally +Developers can test their state machines locally using Step Functions Local before deploying them to an AWS account. Often, developers want to test the control and data flows of their state machine executions in isolation, without any dependency on service integration availability. This is now possible using the [Mocked Service Integrations for Step Functions Local](https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-test-sm-exec.html). + +The `statemachine/tests/MockConfigFile.json` contains various test cases with mocked service integrations. + +- `HappyPathSellStockTest` - This test mocks the output of Check Stock Value, Sell Stock and Record Transaction using LambdaMockedSuccessValueGreaterThan50, SellStockLambdaMockedResponse and RecordTransactionDDBMockedResponse respectively. +- `HappyPathBuyStockTest` - This test mocks the output of Check Stock Value, Buy Stock and Record Transaction using LambdaMockedSuccessValueLowerThan50, BuyStockLambdaMockedResponse and RecordTransactionDDBMockedResponse respectively. +- `CheckStockRetryOnServiceExceptionTest` and `SellStockRetryOnServiceExceptionTest`- These tests mocks the failure with exponential retries. + +This application also provides a `makefile` which has all the required commands to run docker [`make run`], create state machine and execute tests [`make all`], and checking history [`make history`]. + +On a terminal window, first start with running docker: + +```bash +make run +``` + +On a different terminal window/tab, you can then run: + +```bash +make all +``` + +Finally, you can check history of each execution by running: + +```bash +make history +``` + +Check [`makefile`](./makefile) for details + + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/.npmignore b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/.npmignore new file mode 100644 index 000000000..e7e1fb04f --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/app.mjs b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/app.mjs new file mode 100644 index 000000000..9db79a08a --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/app.mjs @@ -0,0 +1,30 @@ +import { randomBytes } from "crypto"; + +function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)) + 1; +} + +/** + * Sample Lambda function which mocks the operation of buying a random number of shares for a stock. + * For demonstration purposes, this Lambda function does not actually perform any actual transactions. It simply returns a mocked result. + * + * @param {Object} event - Input event to the Lambda function + * @param {Object} context - Lambda Context runtime methods and attributes + * + * @returns {Object} object - Object containing details of the stock buying transaction + * + */ +export const lambdaHandler = async (event, context) => { + // Get the price of the stock provided as input + const stock_price = event["stock_price"]; + var date = new Date(); + // Mocked result of a stock buying transaction + let transaction_result = { + 'id': randomBytes(16).toString("hex"), // Unique ID for the transaction + 'price': stock_price.toString(), // Price of each share + 'type': "buy", // Type of transaction(buy/ sell) + 'qty': getRandomInt(10).toString(), // Number of shares bought / sold(We are mocking this as a random integer between 1 and 10) + 'timestamp': date.toISOString(), // Timestamp of the when the transaction was completed + } + return transaction_result +}; diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/package.json b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/package.json new file mode 100644 index 000000000..53e3571a0 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/package.json @@ -0,0 +1,17 @@ +{ + "name": "stock_checker", + "version": "1.0.0", + "description": "Mock stock checker for NodeJS", + "main": "app.js", + "repository": "TODO", + "author": "SAM CLI", + "license": "MIT", + "dependencies": {}, + "scripts": { + "test": "mocha tests/unit/" + }, + "devDependencies": { + "chai": "^4.3.6", + "mocha": "^10.1.0" + } +} diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/tests/unit/test-handler.mjs b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/tests/unit/test-handler.mjs new file mode 100644 index 000000000..b581b2a15 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-buyer/tests/unit/test-handler.mjs @@ -0,0 +1,21 @@ +'use strict'; + +import { lambdaHandler } from '../../app.mjs'; +import { expect } from 'chai'; +var event, context; + +describe('Tests Stock Buyer', function () { + it('Verifies response', async () => { + event = { + 'stock_price': 25 + } + const result = await lambdaHandler(event, context) + + expect(result).to.be.an('object'); + expect(result).to.have.all.keys('id', 'price', 'type', 'timestamp', 'qty'); + expect(result.type).to.equal('buy'); + let qty = parseInt(result.qty); + expect(qty).to.be.at.least(1); + expect(qty).to.be.at.most(10); + }); +}); diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/.npmignore b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/.npmignore new file mode 100644 index 000000000..e7e1fb04f --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/app.mjs b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/app.mjs new file mode 100644 index 000000000..fd1733dd3 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/app.mjs @@ -0,0 +1,19 @@ +function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)); +} + +/** + * Sample Lambda function which mocks the operation of checking the current price of a stock. + * For demonstration purposes this Lambda function simply returns a random integer between 0 and 100 as the stock price. + * + * @param {Object} event - Input event to the Lambda function + * @param {Object} context - Lambda Context runtime methods and attributes + * + * @returns {Object} object - Object containing the current price of the stock + * + */ +export const lambdaHandler = async (event, context) => { + // Check current price of the stock + const stock_price = getRandomInt(100) // Current stock price is mocked as a random integer between 0 and 100 + return { 'stock_price': stock_price } +}; diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/package.json b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/package.json new file mode 100644 index 000000000..53e3571a0 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/package.json @@ -0,0 +1,17 @@ +{ + "name": "stock_checker", + "version": "1.0.0", + "description": "Mock stock checker for NodeJS", + "main": "app.js", + "repository": "TODO", + "author": "SAM CLI", + "license": "MIT", + "dependencies": {}, + "scripts": { + "test": "mocha tests/unit/" + }, + "devDependencies": { + "chai": "^4.3.6", + "mocha": "^10.1.0" + } +} diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/tests/unit/test-handler.mjs b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/tests/unit/test-handler.mjs new file mode 100644 index 000000000..fb5a6c614 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-checker/tests/unit/test-handler.mjs @@ -0,0 +1,16 @@ +'use strict'; + +import { lambdaHandler } from '../../app.mjs'; +import { expect } from 'chai'; +var event, context; + +describe('Tests Stock Checker', function () { + it('Verifies response', async () => { + const result = await lambdaHandler(event, context) + + expect(result).to.be.an('object'); + expect(result.stock_price).to.be.an('number'); + expect(result.stock_price).to.be.at.least(0); + expect(result.stock_price).to.be.at.most(100); + }); +}); diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/.npmignore b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/.npmignore new file mode 100644 index 000000000..e7e1fb04f --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/app.mjs b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/app.mjs new file mode 100644 index 000000000..9b3eeb7d2 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/app.mjs @@ -0,0 +1,30 @@ +import { randomBytes } from "crypto"; + +function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)) + 1; +} + +/** + * Sample Lambda function which mocks the operation of selling a random number of shares for a stock. + * For demonstration purposes, this Lambda function does not actually perform any actual transactions. It simply returns a mocked result. + * + * @param {Object} event - Input event to the Lambda function + * @param {Object} context - Lambda Context runtime methods and attributes + * + * @returns {Object} object - Object containing details of the stock selling transaction + * + */ +export const lambdaHandler = async (event, context) => { + // Get the price of the stock provided as input + const stock_price = event["stock_price"] + var date = new Date(); + // Mocked result of a stock selling transaction + let transaction_result = { + 'id': randomBytes(16).toString("hex"), // Unique ID for the transaction + 'price': stock_price.toString(), // Price of each share + 'type': "sell", // Type of transaction(buy/ sell) + 'qty': getRandomInt(10).toString(), // Number of shares bought / sold(We are mocking this as a random integer between 1 and 10) + 'timestamp': date.toISOString(), // Timestamp of the when the transaction was completed + } + return transaction_result +}; diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/package.json b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/package.json new file mode 100644 index 000000000..53e3571a0 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/package.json @@ -0,0 +1,17 @@ +{ + "name": "stock_checker", + "version": "1.0.0", + "description": "Mock stock checker for NodeJS", + "main": "app.js", + "repository": "TODO", + "author": "SAM CLI", + "license": "MIT", + "dependencies": {}, + "scripts": { + "test": "mocha tests/unit/" + }, + "devDependencies": { + "chai": "^4.3.6", + "mocha": "^10.1.0" + } +} diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/tests/unit/test-handler.mjs b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/tests/unit/test-handler.mjs new file mode 100644 index 000000000..86a6b44bb --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/functions/stock-seller/tests/unit/test-handler.mjs @@ -0,0 +1,21 @@ +'use strict'; + +import { lambdaHandler } from '../../app.mjs'; +import { expect } from 'chai'; +var event, context; + +describe('Tests Stock Seller', function () { + it('Verifies response', async () => { + event = { + 'stock_price': 75 + } + const result = await lambdaHandler(event, context) + + expect(result).to.be.an('object'); + expect(result).to.have.all.keys('id', 'price', 'type', 'timestamp', 'qty'); + expect(result.type).to.equal('sell'); + let qty = parseInt(result.qty); + expect(qty).to.be.at.least(1); + expect(qty).to.be.at.most(10); + }); +}); diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/makefile b/nodejs22.x/step-func/{{cookiecutter.project_name}}/makefile new file mode 100644 index 000000000..3e9440fd3 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/makefile @@ -0,0 +1,71 @@ +ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +run: + docker run -p 8083:8083 \ + --mount type=bind,readonly,source=$(ROOT_DIR)/statemachine/test/MockConfigFile.json,destination=/home/StepFunctionsLocal/MockConfigFile.json \ + -e SFN_MOCK_CONFIG="/home/StepFunctionsLocal/MockConfigFile.json" \ + amazon/aws-stepfunctions-local + +create: + sed -E -e 's/\$$\{.+\}/arn:aws:lambda:us-east-1:123456789012:function:mock/' statemachine/stock_trader.asl.json > statemachine/test/mocked.test.asl.json + aws stepfunctions create-state-machine \ + --endpoint-url http://localhost:8083 \ + --definition file://statemachine/test/mocked.test.asl.json \ + --name "StockTradingLocalTesting" \ + --role-arn "arn:aws:iam::123456789012:role/DummyRole" \ + --no-cli-pager + rm statemachine/test/mocked.test.asl.json + +happypathsellstocktest: + aws stepfunctions start-execution \ + --endpoint http://localhost:8083 \ + --name HappyPathSellStockTest \ + --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:StockTradingLocalTesting#HappyPathSellStockTest \ + --no-cli-pager + +happypathbuystocktest: + aws stepfunctions start-execution \ + --endpoint http://localhost:8083 \ + --name HappyPathBuyStockTest \ + --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:StockTradingLocalTesting#HappyPathBuyStockTest \ + --no-cli-pager + +checkstockerrorwithretry: + aws stepfunctions start-execution \ + --endpoint http://localhost:8083 \ + --name checkStockErrorExecution \ + --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:StockTradingLocalTesting#CheckStockRetryOnServiceExceptionTest \ + --no-cli-pager + +sellstockerrorwithretry: + aws stepfunctions start-execution \ + --endpoint http://localhost:8083 \ + --name sellStockErrorExecution \ + --state-machine arn:aws:states:us-east-1:123456789012:stateMachine:StockTradingLocalTesting#SellStockRetryOnServiceExceptionTest \ + --no-cli-pager + + +all: create happypathsellstocktest happypathbuystocktest checkstockerrorwithretry sellstockerrorwithretry + +happypathsellstocktest-h: + aws stepfunctions get-execution-history \ + --endpoint http://localhost:8083 \ + --execution-arn arn:aws:states:us-east-1:123456789012:execution:StockTradingLocalTesting:HappyPathSellStockTest \ + --query 'events[?type==`TaskStateExited` && stateExitedEventDetails.name==`Record Transaction`]' \ + --no-cli-pager + +checkstockerror: + aws stepfunctions get-execution-history \ + --endpoint http://localhost:8083 \ + --execution-arn arn:aws:states:us-east-1:123456789012:execution:StockTradingLocalTesting:checkStockErrorExecution \ + --query 'events[?type==`TaskStateExited` && stateExitedEventDetails.name==`Record Transaction` ]' \ + --no-cli-pager + +sellstockerror: + aws stepfunctions get-execution-history \ + --endpoint http://localhost:8083 \ + --execution-arn arn:aws:states:us-east-1:123456789012:execution:StockTradingLocalTesting:sellStockErrorExecution \ + --query 'events[?type==`TaskStateExited` && stateExitedEventDetails.name==`Record Transaction` ]' \ + --no-cli-pager + +history: happypathsellstocktest-h checkstockerror sellstockerror \ No newline at end of file diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json b/nodejs22.x/step-func/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json new file mode 100644 index 000000000..c29281b34 --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/statemachine/stock_trader.asl.json @@ -0,0 +1,97 @@ +{ + "Comment": "A state machine that does mock stock trading.", + "StartAt": "Check Stock Value", + "States": { + "Check Stock Value": { + "Type": "Task", + "Resource": "${StockCheckerFunctionArn}", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 15, + "MaxAttempts": 5, + "BackoffRate": 1.5 + } + ], + "Next": "Buy or Sell?" + }, + "Buy or Sell?": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.stock_price", + "NumericLessThanEquals": 50, + "Next": "Buy Stock" + } + ], + "Default": "Sell Stock" + }, + "Sell Stock": { + "Type": "Task", + "Resource": "${StockSellerFunctionArn}", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 2, + "MaxAttempts": 3, + "BackoffRate": 1 + } + ], + "Next": "Record Transaction" + }, + "Buy Stock": { + "Type": "Task", + "Resource": "${StockBuyerFunctionArn}", + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 2, + "MaxAttempts": 3, + "BackoffRate": 1 + } + ], + "Next": "Record Transaction" + }, + "Record Transaction": { + "Type": "Task", + "Resource": "${DDBPutItem}", + "Parameters": { + "TableName": "${DDBTable}", + "Item": { + "Id": { + "S.$": "$.id" + }, + "Type": { + "S.$": "$.type" + }, + "Price": { + "N.$": "$.price" + }, + "Quantity": { + "N.$": "$.qty" + }, + "Timestamp": { + "S.$": "$.timestamp" + } + } + }, + "Retry": [ + { + "ErrorEquals": [ + "States.TaskFailed" + ], + "IntervalSeconds": 20, + "MaxAttempts": 5, + "BackoffRate": 10 + } + ], + "End": true + } + } +} \ No newline at end of file diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/statemachine/test/MockConfigFile.json b/nodejs22.x/step-func/{{cookiecutter.project_name}}/statemachine/test/MockConfigFile.json new file mode 100644 index 000000000..20db6d24c --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/statemachine/test/MockConfigFile.json @@ -0,0 +1,142 @@ +{ + "StateMachines": { + "StockTradingLocalTesting": { + "TestCases": { + "HappyPathSellStockTest": { + "Check Stock Value": "LambdaMockedSuccessValueGreaterThan50", + "Sell Stock": "SellStockLambdaMockedResponse", + "Record Transaction": "RecordTransactionDDBMockedResponse" + }, + "HappyPathBuyStockTest": { + "Check Stock Value": "LambdaMockedSuccessValueLowerThan50", + "Buy Stock": "BuyStockLambdaMockedResponse", + "Record Transaction": "RecordTransactionDDBMockedResponse" + }, + "CheckStockRetryOnServiceExceptionTest": { + "Check Stock Value": "CheckStockLambdaMockedRetryWithSuccess", + "Sell Stock": "SellStockLambdaMockedResponse", + "Record Transaction": "RecordTransactionDDBMockedResponse" + }, + "SellStockRetryOnServiceExceptionTest": { + "Check Stock Value": "LambdaMockedSuccessValueGreaterThan50", + "Sell Stock": "SellStockLambdaMockedRetryWithSuccess", + "Record Transaction": "RecordTransactionDDBMockedResponse" + } + } + } + }, + "MockedResponses": { + "LambdaMockedSuccessValueGreaterThan50": { + "0": { + "Return": { + "stock_price": 98 + } + } + }, + "LambdaMockedSuccessValueLowerThan50": { + "0": { + "Return": { + "stock_price": 32 + } + } + }, + "SellStockLambdaMockedResponse": { + "0": { + "Return": { + "id": "8cd8e4d92fe716b7cedff5636ab9d24e", + "price": "98", + "type": "sell", + "qty": "7", + "timestamp": "2022-06-22T21:14:18.228Z" + } + } + }, + "BuyStockLambdaMockedResponse": { + "0": { + "Return": { + "id": "8cd8e4d92fe716b7cedff5636ab9d24e", + "price": "32", + "type": "buy", + "qty": "7", + "timestamp": "2022-06-22T21:14:18.228Z" + } + } + }, + "RecordTransactionDDBMockedResponse": { + "0": { + "Return": { + "resourceType": "dynamodb", + "resource": "putItem", + "output": { + "SdkHttpMetadata": { + "AllHttpHeaders": { + "Server": [ + "Server" + ], + "Connection": [ + "keep-alive" + ], + "x-amzn-RequestId": [ + "LAT641GUVNBRI0DC1HC6S4MDH3VV4KQNSO5AEMVJF66Q9ASUAAJG" + ], + "x-amz-crc32": [ + "2745614147" + ], + "Content-Length": [ + "2" + ], + "Date": [ + "Wed, 22 Jun 2022 21:14:18 GMT" + ], + "Content-Type": [ + "application/x-amz-json-1.0" + ] + }, + "HttpHeaders": { + "Connection": "keep-alive", + "Content-Length": "2", + "Content-Type": "application/x-amz-json-1.0", + "Date": "Wed, 22 Jun 2022 21:14:18 GMT", + "Server": "Server", + "x-amz-crc32": "2745614147", + "x-amzn-RequestId": "LAT641GUVNBRI0DC1HC6S4MDH3VV4KQNSO5AEMVJF66Q9ASUAAJG" + }, + "HttpStatusCode": 200 + }, + "SdkResponseMetadata": { + "RequestId": "LAT641GUVNBRI0DC1HC6S4MDH3VV4KQNSO5AEMVJF66Q9ASUAAJG" + } + } + } + } + }, + "CheckStockLambdaMockedRetryWithSuccess": { + "0-2": { + "Throw": { + "Error": "States.TaskFailed" + } + }, + "3": { + "Return": { + "stock_price": 89 + } + } + }, + "SellStockLambdaMockedRetryWithSuccess": { + "0-2": { + "Throw": { + "Error": "States.TaskFailed" + } + }, + "3": { + "Return": { + "id": "8cd8e4d92fe716b7cedff5636ab9d24e", + "price": "98", + "type": "sell", + "qty": "5", + "timestamp": "2022-06-22T21:14:18.228Z" + } + } + } + } +} \ No newline at end of file diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/step-func/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..230774dfc --- /dev/null +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,94 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +Resources: + StockTradingStateMachine: + Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html + Properties: + DefinitionUri: statemachine/stock_trader.asl.json + DefinitionSubstitutions: + StockCheckerFunctionArn: !GetAtt StockCheckerFunction.Arn + StockSellerFunctionArn: !GetAtt StockSellerFunction.Arn + StockBuyerFunctionArn: !GetAtt StockBuyerFunction.Arn + DDBPutItem: !Sub arn:${AWS::Partition}:states:::dynamodb:putItem + DDBTable: !Ref TransactionTable + Events: + HourlyTradingSchedule: + Type: Schedule # More info about Schedule Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-statemachine-schedule.html + Properties: + Description: Schedule to run the stock trading state machine every hour + Enabled: False # This schedule is disabled by default to avoid incurring charges. + Schedule: "rate(1 hour)" + Policies: # Find out more about SAM policy templates: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-policy-templates.html + - LambdaInvokePolicy: + FunctionName: !Ref StockCheckerFunction + - LambdaInvokePolicy: + FunctionName: !Ref StockSellerFunction + - LambdaInvokePolicy: + FunctionName: !Ref StockBuyerFunction + - DynamoDBWritePolicy: + TableName: !Ref TransactionTable + + StockCheckerFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html + Properties: + CodeUri: functions/stock-checker/ + Handler: app.lambdaHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + + StockSellerFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: functions/stock-seller/ + Handler: app.lambdaHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + + StockBuyerFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: functions/stock-buyer/ + Handler: app.lambdaHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + + TransactionTable: + Type: AWS::Serverless::SimpleTable # More info about SimpleTable Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-simpletable.html + Properties: + PrimaryKey: + Name: Id + Type: String + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 + +Outputs: + # StockTradingStateMachineHourlyTradingSchedule is an implicit Schedule event rule created out of Events key under Serverless::StateMachine + # Find out more about other implicit resources you can reference within SAM + # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-generated-resources.html + StockTradingStateMachineArn: + Description: "Stock Trading state machine ARN" + Value: !Ref StockTradingStateMachine + StockTradingStateMachineRole: + Description: "IAM Role created for Stock Trading state machine based on the specified SAM Policy Templates" + Value: !GetAtt StockTradingStateMachineRole.Arn diff --git a/nodejs22.x/web/.gitignore b/nodejs22.x/web/.gitignore new file mode 100644 index 000000000..41bcace31 --- /dev/null +++ b/nodejs22.x/web/.gitignore @@ -0,0 +1,229 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,sam diff --git a/nodejs22.x/web/README.md b/nodejs22.x/web/README.md new file mode 100644 index 000000000..2290fdc5d --- /dev/null +++ b/nodejs22.x/web/README.md @@ -0,0 +1,20 @@ +# Cookiecutter NodeJS Quick Start Web Application + +A cookiecutter template to create a NodeJS Quick Start Web Application using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-web --name web-app` + +> **NOTE**: ``--name`` allows you to specify a different project folder name + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + diff --git a/nodejs22.x/web/cookiecutter.json b/nodejs22.x/web/cookiecutter.json new file mode 100644 index 000000000..bc6045867 --- /dev/null +++ b/nodejs22.x/web/cookiecutter.json @@ -0,0 +1,10 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs22.x", + "architectures": { + "value": ["x86_64", "arm64"] + }, + "_copy_without_render": [ + ".gitignore" + ] +} \ No newline at end of file diff --git a/nodejs22.x/web/setup.cfg b/nodejs22.x/web/setup.cfg new file mode 100644 index 000000000..eee4ab11a --- /dev/null +++ b/nodejs22.x/web/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/.gitignore b/nodejs22.x/web/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/README.md b/nodejs22.x/web/{{cookiecutter.project_name}}/README.md new file mode 100644 index 000000000..1041d4ece --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,160 @@ +# {{cookiecutter.project_name}} + +This project contains source code and supporting files for a serverless application that you can deploy with the AWS Serverless Application Model (AWS SAM) command line interface (CLI). It includes the following files and folders: + +- `src` - Code for the application's Lambda function. +- `events` - Invocation events that you can use to invoke the function. +- `__tests__` - Unit tests for the application code. +- `template.yaml` - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions, an API Gateway API, and Amazon DynamoDB tables. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open-source plugin for popular IDEs that uses the AWS SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds step-through debugging for Lambda function code. + +To get started, see the following: + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + +## Deploy the sample application + +The AWS SAM CLI is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. + +To use the AWS SAM CLI, you need the following tools: + +* AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy --guided +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +The API Gateway endpoint API will be displayed in the outputs when the deployment is complete. + +## Use the AWS SAM CLI to build and test locally + +Build your application by using the `sam build` command. + +```bash +my-application$ sam build +``` + +The AWS SAM CLI installs dependencies that are defined in `package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +my-application$ sam local invoke putItemFunction --event events/event-post-item.json +my-application$ sam local invoke getAllItemsFunction --event events/event-get-all-items.json +``` + +The AWS SAM CLI can also emulate your application's API. Use the `sam local start-api` command to run the API locally on port 3000. + +```bash +my-application$ sam local start-api +my-application$ curl http://localhost:3000/ +``` + +The AWS SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET +``` + +## Add a resource to your application +The application template uses AWS SAM to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources, such as functions, triggers, and APIs. For resources that aren't included in the [AWS SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use the standard [AWS CloudFormation resource types](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html). + +Update `template.yaml` to add a dead-letter queue to your application. In the **Resources** section, add a resource named **MyQueue** with the type **AWS::SQS::Queue**. Then add a property to the **AWS::Serverless::Function** resource named **DeadLetterQueue** that targets the queue's Amazon Resource Name (ARN), and a policy that grants the function permission to access the queue. + +``` +Resources: + MyQueue: + Type: AWS::SQS::Queue + getAllItemsFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get-all-items.getAllItemsHandler + Runtime: nodejs22.x + DeadLetterQueue: + Type: SQS + TargetArn: !GetAtt MyQueue.Arn + Policies: + - SQSSendMessagePolicy: + QueueName: !GetAtt MyQueue.QueueName +``` + +The dead-letter queue is a location for Lambda to send events that could not be processed. It's only used if you invoke your function asynchronously, but it's useful here to show how you can modify your application's resources and function configuration. + +Deploy the updated application. + +```bash +my-application$ sam deploy +``` + +Open the [**Applications**](https://console.aws.amazon.com/lambda/home#/applications) page of the Lambda console, and choose your application. When the deployment completes, view the application resources on the **Overview** tab to see the new resource. Then, choose the function to see the updated configuration that specifies the dead-letter queue. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, the AWS SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs that are generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +**NOTE:** This command works for all Lambda functions, not just the ones you deploy using AWS SAM. + +```bash +my-application$ sam logs -n putItemFunction --stack-name sam-app --tail +``` + +**NOTE:** This uses the logical name of the function within the stack. This is the correct name to use when searching logs inside an AWS Lambda function within a CloudFormation stack, even if the deployed function name varies due to CloudFormation's unique resource name generation. + +You can find more information and examples about filtering Lambda function logs in the [AWS SAM CLI documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Unit tests + +Tests are defined in the `__tests__` folder in this project. Use `npm` to install the [Jest test framework](https://jestjs.io/) and run unit tests. + +```bash +my-application$ npm install +my-application$ npm run test +``` + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete --stack-name {{ cookiecutter.project_name }} +``` + +## Resources + +For an introduction to the AWS SAM specification, the AWS SAM CLI, and serverless application concepts, see the [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html). + +Next, you can use the AWS Serverless Application Repository to deploy ready-to-use apps that go beyond Hello World samples and learn how authors developed their applications. For more information, see the [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) and the [AWS Serverless Application Repository Developer Guide](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/what-is-serverlessrepo.html). diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/get-all-items.test.mjs b/nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/get-all-items.test.mjs new file mode 100644 index 000000000..f9b642f19 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/get-all-items.test.mjs @@ -0,0 +1,38 @@ +// Import getAllItemsHandler function from get-all-items.mjs +import { getAllItemsHandler } from '../../../src/handlers/get-all-items.mjs'; +// Import dynamodb from aws-sdk +import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb'; +import { mockClient } from "aws-sdk-client-mock"; + +// This includes all tests for getAllItemsHandler() +describe('Test getAllItemsHandler', () => { + const ddbMock = mockClient(DynamoDBDocumentClient); + + beforeEach(() => { + ddbMock.reset(); + }); + + it('should return ids', async () => { + const items = [{ id: 'id1' }, { id: 'id2' }]; + + // Return the specified value whenever the spied scan function is called + ddbMock.on(ScanCommand).resolves({ + Items: items, + }); + + const event = { + httpMethod: 'GET' + }; + + // Invoke helloFromLambdaHandler() + const result = await getAllItemsHandler(event); + + const expectedResult = { + statusCode: 200, + body: JSON.stringify(items) + }; + + // Compare the result with the expected result + expect(result).toEqual(expectedResult); + }); +}); diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/get-by-id.test.mjs b/nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/get-by-id.test.mjs new file mode 100644 index 000000000..2d97c98d7 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/get-by-id.test.mjs @@ -0,0 +1,43 @@ +// Import getByIdHandler function from get-by-id.mjs +import { getByIdHandler } from '../../../src/handlers/get-by-id.mjs'; +// Import dynamodb from aws-sdk +import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb'; +import { mockClient } from "aws-sdk-client-mock"; + +// This includes all tests for getByIdHandler() +describe('Test getByIdHandler', () => { + const ddbMock = mockClient(DynamoDBDocumentClient); + + beforeEach(() => { + ddbMock.reset(); + }); + + // This test invokes getByIdHandler() and compare the result + it('should get item by id', async () => { + const item = { id: 'id1' }; + + // Return the specified value whenever the spied get function is called + ddbMock.on(GetCommand).resolves({ + Item: item, + }); + + const event = { + httpMethod: 'GET', + pathParameters: { + id: 'id1' + } + }; + + // Invoke getByIdHandler() + const result = await getByIdHandler(event); + + const expectedResult = { + statusCode: 200, + body: JSON.stringify(item) + }; + + // Compare the result with the expected result + expect(result).toEqual(expectedResult); + }); +}); + \ No newline at end of file diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/put-item.test.mjs b/nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/put-item.test.mjs new file mode 100644 index 000000000..64ea140d1 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/__tests__/unit/handlers/put-item.test.mjs @@ -0,0 +1,40 @@ +// Import putItemHandler function from put-item.mjs +import { putItemHandler } from '../../../src/handlers/put-item.mjs'; +// Import dynamodb from aws-sdk +import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb'; +import { mockClient } from "aws-sdk-client-mock"; +// This includes all tests for putItemHandler() +describe('Test putItemHandler', function () { + const ddbMock = mockClient(DynamoDBDocumentClient); + + beforeEach(() => { + ddbMock.reset(); + }); + + // This test invokes putItemHandler() and compare the result + it('should add id to the table', async () => { + const returnedItem = { id: 'id1', name: 'name1' }; + + // Return the specified value whenever the spied put function is called + ddbMock.on(PutCommand).resolves({ + returnedItem + }); + + const event = { + httpMethod: 'POST', + body: '{"id": "id1","name": "name1"}' + }; + + // Invoke putItemHandler() + const result = await putItemHandler(event); + + const expectedResult = { + statusCode: 200, + body: JSON.stringify(returnedItem) + }; + + // Compare the result with the expected result + expect(result).toEqual(expectedResult); + }); +}); + \ No newline at end of file diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/buildspec.yml b/nodejs22.x/web/{{cookiecutter.project_name}}/buildspec.yml new file mode 100644 index 000000000..7c1dae85e --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/buildspec.yml @@ -0,0 +1,22 @@ +version: 0.2 +phases: + install: + commands: + # Install all dependencies (including dependencies for running tests) + - npm install + pre_build: + commands: + # Discover and run unit tests in the '__tests__' directory + - npm run test + # Remove all unit tests to reduce the size of the package that will be ultimately uploaded to Lambda + - rm -rf ./__tests__ + # Remove all dependencies not needed for the Lambda deployment package (the packages from devDependencies in package.json) + - npm prune --production + build: + commands: + # Use AWS SAM to package the application by using AWS CloudFormation + - aws cloudformation package --template template.yaml --s3-bucket $S3_BUCKET --output-template template-export.yml +artifacts: + type: zip + files: + - template-export.yml diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/env.json b/nodejs22.x/web/{{cookiecutter.project_name}}/env.json new file mode 100644 index 000000000..c6073eb26 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/env.json @@ -0,0 +1,11 @@ +{ + "getAllItemsFunction": { + "SAMPLE_TABLE": "" + }, + "getByIdFunction": { + "SAMPLE_TABLE": "" + }, + "putItemFunction": { + "SAMPLE_TABLE": "" + } + } \ No newline at end of file diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/events/event-get-all-items.json b/nodejs22.x/web/{{cookiecutter.project_name}}/events/event-get-all-items.json new file mode 100644 index 000000000..3a0cb5f77 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/events/event-get-all-items.json @@ -0,0 +1,3 @@ +{ + "httpMethod": "GET" +} \ No newline at end of file diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/events/event-get-by-id.json b/nodejs22.x/web/{{cookiecutter.project_name}}/events/event-get-by-id.json new file mode 100644 index 000000000..63a64fb45 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/events/event-get-by-id.json @@ -0,0 +1,6 @@ +{ + "httpMethod": "GET", + "pathParameters": { + "id": "id1" + } +} \ No newline at end of file diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/events/event-post-item.json b/nodejs22.x/web/{{cookiecutter.project_name}}/events/event-post-item.json new file mode 100644 index 000000000..6367003e5 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/events/event-post-item.json @@ -0,0 +1,4 @@ +{ + "httpMethod": "POST", + "body": "{\"id\": \"id1\",\"name\": \"name1\"}" +} \ No newline at end of file diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/package.json b/nodejs22.x/web/{{cookiecutter.project_name}}/package.json new file mode 100644 index 000000000..11e7ea689 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/package.json @@ -0,0 +1,30 @@ +{ + "name": "delete-test-01", + "description": "delete-test-01-description", + "version": "0.0.1", + "private": true, + "dependencies": { + "@aws-sdk/client-dynamodb": "^3.398.0", + "@aws-sdk/lib-dynamodb": "^3.398.0" + }, + "devDependencies": { + "aws-sdk-client-mock": "^2.0.0", + "jest": "^29.2.1" + }, + "scripts": { + "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js" + }, + "jest": { + "testMatch": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)", + "**/__tests__/**/*.mjs?(x)", + "**/?(*.)+(spec|test).mjs?(x)" + ], + "moduleFileExtensions": [ + "mjs", + "js" + ] + } +} + diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/get-all-items.mjs b/nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/get-all-items.mjs new file mode 100644 index 000000000..d76ba9af9 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/get-all-items.mjs @@ -0,0 +1,44 @@ +// Create clients and set shared const values outside of the handler. + +// Create a DocumentClient that represents the query to add an item +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb'; +const client = new DynamoDBClient({}); +const ddbDocClient = DynamoDBDocumentClient.from(client); + +// Get the DynamoDB table name from environment variables +const tableName = process.env.SAMPLE_TABLE; + +/** + * A simple example includes a HTTP get method to get all items from a DynamoDB table. + */ +export const getAllItemsHandler = async (event) => { + if (event.httpMethod !== 'GET') { + throw new Error(`getAllItems only accept GET method, you tried: ${event.httpMethod}`); + } + // All log statements are written to CloudWatch + console.info('received:', event); + + // get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data) + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property + // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html + var params = { + TableName : tableName + }; + + try { + const data = await ddbDocClient.send(new ScanCommand(params)); + var items = data.Items; + } catch (err) { + console.log("Error", err); + } + + const response = { + statusCode: 200, + body: JSON.stringify(items) + }; + + // All log statements are written to CloudWatch + console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); + return response; +} diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/get-by-id.mjs b/nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/get-by-id.mjs new file mode 100644 index 000000000..958e24969 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/get-by-id.mjs @@ -0,0 +1,47 @@ +// Create clients and set shared const values outside of the handler. + +// Create a DocumentClient that represents the query to add an item +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb'; +const client = new DynamoDBClient({}); +const ddbDocClient = DynamoDBDocumentClient.from(client); + +// Get the DynamoDB table name from environment variables +const tableName = process.env.SAMPLE_TABLE; + +/** + * A simple example includes a HTTP get method to get one item by id from a DynamoDB table. + */ +export const getByIdHandler = async (event) => { + if (event.httpMethod !== 'GET') { + throw new Error(`getMethod only accept GET method, you tried: ${event.httpMethod}`); + } + // All log statements are written to CloudWatch + console.info('received:', event); + + // Get id from pathParameters from APIGateway because of `/{id}` at template.yaml + const id = event.pathParameters.id; + + // Get the item from the table + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property + var params = { + TableName : tableName, + Key: { id: id }, + }; + + try { + const data = await ddbDocClient.send(new GetCommand(params)); + var item = data.Item; + } catch (err) { + console.log("Error", err); + } + + const response = { + statusCode: 200, + body: JSON.stringify(item) + }; + + // All log statements are written to CloudWatch + console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); + return response; +} diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/put-item.mjs b/nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/put-item.mjs new file mode 100644 index 000000000..fb26f652e --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/src/handlers/put-item.mjs @@ -0,0 +1,49 @@ +// Create clients and set shared const values outside of the handler. + +// Create a DocumentClient that represents the query to add an item +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb'; +const client = new DynamoDBClient({}); +const ddbDocClient = DynamoDBDocumentClient.from(client); + +// Get the DynamoDB table name from environment variables +const tableName = process.env.SAMPLE_TABLE; + +/** + * A simple example includes a HTTP post method to add one item to a DynamoDB table. + */ +export const putItemHandler = async (event) => { + if (event.httpMethod !== 'POST') { + throw new Error(`postMethod only accepts POST method, you tried: ${event.httpMethod} method.`); + } + // All log statements are written to CloudWatch + console.info('received:', event); + + // Get id and name from the body of the request + const body = JSON.parse(event.body); + const id = body.id; + const name = body.name; + + // Creates a new item, or replaces an old item with a new item + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property + var params = { + TableName : tableName, + Item: { id : id, name: name } + }; + + try { + const data = await ddbDocClient.send(new PutCommand(params)); + console.log("Success - item added or updated", data); + } catch (err) { + console.log("Error", err.stack); + } + + const response = { + statusCode: 200, + body: JSON.stringify(body) + }; + + // All log statements are written to CloudWatch + console.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`); + return response; +}; diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/template.yaml b/nodejs22.x/web/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 000000000..08f3489e3 --- /dev/null +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,131 @@ +# This is the SAM template that represents the architecture of your serverless application +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html + +# The AWSTemplateFormatVersion identifies the capabilities of the template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + {{cookiecutter.project_name}} + +# Transform section specifies one or more macros that AWS CloudFormation uses to process your template +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html +Transform: +- AWS::Serverless-2016-10-31 + +# Resources declares the AWS resources that you want to include in the stack +# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html +Resources: + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: get-all-items.js + getAllItemsFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get-all-items.getAllItemsHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP get method to get all items from a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: / + Method: GET + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: get-by-id.js + getByIdFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/get-by-id.getByIdHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP get method to get one item by id from a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: /{id} + Method: GET + # Each Lambda function is defined by properties: + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + + # This is a Lambda function config associated with the source code: put-item.js + putItemFunction: + Type: AWS::Serverless::Function + Properties: + Handler: src/handlers/put-item.putItemHandler + Runtime: nodejs22.x + {%- if cookiecutter.architectures.value != []%} + Architectures: + {%- for arch in cookiecutter.architectures.value %} + - {{arch}} + {%- endfor %} + {%- endif %} + MemorySize: 128 + Timeout: 100 + Description: A simple example includes a HTTP post method to add one item to a DynamoDB table. + Policies: + # Give Create/Read/Update/Delete Permissions to the SampleTable + - DynamoDBCrudPolicy: + TableName: !Ref SampleTable + Environment: + Variables: + # Make table name accessible as environment variable from function code during execution + SAMPLE_TABLE: !Ref SampleTable + Events: + Api: + Type: Api + Properties: + Path: / + Method: POST + # Simple syntax to create a DynamoDB table with a single attribute primary key, more in + # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + + # DynamoDB table to store item: {id: <ID>, name: <NAME>} + SampleTable: + Type: AWS::Serverless::SimpleTable + Properties: + PrimaryKey: + Name: id + Type: String + ProvisionedThroughput: + ReadCapacityUnits: 2 + WriteCapacityUnits: 2 + +Outputs: + WebEndpoint: + Description: "API Gateway endpoint URL for Prod stage" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" diff --git a/tests/integration/build_invoke/node/test_node_22.py b/tests/integration/build_invoke/node/test_node_22.py new file mode 100644 index 000000000..2e95e037a --- /dev/null +++ b/tests/integration/build_invoke/node/test_node_22.py @@ -0,0 +1,80 @@ +from tests.integration.build_invoke.build_invoke_base import BuildInvokeBase + +""" +For each template, it will test the following sam commands: +1. sam init +2. sam build --use-container (if self.use_container is False, --use-container will be omitted) +3. (if there are event jsons), for each event json, check `sam local invoke` response is a valid json +""" + +class BuildInvoke_nodejs22_x_cookiecutter_aws_sam_hello_nodejs(BuildInvokeBase.SimpleHelloWorldBuildInvokeBase): + directory = "nodejs22.x/hello" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + +class BuildInvoke_nodejs22_x_cookiecutter_aws_sam_step_functions_sample_app(BuildInvokeBase.BuildInvokeBase): + directory = "nodejs22.x/step-func" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_nodejs22_x_cookiecutter_quick_start_from_scratch(BuildInvokeBase.BuildInvokeBase): + directory = "nodejs22.x/scratch" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_nodejs22_x_cookiecutter_quick_start_cloudwatch_events(BuildInvokeBase.BuildInvokeBase): + directory = "nodejs22.x/cw-event" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_nodejs22_x_cookiecutter_quick_start_s3(BuildInvokeBase.BuildInvokeBase): + directory = "nodejs22.x/s3" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_nodejs22_x_cookiecutter_quick_start_sns(BuildInvokeBase.BuildInvokeBase): + directory = "nodejs22.x/sns" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_nodejs22_x_cookiecutter_quick_start_sqs(BuildInvokeBase.BuildInvokeBase): + directory = "nodejs22.x/sqs" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_nodejs22_x_cookiecutter_quick_start_response_streaming(BuildInvokeBase.BuildInvokeBase): + directory = "nodejs22.x/response-streaming" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_nodejs22_x_cookiecutter_quick_start_web(BuildInvokeBase.QuickStartWebBuildInvokeBase): + directory = "nodejs22.x/web" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_nodejs22_x_cookiecutter_quick_start_full_stack(BuildInvokeBase.QuickStartWebBuildInvokeBase): + directory = "nodejs22.x/full-stack" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_image_nodejs22_x_cookiecutter_aws_sam_hello_nodejs_lambda_image( + BuildInvokeBase.SimpleHelloWorldBuildInvokeBase +): + directory = "nodejs22.x/hello-img" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class BuildInvoke_nodejs22_x_cookiecutter_aws_sam_gql_quick_start(BuildInvokeBase.BuildInvokeBase): + directory = "nodejs22.x/hello-gql" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False diff --git a/tests/integration/unit_test/test_unit_test_nodejs22_x.py b/tests/integration/unit_test/test_unit_test_nodejs22_x.py new file mode 100644 index 000000000..1d4477d76 --- /dev/null +++ b/tests/integration/unit_test/test_unit_test_nodejs22_x.py @@ -0,0 +1,75 @@ +from tests.integration.unit_test.unit_test_base import UnitTestBase + + +class UnitTest_nodejs22_x_cookiecutter_aws_sam_hello_nodejs(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/hello" + code_directories = ["hello-world"] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_aws_sam_step_functions_sample_app(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/step-func" + code_directories = [ + "functions/stock-buyer", + "functions/stock-checker", + "functions/stock-seller", + ] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_quick_start_from_scratch(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/scratch" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_quick_start_cloudwatch_events(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/cw-event" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_quick_start_response_streaming(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/response-streaming" + code_directories = ["src"] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_quick_start_s3(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/s3" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_quick_start_sns(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/sns" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_quick_start_sqs(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/sqs" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_quick_start_web(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/web" + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_quick_start_full_stack(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/full-stack" + code_directories = ["backend", "frontend"] + # TODO: remove the line remove once python3.13 is GA + should_test_lint = False + + +class UnitTest_nodejs22_x_cookiecutter_aws_sam_gql_quick_start(UnitTestBase.NodejsUnitTestBase): + directory = "nodejs22.x/hello-gql" + code_directories = ["greeter"] + should_test_lint = False From c6989e9246f367c10075888e5e8eab0d694e527c Mon Sep 17 00:00:00 2001 From: Haresh Nasit Date: Mon, 21 Oct 2024 15:41:07 -0700 Subject: [PATCH 02/28] chore: update comments --- .../build_invoke/node/test_node_22.py | 24 +++++++++---------- .../unit_test/test_unit_test_nodejs22_x.py | 21 ++++++++-------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/tests/integration/build_invoke/node/test_node_22.py b/tests/integration/build_invoke/node/test_node_22.py index 2e95e037a..62583366f 100644 --- a/tests/integration/build_invoke/node/test_node_22.py +++ b/tests/integration/build_invoke/node/test_node_22.py @@ -9,60 +9,60 @@ class BuildInvoke_nodejs22_x_cookiecutter_aws_sam_hello_nodejs(BuildInvokeBase.SimpleHelloWorldBuildInvokeBase): directory = "nodejs22.x/hello" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_aws_sam_step_functions_sample_app(BuildInvokeBase.BuildInvokeBase): directory = "nodejs22.x/step-func" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_quick_start_from_scratch(BuildInvokeBase.BuildInvokeBase): directory = "nodejs22.x/scratch" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_quick_start_cloudwatch_events(BuildInvokeBase.BuildInvokeBase): directory = "nodejs22.x/cw-event" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_quick_start_s3(BuildInvokeBase.BuildInvokeBase): directory = "nodejs22.x/s3" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_quick_start_sns(BuildInvokeBase.BuildInvokeBase): directory = "nodejs22.x/sns" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_quick_start_sqs(BuildInvokeBase.BuildInvokeBase): directory = "nodejs22.x/sqs" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_quick_start_response_streaming(BuildInvokeBase.BuildInvokeBase): directory = "nodejs22.x/response-streaming" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_quick_start_web(BuildInvokeBase.QuickStartWebBuildInvokeBase): directory = "nodejs22.x/web" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_quick_start_full_stack(BuildInvokeBase.QuickStartWebBuildInvokeBase): directory = "nodejs22.x/full-stack" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False @@ -70,11 +70,11 @@ class BuildInvoke_image_nodejs22_x_cookiecutter_aws_sam_hello_nodejs_lambda_imag BuildInvokeBase.SimpleHelloWorldBuildInvokeBase ): directory = "nodejs22.x/hello-img" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class BuildInvoke_nodejs22_x_cookiecutter_aws_sam_gql_quick_start(BuildInvokeBase.BuildInvokeBase): directory = "nodejs22.x/hello-gql" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False diff --git a/tests/integration/unit_test/test_unit_test_nodejs22_x.py b/tests/integration/unit_test/test_unit_test_nodejs22_x.py index 1d4477d76..6245cecb2 100644 --- a/tests/integration/unit_test/test_unit_test_nodejs22_x.py +++ b/tests/integration/unit_test/test_unit_test_nodejs22_x.py @@ -4,7 +4,7 @@ class UnitTest_nodejs22_x_cookiecutter_aws_sam_hello_nodejs(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/hello" code_directories = ["hello-world"] - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False @@ -15,61 +15,62 @@ class UnitTest_nodejs22_x_cookiecutter_aws_sam_step_functions_sample_app(UnitTes "functions/stock-checker", "functions/stock-seller", ] - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class UnitTest_nodejs22_x_cookiecutter_quick_start_from_scratch(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/scratch" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class UnitTest_nodejs22_x_cookiecutter_quick_start_cloudwatch_events(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/cw-event" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class UnitTest_nodejs22_x_cookiecutter_quick_start_response_streaming(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/response-streaming" code_directories = ["src"] - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class UnitTest_nodejs22_x_cookiecutter_quick_start_s3(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/s3" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class UnitTest_nodejs22_x_cookiecutter_quick_start_sns(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/sns" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class UnitTest_nodejs22_x_cookiecutter_quick_start_sqs(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/sqs" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class UnitTest_nodejs22_x_cookiecutter_quick_start_web(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/web" - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class UnitTest_nodejs22_x_cookiecutter_quick_start_full_stack(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/full-stack" code_directories = ["backend", "frontend"] - # TODO: remove the line remove once python3.13 is GA + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False class UnitTest_nodejs22_x_cookiecutter_aws_sam_gql_quick_start(UnitTestBase.NodejsUnitTestBase): directory = "nodejs22.x/hello-gql" code_directories = ["greeter"] + # TODO: remove the line remove once nodejs22.x is GA should_test_lint = False From 38506b6b25152c6f635ec6dd12a7de54125f510e Mon Sep 17 00:00:00 2001 From: Haresh Nasit Date: Sat, 16 Nov 2024 13:57:32 -0800 Subject: [PATCH 03/28] Fix gql app tests and bump happy-dom version --- .../{{cookiecutter.project_name}}/frontend/package.json | 2 +- .../greeter/tests/unit/test-handler.mjs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json index ddc58ceda..165afa073 100644 --- a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/frontend/package.json @@ -25,7 +25,7 @@ "babel-eslint": "^10.1.0", "eslint": "^7.32.0", "eslint-plugin-vue": "^7.18.0", - "happy-dom": "^9.20.3", + "happy-dom": "^15.10.2", "vitest": "^0.31.1", "vue-template-compiler": "^2.6.14" }, diff --git a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/unit/test-handler.mjs b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/unit/test-handler.mjs index c59093c6f..6a53f78b4 100644 --- a/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/unit/test-handler.mjs +++ b/nodejs22.x/hello-gql/{{cookiecutter.project_name}}/greeter/tests/unit/test-handler.mjs @@ -2,8 +2,10 @@ import { lambdaHandler } from "../../app.mjs"; import { expect } from "chai"; +import { createRequire } from 'module'; -import event from "../events/appsync.json" assert { type: "json" }; +const require = createRequire(import.meta.url); +const event = require('../events/appsync.json'); const context = {}; From 838ee017b0d07237e08a2c1104e30b5e5e909dbd Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:42:53 -0800 Subject: [PATCH 04/28] Update nodejs22.x/cw-event/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/cw-event/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/cw-event/README.md b/nodejs22.x/cw-event/README.md index 024e2272e..bed29659a 100644 --- a/nodejs22.x/cw-event/README.md +++ b/nodejs22.x/cw-event/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS CloudWatch Events Quick Start Applica Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-cloudwatch-events --name cwe-app` +* **NodeJS 22**: `sam init --runtime nodejs22.x --app-template quick-start-cloudwatch-events --name cwe-app` > **NOTE**: ``--name`` allows you to specify a different project folder name From ca9798768b2c6d3714b79c81dc5153079c535ec6 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:43:00 -0800 Subject: [PATCH 05/28] Update nodejs22.x/full-stack/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/full-stack/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/full-stack/README.md b/nodejs22.x/full-stack/README.md index cebaeea0a..62e42badd 100644 --- a/nodejs22.x/full-stack/README.md +++ b/nodejs22.x/full-stack/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS Quick Start Web Application using [Se Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-full-stack --name full-stack-app` +* **NodeJS 22**: `sam init --runtime nodejs22.x --app-template quick-start-full-stack --name full-stack-app` > **NOTE**: ``--name`` allows you to specify a different project folder name From b4e2da774809d5f3f024e4b8f7629cd850770ba0 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:43:14 -0800 Subject: [PATCH 06/28] Update nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md index c76375afe..17c6d2267 100644 --- a/nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/full-stack/{{cookiecutter.project_name}}/README.md @@ -43,7 +43,7 @@ The AWS SAM CLI is an extension of the AWS CLI that adds functionality for build To use the AWS SAM CLI, you need the following tools: * AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the npm package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). To build and deploy your application for the first time, run the following in your shell: From 04fd321dd5faf18570b61d1f5c9c1b52ce799678 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:43:20 -0800 Subject: [PATCH 07/28] Update nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md index 0cf5f61f2..f730e0d1d 100644 --- a/nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/cw-event/{{cookiecutter.project_name}}/README.md @@ -33,7 +33,7 @@ The AWS SAM CLI is an extension of the AWS CLI that adds functionality for build To use the AWS SAM CLI, you need the following tools: * AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the npm package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). To build and deploy your application for the first time, run the following in your shell: From 963f22c86ef187784cd84de3aa0e3c96ed21a84c Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:43:25 -0800 Subject: [PATCH 08/28] Update nodejs22.x/hello-img/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/hello-img/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/hello-img/README.md b/nodejs22.x/hello-img/README.md index 478057812..58ce0733c 100644 --- a/nodejs22.x/hello-img/README.md +++ b/nodejs22.x/hello-img/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS Hello world boilerplate using [Server Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x` +* **NodeJS 22**: `sam init --runtime nodejs22.x` > **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) From afbb7840398dbdd0c2236ac1198215a2ff4c3a18 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:43:35 -0800 Subject: [PATCH 09/28] Update nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md index 00031caee..e51c65a5c 100644 --- a/nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/hello-img/{{cookiecutter.project_name}}/README.md @@ -20,7 +20,7 @@ To use the SAM CLI, you need the following tools. You may need the following for local testing. -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the NPM package management tool. To build and deploy your application for the first time, run the following in your shell: From 24a85489c73d2c4cffd5df176f2789b4f1ef5930 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:43:41 -0800 Subject: [PATCH 10/28] Update nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md b/nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md index 0d9374a96..5f0fef20c 100644 --- a/nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/sqs/{{cookiecutter.project_name}}/README.md @@ -33,7 +33,7 @@ The AWS SAM CLI is an extension of the AWS CLI that adds functionality for build To use the AWS SAM CLI, you need the following tools: * AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the npm package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). To build and deploy your application for the first time, run the following in your shell: From acce2fabe64220f5ae42e96d3789d069b1aeac03 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:43:46 -0800 Subject: [PATCH 11/28] Update nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md b/nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md index b298e4784..8ba8ea4cf 100644 --- a/nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/step-func/{{cookiecutter.project_name}}/README.md @@ -35,7 +35,7 @@ The Serverless Application Model Command Line Interface (SAM CLI) is an extensio To use the SAM CLI, you need the following tools: * SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the NPM package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) To build and deploy your application for the first time, run the following in your shell: From 7ff3886bb45de8edccd1253cdf668a637cd26c66 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:43:52 -0800 Subject: [PATCH 12/28] Update nodejs22.x/step-func/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/step-func/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/step-func/README.md b/nodejs22.x/step-func/README.md index 33cc327c9..a97a06707 100644 --- a/nodejs22.x/step-func/README.md +++ b/nodejs22.x/step-func/README.md @@ -13,7 +13,7 @@ This application creates a mock stock trading workflow which runs on a pre-defin Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x` +* **NodeJS 22**: `sam init --runtime nodejs22.x` > **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) From 63f4c6e7d8858210108b5111128486741173e6c4 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:43:59 -0800 Subject: [PATCH 13/28] Update nodejs22.x/web/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/web/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/web/{{cookiecutter.project_name}}/README.md b/nodejs22.x/web/{{cookiecutter.project_name}}/README.md index 1041d4ece..7a386155c 100644 --- a/nodejs22.x/web/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/web/{{cookiecutter.project_name}}/README.md @@ -33,7 +33,7 @@ The AWS SAM CLI is an extension of the AWS CLI that adds functionality for build To use the AWS SAM CLI, you need the following tools: * AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the npm package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). To build and deploy your application for the first time, run the following in your shell: From 4a34e8ee3b4c4191ea60880b43576d74ef9865b1 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:44:05 -0800 Subject: [PATCH 14/28] Update nodejs22.x/web/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/web/README.md b/nodejs22.x/web/README.md index 2290fdc5d..628f3d70b 100644 --- a/nodejs22.x/web/README.md +++ b/nodejs22.x/web/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS Quick Start Web Application using [Se Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-web --name web-app` +* **NodeJS 22**: `sam init --runtime nodejs22.x --app-template quick-start-web --name web-app` > **NOTE**: ``--name`` allows you to specify a different project folder name From 2b2b1aa083e305c88fc30d54da6383b82007adfe Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:44:28 -0800 Subject: [PATCH 15/28] Update nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md index fecf4d780..f6482f29e 100644 --- a/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/hello-ts-pt/{{cookiecutter.project_name}}/README.md @@ -85,7 +85,7 @@ The Serverless Application Model Command Line Interface (SAM CLI) is an extensio To use the SAM CLI, you need the following tools. * SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the NPM package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) To build and deploy your application for the first time, run the following in your shell: From c6ce79ee601c38041438a69db63dd561176b100d Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:44:41 -0800 Subject: [PATCH 16/28] Update nodejs22.x/hello-ts/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/hello-ts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/hello-ts/README.md b/nodejs22.x/hello-ts/README.md index 9eada2829..21aad6c88 100644 --- a/nodejs22.x/hello-ts/README.md +++ b/nodejs22.x/hello-ts/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS TypeScript Hello world boilerplate us Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x` +* **NodeJS 22**: `sam init --runtime nodejs22.x` > **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) From 33962c6e1b84f4e7a8819d1fc9873746b8474105 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:45:02 -0800 Subject: [PATCH 17/28] Update nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md index 26cd83248..be35eb981 100644 --- a/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/hello-ts/{{cookiecutter.project_name}}/README.md @@ -31,7 +31,7 @@ The Serverless Application Model Command Line Interface (SAM CLI) is an extensio To use the SAM CLI, you need the following tools. * SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the NPM package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) To build and deploy your application for the first time, run the following in your shell: From 7210673e9f001668b0f30142ac89f53c171baeba Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:45:18 -0800 Subject: [PATCH 18/28] Update nodejs22.x/sqs/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/sqs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/sqs/README.md b/nodejs22.x/sqs/README.md index 890e00190..e8143e99a 100644 --- a/nodejs22.x/sqs/README.md +++ b/nodejs22.x/sqs/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS SQS Quick Start Application using [Se Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-sqs --name sqs-app` +* **NodeJS 22**: `sam init --runtime nodejs22.x --app-template quick-start-sqs --name sqs-app` > **NOTE**: ``--name`` allows you to specify a different project folder name From 0995ee8d909d52101e300e18e00fac5916c5e4e5 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:45:37 -0800 Subject: [PATCH 19/28] Update nodejs22.x/hello/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/hello/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/hello/README.md b/nodejs22.x/hello/README.md index 478057812..58ce0733c 100644 --- a/nodejs22.x/hello/README.md +++ b/nodejs22.x/hello/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS Hello world boilerplate using [Server Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x` +* **NodeJS 22**: `sam init --runtime nodejs22.x` > **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) From 59b11675b6fd251fdc851a22287f2e03719791cd Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:45:50 -0800 Subject: [PATCH 20/28] Update nodejs22.x/sns/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/sns/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/sns/{{cookiecutter.project_name}}/README.md b/nodejs22.x/sns/{{cookiecutter.project_name}}/README.md index 3424d48b9..425871001 100644 --- a/nodejs22.x/sns/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/sns/{{cookiecutter.project_name}}/README.md @@ -33,7 +33,7 @@ The AWS SAM CLI is an extension of the AWS CLI that adds functionality for build To use the AWS SAM CLI, you need the following tools: * AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the npm package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). To build and deploy your application for the first time, run the following in your shell: From fbc76a098fba151cb55a078ea7e27b36c74c1108 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:45:59 -0800 Subject: [PATCH 21/28] Update nodejs22.x/hello/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/hello/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/hello/{{cookiecutter.project_name}}/README.md b/nodejs22.x/hello/{{cookiecutter.project_name}}/README.md index 9b53d9910..b4f741cae 100644 --- a/nodejs22.x/hello/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/hello/{{cookiecutter.project_name}}/README.md @@ -31,7 +31,7 @@ The Serverless Application Model Command Line Interface (SAM CLI) is an extensio To use the SAM CLI, you need the following tools. * SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the NPM package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the NPM package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) To build and deploy your application for the first time, run the following in your shell: From 42626442e428797b71e34b210d90e88a46d3293b Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:46:11 -0800 Subject: [PATCH 22/28] Update nodejs22.x/response-streaming/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/response-streaming/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/response-streaming/README.md b/nodejs22.x/response-streaming/README.md index 478057812..58ce0733c 100644 --- a/nodejs22.x/response-streaming/README.md +++ b/nodejs22.x/response-streaming/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS Hello world boilerplate using [Server Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x` +* **NodeJS 22**: `sam init --runtime nodejs22.x` > **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) From da497182f6f6a5b303a7d9e6d13bad24adc96fc2 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:46:28 -0800 Subject: [PATCH 23/28] Update nodejs22.x/sns/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/sns/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/sns/README.md b/nodejs22.x/sns/README.md index 249b36007..0369c4ee7 100644 --- a/nodejs22.x/sns/README.md +++ b/nodejs22.x/sns/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS SNS Quick Start Application using [Se Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-sns --name sns-app` +* **NodeJS 22**: `sam init --runtime nodejs22.x --app-template quick-start-sns --name sns-app` > **NOTE**: ``--name`` allows you to specify a different project folder name From cc38b2ab61e65fa2d578a136556d853a1f846452 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:46:38 -0800 Subject: [PATCH 24/28] Update nodejs22.x/response-streaming/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- .../response-streaming/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/README.md b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/README.md index b6a5a687d..479e6e861 100644 --- a/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/response-streaming/{{cookiecutter.project_name}}/README.md @@ -32,7 +32,7 @@ The AWS SAM CLI is an extension of the AWS CLI that adds functionality for build To use the AWS SAM CLI, you need the following tools: * AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the npm package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). To build and deploy your application for the first time, run the following in your shell: From 956a1c5b97ef3b0dabab31f3420d52b869876f01 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:46:51 -0800 Subject: [PATCH 25/28] Update nodejs22.x/s3/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/s3/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/s3/README.md b/nodejs22.x/s3/README.md index 255dbbee9..0627aa6ab 100644 --- a/nodejs22.x/s3/README.md +++ b/nodejs22.x/s3/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS S3 Quick Start Application using [Ser Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-s3 --name s3-app` +* **NodeJS 22**: `sam init --runtime nodejs22.x --app-template quick-start-s3 --name s3-app` > **NOTE**: ``--name`` allows you to specify a different project folder name From a08bdf72760d2e718c475f9fe44384595c5d19b3 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:47:04 -0800 Subject: [PATCH 26/28] Update nodejs22.x/s3/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/s3/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/s3/{{cookiecutter.project_name}}/README.md b/nodejs22.x/s3/{{cookiecutter.project_name}}/README.md index c91f8acba..0020be236 100644 --- a/nodejs22.x/s3/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/s3/{{cookiecutter.project_name}}/README.md @@ -33,7 +33,7 @@ The AWS SAM CLI is an extension of the AWS CLI that adds functionality for build To use the AWS SAM CLI, you need the following tools: * AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the npm package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). To build and deploy your application for the first time, run the following in your shell: From e4f7b909c07987d281b8b379e8c250fb0d7ad688 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:47:14 -0800 Subject: [PATCH 27/28] Update nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md b/nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md index d6bd097b3..fe8764ad1 100644 --- a/nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md +++ b/nodejs22.x/scratch/{{cookiecutter.project_name}}/README.md @@ -33,7 +33,7 @@ The AWS SAM CLI is an extension of the AWS CLI that adds functionality for build To use the AWS SAM CLI, you need the following tools: * AWS SAM CLI - [Install the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). -* Node.js - [Install Node.js 20](https://nodejs.org/en/), including the npm package management tool. +* Node.js - [Install Node.js 22](https://nodejs.org/en/), including the npm package management tool. * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community). To build and deploy your application for the first time, run the following in your shell: From 51bfcd3e8de343cbf043a7c0b1978750dc602bf6 Mon Sep 17 00:00:00 2001 From: Haresh Nasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:47:23 -0800 Subject: [PATCH 28/28] Update nodejs22.x/scratch/README.md Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> --- nodejs22.x/scratch/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs22.x/scratch/README.md b/nodejs22.x/scratch/README.md index f6caedd36..71e0f421d 100644 --- a/nodejs22.x/scratch/README.md +++ b/nodejs22.x/scratch/README.md @@ -10,7 +10,7 @@ A cookiecutter template to create a NodeJS Basic Quick Start Application using [ Generate a boilerplate template in your current project directory using the following syntax: -* **NodeJS 20**: `sam init --runtime nodejs22.x --app-template quick-start-from-scratch --name sam-app` +* **NodeJS 22**: `sam init --runtime nodejs22.x --app-template quick-start-from-scratch --name sam-app` > **NOTE**: ``--name`` allows you to specify a different project folder name