├── .eslintrc.json ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── auto-queue.yml │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ ├── upgrade-cdklabs-projen-project-types-main.yml │ ├── upgrade-dev-deps-main.yml │ └── upgrade-main.yml ├── .gitignore ├── .npmignore ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.ts ├── API.md ├── LICENSE ├── README.md ├── lambda ├── .gitignore ├── index.js ├── render-test.js ├── render.js └── stylesheet.js ├── package.json ├── rosetta └── default.ts-fixture ├── src ├── index.ts └── table-viewer.ts ├── test ├── __snapshots__ │ └── viewer.test.ts.snap ├── sample │ ├── .gitignore │ ├── cdk.json │ └── index.ts └── viewer.test.ts ├── tsconfig.dev.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "env": { 4 | "jest": true, 5 | "node": true 6 | }, 7 | "root": true, 8 | "plugins": [ 9 | "@typescript-eslint", 10 | "import", 11 | "@stylistic" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaVersion": 2018, 16 | "sourceType": "module", 17 | "project": "./tsconfig.dev.json" 18 | }, 19 | "extends": [ 20 | "plugin:import/typescript" 21 | ], 22 | "settings": { 23 | "import/parsers": { 24 | "@typescript-eslint/parser": [ 25 | ".ts", 26 | ".tsx" 27 | ] 28 | }, 29 | "import/resolver": { 30 | "node": {}, 31 | "typescript": { 32 | "project": "./tsconfig.dev.json", 33 | "alwaysTryTypes": true 34 | } 35 | } 36 | }, 37 | "ignorePatterns": [ 38 | "*.js", 39 | "*.d.ts", 40 | "node_modules/", 41 | "*.generated.ts", 42 | "coverage", 43 | "!.projenrc.ts", 44 | "!projenrc/**/*.ts" 45 | ], 46 | "rules": { 47 | "@stylistic/indent": [ 48 | "error", 49 | 2 50 | ], 51 | "@stylistic/quotes": [ 52 | "error", 53 | "single", 54 | { 55 | "avoidEscape": true 56 | } 57 | ], 58 | "@stylistic/comma-dangle": [ 59 | "error", 60 | "always-multiline" 61 | ], 62 | "@stylistic/comma-spacing": [ 63 | "error", 64 | { 65 | "before": false, 66 | "after": true 67 | } 68 | ], 69 | "@stylistic/no-multi-spaces": [ 70 | "error", 71 | { 72 | "ignoreEOLComments": false 73 | } 74 | ], 75 | "@stylistic/array-bracket-spacing": [ 76 | "error", 77 | "never" 78 | ], 79 | "@stylistic/array-bracket-newline": [ 80 | "error", 81 | "consistent" 82 | ], 83 | "@stylistic/object-curly-spacing": [ 84 | "error", 85 | "always" 86 | ], 87 | "@stylistic/object-curly-newline": [ 88 | "error", 89 | { 90 | "multiline": true, 91 | "consistent": true 92 | } 93 | ], 94 | "@stylistic/object-property-newline": [ 95 | "error", 96 | { 97 | "allowAllPropertiesOnSameLine": true 98 | } 99 | ], 100 | "@stylistic/keyword-spacing": [ 101 | "error" 102 | ], 103 | "@stylistic/brace-style": [ 104 | "error", 105 | "1tbs", 106 | { 107 | "allowSingleLine": true 108 | } 109 | ], 110 | "@stylistic/space-before-blocks": [ 111 | "error" 112 | ], 113 | "@stylistic/member-delimiter-style": [ 114 | "error" 115 | ], 116 | "@stylistic/semi": [ 117 | "error", 118 | "always" 119 | ], 120 | "@stylistic/max-len": [ 121 | "error", 122 | { 123 | "code": 150, 124 | "ignoreUrls": true, 125 | "ignoreStrings": true, 126 | "ignoreTemplateLiterals": true, 127 | "ignoreComments": true, 128 | "ignoreRegExpLiterals": true 129 | } 130 | ], 131 | "@stylistic/quote-props": [ 132 | "error", 133 | "consistent-as-needed" 134 | ], 135 | "@stylistic/key-spacing": [ 136 | "error" 137 | ], 138 | "@stylistic/no-multiple-empty-lines": [ 139 | "error" 140 | ], 141 | "@stylistic/no-trailing-spaces": [ 142 | "error" 143 | ], 144 | "curly": [ 145 | "error", 146 | "multi-line", 147 | "consistent" 148 | ], 149 | "@typescript-eslint/no-require-imports": "error", 150 | "import/no-extraneous-dependencies": [ 151 | "error", 152 | { 153 | "devDependencies": [ 154 | "**/test/**", 155 | "**/build-tools/**", 156 | ".projenrc.ts", 157 | "projenrc/**/*.ts" 158 | ], 159 | "optionalDependencies": false, 160 | "peerDependencies": true 161 | } 162 | ], 163 | "import/no-unresolved": [ 164 | "error" 165 | ], 166 | "import/order": [ 167 | "warn", 168 | { 169 | "groups": [ 170 | "builtin", 171 | "external" 172 | ], 173 | "alphabetize": { 174 | "order": "asc", 175 | "caseInsensitive": true 176 | } 177 | } 178 | ], 179 | "import/no-duplicates": [ 180 | "error" 181 | ], 182 | "no-shadow": [ 183 | "off" 184 | ], 185 | "@typescript-eslint/no-shadow": "error", 186 | "@typescript-eslint/no-floating-promises": "error", 187 | "no-return-await": [ 188 | "off" 189 | ], 190 | "@typescript-eslint/return-await": "error", 191 | "dot-notation": [ 192 | "error" 193 | ], 194 | "no-bitwise": [ 195 | "error" 196 | ], 197 | "@typescript-eslint/member-ordering": [ 198 | "error", 199 | { 200 | "default": [ 201 | "public-static-field", 202 | "public-static-method", 203 | "protected-static-field", 204 | "protected-static-method", 205 | "private-static-field", 206 | "private-static-method", 207 | "field", 208 | "constructor", 209 | "method" 210 | ] 211 | } 212 | ] 213 | }, 214 | "overrides": [ 215 | { 216 | "files": [ 217 | ".projenrc.ts" 218 | ], 219 | "rules": { 220 | "@typescript-eslint/no-require-imports": "off", 221 | "import/no-extraneous-dependencies": "off" 222 | } 223 | } 224 | ] 225 | } 226 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | * text=auto eol=lf 4 | *.snap linguist-generated 5 | /.eslintrc.json linguist-generated 6 | /.gitattributes linguist-generated 7 | /.github/pull_request_template.md linguist-generated 8 | /.github/workflows/auto-approve.yml linguist-generated 9 | /.github/workflows/auto-queue.yml linguist-generated 10 | /.github/workflows/build.yml linguist-generated 11 | /.github/workflows/pull-request-lint.yml linguist-generated 12 | /.github/workflows/release.yml linguist-generated 13 | /.github/workflows/upgrade-cdklabs-projen-project-types-main.yml linguist-generated 14 | /.github/workflows/upgrade-dev-deps-main.yml linguist-generated 15 | /.github/workflows/upgrade-main.yml linguist-generated 16 | /.gitignore linguist-generated 17 | /.npmignore linguist-generated 18 | /.projen/** linguist-generated 19 | /.projen/deps.json linguist-generated 20 | /.projen/files.json linguist-generated 21 | /.projen/tasks.json linguist-generated 22 | /API.md linguist-generated 23 | /LICENSE linguist-generated 24 | /package.json linguist-generated 25 | /tsconfig.dev.json linguist-generated 26 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'cdklabs-automation' || github.event.pull_request.user.login == 'dependabot[bot]') 18 | steps: 19 | - uses: hmarr/auto-approve-action@v2.2.1 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/auto-queue.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: auto-queue 4 | on: 5 | pull_request_target: 6 | types: 7 | - opened 8 | - reopened 9 | - ready_for_review 10 | jobs: 11 | enableAutoQueue: 12 | name: "Set AutoQueue on PR #${{ github.event.number }}" 13 | runs-on: ubuntu-latest 14 | permissions: 15 | pull-requests: write 16 | contents: write 17 | steps: 18 | - uses: peter-evans/enable-pull-request-automerge@v3 19 | with: 20 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 21 | pull-request-number: ${{ github.event.number }} 22 | merge-method: squash 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: build 4 | on: 5 | pull_request: {} 6 | workflow_dispatch: {} 7 | merge_group: {} 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | outputs: 14 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 15 | env: 16 | CI: "true" 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | ref: ${{ github.event.pull_request.head.ref }} 22 | repository: ${{ github.event.pull_request.head.repo.full_name }} 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: lts/* 27 | - name: Install dependencies 28 | run: yarn install --check-files 29 | - name: build 30 | run: npx projen build 31 | - name: Find mutations 32 | id: self_mutation 33 | run: |- 34 | git add . 35 | git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 36 | working-directory: ./ 37 | - name: Upload patch 38 | if: steps.self_mutation.outputs.self_mutation_happened 39 | uses: actions/upload-artifact@v4.4.0 40 | with: 41 | name: repo.patch 42 | path: repo.patch 43 | overwrite: true 44 | - name: Fail build on mutation 45 | if: steps.self_mutation.outputs.self_mutation_happened 46 | run: |- 47 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 48 | cat repo.patch 49 | exit 1 50 | - name: Backup artifact permissions 51 | run: cd dist && getfacl -R . > permissions-backup.acl 52 | continue-on-error: true 53 | - name: Upload artifact 54 | uses: actions/upload-artifact@v4.4.0 55 | with: 56 | name: build-artifact 57 | path: dist 58 | overwrite: true 59 | self-mutation: 60 | needs: build 61 | runs-on: ubuntu-latest 62 | permissions: 63 | contents: write 64 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 65 | steps: 66 | - name: Checkout 67 | uses: actions/checkout@v4 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | ref: ${{ github.event.pull_request.head.ref }} 71 | repository: ${{ github.event.pull_request.head.repo.full_name }} 72 | - name: Download patch 73 | uses: actions/download-artifact@v4 74 | with: 75 | name: repo.patch 76 | path: ${{ runner.temp }} 77 | - name: Apply patch 78 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 79 | - name: Set git identity 80 | run: |- 81 | git config user.name "github-actions" 82 | git config user.email "github-actions@github.com" 83 | - name: Push changes 84 | env: 85 | PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }} 86 | run: |- 87 | git add . 88 | git commit -s -m "chore: self mutation" 89 | git push origin HEAD:$PULL_REQUEST_REF 90 | package-js: 91 | needs: build 92 | runs-on: ubuntu-latest 93 | permissions: 94 | contents: read 95 | if: ${{ !needs.build.outputs.self_mutation_happened }} 96 | steps: 97 | - uses: actions/setup-node@v4 98 | with: 99 | node-version: lts/* 100 | - name: Download build artifacts 101 | uses: actions/download-artifact@v4 102 | with: 103 | name: build-artifact 104 | path: dist 105 | - name: Restore build artifact permissions 106 | run: cd dist && setfacl --restore=permissions-backup.acl 107 | continue-on-error: true 108 | - name: Checkout 109 | uses: actions/checkout@v4 110 | with: 111 | ref: ${{ github.event.pull_request.head.ref }} 112 | repository: ${{ github.event.pull_request.head.repo.full_name }} 113 | path: .repo 114 | - name: Install Dependencies 115 | run: cd .repo && yarn install --check-files --frozen-lockfile 116 | - name: Extract build artifact 117 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 118 | - name: Move build artifact out of the way 119 | run: mv dist dist.old 120 | - name: Create js artifact 121 | run: cd .repo && npx projen package:js 122 | - name: Collect js artifact 123 | run: mv .repo/dist dist 124 | package-java: 125 | needs: build 126 | runs-on: ubuntu-latest 127 | permissions: 128 | contents: read 129 | if: ${{ !needs.build.outputs.self_mutation_happened }} 130 | steps: 131 | - uses: actions/setup-java@v4 132 | with: 133 | distribution: corretto 134 | java-version: "11" 135 | - uses: actions/setup-node@v4 136 | with: 137 | node-version: lts/* 138 | - name: Download build artifacts 139 | uses: actions/download-artifact@v4 140 | with: 141 | name: build-artifact 142 | path: dist 143 | - name: Restore build artifact permissions 144 | run: cd dist && setfacl --restore=permissions-backup.acl 145 | continue-on-error: true 146 | - name: Checkout 147 | uses: actions/checkout@v4 148 | with: 149 | ref: ${{ github.event.pull_request.head.ref }} 150 | repository: ${{ github.event.pull_request.head.repo.full_name }} 151 | path: .repo 152 | - name: Install Dependencies 153 | run: cd .repo && yarn install --check-files --frozen-lockfile 154 | - name: Extract build artifact 155 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 156 | - name: Move build artifact out of the way 157 | run: mv dist dist.old 158 | - name: Create java artifact 159 | run: cd .repo && npx projen package:java 160 | - name: Collect java artifact 161 | run: mv .repo/dist dist 162 | package-python: 163 | needs: build 164 | runs-on: ubuntu-latest 165 | permissions: 166 | contents: read 167 | if: ${{ !needs.build.outputs.self_mutation_happened }} 168 | steps: 169 | - uses: actions/setup-node@v4 170 | with: 171 | node-version: lts/* 172 | - uses: actions/setup-python@v5 173 | with: 174 | python-version: 3.x 175 | - name: Download build artifacts 176 | uses: actions/download-artifact@v4 177 | with: 178 | name: build-artifact 179 | path: dist 180 | - name: Restore build artifact permissions 181 | run: cd dist && setfacl --restore=permissions-backup.acl 182 | continue-on-error: true 183 | - name: Checkout 184 | uses: actions/checkout@v4 185 | with: 186 | ref: ${{ github.event.pull_request.head.ref }} 187 | repository: ${{ github.event.pull_request.head.repo.full_name }} 188 | path: .repo 189 | - name: Install Dependencies 190 | run: cd .repo && yarn install --check-files --frozen-lockfile 191 | - name: Extract build artifact 192 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 193 | - name: Move build artifact out of the way 194 | run: mv dist dist.old 195 | - name: Create python artifact 196 | run: cd .repo && npx projen package:python 197 | - name: Collect python artifact 198 | run: mv .repo/dist dist 199 | package-dotnet: 200 | needs: build 201 | runs-on: ubuntu-latest 202 | permissions: 203 | contents: read 204 | if: ${{ !needs.build.outputs.self_mutation_happened }} 205 | steps: 206 | - uses: actions/setup-node@v4 207 | with: 208 | node-version: lts/* 209 | - uses: actions/setup-dotnet@v4 210 | with: 211 | dotnet-version: 6.x 212 | - name: Download build artifacts 213 | uses: actions/download-artifact@v4 214 | with: 215 | name: build-artifact 216 | path: dist 217 | - name: Restore build artifact permissions 218 | run: cd dist && setfacl --restore=permissions-backup.acl 219 | continue-on-error: true 220 | - name: Checkout 221 | uses: actions/checkout@v4 222 | with: 223 | ref: ${{ github.event.pull_request.head.ref }} 224 | repository: ${{ github.event.pull_request.head.repo.full_name }} 225 | path: .repo 226 | - name: Install Dependencies 227 | run: cd .repo && yarn install --check-files --frozen-lockfile 228 | - name: Extract build artifact 229 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 230 | - name: Move build artifact out of the way 231 | run: mv dist dist.old 232 | - name: Create dotnet artifact 233 | run: cd .repo && npx projen package:dotnet 234 | - name: Collect dotnet artifact 235 | run: mv .repo/dist dist 236 | package-go: 237 | needs: build 238 | runs-on: ubuntu-latest 239 | permissions: 240 | contents: read 241 | if: ${{ !needs.build.outputs.self_mutation_happened }} 242 | steps: 243 | - uses: actions/setup-node@v4 244 | with: 245 | node-version: lts/* 246 | - uses: actions/setup-go@v5 247 | with: 248 | go-version: ^1.18.0 249 | - name: Download build artifacts 250 | uses: actions/download-artifact@v4 251 | with: 252 | name: build-artifact 253 | path: dist 254 | - name: Restore build artifact permissions 255 | run: cd dist && setfacl --restore=permissions-backup.acl 256 | continue-on-error: true 257 | - name: Checkout 258 | uses: actions/checkout@v4 259 | with: 260 | ref: ${{ github.event.pull_request.head.ref }} 261 | repository: ${{ github.event.pull_request.head.repo.full_name }} 262 | path: .repo 263 | - name: Install Dependencies 264 | run: cd .repo && yarn install --check-files --frozen-lockfile 265 | - name: Extract build artifact 266 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 267 | - name: Move build artifact out of the way 268 | run: mv dist dist.old 269 | - name: Create go artifact 270 | run: cd .repo && npx projen package:go 271 | - name: Collect go artifact 272 | run: mv .repo/dist dist 273 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | merge_group: {} 14 | jobs: 15 | validate: 16 | name: Validate PR title 17 | runs-on: ubuntu-latest 18 | permissions: 19 | pull-requests: write 20 | if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') 21 | steps: 22 | - uses: amannn/action-semantic-pull-request@v5.4.0 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | types: |- 27 | feat 28 | fix 29 | chore 30 | requireScope: false 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: release 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: false 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | outputs: 18 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 19 | tag_exists: ${{ steps.check_tag_exists.outputs.exists }} 20 | env: 21 | CI: "true" 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | - name: Set git identity 28 | run: |- 29 | git config user.name "github-actions" 30 | git config user.email "github-actions@github.com" 31 | - name: Setup Node.js 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: lts/* 35 | - name: Install dependencies 36 | run: yarn install --check-files --frozen-lockfile 37 | - name: release 38 | run: npx projen release 39 | - name: Check if version has already been tagged 40 | id: check_tag_exists 41 | run: |- 42 | TAG=$(cat dist/releasetag.txt) 43 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 44 | cat $GITHUB_OUTPUT 45 | - name: Check for new commits 46 | id: git_remote 47 | run: |- 48 | echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 49 | cat $GITHUB_OUTPUT 50 | - name: Backup artifact permissions 51 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 52 | run: cd dist && getfacl -R . > permissions-backup.acl 53 | continue-on-error: true 54 | - name: Upload artifact 55 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 56 | uses: actions/upload-artifact@v4.4.0 57 | with: 58 | name: build-artifact 59 | path: dist 60 | overwrite: true 61 | release_github: 62 | name: Publish to GitHub Releases 63 | needs: 64 | - release 65 | - release_npm 66 | - release_maven 67 | - release_pypi 68 | - release_nuget 69 | - release_golang 70 | runs-on: ubuntu-latest 71 | permissions: 72 | contents: write 73 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 74 | steps: 75 | - uses: actions/setup-node@v4 76 | with: 77 | node-version: lts/* 78 | - name: Download build artifacts 79 | uses: actions/download-artifact@v4 80 | with: 81 | name: build-artifact 82 | path: dist 83 | - name: Restore build artifact permissions 84 | run: cd dist && setfacl --restore=permissions-backup.acl 85 | continue-on-error: true 86 | - name: Release 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_SHA 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 90 | release_npm: 91 | name: Publish to npm 92 | needs: release 93 | runs-on: ubuntu-latest 94 | permissions: 95 | id-token: write 96 | contents: read 97 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 98 | steps: 99 | - uses: actions/setup-node@v4 100 | with: 101 | node-version: lts/* 102 | - name: Download build artifacts 103 | uses: actions/download-artifact@v4 104 | with: 105 | name: build-artifact 106 | path: dist 107 | - name: Restore build artifact permissions 108 | run: cd dist && setfacl --restore=permissions-backup.acl 109 | continue-on-error: true 110 | - name: Checkout 111 | uses: actions/checkout@v4 112 | with: 113 | path: .repo 114 | - name: Install Dependencies 115 | run: cd .repo && yarn install --check-files --frozen-lockfile 116 | - name: Extract build artifact 117 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 118 | - name: Move build artifact out of the way 119 | run: mv dist dist.old 120 | - name: Create js artifact 121 | run: cd .repo && npx projen package:js 122 | - name: Collect js artifact 123 | run: mv .repo/dist dist 124 | - name: Release 125 | env: 126 | NPM_DIST_TAG: latest 127 | NPM_REGISTRY: registry.npmjs.org 128 | NPM_CONFIG_PROVENANCE: "true" 129 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 130 | run: npx -p publib@latest publib-npm 131 | release_maven: 132 | name: Publish to Maven Central 133 | needs: release 134 | runs-on: ubuntu-latest 135 | permissions: 136 | contents: read 137 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 138 | steps: 139 | - uses: actions/setup-java@v4 140 | with: 141 | distribution: corretto 142 | java-version: "11" 143 | - uses: actions/setup-node@v4 144 | with: 145 | node-version: lts/* 146 | - name: Download build artifacts 147 | uses: actions/download-artifact@v4 148 | with: 149 | name: build-artifact 150 | path: dist 151 | - name: Restore build artifact permissions 152 | run: cd dist && setfacl --restore=permissions-backup.acl 153 | continue-on-error: true 154 | - name: Checkout 155 | uses: actions/checkout@v4 156 | with: 157 | path: .repo 158 | - name: Install Dependencies 159 | run: cd .repo && yarn install --check-files --frozen-lockfile 160 | - name: Extract build artifact 161 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 162 | - name: Move build artifact out of the way 163 | run: mv dist dist.old 164 | - name: Create java artifact 165 | run: cd .repo && npx projen package:java 166 | - name: Collect java artifact 167 | run: mv .repo/dist dist 168 | - name: Release 169 | env: 170 | MAVEN_SERVER_ID: central-ossrh 171 | MAVEN_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 172 | MAVEN_GPG_PRIVATE_KEY_PASSPHRASE: ${{ secrets.MAVEN_GPG_PRIVATE_KEY_PASSPHRASE }} 173 | MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} 174 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 175 | MAVEN_STAGING_PROFILE_ID: ${{ secrets.MAVEN_STAGING_PROFILE_ID }} 176 | run: npx -p publib@latest publib-maven 177 | release_pypi: 178 | name: Publish to PyPI 179 | needs: release 180 | runs-on: ubuntu-latest 181 | permissions: 182 | contents: read 183 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 184 | steps: 185 | - uses: actions/setup-node@v4 186 | with: 187 | node-version: lts/* 188 | - uses: actions/setup-python@v5 189 | with: 190 | python-version: 3.x 191 | - name: Download build artifacts 192 | uses: actions/download-artifact@v4 193 | with: 194 | name: build-artifact 195 | path: dist 196 | - name: Restore build artifact permissions 197 | run: cd dist && setfacl --restore=permissions-backup.acl 198 | continue-on-error: true 199 | - name: Checkout 200 | uses: actions/checkout@v4 201 | with: 202 | path: .repo 203 | - name: Install Dependencies 204 | run: cd .repo && yarn install --check-files --frozen-lockfile 205 | - name: Extract build artifact 206 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 207 | - name: Move build artifact out of the way 208 | run: mv dist dist.old 209 | - name: Create python artifact 210 | run: cd .repo && npx projen package:python 211 | - name: Collect python artifact 212 | run: mv .repo/dist dist 213 | - name: Release 214 | env: 215 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 216 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 217 | run: npx -p publib@latest publib-pypi 218 | release_nuget: 219 | name: Publish to NuGet Gallery 220 | needs: release 221 | runs-on: ubuntu-latest 222 | permissions: 223 | contents: read 224 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 225 | steps: 226 | - uses: actions/setup-node@v4 227 | with: 228 | node-version: lts/* 229 | - uses: actions/setup-dotnet@v4 230 | with: 231 | dotnet-version: 6.x 232 | - name: Download build artifacts 233 | uses: actions/download-artifact@v4 234 | with: 235 | name: build-artifact 236 | path: dist 237 | - name: Restore build artifact permissions 238 | run: cd dist && setfacl --restore=permissions-backup.acl 239 | continue-on-error: true 240 | - name: Checkout 241 | uses: actions/checkout@v4 242 | with: 243 | path: .repo 244 | - name: Install Dependencies 245 | run: cd .repo && yarn install --check-files --frozen-lockfile 246 | - name: Extract build artifact 247 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 248 | - name: Move build artifact out of the way 249 | run: mv dist dist.old 250 | - name: Create dotnet artifact 251 | run: cd .repo && npx projen package:dotnet 252 | - name: Collect dotnet artifact 253 | run: mv .repo/dist dist 254 | - name: Release 255 | env: 256 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 257 | run: npx -p publib@latest publib-nuget 258 | release_golang: 259 | name: Publish to GitHub Go Module Repository 260 | needs: release 261 | runs-on: ubuntu-latest 262 | permissions: 263 | contents: read 264 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 265 | steps: 266 | - uses: actions/setup-node@v4 267 | with: 268 | node-version: lts/* 269 | - uses: actions/setup-go@v5 270 | with: 271 | go-version: ^1.18.0 272 | - name: Download build artifacts 273 | uses: actions/download-artifact@v4 274 | with: 275 | name: build-artifact 276 | path: dist 277 | - name: Restore build artifact permissions 278 | run: cd dist && setfacl --restore=permissions-backup.acl 279 | continue-on-error: true 280 | - name: Checkout 281 | uses: actions/checkout@v4 282 | with: 283 | path: .repo 284 | - name: Install Dependencies 285 | run: cd .repo && yarn install --check-files --frozen-lockfile 286 | - name: Extract build artifact 287 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 288 | - name: Move build artifact out of the way 289 | run: mv dist dist.old 290 | - name: Create go artifact 291 | run: cd .repo && npx projen package:go 292 | - name: Collect go artifact 293 | run: mv .repo/dist dist 294 | - name: Release 295 | env: 296 | GIT_USER_NAME: github-actions 297 | GIT_USER_EMAIL: github-actions@github.com 298 | GITHUB_TOKEN: ${{ secrets.GO_GITHUB_TOKEN }} 299 | run: npx -p publib@latest publib-golang 300 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-cdklabs-projen-project-types-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-cdklabs-projen-project-types-main 4 | on: 5 | workflow_dispatch: {} 6 | jobs: 7 | upgrade: 8 | name: Upgrade 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | outputs: 13 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | ref: main 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | - name: Install dependencies 22 | run: yarn install --check-files --frozen-lockfile 23 | - name: Upgrade dependencies 24 | run: npx projen upgrade-cdklabs-projen-project-types 25 | - name: Find mutations 26 | id: create_patch 27 | run: |- 28 | git add . 29 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 30 | working-directory: ./ 31 | - name: Upload patch 32 | if: steps.create_patch.outputs.patch_created 33 | uses: actions/upload-artifact@v4.4.0 34 | with: 35 | name: repo.patch 36 | path: repo.patch 37 | overwrite: true 38 | pr: 39 | name: Create Pull Request 40 | needs: upgrade 41 | runs-on: ubuntu-latest 42 | permissions: 43 | contents: read 44 | if: ${{ needs.upgrade.outputs.patch_created }} 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v4 48 | with: 49 | ref: main 50 | - name: Download patch 51 | uses: actions/download-artifact@v4 52 | with: 53 | name: repo.patch 54 | path: ${{ runner.temp }} 55 | - name: Apply patch 56 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 57 | - name: Set git identity 58 | run: |- 59 | git config user.name "github-actions" 60 | git config user.email "github-actions@github.com" 61 | - name: Create Pull Request 62 | id: create-pr 63 | uses: peter-evans/create-pull-request@v6 64 | with: 65 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 66 | commit-message: |- 67 | chore(deps): upgrade cdklabs-projen-project-types 68 | 69 | Upgrades project dependencies. See details in [workflow run]. 70 | 71 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 72 | 73 | ------ 74 | 75 | *Automatically created by projen via the "upgrade-cdklabs-projen-project-types-main" workflow* 76 | branch: github-actions/upgrade-cdklabs-projen-project-types-main 77 | title: "chore(deps): upgrade cdklabs-projen-project-types" 78 | labels: auto-approve 79 | body: |- 80 | Upgrades project dependencies. See details in [workflow run]. 81 | 82 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 83 | 84 | ------ 85 | 86 | *Automatically created by projen via the "upgrade-cdklabs-projen-project-types-main" workflow* 87 | author: github-actions 88 | committer: github-actions 89 | signoff: true 90 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dev-deps-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-dev-deps-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 18 * * 1 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-dev-deps 29 | - name: Find mutations 30 | id: create_patch 31 | run: |- 32 | git add . 33 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 34 | working-directory: ./ 35 | - name: Upload patch 36 | if: steps.create_patch.outputs.patch_created 37 | uses: actions/upload-artifact@v4.4.0 38 | with: 39 | name: repo.patch 40 | path: repo.patch 41 | overwrite: true 42 | pr: 43 | name: Create Pull Request 44 | needs: upgrade 45 | runs-on: ubuntu-latest 46 | permissions: 47 | contents: read 48 | if: ${{ needs.upgrade.outputs.patch_created }} 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | with: 53 | ref: main 54 | - name: Download patch 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: repo.patch 58 | path: ${{ runner.temp }} 59 | - name: Apply patch 60 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 61 | - name: Set git identity 62 | run: |- 63 | git config user.name "github-actions" 64 | git config user.email "github-actions@github.com" 65 | - name: Create Pull Request 66 | id: create-pr 67 | uses: peter-evans/create-pull-request@v6 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | commit-message: |- 71 | chore(deps): upgrade dev dependencies 72 | 73 | Upgrades project dependencies. See details in [workflow run]. 74 | 75 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 76 | 77 | ------ 78 | 79 | *Automatically created by projen via the "upgrade-dev-deps-main" workflow* 80 | branch: github-actions/upgrade-dev-deps-main 81 | title: "chore(deps): upgrade dev dependencies" 82 | labels: auto-approve 83 | body: |- 84 | Upgrades project dependencies. See details in [workflow run]. 85 | 86 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 87 | 88 | ------ 89 | 90 | *Automatically created by projen via the "upgrade-dev-deps-main" workflow* 91 | author: github-actions 92 | committer: github-actions 93 | signoff: true 94 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 18 * * 1 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade 29 | - name: Find mutations 30 | id: create_patch 31 | run: |- 32 | git add . 33 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 34 | working-directory: ./ 35 | - name: Upload patch 36 | if: steps.create_patch.outputs.patch_created 37 | uses: actions/upload-artifact@v4.4.0 38 | with: 39 | name: repo.patch 40 | path: repo.patch 41 | overwrite: true 42 | pr: 43 | name: Create Pull Request 44 | needs: upgrade 45 | runs-on: ubuntu-latest 46 | permissions: 47 | contents: read 48 | if: ${{ needs.upgrade.outputs.patch_created }} 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | with: 53 | ref: main 54 | - name: Download patch 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: repo.patch 58 | path: ${{ runner.temp }} 59 | - name: Apply patch 60 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 61 | - name: Set git identity 62 | run: |- 63 | git config user.name "github-actions" 64 | git config user.email "github-actions@github.com" 65 | - name: Create Pull Request 66 | id: create-pr 67 | uses: peter-evans/create-pull-request@v6 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | commit-message: |- 71 | fix(deps): upgrade dependencies 72 | 73 | Upgrades project dependencies. See details in [workflow run]. 74 | 75 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 76 | 77 | ------ 78 | 79 | *Automatically created by projen via the "upgrade-main" workflow* 80 | branch: github-actions/upgrade-main 81 | title: "fix(deps): upgrade dependencies" 82 | labels: auto-approve 83 | body: |- 84 | Upgrades project dependencies. See details in [workflow run]. 85 | 86 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 87 | 88 | ------ 89 | 90 | *Automatically created by projen via the "upgrade-main" workflow* 91 | author: github-actions 92 | committer: github-actions 93 | signoff: true 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/.github/workflows/auto-approve.yml 8 | !/package.json 9 | !/LICENSE 10 | !/.npmignore 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | lib-cov 23 | coverage 24 | *.lcov 25 | .nyc_output 26 | build/Release 27 | node_modules/ 28 | jspm_packages/ 29 | *.tsbuildinfo 30 | .eslintcache 31 | *.tgz 32 | .yarn-integrity 33 | .cache 34 | /test-reports/ 35 | junit.xml 36 | /coverage/ 37 | !/.github/workflows/build.yml 38 | /dist/changelog.md 39 | /dist/version.txt 40 | !/.github/workflows/release.yml 41 | !/.github/pull_request_template.md 42 | !/test/ 43 | !/tsconfig.dev.json 44 | !/src/ 45 | /lib 46 | /dist/ 47 | !/.eslintrc.json 48 | .jsii 49 | tsconfig.json 50 | !/API.md 51 | .jsii.tabl.json 52 | !/rosetta/default.ts-fixture 53 | !/.github/workflows/auto-queue.yml 54 | !/.github/workflows/upgrade-cdklabs-projen-project-types-main.yml 55 | !/.github/workflows/upgrade-main.yml 56 | !/.github/workflows/upgrade-dev-deps-main.yml 57 | !/.projenrc.ts 58 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | /.projen/ 3 | /test-reports/ 4 | junit.xml 5 | /coverage/ 6 | permissions-backup.acl 7 | /dist/changelog.md 8 | /dist/version.txt 9 | /test/ 10 | /tsconfig.dev.json 11 | /src/ 12 | !/lib/ 13 | !/lib/**/*.js 14 | !/lib/**/*.d.ts 15 | dist 16 | /tsconfig.json 17 | /.github/ 18 | /.vscode/ 19 | /.idea/ 20 | /.projenrc.js 21 | tsconfig.tsbuildinfo 22 | /.eslintrc.json 23 | !.jsii 24 | /.gitattributes 25 | /.projenrc.ts 26 | /projenrc 27 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@aws-sdk/client-dynamodb", 5 | "type": "build" 6 | }, 7 | { 8 | "name": "@stylistic/eslint-plugin", 9 | "version": "^2", 10 | "type": "build" 11 | }, 12 | { 13 | "name": "@types/jest", 14 | "type": "build" 15 | }, 16 | { 17 | "name": "@types/node", 18 | "version": "^18", 19 | "type": "build" 20 | }, 21 | { 22 | "name": "@typescript-eslint/eslint-plugin", 23 | "version": "^8", 24 | "type": "build" 25 | }, 26 | { 27 | "name": "@typescript-eslint/parser", 28 | "version": "^8", 29 | "type": "build" 30 | }, 31 | { 32 | "name": "aws-cdk-lib", 33 | "type": "build" 34 | }, 35 | { 36 | "name": "cdklabs-projen-project-types", 37 | "type": "build" 38 | }, 39 | { 40 | "name": "commit-and-tag-version", 41 | "version": "^12", 42 | "type": "build" 43 | }, 44 | { 45 | "name": "eslint-import-resolver-typescript", 46 | "type": "build" 47 | }, 48 | { 49 | "name": "eslint-plugin-import", 50 | "type": "build" 51 | }, 52 | { 53 | "name": "eslint", 54 | "version": "^9", 55 | "type": "build" 56 | }, 57 | { 58 | "name": "jest", 59 | "type": "build" 60 | }, 61 | { 62 | "name": "jest-junit", 63 | "version": "^16", 64 | "type": "build" 65 | }, 66 | { 67 | "name": "jsii-diff", 68 | "type": "build" 69 | }, 70 | { 71 | "name": "jsii-docgen", 72 | "version": "^10.5.0", 73 | "type": "build" 74 | }, 75 | { 76 | "name": "jsii-pacmak", 77 | "type": "build" 78 | }, 79 | { 80 | "name": "jsii-rosetta", 81 | "type": "build" 82 | }, 83 | { 84 | "name": "jsii", 85 | "version": "~5.7", 86 | "type": "build" 87 | }, 88 | { 89 | "name": "projen", 90 | "type": "build" 91 | }, 92 | { 93 | "name": "ts-jest", 94 | "type": "build" 95 | }, 96 | { 97 | "name": "ts-node", 98 | "type": "build" 99 | }, 100 | { 101 | "name": "typescript", 102 | "type": "build" 103 | }, 104 | { 105 | "name": "@aws-cdk/integ-runner", 106 | "version": "latest", 107 | "type": "devenv" 108 | }, 109 | { 110 | "name": "@aws-cdk/integ-tests-alpha", 111 | "version": "latest", 112 | "type": "devenv" 113 | }, 114 | { 115 | "name": "aws-cdk-lib", 116 | "version": "^2.60.0", 117 | "type": "peer" 118 | }, 119 | { 120 | "name": "constructs", 121 | "version": "^10.0.5", 122 | "type": "peer" 123 | } 124 | ], 125 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 126 | } 127 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/auto-approve.yml", 7 | ".github/workflows/auto-queue.yml", 8 | ".github/workflows/build.yml", 9 | ".github/workflows/pull-request-lint.yml", 10 | ".github/workflows/release.yml", 11 | ".github/workflows/upgrade-cdklabs-projen-project-types-main.yml", 12 | ".github/workflows/upgrade-dev-deps-main.yml", 13 | ".github/workflows/upgrade-main.yml", 14 | ".gitignore", 15 | ".projen/deps.json", 16 | ".projen/files.json", 17 | ".projen/tasks.json", 18 | "LICENSE", 19 | "tsconfig.dev.json" 20 | ], 21 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 22 | } 23 | -------------------------------------------------------------------------------- /.projen/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build": { 4 | "name": "build", 5 | "description": "Full release build", 6 | "steps": [ 7 | { 8 | "spawn": "default" 9 | }, 10 | { 11 | "spawn": "pre-compile" 12 | }, 13 | { 14 | "spawn": "compile" 15 | }, 16 | { 17 | "spawn": "post-compile" 18 | }, 19 | { 20 | "spawn": "test" 21 | }, 22 | { 23 | "spawn": "package" 24 | } 25 | ] 26 | }, 27 | "bump": { 28 | "name": "bump", 29 | "description": "Bumps version based on latest git tag and generates a changelog entry", 30 | "env": { 31 | "OUTFILE": "package.json", 32 | "CHANGELOG": "dist/changelog.md", 33 | "BUMPFILE": "dist/version.txt", 34 | "RELEASETAG": "dist/releasetag.txt", 35 | "RELEASE_TAG_PREFIX": "", 36 | "BUMP_PACKAGE": "commit-and-tag-version@^12", 37 | "RELEASABLE_COMMITS": "git log --no-merges --oneline $LATEST_TAG..HEAD -E --grep \"^(feat|fix){1}(\\([^()[:space:]]+\\))?(!)?:[[:blank:]]+.+\"" 38 | }, 39 | "steps": [ 40 | { 41 | "builtin": "release/bump-version" 42 | } 43 | ], 44 | "condition": "git log --oneline -1 | grep -qv \"chore(release):\"" 45 | }, 46 | "clobber": { 47 | "name": "clobber", 48 | "description": "hard resets to HEAD of origin and cleans the local repo", 49 | "env": { 50 | "BRANCH": "$(git branch --show-current)" 51 | }, 52 | "steps": [ 53 | { 54 | "exec": "git checkout -b scratch", 55 | "name": "save current HEAD in \"scratch\" branch" 56 | }, 57 | { 58 | "exec": "git checkout $BRANCH" 59 | }, 60 | { 61 | "exec": "git fetch origin", 62 | "name": "fetch latest changes from origin" 63 | }, 64 | { 65 | "exec": "git reset --hard origin/$BRANCH", 66 | "name": "hard reset to origin commit" 67 | }, 68 | { 69 | "exec": "git clean -fdx", 70 | "name": "clean all untracked files" 71 | }, 72 | { 73 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 74 | } 75 | ], 76 | "condition": "git diff --exit-code > /dev/null" 77 | }, 78 | "compat": { 79 | "name": "compat", 80 | "description": "Perform API compatibility check against latest version", 81 | "steps": [ 82 | { 83 | "exec": "jsii-diff npm:$(node -p \"require('./package.json').name\") -k --ignore-file .compatignore || (echo \"\nUNEXPECTED BREAKING CHANGES: add keys such as 'removed:constructs.Node.of' to .compatignore to skip.\n\" && exit 1)" 84 | } 85 | ] 86 | }, 87 | "compile": { 88 | "name": "compile", 89 | "description": "Only compile", 90 | "steps": [ 91 | { 92 | "exec": "jsii --silence-warnings=reserved-word" 93 | } 94 | ] 95 | }, 96 | "default": { 97 | "name": "default", 98 | "description": "Synthesize project files", 99 | "steps": [ 100 | { 101 | "exec": "ts-node --project tsconfig.dev.json .projenrc.ts" 102 | } 103 | ] 104 | }, 105 | "docgen": { 106 | "name": "docgen", 107 | "description": "Generate API.md from .jsii manifest", 108 | "steps": [ 109 | { 110 | "exec": "jsii-docgen -o API.md" 111 | } 112 | ] 113 | }, 114 | "eject": { 115 | "name": "eject", 116 | "description": "Remove projen from the project", 117 | "env": { 118 | "PROJEN_EJECTING": "true" 119 | }, 120 | "steps": [ 121 | { 122 | "spawn": "default" 123 | } 124 | ] 125 | }, 126 | "eslint": { 127 | "name": "eslint", 128 | "description": "Runs eslint against the codebase", 129 | "env": { 130 | "ESLINT_USE_FLAT_CONFIG": "false" 131 | }, 132 | "steps": [ 133 | { 134 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools projenrc .projenrc.ts", 135 | "receiveArgs": true 136 | } 137 | ] 138 | }, 139 | "install": { 140 | "name": "install", 141 | "description": "Install project dependencies and update lockfile (non-frozen)", 142 | "steps": [ 143 | { 144 | "exec": "yarn install --check-files" 145 | } 146 | ] 147 | }, 148 | "install:ci": { 149 | "name": "install:ci", 150 | "description": "Install project dependencies using frozen lockfile", 151 | "steps": [ 152 | { 153 | "exec": "yarn install --check-files --frozen-lockfile" 154 | } 155 | ] 156 | }, 157 | "integ": { 158 | "name": "integ", 159 | "description": "Run integration snapshot tests", 160 | "steps": [ 161 | { 162 | "exec": "yarn integ-runner --language typescript", 163 | "receiveArgs": true 164 | } 165 | ] 166 | }, 167 | "integ:update": { 168 | "name": "integ:update", 169 | "description": "Run and update integration snapshot tests", 170 | "steps": [ 171 | { 172 | "exec": "yarn integ-runner --language typescript --update-on-failed", 173 | "receiveArgs": true 174 | } 175 | ] 176 | }, 177 | "package": { 178 | "name": "package", 179 | "description": "Creates the distribution package", 180 | "steps": [ 181 | { 182 | "spawn": "package:js", 183 | "condition": "node -e \"if (!process.env.CI) process.exit(1)\"" 184 | }, 185 | { 186 | "spawn": "package-all", 187 | "condition": "node -e \"if (process.env.CI) process.exit(1)\"" 188 | } 189 | ] 190 | }, 191 | "package-all": { 192 | "name": "package-all", 193 | "description": "Packages artifacts for all target languages", 194 | "steps": [ 195 | { 196 | "spawn": "package:js" 197 | }, 198 | { 199 | "spawn": "package:java" 200 | }, 201 | { 202 | "spawn": "package:python" 203 | }, 204 | { 205 | "spawn": "package:dotnet" 206 | }, 207 | { 208 | "spawn": "package:go" 209 | } 210 | ] 211 | }, 212 | "package:dotnet": { 213 | "name": "package:dotnet", 214 | "description": "Create dotnet language bindings", 215 | "steps": [ 216 | { 217 | "exec": "jsii-pacmak -v --target dotnet" 218 | } 219 | ] 220 | }, 221 | "package:go": { 222 | "name": "package:go", 223 | "description": "Create go language bindings", 224 | "steps": [ 225 | { 226 | "exec": "jsii-pacmak -v --target go" 227 | } 228 | ] 229 | }, 230 | "package:java": { 231 | "name": "package:java", 232 | "description": "Create java language bindings", 233 | "steps": [ 234 | { 235 | "exec": "jsii-pacmak -v --target java" 236 | } 237 | ] 238 | }, 239 | "package:js": { 240 | "name": "package:js", 241 | "description": "Create js language bindings", 242 | "steps": [ 243 | { 244 | "exec": "jsii-pacmak -v --target js" 245 | } 246 | ] 247 | }, 248 | "package:python": { 249 | "name": "package:python", 250 | "description": "Create python language bindings", 251 | "steps": [ 252 | { 253 | "exec": "jsii-pacmak -v --target python" 254 | } 255 | ] 256 | }, 257 | "post-compile": { 258 | "name": "post-compile", 259 | "description": "Runs after successful compilation", 260 | "steps": [ 261 | { 262 | "spawn": "docgen" 263 | }, 264 | { 265 | "spawn": "rosetta:extract" 266 | } 267 | ] 268 | }, 269 | "post-upgrade": { 270 | "name": "post-upgrade", 271 | "description": "Runs after upgrading dependencies" 272 | }, 273 | "pre-compile": { 274 | "name": "pre-compile", 275 | "description": "Prepare the project for compilation" 276 | }, 277 | "release": { 278 | "name": "release", 279 | "description": "Prepare a release from \"main\" branch", 280 | "env": { 281 | "RELEASE": "true" 282 | }, 283 | "steps": [ 284 | { 285 | "exec": "rm -fr dist" 286 | }, 287 | { 288 | "spawn": "bump" 289 | }, 290 | { 291 | "spawn": "build" 292 | }, 293 | { 294 | "spawn": "unbump" 295 | }, 296 | { 297 | "exec": "git diff --ignore-space-at-eol --exit-code" 298 | } 299 | ] 300 | }, 301 | "rosetta:extract": { 302 | "name": "rosetta:extract", 303 | "description": "Test rosetta extract", 304 | "steps": [ 305 | { 306 | "exec": "yarn --silent jsii-rosetta extract --strict" 307 | } 308 | ] 309 | }, 310 | "test": { 311 | "name": "test", 312 | "description": "Run tests", 313 | "steps": [ 314 | { 315 | "exec": "jest --passWithNoTests --updateSnapshot", 316 | "receiveArgs": true 317 | }, 318 | { 319 | "spawn": "eslint" 320 | }, 321 | { 322 | "spawn": "integ" 323 | } 324 | ] 325 | }, 326 | "test:watch": { 327 | "name": "test:watch", 328 | "description": "Run jest in watch mode", 329 | "steps": [ 330 | { 331 | "exec": "jest --watch" 332 | } 333 | ] 334 | }, 335 | "unbump": { 336 | "name": "unbump", 337 | "description": "Restores version to 0.0.0", 338 | "env": { 339 | "OUTFILE": "package.json", 340 | "CHANGELOG": "dist/changelog.md", 341 | "BUMPFILE": "dist/version.txt", 342 | "RELEASETAG": "dist/releasetag.txt", 343 | "RELEASE_TAG_PREFIX": "", 344 | "BUMP_PACKAGE": "commit-and-tag-version@^12", 345 | "RELEASABLE_COMMITS": "git log --no-merges --oneline $LATEST_TAG..HEAD -E --grep \"^(feat|fix){1}(\\([^()[:space:]]+\\))?(!)?:[[:blank:]]+.+\"" 346 | }, 347 | "steps": [ 348 | { 349 | "builtin": "release/reset-version" 350 | } 351 | ] 352 | }, 353 | "upgrade": { 354 | "name": "upgrade", 355 | "description": "upgrade dependencies", 356 | "env": { 357 | "CI": "0" 358 | }, 359 | "steps": [ 360 | { 361 | "exec": "echo No dependencies to upgrade." 362 | } 363 | ] 364 | }, 365 | "upgrade-cdklabs-projen-project-types": { 366 | "name": "upgrade-cdklabs-projen-project-types", 367 | "description": "upgrade cdklabs-projen-project-types", 368 | "env": { 369 | "CI": "0" 370 | }, 371 | "steps": [ 372 | { 373 | "exec": "npx npm-check-updates@16 --upgrade --target=latest --peer --no-deprecated --dep=dev,peer,prod,optional --filter=cdklabs-projen-project-types,projen" 374 | }, 375 | { 376 | "exec": "yarn install --check-files" 377 | }, 378 | { 379 | "exec": "yarn upgrade cdklabs-projen-project-types projen" 380 | }, 381 | { 382 | "exec": "npx projen" 383 | }, 384 | { 385 | "spawn": "post-upgrade" 386 | } 387 | ] 388 | }, 389 | "upgrade-dev-deps": { 390 | "name": "upgrade-dev-deps", 391 | "description": "upgrade dev dependencies", 392 | "env": { 393 | "CI": "0" 394 | }, 395 | "steps": [ 396 | { 397 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev --filter=@aws-sdk/client-dynamodb,@types/jest,aws-cdk-lib,eslint-import-resolver-typescript,eslint-plugin-import,jest,jsii-diff,jsii-pacmak,jsii-rosetta,ts-jest,ts-node,typescript" 398 | }, 399 | { 400 | "exec": "yarn install --check-files" 401 | }, 402 | { 403 | "exec": "yarn upgrade @aws-sdk/client-dynamodb @stylistic/eslint-plugin @types/jest @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser aws-cdk-lib commit-and-tag-version eslint-import-resolver-typescript eslint-plugin-import eslint jest jest-junit jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii ts-jest ts-node typescript @aws-cdk/integ-runner @aws-cdk/integ-tests-alpha" 404 | }, 405 | { 406 | "exec": "npx projen" 407 | }, 408 | { 409 | "spawn": "post-upgrade" 410 | } 411 | ] 412 | }, 413 | "watch": { 414 | "name": "watch", 415 | "description": "Watch & compile in the background", 416 | "steps": [ 417 | { 418 | "exec": "jsii -w --silence-warnings=reserved-word" 419 | } 420 | ] 421 | } 422 | }, 423 | "env": { 424 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 425 | }, 426 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 427 | } 428 | -------------------------------------------------------------------------------- /.projenrc.ts: -------------------------------------------------------------------------------- 1 | import { CdklabsConstructLibrary } from 'cdklabs-projen-project-types'; 2 | 3 | const project = new CdklabsConstructLibrary({ 4 | defaultReleaseBranch: 'main', 5 | projenrcTs: true, 6 | enablePRAutoMerge: true, 7 | private: false, 8 | repositoryUrl: 'https://github.com/cdklabs/cdk-dynamo-table-viewer.git', 9 | name: 'cdk-dynamo-table-viewer', 10 | description: 'An AWS CDK construct which exposes an endpoint with the contents of a DynamoDB table', 11 | 12 | author: 'Amazon Web Services', 13 | authorAddress: 'aws-cdk-dev@amazon.com', 14 | 15 | cdkVersion: '2.60.0', 16 | 17 | devDeps: [ 18 | 'ts-node@^10.8.1', 19 | 'aws-cdk-lib', 20 | 'constructs', 21 | '@aws-sdk/client-dynamodb', 22 | 'cdklabs-projen-project-types', 23 | ], 24 | peerDeps: ['aws-cdk-lib', 'constructs'], 25 | 26 | catalog: { 27 | twitter: 'emeshbi', 28 | }, 29 | 30 | publishToMaven: { 31 | javaPackage: 'io.github.cdklabs.dynamotableviewer', 32 | mavenGroupId: 'io.github.cdklabs', 33 | mavenArtifactId: 'cdk-dynamo-table-view', 34 | mavenServerId: 'central-ossrh', 35 | }, 36 | 37 | publishToPypi: { 38 | distName: 'cdk-dynamo-table-view', 39 | module: 'cdk_dynamo_table_view', 40 | }, 41 | 42 | publishToNuget: { 43 | dotNetNamespace: 'Cdklabs.DynamoTableViewer', 44 | packageId: 'Cdklabs.DynamoTableViewer', 45 | }, 46 | 47 | publishToGo: { 48 | moduleName: 'github.com/cdklabs/cdk-dynamo-table-viewer-go', 49 | packageName: 'dynamotableviewer', 50 | }, 51 | 52 | depsUpgradeOptions: { 53 | exclude: ['aws-cdk-lib', 'constructs'], 54 | }, 55 | }); 56 | 57 | project.synth(); 58 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Constructs 4 | 5 | ### TableViewer 6 | 7 | Installs an endpoint in your stack that allows users to view the contents of a DynamoDB table through their browser. 8 | 9 | #### Initializers 10 | 11 | ```typescript 12 | import { TableViewer } from 'cdk-dynamo-table-viewer' 13 | 14 | new TableViewer(parent: Construct, id: string, props: TableViewerProps) 15 | ``` 16 | 17 | | **Name** | **Type** | **Description** | 18 | | --- | --- | --- | 19 | | parent | constructs.Construct | *No description.* | 20 | | id | string | *No description.* | 21 | | props | TableViewerProps | *No description.* | 22 | 23 | --- 24 | 25 | ##### `parent`Required 26 | 27 | - *Type:* constructs.Construct 28 | 29 | --- 30 | 31 | ##### `id`Required 32 | 33 | - *Type:* string 34 | 35 | --- 36 | 37 | ##### `props`Required 38 | 39 | - *Type:* TableViewerProps 40 | 41 | --- 42 | 43 | #### Methods 44 | 45 | | **Name** | **Description** | 46 | | --- | --- | 47 | | toString | Returns a string representation of this construct. | 48 | 49 | --- 50 | 51 | ##### `toString` 52 | 53 | ```typescript 54 | public toString(): string 55 | ``` 56 | 57 | Returns a string representation of this construct. 58 | 59 | #### Static Functions 60 | 61 | | **Name** | **Description** | 62 | | --- | --- | 63 | | isConstruct | Checks if `x` is a construct. | 64 | 65 | --- 66 | 67 | ##### ~~`isConstruct`~~ 68 | 69 | ```typescript 70 | import { TableViewer } from 'cdk-dynamo-table-viewer' 71 | 72 | TableViewer.isConstruct(x: any) 73 | ``` 74 | 75 | Checks if `x` is a construct. 76 | 77 | ###### `x`Required 78 | 79 | - *Type:* any 80 | 81 | Any object. 82 | 83 | --- 84 | 85 | #### Properties 86 | 87 | | **Name** | **Type** | **Description** | 88 | | --- | --- | --- | 89 | | node | constructs.Node | The tree node. | 90 | | endpoint | string | *No description.* | 91 | 92 | --- 93 | 94 | ##### `node`Required 95 | 96 | ```typescript 97 | public readonly node: Node; 98 | ``` 99 | 100 | - *Type:* constructs.Node 101 | 102 | The tree node. 103 | 104 | --- 105 | 106 | ##### `endpoint`Required 107 | 108 | ```typescript 109 | public readonly endpoint: string; 110 | ``` 111 | 112 | - *Type:* string 113 | 114 | --- 115 | 116 | 117 | ## Structs 118 | 119 | ### TableViewerProps 120 | 121 | #### Initializer 122 | 123 | ```typescript 124 | import { TableViewerProps } from 'cdk-dynamo-table-viewer' 125 | 126 | const tableViewerProps: TableViewerProps = { ... } 127 | ``` 128 | 129 | #### Properties 130 | 131 | | **Name** | **Type** | **Description** | 132 | | --- | --- | --- | 133 | | table | aws-cdk-lib.aws_dynamodb.ITable | The DynamoDB table to view. | 134 | | endpointType | aws-cdk-lib.aws_apigateway.EndpointType | The endpoint type of the [LambdaRestApi](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaRestApi.html) that will be created. | 135 | | sortBy | string | Name of the column to sort by, prefix with "-" for descending order. | 136 | | title | string | The web page title. | 137 | 138 | --- 139 | 140 | ##### `table`Required 141 | 142 | ```typescript 143 | public readonly table: ITable; 144 | ``` 145 | 146 | - *Type:* aws-cdk-lib.aws_dynamodb.ITable 147 | 148 | The DynamoDB table to view. 149 | 150 | Note that all contents of this table will be 151 | visible to the public. 152 | 153 | --- 154 | 155 | ##### `endpointType`Optional 156 | 157 | ```typescript 158 | public readonly endpointType: EndpointType; 159 | ``` 160 | 161 | - *Type:* aws-cdk-lib.aws_apigateway.EndpointType 162 | - *Default:* EDGE 163 | 164 | The endpoint type of the [LambdaRestApi](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaRestApi.html) that will be created. 165 | 166 | --- 167 | 168 | ##### `sortBy`Optional 169 | 170 | ```typescript 171 | public readonly sortBy: string; 172 | ``` 173 | 174 | - *Type:* string 175 | - *Default:* No sort 176 | 177 | Name of the column to sort by, prefix with "-" for descending order. 178 | 179 | --- 180 | 181 | ##### `title`Optional 182 | 183 | ```typescript 184 | public readonly title: string; 185 | ``` 186 | 187 | - *Type:* string 188 | - *Default:* No title 189 | 190 | The web page title. 191 | 192 | --- 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cdk-dynamo-table-viewer 2 | 3 | An AWS CDK construct which exposes a public HTTP endpoint which displays an HTML 4 | page with the contents of a DynamoDB table in your stack. 5 | 6 | __SECURITY NOTE__: this construct was built for demonstration purposes and 7 | using it in production is probably a really bad idea. It exposes the entire 8 | contents of a DynamoDB table in your account to the general public. 9 | 10 | The library is published under the following names: 11 | 12 | |Language|Repository 13 | |--------|----------- 14 | |JavaScript/TypeScript|[cdk-dynamo-table-viewer](https://www.npmjs.com/package/cdk-dynamo-table-viewer) 15 | |Python|[cdk-dynamo-table-viewer](https://pypi.org/project/cdk-dynamo-table-viewer/) 16 | |.NET|[Eladb.DynamoTableViewer](https://www.nuget.org/packages/Eladb.DynamoTableViewer/) 17 | |Java|[com.github.eladb/cdk-dynamo-table-viewer](https://search.maven.org/artifact/com.github.eladb/cdk-dynamo-table-viewer) 18 | |Go|[github.com/cdklabs/cdk-dynamo-table-viewer-go/dynamotableviewer](https://pkg.go.dev/github.com/cdklabs/cdk-dynamo-table-viewer-go/dynamotableviewer) 19 | 20 | ## Usage (TypeScript/JavaScript) 21 | 22 | Install via npm: 23 | 24 | ```shell 25 | $ npm i cdk-dynamo-table-viewer 26 | ``` 27 | 28 | Add to your CDK stack: 29 | 30 | ```ts 31 | declare const cookiesTable: dynamodb.Table; 32 | 33 | const viewer = new TableViewer(this, 'CookiesViewer', { 34 | table: cookiesTable, 35 | title: 'Cookie Sales', // optional 36 | sortBy: '-sales' // optional ("-" denotes descending order) 37 | }); 38 | ``` 39 | 40 | Notes: 41 | 42 | - The endpoint will be available (as an deploy-time value) under `viewer.endpoint`. 43 | It will also be exported as a stack output. 44 | - Paging is not supported. This means that only the first 1MB of items will be 45 | displayed (again, this is a demo...) 46 | - Supports CDK version 2.60.0 and above 47 | 48 | ## License 49 | 50 | Apache 2.0 51 | -------------------------------------------------------------------------------- /lambda/.gitignore: -------------------------------------------------------------------------------- 1 | !*.js 2 | -------------------------------------------------------------------------------- /lambda/index.js: -------------------------------------------------------------------------------- 1 | const { DynamoDBClient, ScanCommand } = require("@aws-sdk/client-dynamodb"); 2 | const render = require('./render'); 3 | 4 | exports.handler = async function(event) { 5 | console.log('request:', JSON.stringify(event, undefined, 2)); 6 | 7 | const dynamo = new DynamoDBClient({ }); 8 | 9 | const resp = await dynamo.send(new ScanCommand({ 10 | TableName: process.env.TABLE_NAME, 11 | })); 12 | 13 | const html = render({ 14 | items: resp.Items, 15 | title: process.env.TITLE, 16 | sort: process.env.SORT_BY, 17 | }); 18 | 19 | return { 20 | statusCode: 200, 21 | headers: { 22 | 'Content-Type': 'text/html' 23 | }, 24 | body: html 25 | }; 26 | }; -------------------------------------------------------------------------------- /lambda/render-test.js: -------------------------------------------------------------------------------- 1 | const render = require('./render'); 2 | 3 | console.log(render({ 4 | title: 'my-table', 5 | sort: 'hits', 6 | items: [ 7 | { 8 | field1: { S: 'item1.value1' }, 9 | field2: { S: 'item1.value2' }, 10 | hits: { N: 10 }, 11 | }, 12 | { 13 | field1: { S: 'item2.value1' }, 14 | field3: { S: 'item2.value3' }, 15 | field4: { S: 'item2.value4' }, 16 | hits: { N: 2 }, 17 | }, 18 | { 19 | hits: { N: 99 } 20 | } 21 | ] 22 | })); -------------------------------------------------------------------------------- /lambda/render.js: -------------------------------------------------------------------------------- 1 | const stylesheet = require('./stylesheet'); 2 | 3 | module.exports = function(props) { 4 | const title = props.title; 5 | let sort = props.sort; 6 | 7 | let items = props.items || []; 8 | if (sort) { 9 | let desc = false; 10 | if (sort.startsWith('-')) { 11 | sort = sort.substr(1); 12 | desc = true; 13 | } 14 | 15 | items = items.sort((i1, i2) => { 16 | const result = compare(i1, i2); 17 | return desc ? -1 * result : result; 18 | }); 19 | 20 | function compare(i1, i2) { 21 | const v1 = i1[sort]; 22 | const v2 = i2[sort]; 23 | if (v1 && v2 && v1.N && v2.N) { 24 | return v1.N - v2.N; 25 | } 26 | 27 | return getAttribute(i1, sort).localeCompare(getAttribute(i2, sort)); 28 | } 29 | } 30 | 31 | const headers = collectHeaders() 32 | 33 | return ` 34 | 35 | 36 | 37 | 38 | ${ title ? `${title}` : '' } 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | ${ title ? `
${title}
` : '' } 49 | 50 | 51 | ${ renderHeaderRow() } 52 | ${ items.map(item => renderItemRow(item)).join('\n') } 53 |
54 |
55 | 56 | 57 | 58 | `; 59 | 60 | function renderHeaderRow() { 61 | return ` 62 | 63 | ${ headers.map(header => `${header}`).join('\n') } 64 | 65 | `; 66 | } 67 | 68 | function renderItemRow(item) { 69 | return ` 70 | 71 | ${ headers.map(header => `${renderAttribute(item, header)}`).join('\n') } 72 | 73 | ` 74 | } 75 | 76 | function renderAttribute(item, attribute) { 77 | return getAttribute(item, attribute).toString(); 78 | } 79 | 80 | function getAttribute(item, attribute) { 81 | if (!(attribute in item)) { 82 | return ''; 83 | } 84 | 85 | const type = Object.keys(item[attribute]); 86 | return item[attribute][type]; 87 | } 88 | 89 | // iterate over all items and create a union of all keys for table headers 90 | function collectHeaders() { 91 | const headerSet = new Set(); 92 | for (const item of items) { 93 | for (const key of Object.keys(item)) { 94 | headerSet.add(key); 95 | } 96 | } 97 | 98 | return Array.from(headerSet).sort(); 99 | } 100 | 101 | function isNumber(v) { 102 | return (parseInt(v).toString() === v.toString()); 103 | } 104 | } -------------------------------------------------------------------------------- /lambda/stylesheet.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | 3 | @import url('https://fonts.googleapis.com/css?family=Open+Sans'); 4 | 5 | body { 6 | background-color: #91ced4; 7 | font-family: 'Open Sans', sans-serif;; 8 | } 9 | body * { 10 | box-sizing: border-box; 11 | } 12 | 13 | .header { 14 | background-color: #327a81; 15 | color: white; 16 | font-size: 1.5em; 17 | padding: 1rem; 18 | text-align: center; 19 | text-transform: uppercase; 20 | } 21 | 22 | img { 23 | border-radius: 50%; 24 | height: 60px; 25 | width: 60px; 26 | } 27 | 28 | .nice-table { 29 | border: 1px solid #327a81; 30 | border-radius: 10px; 31 | box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.1); 32 | max-width: calc(100% - 2em); 33 | margin: 1em auto; 34 | overflow: hidden; 35 | width: 800px; 36 | } 37 | 38 | table { 39 | width: 100%; 40 | } 41 | table td, table th { 42 | color: #2b686e; 43 | padding: 10px; 44 | } 45 | table td { 46 | text-align: center; 47 | vertical-align: middle; 48 | } 49 | table th { 50 | background-color: #a7d9dd; 51 | font-size: large; 52 | } 53 | table tr:nth-child(2n) { 54 | background-color: white; 55 | } 56 | table tr:nth-child(2n+1) { 57 | background-color: #edf7f8; 58 | } 59 | 60 | @media screen and (max-width: 700px) { 61 | table, tr, td { 62 | display: block; 63 | } 64 | 65 | td:first-child { 66 | position: absolute; 67 | top: 50%; 68 | -webkit-transform: translateY(-50%); 69 | transform: translateY(-50%); 70 | width: 100px; 71 | } 72 | td:not(:first-child) { 73 | clear: both; 74 | margin-left: 100px; 75 | padding: 4px 20px 4px 90px; 76 | position: relative; 77 | text-align: left; 78 | } 79 | td:not(:first-child):before { 80 | color: #91ced4; 81 | display: block; 82 | left: 0; 83 | position: absolute; 84 | } 85 | tr { 86 | padding: 10px 0; 87 | position: relative; 88 | } 89 | tr:first-child { 90 | display: none; 91 | } 92 | } 93 | @media screen and (max-width: 500px) { 94 | .header { 95 | background-color: transparent; 96 | color: white; 97 | font-size: 2em; 98 | font-weight: 700; 99 | padding: 0; 100 | text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.1); 101 | } 102 | 103 | img { 104 | border: 3px solid; 105 | border-color: #daeff1; 106 | height: 100px; 107 | margin: 0.5rem 0; 108 | width: 100px; 109 | } 110 | 111 | td:first-child { 112 | background-color: #c8e7ea; 113 | border-bottom: 1px solid #91ced4; 114 | border-radius: 10px 10px 0 0; 115 | position: relative; 116 | top: 0; 117 | -webkit-transform: translateY(0); 118 | transform: translateY(0); 119 | width: 100%; 120 | } 121 | td:not(:first-child) { 122 | margin: 0; 123 | padding: 5px 1em; 124 | width: 100%; 125 | } 126 | td:not(:first-child):before { 127 | font-size: .8em; 128 | padding-top: 0.3em; 129 | position: relative; 130 | } 131 | 132 | tr { 133 | background-color: white !important; 134 | border: 1px solid #6cbec6; 135 | border-radius: 10px; 136 | box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.1); 137 | margin: 0.5rem 0; 138 | padding: 0; 139 | } 140 | 141 | .nice-table { 142 | border: none; 143 | box-shadow: none; 144 | overflow: visible; 145 | } 146 | } 147 | 148 | `; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-dynamo-table-viewer", 3 | "description": "An AWS CDK construct which exposes an endpoint with the contents of a DynamoDB table", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/cdklabs/cdk-dynamo-table-viewer.git" 7 | }, 8 | "scripts": { 9 | "build": "npx projen build", 10 | "bump": "npx projen bump", 11 | "clobber": "npx projen clobber", 12 | "compat": "npx projen compat", 13 | "compile": "npx projen compile", 14 | "default": "npx projen default", 15 | "docgen": "npx projen docgen", 16 | "eject": "npx projen eject", 17 | "eslint": "npx projen eslint", 18 | "integ": "npx projen integ", 19 | "integ:update": "npx projen integ:update", 20 | "package": "npx projen package", 21 | "package-all": "npx projen package-all", 22 | "package:dotnet": "npx projen package:dotnet", 23 | "package:go": "npx projen package:go", 24 | "package:java": "npx projen package:java", 25 | "package:js": "npx projen package:js", 26 | "package:python": "npx projen package:python", 27 | "post-compile": "npx projen post-compile", 28 | "post-upgrade": "npx projen post-upgrade", 29 | "pre-compile": "npx projen pre-compile", 30 | "release": "npx projen release", 31 | "rosetta:extract": "npx projen rosetta:extract", 32 | "test": "npx projen test", 33 | "test:watch": "npx projen test:watch", 34 | "unbump": "npx projen unbump", 35 | "upgrade": "npx projen upgrade", 36 | "upgrade-cdklabs-projen-project-types": "npx projen upgrade-cdklabs-projen-project-types", 37 | "upgrade-dev-deps": "npx projen upgrade-dev-deps", 38 | "watch": "npx projen watch", 39 | "projen": "npx projen" 40 | }, 41 | "author": { 42 | "name": "Amazon Web Services", 43 | "email": "aws-cdk-dev@amazon.com", 44 | "organization": true 45 | }, 46 | "devDependencies": { 47 | "@aws-cdk/integ-runner": "latest", 48 | "@aws-cdk/integ-tests-alpha": "latest", 49 | "@aws-sdk/client-dynamodb": "^3.823.0", 50 | "@stylistic/eslint-plugin": "^2", 51 | "@types/jest": "^27", 52 | "@types/node": "^18", 53 | "@typescript-eslint/eslint-plugin": "^8", 54 | "@typescript-eslint/parser": "^8", 55 | "aws-cdk-lib": "2.60.0", 56 | "cdklabs-projen-project-types": "^0.3.1", 57 | "commit-and-tag-version": "^12", 58 | "constructs": "10.0.5", 59 | "eslint": "^9", 60 | "eslint-import-resolver-typescript": "^2.7.1", 61 | "eslint-plugin-import": "^2.31.0", 62 | "jest": "^27", 63 | "jest-junit": "^16", 64 | "jsii": "~5.7", 65 | "jsii-diff": "^1.112.0", 66 | "jsii-docgen": "^10.5.0", 67 | "jsii-pacmak": "^1.112.0", 68 | "jsii-rosetta": "^5.8.9", 69 | "projen": "^0.92.9", 70 | "ts-jest": "^27", 71 | "ts-node": "^10.9.2", 72 | "typescript": "^4.9.5" 73 | }, 74 | "peerDependencies": { 75 | "aws-cdk-lib": "^2.60.0", 76 | "constructs": "^10.0.5" 77 | }, 78 | "keywords": [ 79 | "cdk" 80 | ], 81 | "engines": { 82 | "node": ">= 18.12.0" 83 | }, 84 | "main": "lib/index.js", 85 | "license": "Apache-2.0", 86 | "publishConfig": { 87 | "access": "public" 88 | }, 89 | "version": "0.0.0", 90 | "jest": { 91 | "coverageProvider": "v8", 92 | "testMatch": [ 93 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 94 | "/@(src|test)/**/__tests__/**/*.ts?(x)", 95 | "/@(projenrc)/**/*(*.)@(spec|test).ts?(x)", 96 | "/@(projenrc)/**/__tests__/**/*.ts?(x)" 97 | ], 98 | "clearMocks": true, 99 | "collectCoverage": true, 100 | "coverageReporters": [ 101 | "json", 102 | "lcov", 103 | "clover", 104 | "cobertura", 105 | "text" 106 | ], 107 | "coverageDirectory": "coverage", 108 | "coveragePathIgnorePatterns": [ 109 | "/node_modules/" 110 | ], 111 | "testPathIgnorePatterns": [ 112 | "/node_modules/" 113 | ], 114 | "watchPathIgnorePatterns": [ 115 | "/node_modules/" 116 | ], 117 | "reporters": [ 118 | "default", 119 | [ 120 | "jest-junit", 121 | { 122 | "outputDirectory": "test-reports" 123 | } 124 | ] 125 | ], 126 | "preset": "ts-jest", 127 | "globals": { 128 | "ts-jest": { 129 | "tsconfig": "tsconfig.dev.json" 130 | } 131 | } 132 | }, 133 | "types": "lib/index.d.ts", 134 | "stability": "experimental", 135 | "jsii": { 136 | "outdir": "dist", 137 | "targets": { 138 | "java": { 139 | "package": "io.github.cdklabs.dynamotableviewer", 140 | "maven": { 141 | "groupId": "io.github.cdklabs", 142 | "artifactId": "cdk-dynamo-table-view" 143 | } 144 | }, 145 | "python": { 146 | "distName": "cdk-dynamo-table-view", 147 | "module": "cdk_dynamo_table_view" 148 | }, 149 | "dotnet": { 150 | "namespace": "Cdklabs.DynamoTableViewer", 151 | "packageId": "Cdklabs.DynamoTableViewer" 152 | }, 153 | "go": { 154 | "moduleName": "github.com/cdklabs/cdk-dynamo-table-viewer-go", 155 | "packageName": "dynamotableviewer" 156 | } 157 | }, 158 | "tsc": { 159 | "outDir": "lib", 160 | "rootDir": "src" 161 | } 162 | }, 163 | "awscdkio": { 164 | "twitter": "emeshbi" 165 | }, 166 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 167 | } 168 | -------------------------------------------------------------------------------- /rosetta/default.ts-fixture: -------------------------------------------------------------------------------- 1 | // Fixture with packages imported, but nothing else 2 | import { Construct } from 'constructs'; 3 | import { 4 | Stack, 5 | aws_dynamodb as dynamodb, 6 | } from 'aws-cdk-lib'; 7 | import { TableViewer } from 'cdk-dynamo-table-viewer'; 8 | 9 | class Fixture extends Stack { 10 | constructor(scope: Construct, id: string) { 11 | super(scope, id); 12 | 13 | /// here 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table-viewer'; -------------------------------------------------------------------------------- /src/table-viewer.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { 3 | aws_apigateway as apigw, 4 | aws_lambda as lambda, 5 | aws_dynamodb as dynamodb, 6 | } from 'aws-cdk-lib'; 7 | import { Construct } from 'constructs'; 8 | 9 | export interface TableViewerProps { 10 | /** 11 | * The endpoint type of the 12 | * [LambdaRestApi](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.LambdaRestApi.html) 13 | * that will be created 14 | * @default - EDGE 15 | */ 16 | readonly endpointType?: apigw.EndpointType; 17 | 18 | /** 19 | * The DynamoDB table to view. Note that all contents of this table will be 20 | * visible to the public. 21 | */ 22 | readonly table: dynamodb.ITable; 23 | 24 | /** 25 | * The web page title. 26 | * @default - No title 27 | */ 28 | readonly title?: string; 29 | 30 | /** 31 | * Name of the column to sort by, prefix with "-" for descending order. 32 | * @default - No sort 33 | */ 34 | readonly sortBy?: string; 35 | } 36 | 37 | /** 38 | * Installs an endpoint in your stack that allows users to view the contents 39 | * of a DynamoDB table through their browser. 40 | */ 41 | export class TableViewer extends Construct { 42 | 43 | public readonly endpoint: string; 44 | 45 | constructor(parent: Construct, id: string, props: TableViewerProps) { 46 | super(parent, id); 47 | 48 | const handler = new lambda.Function(this, 'Rendered', { 49 | code: lambda.Code.fromAsset(path.join(__dirname, '..', 'lambda')), 50 | runtime: lambda.Runtime.NODEJS_18_X, 51 | handler: 'index.handler', 52 | environment: { 53 | TABLE_NAME: props.table.tableName, 54 | TITLE: props.title || '', 55 | SORT_BY: props.sortBy || '', 56 | }, 57 | }); 58 | 59 | props.table.grantReadData(handler); 60 | 61 | const home = new apigw.LambdaRestApi(this, 'ViewerEndpoint', { 62 | handler, 63 | endpointConfiguration: props.endpointType 64 | ? { types: [props.endpointType] } 65 | : undefined, 66 | }); 67 | this.endpoint = home.url; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/__snapshots__/viewer.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`happy flow 1`] = ` 4 | Object { 5 | "Outputs": Object { 6 | "viewerViewerEndpoint2EEAF9D2": Object { 7 | "Value": Object { 8 | "Fn::Join": Array [ 9 | "", 10 | Array [ 11 | "https://", 12 | Object { 13 | "Ref": "viewerViewerEndpointDED2DEA4", 14 | }, 15 | ".execute-api.", 16 | Object { 17 | "Ref": "AWS::Region", 18 | }, 19 | ".", 20 | Object { 21 | "Ref": "AWS::URLSuffix", 22 | }, 23 | "/", 24 | Object { 25 | "Ref": "viewerViewerEndpointDeploymentStageprod95943DC5", 26 | }, 27 | "/", 28 | ], 29 | ], 30 | }, 31 | }, 32 | }, 33 | "Parameters": Object { 34 | "BootstrapVersion": Object { 35 | "Default": "/cdk-bootstrap/hnb659fds/version", 36 | "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", 37 | "Type": "AWS::SSM::Parameter::Value", 38 | }, 39 | }, 40 | "Resources": Object { 41 | "MyTable794EDED1": Object { 42 | "DeletionPolicy": "Retain", 43 | "Properties": Object { 44 | "AttributeDefinitions": Array [ 45 | Object { 46 | "AttributeName": "id", 47 | "AttributeType": "S", 48 | }, 49 | ], 50 | "KeySchema": Array [ 51 | Object { 52 | "AttributeName": "id", 53 | "KeyType": "HASH", 54 | }, 55 | ], 56 | "ProvisionedThroughput": Object { 57 | "ReadCapacityUnits": 5, 58 | "WriteCapacityUnits": 5, 59 | }, 60 | }, 61 | "Type": "AWS::DynamoDB::Table", 62 | "UpdateReplacePolicy": "Retain", 63 | }, 64 | "viewerRenderedEEFA1E5E": Object { 65 | "DependsOn": Array [ 66 | "viewerRenderedServiceRoleDefaultPolicy196964DF", 67 | "viewerRenderedServiceRole7AE70B4C", 68 | ], 69 | "Properties": Object { 70 | "Code": Object { 71 | "S3Bucket": Object { 72 | "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}", 73 | }, 74 | "S3Key": "6ef63c8e0feb2ee73d336465b0d50733c8ede8bfdb37d27af4a6582fecd14ed0.zip", 75 | }, 76 | "Environment": Object { 77 | "Variables": Object { 78 | "SORT_BY": "", 79 | "TABLE_NAME": Object { 80 | "Ref": "MyTable794EDED1", 81 | }, 82 | "TITLE": "", 83 | }, 84 | }, 85 | "Handler": "index.handler", 86 | "Role": Object { 87 | "Fn::GetAtt": Array [ 88 | "viewerRenderedServiceRole7AE70B4C", 89 | "Arn", 90 | ], 91 | }, 92 | "Runtime": "nodejs18.x", 93 | }, 94 | "Type": "AWS::Lambda::Function", 95 | }, 96 | "viewerRenderedServiceRole7AE70B4C": Object { 97 | "Properties": Object { 98 | "AssumeRolePolicyDocument": Object { 99 | "Statement": Array [ 100 | Object { 101 | "Action": "sts:AssumeRole", 102 | "Effect": "Allow", 103 | "Principal": Object { 104 | "Service": "lambda.amazonaws.com", 105 | }, 106 | }, 107 | ], 108 | "Version": "2012-10-17", 109 | }, 110 | "ManagedPolicyArns": Array [ 111 | Object { 112 | "Fn::Join": Array [ 113 | "", 114 | Array [ 115 | "arn:", 116 | Object { 117 | "Ref": "AWS::Partition", 118 | }, 119 | ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", 120 | ], 121 | ], 122 | }, 123 | ], 124 | }, 125 | "Type": "AWS::IAM::Role", 126 | }, 127 | "viewerRenderedServiceRoleDefaultPolicy196964DF": Object { 128 | "Properties": Object { 129 | "PolicyDocument": Object { 130 | "Statement": Array [ 131 | Object { 132 | "Action": Array [ 133 | "dynamodb:BatchGetItem", 134 | "dynamodb:GetRecords", 135 | "dynamodb:GetShardIterator", 136 | "dynamodb:Query", 137 | "dynamodb:GetItem", 138 | "dynamodb:Scan", 139 | "dynamodb:ConditionCheckItem", 140 | "dynamodb:DescribeTable", 141 | ], 142 | "Effect": "Allow", 143 | "Resource": Array [ 144 | Object { 145 | "Fn::GetAtt": Array [ 146 | "MyTable794EDED1", 147 | "Arn", 148 | ], 149 | }, 150 | Object { 151 | "Ref": "AWS::NoValue", 152 | }, 153 | ], 154 | }, 155 | ], 156 | "Version": "2012-10-17", 157 | }, 158 | "PolicyName": "viewerRenderedServiceRoleDefaultPolicy196964DF", 159 | "Roles": Array [ 160 | Object { 161 | "Ref": "viewerRenderedServiceRole7AE70B4C", 162 | }, 163 | ], 164 | }, 165 | "Type": "AWS::IAM::Policy", 166 | }, 167 | "viewerViewerEndpointANY4A9252D2": Object { 168 | "Properties": Object { 169 | "AuthorizationType": "NONE", 170 | "HttpMethod": "ANY", 171 | "Integration": Object { 172 | "IntegrationHttpMethod": "POST", 173 | "Type": "AWS_PROXY", 174 | "Uri": Object { 175 | "Fn::Join": Array [ 176 | "", 177 | Array [ 178 | "arn:", 179 | Object { 180 | "Ref": "AWS::Partition", 181 | }, 182 | ":apigateway:", 183 | Object { 184 | "Ref": "AWS::Region", 185 | }, 186 | ":lambda:path/2015-03-31/functions/", 187 | Object { 188 | "Fn::GetAtt": Array [ 189 | "viewerRenderedEEFA1E5E", 190 | "Arn", 191 | ], 192 | }, 193 | "/invocations", 194 | ], 195 | ], 196 | }, 197 | }, 198 | "ResourceId": Object { 199 | "Fn::GetAtt": Array [ 200 | "viewerViewerEndpointDED2DEA4", 201 | "RootResourceId", 202 | ], 203 | }, 204 | "RestApiId": Object { 205 | "Ref": "viewerViewerEndpointDED2DEA4", 206 | }, 207 | }, 208 | "Type": "AWS::ApiGateway::Method", 209 | }, 210 | "viewerViewerEndpointANYApiPermissionTesttestviewerViewerEndpointDCCECA7FANYF9156524": Object { 211 | "Properties": Object { 212 | "Action": "lambda:InvokeFunction", 213 | "FunctionName": Object { 214 | "Fn::GetAtt": Array [ 215 | "viewerRenderedEEFA1E5E", 216 | "Arn", 217 | ], 218 | }, 219 | "Principal": "apigateway.amazonaws.com", 220 | "SourceArn": Object { 221 | "Fn::Join": Array [ 222 | "", 223 | Array [ 224 | "arn:", 225 | Object { 226 | "Ref": "AWS::Partition", 227 | }, 228 | ":execute-api:", 229 | Object { 230 | "Ref": "AWS::Region", 231 | }, 232 | ":", 233 | Object { 234 | "Ref": "AWS::AccountId", 235 | }, 236 | ":", 237 | Object { 238 | "Ref": "viewerViewerEndpointDED2DEA4", 239 | }, 240 | "/test-invoke-stage/*/", 241 | ], 242 | ], 243 | }, 244 | }, 245 | "Type": "AWS::Lambda::Permission", 246 | }, 247 | "viewerViewerEndpointANYApiPermissiontestviewerViewerEndpointDCCECA7FANY731E84A4": Object { 248 | "Properties": Object { 249 | "Action": "lambda:InvokeFunction", 250 | "FunctionName": Object { 251 | "Fn::GetAtt": Array [ 252 | "viewerRenderedEEFA1E5E", 253 | "Arn", 254 | ], 255 | }, 256 | "Principal": "apigateway.amazonaws.com", 257 | "SourceArn": Object { 258 | "Fn::Join": Array [ 259 | "", 260 | Array [ 261 | "arn:", 262 | Object { 263 | "Ref": "AWS::Partition", 264 | }, 265 | ":execute-api:", 266 | Object { 267 | "Ref": "AWS::Region", 268 | }, 269 | ":", 270 | Object { 271 | "Ref": "AWS::AccountId", 272 | }, 273 | ":", 274 | Object { 275 | "Ref": "viewerViewerEndpointDED2DEA4", 276 | }, 277 | "/", 278 | Object { 279 | "Ref": "viewerViewerEndpointDeploymentStageprod95943DC5", 280 | }, 281 | "/*/", 282 | ], 283 | ], 284 | }, 285 | }, 286 | "Type": "AWS::Lambda::Permission", 287 | }, 288 | "viewerViewerEndpointAccount1D965397": Object { 289 | "DeletionPolicy": "Retain", 290 | "DependsOn": Array [ 291 | "viewerViewerEndpointDED2DEA4", 292 | ], 293 | "Properties": Object { 294 | "CloudWatchRoleArn": Object { 295 | "Fn::GetAtt": Array [ 296 | "viewerViewerEndpointCloudWatchRole28045685", 297 | "Arn", 298 | ], 299 | }, 300 | }, 301 | "Type": "AWS::ApiGateway::Account", 302 | "UpdateReplacePolicy": "Retain", 303 | }, 304 | "viewerViewerEndpointCloudWatchRole28045685": Object { 305 | "DeletionPolicy": "Retain", 306 | "Properties": Object { 307 | "AssumeRolePolicyDocument": Object { 308 | "Statement": Array [ 309 | Object { 310 | "Action": "sts:AssumeRole", 311 | "Effect": "Allow", 312 | "Principal": Object { 313 | "Service": "apigateway.amazonaws.com", 314 | }, 315 | }, 316 | ], 317 | "Version": "2012-10-17", 318 | }, 319 | "ManagedPolicyArns": Array [ 320 | Object { 321 | "Fn::Join": Array [ 322 | "", 323 | Array [ 324 | "arn:", 325 | Object { 326 | "Ref": "AWS::Partition", 327 | }, 328 | ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs", 329 | ], 330 | ], 331 | }, 332 | ], 333 | }, 334 | "Type": "AWS::IAM::Role", 335 | "UpdateReplacePolicy": "Retain", 336 | }, 337 | "viewerViewerEndpointDED2DEA4": Object { 338 | "Properties": Object { 339 | "Name": "ViewerEndpoint", 340 | }, 341 | "Type": "AWS::ApiGateway::RestApi", 342 | }, 343 | "viewerViewerEndpointDeployment00B069459b597739070db86e729796710f9f86da": Object { 344 | "DependsOn": Array [ 345 | "viewerViewerEndpointproxyANYF7759A2A", 346 | "viewerViewerEndpointproxy103C52F5", 347 | "viewerViewerEndpointANY4A9252D2", 348 | ], 349 | "Properties": Object { 350 | "Description": "Automatically created by the RestApi construct", 351 | "RestApiId": Object { 352 | "Ref": "viewerViewerEndpointDED2DEA4", 353 | }, 354 | }, 355 | "Type": "AWS::ApiGateway::Deployment", 356 | }, 357 | "viewerViewerEndpointDeploymentStageprod95943DC5": Object { 358 | "DependsOn": Array [ 359 | "viewerViewerEndpointAccount1D965397", 360 | ], 361 | "Properties": Object { 362 | "DeploymentId": Object { 363 | "Ref": "viewerViewerEndpointDeployment00B069459b597739070db86e729796710f9f86da", 364 | }, 365 | "RestApiId": Object { 366 | "Ref": "viewerViewerEndpointDED2DEA4", 367 | }, 368 | "StageName": "prod", 369 | }, 370 | "Type": "AWS::ApiGateway::Stage", 371 | }, 372 | "viewerViewerEndpointproxy103C52F5": Object { 373 | "Properties": Object { 374 | "ParentId": Object { 375 | "Fn::GetAtt": Array [ 376 | "viewerViewerEndpointDED2DEA4", 377 | "RootResourceId", 378 | ], 379 | }, 380 | "PathPart": "{proxy+}", 381 | "RestApiId": Object { 382 | "Ref": "viewerViewerEndpointDED2DEA4", 383 | }, 384 | }, 385 | "Type": "AWS::ApiGateway::Resource", 386 | }, 387 | "viewerViewerEndpointproxyANYApiPermissionTesttestviewerViewerEndpointDCCECA7FANYproxyC67CDEE2": Object { 388 | "Properties": Object { 389 | "Action": "lambda:InvokeFunction", 390 | "FunctionName": Object { 391 | "Fn::GetAtt": Array [ 392 | "viewerRenderedEEFA1E5E", 393 | "Arn", 394 | ], 395 | }, 396 | "Principal": "apigateway.amazonaws.com", 397 | "SourceArn": Object { 398 | "Fn::Join": Array [ 399 | "", 400 | Array [ 401 | "arn:", 402 | Object { 403 | "Ref": "AWS::Partition", 404 | }, 405 | ":execute-api:", 406 | Object { 407 | "Ref": "AWS::Region", 408 | }, 409 | ":", 410 | Object { 411 | "Ref": "AWS::AccountId", 412 | }, 413 | ":", 414 | Object { 415 | "Ref": "viewerViewerEndpointDED2DEA4", 416 | }, 417 | "/test-invoke-stage/*/*", 418 | ], 419 | ], 420 | }, 421 | }, 422 | "Type": "AWS::Lambda::Permission", 423 | }, 424 | "viewerViewerEndpointproxyANYApiPermissiontestviewerViewerEndpointDCCECA7FANYproxy04819798": Object { 425 | "Properties": Object { 426 | "Action": "lambda:InvokeFunction", 427 | "FunctionName": Object { 428 | "Fn::GetAtt": Array [ 429 | "viewerRenderedEEFA1E5E", 430 | "Arn", 431 | ], 432 | }, 433 | "Principal": "apigateway.amazonaws.com", 434 | "SourceArn": Object { 435 | "Fn::Join": Array [ 436 | "", 437 | Array [ 438 | "arn:", 439 | Object { 440 | "Ref": "AWS::Partition", 441 | }, 442 | ":execute-api:", 443 | Object { 444 | "Ref": "AWS::Region", 445 | }, 446 | ":", 447 | Object { 448 | "Ref": "AWS::AccountId", 449 | }, 450 | ":", 451 | Object { 452 | "Ref": "viewerViewerEndpointDED2DEA4", 453 | }, 454 | "/", 455 | Object { 456 | "Ref": "viewerViewerEndpointDeploymentStageprod95943DC5", 457 | }, 458 | "/*/*", 459 | ], 460 | ], 461 | }, 462 | }, 463 | "Type": "AWS::Lambda::Permission", 464 | }, 465 | "viewerViewerEndpointproxyANYF7759A2A": Object { 466 | "Properties": Object { 467 | "AuthorizationType": "NONE", 468 | "HttpMethod": "ANY", 469 | "Integration": Object { 470 | "IntegrationHttpMethod": "POST", 471 | "Type": "AWS_PROXY", 472 | "Uri": Object { 473 | "Fn::Join": Array [ 474 | "", 475 | Array [ 476 | "arn:", 477 | Object { 478 | "Ref": "AWS::Partition", 479 | }, 480 | ":apigateway:", 481 | Object { 482 | "Ref": "AWS::Region", 483 | }, 484 | ":lambda:path/2015-03-31/functions/", 485 | Object { 486 | "Fn::GetAtt": Array [ 487 | "viewerRenderedEEFA1E5E", 488 | "Arn", 489 | ], 490 | }, 491 | "/invocations", 492 | ], 493 | ], 494 | }, 495 | }, 496 | "ResourceId": Object { 497 | "Ref": "viewerViewerEndpointproxy103C52F5", 498 | }, 499 | "RestApiId": Object { 500 | "Ref": "viewerViewerEndpointDED2DEA4", 501 | }, 502 | }, 503 | "Type": "AWS::ApiGateway::Method", 504 | }, 505 | }, 506 | "Rules": Object { 507 | "CheckBootstrapVersion": Object { 508 | "Assertions": Array [ 509 | Object { 510 | "Assert": Object { 511 | "Fn::Not": Array [ 512 | Object { 513 | "Fn::Contains": Array [ 514 | Array [ 515 | "1", 516 | "2", 517 | "3", 518 | "4", 519 | "5", 520 | ], 521 | Object { 522 | "Ref": "BootstrapVersion", 523 | }, 524 | ], 525 | }, 526 | ], 527 | }, 528 | "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", 529 | }, 530 | ], 531 | }, 532 | }, 533 | } 534 | `; 535 | -------------------------------------------------------------------------------- /test/sample/.gitignore: -------------------------------------------------------------------------------- 1 | cdk.out -------------------------------------------------------------------------------- /test/sample/cdk.json: -------------------------------------------------------------------------------- 1 | { "app": "npx ts-node -P ../../tsconfig.jest.json index.ts" } 2 | -------------------------------------------------------------------------------- /test/sample/index.ts: -------------------------------------------------------------------------------- 1 | import { StackProps, Stack, App } from 'aws-cdk-lib'; 2 | import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; 3 | import { Construct } from 'constructs'; 4 | import { TableViewer } from '../../src'; 5 | 6 | class MyStack extends Stack { 7 | constructor(scope: Construct, id: string, props: StackProps = {}) { 8 | super(scope, id, props); 9 | 10 | const table = new dynamodb.Table(this, 'my-table', { 11 | partitionKey: { 12 | name: 'id', type: dynamodb.AttributeType.STRING, 13 | }, 14 | }); 15 | 16 | new TableViewer(this, 'viewer', { 17 | table, 18 | }); 19 | } 20 | } 21 | 22 | const app = new App(); 23 | new MyStack(app, 'my-stack'); 24 | app.synth(); 25 | -------------------------------------------------------------------------------- /test/viewer.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack, App } from 'aws-cdk-lib'; 2 | import * as assertions from 'aws-cdk-lib/assertions'; 3 | import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; 4 | import { TableViewer } from '../src'; 5 | 6 | test('happy flow', () => { 7 | // GIVEN 8 | const app = new App(); 9 | const stack = new Stack(app, 'test'); 10 | const table = new dynamodb.Table(stack, 'MyTable', { 11 | partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, 12 | }); 13 | 14 | // WHEN 15 | new TableViewer(stack, 'viewer', { table }); 16 | 17 | // THEN 18 | const template = assertions.Template.fromStack(stack); 19 | 20 | // keeping this so that we can compare changes for v2 upgrade 21 | expect(template).toMatchSnapshot(); 22 | 23 | template.hasResourceProperties('AWS::Lambda::Function', { 24 | Handler: 'index.handler', 25 | Environment: { 26 | Variables: { 27 | SORT_BY: '', 28 | TABLE_NAME: { 29 | Ref: 'MyTable794EDED1', 30 | }, 31 | TITLE: '', 32 | }, 33 | }, 34 | Runtime: 'nodejs18.x', 35 | }); 36 | template.hasResourceProperties('AWS::ApiGateway::RestApi', { 37 | Name: 'ViewerEndpoint', 38 | }); 39 | template.hasResourceProperties('AWS::ApiGateway::Stage', { 40 | StageName: 'prod', 41 | }); 42 | template.hasResourceProperties('AWS::ApiGateway::Resource', { 43 | PathPart: '{proxy+}', 44 | }); 45 | 46 | template.resourceCountIs('AWS::ApiGateway::Method', 2); 47 | template.hasResourceProperties('AWS::ApiGateway::Method', { 48 | HttpMethod: 'ANY', 49 | Integration: { 50 | IntegrationHttpMethod: 'POST', 51 | Type: 'AWS_PROXY', 52 | }, 53 | }); 54 | template.hasResourceProperties('AWS::IAM::Policy', { 55 | PolicyName: 'viewerRenderedServiceRoleDefaultPolicy196964DF', 56 | PolicyDocument: { 57 | Statement: assertions.Match.arrayWith([ 58 | { 59 | Action: [ 60 | 'dynamodb:BatchGetItem', 61 | 'dynamodb:GetRecords', 62 | 'dynamodb:GetShardIterator', 63 | 'dynamodb:Query', 64 | 'dynamodb:GetItem', 65 | 'dynamodb:Scan', 66 | 'dynamodb:ConditionCheckItem', 67 | 'dynamodb:DescribeTable', 68 | ], 69 | Effect: 'Allow', 70 | Resource: assertions.Match.arrayWith([ 71 | { 72 | 'Fn::GetAtt': ['MyTable794EDED1', 'Arn'], 73 | }, 74 | ]), 75 | }, 76 | ]), 77 | Version: '2012-10-17', 78 | }, 79 | Roles: [{ 80 | Ref: 'viewerRenderedServiceRole7AE70B4C', 81 | }], 82 | }); 83 | template.resourceCountIs('AWS::DynamoDB::Table', 1); 84 | template.resourceCountIs('AWS::ApiGateway::Account', 1); 85 | template.resourceCountIs('AWS::ApiGateway::Deployment', 1); 86 | template.resourceCountIs('AWS::IAM::Role', 2); 87 | template.resourceCountIs('AWS::Lambda::Permission', 4); 88 | }); 89 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "alwaysStrict": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "lib": [ 11 | "es2020" 12 | ], 13 | "module": "CommonJS", 14 | "noEmitOnError": false, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "resolveJsonModule": true, 22 | "strict": true, 23 | "strictNullChecks": true, 24 | "strictPropertyInitialization": true, 25 | "stripInternal": true, 26 | "target": "ES2020" 27 | }, 28 | "include": [ 29 | "src/**/*.ts", 30 | "test/**/*.ts", 31 | ".projenrc.ts", 32 | "projenrc/**/*.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules" 36 | ] 37 | } 38 | --------------------------------------------------------------------------------