cdk-cost-analyzer 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cdk-cost-analyzer-cache/metadata.json +12 -0
- package/.gitlab-ci.yml +214 -0
- package/.husky/pre-commit +12 -0
- package/.kiro/hooks/accessibility-audit.kiro.hook +18 -0
- package/.kiro/hooks/api-schema-validation.kiro.hook +21 -0
- package/.kiro/hooks/auto-test-on-save.kiro.hook +19 -0
- package/.kiro/hooks/cdk-synth-on-change.kiro.hook +20 -0
- package/.kiro/hooks/code-coverage-check.kiro.hook +14 -0
- package/.kiro/hooks/commit-message-helper.kiro.hook +14 -0
- package/.kiro/hooks/dependency-update-check.kiro.hook +14 -0
- package/.kiro/hooks/env-file-validation.kiro.hook +18 -0
- package/.kiro/hooks/lint-and-format-on-save.kiro.hook +21 -0
- package/.kiro/hooks/mcp-config-validation.kiro.hook +17 -0
- package/.kiro/hooks/mcp-server-test.kiro.hook +14 -0
- package/.kiro/hooks/performance-analysis.kiro.hook +14 -0
- package/.kiro/hooks/readme-spell-check.kiro.hook +14 -0
- package/.kiro/hooks/security-scan-on-dependency-change.kiro.hook +21 -0
- package/.kiro/hooks/translation-update.kiro.hook +18 -0
- package/.kiro/hooks/update-documentation.kiro.hook +18 -0
- package/.kiro/settings/mcp.json +20 -0
- package/.kiro/specs/cdk-cost-analyzer/design.md +620 -0
- package/.kiro/specs/cdk-cost-analyzer/requirements.md +183 -0
- package/.kiro/specs/cdk-cost-analyzer/tasks.md +357 -0
- package/.kiro/specs/github-actions-ci/design.md +281 -0
- package/.kiro/specs/github-actions-ci/requirements.md +86 -0
- package/.kiro/specs/github-actions-ci/tasks.md +115 -0
- package/.kiro/specs/nlb-calculator-test-coverage/design.md +190 -0
- package/.kiro/specs/nlb-calculator-test-coverage/requirements.md +84 -0
- package/.kiro/specs/nlb-calculator-test-coverage/tasks.md +150 -0
- package/.kiro/specs/production-readiness/design.md +1213 -0
- package/.kiro/specs/production-readiness/requirements.md +312 -0
- package/.kiro/specs/production-readiness/tasks.md +269 -0
- package/.kiro/specs/repository-cleanup/design.md +283 -0
- package/.kiro/specs/repository-cleanup/requirements.md +74 -0
- package/.kiro/specs/repository-cleanup/tasks.md +64 -0
- package/.kiro/steering/aws-cli-best-practices.md +41 -0
- package/.kiro/steering/cdk-best-practices.md +49 -0
- package/.kiro/steering/development-standards.md +54 -0
- package/.kiro/steering/docker-best-practices.md +34 -0
- package/.kiro/steering/documentation-style.md +151 -0
- package/.kiro/steering/git-best-practices.md +37 -0
- package/.kiro/steering/mcp-best-practices.md +95 -0
- package/.kiro/steering/python-best-practices.md +48 -0
- package/.kiro/steering/react-best-practices.md +44 -0
- package/.kiro/steering/security-best-practices.md +41 -0
- package/.kiro/steering/testing-best-practices.md +59 -0
- package/.kiro/steering/typescript-best-practices.md +40 -0
- package/CHANGELOG.md +49 -0
- package/CONTRIBUTING.md +258 -0
- package/LICENSE +19 -0
- package/README.md +480 -0
- package/SECURITY.md +117 -0
- package/dist/api/index.d.ts +11 -0
- package/dist/api/index.js +65 -0
- package/dist/api/types.d.ts +15 -0
- package/dist/api/types.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +262 -0
- package/dist/config/ConfigManager.d.ts +40 -0
- package/dist/config/ConfigManager.js +238 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +19 -0
- package/dist/config/types.d.ts +72 -0
- package/dist/config/types.js +15 -0
- package/dist/diff/DiffEngine.d.ts +7 -0
- package/dist/diff/DiffEngine.js +73 -0
- package/dist/diff/index.d.ts +2 -0
- package/dist/diff/index.js +21 -0
- package/dist/diff/types.d.ts +20 -0
- package/dist/diff/types.js +3 -0
- package/dist/integrations/GitLabIntegration.d.ts +7 -0
- package/dist/integrations/GitLabIntegration.js +45 -0
- package/dist/integrations/index.d.ts +2 -0
- package/dist/integrations/index.js +21 -0
- package/dist/integrations/types.d.ts +11 -0
- package/dist/integrations/types.js +13 -0
- package/dist/parser/TemplateParser.d.ts +8 -0
- package/dist/parser/TemplateParser.js +75 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +22 -0
- package/dist/parser/types.d.ts +30 -0
- package/dist/parser/types.js +3 -0
- package/dist/pipeline/PipelineOrchestrator.d.ts +23 -0
- package/dist/pipeline/PipelineOrchestrator.js +191 -0
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +19 -0
- package/dist/pipeline/types.d.ts +41 -0
- package/dist/pipeline/types.js +13 -0
- package/dist/pricing/CacheManager.d.ts +75 -0
- package/dist/pricing/CacheManager.js +195 -0
- package/dist/pricing/PricingClient.d.ts +17 -0
- package/dist/pricing/PricingClient.js +122 -0
- package/dist/pricing/PricingService.d.ts +16 -0
- package/dist/pricing/PricingService.js +149 -0
- package/dist/pricing/calculators/ALBCalculator.d.ts +16 -0
- package/dist/pricing/calculators/ALBCalculator.js +163 -0
- package/dist/pricing/calculators/APIGatewayCalculator.d.ts +10 -0
- package/dist/pricing/calculators/APIGatewayCalculator.js +177 -0
- package/dist/pricing/calculators/CloudFrontCalculator.d.ts +59 -0
- package/dist/pricing/calculators/CloudFrontCalculator.js +151 -0
- package/dist/pricing/calculators/DynamoDBCalculator.d.ts +9 -0
- package/dist/pricing/calculators/DynamoDBCalculator.js +146 -0
- package/dist/pricing/calculators/EC2Calculator.d.ts +7 -0
- package/dist/pricing/calculators/EC2Calculator.js +80 -0
- package/dist/pricing/calculators/ECSCalculator.d.ts +9 -0
- package/dist/pricing/calculators/ECSCalculator.js +116 -0
- package/dist/pricing/calculators/ElastiCacheCalculator.d.ts +8 -0
- package/dist/pricing/calculators/ElastiCacheCalculator.js +106 -0
- package/dist/pricing/calculators/LambdaCalculator.d.ts +13 -0
- package/dist/pricing/calculators/LambdaCalculator.js +111 -0
- package/dist/pricing/calculators/NLBCalculator.d.ts +16 -0
- package/dist/pricing/calculators/NLBCalculator.js +138 -0
- package/dist/pricing/calculators/NatGatewayCalculator.d.ts +12 -0
- package/dist/pricing/calculators/NatGatewayCalculator.js +116 -0
- package/dist/pricing/calculators/RDSCalculator.d.ts +9 -0
- package/dist/pricing/calculators/RDSCalculator.js +103 -0
- package/dist/pricing/calculators/S3Calculator.d.ts +8 -0
- package/dist/pricing/calculators/S3Calculator.js +68 -0
- package/dist/pricing/calculators/VPCEndpointCalculator.d.ts +12 -0
- package/dist/pricing/calculators/VPCEndpointCalculator.js +129 -0
- package/dist/pricing/index.d.ts +10 -0
- package/dist/pricing/index.js +37 -0
- package/dist/pricing/types.d.ts +53 -0
- package/dist/pricing/types.js +22 -0
- package/dist/releasetag.txt +1 -0
- package/dist/reporter/Reporter.d.ts +18 -0
- package/dist/reporter/Reporter.js +412 -0
- package/dist/reporter/index.d.ts +2 -0
- package/dist/reporter/index.js +21 -0
- package/dist/reporter/types.d.ts +72 -0
- package/dist/reporter/types.js +3 -0
- package/dist/synthesis/SynthesisOrchestrator.d.ts +26 -0
- package/dist/synthesis/SynthesisOrchestrator.js +243 -0
- package/dist/synthesis/index.d.ts +2 -0
- package/dist/synthesis/index.js +19 -0
- package/dist/synthesis/types.d.ts +17 -0
- package/dist/synthesis/types.js +13 -0
- package/dist/threshold/ThresholdEnforcer.d.ts +29 -0
- package/dist/threshold/ThresholdEnforcer.js +143 -0
- package/dist/threshold/index.d.ts +2 -0
- package/dist/threshold/index.js +19 -0
- package/dist/threshold/types.d.ts +15 -0
- package/dist/threshold/types.js +17 -0
- package/docs/CALCULATORS.md +820 -0
- package/docs/CI_CD.md +608 -0
- package/docs/CONFIGURATION.md +407 -0
- package/docs/DEVELOPMENT.md +387 -0
- package/docs/RELEASE.md +223 -0
- package/docs/TROUBLESHOOTING.md +847 -0
- package/examples/.cdk-cost-analyzer.yml +85 -0
- package/examples/.gitlab-ci.yml +125 -0
- package/examples/api-usage.js +26 -0
- package/examples/complex/base.json +16 -0
- package/examples/complex/target.json +29 -0
- package/examples/monorepo/.gitlab-ci.yml +251 -0
- package/examples/monorepo/README.md +341 -0
- package/examples/monorepo/package.json +27 -0
- package/examples/monorepo/packages/backend-infra/.cdk-cost-analyzer.yml +34 -0
- package/examples/monorepo/packages/backend-infra/bin/app.ts +16 -0
- package/examples/monorepo/packages/backend-infra/cdk.json +7 -0
- package/examples/monorepo/packages/backend-infra/lib/backend-stack.ts +128 -0
- package/examples/monorepo/packages/backend-infra/package.json +30 -0
- package/examples/monorepo/packages/backend-infra/tsconfig.json +11 -0
- package/examples/monorepo/packages/data-infra/.cdk-cost-analyzer.yml +38 -0
- package/examples/monorepo/packages/data-infra/bin/app.ts +16 -0
- package/examples/monorepo/packages/data-infra/cdk.json +7 -0
- package/examples/monorepo/packages/data-infra/lib/data-stack.ts +121 -0
- package/examples/monorepo/packages/data-infra/package.json +30 -0
- package/examples/monorepo/packages/data-infra/tsconfig.json +11 -0
- package/examples/monorepo/packages/frontend-infra/.cdk-cost-analyzer.yml +31 -0
- package/examples/monorepo/packages/frontend-infra/bin/app.ts +16 -0
- package/examples/monorepo/packages/frontend-infra/cdk.json +7 -0
- package/examples/monorepo/packages/frontend-infra/lib/frontend-stack.ts +60 -0
- package/examples/monorepo/packages/frontend-infra/package.json +30 -0
- package/examples/monorepo/packages/frontend-infra/tsconfig.json +11 -0
- package/examples/monorepo/tsconfig.json +35 -0
- package/examples/multi-stack/.cdk-cost-analyzer.yml +72 -0
- package/examples/multi-stack/.gitlab-ci.yml +184 -0
- package/examples/multi-stack/README.md +279 -0
- package/examples/multi-stack/bin/app.ts +36 -0
- package/examples/multi-stack/cdk.json +72 -0
- package/examples/multi-stack/lib/compute-stack.ts +128 -0
- package/examples/multi-stack/lib/networking-stack.ts +69 -0
- package/examples/multi-stack/lib/storage-stack.ts +141 -0
- package/examples/multi-stack/package-lock.json +4437 -0
- package/examples/multi-stack/package.json +42 -0
- package/examples/multi-stack/tsconfig.json +34 -0
- package/examples/simple/base.json +8 -0
- package/examples/simple/target.json +14 -0
- package/examples/single-stack/.NVP +0 -0
- package/examples/single-stack/.cdk-cost-analyzer.yml +52 -0
- package/examples/single-stack/.gitlab-ci.yml +126 -0
- package/examples/single-stack/README.md +184 -0
- package/examples/single-stack/UeK +0 -0
- package/examples/single-stack/bin/app.ts +16 -0
- package/examples/single-stack/cdk.json +72 -0
- package/examples/single-stack/lib/infrastructure-stack.ts +119 -0
- package/examples/single-stack/package-lock.json +4443 -0
- package/examples/single-stack/package.json +38 -0
- package/examples/single-stack/tsconfig.json +34 -0
- package/package.json +139 -0
- package/test-cdk-project/README-COMPUTE.md +141 -0
- package/test-cdk-project/README.md +95 -0
- package/test-cdk-project/app-with-compute.js +102 -0
- package/test-cdk-project/app.js +81 -0
- package/test-cdk-project/cdk-compute.json +3 -0
- package/test-cdk-project/cdk.context.json +7 -0
- package/test-cdk-project/cdk.json +3 -0
- package/test-cdk-project/cdk.out/TestStack.assets.json +21 -0
- package/test-cdk-project/cdk.out/TestStack.template.json +115 -0
- package/test-cdk-project/cdk.out/cdk.out +1 -0
- package/test-cdk-project/cdk.out/manifest.json +503 -0
- package/test-cdk-project/cdk.out/tree.json +1 -0
- package/test-cdk-project/cdk.out.base/TestStack.assets.json +21 -0
- package/test-cdk-project/cdk.out.base/TestStack.template.json +115 -0
- package/test-cdk-project/cdk.out.base/cdk.out +1 -0
- package/test-cdk-project/cdk.out.base/manifest.json +503 -0
- package/test-cdk-project/cdk.out.base/tree.json +1 -0
- package/test-cdk-project/cdk.out.target/TestStack.assets.json +21 -0
- package/test-cdk-project/cdk.out.target/TestStack.template.json +183 -0
- package/test-cdk-project/cdk.out.target/cdk.out +1 -0
- package/test-cdk-project/cdk.out.target/manifest.json +521 -0
- package/test-cdk-project/cdk.out.target/tree.json +1 -0
- package/test-cdk-project/package-lock.json +422 -0
- package/test-cdk-project/package.json +17 -0
- package/tools/workflows/README.md +102 -0
- package/tools/workflows/validate-workflows.js +109 -0
- package/tools/workflows/workflow-utils.ts +181 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"entries": {
|
|
3
|
+
"AmazonS3:EU (Frankfurt):storageClass:General Purpose|volumeType:Standard": {
|
|
4
|
+
"price": 0.023,
|
|
5
|
+
"timestamp": 1767969942826
|
|
6
|
+
},
|
|
7
|
+
"AmazonS3:invalid-region-123:storageClass:General Purpose|volumeType:Standard": {
|
|
8
|
+
"price": 0.023,
|
|
9
|
+
"timestamp": 1767969942865
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
package/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# GitLab CI/CD Pipeline for CDK Cost Analyzer
|
|
2
|
+
# This pipeline handles testing, building, and publishing the package
|
|
3
|
+
|
|
4
|
+
stages:
|
|
5
|
+
- test
|
|
6
|
+
# - build # Temporarily disabled due to build issues
|
|
7
|
+
- release
|
|
8
|
+
|
|
9
|
+
variables:
|
|
10
|
+
NODE_VERSION: "18"
|
|
11
|
+
NPM_CONFIG_CACHE: "$CI_PROJECT_DIR/.npm"
|
|
12
|
+
|
|
13
|
+
# Cache configuration for faster builds
|
|
14
|
+
.node_cache: &node_cache
|
|
15
|
+
cache:
|
|
16
|
+
key:
|
|
17
|
+
files:
|
|
18
|
+
- yarn.lock
|
|
19
|
+
paths:
|
|
20
|
+
- node_modules/
|
|
21
|
+
- .yarn/
|
|
22
|
+
- .npm/
|
|
23
|
+
|
|
24
|
+
# Test stage - Run on all commits
|
|
25
|
+
test:
|
|
26
|
+
stage: test
|
|
27
|
+
image: node:${NODE_VERSION}
|
|
28
|
+
<<: *node_cache
|
|
29
|
+
before_script:
|
|
30
|
+
- npm install -g projen
|
|
31
|
+
- npx projen install:ci
|
|
32
|
+
script:
|
|
33
|
+
# Run linting
|
|
34
|
+
- npx projen lint
|
|
35
|
+
# Run tests
|
|
36
|
+
- npx projen test
|
|
37
|
+
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
|
|
38
|
+
artifacts:
|
|
39
|
+
reports:
|
|
40
|
+
coverage_report:
|
|
41
|
+
coverage_format: cobertura
|
|
42
|
+
path: coverage/cobertura-coverage.xml
|
|
43
|
+
paths:
|
|
44
|
+
- coverage/
|
|
45
|
+
expire_in: 1 week
|
|
46
|
+
rules:
|
|
47
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
48
|
+
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
|
49
|
+
- if: '$CI_COMMIT_TAG'
|
|
50
|
+
|
|
51
|
+
# Build stage - Create package (TEMPORARILY DISABLED)
|
|
52
|
+
# build:
|
|
53
|
+
# stage: build
|
|
54
|
+
# image: node:${NODE_VERSION}
|
|
55
|
+
# <<: *node_cache
|
|
56
|
+
# before_script:
|
|
57
|
+
# - npm install -g projen
|
|
58
|
+
# - npx projen install:ci
|
|
59
|
+
# script:
|
|
60
|
+
# # Run full build (includes compile, test, package)
|
|
61
|
+
# - npx projen build
|
|
62
|
+
# artifacts:
|
|
63
|
+
# paths:
|
|
64
|
+
# - dist/
|
|
65
|
+
# - dist/js/*.tgz
|
|
66
|
+
# expire_in: 1 week
|
|
67
|
+
# rules:
|
|
68
|
+
# - if: '$CI_COMMIT_TAG'
|
|
69
|
+
# - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
|
70
|
+
|
|
71
|
+
# Publish to NPM - Manual trigger for tags (DISABLED - depends on build)
|
|
72
|
+
# publish:npm:
|
|
73
|
+
# stage: release
|
|
74
|
+
# image: node:${NODE_VERSION}
|
|
75
|
+
# dependencies:
|
|
76
|
+
# - build
|
|
77
|
+
# before_script:
|
|
78
|
+
# - npm install -g projen
|
|
79
|
+
# script:
|
|
80
|
+
# # Configure NPM authentication
|
|
81
|
+
# - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
|
|
82
|
+
# # Publish to NPM
|
|
83
|
+
# - npm publish --access public
|
|
84
|
+
# after_script:
|
|
85
|
+
# # Clean up credentials
|
|
86
|
+
# - rm -f .npmrc
|
|
87
|
+
# rules:
|
|
88
|
+
# - if: '$CI_COMMIT_TAG && $NPM_TOKEN'
|
|
89
|
+
# when: manual
|
|
90
|
+
# environment:
|
|
91
|
+
# name: npm-registry
|
|
92
|
+
# url: https://www.npmjs.com/package/cdk-cost-analyzer
|
|
93
|
+
|
|
94
|
+
# Publish to GitLab Package Registry - Automatic for tags (DISABLED - depends on build)
|
|
95
|
+
# publish:gitlab:
|
|
96
|
+
# stage: release
|
|
97
|
+
# image: node:${NODE_VERSION}
|
|
98
|
+
# dependencies:
|
|
99
|
+
# - build
|
|
100
|
+
# before_script:
|
|
101
|
+
# - npm install -g projen
|
|
102
|
+
# script:
|
|
103
|
+
# # Configure GitLab Package Registry authentication
|
|
104
|
+
# - echo "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}" > .npmrc
|
|
105
|
+
# - echo "@${CI_PROJECT_ROOT_NAMESPACE}:registry=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/" >> .npmrc
|
|
106
|
+
# # Publish to GitLab Package Registry
|
|
107
|
+
# - npm publish
|
|
108
|
+
# after_script:
|
|
109
|
+
# # Clean up credentials
|
|
110
|
+
# - rm -f .npmrc
|
|
111
|
+
# rules:
|
|
112
|
+
# - if: '$CI_COMMIT_TAG'
|
|
113
|
+
# environment:
|
|
114
|
+
# name: gitlab-package-registry
|
|
115
|
+
# url: ${CI_PROJECT_URL}/-/packages
|
|
116
|
+
|
|
117
|
+
# Create GitLab Release with release notes (DISABLED - depends on build)
|
|
118
|
+
# release:
|
|
119
|
+
# stage: release
|
|
120
|
+
# image: registry.gitlab.com/gitlab-org/release-cli:latest
|
|
121
|
+
# dependencies:
|
|
122
|
+
# - build
|
|
123
|
+
# script:
|
|
124
|
+
# - echo "Creating release for ${CI_COMMIT_TAG}"
|
|
125
|
+
# release:
|
|
126
|
+
# tag_name: '${CI_COMMIT_TAG}'
|
|
127
|
+
# description: |
|
|
128
|
+
# ## Release ${CI_COMMIT_TAG}
|
|
129
|
+
#
|
|
130
|
+
# ### Installation
|
|
131
|
+
#
|
|
132
|
+
# ```bash
|
|
133
|
+
# npm install -g cdk-cost-analyzer@${CI_COMMIT_TAG}
|
|
134
|
+
# ```
|
|
135
|
+
#
|
|
136
|
+
# ### Changes
|
|
137
|
+
#
|
|
138
|
+
# See [CHANGELOG.md](${CI_PROJECT_URL}/-/blob/${CI_COMMIT_TAG}/CHANGELOG.md) for details.
|
|
139
|
+
#
|
|
140
|
+
# ### Package
|
|
141
|
+
#
|
|
142
|
+
# - [NPM Package](https://www.npmjs.com/package/cdk-cost-analyzer/v/${CI_COMMIT_TAG})
|
|
143
|
+
# - [GitLab Package](${CI_PROJECT_URL}/-/packages)
|
|
144
|
+
# assets:
|
|
145
|
+
# links:
|
|
146
|
+
# - name: 'NPM Package'
|
|
147
|
+
# url: 'https://www.npmjs.com/package/cdk-cost-analyzer'
|
|
148
|
+
# link_type: 'package'
|
|
149
|
+
# rules:
|
|
150
|
+
# - if: '$CI_COMMIT_TAG'
|
|
151
|
+
|
|
152
|
+
# Quality gates - Run on merge requests
|
|
153
|
+
quality-gates:
|
|
154
|
+
stage: test
|
|
155
|
+
image: node:${NODE_VERSION}
|
|
156
|
+
<<: *node_cache
|
|
157
|
+
before_script:
|
|
158
|
+
- npm install -g projen
|
|
159
|
+
- npx projen install:ci
|
|
160
|
+
script:
|
|
161
|
+
# Check TypeScript compilation
|
|
162
|
+
- npx projen compile
|
|
163
|
+
# Run linting
|
|
164
|
+
- npx projen lint
|
|
165
|
+
# Run tests with coverage
|
|
166
|
+
- npx projen test
|
|
167
|
+
# Verify coverage threshold (80%)
|
|
168
|
+
- |
|
|
169
|
+
if [ -f coverage/coverage-summary.json ]; then
|
|
170
|
+
COVERAGE=$(node -pe "JSON.parse(require('fs').readFileSync('coverage/coverage-summary.json')).total.lines.pct")
|
|
171
|
+
echo "Code coverage: ${COVERAGE}%"
|
|
172
|
+
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
|
|
173
|
+
echo "ERROR: Code coverage ${COVERAGE}% is below threshold of 80%"
|
|
174
|
+
exit 1
|
|
175
|
+
fi
|
|
176
|
+
fi
|
|
177
|
+
rules:
|
|
178
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
179
|
+
allow_failure: false
|
|
180
|
+
|
|
181
|
+
# Dependency security scanning
|
|
182
|
+
dependency-scanning:
|
|
183
|
+
stage: test
|
|
184
|
+
image: node:${NODE_VERSION}
|
|
185
|
+
script:
|
|
186
|
+
- npm audit --audit-level=moderate
|
|
187
|
+
rules:
|
|
188
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
189
|
+
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
|
190
|
+
allow_failure: true
|
|
191
|
+
|
|
192
|
+
# Version bump helper - Manual trigger to create version bump commit (DISABLED - depends on build)
|
|
193
|
+
# bump-version:
|
|
194
|
+
# stage: build
|
|
195
|
+
# image: node:${NODE_VERSION}
|
|
196
|
+
# <<: *node_cache
|
|
197
|
+
# before_script:
|
|
198
|
+
# - npm install -g projen
|
|
199
|
+
# - npx projen install:ci
|
|
200
|
+
# # Configure git
|
|
201
|
+
# - git config user.email "ci@gitlab.com"
|
|
202
|
+
# - git config user.name "GitLab CI"
|
|
203
|
+
# script:
|
|
204
|
+
# # Run projen bump to create version bump
|
|
205
|
+
# - npx projen bump
|
|
206
|
+
# # Push the version bump commit and tag
|
|
207
|
+
# - git push origin HEAD:${CI_COMMIT_BRANCH}
|
|
208
|
+
# - git push --tags
|
|
209
|
+
# rules:
|
|
210
|
+
# - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
|
211
|
+
# when: manual
|
|
212
|
+
# only:
|
|
213
|
+
# - main
|
|
214
|
+
# - master
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Run linting and tests
|
|
2
|
+
npm run lint
|
|
3
|
+
npm run test:silent
|
|
4
|
+
|
|
5
|
+
# Validate GitHub Actions workflows if act is available
|
|
6
|
+
if command -v act >/dev/null 2>&1; then
|
|
7
|
+
echo "🔍 Validating GitHub Actions workflows..."
|
|
8
|
+
npm run validate:workflows
|
|
9
|
+
else
|
|
10
|
+
echo "⚠️ act not found - skipping workflow validation"
|
|
11
|
+
echo " Install with: brew install act"
|
|
12
|
+
fi
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": false,
|
|
3
|
+
"name": "Accessibility Audit",
|
|
4
|
+
"description": "Audit React/HTML components for accessibility issues (disabled by default for performance)",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
"**/*.tsx",
|
|
10
|
+
"**/*.jsx",
|
|
11
|
+
"**/*.html"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"then": {
|
|
15
|
+
"type": "askAgent",
|
|
16
|
+
"prompt": "A UI component file has been modified. Please audit it for accessibility issues following React and web accessibility best practices:\n1. Check for proper ARIA labels and roles\n2. Ensure all interactive elements are keyboard accessible\n3. Verify proper heading hierarchy (h1, h2, h3, etc.)\n4. Check color contrast ratios\n5. Ensure images have alt text\n6. Verify form labels are properly associated\n7. Check for semantic HTML usage\n8. Suggest improvements for screen reader compatibility\n9. Follow React accessibility patterns and hooks usage\n10. Ensure components are properly tested for accessibility"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "API Schema Validation",
|
|
4
|
+
"description": "Validate API schemas and generate types when OpenAPI/GraphQL schemas change",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
"**/*.openapi.yml",
|
|
10
|
+
"**/*.openapi.yaml",
|
|
11
|
+
"**/*.swagger.yml",
|
|
12
|
+
"**/*.swagger.yaml",
|
|
13
|
+
"**/*.graphql",
|
|
14
|
+
"**/schema.json"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"then": {
|
|
18
|
+
"type": "askAgent",
|
|
19
|
+
"prompt": "An API schema file has been modified. Please:\n1. Validate the schema syntax (OpenAPI/Swagger or GraphQL)\n2. Generate TypeScript types from the schema\n3. Update any existing API client code to match schema changes\n4. Check for breaking changes and document them\n5. Validate that examples in the schema are correct\n6. Update API documentation if needed\n7. Run any schema-based tests to ensure compatibility\n8. Follow TypeScript best practices for generated types\n9. Ensure proper error handling for API responses\n10. Document API endpoints and data structures\n11. Include relevant documentation links in code comments"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "Auto Test on Save",
|
|
4
|
+
"description": "Automatically run tests when TypeScript or JavaScript files are saved with minimal verbosity",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
"**/*.ts",
|
|
10
|
+
"**/*.js",
|
|
11
|
+
"**/*.tsx",
|
|
12
|
+
"**/*.jsx"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"then": {
|
|
16
|
+
"type": "askAgent",
|
|
17
|
+
"prompt": "A code file has been saved. Run the appropriate tests for this file and report any failures. IMPORTANT:\n1. Use Projen test command: npx projen test:silent (which runs vitest with --silent flag)\n2. For specific test files, use: npx vitest run --silent <test-file-path>\n3. If this is a test file, run it directly\n4. If this is a source file, find and run related tests\n5. Keep output minimal to prevent session timeouts"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "CDK Synth on Change",
|
|
4
|
+
"description": "Run CDK synth when CDK code is modified following CDK best practices",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
"src/**/*.ts",
|
|
10
|
+
"lib/**/*.ts",
|
|
11
|
+
"cdk.json",
|
|
12
|
+
"**/*Stack.ts",
|
|
13
|
+
"**/*Construct.ts"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"then": {
|
|
17
|
+
"type": "askAgent",
|
|
18
|
+
"prompt": "CDK code has been modified. Please:\n1. Run 'cdk synth' to validate the CDK code\n2. Check for any synthesis errors or warnings\n3. Validate that the generated CloudFormation templates are correct\n4. Run CDK diff if there's an existing deployment to show what would change\n5. Check for any security issues in the generated templates\n6. Ensure all constructs follow the established CDK patterns and best practices\n7. Verify proper resource naming and tagging conventions\n8. Check that all resources are properly configured for the target environment"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "Code Coverage Check",
|
|
4
|
+
"description": "Run tests with coverage and report on coverage gaps following testing best practices",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "manual",
|
|
8
|
+
"button_text": "Check Coverage"
|
|
9
|
+
},
|
|
10
|
+
"then": {
|
|
11
|
+
"type": "askAgent",
|
|
12
|
+
"prompt": "Run tests with code coverage analysis following testing best practices. Please:\n1. Run the test suite with coverage: npx vitest run --coverage --silent\n2. Generate a coverage report showing which lines/functions are not covered\n3. Identify files with low coverage (< 80% as configured in .projenrc.ts)\n4. Suggest specific areas that need more tests\n5. Check for any dead code that can be removed\n6. Report overall coverage statistics\n7. Suggest improvements to increase coverage\n8. Focus on testing behavior, not implementation\n9. Ensure tests are descriptive and well-organized\n\nNote: This project uses Projen with Vitest for testing. Coverage threshold is set to 80% in .projenrc.ts."
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "Commit Message Helper",
|
|
4
|
+
"description": "Help generate conventional commit messages following Git best practices",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "manual",
|
|
8
|
+
"button_text": "Generate Commit Message"
|
|
9
|
+
},
|
|
10
|
+
"then": {
|
|
11
|
+
"type": "askAgent",
|
|
12
|
+
"prompt": "Help generate a conventional commit message following Git best practices. Please:\n1. Analyze the current git diff to understand what changes were made\n2. Determine the appropriate commit type (feat, fix, docs, style, refactor, test, chore)\n3. Identify the scope if applicable\n4. Generate a concise, descriptive commit message following conventional commit format\n5. Keep the first line under 50 characters and use imperative mood\n6. Suggest the commit message and ask if the user wants to use it\n7. If there are multiple logical changes, suggest splitting into multiple commits\n8. Include body for complex changes if needed"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "Dependency Update Check",
|
|
4
|
+
"description": "Check for outdated dependencies and suggest updates following dependency management best practices",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "manual",
|
|
8
|
+
"button_text": "Check Dependencies"
|
|
9
|
+
},
|
|
10
|
+
"then": {
|
|
11
|
+
"type": "askAgent",
|
|
12
|
+
"prompt": "Check for outdated dependencies and suggest updates following dependency management best practices. Please:\n1. Run dependency check commands (npm outdated, pip list --outdated, etc.)\n2. Identify packages that have newer versions available\n3. Categorize updates by severity (major, minor, patch)\n4. Check for security vulnerabilities in current versions\n5. Suggest which updates are safe to apply immediately\n6. Warn about potentially breaking changes in major updates\n7. Provide commands to update specific packages\n8. Use latest stable versions of all libraries and dependencies\n9. Leverage Context7 MCP server to verify compatibility before adding dependencies\n10. Justify each new dependency with clear business or technical value"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "Environment File Validation",
|
|
4
|
+
"description": "Validate environment files and check for missing variables following security best practices",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
".env*",
|
|
10
|
+
"**/.env*",
|
|
11
|
+
"*.env"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"then": {
|
|
15
|
+
"type": "askAgent",
|
|
16
|
+
"prompt": "An environment file has been modified. Please:\n1. Check that no secrets or sensitive data are included (follow security best practices)\n2. Validate the format of environment variables\n3. Compare with .env.example to ensure all required variables are present\n4. Check that the corresponding .env file is in .gitignore\n5. Verify that environment variables are properly used in the code\n6. Suggest creating .env.example if it doesn't exist\n7. Check for any unused environment variables\n8. Ensure proper naming conventions (UPPER_SNAKE_CASE)\n9. Validate that sensitive data uses proper encryption or external secret management"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "Lint and Format on Save",
|
|
4
|
+
"description": "Automatically lint and format code when files are saved following project standards",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
"**/*.ts",
|
|
10
|
+
"**/*.js",
|
|
11
|
+
"**/*.tsx",
|
|
12
|
+
"**/*.jsx",
|
|
13
|
+
"**/*.py",
|
|
14
|
+
"**/*.json"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"then": {
|
|
18
|
+
"type": "askAgent",
|
|
19
|
+
"prompt": "A code file has been saved. Please:\n1. Run the TypeScript compiler check: npx projen lint (which runs tsc --noEmit)\n2. Use getDiagnostics tool to check for type errors in the modified file\n3. Report any remaining issues that need manual attention\n\nNote: This project uses Projen for build management. ESLint is configured but has version compatibility issues. Use TypeScript compiler for validation."
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "MCP Configuration Validation",
|
|
4
|
+
"description": "Validate MCP server configuration when mcp.json files are modified following MCP best practices",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
".kiro/settings/mcp.json",
|
|
10
|
+
"~/.kiro/settings/mcp.json"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"then": {
|
|
14
|
+
"type": "askAgent",
|
|
15
|
+
"prompt": "An MCP configuration file has been modified. Please:\n1. Validate the JSON syntax and structure\n2. Check that all required fields are present (command, args)\n3. Verify server names are unique and descriptive\n4. Review autoApprove settings for security implications\n5. Suggest testing the configured servers immediately\n6. Check for common configuration issues (missing uvx, incorrect paths)\n7. Recommend appropriate FASTMCP_LOG_LEVEL settings\n8. Verify that disabled servers are intentionally disabled\n9. Ensure version pinning follows MCP best practices (@latest or specific versions)\n10. Test servers immediately after configuration changes"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "Test MCP Servers",
|
|
4
|
+
"description": "Test all configured MCP servers and their tools following MCP best practices",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "manual",
|
|
8
|
+
"button_text": "Test MCP Servers"
|
|
9
|
+
},
|
|
10
|
+
"then": {
|
|
11
|
+
"type": "askAgent",
|
|
12
|
+
"prompt": "Test all configured MCP servers and their functionality following MCP best practices. Please:\n1. Check which MCP servers are currently configured and enabled\n2. Test basic connectivity to each server\n3. List available tools from each server\n4. Run sample calls to verify tool functionality\n5. Check for any connection errors or timeouts\n6. Verify that auto-approved tools are working correctly\n7. Report any servers that are disabled and suggest re-enabling if appropriate\n8. Document any issues found and suggest fixes\n9. Test with various parameter combinations\n10. Don't inspect the configuration files unless there are specific issues - focus on testing the actual tool functionality"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "Performance Analysis",
|
|
4
|
+
"description": "Analyze code for performance issues and optimization opportunities following development standards",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "manual",
|
|
8
|
+
"button_text": "Analyze Performance"
|
|
9
|
+
},
|
|
10
|
+
"then": {
|
|
11
|
+
"type": "askAgent",
|
|
12
|
+
"prompt": "Analyze the codebase for performance issues and optimization opportunities following development standards. Please:\n1. Look for common performance anti-patterns (N+1 queries, inefficient loops, etc.)\n2. Check for memory leaks or excessive memory usage\n3. Identify slow or blocking operations\n4. Suggest optimizations for database queries\n5. Look for opportunities to use caching\n6. Check bundle size and suggest ways to reduce it\n7. Identify unused code that can be removed\n8. Suggest performance monitoring tools if not already in use\n9. Follow language-specific performance best practices\n10. Keep functions small and focused on single responsibilities\n11. Implement proper error handling and logging"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "README Spell Check",
|
|
4
|
+
"description": "Check and fix spelling/grammar in README files following documentation standards",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "manual",
|
|
8
|
+
"button_text": "Spell Check README"
|
|
9
|
+
},
|
|
10
|
+
"then": {
|
|
11
|
+
"type": "askAgent",
|
|
12
|
+
"prompt": "Please review and fix spelling and grammar errors in README files following documentation best practices. Please:\n1. Check all README.md files in the project\n2. Identify spelling errors, grammar issues, and typos\n3. Fix obvious errors while preserving the original meaning\n4. Improve readability and clarity where needed\n5. Ensure consistent formatting and style\n6. Check that code examples are properly formatted\n7. Verify that links are working and properly formatted\n8. Maintain single comprehensive README covering all aspects including deployment\n9. Reference official sources through MCP servers when available"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"name": "Security Scan on Dependency Change",
|
|
4
|
+
"description": "Run security audit when package files are modified following security best practices",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
"package.json",
|
|
10
|
+
"package-lock.json",
|
|
11
|
+
"yarn.lock",
|
|
12
|
+
"requirements.txt",
|
|
13
|
+
"poetry.lock",
|
|
14
|
+
"Pipfile.lock"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"then": {
|
|
18
|
+
"type": "askAgent",
|
|
19
|
+
"prompt": "A dependency file has been modified. Please:\n1. Run security audit (npm audit, yarn audit, or pip-audit for Python)\n2. Check for known vulnerabilities in dependencies\n3. Suggest updates for vulnerable packages\n4. Report any high-severity issues that need immediate attention\n5. Check for any new dependencies that might introduce security risks\n6. Ensure all dependencies are from trusted sources and have active maintenance\n7. Follow security best practices for dependency management"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": false,
|
|
3
|
+
"name": "Translation Update",
|
|
4
|
+
"description": "Update translations when base language files change (disabled by default for performance)",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
"**/locales/en/**/*.json",
|
|
10
|
+
"**/i18n/en/**/*.json",
|
|
11
|
+
"**/translations/en.json"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"then": {
|
|
15
|
+
"type": "askAgent",
|
|
16
|
+
"prompt": "Base language translation file has been updated. Please:\n1. Identify what keys were added, modified, or removed\n2. Update corresponding translation files for other languages\n3. Mark new or changed keys as needing translation\n4. Preserve existing translations where keys haven't changed\n5. Generate placeholder translations or mark missing translations\n6. Update any translation documentation or guides\n7. Follow consistent naming conventions across translations\n8. Ensure proper file organization and structure\n9. Validate JSON syntax and structure\n10. Document any breaking changes in translation keys"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": false,
|
|
3
|
+
"name": "Update Documentation",
|
|
4
|
+
"description": "Update documentation when code changes (disabled by default for performance)",
|
|
5
|
+
"version": "1",
|
|
6
|
+
"when": {
|
|
7
|
+
"type": "fileEdited",
|
|
8
|
+
"patterns": [
|
|
9
|
+
"src/**/*.ts",
|
|
10
|
+
"src/**/*.js",
|
|
11
|
+
"lib/**/*.py"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"then": {
|
|
15
|
+
"type": "askAgent",
|
|
16
|
+
"prompt": "A source code file has been modified. Please update documentation following documentation best practices:\n1. Check if this file has associated documentation that needs updating\n2. Look for README files, API documentation, or inline comments that reference this code\n3. Update any outdated documentation to reflect the changes\n4. Generate JSDoc/docstring comments if they're missing from new functions\n5. Update any relevant examples or usage instructions\n6. Maintain single comprehensive README covering all aspects including deployment\n7. Reference official sources through MCP servers when available\n8. Keep documentation close to relevant code\n9. Include relevant documentation links in code comments\n10. Update documentation when upgrading dependencies"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"Context7": {
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": ["-y", "@upstash/context7-mcp", "--api-key", "ctx7sk-cd86342d-1e80-4236-b12b-7697ce5fe257"],
|
|
6
|
+
"env": {},
|
|
7
|
+
"disabled": true,
|
|
8
|
+
"autoApprove": []
|
|
9
|
+
},
|
|
10
|
+
"aws-docs": {
|
|
11
|
+
"command": "uvx",
|
|
12
|
+
"args": ["awslabs.aws-documentation-mcp-server@latest"],
|
|
13
|
+
"env": {
|
|
14
|
+
"FASTMCP_LOG_LEVEL": "ERROR"
|
|
15
|
+
},
|
|
16
|
+
"disabled": false,
|
|
17
|
+
"autoApprove": []
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|