├── .compatignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── auto-queue.yml │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ ├── upgrade-10.x.yml │ ├── upgrade-cdklabs-projen-project-types-10.x.yml │ └── upgrade-dev-deps-10.x.yml ├── .gitignore ├── .npmignore ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.ts ├── API.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── NOTICE ├── README.md ├── package.json ├── scripts ├── compat.sh └── link.sh ├── src ├── construct.ts ├── dependency.ts ├── index.ts ├── metadata.ts └── private │ ├── stack-trace.ts │ └── uniqueid.ts ├── test ├── construct.test.ts └── util.ts ├── tsconfig.dev.json ├── version.json └── yarn.lock /.compatignore: -------------------------------------------------------------------------------- 1 | # 10.x BREAKING CHANGES 2 | removed-argument:constructs.Construct. 3 | removed:constructs.Construct.onPrepare 4 | removed:constructs.Construct.onSynthesize 5 | removed:constructs.Construct.onValidate 6 | removed:constructs.ConstructMetadata 7 | removed:constructs.Node.addError 8 | removed:constructs.Node.addInfo 9 | incompatible-argument:constructs.Node.addMetadata 10 | removed:constructs.Node.addWarning 11 | removed:constructs.Node.applyAspect 12 | removed:constructs.Node.prepare 13 | removed:constructs.Node.synthesize 14 | change-return-type:constructs.Node.validate 15 | removed:constructs.ValidationError 16 | changed-type:constructs.Node.dependencies 17 | removed:constructs.ConstructOptions 18 | removed:constructs.Dependency 19 | removed:constructs.IAspect 20 | removed:constructs.INodeFactory 21 | removed:constructs.ISynthesisSession 22 | removed:constructs.SynthesisOptions 23 | removed:constructs.Node.uniqueId 24 | -------------------------------------------------------------------------------- /.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-10.x.yml linguist-generated 14 | /.github/workflows/upgrade-cdklabs-projen-project-types-10.x.yml linguist-generated 15 | /.github/workflows/upgrade-dev-deps-10.x.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 | installable_with_npm: 274 | needs: build 275 | runs-on: ubuntu-latest 276 | permissions: 277 | contents: read 278 | if: ${{ !needs.build.outputs.self_mutation_happened }} 279 | steps: 280 | - name: Download build artifacts 281 | uses: actions/download-artifact@v4 282 | with: 283 | name: build-artifact 284 | path: dist 285 | - name: Restore build artifact permissions 286 | run: cd dist && setfacl --restore=permissions-backup.acl 287 | continue-on-error: true 288 | - name: Checkout 289 | uses: actions/checkout@v4 290 | with: 291 | ref: ${{ github.event.pull_request.head.ref }} 292 | repository: ${{ github.event.pull_request.head.repo.full_name }} 293 | - run: npm --version && npm install 294 | -------------------------------------------------------------------------------- /.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 | - 10.x 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:10.x 38 | run: npx projen release:10.x 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.CONSTRUCTS_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: AWS CDK Team 297 | GIT_USER_EMAIL: aws-cdk-dev@amazon.com 298 | GITHUB_TOKEN: ${{ secrets.GO_GITHUB_TOKEN }} 299 | run: npx -p publib@latest publib-golang 300 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-10.x.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-10.x 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: 10.x 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: 10.x 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-10.x" workflow* 80 | branch: github-actions/upgrade-10.x 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-10.x" workflow* 91 | author: github-actions 92 | committer: github-actions 93 | signoff: true 94 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-cdklabs-projen-project-types-10.x.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-cdklabs-projen-project-types-10.x 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: 10.x 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: 10.x 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-10.x" workflow* 76 | branch: github-actions/upgrade-cdklabs-projen-project-types-10.x 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-10.x" workflow* 87 | author: github-actions 88 | committer: github-actions 89 | signoff: true 90 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dev-deps-10.x.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-dev-deps-10.x 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: 10.x 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: 10.x 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-10.x" workflow* 80 | branch: github-actions/upgrade-dev-deps-10.x 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-10.x" 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 | !/.github/workflows/auto-queue.yml 52 | !/.github/workflows/upgrade-cdklabs-projen-project-types-10.x.yml 53 | !/.github/workflows/upgrade-10.x.yml 54 | !/.github/workflows/upgrade-dev-deps-10.x.yml 55 | !/.projenrc.ts 56 | -------------------------------------------------------------------------------- /.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 | /scripts/ 25 | .projenrc.ts 26 | /.gitattributes 27 | /.projenrc.ts 28 | /projenrc 29 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@stylistic/eslint-plugin", 5 | "version": "^2", 6 | "type": "build" 7 | }, 8 | { 9 | "name": "@types/jest", 10 | "type": "build" 11 | }, 12 | { 13 | "name": "@types/node", 14 | "version": "^18", 15 | "type": "build" 16 | }, 17 | { 18 | "name": "@typescript-eslint/eslint-plugin", 19 | "version": "^8", 20 | "type": "build" 21 | }, 22 | { 23 | "name": "@typescript-eslint/parser", 24 | "version": "^8", 25 | "type": "build" 26 | }, 27 | { 28 | "name": "cdklabs-projen-project-types", 29 | "type": "build" 30 | }, 31 | { 32 | "name": "commit-and-tag-version", 33 | "version": "^12", 34 | "type": "build" 35 | }, 36 | { 37 | "name": "eslint-import-resolver-typescript", 38 | "type": "build" 39 | }, 40 | { 41 | "name": "eslint-plugin-import", 42 | "type": "build" 43 | }, 44 | { 45 | "name": "eslint", 46 | "version": "^9", 47 | "type": "build" 48 | }, 49 | { 50 | "name": "jest", 51 | "type": "build" 52 | }, 53 | { 54 | "name": "jest-junit", 55 | "version": "^16", 56 | "type": "build" 57 | }, 58 | { 59 | "name": "jsii-diff", 60 | "type": "build" 61 | }, 62 | { 63 | "name": "jsii-docgen", 64 | "version": "^10.5.0", 65 | "type": "build" 66 | }, 67 | { 68 | "name": "jsii-pacmak", 69 | "version": "1.102.0", 70 | "type": "build" 71 | }, 72 | { 73 | "name": "jsii-rosetta", 74 | "version": "5.4.x", 75 | "type": "build" 76 | }, 77 | { 78 | "name": "jsii", 79 | "version": "5.4.x", 80 | "type": "build" 81 | }, 82 | { 83 | "name": "projen", 84 | "type": "build" 85 | }, 86 | { 87 | "name": "ts-jest", 88 | "type": "build" 89 | }, 90 | { 91 | "name": "ts-node", 92 | "type": "build" 93 | }, 94 | { 95 | "name": "typescript", 96 | "version": "5.4.x", 97 | "type": "build" 98 | } 99 | ], 100 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 101 | } 102 | -------------------------------------------------------------------------------- /.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-10.x.yml", 12 | ".github/workflows/upgrade-cdklabs-projen-project-types-10.x.yml", 13 | ".github/workflows/upgrade-dev-deps-10.x.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 | "package": { 158 | "name": "package", 159 | "description": "Creates the distribution package", 160 | "steps": [ 161 | { 162 | "exec": "go env -w GOSUMDB=off" 163 | }, 164 | { 165 | "spawn": "package:js", 166 | "condition": "node -e \"if (!process.env.CI) process.exit(1)\"" 167 | }, 168 | { 169 | "spawn": "package-all", 170 | "condition": "node -e \"if (process.env.CI) process.exit(1)\"" 171 | } 172 | ] 173 | }, 174 | "package-all": { 175 | "name": "package-all", 176 | "description": "Packages artifacts for all target languages", 177 | "steps": [ 178 | { 179 | "spawn": "package:js" 180 | }, 181 | { 182 | "spawn": "package:java" 183 | }, 184 | { 185 | "spawn": "package:python" 186 | }, 187 | { 188 | "spawn": "package:dotnet" 189 | }, 190 | { 191 | "spawn": "package:go" 192 | } 193 | ] 194 | }, 195 | "package:dotnet": { 196 | "name": "package:dotnet", 197 | "description": "Create dotnet language bindings", 198 | "steps": [ 199 | { 200 | "exec": "jsii-pacmak -v --target dotnet" 201 | } 202 | ] 203 | }, 204 | "package:go": { 205 | "name": "package:go", 206 | "description": "Create go language bindings", 207 | "steps": [ 208 | { 209 | "exec": "jsii-pacmak -v --target go" 210 | } 211 | ] 212 | }, 213 | "package:java": { 214 | "name": "package:java", 215 | "description": "Create java language bindings", 216 | "steps": [ 217 | { 218 | "exec": "jsii-pacmak -v --target java" 219 | } 220 | ] 221 | }, 222 | "package:js": { 223 | "name": "package:js", 224 | "description": "Create js language bindings", 225 | "steps": [ 226 | { 227 | "exec": "jsii-pacmak -v --target js" 228 | } 229 | ] 230 | }, 231 | "package:python": { 232 | "name": "package:python", 233 | "description": "Create python language bindings", 234 | "steps": [ 235 | { 236 | "exec": "jsii-pacmak -v --target python" 237 | } 238 | ] 239 | }, 240 | "post-compile": { 241 | "name": "post-compile", 242 | "description": "Runs after successful compilation", 243 | "steps": [ 244 | { 245 | "spawn": "docgen" 246 | } 247 | ] 248 | }, 249 | "post-upgrade": { 250 | "name": "post-upgrade", 251 | "description": "Runs after upgrading dependencies" 252 | }, 253 | "pre-compile": { 254 | "name": "pre-compile", 255 | "description": "Prepare the project for compilation" 256 | }, 257 | "release:10.x": { 258 | "name": "release:10.x", 259 | "description": "Prepare a release from \"10.x\" branch", 260 | "env": { 261 | "RELEASE": "true", 262 | "MAJOR": "10" 263 | }, 264 | "steps": [ 265 | { 266 | "exec": "rm -fr dist" 267 | }, 268 | { 269 | "spawn": "bump" 270 | }, 271 | { 272 | "spawn": "build" 273 | }, 274 | { 275 | "spawn": "unbump" 276 | }, 277 | { 278 | "exec": "git diff --ignore-space-at-eol --exit-code" 279 | } 280 | ] 281 | }, 282 | "test": { 283 | "name": "test", 284 | "description": "Run tests", 285 | "steps": [ 286 | { 287 | "exec": "jest --passWithNoTests --updateSnapshot", 288 | "receiveArgs": true 289 | }, 290 | { 291 | "spawn": "eslint" 292 | } 293 | ] 294 | }, 295 | "test:watch": { 296 | "name": "test:watch", 297 | "description": "Run jest in watch mode", 298 | "steps": [ 299 | { 300 | "exec": "jest --watch" 301 | } 302 | ] 303 | }, 304 | "unbump": { 305 | "name": "unbump", 306 | "description": "Restores version to 0.0.0", 307 | "env": { 308 | "OUTFILE": "package.json", 309 | "CHANGELOG": "dist/changelog.md", 310 | "BUMPFILE": "dist/version.txt", 311 | "RELEASETAG": "dist/releasetag.txt", 312 | "RELEASE_TAG_PREFIX": "", 313 | "BUMP_PACKAGE": "commit-and-tag-version@^12", 314 | "RELEASABLE_COMMITS": "git log --no-merges --oneline $LATEST_TAG..HEAD -E --grep \"^(feat|fix){1}(\\([^()[:space:]]+\\))?(!)?:[[:blank:]]+.+\"" 315 | }, 316 | "steps": [ 317 | { 318 | "builtin": "release/reset-version" 319 | } 320 | ] 321 | }, 322 | "upgrade": { 323 | "name": "upgrade", 324 | "description": "upgrade dependencies", 325 | "env": { 326 | "CI": "0" 327 | }, 328 | "steps": [ 329 | { 330 | "exec": "echo No dependencies to upgrade." 331 | } 332 | ] 333 | }, 334 | "upgrade-cdklabs-projen-project-types": { 335 | "name": "upgrade-cdklabs-projen-project-types", 336 | "description": "upgrade cdklabs-projen-project-types", 337 | "env": { 338 | "CI": "0" 339 | }, 340 | "steps": [ 341 | { 342 | "exec": "npx npm-check-updates@16 --upgrade --target=latest --peer --no-deprecated --dep=dev,peer,prod,optional --filter=cdklabs-projen-project-types,projen" 343 | }, 344 | { 345 | "exec": "yarn install --check-files" 346 | }, 347 | { 348 | "exec": "yarn upgrade cdklabs-projen-project-types projen" 349 | }, 350 | { 351 | "exec": "npx projen" 352 | }, 353 | { 354 | "spawn": "post-upgrade" 355 | } 356 | ] 357 | }, 358 | "upgrade-dev-deps": { 359 | "name": "upgrade-dev-deps", 360 | "description": "upgrade dev dependencies", 361 | "env": { 362 | "CI": "0" 363 | }, 364 | "steps": [ 365 | { 366 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev --filter=@types/jest,eslint-import-resolver-typescript,eslint-plugin-import,jest,jsii-diff,ts-jest,ts-node" 367 | }, 368 | { 369 | "exec": "yarn install --check-files" 370 | }, 371 | { 372 | "exec": "yarn upgrade @stylistic/eslint-plugin @types/jest @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser 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" 373 | }, 374 | { 375 | "exec": "npx projen" 376 | }, 377 | { 378 | "spawn": "post-upgrade" 379 | } 380 | ] 381 | }, 382 | "watch": { 383 | "name": "watch", 384 | "description": "Watch & compile in the background", 385 | "steps": [ 386 | { 387 | "exec": "jsii -w --silence-warnings=reserved-word" 388 | } 389 | ] 390 | } 391 | }, 392 | "env": { 393 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 394 | }, 395 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 396 | } 397 | -------------------------------------------------------------------------------- /.projenrc.ts: -------------------------------------------------------------------------------- 1 | import { CdklabsJsiiProject } from 'cdklabs-projen-project-types'; 2 | 3 | const project = new CdklabsJsiiProject({ 4 | name: 'constructs', 5 | projenrcTs: true, 6 | private: false, 7 | description: 'A programming model for software-defined state', 8 | repositoryUrl: 'https://github.com/aws/constructs.git', 9 | 10 | // release branches 11 | defaultReleaseBranch: '10.x', 12 | majorVersion: 10, 13 | npmDistTag: 'latest', 14 | devDeps: ['cdklabs-projen-project-types'], 15 | 16 | // author 17 | author: 'Amazon Web Services', 18 | authorAddress: 'aws-cdk-dev@amazon.com', 19 | homepage: 'https://github.com/aws/constructs', 20 | 21 | copyrightPeriod: `2018-${new Date().getFullYear()}`, 22 | copyrightOwner: 'Amazon.com, Inc. or its affiliates. All Rights Reserved.', 23 | 24 | keywords: ['aws', 'constructs', 'cdk', 'jsii'], 25 | 26 | publishToMaven: { 27 | javaPackage: 'software.constructs', 28 | mavenGroupId: 'software.constructs', 29 | mavenArtifactId: 'constructs', 30 | mavenStagingProfileId: 'CONSTRUCTS_MAVEN_STAGING_PROFILE_ID', 31 | mavenServerId: 'central-ossrh', 32 | }, 33 | 34 | publishToPypi: { 35 | distName: 'constructs', 36 | module: 'constructs', 37 | }, 38 | 39 | publishToNuget: { 40 | dotNetNamespace: 'Constructs', 41 | packageId: 'Constructs', 42 | }, 43 | 44 | publishToGo: { 45 | moduleName: 'github.com/aws/constructs-go', 46 | gitUserName: 'AWS CDK Team', 47 | gitUserEmail: 'aws-cdk-dev@amazon.com', 48 | }, 49 | 50 | stability: 'stable', 51 | setNodeEngineVersion: false, 52 | compat: true, 53 | 54 | enablePRAutoMerge: true, 55 | autoApproveOptions: { 56 | allowedUsernames: ['cdklabs-automation'], 57 | secret: 'GITHUB_TOKEN', 58 | }, 59 | autoApproveUpgrades: true, 60 | 61 | jsiiVersion: '5.4.x', 62 | typescriptVersion: '5.4.x', 63 | }); 64 | 65 | // disable go sumdb so that go deps are resolved directly against github 66 | project.tasks.tryFind('package')?.prependExec('go env -w GOSUMDB=off'); 67 | 68 | // Also check that our dependency closure is installable using NPM, not just yarn 69 | // (Not just additional steps, make it separate job) 70 | project.buildWorkflow?.addPostBuildJobCommands( 71 | 'installable_with_npm', 72 | ['npm --version && npm install'], 73 | { checkoutRepo: true }, 74 | ); 75 | 76 | project.npmignore?.exclude('/scripts/', '.projenrc.ts'); 77 | 78 | // cdklabs-projen-project-types is overzealous about adding this dependency 79 | project.deps.removeDependency('constructs'); 80 | 81 | // temporary to 82 | // to resolve https://github.com/aws/jsii/issues/4658 83 | project.addDevDeps('jsii-pacmak@1.102.0'); 84 | 85 | project.synth(); 86 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Constructs 4 | 5 | ### Construct 6 | 7 | - *Implements:* IConstruct 8 | 9 | Represents the building block of the construct graph. 10 | 11 | All constructs besides the root construct must be created within the scope of 12 | another construct. 13 | 14 | #### Initializers 15 | 16 | ```typescript 17 | import { Construct } from 'constructs' 18 | 19 | new Construct(scope: Construct, id: string) 20 | ``` 21 | 22 | | **Name** | **Type** | **Description** | 23 | | --- | --- | --- | 24 | | scope | Construct | The scope in which to define this construct. | 25 | | id | string | The scoped construct ID. | 26 | 27 | --- 28 | 29 | ##### `scope`Required 30 | 31 | - *Type:* Construct 32 | 33 | The scope in which to define this construct. 34 | 35 | --- 36 | 37 | ##### `id`Required 38 | 39 | - *Type:* string 40 | 41 | The scoped construct ID. 42 | 43 | Must be unique amongst siblings. If 44 | the ID includes a path separator (`/`), then it will be replaced by double 45 | dash `--`. 46 | 47 | --- 48 | 49 | #### Methods 50 | 51 | | **Name** | **Description** | 52 | | --- | --- | 53 | | toString | Returns a string representation of this construct. | 54 | 55 | --- 56 | 57 | ##### `toString` 58 | 59 | ```typescript 60 | public toString(): string 61 | ``` 62 | 63 | Returns a string representation of this construct. 64 | 65 | #### Static Functions 66 | 67 | | **Name** | **Description** | 68 | | --- | --- | 69 | | isConstruct | Checks if `x` is a construct. | 70 | 71 | --- 72 | 73 | ##### `isConstruct` 74 | 75 | ```typescript 76 | import { Construct } from 'constructs' 77 | 78 | Construct.isConstruct(x: any) 79 | ``` 80 | 81 | Checks if `x` is a construct. 82 | 83 | Use this method instead of `instanceof` to properly detect `Construct` 84 | instances, even when the construct library is symlinked. 85 | 86 | Explanation: in JavaScript, multiple copies of the `constructs` library on 87 | disk are seen as independent, completely different libraries. As a 88 | consequence, the class `Construct` in each copy of the `constructs` library 89 | is seen as a different class, and an instance of one class will not test as 90 | `instanceof` the other class. `npm install` will not create installations 91 | like this, but users may manually symlink construct libraries together or 92 | use a monorepo tool: in those cases, multiple copies of the `constructs` 93 | library can be accidentally installed, and `instanceof` will behave 94 | unpredictably. It is safest to avoid using `instanceof`, and using 95 | this type-testing method instead. 96 | 97 | ###### `x`Required 98 | 99 | - *Type:* any 100 | 101 | Any object. 102 | 103 | --- 104 | 105 | #### Properties 106 | 107 | | **Name** | **Type** | **Description** | 108 | | --- | --- | --- | 109 | | node | Node | The tree node. | 110 | 111 | --- 112 | 113 | ##### `node`Required 114 | 115 | ```typescript 116 | public readonly node: Node; 117 | ``` 118 | 119 | - *Type:* Node 120 | 121 | The tree node. 122 | 123 | --- 124 | 125 | 126 | ### RootConstruct 127 | 128 | Creates a new root construct node. 129 | 130 | The root construct represents the top of the construct tree and is not contained within a parent scope itself. 131 | For root constructs, the id is optional. 132 | 133 | #### Initializers 134 | 135 | ```typescript 136 | import { RootConstruct } from 'constructs' 137 | 138 | new RootConstruct(id?: string) 139 | ``` 140 | 141 | | **Name** | **Type** | **Description** | 142 | | --- | --- | --- | 143 | | id | string | The scoped construct ID. | 144 | 145 | --- 146 | 147 | ##### `id`Optional 148 | 149 | - *Type:* string 150 | 151 | The scoped construct ID. 152 | 153 | Must be unique amongst siblings. If 154 | the ID includes a path separator (`/`), then it will be replaced by double 155 | dash `--`. 156 | 157 | --- 158 | 159 | #### Methods 160 | 161 | | **Name** | **Description** | 162 | | --- | --- | 163 | | toString | Returns a string representation of this construct. | 164 | 165 | --- 166 | 167 | ##### `toString` 168 | 169 | ```typescript 170 | public toString(): string 171 | ``` 172 | 173 | Returns a string representation of this construct. 174 | 175 | #### Static Functions 176 | 177 | | **Name** | **Description** | 178 | | --- | --- | 179 | | isConstruct | Checks if `x` is a construct. | 180 | 181 | --- 182 | 183 | ##### `isConstruct` 184 | 185 | ```typescript 186 | import { RootConstruct } from 'constructs' 187 | 188 | RootConstruct.isConstruct(x: any) 189 | ``` 190 | 191 | Checks if `x` is a construct. 192 | 193 | Use this method instead of `instanceof` to properly detect `Construct` 194 | instances, even when the construct library is symlinked. 195 | 196 | Explanation: in JavaScript, multiple copies of the `constructs` library on 197 | disk are seen as independent, completely different libraries. As a 198 | consequence, the class `Construct` in each copy of the `constructs` library 199 | is seen as a different class, and an instance of one class will not test as 200 | `instanceof` the other class. `npm install` will not create installations 201 | like this, but users may manually symlink construct libraries together or 202 | use a monorepo tool: in those cases, multiple copies of the `constructs` 203 | library can be accidentally installed, and `instanceof` will behave 204 | unpredictably. It is safest to avoid using `instanceof`, and using 205 | this type-testing method instead. 206 | 207 | ###### `x`Required 208 | 209 | - *Type:* any 210 | 211 | Any object. 212 | 213 | --- 214 | 215 | #### Properties 216 | 217 | | **Name** | **Type** | **Description** | 218 | | --- | --- | --- | 219 | | node | Node | The tree node. | 220 | 221 | --- 222 | 223 | ##### `node`Required 224 | 225 | ```typescript 226 | public readonly node: Node; 227 | ``` 228 | 229 | - *Type:* Node 230 | 231 | The tree node. 232 | 233 | --- 234 | 235 | 236 | ## Structs 237 | 238 | ### MetadataEntry 239 | 240 | An entry in the construct metadata table. 241 | 242 | #### Initializer 243 | 244 | ```typescript 245 | import { MetadataEntry } from 'constructs' 246 | 247 | const metadataEntry: MetadataEntry = { ... } 248 | ``` 249 | 250 | #### Properties 251 | 252 | | **Name** | **Type** | **Description** | 253 | | --- | --- | --- | 254 | | data | any | The data. | 255 | | type | string | The metadata entry type. | 256 | | trace | string[] | Stack trace at the point of adding the metadata. | 257 | 258 | --- 259 | 260 | ##### `data`Required 261 | 262 | ```typescript 263 | public readonly data: any; 264 | ``` 265 | 266 | - *Type:* any 267 | 268 | The data. 269 | 270 | --- 271 | 272 | ##### `type`Required 273 | 274 | ```typescript 275 | public readonly type: string; 276 | ``` 277 | 278 | - *Type:* string 279 | 280 | The metadata entry type. 281 | 282 | --- 283 | 284 | ##### `trace`Optional 285 | 286 | ```typescript 287 | public readonly trace: string[]; 288 | ``` 289 | 290 | - *Type:* string[] 291 | - *Default:* no trace information 292 | 293 | Stack trace at the point of adding the metadata. 294 | 295 | Only available if `addMetadata()` is called with `stackTrace: true`. 296 | 297 | --- 298 | 299 | ### MetadataOptions 300 | 301 | Options for `construct.addMetadata()`. 302 | 303 | #### Initializer 304 | 305 | ```typescript 306 | import { MetadataOptions } from 'constructs' 307 | 308 | const metadataOptions: MetadataOptions = { ... } 309 | ``` 310 | 311 | #### Properties 312 | 313 | | **Name** | **Type** | **Description** | 314 | | --- | --- | --- | 315 | | stackTrace | boolean | Include stack trace with metadata entry. | 316 | | traceFromFunction | any | A JavaScript function to begin tracing from. | 317 | 318 | --- 319 | 320 | ##### `stackTrace`Optional 321 | 322 | ```typescript 323 | public readonly stackTrace: boolean; 324 | ``` 325 | 326 | - *Type:* boolean 327 | - *Default:* false 328 | 329 | Include stack trace with metadata entry. 330 | 331 | --- 332 | 333 | ##### `traceFromFunction`Optional 334 | 335 | ```typescript 336 | public readonly traceFromFunction: any; 337 | ``` 338 | 339 | - *Type:* any 340 | - *Default:* addMetadata() 341 | 342 | A JavaScript function to begin tracing from. 343 | 344 | This option is ignored unless `stackTrace` is `true`. 345 | 346 | --- 347 | 348 | ## Classes 349 | 350 | ### Dependable 351 | 352 | Trait for IDependable. 353 | 354 | Traits are interfaces that are privately implemented by objects. Instead of 355 | showing up in the public interface of a class, they need to be queried 356 | explicitly. This is used to implement certain framework features that are 357 | not intended to be used by Construct consumers, and so should be hidden 358 | from accidental use. 359 | 360 | *Example* 361 | 362 | ```typescript 363 | // Usage 364 | const roots = Dependable.of(construct).dependencyRoots; 365 | 366 | // Definition 367 | Dependable.implement(construct, { 368 | dependencyRoots: [construct], 369 | }); 370 | ``` 371 | 372 | 373 | #### Initializers 374 | 375 | ```typescript 376 | import { Dependable } from 'constructs' 377 | 378 | new Dependable() 379 | ``` 380 | 381 | | **Name** | **Type** | **Description** | 382 | | --- | --- | --- | 383 | 384 | --- 385 | 386 | 387 | #### Static Functions 388 | 389 | | **Name** | **Description** | 390 | | --- | --- | 391 | | get | Return the matching Dependable for the given class instance. | 392 | | implement | Turn any object into an IDependable. | 393 | | of | Return the matching Dependable for the given class instance. | 394 | 395 | --- 396 | 397 | ##### ~~`get`~~ 398 | 399 | ```typescript 400 | import { Dependable } from 'constructs' 401 | 402 | Dependable.get(instance: IDependable) 403 | ``` 404 | 405 | Return the matching Dependable for the given class instance. 406 | 407 | ###### `instance`Required 408 | 409 | - *Type:* IDependable 410 | 411 | --- 412 | 413 | ##### `implement` 414 | 415 | ```typescript 416 | import { Dependable } from 'constructs' 417 | 418 | Dependable.implement(instance: IDependable, trait: Dependable) 419 | ``` 420 | 421 | Turn any object into an IDependable. 422 | 423 | ###### `instance`Required 424 | 425 | - *Type:* IDependable 426 | 427 | --- 428 | 429 | ###### `trait`Required 430 | 431 | - *Type:* Dependable 432 | 433 | --- 434 | 435 | ##### `of` 436 | 437 | ```typescript 438 | import { Dependable } from 'constructs' 439 | 440 | Dependable.of(instance: IDependable) 441 | ``` 442 | 443 | Return the matching Dependable for the given class instance. 444 | 445 | ###### `instance`Required 446 | 447 | - *Type:* IDependable 448 | 449 | --- 450 | 451 | #### Properties 452 | 453 | | **Name** | **Type** | **Description** | 454 | | --- | --- | --- | 455 | | dependencyRoots | IConstruct[] | The set of constructs that form the root of this dependable. | 456 | 457 | --- 458 | 459 | ##### `dependencyRoots`Required 460 | 461 | ```typescript 462 | public readonly dependencyRoots: IConstruct[]; 463 | ``` 464 | 465 | - *Type:* IConstruct[] 466 | 467 | The set of constructs that form the root of this dependable. 468 | 469 | All resources under all returned constructs are included in the ordering 470 | dependency. 471 | 472 | --- 473 | 474 | 475 | ### DependencyGroup 476 | 477 | - *Implements:* IDependable 478 | 479 | A set of constructs to be used as a dependable. 480 | 481 | This class can be used when a set of constructs which are disjoint in the 482 | construct tree needs to be combined to be used as a single dependable. 483 | 484 | #### Initializers 485 | 486 | ```typescript 487 | import { DependencyGroup } from 'constructs' 488 | 489 | new DependencyGroup(deps: ...IDependable[]) 490 | ``` 491 | 492 | | **Name** | **Type** | **Description** | 493 | | --- | --- | --- | 494 | | deps | ...IDependable[] | *No description.* | 495 | 496 | --- 497 | 498 | ##### `deps`Required 499 | 500 | - *Type:* ...IDependable[] 501 | 502 | --- 503 | 504 | #### Methods 505 | 506 | | **Name** | **Description** | 507 | | --- | --- | 508 | | add | Add a construct to the dependency roots. | 509 | 510 | --- 511 | 512 | ##### `add` 513 | 514 | ```typescript 515 | public add(scopes: ...IDependable[]): void 516 | ``` 517 | 518 | Add a construct to the dependency roots. 519 | 520 | ###### `scopes`Required 521 | 522 | - *Type:* ...IDependable[] 523 | 524 | --- 525 | 526 | 527 | 528 | 529 | ### Node 530 | 531 | Represents the construct node in the scope tree. 532 | 533 | #### Initializers 534 | 535 | ```typescript 536 | import { Node } from 'constructs' 537 | 538 | new Node(host: Construct, scope: IConstruct, id: string) 539 | ``` 540 | 541 | | **Name** | **Type** | **Description** | 542 | | --- | --- | --- | 543 | | host | Construct | *No description.* | 544 | | scope | IConstruct | *No description.* | 545 | | id | string | *No description.* | 546 | 547 | --- 548 | 549 | ##### `host`Required 550 | 551 | - *Type:* Construct 552 | 553 | --- 554 | 555 | ##### `scope`Required 556 | 557 | - *Type:* IConstruct 558 | 559 | --- 560 | 561 | ##### `id`Required 562 | 563 | - *Type:* string 564 | 565 | --- 566 | 567 | #### Methods 568 | 569 | | **Name** | **Description** | 570 | | --- | --- | 571 | | addDependency | Add an ordering dependency on another construct. | 572 | | addMetadata | Adds a metadata entry to this construct. | 573 | | addValidation | Adds a validation to this construct. | 574 | | findAll | Return this construct and all of its children in the given order. | 575 | | findChild | Return a direct child by id. | 576 | | getAllContext | Retrieves the all context of a node from tree context. | 577 | | getContext | Retrieves a value from tree context if present. Otherwise, would throw an error. | 578 | | lock | Locks this construct from allowing more children to be added. | 579 | | setContext | This can be used to set contextual values. | 580 | | tryFindChild | Return a direct child by id, or undefined. | 581 | | tryGetContext | Retrieves a value from tree context. | 582 | | tryRemoveChild | Remove the child with the given name, if present. | 583 | | validate | Validates this construct. | 584 | 585 | --- 586 | 587 | ##### `addDependency` 588 | 589 | ```typescript 590 | public addDependency(deps: ...IDependable[]): void 591 | ``` 592 | 593 | Add an ordering dependency on another construct. 594 | 595 | An `IDependable` 596 | 597 | ###### `deps`Required 598 | 599 | - *Type:* ...IDependable[] 600 | 601 | --- 602 | 603 | ##### `addMetadata` 604 | 605 | ```typescript 606 | public addMetadata(type: string, data: any, options?: MetadataOptions): void 607 | ``` 608 | 609 | Adds a metadata entry to this construct. 610 | 611 | Entries are arbitrary values and will also include a stack trace to allow tracing back to 612 | the code location for when the entry was added. It can be used, for example, to include source 613 | mapping in CloudFormation templates to improve diagnostics. 614 | Note that construct metadata is not the same as CloudFormation resource metadata and is never written to the CloudFormation template. 615 | The metadata entries are written to the Cloud Assembly Manifest if the `treeMetadata` property is specified in the props of the App that contains this Construct. 616 | 617 | ###### `type`Required 618 | 619 | - *Type:* string 620 | 621 | a string denoting the type of metadata. 622 | 623 | --- 624 | 625 | ###### `data`Required 626 | 627 | - *Type:* any 628 | 629 | the value of the metadata (can be a Token). 630 | 631 | If null/undefined, metadata will not be added. 632 | 633 | --- 634 | 635 | ###### `options`Optional 636 | 637 | - *Type:* MetadataOptions 638 | 639 | options. 640 | 641 | --- 642 | 643 | ##### `addValidation` 644 | 645 | ```typescript 646 | public addValidation(validation: IValidation): void 647 | ``` 648 | 649 | Adds a validation to this construct. 650 | 651 | When `node.validate()` is called, the `validate()` method will be called on 652 | all validations and all errors will be returned. 653 | 654 | ###### `validation`Required 655 | 656 | - *Type:* IValidation 657 | 658 | The validation object. 659 | 660 | --- 661 | 662 | ##### `findAll` 663 | 664 | ```typescript 665 | public findAll(order?: ConstructOrder): IConstruct[] 666 | ``` 667 | 668 | Return this construct and all of its children in the given order. 669 | 670 | ###### `order`Optional 671 | 672 | - *Type:* ConstructOrder 673 | 674 | --- 675 | 676 | ##### `findChild` 677 | 678 | ```typescript 679 | public findChild(id: string): IConstruct 680 | ``` 681 | 682 | Return a direct child by id. 683 | 684 | Throws an error if the child is not found. 685 | 686 | ###### `id`Required 687 | 688 | - *Type:* string 689 | 690 | Identifier of direct child. 691 | 692 | --- 693 | 694 | ##### `getAllContext` 695 | 696 | ```typescript 697 | public getAllContext(defaults?: object): any 698 | ``` 699 | 700 | Retrieves the all context of a node from tree context. 701 | 702 | Context is usually initialized at the root, but can be overridden at any point in the tree. 703 | 704 | ###### `defaults`Optional 705 | 706 | - *Type:* object 707 | 708 | Any keys to override the retrieved context. 709 | 710 | --- 711 | 712 | ##### `getContext` 713 | 714 | ```typescript 715 | public getContext(key: string): any 716 | ``` 717 | 718 | Retrieves a value from tree context if present. Otherwise, would throw an error. 719 | 720 | Context is usually initialized at the root, but can be overridden at any point in the tree. 721 | 722 | ###### `key`Required 723 | 724 | - *Type:* string 725 | 726 | The context key. 727 | 728 | --- 729 | 730 | ##### `lock` 731 | 732 | ```typescript 733 | public lock(): void 734 | ``` 735 | 736 | Locks this construct from allowing more children to be added. 737 | 738 | After this 739 | call, no more children can be added to this construct or to any children. 740 | 741 | ##### `setContext` 742 | 743 | ```typescript 744 | public setContext(key: string, value: any): void 745 | ``` 746 | 747 | This can be used to set contextual values. 748 | 749 | Context must be set before any children are added, since children may consult context info during construction. 750 | If the key already exists, it will be overridden. 751 | 752 | ###### `key`Required 753 | 754 | - *Type:* string 755 | 756 | The context key. 757 | 758 | --- 759 | 760 | ###### `value`Required 761 | 762 | - *Type:* any 763 | 764 | The context value. 765 | 766 | --- 767 | 768 | ##### `tryFindChild` 769 | 770 | ```typescript 771 | public tryFindChild(id: string): IConstruct 772 | ``` 773 | 774 | Return a direct child by id, or undefined. 775 | 776 | ###### `id`Required 777 | 778 | - *Type:* string 779 | 780 | Identifier of direct child. 781 | 782 | --- 783 | 784 | ##### `tryGetContext` 785 | 786 | ```typescript 787 | public tryGetContext(key: string): any 788 | ``` 789 | 790 | Retrieves a value from tree context. 791 | 792 | Context is usually initialized at the root, but can be overridden at any point in the tree. 793 | 794 | ###### `key`Required 795 | 796 | - *Type:* string 797 | 798 | The context key. 799 | 800 | --- 801 | 802 | ##### `tryRemoveChild` 803 | 804 | ```typescript 805 | public tryRemoveChild(childName: string): boolean 806 | ``` 807 | 808 | Remove the child with the given name, if present. 809 | 810 | ###### `childName`Required 811 | 812 | - *Type:* string 813 | 814 | --- 815 | 816 | ##### `validate` 817 | 818 | ```typescript 819 | public validate(): string[] 820 | ``` 821 | 822 | Validates this construct. 823 | 824 | Invokes the `validate()` method on all validations added through 825 | `addValidation()`. 826 | 827 | #### Static Functions 828 | 829 | | **Name** | **Description** | 830 | | --- | --- | 831 | | of | Returns the node associated with a construct. | 832 | 833 | --- 834 | 835 | ##### ~~`of`~~ 836 | 837 | ```typescript 838 | import { Node } from 'constructs' 839 | 840 | Node.of(construct: IConstruct) 841 | ``` 842 | 843 | Returns the node associated with a construct. 844 | 845 | ###### `construct`Required 846 | 847 | - *Type:* IConstruct 848 | 849 | the construct. 850 | 851 | --- 852 | 853 | #### Properties 854 | 855 | | **Name** | **Type** | **Description** | 856 | | --- | --- | --- | 857 | | addr | string | Returns an opaque tree-unique address for this construct. | 858 | | children | IConstruct[] | All direct children of this construct. | 859 | | dependencies | IConstruct[] | Return all dependencies registered on this node (non-recursive). | 860 | | id | string | The id of this construct within the current scope. | 861 | | locked | boolean | Returns true if this construct or the scopes in which it is defined are locked. | 862 | | metadata | MetadataEntry[] | An immutable array of metadata objects associated with this construct. | 863 | | path | string | The full, absolute path of this construct in the tree. | 864 | | root | IConstruct | Returns the root of the construct tree. | 865 | | scopes | IConstruct[] | All parent scopes of this construct. | 866 | | scope | IConstruct | Returns the scope in which this construct is defined. | 867 | | defaultChild | IConstruct | Returns the child construct that has the id `Default` or `Resource`. | 868 | 869 | --- 870 | 871 | ##### `addr`Required 872 | 873 | ```typescript 874 | public readonly addr: string; 875 | ``` 876 | 877 | - *Type:* string 878 | 879 | Returns an opaque tree-unique address for this construct. 880 | 881 | Addresses are 42 characters hexadecimal strings. They begin with "c8" 882 | followed by 40 lowercase hexadecimal characters (0-9a-f). 883 | 884 | Addresses are calculated using a SHA-1 of the components of the construct 885 | path. 886 | 887 | To enable refactoring of construct trees, constructs with the ID `Default` 888 | will be excluded from the calculation. In those cases constructs in the 889 | same tree may have the same address. 890 | 891 | --- 892 | 893 | *Example* 894 | 895 | ```typescript 896 | c83a2846e506bcc5f10682b564084bca2d275709ee 897 | ``` 898 | 899 | 900 | ##### `children`Required 901 | 902 | ```typescript 903 | public readonly children: IConstruct[]; 904 | ``` 905 | 906 | - *Type:* IConstruct[] 907 | 908 | All direct children of this construct. 909 | 910 | --- 911 | 912 | ##### `dependencies`Required 913 | 914 | ```typescript 915 | public readonly dependencies: IConstruct[]; 916 | ``` 917 | 918 | - *Type:* IConstruct[] 919 | 920 | Return all dependencies registered on this node (non-recursive). 921 | 922 | --- 923 | 924 | ##### `id`Required 925 | 926 | ```typescript 927 | public readonly id: string; 928 | ``` 929 | 930 | - *Type:* string 931 | 932 | The id of this construct within the current scope. 933 | 934 | This is a scope-unique id. To obtain an app-unique id for this construct, use `addr`. 935 | 936 | --- 937 | 938 | ##### `locked`Required 939 | 940 | ```typescript 941 | public readonly locked: boolean; 942 | ``` 943 | 944 | - *Type:* boolean 945 | 946 | Returns true if this construct or the scopes in which it is defined are locked. 947 | 948 | --- 949 | 950 | ##### `metadata`Required 951 | 952 | ```typescript 953 | public readonly metadata: MetadataEntry[]; 954 | ``` 955 | 956 | - *Type:* MetadataEntry[] 957 | 958 | An immutable array of metadata objects associated with this construct. 959 | 960 | This can be used, for example, to implement support for deprecation notices, source mapping, etc. 961 | 962 | --- 963 | 964 | ##### `path`Required 965 | 966 | ```typescript 967 | public readonly path: string; 968 | ``` 969 | 970 | - *Type:* string 971 | 972 | The full, absolute path of this construct in the tree. 973 | 974 | Components are separated by '/'. 975 | 976 | --- 977 | 978 | ##### `root`Required 979 | 980 | ```typescript 981 | public readonly root: IConstruct; 982 | ``` 983 | 984 | - *Type:* IConstruct 985 | 986 | Returns the root of the construct tree. 987 | 988 | --- 989 | 990 | ##### `scopes`Required 991 | 992 | ```typescript 993 | public readonly scopes: IConstruct[]; 994 | ``` 995 | 996 | - *Type:* IConstruct[] 997 | 998 | All parent scopes of this construct. 999 | 1000 | --- 1001 | 1002 | ##### `scope`Optional 1003 | 1004 | ```typescript 1005 | public readonly scope: IConstruct; 1006 | ``` 1007 | 1008 | - *Type:* IConstruct 1009 | 1010 | Returns the scope in which this construct is defined. 1011 | 1012 | The value is `undefined` at the root of the construct scope tree. 1013 | 1014 | --- 1015 | 1016 | ##### `defaultChild`Optional 1017 | 1018 | ```typescript 1019 | public readonly defaultChild: IConstruct; 1020 | ``` 1021 | 1022 | - *Type:* IConstruct 1023 | 1024 | Returns the child construct that has the id `Default` or `Resource`. 1025 | 1026 | This is usually the construct that provides the bulk of the underlying functionality. 1027 | Useful for modifications of the underlying construct that are not available at the higher levels. 1028 | Override the defaultChild property. 1029 | 1030 | This should only be used in the cases where the correct 1031 | default child is not named 'Resource' or 'Default' as it 1032 | should be. 1033 | 1034 | If you set this to undefined, the default behavior of finding 1035 | the child named 'Resource' or 'Default' will be used. 1036 | 1037 | --- 1038 | 1039 | #### Constants 1040 | 1041 | | **Name** | **Type** | **Description** | 1042 | | --- | --- | --- | 1043 | | PATH_SEP | string | Separator used to delimit construct path components. | 1044 | 1045 | --- 1046 | 1047 | ##### `PATH_SEP`Required 1048 | 1049 | ```typescript 1050 | public readonly PATH_SEP: string; 1051 | ``` 1052 | 1053 | - *Type:* string 1054 | 1055 | Separator used to delimit construct path components. 1056 | 1057 | --- 1058 | 1059 | ## Protocols 1060 | 1061 | ### IConstruct 1062 | 1063 | - *Extends:* IDependable 1064 | 1065 | - *Implemented By:* Construct, RootConstruct, cdklabs-projen-project-types.yarn.CdkLabsMonorepo, cdklabs-projen-project-types.yarn.Monorepo, cdklabs-projen-project-types.yarn.MonorepoRelease, cdklabs-projen-project-types.yarn.TypeScriptWorkspace, cdklabs-projen-project-types.yarn.WorkspaceRelease, cdklabs-projen-project-types.CdkConstructLibrary, cdklabs-projen-project-types.CdkJsiiProject, cdklabs-projen-project-types.CdkTypeScriptProject, cdklabs-projen-project-types.CdklabsConstructLibrary, cdklabs-projen-project-types.CdklabsJsiiProject, cdklabs-projen-project-types.CdklabsTypeScriptProject, cdklabs-projen-project-types.Rosetta, projen.awscdk.AutoDiscover, projen.awscdk.AwsCdkConstructLibrary, projen.awscdk.AwsCdkDeps, projen.awscdk.AwsCdkDepsJava, projen.awscdk.AwsCdkDepsJs, projen.awscdk.AwsCdkDepsPy, projen.awscdk.AwsCdkJavaApp, projen.awscdk.AwsCdkPythonApp, projen.awscdk.AwsCdkTypeScriptApp, projen.awscdk.CdkConfig, projen.awscdk.CdkTasks, projen.awscdk.ConstructLibraryAws, projen.awscdk.EdgeLambdaAutoDiscover, projen.awscdk.IntegrationTest, projen.awscdk.IntegrationTestAutoDiscover, projen.awscdk.LambdaAutoDiscover, projen.awscdk.LambdaExtension, projen.awscdk.LambdaExtensionAutoDiscover, projen.awscdk.LambdaFunction, projen.build.BuildWorkflow, projen.cdk.AutoDiscoverBase, projen.cdk.ConstructLibrary, projen.cdk.IntegrationTestAutoDiscoverBase, projen.cdk.IntegrationTestBase, projen.cdk.JsiiDocgen, projen.cdk.JsiiProject, projen.cdk8s.AutoDiscover, projen.cdk8s.Cdk8sDeps, projen.cdk8s.Cdk8sDepsPy, projen.cdk8s.Cdk8sPythonApp, projen.cdk8s.Cdk8sTypeScriptApp, projen.cdk8s.ConstructLibraryCdk8s, projen.cdk8s.IntegrationTest, projen.cdk8s.IntegrationTestAutoDiscover, projen.cdktf.ConstructLibraryCdktf, projen.circleci.Circleci, projen.github.AutoApprove, projen.github.AutoMerge, projen.github.AutoQueue, projen.github.Dependabot, projen.github.GitHub, projen.github.GitHubProject, projen.github.GithubWorkflow, projen.github.MergeQueue, projen.github.Mergify, projen.github.PullRequestBackport, projen.github.PullRequestLint, projen.github.PullRequestTemplate, projen.github.Stale, projen.github.TaskWorkflow, projen.github.TaskWorkflowJob, projen.gitlab.CiConfiguration, projen.gitlab.GitlabConfiguration, projen.gitlab.NestedConfiguration, projen.java.JavaProject, projen.java.Junit, projen.java.MavenCompile, projen.java.MavenPackaging, projen.java.MavenSample, projen.java.Pom, projen.java.Projenrc, projen.javascript.Bundler, projen.javascript.Eslint, projen.javascript.Jest, projen.javascript.LicenseChecker, projen.javascript.NodePackage, projen.javascript.NodeProject, projen.javascript.NpmConfig, projen.javascript.Prettier, projen.javascript.Projenrc, projen.javascript.TypescriptConfig, projen.javascript.UpgradeDependencies, projen.javascript.Yarnrc, projen.python.Pip, projen.python.Poetry, projen.python.PoetryPyproject, projen.python.Projenrc, projen.python.Pytest, projen.python.PytestSample, projen.python.PythonProject, projen.python.PythonSample, projen.python.RequirementsFile, projen.python.SetupPy, projen.python.Setuptools, projen.python.Venv, projen.release.Publisher, projen.release.Release, projen.typescript.Projenrc, projen.typescript.ProjenrcTs, projen.typescript.TypeScriptAppProject, projen.typescript.TypeScriptLibraryProject, projen.typescript.TypeScriptProject, projen.vscode.DevContainer, projen.vscode.VsCode, projen.vscode.VsCodeLaunchConfig, projen.vscode.VsCodeRecommendedExtensions, projen.vscode.VsCodeSettings, projen.web.NextComponent, projen.web.NextJsProject, projen.web.NextJsTypeScriptProject, projen.web.ReactComponent, projen.web.ReactProject, projen.web.ReactTypeDef, projen.web.ReactTypeScriptProject, projen.Component, projen.Dependencies, projen.DockerCompose, projen.FileBase, projen.GitAttributesFile, projen.Gitpod, projen.IgnoreFile, projen.IniFile, projen.JsonFile, projen.License, projen.Logger, projen.Makefile, projen.ObjectFile, projen.Project, projen.ProjectBuild, projen.ProjectTree, projen.Projenrc, projen.ProjenrcFile, projen.ProjenrcJson, projen.Renovatebot, projen.SampleDir, projen.SampleFile, projen.SampleReadme, projen.SourceCode, projen.Tasks, projen.TextFile, projen.TomlFile, projen.Version, projen.XmlFile, projen.YamlFile, IConstruct 1066 | 1067 | Represents a construct. 1068 | 1069 | 1070 | #### Properties 1071 | 1072 | | **Name** | **Type** | **Description** | 1073 | | --- | --- | --- | 1074 | | node | Node | The tree node. | 1075 | 1076 | --- 1077 | 1078 | ##### `node`Required 1079 | 1080 | ```typescript 1081 | public readonly node: Node; 1082 | ``` 1083 | 1084 | - *Type:* Node 1085 | 1086 | The tree node. 1087 | 1088 | --- 1089 | 1090 | ### IDependable 1091 | 1092 | - *Implemented By:* Construct, DependencyGroup, RootConstruct, cdklabs-projen-project-types.yarn.CdkLabsMonorepo, cdklabs-projen-project-types.yarn.Monorepo, cdklabs-projen-project-types.yarn.MonorepoRelease, cdklabs-projen-project-types.yarn.TypeScriptWorkspace, cdklabs-projen-project-types.yarn.WorkspaceRelease, cdklabs-projen-project-types.CdkConstructLibrary, cdklabs-projen-project-types.CdkJsiiProject, cdklabs-projen-project-types.CdkTypeScriptProject, cdklabs-projen-project-types.CdklabsConstructLibrary, cdklabs-projen-project-types.CdklabsJsiiProject, cdklabs-projen-project-types.CdklabsTypeScriptProject, cdklabs-projen-project-types.Rosetta, projen.awscdk.AutoDiscover, projen.awscdk.AwsCdkConstructLibrary, projen.awscdk.AwsCdkDeps, projen.awscdk.AwsCdkDepsJava, projen.awscdk.AwsCdkDepsJs, projen.awscdk.AwsCdkDepsPy, projen.awscdk.AwsCdkJavaApp, projen.awscdk.AwsCdkPythonApp, projen.awscdk.AwsCdkTypeScriptApp, projen.awscdk.CdkConfig, projen.awscdk.CdkTasks, projen.awscdk.ConstructLibraryAws, projen.awscdk.EdgeLambdaAutoDiscover, projen.awscdk.IntegrationTest, projen.awscdk.IntegrationTestAutoDiscover, projen.awscdk.LambdaAutoDiscover, projen.awscdk.LambdaExtension, projen.awscdk.LambdaExtensionAutoDiscover, projen.awscdk.LambdaFunction, projen.build.BuildWorkflow, projen.cdk.AutoDiscoverBase, projen.cdk.ConstructLibrary, projen.cdk.IntegrationTestAutoDiscoverBase, projen.cdk.IntegrationTestBase, projen.cdk.JsiiDocgen, projen.cdk.JsiiProject, projen.cdk8s.AutoDiscover, projen.cdk8s.Cdk8sDeps, projen.cdk8s.Cdk8sDepsPy, projen.cdk8s.Cdk8sPythonApp, projen.cdk8s.Cdk8sTypeScriptApp, projen.cdk8s.ConstructLibraryCdk8s, projen.cdk8s.IntegrationTest, projen.cdk8s.IntegrationTestAutoDiscover, projen.cdktf.ConstructLibraryCdktf, projen.circleci.Circleci, projen.github.AutoApprove, projen.github.AutoMerge, projen.github.AutoQueue, projen.github.Dependabot, projen.github.GitHub, projen.github.GitHubProject, projen.github.GithubWorkflow, projen.github.MergeQueue, projen.github.Mergify, projen.github.PullRequestBackport, projen.github.PullRequestLint, projen.github.PullRequestTemplate, projen.github.Stale, projen.github.TaskWorkflow, projen.github.TaskWorkflowJob, projen.gitlab.CiConfiguration, projen.gitlab.GitlabConfiguration, projen.gitlab.NestedConfiguration, projen.java.JavaProject, projen.java.Junit, projen.java.MavenCompile, projen.java.MavenPackaging, projen.java.MavenSample, projen.java.Pom, projen.java.Projenrc, projen.javascript.Bundler, projen.javascript.Eslint, projen.javascript.Jest, projen.javascript.LicenseChecker, projen.javascript.NodePackage, projen.javascript.NodeProject, projen.javascript.NpmConfig, projen.javascript.Prettier, projen.javascript.Projenrc, projen.javascript.TypescriptConfig, projen.javascript.UpgradeDependencies, projen.javascript.Yarnrc, projen.python.Pip, projen.python.Poetry, projen.python.PoetryPyproject, projen.python.Projenrc, projen.python.Pytest, projen.python.PytestSample, projen.python.PythonProject, projen.python.PythonSample, projen.python.RequirementsFile, projen.python.SetupPy, projen.python.Setuptools, projen.python.Venv, projen.release.Publisher, projen.release.Release, projen.typescript.Projenrc, projen.typescript.ProjenrcTs, projen.typescript.TypeScriptAppProject, projen.typescript.TypeScriptLibraryProject, projen.typescript.TypeScriptProject, projen.vscode.DevContainer, projen.vscode.VsCode, projen.vscode.VsCodeLaunchConfig, projen.vscode.VsCodeRecommendedExtensions, projen.vscode.VsCodeSettings, projen.web.NextComponent, projen.web.NextJsProject, projen.web.NextJsTypeScriptProject, projen.web.ReactComponent, projen.web.ReactProject, projen.web.ReactTypeDef, projen.web.ReactTypeScriptProject, projen.Component, projen.Dependencies, projen.DockerCompose, projen.FileBase, projen.GitAttributesFile, projen.Gitpod, projen.IgnoreFile, projen.IniFile, projen.JsonFile, projen.License, projen.Logger, projen.Makefile, projen.ObjectFile, projen.Project, projen.ProjectBuild, projen.ProjectTree, projen.Projenrc, projen.ProjenrcFile, projen.ProjenrcJson, projen.Renovatebot, projen.SampleDir, projen.SampleFile, projen.SampleReadme, projen.SourceCode, projen.Tasks, projen.TextFile, projen.TomlFile, projen.Version, projen.XmlFile, projen.YamlFile, IConstruct, IDependable 1093 | 1094 | Trait marker for classes that can be depended upon. 1095 | 1096 | The presence of this interface indicates that an object has 1097 | an `IDependable` implementation. 1098 | 1099 | This interface can be used to take an (ordering) dependency on a set of 1100 | constructs. An ordering dependency implies that the resources represented by 1101 | those constructs are deployed before the resources depending ON them are 1102 | deployed. 1103 | 1104 | 1105 | 1106 | ### IValidation 1107 | 1108 | - *Implemented By:* IValidation 1109 | 1110 | Implement this interface in order for the construct to be able to validate itself. 1111 | 1112 | #### Methods 1113 | 1114 | | **Name** | **Description** | 1115 | | --- | --- | 1116 | | validate | Validate the current construct. | 1117 | 1118 | --- 1119 | 1120 | ##### `validate` 1121 | 1122 | ```typescript 1123 | public validate(): string[] 1124 | ``` 1125 | 1126 | Validate the current construct. 1127 | 1128 | This method can be implemented by derived constructs in order to perform 1129 | validation logic. It is called on all constructs before synthesis. 1130 | 1131 | 1132 | ## Enums 1133 | 1134 | ### ConstructOrder 1135 | 1136 | In what order to return constructs. 1137 | 1138 | #### Members 1139 | 1140 | | **Name** | **Description** | 1141 | | --- | --- | 1142 | | PREORDER | Depth-first, pre-order. | 1143 | | POSTORDER | Depth-first, post-order (leaf nodes first). | 1144 | 1145 | --- 1146 | 1147 | ##### `PREORDER` 1148 | 1149 | Depth-first, pre-order. 1150 | 1151 | --- 1152 | 1153 | 1154 | ##### `POSTORDER` 1155 | 1156 | Depth-first, post-order (leaf nodes first). 1157 | 1158 | --- 1159 | 1160 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug 4 | report, new feature, correction, or additional documentation, we greatly value 5 | feedback and contributions from our community. 6 | 7 | Please read through this document before submitting any issues or pull requests 8 | to ensure we have all the necessary information to effectively respond to your 9 | bug report or contribution. 10 | 11 | ## Development 12 | 13 | To setup a development environment, you'll need 14 | [Node.js](https://nodejs.org/en/) and [yarn](https://yarnpkg.com/). 15 | 16 | Fresh full build: 17 | 18 | 1. Clone the repo 19 | 2. Run `yarn install` 20 | 3. Run `yarn build` 21 | 22 | You can also use these more granular scripts: 23 | 24 | - `yarn compile`: compile code to JavaScript 25 | - `yarn watch`: watch in the background and compile 26 | - `yarn test`: run all tests and linters 27 | - `yarn compat`: check that APIs do not introduce breaking changes 28 | - `yarn lint`: run eslint and API compatibility 29 | 30 | ### Docker Build 31 | 32 | If you want to use docker to build, test and package your work use the following: 33 | 34 | ```shell script 35 | docker build -t constructs . 36 | ``` 37 | 38 | ### Using a local version of this library in a dependency 39 | 40 | If you're doing changes to this library, 41 | you often want to test them being used in a real dependency 42 | (for example, the [AWS CDK](https://github.com/aws/aws-cdk)) 43 | to verify the changes work like expected. 44 | To make that easier, 45 | this repository includes a script in the `scripts` 46 | directory that overwrites the version of `constructs` 47 | in a dependency's `node_modules` 48 | with a symbolic link to the local version of `constructs`: 49 | 50 | ```shell script 51 | cd my/project/that/uses/constructs/library 52 | /path/to/source/of/constructs/scripts/link.sh 53 | ``` 54 | 55 | ## Reporting Bugs/Feature Requests 56 | 57 | We welcome you to use the GitHub issue tracker to report bugs or suggest 58 | features. 59 | 60 | When filing an issue, please check existing open, or recently closed, issues to 61 | make sure somebody else hasn't already reported the issue. Please try to include 62 | as much information as you can. Details like these are incredibly useful: 63 | 64 | * A reproducible test case or series of steps 65 | * The version of our code being used 66 | * Any modifications you've made relevant to the bug 67 | * Anything unusual about your environment or deployment 68 | 69 | ## Contributing via Pull Requests 70 | 71 | Contributions via pull requests are much appreciated. Before sending us a pull 72 | request, please ensure that: 73 | 74 | 1. You are working against the latest source on the *master* branch. 75 | 2. You check existing open, and recently merged, pull requests to make sure 76 | someone else hasn't addressed the problem already. 77 | 3. You open an issue to discuss any significant work - we would hate for your 78 | time to be wasted. 79 | 80 | To send us a pull request, please: 81 | 82 | 1. Fork the repository. 83 | 2. Modify the source; please focus on the specific change you are contributing. 84 | If you also reformat all the code, it will be hard for us to focus on your 85 | change. 86 | 3. Ensure local tests pass. 87 | 4. Commit to your fork using clear commit messages. 88 | 5. Send us a pull request, answering any default questions in the pull request 89 | interface. 90 | 6. Pay attention to any automated CI failures reported in the pull request, and 91 | stay involved in the conversation. 92 | 93 | GitHub provides additional document on [forking a 94 | repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull 95 | request](https://help.github.com/articles/creating-a-pull-request/). 96 | 97 | 98 | ## Finding contributions to work on 99 | 100 | Looking at the existing issues is a great way to find something to contribute 101 | on. As our projects, by default, use the default GitHub issue labels 102 | (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 103 | 'help wanted' issues is a great place to start. 104 | 105 | 106 | ## Code of Conduct 107 | 108 | This project has adopted the [Amazon Open Source Code of 109 | Conduct](https://aws.github.io/code-of-conduct). For more information see the 110 | [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 111 | opensource-codeofconduct@amazon.com with any additional questions or comments. 112 | 113 | ## Security issue notifications 114 | 115 | If you discover a potential security issue in this project we ask that you 116 | notify AWS/Amazon Security via our [vulnerability reporting 117 | page](http://aws.amazon.com/security/vulnerability-reporting/). Please do 118 | **not** create a public github issue. 119 | 120 | ## Releasing a New Version 121 | 122 | To release a new version, run `yarn bump` which will:" 123 | 124 | - Calculate the next version (minor/patch) based on [conventional 125 | commits](https://www.conventionalcommits.org/en/v1.0.0/). 126 | - Update the [CHANGELOG](./CHANGELOG.md). 127 | - Create a git commit and tag. 128 | 129 | Then, execute: 130 | 131 | ```shell 132 | git push --follow-tags origin master 133 | ``` 134 | 135 | Once the commit is pushed to master, the [release 136 | workflow](./.github/workflows/release.yml) will be triggered and the new version 137 | will be published to all package managers. 138 | 139 | ## Licensing 140 | 141 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to 142 | confirm the licensing of your contribution. 143 | 144 | We may ask you to sign a [Contributor License Agreement 145 | (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger 146 | changes. 147 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jsii/superchain:1-bookworm-slim 2 | 3 | WORKDIR /app 4 | 5 | ARG BUILD_ARGS 6 | 7 | COPY . . 8 | 9 | RUN yarn install && yarn build ${BUILD_ARGS} 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Constructs 2 | 3 | > Software-defined persistent state 4 | 5 | [![Release](https://github.com/aws/constructs/actions/workflows/release.yml/badge.svg)](https://github.com/aws/constructs/actions/workflows/release.yml) 6 | [![npm version](https://badge.fury.io/js/constructs.svg)](https://badge.fury.io/js/constructs) 7 | [![PyPI version](https://badge.fury.io/py/constructs.svg)](https://badge.fury.io/py/constructs) 8 | [![NuGet version](https://badge.fury.io/nu/Constructs.svg)](https://badge.fury.io/nu/Constructs) 9 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/software.constructs/constructs/badge.svg?style=plastic)](https://maven-badges.herokuapp.com/maven-central/software.constructs/constructs) 10 | 11 | ## What are constructs? 12 | 13 | Constructs are classes which define a "piece of system state". Constructs can be composed together to form higher-level building blocks which represent more complex state. 14 | 15 | Constructs are often used to represent the _desired state_ of cloud applications. For example, in the AWS CDK, which is used to define the desired state for AWS infrastructure using CloudFormation, the lowest-level construct represents a _resource definition_ in a CloudFormation template. These resources are composed to represent higher-level logical units of a cloud application, etc. 16 | 17 | ## Support policy 18 | 19 | All [maintained Node.js versions](https://nodejs.org/en/about/previous-releases#release-schedule) are supported by this package. 20 | 21 | ## Contributing 22 | 23 | This project has adopted the [Amazon Open Source Code of 24 | Conduct](https://aws.github.io/code-of-conduct). 25 | 26 | We welcome community contributions and pull requests. See our [contribution 27 | guide](./CONTRIBUTING.md) for more information on how to report issues, set up a 28 | development environment and submit code. 29 | 30 | ## License 31 | 32 | This project is distributed under the [Apache License, Version 2.0](./LICENSE). 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "constructs", 3 | "description": "A programming model for software-defined state", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/aws/constructs.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 | "package": "npx projen package", 19 | "package-all": "npx projen package-all", 20 | "package:dotnet": "npx projen package:dotnet", 21 | "package:go": "npx projen package:go", 22 | "package:java": "npx projen package:java", 23 | "package:js": "npx projen package:js", 24 | "package:python": "npx projen package:python", 25 | "post-compile": "npx projen post-compile", 26 | "post-upgrade": "npx projen post-upgrade", 27 | "pre-compile": "npx projen pre-compile", 28 | "release:10.x": "npx projen release:10.x", 29 | "test": "npx projen test", 30 | "test:watch": "npx projen test:watch", 31 | "unbump": "npx projen unbump", 32 | "upgrade": "npx projen upgrade", 33 | "upgrade-cdklabs-projen-project-types": "npx projen upgrade-cdklabs-projen-project-types", 34 | "upgrade-dev-deps": "npx projen upgrade-dev-deps", 35 | "watch": "npx projen watch", 36 | "projen": "npx projen" 37 | }, 38 | "author": { 39 | "name": "Amazon Web Services", 40 | "email": "aws-cdk-dev@amazon.com", 41 | "organization": true 42 | }, 43 | "devDependencies": { 44 | "@stylistic/eslint-plugin": "^2", 45 | "@types/jest": "^29", 46 | "@types/node": "^18", 47 | "@typescript-eslint/eslint-plugin": "^8", 48 | "@typescript-eslint/parser": "^8", 49 | "cdklabs-projen-project-types": "^0.3.1", 50 | "commit-and-tag-version": "^12", 51 | "eslint": "^9", 52 | "eslint-import-resolver-typescript": "^3.10.1", 53 | "eslint-plugin-import": "^2.31.0", 54 | "jest": "^29", 55 | "jest-junit": "^16", 56 | "jsii": "5.4.x", 57 | "jsii-diff": "^1.112.0", 58 | "jsii-docgen": "^10.5.0", 59 | "jsii-pacmak": "1.102.0", 60 | "jsii-rosetta": "5.4.x", 61 | "projen": "^0.92.9", 62 | "ts-jest": "^29", 63 | "ts-node": "^10.9.2", 64 | "typescript": "5.4.x" 65 | }, 66 | "keywords": [ 67 | "aws", 68 | "cdk", 69 | "constructs", 70 | "jsii" 71 | ], 72 | "main": "lib/index.js", 73 | "license": "Apache-2.0", 74 | "homepage": "https://github.com/aws/constructs", 75 | "publishConfig": { 76 | "access": "public" 77 | }, 78 | "version": "0.0.0", 79 | "jest": { 80 | "coverageProvider": "v8", 81 | "testMatch": [ 82 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 83 | "/@(src|test)/**/__tests__/**/*.ts?(x)", 84 | "/@(projenrc)/**/*(*.)@(spec|test).ts?(x)", 85 | "/@(projenrc)/**/__tests__/**/*.ts?(x)" 86 | ], 87 | "clearMocks": true, 88 | "collectCoverage": true, 89 | "coverageReporters": [ 90 | "json", 91 | "lcov", 92 | "clover", 93 | "cobertura", 94 | "text" 95 | ], 96 | "coverageDirectory": "coverage", 97 | "coveragePathIgnorePatterns": [ 98 | "/node_modules/" 99 | ], 100 | "testPathIgnorePatterns": [ 101 | "/node_modules/" 102 | ], 103 | "watchPathIgnorePatterns": [ 104 | "/node_modules/" 105 | ], 106 | "reporters": [ 107 | "default", 108 | [ 109 | "jest-junit", 110 | { 111 | "outputDirectory": "test-reports" 112 | } 113 | ] 114 | ], 115 | "transform": { 116 | "^.+\\.[t]sx?$": [ 117 | "ts-jest", 118 | { 119 | "tsconfig": "tsconfig.dev.json" 120 | } 121 | ] 122 | } 123 | }, 124 | "types": "lib/index.d.ts", 125 | "stability": "stable", 126 | "jsii": { 127 | "outdir": "dist", 128 | "targets": { 129 | "java": { 130 | "package": "software.constructs", 131 | "maven": { 132 | "groupId": "software.constructs", 133 | "artifactId": "constructs" 134 | } 135 | }, 136 | "python": { 137 | "distName": "constructs", 138 | "module": "constructs" 139 | }, 140 | "dotnet": { 141 | "namespace": "Constructs", 142 | "packageId": "Constructs" 143 | }, 144 | "go": { 145 | "moduleName": "github.com/aws/constructs-go" 146 | } 147 | }, 148 | "tsc": { 149 | "outDir": "lib", 150 | "rootDir": "src" 151 | } 152 | }, 153 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 154 | } 155 | -------------------------------------------------------------------------------- /scripts/compat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # verifies that the package does not introduce breaking changes to APIs 3 | # against the currently published version. 4 | set -euo pipefail 5 | script_dir="$(cd $(dirname $0) && pwd)" 6 | package_name=$(node -p "require('./package.json').name") 7 | exec npx jsii-diff --keys npm:${package_name} 8 | -------------------------------------------------------------------------------- /scripts/link.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Creates a symlink under node_modules of a package consuming this library 4 | # that replaces the version from NPM with this local version. 5 | # Helpful when developing a project that depends on this library 6 | # (like the AWS CDK), 7 | # and needing to do some changes in this package - 8 | # you can test your changes on a real consumer of this library. 9 | # 10 | # Usage: 11 | # cd my/project/that/uses/constructs/library 12 | # /path/to/source/of/constructs/scripts/link.sh 13 | 14 | set -euo pipefail 15 | 16 | # we are in scripts/, so the root is one level up 17 | root="$(cd $(dirname $0)/.. && pwd)" 18 | mkdir -p node_modules # -p makes mkdir not fail if the directory already exists 19 | dest=node_modules/constructs 20 | 21 | echo "Linking: '${root}' to '${dest}'" 22 | rm -fr ${dest} 23 | # since root is a directory, node_modules needs to be the destination 24 | ln -fs ${root} node_modules 25 | -------------------------------------------------------------------------------- /src/construct.ts: -------------------------------------------------------------------------------- 1 | import { Dependable, IDependable } from './dependency'; 2 | import { MetadataEntry } from './metadata'; 3 | import { captureStackTrace } from './private/stack-trace'; 4 | import { addressOf } from './private/uniqueid'; 5 | 6 | const CONSTRUCT_SYM = Symbol.for('constructs.Construct'); 7 | 8 | /** 9 | * Represents a construct. 10 | */ 11 | export interface IConstruct extends IDependable { 12 | /** 13 | * The tree node. 14 | */ 15 | readonly node: Node; 16 | } 17 | 18 | /** 19 | * Represents the construct node in the scope tree. 20 | */ 21 | export class Node { 22 | /** 23 | * Separator used to delimit construct path components. 24 | */ 25 | public static readonly PATH_SEP = '/'; 26 | 27 | /** 28 | * Returns the node associated with a construct. 29 | * @param construct the construct 30 | * 31 | * @deprecated use `construct.node` instead 32 | */ 33 | public static of(construct: IConstruct): Node { 34 | return construct.node; 35 | } 36 | 37 | /** 38 | * Returns the scope in which this construct is defined. 39 | * 40 | * The value is `undefined` at the root of the construct scope tree. 41 | */ 42 | public readonly scope?: IConstruct; 43 | 44 | /** 45 | * The id of this construct within the current scope. 46 | * 47 | * This is a scope-unique id. To obtain an app-unique id for this construct, use `addr`. 48 | */ 49 | public readonly id: string; 50 | 51 | private _locked = false; // if this is "true", addChild will fail 52 | private readonly _children: { [id: string]: IConstruct } = { }; 53 | private readonly _context: { [key: string]: any } = { }; 54 | private readonly _metadata = new Array(); 55 | private readonly _dependencies = new Set(); 56 | private _defaultChild: IConstruct | undefined; 57 | private readonly _validations = new Array(); 58 | private _addr?: string; // cache 59 | 60 | public constructor(private readonly host: Construct, scope: IConstruct, id: string) { 61 | id = id ?? ''; // if undefined, convert to empty string 62 | 63 | this.id = sanitizeId(id); 64 | this.scope = scope; 65 | 66 | if (scope && !this.id) { 67 | throw new Error('Only root constructs may have an empty ID'); 68 | } 69 | 70 | // add to parent scope 71 | scope?.node.addChild(host, this.id); 72 | } 73 | 74 | /** 75 | * The full, absolute path of this construct in the tree. 76 | * 77 | * Components are separated by '/'. 78 | */ 79 | public get path(): string { 80 | const components = []; 81 | for (const scope of this.scopes) { 82 | if (scope.node.id) { 83 | components.push(scope.node.id); 84 | } 85 | } 86 | return components.join(Node.PATH_SEP); 87 | } 88 | 89 | /** 90 | * Returns an opaque tree-unique address for this construct. 91 | * 92 | * Addresses are 42 characters hexadecimal strings. They begin with "c8" 93 | * followed by 40 lowercase hexadecimal characters (0-9a-f). 94 | * 95 | * Addresses are calculated using a SHA-1 of the components of the construct 96 | * path. 97 | * 98 | * To enable refactoring of construct trees, constructs with the ID `Default` 99 | * will be excluded from the calculation. In those cases constructs in the 100 | * same tree may have the same address. 101 | * 102 | * @example c83a2846e506bcc5f10682b564084bca2d275709ee 103 | */ 104 | public get addr(): string { 105 | if (!this._addr) { 106 | this._addr = addressOf(this.scopes.map(c => c.node.id)); 107 | } 108 | 109 | return this._addr; 110 | } 111 | 112 | /** 113 | * Return a direct child by id, or undefined 114 | * 115 | * @param id Identifier of direct child 116 | * @returns the child if found, or undefined 117 | */ 118 | public tryFindChild(id: string): IConstruct | undefined { 119 | return this._children[sanitizeId(id)]; 120 | } 121 | 122 | /** 123 | * Return a direct child by id 124 | * 125 | * Throws an error if the child is not found. 126 | * 127 | * @param id Identifier of direct child 128 | * @returns Child with the given id. 129 | */ 130 | public findChild(id: string): IConstruct { 131 | const ret = this.tryFindChild(id); 132 | if (!ret) { 133 | throw new Error(`No child with id: '${id}'`); 134 | } 135 | return ret; 136 | } 137 | 138 | /** 139 | * Returns the child construct that has the id `Default` or `Resource`. 140 | * This is usually the construct that provides the bulk of the underlying functionality. 141 | * Useful for modifications of the underlying construct that are not available at the higher levels. 142 | * 143 | * @throws if there is more than one child 144 | * @returns a construct or undefined if there is no default child 145 | */ 146 | public get defaultChild(): IConstruct | undefined { 147 | if (this._defaultChild !== undefined) { 148 | return this._defaultChild; 149 | } 150 | 151 | const resourceChild = this.tryFindChild('Resource'); 152 | const defaultChild = this.tryFindChild('Default'); 153 | if (resourceChild && defaultChild) { 154 | throw new Error(`Cannot determine default child for ${this.path}. There is both a child with id "Resource" and id "Default"`); 155 | } 156 | 157 | return defaultChild || resourceChild; 158 | } 159 | 160 | /** 161 | * Override the defaultChild property. 162 | * 163 | * This should only be used in the cases where the correct 164 | * default child is not named 'Resource' or 'Default' as it 165 | * should be. 166 | * 167 | * If you set this to undefined, the default behavior of finding 168 | * the child named 'Resource' or 'Default' will be used. 169 | */ 170 | public set defaultChild(value: IConstruct | undefined) { 171 | this._defaultChild = value; 172 | } 173 | 174 | /** 175 | * All direct children of this construct. 176 | */ 177 | public get children() { 178 | return Object.values(this._children); 179 | } 180 | 181 | /** 182 | * Return this construct and all of its children in the given order 183 | */ 184 | public findAll(order: ConstructOrder = ConstructOrder.PREORDER): IConstruct[] { 185 | const ret = new Array(); 186 | visit(this.host); 187 | return ret; 188 | 189 | function visit(c: IConstruct) { 190 | if (order === ConstructOrder.PREORDER) { 191 | ret.push(c); 192 | } 193 | 194 | for (const child of c.node.children) { 195 | visit(child); 196 | } 197 | 198 | if (order === ConstructOrder.POSTORDER) { 199 | ret.push(c); 200 | } 201 | } 202 | } 203 | 204 | /** 205 | * This can be used to set contextual values. 206 | * Context must be set before any children are added, since children may consult context info during construction. 207 | * If the key already exists, it will be overridden. 208 | * @param key The context key 209 | * @param value The context value 210 | */ 211 | public setContext(key: string, value: any) { 212 | if (this.children.length > 0) { 213 | const names = this.children.map(c => c.node.id); 214 | throw new Error('Cannot set context after children have been added: ' + names.join(',')); 215 | } 216 | this._context[key] = value; 217 | } 218 | 219 | /** 220 | * Retrieves a value from tree context if present. Otherwise, would throw an error. 221 | * 222 | * Context is usually initialized at the root, but can be overridden at any point in the tree. 223 | * 224 | * @param key The context key 225 | * @returns The context value or throws error if there is no context value for this key 226 | */ 227 | public getContext(key: string): any { 228 | const value = this._context[key]; 229 | 230 | if (value !== undefined) { return value; } 231 | 232 | if (value === undefined && !this.scope?.node) { 233 | throw new Error(`No context value present for ${key} key`); 234 | } 235 | 236 | return this.scope && this.scope.node.getContext(key); 237 | } 238 | 239 | /** 240 | * Retrieves the all context of a node from tree context. 241 | * 242 | * Context is usually initialized at the root, but can be overridden at any point in the tree. 243 | * 244 | * @param defaults Any keys to override the retrieved context 245 | * @returns The context object or an empty object if there is discovered context 246 | */ 247 | public getAllContext(defaults?: object): any { 248 | return this.scopes.reverse() 249 | .reduce((a, s) => ({ ...(s.node._context), ...a }), { ...defaults }); 250 | } 251 | 252 | /** 253 | * Retrieves a value from tree context. 254 | * 255 | * Context is usually initialized at the root, but can be overridden at any point in the tree. 256 | * 257 | * @param key The context key 258 | * @returns The context value or `undefined` if there is no context value for this key. 259 | */ 260 | public tryGetContext(key: string): any { 261 | const value = this._context[key]; 262 | if (value !== undefined) { return value; } 263 | 264 | return this.scope && this.scope.node.tryGetContext(key); 265 | } 266 | 267 | /** 268 | * An immutable array of metadata objects associated with this construct. 269 | * This can be used, for example, to implement support for deprecation notices, source mapping, etc. 270 | */ 271 | public get metadata() { 272 | return [...this._metadata]; 273 | } 274 | 275 | /** 276 | * Adds a metadata entry to this construct. 277 | * Entries are arbitrary values and will also include a stack trace to allow tracing back to 278 | * the code location for when the entry was added. It can be used, for example, to include source 279 | * mapping in CloudFormation templates to improve diagnostics. 280 | * Note that construct metadata is not the same as CloudFormation resource metadata and is never written to the CloudFormation template. 281 | * The metadata entries are written to the Cloud Assembly Manifest if the `treeMetadata` property is specified in the props of the App that contains this Construct. 282 | * 283 | * @param type a string denoting the type of metadata 284 | * @param data the value of the metadata (can be a Token). If null/undefined, metadata will not be added. 285 | * @param options options 286 | */ 287 | public addMetadata(type: string, data: any, options: MetadataOptions = { }): void { 288 | if (data == null) { 289 | return; 290 | } 291 | 292 | const shouldTrace = options.stackTrace ?? false; 293 | const trace = shouldTrace ? captureStackTrace(options.traceFromFunction ?? this.addMetadata) : undefined; 294 | this._metadata.push({ type, data, trace }); 295 | } 296 | 297 | /** 298 | * All parent scopes of this construct. 299 | * 300 | * @returns a list of parent scopes. The last element in the list will always 301 | * be the current construct and the first element will be the root of the 302 | * tree. 303 | */ 304 | public get scopes(): IConstruct[] { 305 | const ret = new Array(); 306 | 307 | let curr: IConstruct | undefined = this.host; 308 | while (curr) { 309 | ret.unshift(curr); 310 | curr = curr.node.scope; 311 | } 312 | 313 | return ret; 314 | } 315 | 316 | /** 317 | * Returns the root of the construct tree. 318 | * @returns The root of the construct tree. 319 | */ 320 | public get root() { 321 | return this.scopes[0]; 322 | } 323 | 324 | /** 325 | * Returns true if this construct or the scopes in which it is defined are 326 | * locked. 327 | */ 328 | public get locked() { 329 | if (this._locked) { 330 | return true; 331 | } 332 | 333 | if (this.scope && this.scope.node.locked) { 334 | return true; 335 | } 336 | 337 | return false; 338 | } 339 | 340 | /** 341 | * Add an ordering dependency on another construct. 342 | * 343 | * An `IDependable` 344 | */ 345 | public addDependency(...deps: IDependable[]) { 346 | for (const d of deps) { 347 | this._dependencies.add(d); 348 | } 349 | } 350 | 351 | /** 352 | * Return all dependencies registered on this node (non-recursive). 353 | */ 354 | public get dependencies(): IConstruct[] { 355 | const result = new Array(); 356 | for (const dep of this._dependencies) { 357 | for (const root of Dependable.of(dep).dependencyRoots) { 358 | result.push(root); 359 | } 360 | } 361 | 362 | return result; 363 | } 364 | 365 | /** 366 | * Remove the child with the given name, if present. 367 | * 368 | * @returns Whether a child with the given name was deleted. 369 | */ 370 | public tryRemoveChild(childName: string): boolean { 371 | if (!(childName in this._children)) { return false; } 372 | delete this._children[childName]; 373 | return true; 374 | } 375 | 376 | /** 377 | * Adds a validation to this construct. 378 | * 379 | * When `node.validate()` is called, the `validate()` method will be called on 380 | * all validations and all errors will be returned. 381 | * 382 | * @param validation The validation object 383 | */ 384 | public addValidation(validation: IValidation) { 385 | this._validations.push(validation); 386 | } 387 | 388 | /** 389 | * Validates this construct. 390 | * 391 | * Invokes the `validate()` method on all validations added through 392 | * `addValidation()`. 393 | * 394 | * @returns an array of validation error messages associated with this 395 | * construct. 396 | */ 397 | public validate(): string[] { 398 | return this._validations.flatMap(v => v.validate()); 399 | } 400 | 401 | /** 402 | * Locks this construct from allowing more children to be added. After this 403 | * call, no more children can be added to this construct or to any children. 404 | */ 405 | public lock() { 406 | this._locked = true; 407 | } 408 | 409 | /** 410 | * Adds a child construct to this node. 411 | * 412 | * @param child The child construct 413 | * @param childName The type name of the child construct. 414 | * @returns The resolved path part name of the child 415 | */ 416 | private addChild(child: Construct, childName: string) { 417 | if (this.locked) { 418 | 419 | // special error if root is locked 420 | if (!this.path) { 421 | throw new Error('Cannot add children during synthesis'); 422 | } 423 | 424 | throw new Error(`Cannot add children to "${this.path}" during synthesis`); 425 | } 426 | 427 | if (this._children[childName]) { 428 | const name = this.id ?? ''; 429 | const typeName = this.host.constructor.name; 430 | throw new Error(`There is already a Construct with name '${childName}' in ${typeName}${name.length > 0 ? ' [' + name + ']' : ''}`); 431 | } 432 | 433 | this._children[childName] = child; 434 | } 435 | } 436 | 437 | /** 438 | * Represents the building block of the construct graph. 439 | * 440 | * All constructs besides the root construct must be created within the scope of 441 | * another construct. 442 | */ 443 | export class Construct implements IConstruct { 444 | /** 445 | * Checks if `x` is a construct. 446 | * 447 | * Use this method instead of `instanceof` to properly detect `Construct` 448 | * instances, even when the construct library is symlinked. 449 | * 450 | * Explanation: in JavaScript, multiple copies of the `constructs` library on 451 | * disk are seen as independent, completely different libraries. As a 452 | * consequence, the class `Construct` in each copy of the `constructs` library 453 | * is seen as a different class, and an instance of one class will not test as 454 | * `instanceof` the other class. `npm install` will not create installations 455 | * like this, but users may manually symlink construct libraries together or 456 | * use a monorepo tool: in those cases, multiple copies of the `constructs` 457 | * library can be accidentally installed, and `instanceof` will behave 458 | * unpredictably. It is safest to avoid using `instanceof`, and using 459 | * this type-testing method instead. 460 | * 461 | * @returns true if `x` is an object created from a class which extends `Construct`. 462 | * @param x Any object 463 | */ 464 | public static isConstruct(x: any): x is Construct { 465 | return x && typeof x === 'object' && x[CONSTRUCT_SYM]; 466 | } 467 | 468 | /** 469 | * The tree node. 470 | */ 471 | public readonly node: Node; 472 | 473 | /** 474 | * Creates a new construct node. 475 | * 476 | * @param scope The scope in which to define this construct 477 | * @param id The scoped construct ID. Must be unique amongst siblings. If 478 | * the ID includes a path separator (`/`), then it will be replaced by double 479 | * dash `--`. 480 | */ 481 | constructor(scope: Construct, id: string) { 482 | this.node = new Node(this, scope, id); 483 | 484 | // implement IDependable privately 485 | Dependable.implement(this, { 486 | dependencyRoots: [this], 487 | }); 488 | } 489 | 490 | /** 491 | * Returns a string representation of this construct. 492 | */ 493 | public toString() { 494 | return this.node.path || ''; 495 | } 496 | } 497 | 498 | /** 499 | * Implement this interface in order for the construct to be able to validate itself. 500 | */ 501 | export interface IValidation { 502 | /** 503 | * Validate the current construct. 504 | * 505 | * This method can be implemented by derived constructs in order to perform 506 | * validation logic. It is called on all constructs before synthesis. 507 | * 508 | * @returns An array of validation error messages, or an empty array if there the construct is valid. 509 | */ 510 | validate(): string[]; 511 | } 512 | 513 | /** 514 | * In what order to return constructs 515 | */ 516 | export enum ConstructOrder { 517 | /** 518 | * Depth-first, pre-order 519 | */ 520 | PREORDER, 521 | 522 | /** 523 | * Depth-first, post-order (leaf nodes first) 524 | */ 525 | POSTORDER, 526 | } 527 | 528 | const PATH_SEP_REGEX = new RegExp(`${Node.PATH_SEP}`, 'g'); 529 | 530 | /** 531 | * Return a sanitized version of an arbitrary string, so it can be used as an ID 532 | */ 533 | function sanitizeId(id: string) { 534 | // Escape path seps as double dashes 535 | return id.replace(PATH_SEP_REGEX, '--'); 536 | } 537 | 538 | /** 539 | * Options for `construct.addMetadata()`. 540 | */ 541 | export interface MetadataOptions { 542 | /** 543 | * Include stack trace with metadata entry. 544 | * @default false 545 | */ 546 | readonly stackTrace?: boolean; 547 | 548 | /** 549 | * A JavaScript function to begin tracing from. 550 | * 551 | * This option is ignored unless `stackTrace` is `true`. 552 | * 553 | * @default addMetadata() 554 | */ 555 | readonly traceFromFunction?: any; 556 | } 557 | 558 | // Mark all instances of 'Construct' 559 | Object.defineProperty(Construct.prototype, CONSTRUCT_SYM, { 560 | value: true, 561 | enumerable: false, 562 | writable: false, 563 | }); 564 | 565 | /** 566 | * Creates a new root construct node. 567 | * 568 | * The root construct represents the top of the construct tree and is not contained within a parent scope itself. 569 | * For root constructs, the id is optional. 570 | */ 571 | export class RootConstruct extends Construct { 572 | /** 573 | * Creates a new root construct node. 574 | * 575 | * @param id The scoped construct ID. Must be unique amongst siblings. If 576 | * the ID includes a path separator (`/`), then it will be replaced by double 577 | * dash `--`. 578 | */ 579 | constructor(id?: string) { 580 | super(undefined as any, id ?? ''); 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /src/dependency.ts: -------------------------------------------------------------------------------- 1 | import { IConstruct } from './construct'; 2 | 3 | /** 4 | * Trait marker for classes that can be depended upon 5 | * 6 | * The presence of this interface indicates that an object has 7 | * an `IDependable` implementation. 8 | * 9 | * This interface can be used to take an (ordering) dependency on a set of 10 | * constructs. An ordering dependency implies that the resources represented by 11 | * those constructs are deployed before the resources depending ON them are 12 | * deployed. 13 | */ 14 | export interface IDependable { 15 | // Empty, this interface is a trait marker 16 | } 17 | 18 | /** 19 | * A set of constructs to be used as a dependable 20 | * 21 | * This class can be used when a set of constructs which are disjoint in the 22 | * construct tree needs to be combined to be used as a single dependable. 23 | */ 24 | export class DependencyGroup implements IDependable { 25 | private readonly _deps = new Array(); 26 | 27 | constructor(...deps: IDependable[]) { 28 | const self = this; 29 | 30 | Dependable.implement(this, { 31 | get dependencyRoots() { 32 | const result = new Array(); 33 | for (const d of self._deps) { 34 | result.push(...Dependable.of(d).dependencyRoots); 35 | } 36 | return result; 37 | }, 38 | }); 39 | 40 | this.add(...deps); 41 | } 42 | 43 | /** 44 | * Add a construct to the dependency roots 45 | */ 46 | public add(...scopes: IDependable[]) { 47 | this._deps.push(...scopes); 48 | } 49 | } 50 | 51 | const DEPENDABLE_SYMBOL = Symbol.for('@aws-cdk/core.DependableTrait'); 52 | 53 | /** 54 | * Trait for IDependable 55 | * 56 | * Traits are interfaces that are privately implemented by objects. Instead of 57 | * showing up in the public interface of a class, they need to be queried 58 | * explicitly. This is used to implement certain framework features that are 59 | * not intended to be used by Construct consumers, and so should be hidden 60 | * from accidental use. 61 | * 62 | * @example 63 | * 64 | * // Usage 65 | * const roots = Dependable.of(construct).dependencyRoots; 66 | * 67 | * // Definition 68 | * Dependable.implement(construct, { 69 | * dependencyRoots: [construct], 70 | * }); 71 | */ 72 | export abstract class Dependable { 73 | /** 74 | * Turn any object into an IDependable. 75 | */ 76 | public static implement(instance: IDependable, trait: Dependable) { 77 | // I would also like to reference classes (to cut down on the list of objects 78 | // we need to manage), but we can't do that either since jsii doesn't have the 79 | // concept of a class reference. 80 | (instance as any)[DEPENDABLE_SYMBOL] = trait; 81 | } 82 | 83 | /** 84 | * Return the matching Dependable for the given class instance. 85 | */ 86 | public static of(instance: IDependable): Dependable { 87 | const ret = (instance as any)[DEPENDABLE_SYMBOL]; 88 | if (!ret) { 89 | throw new Error(`${instance} does not implement IDependable. Use "Dependable.implement()" to implement`); 90 | } 91 | return ret; 92 | } 93 | 94 | /** 95 | * Return the matching Dependable for the given class instance. 96 | * @deprecated use `of` 97 | */ 98 | public static get(instance: IDependable): Dependable { 99 | return this.of(instance); 100 | } 101 | 102 | /** 103 | * The set of constructs that form the root of this dependable 104 | * 105 | * All resources under all returned constructs are included in the ordering 106 | * dependency. 107 | */ 108 | public abstract readonly dependencyRoots: IConstruct[]; 109 | } 110 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './construct'; 2 | export * from './metadata'; 3 | export * from './dependency'; -------------------------------------------------------------------------------- /src/metadata.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An entry in the construct metadata table. 3 | */ 4 | export interface MetadataEntry { 5 | /** 6 | * The metadata entry type. 7 | */ 8 | readonly type: string; 9 | 10 | /** 11 | * The data. 12 | */ 13 | readonly data: any; 14 | 15 | /** 16 | * Stack trace at the point of adding the metadata. 17 | * 18 | * Only available if `addMetadata()` is called with `stackTrace: true`. 19 | * 20 | * @default - no trace information 21 | */ 22 | readonly trace?: string[]; 23 | } 24 | -------------------------------------------------------------------------------- /src/private/stack-trace.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:ban-types 2 | export function captureStackTrace(below?: Function): string[] { 3 | below = below || captureStackTrace; // hide myself if nothing else 4 | const object = { stack: '' }; 5 | const previousLimit = Error.stackTraceLimit; 6 | try { 7 | Error.stackTraceLimit = Number.MAX_SAFE_INTEGER; 8 | Error.captureStackTrace(object, below); 9 | } finally { 10 | Error.stackTraceLimit = previousLimit; 11 | } 12 | if (!object.stack) { 13 | return []; 14 | } 15 | return object.stack.split('\n').slice(1).map(s => s.replace(/^\s*at\s+/, '')); 16 | } 17 | -------------------------------------------------------------------------------- /src/private/uniqueid.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto'; 2 | 3 | /** 4 | * Resources with this ID are complete hidden from the logical ID calculation. 5 | */ 6 | const HIDDEN_ID = 'Default'; 7 | 8 | /** 9 | * Calculates the construct uid based on path components. 10 | * 11 | * Components named `Default` (case sensitive) are excluded from uid calculation 12 | * to allow tree refactorings. 13 | * 14 | * @param components path components 15 | */ 16 | export function addressOf(components: string[]) { 17 | const hash = crypto.createHash('sha1'); 18 | for (const c of components) { 19 | // skip components called "Default" to enable refactorings 20 | if (c === HIDDEN_ID) { continue; } 21 | 22 | hash.update(c); 23 | hash.update('\n'); 24 | } 25 | 26 | // prefix with "c8" so to ensure it starts with non-digit. 27 | return 'c8' + hash.digest('hex'); 28 | } 29 | -------------------------------------------------------------------------------- /test/construct.test.ts: -------------------------------------------------------------------------------- 1 | import { App as Root } from './util'; 2 | import { Construct, ConstructOrder, DependencyGroup, Dependable, IConstruct } from '../src'; 3 | 4 | // tslint:disable:variable-name 5 | // tslint:disable:max-line-length 6 | 7 | test('the "Root" construct is a special construct which can be used as the root of the tree', () => { 8 | const root = new Root(); 9 | const node = root.node; 10 | expect(node.id).toBe(''); 11 | expect(node.scope).toBeUndefined(); 12 | expect(node.children.length).toBe(0); 13 | }); 14 | 15 | test('an empty string is a valid name for the root construct', () => { 16 | const root = new Root(); 17 | expect(root.node.id).toEqual(''); 18 | 19 | expect(() => new Construct(root, '')).toThrow(/Only root constructs/); 20 | }); 21 | 22 | test('construct.name returns the name of the construct', () => { 23 | const t = createTree(); 24 | 25 | expect(t.child1.node.id).toBe('Child1'); 26 | expect(t.child2.node.id).toBe('Child2'); 27 | expect(t.child1_1.node.id).toBe('Child11'); 28 | expect(t.child1_2.node.id).toBe('Child12'); 29 | expect(t.child1_1_1.node.id).toBe('Child111'); 30 | expect(t.child2_1.node.id).toBe('Child21'); 31 | 32 | }); 33 | 34 | test('construct id can use any character except the path separator', () => { 35 | const root = new Root(); 36 | expect(() => new Construct(root, 'valid')).not.toThrow(); 37 | expect(() => new Construct(root, 'ValiD')).not.toThrow(); 38 | expect(() => new Construct(root, 'Va123lid')).not.toThrow(); 39 | expect(() => new Construct(root, 'v')).not.toThrow(); 40 | expect(() => new Construct(root, ' invalid' )).not.toThrow(); 41 | expect(() => new Construct(root, 'invalid ' )).not.toThrow(); 42 | expect(() => new Construct(root, '123invalid' )).not.toThrow(); 43 | expect(() => new Construct(root, 'in valid' )).not.toThrow(); 44 | expect(() => new Construct(root, 'in_Valid' )).not.toThrow(); 45 | expect(() => new Construct(root, 'in-Valid' )).not.toThrow(); 46 | expect(() => new Construct(root, 'in\\Valid' )).not.toThrow(); 47 | expect(() => new Construct(root, 'in.Valid' )).not.toThrow(); 48 | }); 49 | 50 | test('if construct id contains path seperators, they will be replaced by double-dash', () => { 51 | const root = new Root(); 52 | const c = new Construct(root, 'Boom/Boom/Bam'); 53 | expect(c.node.id).toBe('Boom--Boom--Bam'); 54 | }); 55 | 56 | test('if "undefined" is forcefully used as an "id", it will be treated as an empty string', () => { 57 | const c = new Construct(undefined as any, undefined as any); 58 | expect(c.node.id).toBe(''); 59 | }); 60 | 61 | test('node.addr returns an opaque app-unique address for any construct', () => { 62 | const root = new Root(); 63 | 64 | const child1 = new Construct(root, 'This is the first child'); 65 | const child2 = new Construct(child1, 'Second level'); 66 | const c1 = new Construct(child2, 'My construct'); 67 | const c2 = new Construct(child1, 'My construct'); 68 | 69 | expect(c1.node.path).toBe('This is the first child/Second level/My construct'); 70 | expect(c2.node.path).toBe('This is the first child/My construct'); 71 | expect(child1.node.addr).toBe('c8a0dfcbdc45cb728d75ebe6914d369e565dc3f61c'); 72 | expect(child2.node.addr).toBe('c825c5541e02ebd68e79ea636e370985b6c2de40a9'); 73 | expect(c1.node.addr).toBe('c83a2846e506bcc5f10682b564084bca2d275709ee'); 74 | expect(c2.node.addr).toBe('c8003bcb3e82977712d0d7220b155cb69abd9ad383'); 75 | }); 76 | 77 | test('node.addr excludes "default" from the address calculation', () => { 78 | // GIVEN 79 | const root = new Root(); 80 | const c1 = new Construct(root, 'c1'); 81 | 82 | // WHEN: 83 | const group1 = new Construct(root, 'Default'); // <-- this is a "hidden node" 84 | const c1a = new Construct(group1, 'c1'); 85 | const group2 = new Construct(root, 'DeFAULt'); // <-- not hidden, "Default" is case sensitive 86 | const c1b = new Construct(group2, 'c1'); 87 | 88 | // THEN: all addresses are the same because they go through "default" 89 | const addr = c1.node.addr; 90 | const addrA = c1a.node.addr; 91 | const addrB = c1b.node.addr; 92 | 93 | expect(addr).toEqual('c86a34031367d11f4bef80afca42b7e7e5c6253b77'); 94 | expect(addrA).toEqual(addr); 95 | expect(addrB).toEqual('c8fa72abd28f794f6bacb100b26beb761d004572f5'); 96 | expect(addrB).not.toEqual(addr); 97 | }); 98 | 99 | test('construct.getChildren() returns an array of all children', () => { 100 | const root = new Root(); 101 | const child = new Construct(root, 'Child1'); 102 | new Construct(root, 'Child2'); 103 | expect(child.node.children.length).toBe(0); 104 | expect(root.node.children.length).toBe(2); 105 | }); 106 | 107 | test('construct.findChild(name) can be used to retrieve a child from a parent', () => { 108 | const root = new Root(); 109 | const child = new Construct(root, 'Contruct'); 110 | expect(root.node.tryFindChild(child.node.id)).toBe(child); 111 | expect(root.node.tryFindChild('NotFound')).toBeUndefined(); 112 | }); 113 | 114 | test('construct.getChild(name) can be used to retrieve a child from a parent', () => { 115 | const root = new Root(); 116 | const child = new Construct(root, 'Contruct'); 117 | expect(root.node.findChild(child.node.id)).toBe(child); 118 | expect(() => root.node.findChild('NotFound')).toThrow(/No child with id: 'NotFound'/); 119 | }); 120 | 121 | test('construct.getContext(key) can be used to read a value from context', () => { 122 | // GIVEN 123 | const context = { 124 | ctx1: 12, 125 | ctx2: 'hello', 126 | }; 127 | 128 | // WHEN 129 | const t = createTree(context); 130 | 131 | // THEN 132 | expect(t.child1_2.node.getContext('ctx1')).toBe(12); 133 | expect(t.child1_1_1.node.getContext('ctx2')).toBe('hello'); 134 | }); 135 | 136 | test('construct.getContext(key) throws if context is not defined', () => { 137 | // GIVEN 138 | const context = { 139 | ctx1: 12, 140 | }; 141 | 142 | // WHEN 143 | const t = createTree(context); 144 | const key = 'ctx2'; 145 | 146 | // THEN 147 | expect(t.child1_2.node.getContext('ctx1')).toBe(12); 148 | expect(() => { 149 | t.child1_1_1.node.getContext(key); 150 | }).toThrowError(`No context value present for ${key} key`); 151 | }); 152 | 153 | test('construct.getAllContext can be used to read the full context of a root node', () => { 154 | // GIVEN 155 | const context = { 156 | ctx1: 12, 157 | ctx2: 'hello', 158 | }; 159 | 160 | // WHEN 161 | const t = new Root(); 162 | for (const [k, v] of Object.entries(context)) { 163 | t.node.setContext(k, v); 164 | } 165 | 166 | // THEN 167 | expect(t.node.getAllContext()).toStrictEqual(context); 168 | }); 169 | 170 | test('construct.getAllContext can be used to read the full context of a node', () => { 171 | // GIVEN 172 | const context = { 173 | ctx1: 12, 174 | ctx2: 'hello', 175 | }; 176 | 177 | // WHEN 178 | const t = createTree(context); 179 | t.child1_1_1.node.setContext('ctx1', 13); 180 | 181 | // THEN 182 | expect(t.child1_2.node.getAllContext()).toStrictEqual(context); 183 | expect(t.child1_1_1.node.getAllContext()).toStrictEqual({ ctx1: 13, ctx2: 'hello' }); 184 | }); 185 | 186 | test('construct.tryGetContext(key) can be used to read a value from context defined at the root level', () => { 187 | const context = { 188 | ctx1: 12, 189 | ctx2: 'hello', 190 | }; 191 | 192 | const t = createTree(context); 193 | expect(t.child1_2.node.tryGetContext('ctx1')).toBe(12); 194 | expect(t.child1_1_1.node.tryGetContext('ctx2')).toBe('hello'); 195 | }); 196 | 197 | // tslint:disable-next-line:max-line-length 198 | test('construct.setContext(k,v) sets context at some level and construct.tryGetContext(key) will return the lowermost value defined in the stack', () => { 199 | const root = new Root(); 200 | const highChild = new Construct(root, 'highChild'); 201 | highChild.node.setContext('c1', 'root'); 202 | highChild.node.setContext('c2', 'root'); 203 | 204 | const child1 = new Construct(highChild, 'child1'); 205 | child1.node.setContext('c2', 'child1'); 206 | child1.node.setContext('c3', 'child1'); 207 | 208 | const child2 = new Construct(highChild, 'child2'); 209 | const child3 = new Construct(child1, 'child1child1'); 210 | child3.node.setContext('c1', 'child3'); 211 | child3.node.setContext('c4', 'child3'); 212 | 213 | expect(highChild.node.tryGetContext('c1')).toBe('root'); 214 | expect(highChild.node.tryGetContext('c2')).toBe('root'); 215 | expect(highChild.node.tryGetContext('c3')).toBeUndefined(); 216 | 217 | expect(child1.node.tryGetContext('c1')).toBe('root'); 218 | expect(child1.node.tryGetContext('c2')).toBe('child1'); 219 | expect(child1.node.tryGetContext('c3')).toBe('child1'); 220 | 221 | expect(child2.node.tryGetContext('c1')).toBe('root'); 222 | expect(child2.node.tryGetContext('c2')).toBe('root'); 223 | expect(child2.node.tryGetContext('c3')).toBeUndefined(); 224 | 225 | expect(child3.node.tryGetContext('c1')).toBe('child3'); 226 | expect(child3.node.tryGetContext('c2')).toBe('child1'); 227 | expect(child3.node.tryGetContext('c3')).toBe('child1'); 228 | expect(child3.node.tryGetContext('c4')).toBe('child3'); 229 | }); 230 | 231 | test('construct.setContext(key, value) can only be called before adding any children', () => { 232 | const root = new Root(); 233 | new Construct(root, 'child1'); 234 | expect(() => root.node.setContext('k', 'v')).toThrow(/Cannot set context after children have been added: child1/); 235 | }); 236 | 237 | test('construct.pathParts returns an array of strings of all names from root to node', () => { 238 | const tree = createTree(); 239 | expect(tree.root.node.path).toBe(''); 240 | expect(tree.child1_1_1.node.path).toBe('HighChild/Child1/Child11/Child111'); 241 | expect(tree.child2.node.path).toBe('HighChild/Child2'); 242 | }); 243 | 244 | test('if a root construct has a name, it should be included in the path', () => { 245 | const tree = createTree({}); 246 | expect(tree.root.node.path).toBe(''); 247 | expect(tree.child1_1_1.node.path).toBe('HighChild/Child1/Child11/Child111'); 248 | }); 249 | 250 | test('construct can not be created with the name of a sibling', () => { 251 | const root = new Root(); 252 | 253 | // WHEN 254 | new Construct(root, 'SameName'); 255 | 256 | // THEN: They have different paths 257 | expect(() => new Construct(root, 'SameName')).toThrow(/There is already a Construct with name 'SameName' in App/); 258 | 259 | // WHEN 260 | const c0 = new Construct(root, 'c0'); 261 | new Construct(c0, 'SameName'); 262 | 263 | // THEN: They have different paths 264 | expect(() => new Construct(c0, 'SameName')).toThrow(/There is already a Construct with name 'SameName' in Construct \[c0\]/); 265 | }); 266 | 267 | test('addMetadata(type, data) can be used to attach metadata to constructs', () => { 268 | const root = new Root(); 269 | const con = new Construct(root, 'MyConstruct'); 270 | expect(con.node.metadata).toEqual([]); 271 | 272 | const node = con.node; 273 | (function FIND_ME() { // <-- Creates a stack trace marker we'll be able to look for 274 | node.addMetadata('key', 'value', { stackTrace: true }); 275 | node.addMetadata('number', 103); 276 | node.addMetadata('array', [123, 456]); 277 | })(); 278 | 279 | expect(node.metadata[0].type).toBe('key'); 280 | expect(node.metadata[0].data).toBe('value'); 281 | expect(node.metadata[1].data).toBe(103); 282 | expect(node.metadata[2].data).toEqual([123, 456]); 283 | 284 | expect(node.metadata[0].trace?.[0]).toContain('FIND_ME'); 285 | }); 286 | 287 | test('addMetadata() respects the "stackTrace" option', () => { 288 | const root = new Root(); 289 | const con = new Construct(root, 'Foo'); 290 | 291 | con.node.addMetadata('foo', 'bar1', { stackTrace: true }); 292 | con.node.addMetadata('foo', 'bar2', { stackTrace: false }); 293 | 294 | expect(con.node.metadata.length).toBe(2); 295 | expect(con.node.metadata[0]?.trace?.length).toBeGreaterThan(0); 296 | expect(con.node.metadata[1]?.trace).toBeUndefined(); 297 | }); 298 | 299 | test('addMetadata(type, undefined/null) is ignored', () => { 300 | const root = new Root(); 301 | const con = new Construct(root, 'Foo'); 302 | const node = con.node; 303 | node.addMetadata('Null', null); 304 | node.addMetadata('Undefined', undefined); 305 | node.addMetadata('True', true); 306 | node.addMetadata('False', false); 307 | node.addMetadata('Empty', ''); 308 | 309 | const exists = (key: string) => node.metadata.find(x => x.type === key); 310 | 311 | expect(exists('Null')).toBeFalsy(); 312 | expect(exists('Undefined')).toBeFalsy(); 313 | expect(exists('True')).toBeTruthy(); 314 | expect(exists('False')).toBeTruthy(); 315 | expect(exists('Empty')).toBeTruthy(); 316 | }); 317 | 318 | test('multiple children of the same type, with explicit names are welcome', () => { 319 | const root = new Root(); 320 | new MyBeautifulConstruct(root, 'mbc1'); 321 | new MyBeautifulConstruct(root, 'mbc2'); 322 | new MyBeautifulConstruct(root, 'mbc3'); 323 | new MyBeautifulConstruct(root, 'mbc4'); 324 | expect(root.node.children.length).toBeGreaterThanOrEqual(4); 325 | }); 326 | 327 | // tslint:disable-next-line:max-line-length 328 | test('node.addValidation() can be implemented to perform validation, node.validate() will return errors', () => { 329 | 330 | class MyConstruct extends Construct { 331 | constructor(scope: Construct, id: string) { 332 | super(scope, id); 333 | 334 | this.node.addValidation({ validate: () => ['my-error1', 'my-error2'] }); 335 | } 336 | } 337 | 338 | class YourConstruct extends Construct { 339 | constructor(scope: Construct, id: string) { 340 | super(scope, id); 341 | 342 | this.node.addValidation({ validate: () => ['your-error1'] }); 343 | } 344 | } 345 | 346 | class TheirConstruct extends Construct { 347 | constructor(scope: Construct, id: string) { 348 | super(scope, id); 349 | 350 | new YourConstruct(this, 'YourConstruct'); 351 | 352 | this.node.addValidation({ validate: () => ['their-error'] }); 353 | } 354 | } 355 | 356 | class TestStack extends Root { 357 | constructor() { 358 | super(); 359 | 360 | new MyConstruct(this, 'MyConstruct'); 361 | new TheirConstruct(this, 'TheirConstruct'); 362 | 363 | this.node.addValidation({ validate: () => ['stack-error'] }); 364 | } 365 | } 366 | 367 | const stack = new TestStack(); 368 | 369 | const validateTree = (root: Construct) => { 370 | const errors: ValidationError[] = []; 371 | for (const child of root.node.children) { 372 | errors.push(...validateTree(child)); 373 | } 374 | 375 | errors.push(...root.node.validate().map(message => ({ source: root, message }))); 376 | return errors; 377 | }; 378 | 379 | const errors = validateTree(stack) 380 | .map((v: ValidationError) => ({ path: v.source.node.path, message: v.message })); 381 | 382 | // validate DFS 383 | expect(errors).toEqual([ 384 | { path: 'MyConstruct', message: 'my-error1' }, 385 | { path: 'MyConstruct', message: 'my-error2' }, 386 | { path: 'TheirConstruct/YourConstruct', message: 'your-error1' }, 387 | { path: 'TheirConstruct', message: 'their-error' }, 388 | { path: '', message: 'stack-error' }, 389 | ]); 390 | 391 | }); 392 | 393 | test('node.validate() returns an empty array if the construct does not implement IValidation', () => { 394 | // GIVEN 395 | const root = new Root(); 396 | 397 | // THEN 398 | expect(root.node.validate()).toStrictEqual([]); 399 | }); 400 | 401 | test('node.addValidation() can be used to add a validation function to a construct', () => { 402 | // GIVEN 403 | const construct = new Root(); 404 | construct.node.addValidation({ validate: () => ['error1', 'error2'] }); 405 | construct.node.addValidation({ validate: () => ['error3'] }); 406 | 407 | expect(construct.node.validate()).toStrictEqual(['error1', 'error2', 'error3']); 408 | }); 409 | 410 | test('construct.lock() protects against adding children anywhere under this construct (direct or indirect)', () => { 411 | 412 | const root = new Root(); 413 | 414 | const c0a = new Construct(root, 'c0a'); 415 | const c0b = new Construct(root, 'c0b'); 416 | 417 | const c1a = new Construct(c0a, 'c1a'); 418 | const c1b = new Construct(c0a, 'c1b'); 419 | 420 | c0a.node.lock(); 421 | 422 | // now we should still be able to add children to c0b, but not to c0a or any its children 423 | new Construct(c0b, 'c1a'); 424 | expect(() => new Construct(c0a, 'fail1')).toThrow(/Cannot add children to "c0a" during synthesis/); 425 | expect(() => new Construct(c1a, 'fail2')).toThrow(/Cannot add children to "c0a\/c1a" during synthesis/); 426 | expect(() => new Construct(c1b, 'fail3')).toThrow(/Cannot add children to "c0a\/c1b" during synthesis/); 427 | 428 | new Construct(root, 'c2'); 429 | 430 | // lock root 431 | root.node.lock(); 432 | expect(() => new Construct(root, 'test')).toThrow(/Cannot add children during synthesis/); 433 | }); 434 | 435 | test('findAll returns a list of all children in either DFS or BFS', () => { 436 | // GIVEN 437 | const c1 = new Construct(undefined as any, '1'); 438 | const c2 = new Construct(c1, '2'); 439 | new Construct(c1, '3'); 440 | new Construct(c2, '4'); 441 | new Construct(c2, '5'); 442 | 443 | // THEN 444 | const node = c1.node; 445 | expect(node.findAll().map(x => x.node.id)).toEqual(c1.node.findAll(ConstructOrder.PREORDER).map(x => x.node.id)); // default is PreOrder 446 | expect(node.findAll(ConstructOrder.PREORDER).map(x => x.node.id)).toEqual(['1', '2', '4', '5', '3']); 447 | expect(node.findAll(ConstructOrder.POSTORDER).map(x => x.node.id)).toEqual(['4', '5', '2', '3', '1']); 448 | }); 449 | 450 | test('ancestors returns a list of parents up to root', () => { 451 | const { child1_1_1 } = createTree(); 452 | expect(child1_1_1.node.scopes.map(x => x.node.id)).toEqual(['', 'HighChild', 'Child1', 'Child11', 'Child111']); 453 | }); 454 | 455 | test('"root" returns the root construct', () => { 456 | const { child1, child2, child1_1_1, root } = createTree(); 457 | expect(child1.node.root).toBe(root); 458 | expect(child2.node.root).toBe(root); 459 | expect(child1_1_1.node.root).toBe(root); 460 | }); 461 | 462 | describe('defaultChild', () => { 463 | test('returns the child with id "Resource"', () => { 464 | const root = new Root(); 465 | new Construct(root, 'child1'); 466 | const defaultChild = new Construct(root, 'Resource'); 467 | new Construct(root, 'child2'); 468 | 469 | expect(root.node.defaultChild).toBe(defaultChild); 470 | }); 471 | test('returns the child with id "Default"', () => { 472 | const root = new Root(); 473 | new Construct(root, 'child1'); 474 | const defaultChild = new Construct(root, 'Default'); 475 | new Construct(root, 'child2'); 476 | 477 | expect(root.node.defaultChild).toBe(defaultChild); 478 | }); 479 | test('can override defaultChild', () => { 480 | const root = new Root(); 481 | new Construct(root, 'Resource'); 482 | const defaultChild = new Construct(root, 'OtherResource'); 483 | root.node.defaultChild = defaultChild; 484 | 485 | expect(root.node.defaultChild).toBe(defaultChild); 486 | }); 487 | test('returns "undefined" if there is no default', () => { 488 | const root = new Root(); 489 | new Construct(root, 'child1'); 490 | new Construct(root, 'child2'); 491 | 492 | expect(root.node.defaultChild).toBeUndefined(); 493 | }); 494 | test('fails if there are both "Resource" and "Default"', () => { 495 | const root = new Root(); 496 | new Construct(root, 'child1'); 497 | new Construct(root, 'Default'); 498 | new Construct(root, 'child2'); 499 | new Construct(root, 'Resource'); 500 | 501 | expect(() => root.node.defaultChild) 502 | .toThrow(/Cannot determine default child for . There is both a child with id "Resource" and id "Default"/); 503 | }); 504 | }); 505 | 506 | describe('dependencies', () => { 507 | 508 | test('addDependency() defines a dependency between two scopes', () => { 509 | // GIVEN 510 | const root = new Root(); 511 | const consumer = new Construct(root, 'consumer'); 512 | const producer1 = new Construct(root, 'producer1'); 513 | const producer2 = new Construct(root, 'producer2'); 514 | 515 | // WHEN 516 | consumer.node.addDependency(producer1); 517 | consumer.node.addDependency(producer2); 518 | 519 | // THEN 520 | expect(consumer.node.dependencies.map(x => x.node.path)).toStrictEqual(['producer1', 'producer2']); 521 | }); 522 | 523 | test('are deduplicated', () => { 524 | // GIVEN 525 | const root = new Root(); 526 | const consumer = new Construct(root, 'consumer'); 527 | const producer = new Construct(root, 'producer'); 528 | 529 | 530 | // WHEN 531 | consumer.node.addDependency(producer); 532 | consumer.node.addDependency(producer); 533 | consumer.node.addDependency(producer); 534 | consumer.node.addDependency(producer); 535 | 536 | // THEN 537 | expect(consumer.node.dependencies.map(x => x.node.path)).toStrictEqual(['producer']); 538 | }); 539 | 540 | test('DependencyGroup can represent a group of disjoined producers', () => { 541 | // GIVEN 542 | const root = new Root(); 543 | const group = new DependencyGroup(new Construct(root, 'producer1'), new Construct(root, 'producer2')); 544 | const consumer = new Construct(root, 'consumer'); 545 | 546 | // WHEN 547 | group.add(new Construct(root, 'producer3'), new Construct(root, 'producer4')); 548 | consumer.node.addDependency(group); 549 | 550 | // THEN 551 | expect(consumer.node.dependencies.map(x => x.node.path)).toStrictEqual(['producer1', 'producer2', 'producer3', 'producer4']); 552 | }); 553 | 554 | test('Dependable.implement() can be used to implement IDependable on any object', () => { 555 | // GIVEN 556 | const root = new Root(); 557 | const producer = new Construct(root, 'producer'); 558 | const consumer = new Construct(root, 'consumer'); 559 | 560 | // WHEN 561 | const foo = { }; 562 | Dependable.implement(foo, { 563 | get dependencyRoots() { return [producer]; }, 564 | }); 565 | consumer.node.addDependency(foo); 566 | 567 | // THEN 568 | expect(Dependable.of(foo).dependencyRoots.map(x => x.node.path)).toStrictEqual(['producer']); 569 | expect(consumer.node.dependencies.map(x => x.node.path)).toStrictEqual(['producer']); 570 | }); 571 | 572 | test('Dependable.of() throws an error the object does not implement IDependable', () => { 573 | expect(() => Dependable.of({})).toThrow(/does not implement IDependable/); 574 | }); 575 | 576 | test('dependencyRoots are only resolved when node dependencies are evaluated', () => { 577 | // GIVEN 578 | const root = new Root(); 579 | const c1 = new Construct(root, 'c1'); 580 | const c2 = new Construct(root, 'c2'); 581 | const c3 = new Construct(root, 'c3'); 582 | const group = new DependencyGroup(); 583 | group.add(c2); 584 | c1.node.addDependency(group); 585 | 586 | // WHEN 587 | // add s3 after "addDependency" is called 588 | group.add(c3); 589 | 590 | // THEN 591 | expect(c1.node.dependencies.length).toBe(2); 592 | expect(c1.node.dependencies.map(x => x.node.path)).toStrictEqual(['c2', 'c3']); 593 | }); 594 | 595 | test('DependencyGroup can also include other IDependables', () => { 596 | // GIVEN 597 | const root = new Root(); 598 | const c1 = new Construct(root, 'c1'); 599 | 600 | // WHEN 601 | const groupA = new DependencyGroup(new Construct(root, 'a1'), new Construct(root, 'a2')); 602 | const groupB = new DependencyGroup(new Construct(root, 'b1'), new Construct(root, 'b2')); 603 | const composite = new DependencyGroup(groupA); 604 | 605 | c1.node.addDependency(composite); 606 | composite.add(groupB); 607 | groupB.add(new Construct(root, 'b3')); 608 | 609 | // THEN 610 | expect(c1.node.dependencies.map(x => x.node.path)).toStrictEqual(['a1', 'a2', 'b1', 'b2', 'b3']); 611 | expect(c1.node.dependencies.length).toBe(5); 612 | }); 613 | }); 614 | 615 | test('tryRemoveChild()', () => { 616 | // GIVEN 617 | const root = new Root(); 618 | new Construct(root, 'child1'); 619 | new Construct(root, 'child2'); 620 | 621 | // WHEN 622 | expect(root.node.children.length).toBe(2); 623 | expect(root.node.tryRemoveChild('child1')).toBeTruthy(); 624 | expect(root.node.tryRemoveChild('child-not-found')).toBeFalsy(); 625 | 626 | // THEN 627 | expect(root.node.children.length).toBe(1); 628 | }); 629 | 630 | test('toString()', () => { 631 | // GIVEN 632 | const root = new Root(); 633 | const child = new Construct(root, 'child'); 634 | const grand = new Construct(child, 'grand'); 635 | 636 | // THEN 637 | expect(root.toString()).toStrictEqual(''); 638 | expect(child.toString()).toStrictEqual('child'); 639 | expect(grand.toString()).toStrictEqual('child/grand'); 640 | }); 641 | 642 | test('Construct.isConstruct returns true for constructs', () => { 643 | // GIVEN 644 | const root = new Root(); 645 | class Subclass extends Construct {}; 646 | const subclass = new Subclass(root, 'subclass'); 647 | const someRandomObject = {}; 648 | 649 | // THEN 650 | expect(Construct.isConstruct(root)).toBeTruthy(); 651 | expect(Construct.isConstruct(subclass)).toBeTruthy(); 652 | expect(Construct.isConstruct(undefined)).toBeFalsy(); 653 | expect(Construct.isConstruct(null)).toBeFalsy(); 654 | expect(Construct.isConstruct('string')).toBeFalsy(); 655 | expect(Construct.isConstruct(1234)).toBeFalsy(); 656 | expect(Construct.isConstruct(true)).toBeFalsy(); 657 | expect(Construct.isConstruct([1, 2, 3])).toBeFalsy(); 658 | expect(Construct.isConstruct(someRandomObject)).toBeFalsy(); 659 | }); 660 | 661 | function createTree(context?: any) { 662 | const root = new Root(); 663 | const highChild = new Construct(root, 'HighChild'); 664 | if (context) { 665 | Object.keys(context).forEach(key => highChild.node.setContext(key, context[key])); 666 | } 667 | 668 | const child1 = new Construct(highChild, 'Child1'); 669 | const child2 = new Construct(highChild, 'Child2'); 670 | const child1_1 = new Construct(child1, 'Child11'); 671 | const child1_2 = new Construct(child1, 'Child12'); 672 | const child1_1_1 = new Construct(child1_1, 'Child111'); 673 | const child2_1 = new Construct(child2, 'Child21'); 674 | 675 | return { 676 | root, child1, child2, child1_1, child1_2, child1_1_1, child2_1, 677 | }; 678 | } 679 | 680 | class MyBeautifulConstruct extends Construct { 681 | constructor(scope: Construct, id: string) { 682 | super(scope, id); 683 | } 684 | } 685 | 686 | interface ValidationError { 687 | readonly source: IConstruct; 688 | readonly message: string; 689 | } 690 | -------------------------------------------------------------------------------- /test/util.ts: -------------------------------------------------------------------------------- 1 | import { RootConstruct } from '../src'; 2 | 3 | export class App extends RootConstruct { 4 | constructor() { 5 | super(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "10.0.0" 3 | } 4 | --------------------------------------------------------------------------------