├── .eslintrc.json ├── .gitattributes ├── .github ├── CODEOWNERS ├── pull_request_template.md └── workflows │ ├── add-to-project.yml │ ├── add-to-update-projen-project.yml │ ├── auto-approve.yml │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ ├── renovate.yml │ ├── stale.yml │ └── update-projen-main.yml ├── .gitignore ├── .mergify.yml ├── .npmignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.ts ├── .vscode └── settings.json ├── API.md ├── API.md.md ├── LICENSE ├── README.md ├── codecov.yml ├── docs ├── cdk-context-json │ └── README.md ├── cdk-diff │ └── README.md ├── codecov-bypass-workflow │ └── README.md ├── codecov │ ├── README.md │ ├── codecov-add-private.jpg │ ├── codecov-syncing.jpg │ └── codecov-with-private.jpg ├── datadog-service-catalog │ └── README.md ├── node-upgrade │ ├── README.md │ ├── projenrcts.png │ └── update-projen-main.png ├── renovate │ └── README.md └── slack-notifications │ └── README.md ├── package.json ├── pnpm-lock.yaml ├── renovate.json5 ├── src ├── add-to-project.ts ├── cdk-context-json.ts ├── cdk-diff-workflow.ts ├── clickup-cdk.ts ├── clickup-ts.ts ├── codecov-bypass-workflow.ts ├── codecov.ts ├── datadog-service-catalog.ts ├── datadog.ts ├── index.ts ├── node-version.ts ├── optional-node-version.ts ├── renovate-workflow.ts ├── slack-alert.ts ├── update-projen.ts └── utils │ └── parameters.ts ├── test ├── __snapshots__ │ ├── add-to-project.test.ts.snap │ ├── cdk-context-json.test.ts.snap │ ├── cdk-diff-workflow.test.ts.snap │ ├── clickup-cdk.test.ts.snap │ ├── clickup-ts.test.ts.snap │ ├── codecov-bypass-workflow.test.ts.snap │ ├── codecov.test.ts.snap │ ├── datadog-service-catalog.test.ts.snap │ ├── datadog.test.ts.snap │ ├── node-version.test.ts.snap │ ├── renovate-workflow.test.ts.snap │ ├── slack-alert.test.ts.snap │ └── update-projen.test.ts.snap ├── add-to-project.test.ts ├── cdk-context-json.test.ts ├── cdk-diff-workflow.test.ts ├── clickup-cdk.test.ts ├── clickup-ts.test.ts ├── codecov-bypass-workflow.test.ts ├── codecov.test.ts ├── datadog-service-catalog.test.ts ├── datadog.test.ts ├── node-version.test.ts ├── renovate-workflow.test.ts ├── requiredParams.ts ├── slack-alert.test.ts └── update-projen.test.ts ├── triggerRenovate.sh └── tsconfig.dev.json /.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 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": 2018, 15 | "sourceType": "module", 16 | "project": "./tsconfig.dev.json" 17 | }, 18 | "extends": [ 19 | "plugin:import/typescript", 20 | "plugin:prettier/recommended" 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 | "curly": [ 48 | "error", 49 | "multi-line", 50 | "consistent" 51 | ], 52 | "@typescript-eslint/no-require-imports": "error", 53 | "import/no-extraneous-dependencies": [ 54 | "error", 55 | { 56 | "devDependencies": [ 57 | "**/test/**", 58 | "**/build-tools/**", 59 | ".projenrc.ts", 60 | "projenrc/**/*.ts" 61 | ], 62 | "optionalDependencies": false, 63 | "peerDependencies": true 64 | } 65 | ], 66 | "import/no-unresolved": [ 67 | "error" 68 | ], 69 | "import/order": [ 70 | "warn", 71 | { 72 | "groups": [ 73 | "builtin", 74 | "external" 75 | ], 76 | "alphabetize": { 77 | "order": "asc", 78 | "caseInsensitive": true 79 | } 80 | } 81 | ], 82 | "import/no-duplicates": [ 83 | "error" 84 | ], 85 | "no-shadow": [ 86 | "off" 87 | ], 88 | "@typescript-eslint/no-shadow": "error", 89 | "@typescript-eslint/no-floating-promises": "error", 90 | "no-return-await": [ 91 | "off" 92 | ], 93 | "@typescript-eslint/return-await": "error", 94 | "dot-notation": [ 95 | "error" 96 | ], 97 | "no-bitwise": [ 98 | "error" 99 | ], 100 | "@typescript-eslint/member-ordering": [ 101 | "error", 102 | { 103 | "default": [ 104 | "public-static-field", 105 | "public-static-method", 106 | "protected-static-field", 107 | "protected-static-method", 108 | "private-static-field", 109 | "private-static-method", 110 | "field", 111 | "constructor", 112 | "method" 113 | ] 114 | } 115 | ] 116 | }, 117 | "overrides": [ 118 | { 119 | "files": [ 120 | ".projenrc.ts" 121 | ], 122 | "rules": { 123 | "@typescript-eslint/no-require-imports": "off", 124 | "import/no-extraneous-dependencies": "off" 125 | } 126 | } 127 | ] 128 | } 129 | -------------------------------------------------------------------------------- /.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/add-to-project.yml linguist-generated 9 | /.github/workflows/add-to-update-projen-project.yml linguist-generated 10 | /.github/workflows/auto-approve.yml linguist-generated 11 | /.github/workflows/build.yml linguist-generated 12 | /.github/workflows/pull-request-lint.yml linguist-generated 13 | /.github/workflows/release.yml linguist-generated 14 | /.github/workflows/renovate.yml linguist-generated 15 | /.github/workflows/stale.yml linguist-generated 16 | /.github/workflows/update-projen-main.yml linguist-generated 17 | /.gitignore linguist-generated 18 | /.mergify.yml linguist-generated 19 | /.npmignore linguist-generated 20 | /.npmrc linguist-generated 21 | /.nvmrc linguist-generated 22 | /.prettierignore linguist-generated 23 | /.prettierrc.json linguist-generated 24 | /.projen/** linguist-generated 25 | /.projen/deps.json linguist-generated 26 | /.projen/files.json linguist-generated 27 | /.projen/tasks.json linguist-generated 28 | /API.md linguist-generated 29 | /codecov.yml linguist-generated 30 | /LICENSE linguist-generated 31 | /package.json linguist-generated 32 | /pnpm-lock.yaml linguist-generated 33 | /renovate.json5 linguist-generated 34 | /tsconfig.dev.json linguist-generated -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This should really be @time-loop/cloud-platform or some such, but... for now... 2 | * @time-loop/cloud-platform 3 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: add-to-project 4 | on: 5 | pull_request: 6 | types: 7 | - opened 8 | - labeled 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref_name }} 11 | jobs: 12 | add-to-project: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | steps: 17 | - name: Add to project 18 | uses: actions/add-to-project@v0.4.1 19 | with: 20 | project-url: https://github.com/orgs/time-loop/projects/3 21 | github-token: ${{ secrets.RENOVATEBOT_GITHUB_TOKEN }} 22 | labeled: renovate 23 | -------------------------------------------------------------------------------- /.github/workflows/add-to-update-projen-project.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: add-to-update-projen-project 4 | on: 5 | pull_request: 6 | types: 7 | - opened 8 | - labeled 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref_name }} 11 | jobs: 12 | add-to-update-projen-project: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | steps: 17 | - name: Add to project 18 | uses: actions/add-to-project@v0.4.1 19 | with: 20 | project-url: https://github.com/orgs/time-loop/projects/6 21 | github-token: ${{ secrets.RENOVATEBOT_GITHUB_TOKEN }} 22 | labeled: update-projen 23 | -------------------------------------------------------------------------------- /.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 == 'cu-infra-svc-git') 18 | steps: 19 | - uses: hmarr/auto-approve-action@v2.2.1 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.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 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | outputs: 13 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 14 | env: 15 | CI: "true" 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: ${{ github.event.pull_request.head.ref }} 21 | repository: ${{ github.event.pull_request.head.repo.full_name }} 22 | - name: Setup pnpm 23 | uses: pnpm/action-setup@v3 24 | with: 25 | version: "9" 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: 22.14.0 30 | - name: Install dependencies 31 | run: pnpm i --no-frozen-lockfile 32 | - name: build 33 | run: npx projen build 34 | - name: Upload coverage to Codecov 35 | uses: codecov/codecov-action@v4 36 | with: 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | directory: coverage 39 | - name: Find mutations 40 | id: self_mutation 41 | run: |- 42 | git add . 43 | git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 44 | working-directory: ./ 45 | - name: Upload patch 46 | if: steps.self_mutation.outputs.self_mutation_happened 47 | uses: actions/upload-artifact@v4.4.0 48 | with: 49 | name: repo.patch 50 | path: repo.patch 51 | overwrite: true 52 | - name: Fail build on mutation 53 | if: steps.self_mutation.outputs.self_mutation_happened 54 | run: |- 55 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 56 | cat repo.patch 57 | exit 1 58 | - name: Backup artifact permissions 59 | run: cd dist && getfacl -R . > permissions-backup.acl 60 | continue-on-error: true 61 | - name: Upload artifact 62 | uses: actions/upload-artifact@v4.4.0 63 | with: 64 | name: build-artifact 65 | path: dist 66 | overwrite: true 67 | self-mutation: 68 | needs: build 69 | runs-on: ubuntu-latest 70 | permissions: 71 | contents: write 72 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 73 | steps: 74 | - name: Checkout 75 | uses: actions/checkout@v4 76 | with: 77 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 78 | ref: ${{ github.event.pull_request.head.ref }} 79 | repository: ${{ github.event.pull_request.head.repo.full_name }} 80 | - name: Download patch 81 | uses: actions/download-artifact@v4 82 | with: 83 | name: repo.patch 84 | path: ${{ runner.temp }} 85 | - name: Apply patch 86 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 87 | - name: Set git identity 88 | run: |- 89 | git config user.name "github-actions" 90 | git config user.email "github-actions@github.com" 91 | - name: Push changes 92 | env: 93 | PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }} 94 | run: |- 95 | git add . 96 | git commit -s -m "chore: self mutation" 97 | git push origin HEAD:$PULL_REQUEST_REF 98 | package-js: 99 | needs: build 100 | runs-on: ubuntu-latest 101 | permissions: 102 | contents: read 103 | if: ${{ !needs.build.outputs.self_mutation_happened }} 104 | steps: 105 | - uses: actions/setup-node@v4 106 | with: 107 | node-version: 22.14.0 108 | - name: Download build artifacts 109 | uses: actions/download-artifact@v4 110 | with: 111 | name: build-artifact 112 | path: dist 113 | - name: Restore build artifact permissions 114 | run: cd dist && setfacl --restore=permissions-backup.acl 115 | continue-on-error: true 116 | - name: Setup pnpm 117 | uses: pnpm/action-setup@v3 118 | with: 119 | version: "9" 120 | - name: Checkout 121 | uses: actions/checkout@v4 122 | with: 123 | ref: ${{ github.event.pull_request.head.ref }} 124 | repository: ${{ github.event.pull_request.head.repo.full_name }} 125 | path: .repo 126 | - name: Install Dependencies 127 | run: cd .repo && pnpm i --frozen-lockfile 128 | - name: Extract build artifact 129 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 130 | - name: Move build artifact out of the way 131 | run: mv dist dist.old 132 | - name: Create js artifact 133 | run: cd .repo && npx projen package:js 134 | - name: Collect js artifact 135 | run: mv .repo/dist dist 136 | -------------------------------------------------------------------------------- /.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: true 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: release 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: false 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | outputs: 18 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 19 | tag_exists: ${{ steps.check_tag_exists.outputs.exists }} 20 | env: 21 | CI: "true" 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | - name: Set git identity 28 | run: |- 29 | git config user.name "github-actions" 30 | git config user.email "github-actions@github.com" 31 | - name: Setup pnpm 32 | uses: pnpm/action-setup@v3 33 | with: 34 | version: "9" 35 | - name: Setup Node.js 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: 22.14.0 39 | - name: Install dependencies 40 | run: pnpm i --frozen-lockfile 41 | - name: release 42 | run: npx projen release 43 | - name: Upload coverage to Codecov 44 | uses: codecov/codecov-action@v4 45 | with: 46 | token: ${{ secrets.CODECOV_TOKEN }} 47 | directory: coverage 48 | - name: Check if version has already been tagged 49 | id: check_tag_exists 50 | run: |- 51 | TAG=$(cat dist/releasetag.txt) 52 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 53 | cat $GITHUB_OUTPUT 54 | - name: Check for new commits 55 | id: git_remote 56 | run: |- 57 | echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 58 | cat $GITHUB_OUTPUT 59 | - name: Backup artifact permissions 60 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 61 | run: cd dist && getfacl -R . > permissions-backup.acl 62 | continue-on-error: true 63 | - name: Upload artifact 64 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 65 | uses: actions/upload-artifact@v4.4.0 66 | with: 67 | name: build-artifact 68 | path: dist 69 | overwrite: true 70 | release_github: 71 | name: Publish to GitHub Releases 72 | needs: 73 | - release 74 | - release_npm 75 | runs-on: ubuntu-latest 76 | permissions: 77 | contents: write 78 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 79 | steps: 80 | - uses: actions/setup-node@v4 81 | with: 82 | node-version: 22.14.0 83 | - name: Download build artifacts 84 | uses: actions/download-artifact@v4 85 | with: 86 | name: build-artifact 87 | path: dist 88 | - name: Restore build artifact permissions 89 | run: cd dist && setfacl --restore=permissions-backup.acl 90 | continue-on-error: true 91 | - name: Release 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | 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 95 | release_npm: 96 | name: Publish to npm 97 | needs: release 98 | runs-on: ubuntu-latest 99 | permissions: 100 | id-token: write 101 | contents: read 102 | packages: write 103 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 104 | steps: 105 | - uses: actions/setup-node@v4 106 | with: 107 | node-version: 22.14.0 108 | - name: Download build artifacts 109 | uses: actions/download-artifact@v4 110 | with: 111 | name: build-artifact 112 | path: dist 113 | - name: Restore build artifact permissions 114 | run: cd dist && setfacl --restore=permissions-backup.acl 115 | continue-on-error: true 116 | - name: Setup pnpm 117 | uses: pnpm/action-setup@v3 118 | with: 119 | version: "9" 120 | - name: Checkout 121 | uses: actions/checkout@v4 122 | with: 123 | path: .repo 124 | - name: Install Dependencies 125 | run: cd .repo && pnpm i --frozen-lockfile 126 | - name: Extract build artifact 127 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 128 | - name: Move build artifact out of the way 129 | run: mv dist dist.old 130 | - name: Create js artifact 131 | run: cd .repo && npx projen package:js 132 | - name: Collect js artifact 133 | run: mv .repo/dist dist 134 | - name: Release 135 | env: 136 | NPM_DIST_TAG: latest 137 | NPM_REGISTRY: npm.pkg.github.com 138 | NPM_CONFIG_PROVENANCE: "true" 139 | NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }} 140 | run: npx -p publib@latest publib-npm 141 | send_release_event_to_slack: 142 | name: Send Release Alert to Slack 143 | needs: release 144 | runs-on: ubuntu-latest 145 | permissions: 146 | contents: read 147 | if: needs.release.outputs.latest_commit == github.sha 148 | steps: 149 | - name: Download build artifacts 150 | uses: actions/download-artifact@v4 151 | with: 152 | name: build-artifact 153 | path: dist 154 | - name: Get version 155 | id: event_metadata 156 | run: echo "release_tag=$(cat dist/releasetag.txt)" >> $GITHUB_OUTPUT 157 | - name: Send Slack webhook event 158 | uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7 159 | env: 160 | SLACK_TITLE: ${{ github.repository }}@${{ steps.event_metadata.outputs.release_tag }} released! 161 | SLACK_MESSAGE: "View the release notes here: https://github.com/${{ github.repository }}/releases/tag/${{ steps.event_metadata.outputs.release_tag }}" 162 | SLACK_WEBHOOK: ${{ secrets.PROJEN_RELEASE_SLACK_WEBHOOK }} 163 | SLACK_FOOTER: "" 164 | SLACK_COLOR: success 165 | MSG_MINIMAL: "true" 166 | -------------------------------------------------------------------------------- /.github/workflows/renovate.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-main 4 | on: 5 | schedule: 6 | - cron: 15 0 * * MON-FRI 7 | workflow_dispatch: {} 8 | push: 9 | branches: 10 | - main 11 | paths: 12 | - renovate.json5 13 | - .github/workflows/renovate.yml 14 | issues: 15 | types: 16 | - edited 17 | pull_request: 18 | types: 19 | - edited 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.event_name || '' }} 22 | jobs: 23 | upgrade-main: 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | - name: Self-hosted Renovate 31 | uses: renovatebot/github-action@v41.0.12 32 | if: (github.event_name != 'issues' && github.event_name != 'pull_request') || github.actor != 'cu-infra-svc-git' 33 | with: 34 | configurationFile: renovate.json5 35 | token: x-access-token:${{ secrets.RENOVATEBOT_GITHUB_TOKEN }} 36 | env: 37 | RENOVATE_HOST_RULES: '[{"matchHost":"https://npm.pkg.github.com/","hostType":"npm","token":"${{ secrets.ALL_PACKAGE_READ_TOKEN }}"}]' 38 | RENOVATE_NPMRC: "@${{ github.repository_owner }}:registry=https://npm.pkg.github.com" 39 | RENOVATE_ENABLED_MANAGERS: '["npm"]' 40 | RENOVATE_REPOSITORIES: '["${{ github.repository }}"]' 41 | RENOVATE_FORCE: ${{ github.event_name == 'workflow_dispatch' && '{"schedule":null}' || '' }} 42 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: stale 4 | on: 5 | schedule: 6 | - cron: 0 1 * * * 7 | workflow_dispatch: {} 8 | jobs: 9 | stale: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | issues: write 13 | pull-requests: write 14 | steps: 15 | - uses: actions/stale@v4 16 | with: 17 | days-before-stale: -1 18 | days-before-close: -1 19 | days-before-pr-stale: 14 20 | days-before-pr-close: 2 21 | stale-pr-message: This pull request is now marked as stale because it hasn't seen activity for a while. Add a comment or it will be closed soon. If you wish to exclude this issue from being marked as stale, add the "backlog" label. 22 | close-pr-message: Closing this pull request as it hasn't seen activity for a while. Please add a comment @mentioning a maintainer to reopen. If you wish to exclude this issue from being marked as stale, add the "backlog" label. 23 | stale-pr-label: stale 24 | exempt-pr-labels: backlog 25 | days-before-issue-stale: 60 26 | days-before-issue-close: 7 27 | stale-issue-message: This issue is now marked as stale because it hasn't seen activity for a while. Add a comment or it will be closed soon. If you wish to exclude this issue from being marked as stale, add the "backlog" label. 28 | close-issue-message: Closing this issue as it hasn't seen activity for a while. Please add a comment @mentioning a maintainer to reopen. If you wish to exclude this issue from being marked as stale, add the "backlog" label. 29 | stale-issue-label: stale 30 | exempt-issue-labels: backlog 31 | -------------------------------------------------------------------------------- /.github/workflows/update-projen-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: update-projen-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 0 1 * * 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | ref: main 21 | - name: Setup pnpm 22 | uses: pnpm/action-setup@v3 23 | with: 24 | version: "9" 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 22.14.0 29 | - name: Install dependencies 30 | run: pnpm i --frozen-lockfile 31 | - name: Upgrade dependencies 32 | run: npx projen update-projen 33 | - name: Find mutations 34 | id: create_patch 35 | run: |- 36 | git add . 37 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 38 | working-directory: ./ 39 | - name: Upload patch 40 | if: steps.create_patch.outputs.patch_created 41 | uses: actions/upload-artifact@v4.4.0 42 | with: 43 | name: repo.patch 44 | path: repo.patch 45 | overwrite: true 46 | pr: 47 | name: Create Pull Request 48 | needs: upgrade 49 | runs-on: ubuntu-latest 50 | permissions: 51 | contents: read 52 | if: ${{ needs.upgrade.outputs.patch_created }} 53 | steps: 54 | - name: Checkout 55 | uses: actions/checkout@v4 56 | with: 57 | ref: main 58 | - name: Download patch 59 | uses: actions/download-artifact@v4 60 | with: 61 | name: repo.patch 62 | path: ${{ runner.temp }} 63 | - name: Apply patch 64 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 65 | - name: Set git identity 66 | run: |- 67 | git config user.name "github-actions" 68 | git config user.email "github-actions@github.com" 69 | - name: Create Pull Request 70 | id: create-pr 71 | uses: peter-evans/create-pull-request@v6 72 | with: 73 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 74 | commit-message: |- 75 | fix(deps): upgrade projen 76 | 77 | Upgrades project dependencies. See details in [workflow run]. 78 | 79 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 80 | 81 | ------ 82 | 83 | *Automatically created by projen via the "update-projen-main" workflow* 84 | branch: github-actions/update-projen-main 85 | title: "fix(deps): upgrade projen" 86 | labels: update-projen 87 | body: |- 88 | Upgrades project dependencies. See details in [workflow run]. 89 | 90 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 91 | 92 | ------ 93 | 94 | *Automatically created by projen via the "update-projen-main" workflow* 95 | author: github-actions 96 | committer: github-actions 97 | signoff: true 98 | -------------------------------------------------------------------------------- /.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 | !/renovate.json5 6 | !/.projen/files.json 7 | !/.github/workflows/pull-request-lint.yml 8 | !/.github/workflows/auto-approve.yml 9 | !/.github/workflows/stale.yml 10 | !/package.json 11 | !/LICENSE 12 | !/.npmignore 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | lerna-debug.log* 19 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | lib-cov 25 | coverage 26 | *.lcov 27 | .nyc_output 28 | build/Release 29 | node_modules/ 30 | jspm_packages/ 31 | *.tsbuildinfo 32 | .eslintcache 33 | *.tgz 34 | .yarn-integrity 35 | .cache 36 | .idea 37 | /test-reports/ 38 | junit.xml 39 | /coverage/ 40 | !/.github/workflows/build.yml 41 | /dist/changelog.md 42 | /dist/version.txt 43 | !/.github/workflows/release.yml 44 | !/.mergify.yml 45 | !/.github/pull_request_template.md 46 | !/.prettierignore 47 | !/.prettierrc.json 48 | !/.npmrc 49 | !/test/ 50 | !/tsconfig.dev.json 51 | !/src/ 52 | /lib 53 | /dist/ 54 | !/.eslintrc.json 55 | .jsii 56 | tsconfig.json 57 | !/API.md 58 | !/codecov.yml 59 | !/.nvmrc 60 | !/.github/workflows/renovate.yml 61 | !/.github/workflows/add-to-project.yml 62 | !/.github/workflows/update-projen-main.yml 63 | !/.github/workflows/add-to-update-projen-project.yml 64 | !/.projenrc.ts 65 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | update_method: merge 6 | conditions: 7 | - "#approved-reviews-by>=1" 8 | - -label~=(do-not-merge) 9 | - status-success=build 10 | - status-success=package-js 11 | merge_method: squash 12 | commit_message_template: |- 13 | {{ title }} (#{{ number }}) 14 | 15 | {{ body }} 16 | pull_request_rules: 17 | - name: Automatic merge on approval and successful build 18 | actions: 19 | delete_head_branch: {} 20 | queue: 21 | name: default 22 | conditions: 23 | - "#approved-reviews-by>=1" 24 | - -label~=(do-not-merge) 25 | - status-success=build 26 | - status-success=package-js 27 | -------------------------------------------------------------------------------- /.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 | /.mergify.yml 10 | /.prettierignore 11 | /.prettierrc.json 12 | /test/ 13 | /tsconfig.dev.json 14 | /src/ 15 | !/lib/ 16 | !/lib/**/*.js 17 | !/lib/**/*.d.ts 18 | dist 19 | /tsconfig.json 20 | /.github/ 21 | /.vscode/ 22 | /.idea/ 23 | /.projenrc.js 24 | tsconfig.tsbuildinfo 25 | /.eslintrc.json 26 | !.jsii 27 | /.gitattributes 28 | /.projenrc.ts 29 | /projenrc 30 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | resolution-mode=highest 4 | package-manager-strict=false 5 | manage-package-manager-versions=true 6 | use-node-version=22.14.0 7 | node-linker=hoisted 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.14.0 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "overrides": [] 6 | } 7 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@types/jest", 5 | "type": "build" 6 | }, 7 | { 8 | "name": "@types/node", 9 | "version": "^18", 10 | "type": "build" 11 | }, 12 | { 13 | "name": "@types/semver", 14 | "type": "build" 15 | }, 16 | { 17 | "name": "@typescript-eslint/eslint-plugin", 18 | "version": "^8", 19 | "type": "build" 20 | }, 21 | { 22 | "name": "@typescript-eslint/parser", 23 | "version": "^8", 24 | "type": "build" 25 | }, 26 | { 27 | "name": "commit-and-tag-version", 28 | "version": "^12", 29 | "type": "build" 30 | }, 31 | { 32 | "name": "constructs", 33 | "version": "^10.0.0", 34 | "type": "build" 35 | }, 36 | { 37 | "name": "eslint-config-prettier", 38 | "type": "build" 39 | }, 40 | { 41 | "name": "eslint-import-resolver-typescript", 42 | "type": "build" 43 | }, 44 | { 45 | "name": "eslint-plugin-import", 46 | "type": "build" 47 | }, 48 | { 49 | "name": "eslint-plugin-prettier", 50 | "type": "build" 51 | }, 52 | { 53 | "name": "eslint", 54 | "version": "^9", 55 | "type": "build" 56 | }, 57 | { 58 | "name": "jest", 59 | "type": "build" 60 | }, 61 | { 62 | "name": "jest-junit", 63 | "version": "^16", 64 | "type": "build" 65 | }, 66 | { 67 | "name": "jsii-diff", 68 | "type": "build" 69 | }, 70 | { 71 | "name": "jsii-docgen", 72 | "version": "^10.5.0", 73 | "type": "build" 74 | }, 75 | { 76 | "name": "jsii-pacmak", 77 | "type": "build" 78 | }, 79 | { 80 | "name": "jsii-rosetta", 81 | "version": "~5.8.0", 82 | "type": "build" 83 | }, 84 | { 85 | "name": "jsii", 86 | "version": "~5.8.0", 87 | "type": "build" 88 | }, 89 | { 90 | "name": "prettier", 91 | "type": "build" 92 | }, 93 | { 94 | "name": "projen", 95 | "type": "build" 96 | }, 97 | { 98 | "name": "ts-jest", 99 | "type": "build" 100 | }, 101 | { 102 | "name": "ts-node", 103 | "type": "build" 104 | }, 105 | { 106 | "name": "typescript", 107 | "type": "build" 108 | }, 109 | { 110 | "name": "cson-parser", 111 | "type": "bundled" 112 | }, 113 | { 114 | "name": "semver", 115 | "type": "bundled" 116 | }, 117 | { 118 | "name": "ts-deepmerge", 119 | "type": "bundled" 120 | }, 121 | { 122 | "name": "projen", 123 | "type": "peer" 124 | }, 125 | { 126 | "name": "cson-parser", 127 | "type": "runtime" 128 | }, 129 | { 130 | "name": "projen", 131 | "type": "runtime" 132 | }, 133 | { 134 | "name": "semver", 135 | "type": "runtime" 136 | }, 137 | { 138 | "name": "ts-deepmerge", 139 | "type": "runtime" 140 | } 141 | ], 142 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 143 | } 144 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/add-to-project.yml", 7 | ".github/workflows/add-to-update-projen-project.yml", 8 | ".github/workflows/auto-approve.yml", 9 | ".github/workflows/build.yml", 10 | ".github/workflows/pull-request-lint.yml", 11 | ".github/workflows/release.yml", 12 | ".github/workflows/renovate.yml", 13 | ".github/workflows/stale.yml", 14 | ".github/workflows/update-projen-main.yml", 15 | ".gitignore", 16 | ".mergify.yml", 17 | ".npmrc", 18 | ".nvmrc", 19 | ".prettierignore", 20 | ".prettierrc.json", 21 | ".projen/deps.json", 22 | ".projen/files.json", 23 | ".projen/tasks.json", 24 | "codecov.yml", 25 | "LICENSE", 26 | "renovate.json5", 27 | "tsconfig.dev.json" 28 | ], 29 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 30 | } 31 | -------------------------------------------------------------------------------- /.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 | }, 38 | "steps": [ 39 | { 40 | "builtin": "release/bump-version" 41 | } 42 | ], 43 | "condition": "git log --oneline -1 | grep -qv \"chore(release):\"" 44 | }, 45 | "clobber": { 46 | "name": "clobber", 47 | "description": "hard resets to HEAD of origin and cleans the local repo", 48 | "env": { 49 | "BRANCH": "$(git branch --show-current)" 50 | }, 51 | "steps": [ 52 | { 53 | "exec": "git checkout -b scratch", 54 | "name": "save current HEAD in \"scratch\" branch" 55 | }, 56 | { 57 | "exec": "git checkout $BRANCH" 58 | }, 59 | { 60 | "exec": "git fetch origin", 61 | "name": "fetch latest changes from origin" 62 | }, 63 | { 64 | "exec": "git reset --hard origin/$BRANCH", 65 | "name": "hard reset to origin commit" 66 | }, 67 | { 68 | "exec": "git clean -fdx", 69 | "name": "clean all untracked files" 70 | }, 71 | { 72 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 73 | } 74 | ], 75 | "condition": "git diff --exit-code > /dev/null" 76 | }, 77 | "compat": { 78 | "name": "compat", 79 | "description": "Perform API compatibility check against latest version", 80 | "steps": [ 81 | { 82 | "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)" 83 | } 84 | ] 85 | }, 86 | "compile": { 87 | "name": "compile", 88 | "description": "Only compile", 89 | "steps": [ 90 | { 91 | "exec": "jsii --silence-warnings=reserved-word" 92 | } 93 | ] 94 | }, 95 | "default": { 96 | "name": "default", 97 | "description": "Synthesize project files", 98 | "steps": [ 99 | { 100 | "exec": "ts-node --project tsconfig.dev.json .projenrc.ts" 101 | } 102 | ] 103 | }, 104 | "docgen": { 105 | "name": "docgen", 106 | "description": "Generate API.md from .jsii manifest", 107 | "steps": [ 108 | { 109 | "exec": "jsii-docgen -o API.md" 110 | } 111 | ] 112 | }, 113 | "eject": { 114 | "name": "eject", 115 | "description": "Remove projen from the project", 116 | "env": { 117 | "PROJEN_EJECTING": "true" 118 | }, 119 | "steps": [ 120 | { 121 | "spawn": "default" 122 | } 123 | ] 124 | }, 125 | "eslint": { 126 | "name": "eslint", 127 | "description": "Runs eslint against the codebase", 128 | "env": { 129 | "ESLINT_USE_FLAT_CONFIG": "false" 130 | }, 131 | "steps": [ 132 | { 133 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools projenrc .projenrc.ts", 134 | "receiveArgs": true 135 | } 136 | ] 137 | }, 138 | "install": { 139 | "name": "install", 140 | "description": "Install project dependencies and update lockfile (non-frozen)", 141 | "steps": [ 142 | { 143 | "exec": "pnpm i --no-frozen-lockfile" 144 | } 145 | ] 146 | }, 147 | "install:ci": { 148 | "name": "install:ci", 149 | "description": "Install project dependencies using frozen lockfile", 150 | "steps": [ 151 | { 152 | "exec": "pnpm i --frozen-lockfile" 153 | } 154 | ] 155 | }, 156 | "package": { 157 | "name": "package", 158 | "description": "Creates the distribution package", 159 | "steps": [ 160 | { 161 | "spawn": "package:js", 162 | "condition": "node -e \"if (!process.env.CI) process.exit(1)\"" 163 | }, 164 | { 165 | "spawn": "package-all", 166 | "condition": "node -e \"if (process.env.CI) process.exit(1)\"" 167 | } 168 | ] 169 | }, 170 | "package-all": { 171 | "name": "package-all", 172 | "description": "Packages artifacts for all target languages", 173 | "steps": [ 174 | { 175 | "spawn": "package:js" 176 | } 177 | ] 178 | }, 179 | "package:js": { 180 | "name": "package:js", 181 | "description": "Create js language bindings", 182 | "steps": [ 183 | { 184 | "exec": "jsii-pacmak -v --pack-command \"pnpm pack\" --target js" 185 | } 186 | ] 187 | }, 188 | "post-compile": { 189 | "name": "post-compile", 190 | "description": "Runs after successful compilation", 191 | "steps": [ 192 | { 193 | "spawn": "docgen" 194 | } 195 | ] 196 | }, 197 | "post-upgrade": { 198 | "name": "post-upgrade", 199 | "description": "Runs after upgrading dependencies" 200 | }, 201 | "pre-compile": { 202 | "name": "pre-compile", 203 | "description": "Prepare the project for compilation" 204 | }, 205 | "release": { 206 | "name": "release", 207 | "description": "Prepare a release from \"main\" branch", 208 | "env": { 209 | "RELEASE": "true", 210 | "MAJOR": "1" 211 | }, 212 | "steps": [ 213 | { 214 | "exec": "rm -fr dist" 215 | }, 216 | { 217 | "spawn": "bump" 218 | }, 219 | { 220 | "spawn": "build" 221 | }, 222 | { 223 | "spawn": "unbump" 224 | }, 225 | { 226 | "exec": "git diff --ignore-space-at-eol --exit-code" 227 | } 228 | ] 229 | }, 230 | "test": { 231 | "name": "test", 232 | "description": "Run tests", 233 | "steps": [ 234 | { 235 | "exec": "jest --passWithNoTests --updateSnapshot", 236 | "receiveArgs": true 237 | }, 238 | { 239 | "spawn": "eslint" 240 | } 241 | ] 242 | }, 243 | "test:watch": { 244 | "name": "test:watch", 245 | "description": "Run jest in watch mode", 246 | "steps": [ 247 | { 248 | "exec": "jest --watch" 249 | } 250 | ] 251 | }, 252 | "unbump": { 253 | "name": "unbump", 254 | "description": "Restores version to 0.0.0", 255 | "env": { 256 | "OUTFILE": "package.json", 257 | "CHANGELOG": "dist/changelog.md", 258 | "BUMPFILE": "dist/version.txt", 259 | "RELEASETAG": "dist/releasetag.txt", 260 | "RELEASE_TAG_PREFIX": "", 261 | "BUMP_PACKAGE": "commit-and-tag-version@^12" 262 | }, 263 | "steps": [ 264 | { 265 | "builtin": "release/reset-version" 266 | } 267 | ] 268 | }, 269 | "update-projen": { 270 | "name": "update-projen", 271 | "description": "upgrade projen", 272 | "env": { 273 | "CI": "0" 274 | }, 275 | "steps": [ 276 | { 277 | "exec": "pnpm dlx npm-check-updates@16 --upgrade --target=latest --peer --no-deprecated --dep=dev,peer,prod,optional --filter=projen,@time-loop/clickup-projen" 278 | }, 279 | { 280 | "exec": "pnpm i --no-frozen-lockfile" 281 | }, 282 | { 283 | "exec": "pnpm update projen @time-loop/clickup-projen" 284 | }, 285 | { 286 | "exec": "npx projen" 287 | }, 288 | { 289 | "spawn": "post-upgrade" 290 | } 291 | ] 292 | }, 293 | "watch": { 294 | "name": "watch", 295 | "description": "Watch & compile in the background", 296 | "steps": [ 297 | { 298 | "exec": "jsii -w --silence-warnings=reserved-word" 299 | } 300 | ] 301 | } 302 | }, 303 | "env": { 304 | "PATH": "$(pnpm -c exec \"node --print process.env.PATH\")" 305 | }, 306 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 307 | } 308 | -------------------------------------------------------------------------------- /.projenrc.ts: -------------------------------------------------------------------------------- 1 | import { cdk, github, javascript, TextFile, YamlFile } from 'projen'; 2 | import { addToProjectWorkflow } from './src/add-to-project'; 3 | import { renovateWorkflow } from './src/renovate-workflow'; 4 | import { slackAlert } from './src/slack-alert'; 5 | import { updateProjen } from './src/update-projen'; 6 | import { parameters } from './src/utils/parameters'; 7 | 8 | const bundledDeps = ['cson-parser', 'semver', 'ts-deepmerge']; 9 | 10 | const project = new cdk.JsiiProject({ 11 | name: '@time-loop/clickup-projen', 12 | author: 'ClickUp DevOps', 13 | authorAddress: 'devops@clickup.com', 14 | authorName: 'ClickUp DevOps', 15 | authorOrganization: true, 16 | majorVersion: 1, // trigger a 1.0.0 release 17 | jsiiVersion: '~5.8.0', // per note, JSII since v5.0.0 are not semver'd so... stick with minor version updates. 18 | // Apache open source license, to match projen license 19 | 20 | packageManager: javascript.NodePackageManager.PNPM, 21 | pnpmVersion: '9', 22 | 23 | minNodeVersion: parameters.PROJEN_MIN_ENGINE_NODE_VERSION, 24 | workflowNodeVersion: parameters.PROJEN_NODE_VERSION, 25 | 26 | defaultReleaseBranch: 'main', 27 | // release: true, // default 28 | npmRegistryUrl: 'https://npm.pkg.github.com', 29 | repositoryUrl: 'https://github.com/time-loop/clickup-projen.git', // default 30 | npmAccess: javascript.NpmAccess.PUBLIC, 31 | 32 | githubOptions: { 33 | // TODO: we should instead be using an app for auth. 34 | // projenCredentials: github.GithubCredentials.fromApp(writeme), 35 | projenCredentials: github.GithubCredentials.fromPersonalAccessToken(), 36 | pullRequestLintOptions: { 37 | // Enforce conventional commits https://www.conventionalcommits.org/ 38 | // https://github.com/marketplace/actions/semantic-pull-request 39 | semanticTitleOptions: { requireScope: true }, 40 | }, 41 | }, 42 | 43 | gitignore: ['.idea'], 44 | 45 | // We don't depend on any private resources. 46 | // Add a .npmrc before we try to Install dependencies 47 | // workflowBootstrapSteps: [ 48 | // { 49 | // name: 'GitHub Packages authorization', 50 | // run: [ 51 | // 'cat > .npmrc < { 67 | + const namedEnv = factory('us-east-1'); 68 | + const stageId = cdkPipeline.getUniqueStageIdentifier(namedEnv).addPrefix(['oidc']); 69 | + oidcPermissions.addStage( 70 | + GitHubActionsOIDCPermissions.asStage(this, stageId.pascal, { ...commonProps, namedEnv }), 71 | + ); 72 | + }); 73 | + 74 | const addStage = (factory: core.INamedEnvFactory, addStageProps?: AddStageProps): Stage[] => { 75 | const targets = addStageProps?.shardsFilter ? factory.getShards(addStageProps.shardsFilter) : factory.shards; 76 | return targets.map((shard) => { 77 | ``` 78 | 79 | ## Total Diff 80 | 81 | This is roughly what your PR should end up looking like. 82 | Note that the only files you manually edited were `.projenrc.ts` and `/src/pipeline.ts`. 83 | 84 | ``` 85 | ❯ git diff --stat main 86 | .gitattributes | 1 + 87 | .github/workflows/cdk-diff.yml | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 88 | .gitignore | 1 + 89 | .projen/deps.json | 5 +++ 90 | .projen/files.json | 1 + 91 | .projenrc.ts | 28 +++++++++++++-- 92 | package.json | 1 + 93 | renovate.json5 | 1 + 94 | src/github-actions-oidc-permissions.ts | 52 ++++++++++++++++++++++++++++ 95 | src/pipeline.ts | 10 ++++++ 96 | yarn.lock | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 97 | 11 files changed, 354 insertions(+), 3 deletions(-) 98 | ``` 99 | 100 | ## Why is it broken? 101 | 102 | Understand that when you initially push this, the `cdk-diff` job will fail. 103 | This is expected. It's failing because the OIDC role stacks haven't yet been deployed. 104 | If you have admin access, you can deploy these manually, 105 | but most folks just go ahead and merge the PR. 106 | Then the pipeline will self-mutate and eventually deploy the OIDC role stacks. 107 | 108 | Once the pipeline has finished deploying the OIDC role stacks, 109 | find another PR and update it. 110 | You should see the `cdk-diff` stuff succeeding at this point. 111 | -------------------------------------------------------------------------------- /docs/codecov-bypass-workflow/README.md: -------------------------------------------------------------------------------- 1 | # codecov-bypass 2 | 3 | This is currently an explict opt-in feature. Eventually it will be a default enabled workflow with an explicit disable. 4 | 5 | To add this to your project, you will need to 6 | 7 | - edit your `.projenrc.ts` 8 | - run `npx projen` to generate a bunch of files 9 | - update your branch protection rules 10 | 11 | ## Edit your .projenrc.ts 12 | 13 | ```diff 14 | import { clickupCdk } from '@time-loop/clickup-projen'; 15 | const project = new clickupCdk.ClickUpCdkTypeScriptApp({ 16 | ... 17 | + codecovBypassOptionsConfig: { 18 | + githubAppId: '${{ vars.CODECOV_GITHUB_APP_ID }}', 19 | + githubAppPrivateKey: '${{ secrets.CODECOV_GITHUB_APP_PRIVATE_KEY }}' 20 | + }, 21 | ``` 22 | 23 | ## Generate new files 24 | 25 | After the above change, run `npx projen` to generate the new files. 26 | 27 | ## Update Branch Protection Rules 28 | 29 | Once the above changes are merged, you will need to update your branch protection rules: 30 | 31 | 1. Remove `codecod/patch`, and `codecov/patch` from the list of required checks. 32 | 2. Add `Code coverage increased` to the list of required checks. 33 | -------------------------------------------------------------------------------- /docs/codecov/README.md: -------------------------------------------------------------------------------- 1 | # CodeCov Private Repo Access 2 | 3 | CodeCov defaults to only requesting access to public repos 4 | when you connect up with your github account. 5 | This document show how to enable the "private scope" 6 | so you can see private repos. 7 | 8 | ## Starting Point 9 | 10 | Start by logging into CodeCov. The first time you log in, 11 | you will need to authenticate with your GitHub account. 12 | Eventually you should end up at a screen that looks like this: 13 | 14 | [codecov.io/gh/time-loop](https://codecov.io/gh/time-loop) 15 | 16 | ![codecov before private scope](codecov-add-private.jpg) 17 | 18 | You need to click on the `add private` link, 19 | and then go through what looks like a repeat of the 20 | github auth process, except it will say "Codecov is requesting additional permissions". Click your way through. Once you're done, you will see a `syncing` icon: 21 | 22 | ![codecov syncing](codecov-syncing.jpg) 23 | 24 | In a few moments, you should see all the private repos appear. 25 | 26 | ![codecov with private](codecov-with-private.jpg) 27 | -------------------------------------------------------------------------------- /docs/codecov/codecov-add-private.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/time-loop/clickup-projen/6fb13f7f0be68f13090d001ea898d12df526999c/docs/codecov/codecov-add-private.jpg -------------------------------------------------------------------------------- /docs/codecov/codecov-syncing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/time-loop/clickup-projen/6fb13f7f0be68f13090d001ea898d12df526999c/docs/codecov/codecov-syncing.jpg -------------------------------------------------------------------------------- /docs/codecov/codecov-with-private.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/time-loop/clickup-projen/6fb13f7f0be68f13090d001ea898d12df526999c/docs/codecov/codecov-with-private.jpg -------------------------------------------------------------------------------- /docs/datadog-service-catalog/README.md: -------------------------------------------------------------------------------- 1 | # datadog-service-catalog feature 2 | 3 | This feature is deprecated. Please see https://github.com/time-loop/terraform-target-datadog-account/blob/main/docs/service-catalog.md -------------------------------------------------------------------------------- /docs/node-upgrade/README.md: -------------------------------------------------------------------------------- 1 | # Node Upgrade 2 | 3 | How to upgrade your node to a version greater than the default. 4 | Default is in `/src/utils/parameters.ts PROJEN_NODE_VERSION`. 5 | 6 | ## WARNING 7 | 8 | You could use this to downgrade your node version. 9 | There are no checks in place to prevent this. 10 | Do so at your own risk and make sure you know what you are doing. 11 | 12 | ## Steps 13 | 14 | 1. Update your `.projenrc.ts` with `workflowNodeVersion: '20.11.1'` or whatever version of node you want to use. 15 | ![.projenrc.ts](./projenrcts.png) 16 | 1. Run `npx projen`, `git add .`, and `git commit -m "feat: upgrade node to v20.11.1"` as appropriate. Push and merge. 17 | 18 | ## Troubleshooting 19 | 20 | Make sure you are running at least [v0.0.168](https://github.com/time-loop/clickup-projen/releases/tag/v0.0.168) of this library. 21 | To upgrade this library (as well as core `projen`), use the `update-projen-main` workflow: ![update-projen-main](./update-projen-main.png) 22 | 23 | The `.nvmrc` decides which version of node is used by your `cdkPipeline`. The `.github/workflows` are managed directly by projen so it's pretty easy to see when they've been upgraded. 24 | -------------------------------------------------------------------------------- /docs/node-upgrade/projenrcts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/time-loop/clickup-projen/6fb13f7f0be68f13090d001ea898d12df526999c/docs/node-upgrade/projenrcts.png -------------------------------------------------------------------------------- /docs/node-upgrade/update-projen-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/time-loop/clickup-projen/6fb13f7f0be68f13090d001ea898d12df526999c/docs/node-upgrade/update-projen-main.png -------------------------------------------------------------------------------- /docs/renovate/README.md: -------------------------------------------------------------------------------- 1 | # Renovate at ClickUp 2 | 3 | By default our TypeScript repos have Renovate enabled to manage dependencies. 4 | This document describes the default configuration and some common use patterns. 5 | 6 | ## Default Configuration 7 | 8 | The configuration is defined in `renovate.json5` and defaults to the following. 9 | 10 | ```json 11 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 12 | { 13 | "labels": [ 14 | "renovate" 15 | ], 16 | "schedule": [ 17 | "before 1am on Monday" 18 | ], 19 | "extends": [ 20 | "config:base", 21 | "group:recommended", 22 | "group:monorepos", 23 | "github>whitesource/merge-confidence:beta" 24 | ], 25 | "packageRules": [ 26 | { 27 | "groupName": "all non-major dependencies", 28 | "groupSlug": "all-minor-patch", 29 | "matchPackagePatterns": [ 30 | "*" 31 | ], 32 | "matchUpdateTypes": [ 33 | "minor", 34 | "patch" 35 | ], 36 | "addLabels": [] 37 | }, 38 | { 39 | "matchDepTypes": [ 40 | "optionalDependencies" 41 | ], 42 | "addLabels": [ 43 | "optional" 44 | ] 45 | } 46 | ], 47 | "ignoreDeps": [ 48 | "@time-loop/cdk-log-parser", 49 | "@types/jest", 50 | "@types/node", 51 | "@typescript-eslint/eslint-plugin", 52 | "@typescript-eslint/parser", 53 | "aws-cdk", 54 | "eslint", 55 | "jest-junit", 56 | "jest", 57 | "npm-check-updates", 58 | "standard-version", 59 | "ts-jest", 60 | "ts-node", 61 | "aws-cdk-lib", 62 | "constructs", 63 | "node" 64 | ], 65 | "rangeStrategy": "bump", 66 | "prHourlyLimit": 0, 67 | "prConcurrentLimit": 0, 68 | "automergeType": "pr", 69 | "platformAutomerge": true 70 | } 71 | ``` 72 | 73 | You can tune literally all of these options, 74 | but the defaults are a good starting point. 75 | See [the projen docs](http://projen.io/api/API.html#projen-renovatebotoptions) 76 | for a starting point on how to customize. 77 | 78 | ## Enabling AutoMerge for Updates 79 | 80 | To enable this, in your `.projenrc.ts` add the following: 81 | 82 | ```ts 83 | autoApproveUpgrades: true, 84 | autoApproveOptions: { 85 | allowedUsernames: [renovateWorkflow.RENOVATE_GITHUB_USERNAME], 86 | label: renovateWorkflow.AUTO_APPROVE_PR_LABEL, 87 | }, 88 | ``` 89 | 90 | ## Pinning Problem Libraries 91 | 92 | You may only pin libraries if you have created a task 93 | to address the issue in the future. 94 | 95 | - Your PR to pin MUST reference the task. 96 | - Your pinning code MUST include a link to the task. 97 | - Your squad SHOULD have a plan to address the 98 | associated tech debt in the next quarter. 99 | 100 | Example 101 | [task](https://staging.clickup.com/t/333/CLK-211432), 102 | [pr](https://github.com/time-loop/click/pull/89), 103 | [comment](https://github.com/time-loop/click/pull/89/files#diff-bda22a3de7550efc76dd29e2a427a8d5abd5863f6e96a339ff47390d9bed7d14R42) 104 | 105 | Example code from `.projenrc.ts` in the PR. 106 | 107 | ```ts 108 | renovatebotOptions: { 109 | // See https://staging.clickup.com/t/333/CLK-211432 110 | // tl;dr: we get the following error when we touch the oclif libs. 111 | // 112 | // 👾 build » compile | tsc --build 113 | // Error: node_modules/@oclif/parser/lib/index.d.ts(16,35): error TS2344: Type 'TFlags' does not satisfy the constraint 'Output'. 114 | // Error: node_modules/@oclif/parser/lib/index.d.ts(16,52): error TS2344: Type 'TFlags' does not satisfy the constraint 'OutputFlags'. 115 | // Error: node_modules/@oclif/command/lib/command.d.ts(70,31): error TS2344: Type 'F' does not satisfy the constraint 'Output'. 116 | // Error: node_modules/@oclif/command/lib/command.d.ts(70,67): error TS2344: Type 'F' does not satisfy the constraint 'OutputFlags'. 117 | // 👾 Task "build » compile" failed when executing "tsc --build" (cwd: /home/runner/work/click/click) 118 | // Error: Process completed with exit code 1. 119 | ignore: [ 120 | '@oclif/core', 121 | '@oclif/command', 122 | '@oclif/config', 123 | '@oclif/plugin-help', 124 | '@oclif/plugin-not-found', 125 | '@oclif/test', 126 | 'oclif', 127 | ], 128 | }, 129 | 130 | ``` 131 | -------------------------------------------------------------------------------- /docs/slack-notifications/README.md: -------------------------------------------------------------------------------- 1 | # Slack notifications 2 | 3 | For compliance purposes we need alerts in slack for all CDK repo changes. By default, all `clickup-projen` repos will post alerts for all commits to the `main` branch of your CDK repo to the [`#eng-cdk-release-alerts` slack channel](https://clickup.enterprise.slack.com/archives/C04KH6EKMJ5). 4 | 5 | ## Customising the slack webhook channel 6 | 7 | If you would like to customise the slack channel your repos alerts get posted to, then you can do the following: 8 | 1. Create your alert channel in slack 9 | 2. Setup a [new Slack webhook](https://api.slack.com/apps/AB50VMKMF/incoming-webhooks?) for your channel. If you don't have access to this slack app, you can ask the eng-prod or IT team to create the webhook for you and share it via 1password. 10 | 3. Add your new webhook URL to your repos action secrets with the secret name `PROJEN_RELEASE_SLACK_WEBHOOK` (it should override the organisation level secret) 11 | 12 | ## Customising the slack webhook title and body 13 | 14 | If you would like to override the [default message title and / or body](https://github.com/time-loop/clickup-projen/blob/03ffb318426fc10d31a4267aa4143bf9000263ec/src/slack-alert.ts#L77-L82) posted in the slack alert, you can set the following configuration in your repos `.projenrc.ts`: 15 | ``` 16 | // .projenrc.ts 17 | const project = new clickupCdk.ClickUpCdkTypeScriptApp({ 18 | ...rest of props, 19 | sendSlackWebhookOnReleaseOpts: { 20 | messageTitle: 'My custom message title', 21 | messageBody: 'My custom message body', 22 | } 23 | }); 24 | ``` 25 | 26 | Then run `npx projen` and commit the changes. 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@time-loop/clickup-projen", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/time-loop/clickup-projen.git" 6 | }, 7 | "scripts": { 8 | "build": "npx projen build", 9 | "bump": "npx projen bump", 10 | "clobber": "npx projen clobber", 11 | "compat": "npx projen compat", 12 | "compile": "npx projen compile", 13 | "default": "npx projen default", 14 | "docgen": "npx projen docgen", 15 | "eject": "npx projen eject", 16 | "eslint": "npx projen eslint", 17 | "package": "npx projen package", 18 | "package-all": "npx projen package-all", 19 | "package:js": "npx projen package:js", 20 | "post-compile": "npx projen post-compile", 21 | "post-upgrade": "npx projen post-upgrade", 22 | "pre-compile": "npx projen pre-compile", 23 | "release": "npx projen release", 24 | "test": "npx projen test", 25 | "test:watch": "npx projen test:watch", 26 | "unbump": "npx projen unbump", 27 | "update-projen": "npx projen update-projen", 28 | "watch": "npx projen watch", 29 | "projen": "npx projen" 30 | }, 31 | "author": { 32 | "name": "ClickUp DevOps", 33 | "email": "devops@clickup.com", 34 | "organization": true 35 | }, 36 | "devDependencies": { 37 | "@types/jest": "^29.5.14", 38 | "@types/node": "^18", 39 | "@types/semver": "^7.7.0", 40 | "@typescript-eslint/eslint-plugin": "^8", 41 | "@typescript-eslint/parser": "^8", 42 | "commit-and-tag-version": "^12", 43 | "constructs": "^10.0.0", 44 | "eslint": "^9", 45 | "eslint-config-prettier": "^9.1.0", 46 | "eslint-import-resolver-typescript": "^3.10.1", 47 | "eslint-plugin-import": "^2.31.0", 48 | "eslint-plugin-prettier": "^5.4.1", 49 | "jest": "^29.7.0", 50 | "jest-junit": "^16", 51 | "jsii": "~5.8.0", 52 | "jsii-diff": "^1.112.0", 53 | "jsii-docgen": "^10.5.0", 54 | "jsii-pacmak": "^1.112.0", 55 | "jsii-rosetta": "~5.8.0", 56 | "prettier": "^3.5.3", 57 | "projen": "^0.92.8", 58 | "ts-jest": "^29.3.4", 59 | "ts-node": "^10.9.2", 60 | "typescript": "^5.8.3" 61 | }, 62 | "peerDependencies": { 63 | "projen": "^0.92.8" 64 | }, 65 | "dependencies": { 66 | "cson-parser": "^4.0.9", 67 | "projen": "^0.92.8", 68 | "semver": "^7.7.2", 69 | "ts-deepmerge": "^6.2.1" 70 | }, 71 | "bundledDependencies": [ 72 | "cson-parser", 73 | "semver", 74 | "ts-deepmerge" 75 | ], 76 | "pnpm": {}, 77 | "engines": { 78 | "node": ">= 18.17.1" 79 | }, 80 | "main": "lib/index.js", 81 | "license": "Apache-2.0", 82 | "publishConfig": { 83 | "registry": "https://npm.pkg.github.com/", 84 | "access": "public" 85 | }, 86 | "version": "0.0.0", 87 | "jest": { 88 | "coverageProvider": "v8", 89 | "collectCoverageFrom": [ 90 | "src/**/*.ts" 91 | ], 92 | "testMatch": [ 93 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 94 | "/@(src|test)/**/__tests__/**/*.ts?(x)", 95 | "/@(projenrc)/**/*(*.)@(spec|test).ts?(x)", 96 | "/@(projenrc)/**/__tests__/**/*.ts?(x)" 97 | ], 98 | "clearMocks": true, 99 | "collectCoverage": true, 100 | "coverageReporters": [ 101 | "json", 102 | "lcov", 103 | "clover", 104 | "cobertura", 105 | "text" 106 | ], 107 | "coverageDirectory": "coverage", 108 | "coveragePathIgnorePatterns": [ 109 | "/node_modules/" 110 | ], 111 | "testPathIgnorePatterns": [ 112 | "/node_modules/" 113 | ], 114 | "watchPathIgnorePatterns": [ 115 | "/node_modules/" 116 | ], 117 | "reporters": [ 118 | "default", 119 | [ 120 | "jest-junit", 121 | { 122 | "outputDirectory": "test-reports" 123 | } 124 | ] 125 | ], 126 | "transform": { 127 | "^.+\\.[t]sx?$": [ 128 | "ts-jest", 129 | { 130 | "tsconfig": "tsconfig.dev.json" 131 | } 132 | ] 133 | } 134 | }, 135 | "types": "lib/index.d.ts", 136 | "stability": "stable", 137 | "jsii": { 138 | "outdir": "dist", 139 | "targets": {}, 140 | "tsc": { 141 | "outDir": "lib", 142 | "rootDir": "src" 143 | } 144 | }, 145 | "packageManager": "pnpm@9.15.5", 146 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 147 | } 148 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "labels": [ 4 | "renovate" 5 | ], 6 | "schedule": [ 7 | "before 2am on Monday" 8 | ], 9 | "extends": [ 10 | "config:base", 11 | "group:recommended", 12 | "group:monorepos", 13 | "github>whitesource/merge-confidence:beta" 14 | ], 15 | "packageRules": [ 16 | { 17 | "groupName": "all non-major dependencies", 18 | "groupSlug": "all-minor-patch", 19 | "matchPackagePatterns": [ 20 | "*" 21 | ], 22 | "excludePackagePatterns": [ 23 | "^@time-loop\\/clickup-projen" 24 | ], 25 | "matchUpdateTypes": [ 26 | "minor", 27 | "patch" 28 | ], 29 | "automerge": true, 30 | "addLabels": [ 31 | "auto-approve" 32 | ] 33 | }, 34 | { 35 | "matchDepTypes": [ 36 | "optionalDependencies" 37 | ], 38 | "addLabels": [ 39 | "optional" 40 | ] 41 | }, 42 | { 43 | "matchPackagePatterns": [ 44 | "^@time-loop\\/clickup-projen" 45 | ], 46 | "allowedVersions": "!/^[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?-(alpha|beta).*$/" 47 | }, 48 | { 49 | "matchPackagePrefixes": [ 50 | "@time-loop/" 51 | ], 52 | "matchUpdateTypes": [ 53 | "major" 54 | ], 55 | "prBodyNotes": [ 56 | "# DANGER WILL ROBINSON!!!", 57 | "Applying major version updates without understanding what they do has caused outages at ClickUp. Don't be that guy. Read the release notes. The release notes are in a link buried in the rollup below. Sorry, unfortunately Renovate does not offer a reasonable way to expose them here. If in doubt, escalate to CloudPlatform.", 58 | "CloudPlatform thanks you for being diligent with your library updates!" 59 | ] 60 | } 61 | ], 62 | "ignoreDeps": [ 63 | "@types/node", 64 | "@typescript-eslint/eslint-plugin", 65 | "@typescript-eslint/parser", 66 | "commit-and-tag-version", 67 | "constructs", 68 | "eslint", 69 | "jest-junit", 70 | "jsii-docgen", 71 | "jsii-rosetta", 72 | "jsii", 73 | "node", 74 | "@time-loop/clickup-projen", 75 | "pnpm", 76 | "projen" 77 | ], 78 | "rangeStrategy": "bump", 79 | "prHourlyLimit": 0, 80 | "prConcurrentLimit": 0, 81 | "automergeType": "pr", 82 | "platformAutomerge": true 83 | } 84 | -------------------------------------------------------------------------------- /src/add-to-project.ts: -------------------------------------------------------------------------------- 1 | import { typescript, YamlFile } from 'projen'; 2 | 3 | export module addToProjectWorkflow { 4 | export const RENOVATE_GITHUB_USERNAME = 'cu-infra-svc-git'; 5 | 6 | export const DEFAULT_RENOVATE_PR_LABEL = 'renovate'; 7 | export const GITHUB_PROJECT_NUMBER = '3'; 8 | 9 | const defaultWorkflow = { 10 | name: 'add-to-project', 11 | on: { 12 | pull_request: { 13 | types: ['opened', 'labeled'], 14 | }, 15 | }, 16 | concurrency: { 17 | group: '${{ github.workflow }}-${{ github.ref_name }}', 18 | }, 19 | jobs: { 20 | 'add-to-project': { 21 | 'runs-on': 'ubuntu-latest', 22 | permissions: { 23 | contents: 'read', 24 | }, 25 | steps: [ 26 | { 27 | name: 'Add to project', 28 | uses: 'actions/add-to-project@v0.4.1', 29 | with: { 30 | // github project url 31 | 'project-url': `https://github.com/orgs/time-loop/projects/${GITHUB_PROJECT_NUMBER}`, 32 | 'github-token': '${{ secrets.RENOVATEBOT_GITHUB_TOKEN }}', 33 | labeled: DEFAULT_RENOVATE_PR_LABEL, 34 | }, 35 | }, 36 | ], 37 | }, 38 | }, 39 | }; 40 | 41 | export function addAddToProjectWorkflowYml(project: typescript.TypeScriptProject, override?: any): void { 42 | new YamlFile(project, '.github/workflows/add-to-project.yml', { obj: { ...defaultWorkflow, ...override } }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/cdk-context-json.ts: -------------------------------------------------------------------------------- 1 | import { awscdk, JsonPatch, SampleFile } from 'projen'; 2 | 3 | export module cdkContextJson { 4 | export interface InjectionOptions { 5 | /** 6 | * The account number where the role the GH actions will assume lives. 7 | * This is usually the same account running cdk-pipelines, 8 | * but can be any account which has been configured with 9 | * 10 | * ```ts 11 | * cdk bootstrap --trust-for-lookup $this_account_number ... 12 | * ``` 13 | */ 14 | readonly lookupAccountId: string; 15 | /** 16 | * Which region should GH Auth into? 17 | * @default 'us-west-2' 18 | */ 19 | readonly awsRegion?: string; 20 | /** 21 | * How long should the requested role be valid. 22 | * @default 900 15min seems pretty generous. 23 | */ 24 | readonly roleDurationSeconds?: number; 25 | } 26 | 27 | /** 28 | * Options to support cdk.context.json management 29 | */ 30 | export interface Options { 31 | /** 32 | * @default - do not create stack SampleCode for OIDC role 33 | */ 34 | readonly createOidcRoleStack?: boolean; 35 | /** 36 | * You must configure this if you want to self-mutation cdk.context.json. 37 | * 38 | * @default - do not modify the build workflow to authorize against AWS. 39 | */ 40 | readonly injectionOptions?: InjectionOptions; 41 | } 42 | 43 | export function injectAwsAuthIntoBuild(project: awscdk.AwsCdkTypeScriptApp, options: InjectionOptions): void { 44 | const build = project.tryFindObjectFile('.github/workflows/build.yml'); 45 | build?.addOverride('jobs.build.permissions', { 'id-token': 'write' }); 46 | build?.patch( 47 | JsonPatch.add('/jobs/build/steps/0', { 48 | name: 'Configure AWS Credentials', 49 | uses: 'aws-actions/configure-aws-credentials@v2', 50 | with: { 51 | 'aws-region': options.awsRegion ?? 'us-west-2', 52 | 'role-to-assume': `arn:aws:iam::${options.lookupAccountId}:role/${project.name}-github-actions-role`, 53 | 'role-duration-seconds': options.roleDurationSeconds ?? 900, 54 | }, 55 | }), 56 | ); 57 | } 58 | 59 | export function addOidcRoleStack(project: awscdk.AwsCdkTypeScriptApp): void { 60 | new SampleFile(project, `${project.srcdir}/github-actions-oidc-cdk-context-lookup-role.ts`, { 61 | contents: `import { core } from '@time-loop/cdk-library'; 62 | import { aws_iam, Stage } from 'aws-cdk-lib'; 63 | import { Construct } from 'constructs'; 64 | import { Namer } from 'multi-convention-namer'; 65 | 66 | export class GitHubActionsOIDCCdkContextLookupRole extends core.Stack { 67 | static asStage(scope: Construct, stageName: string, stageProps: core.StageProps): Stage { 68 | return new (class extends Stage { 69 | constructor() { 70 | super(scope, stageName, stageProps); 71 | new GitHubActionsOIDCCdkContextLookupRole(this, stageProps); 72 | } 73 | })(); 74 | } 75 | constructor(scope: Construct, props: core.StackProps) { 76 | const projectName = '${project.name}'; 77 | // NOTE: you should never deploy this anywhere but the cdkPipelines account 78 | // And the GitHubActionsOIDCPermissions class for cdk-diff should never be deployed 79 | // in the cdkPipelines account. Name collision is intentional here. 80 | let id = new Namer([...projectName.split('-'), 'github', 'actions']); 81 | super(scope, id, props); 82 | 83 | const githubActionsRoleName = id.addSuffix(['role']).kebab; 84 | const githubActionsRole = new aws_iam.Role(this, githubActionsRoleName, { 85 | roleName: githubActionsRoleName, 86 | assumedBy: new aws_iam.FederatedPrincipal( 87 | \`arn:aws:iam::\${this.account}:oidc-provider/token.actions.githubusercontent.com\`, 88 | { 89 | StringEquals: { 90 | 'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com', 91 | }, 92 | StringLike: { 93 | 'token.actions.githubusercontent.com:sub': \`repo:time-loop/\${projectName}:*\`, 94 | }, 95 | }, 96 | 'sts:AssumeRoleWithWebIdentity', 97 | ), 98 | }); 99 | 100 | const githubActionsPolicyName = id.addSuffix(['policy']).kebab; 101 | const githubActionsPolicy = new aws_iam.Policy(this, githubActionsPolicyName, { 102 | policyName: githubActionsPolicyName, 103 | statements: [ 104 | new aws_iam.PolicyStatement({ 105 | actions: [ 'sts:AssumeRole' ], 106 | resources: [ 'arn:aws:iam::*:role/cdk-*lookup-role*' ], 107 | }), 108 | ], 109 | }); 110 | 111 | // Attach IAM policy to IAM role 112 | githubActionsPolicy.attachToRole(githubActionsRole); 113 | } 114 | } 115 | `, 116 | }); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/clickup-ts.ts: -------------------------------------------------------------------------------- 1 | import { javascript, typescript, Component, JsonFile } from 'projen'; 2 | import { NodePackage } from 'projen/lib/javascript'; 3 | import merge from 'ts-deepmerge'; 4 | import { addToProjectWorkflow } from './add-to-project'; 5 | import { codecov } from './codecov'; 6 | import { nodeVersion } from './node-version'; 7 | import { renovateWorkflow } from './renovate-workflow'; 8 | import { slackAlert } from './slack-alert'; 9 | import { updateProjen } from './update-projen'; 10 | import { parameters } from './utils/parameters'; 11 | 12 | export module clickupTs { 13 | // This is not included in defaults because other projects may not always want to require it. 14 | export const deps = ['@time-loop/clickup-projen']; 15 | 16 | export const devDeps = ['esbuild', 'eslint-config-prettier', 'eslint-plugin-prettier', 'jsii-release', 'prettier']; 17 | 18 | // We need to be picky about our typing here. 19 | // We don't want a default name 20 | // (you'd think we could deduce it from the github repo name, but that won't work with CodeBuild checkouts by default) 21 | // We need to also get rid of the `| undefined` to make deep merge happy. 22 | export const defaults: Omit & { 23 | authorAddress: string; 24 | authorName: string; 25 | } = { 26 | authorAddress: 'devops@clickup.com', 27 | authorName: 'ClickUp', 28 | authorOrganization: true, 29 | licensed: false, 30 | defaultReleaseBranch: 'main', 31 | 32 | release: true, 33 | releaseToNpm: true, 34 | npmRegistryUrl: 'https://npm.pkg.github.com', 35 | 36 | devDeps, 37 | 38 | depsUpgrade: true, 39 | depsUpgradeOptions: { 40 | workflow: false, 41 | }, 42 | autoApproveUpgrades: true, 43 | autoApproveOptions: { 44 | allowedUsernames: [renovateWorkflow.RENOVATE_GITHUB_USERNAME], 45 | label: renovateWorkflow.AUTO_APPROVE_PR_LABEL, 46 | }, 47 | renovatebot: true, 48 | workflowBootstrapSteps: [ 49 | { 50 | name: 'GitHub Packages authorization', 51 | // This does some env var obverloading where the release step also defines NPM_TOKEN with the action token that allows uploading 52 | env: { NPM_TOKEN: '${{ secrets.ALL_PACKAGE_READ_TOKEN }}' }, 53 | run: [ 54 | 'cat > ~/.npmrc <; 126 | /** 127 | * The file path at which to create the Typedoc config file. 128 | * @default 'typedoc.json' 129 | */ 130 | readonly configFilePath?: string; 131 | /** 132 | * Whether to generate the documentation in rendered HTML as opposed to 133 | * the Markdown format. 134 | * @default false 135 | */ 136 | readonly html?: boolean; 137 | } 138 | 139 | /** 140 | * Adds a simple Typescript documentation generator utilizing `typedoc`. 141 | * Adds the necessary dependencies and automatic doc generation task during 142 | * post-compile phase. 143 | * 144 | * Do not use with JSII projects. 145 | */ 146 | class TypedocDocgen extends Component { 147 | readonly configFile: JsonFile; 148 | 149 | constructor(project: ClickUpTypeScriptProject, props: TypedocDocgenOptions) { 150 | super(project); 151 | this.configFile = this.createConfig(project, props); 152 | this.createTask(project, props); 153 | } 154 | 155 | private createConfig(project: ClickUpTypeScriptProject, props: TypedocDocgenOptions): JsonFile { 156 | const configFileDefaults: Record = { 157 | $schema: 'https://typedoc.org/schema.json', 158 | entryPoints: ['./src/index.ts'], 159 | out: project.docsDirectory, 160 | readme: 'none', 161 | plugin: props.html ? [] : ['typedoc-plugin-markdown'], 162 | }; 163 | return new JsonFile(project, props.configFilePath ?? 'typedoc.json', { 164 | obj: { 165 | ...configFileDefaults, 166 | ...props.configFileContents, // Overrides 167 | disableSources: true, // Cannot be overriden or commit loops are encountered 168 | }, 169 | marker: false, // Disables additional Projen marker since it will not function 170 | }); 171 | } 172 | 173 | private createTask(project: ClickUpTypeScriptProject, props: TypedocDocgenOptions): void { 174 | // Any plugins with name `typedocplugin`, `typedoc-plugin`, or `typedoc-theme` 175 | // are automatically loaded when typedoc executes. 176 | const plugins: string[] = props.html ? [] : ['typedoc-plugin-markdown']; 177 | project.addDevDeps('typedoc', ...plugins); 178 | 179 | // Create automatic documentation generation task... 180 | const docgen = project.addTask('typedocDocgen', { 181 | description: `Generate TypeScript API reference to ${project.docsDirectory} directory`, 182 | exec: `typedoc`, 183 | }); 184 | 185 | // ...and have it run after a successful compile 186 | project.postCompileTask.spawn(docgen); 187 | } 188 | } 189 | 190 | export interface ClickUpTypeScriptProjectOptions 191 | extends typescript.TypeScriptProjectOptions, 192 | slackAlert.SendSlackOptions { 193 | /** 194 | * Additional options pertaining to the typedoc config file. 195 | * NOTE: `docgen` attribute cannot be false. 196 | */ 197 | readonly docgenOptions?: TypedocDocgenOptions; 198 | 199 | /** 200 | * Email address for project author 201 | */ 202 | readonly authorAddress?: string; 203 | 204 | readonly renovateOptionsConfig?: renovateWorkflow.RenovateOptionsConfig; 205 | } 206 | 207 | /** 208 | * ClickUp standardized TypeScript Project 209 | * 210 | * Includes: 211 | * - default author information 212 | * - default proprietary license 213 | * - default release build configuration 214 | * - default linting and codecov configuration 215 | * - default minNodeVersion: '14.17.0' 216 | * - default devDeps (you can add your own, but the base will always be present) 217 | * 218 | * Note that for GitHub Packages to work, the package has to be scoped into the `@time-loop` project. 219 | * We handle that automatically. 220 | */ 221 | export class ClickUpTypeScriptProject extends typescript.TypeScriptProject { 222 | constructor(options: ClickUpTypeScriptProjectOptions) { 223 | const mergedOptions = merge(defaults, { deps }, options, { 224 | // Disable projen's built-in docgen class 225 | docgen: undefined, 226 | name: normalizeName(options.name), 227 | renovatebotOptions: renovateWorkflow.getRenovateOptions(options.renovateOptionsConfig), 228 | }); 229 | super(mergedOptions); 230 | fixTsNodeDeps(this.package); 231 | codecov.addCodeCovYml(this); 232 | nodeVersion.addNodeVersionFile(this, { nodeVersion: mergedOptions.workflowNodeVersion }); 233 | renovateWorkflow.addRenovateWorkflowYml(this); 234 | addToProjectWorkflow.addAddToProjectWorkflowYml(this); 235 | updateProjen.addWorkflows(this); 236 | if (options.docgen ?? true) new TypedocDocgen(this, options.docgenOptions ?? {}); 237 | if (options.sendSlackWebhookOnRelease !== false) { 238 | slackAlert.addReleaseEvent(this, options.sendSlackWebhookOnReleaseOpts); 239 | } 240 | 241 | if (this.package.packageManager === javascript.NodePackageManager.PNPM) { 242 | // Automate part of https://app.clickup-stg.com/333/v/dc/ad-757629/ad-3577645 243 | this.package.addField('packageManager', `pnpm@${parameters.PROJEN_PNPM_VERSION}`); 244 | // necessary to allow minor/patch version updates of pnpm on dev boxes 245 | this.npmrc.addConfig('package-manager-strict', 'false'); 246 | // pnpm will manage the version of the package manager (pnpm) 247 | this.npmrc.addConfig('manage-package-manager-versions', 'true'); 248 | // pnpm checks this value before running commands and will use (and install if missing) the specified version 249 | this.npmrc.addConfig('use-node-version', options.workflowNodeVersion ?? parameters.PROJEN_NODE_VERSION); 250 | } 251 | } 252 | } 253 | 254 | /** 255 | * Resolves resolveTypeReferenceDirective error. It can't be done in 256 | * clickupTs.devDeps (something in TypeScriptProject base class constructor 257 | * gives clickupTs.devDeps lower priority than to the deps derived from other 258 | * packages where ts-node is mentioned?). 259 | */ 260 | export function fixTsNodeDeps(pkg: NodePackage) { 261 | pkg.addDevDeps('ts-node@^10'); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/codecov-bypass-workflow.ts: -------------------------------------------------------------------------------- 1 | import { YamlFile } from 'projen'; 2 | import { clickupCdk } from './clickup-cdk'; 3 | 4 | const CODECOV_GITHUB_APP_ID = 254; 5 | 6 | export module codecovBypassWorkflow { 7 | function createCodecovBypassWorkflow(options?: CodecovBypassOptionsConfig) { 8 | const defaultWorkflow = { 9 | name: options?.workflowName || 'Code coverage increased', 10 | on: { 11 | check_suite: { 12 | types: ['completed'], 13 | }, 14 | pull_request: { 15 | types: ['labeled', 'unlabeled'], 16 | }, 17 | }, 18 | permissions: {}, 19 | jobs: { 20 | 'code-coverage-increased': { 21 | 'runs-on': 'ubuntu-latest', 22 | if: `github.event_name != \'check_suite\' || github.event.check_suite.app.id == ${CODECOV_GITHUB_APP_ID}`, 23 | steps: [ 24 | { 25 | name: 'Check codecov results', 26 | uses: 'time-loop/github-actions/dist/wrap-check-suite@wrap-check-suite+0.3.2', 27 | env: { 28 | FORCE_COLOR: 3, 29 | }, 30 | with: { 31 | 'github-app-id': options?.githubAppId || '${{ vars.CODECOV_GITHUB_APP_ID }}', 32 | 'github-private-key': options?.githubAppPrivateKey || '${{ secrets.CODECOV_GITHUB_APP_PRIVATE_KEY }}', 33 | 'skip-label': options?.skipLabel || 'code coverage not required', 34 | 'check-name': options?.checkName || 'Code coverage increased', 35 | 'check-suite-app-id': CODECOV_GITHUB_APP_ID, 36 | 'check-suite-check-names': options?.checkSuiteCheckNames?.join(',') || 'codecov/patch,codecov/project', 37 | 'details-url': 38 | options?.detailsUrl || 'https://app.codecov.io/gh/${{ github.repository }}/pull/<% prNumber %>', 39 | }, 40 | }, 41 | ], 42 | }, 43 | }, 44 | }; 45 | 46 | return defaultWorkflow; 47 | } 48 | 49 | export interface CodecovBypassOptionsConfig { 50 | /** 51 | * GitHub App ID 52 | * @default '${{ vars.CODECOV_GITHUB_APP_ID }}' 53 | */ 54 | readonly githubAppId?: string; 55 | 56 | /** 57 | * GitHub App Private Key 58 | * @default '${{ secrets.CODECOV_GITHUB_APP_PRIVATE_KEY }}' 59 | */ 60 | readonly githubAppPrivateKey?: string; 61 | 62 | /** 63 | * Skip creating (using) the workflow 64 | * @default false 65 | */ 66 | readonly disabled?: boolean; 67 | 68 | /** 69 | * Workflow Name 70 | * @default 'Code coverage increased' 71 | */ 72 | readonly workflowName?: string; 73 | 74 | /** 75 | * Skip Label 76 | * @default 'code coverage not required' 77 | */ 78 | readonly skipLabel?: string; 79 | 80 | /** 81 | * Check Name 82 | * @default 'Code coverage increased' 83 | */ 84 | readonly checkName?: string; 85 | 86 | /** 87 | * Check Suite Check Names 88 | * @default ['codecov/patch', 'codecov/project'] 89 | */ 90 | readonly checkSuiteCheckNames?: string[]; 91 | 92 | /** 93 | * Details URL 94 | * @default 'https://app.codecov.io/gh/${{ github.repository }}/pull/<% prNumber %>' 95 | */ 96 | readonly detailsUrl?: string; 97 | } 98 | 99 | export function getCodecovBypassOptions(options?: CodecovBypassOptionsConfig) { 100 | return options; 101 | } 102 | 103 | export function addCodecovBypassWorkflowYml( 104 | project: clickupCdk.ClickUpCdkTypeScriptApp, 105 | options?: CodecovBypassOptionsConfig, 106 | override?: any, 107 | ): void { 108 | if (options?.disabled) { 109 | return; 110 | } 111 | new YamlFile(project, '.github/workflows/codecov-bypass.yml', { 112 | obj: { ...createCodecovBypassWorkflow(options), ...override }, 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/codecov.ts: -------------------------------------------------------------------------------- 1 | import { typescript, YamlFile } from 'projen'; 2 | 3 | export module codecov { 4 | export const defaultCodecov = { 5 | coverage: { 6 | precision: 2, 7 | round: 'down', 8 | status: { 9 | project: { 10 | // Controls for the entire project 11 | default: { 12 | target: 'auto', 13 | threshold: '10%', // Allow total coverage to drop by this much while still succeeding. 14 | paths: ['src'], 15 | if_ci_failed: 'error', 16 | only_pulls: true, 17 | }, 18 | }, 19 | // Controls for just the code changed by the PR 20 | patch: { 21 | default: { 22 | base: 'auto', 23 | target: 'auto', 24 | threshold: '10%', 25 | paths: ['src'], 26 | if_ci_failed: 'error', 27 | only_pulls: true, 28 | }, 29 | }, 30 | }, 31 | }, 32 | parsers: { 33 | gcov: { 34 | branch_detection: { 35 | conditional: 'yes', 36 | loop: 'yes', 37 | method: 'no', 38 | macro: 'no', 39 | }, 40 | }, 41 | }, 42 | comment: { 43 | layout: 'reach,diff,flags,files,footer', 44 | behavior: 'default', 45 | require_changes: 'no', 46 | }, 47 | }; 48 | 49 | /** 50 | * Add a `codecov.yml` file to the project. 51 | * @param project the project for which to create / add a codecov.yml file 52 | * @param override changes to apply to the defaultCodecov. WARNING, this is a simple inline'd override, no deep merges. 53 | */ 54 | export function addCodeCovYml(project: typescript.TypeScriptProject, override?: any): void { 55 | new YamlFile(project, 'codecov.yml', { obj: { ...defaultCodecov, ...override } }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/datadog-service-catalog.ts: -------------------------------------------------------------------------------- 1 | import { JobPermission, JobStep } from 'projen/lib/github/workflows-model'; 2 | import { NodeProject } from 'projen/lib/javascript'; 3 | 4 | export module datadogServiceCatalog { 5 | export enum DefaultServiceCatalogValues { 6 | SERVICE_VERSION = 'v2.1', 7 | DATADOG_HOSTNAME = 'app.datadoghq.com', 8 | DATADOG_KEY = '${{ secrets.DD_PROJEN_RELEASE_API_KEY }}', 9 | DATADOG_APP_KEY = '${{ secrets.DD_PROJEN_RELEASE_APP_KEY }}', 10 | SLACK_SUPPORT_CHANNEL = 'https://click-up.slack.com/archives/C043T0JBJKY', 11 | TIER = 'low', 12 | NOT_PROVIDED = 'Not Provided', 13 | } 14 | export interface ServiceInfo { 15 | /** 16 | * The name of the service. This must be unique across all services. 17 | * @default project.name 18 | */ 19 | readonly serviceName?: string; 20 | /** 21 | * Some details on what this service does 22 | * @default 'Not Provided' 23 | */ 24 | readonly description?: string; 25 | /** 26 | * The application/product that this service assists 27 | * @default 'Not Provided' 28 | */ 29 | readonly application?: string; 30 | /** 31 | * Which squad owns this service 32 | */ 33 | readonly team?: string; 34 | /** 35 | * Where is this service in the development cycle (development, staging, production, deprecated) 36 | * @default 'Not Provided' 37 | */ 38 | readonly lifecycle?: string; 39 | /** 40 | * How important is this service for business functionality (low, medium, high, critical) 41 | * @default 'low' 42 | */ 43 | readonly tier?: string; 44 | /** 45 | * The PagerDuty URL for the service. 46 | * @default 'Not Provided' 47 | */ 48 | readonly pagerdutyUrl?: string; 49 | } 50 | export enum ContactType { 51 | EMAIL = 'email', 52 | SLACK = 'slack', 53 | MICROSOFT_TEAMS = 'microsoft-teams', 54 | } 55 | export interface ContactInfo { 56 | /** 57 | * The name of the contact. 58 | */ 59 | readonly name: string; 60 | /** 61 | * The type of the contact. Acceptable values are: email, slack, and microsoft-teams 62 | */ 63 | readonly type: ContactType; 64 | /** 65 | * The actual contact information for the contact. For example, if the type is email, this would be the email address. 66 | */ 67 | readonly contact: string; 68 | } 69 | export enum LinkType { 70 | DOC = 'doc', 71 | RUNBOOK = 'runbook', 72 | REPO = 'repo', 73 | DASHBOARD = 'dashboard', 74 | OTHER = 'other', 75 | } 76 | export interface LinkInfo { 77 | /** 78 | * The name of the link. 79 | */ 80 | readonly name: string; 81 | /** 82 | * The type for the link. Acceptable values are: 'doc', 'runbook', 'repo', 'dashboard', and 'other' 83 | */ 84 | readonly type: LinkType; 85 | /** 86 | * The URL of the link. 87 | */ 88 | readonly url: string; 89 | } 90 | 91 | /** 92 | * Service Catalog Options. 93 | */ 94 | export interface ServiceCatalogOptions { 95 | /** 96 | * Information about the services that will be published to the service catalog. 97 | */ 98 | readonly serviceInfo?: ServiceInfo[]; 99 | /** 100 | * The list of contacts for the service. Each of these contacts is an object with the following properties: name, type, and contact. 101 | * @default undefined 102 | */ 103 | readonly contacts?: ContactInfo[]; 104 | /** 105 | * A list of links associated with the service. Each of these links is an object with the following properties: name, type, and url. 106 | * @default undefined 107 | */ 108 | readonly links?: LinkInfo[]; 109 | /** 110 | * The list of tags that are associated with the service. 111 | * @default undefined 112 | */ 113 | readonly serviceTags?: Record; 114 | } 115 | 116 | /** 117 | * Adds 'Send to Datadog Service Catalog' job to the release workflow. 118 | * @deprecated Please see https://github.com/time-loop/terraform-target-datadog-account/blob/main/docs/service-catalog.md 119 | * @param project The NodeProject to which the release event workflow will be added 120 | * @param options Service information that will be included in the DD Service Catalog. 121 | */ 122 | export function addServiceCatalogEvent(project: NodeProject, options: ServiceCatalogOptions) { 123 | project.release?.addJobs({ 124 | send_service_catalog: { 125 | name: 'Send to Datadog Service Catalog', 126 | permissions: { 127 | contents: JobPermission.READ, 128 | }, 129 | runsOn: ['ubuntu-latest'], 130 | needs: ['release'], 131 | if: 'needs.release.outputs.latest_commit == github.sha', 132 | steps: [ 133 | { 134 | name: 'Download build artifacts', 135 | uses: 'actions/download-artifact@v4', 136 | with: { 137 | name: 'build-artifact', 138 | path: project.release!.artifactsDirectory, 139 | }, 140 | }, 141 | { 142 | name: 'Get version', 143 | id: 'event_metadata', 144 | run: `echo "release_tag=$(cat ${project.release!.artifactsDirectory}/releasetag.txt)" >> $GITHUB_OUTPUT`, 145 | }, 146 | ...createServiceCatalogJobSteps(project, options), 147 | ], 148 | }, 149 | }); 150 | } 151 | 152 | /** 153 | * Creates one JobStep for each serviceInfo record in the ServiceCatalogOptions. 154 | * 155 | * @param project 156 | * @param options 157 | * @returns 158 | */ 159 | function createServiceCatalogJobSteps(project: NodeProject, options: ServiceCatalogOptions): JobStep[] { 160 | let steps: JobStep[] = []; 161 | const serviceTags = mergeServiceTags(project, options.serviceTags); 162 | for (const serviceInfo of options.serviceInfo ?? []) { 163 | const serviceName = serviceInfo?.serviceName ?? project.name; 164 | 165 | const contacts = `${options.contacts?.map( 166 | (contact) => ` 167 | - type: ${contact.type} 168 | contact: ${contact.contact} 169 | name: ${contact.name}`, 170 | )}`; 171 | 172 | const links = `${options.links?.map( 173 | (link) => ` 174 | - type: ${link.type} 175 | url: ${link.url} 176 | name: ${link.name}`, 177 | )}`; 178 | 179 | const tags = `${Object.keys(serviceTags).map( 180 | (key) => ` 181 | - ${key}:${serviceTags[key]}`, 182 | )}`; 183 | 184 | const step: JobStep = { 185 | name: `Publish DD Service Catalog for ${serviceName}`, 186 | uses: 'arcxp/datadog-service-catalog-metadata-provider@v2', 187 | with: { 188 | 'schema-version': `${DefaultServiceCatalogValues.SERVICE_VERSION}`, 189 | 'datadog-hostname': `${DefaultServiceCatalogValues.DATADOG_HOSTNAME}`, 190 | 'datadog-key': `${DefaultServiceCatalogValues.DATADOG_KEY}`, 191 | 'datadog-app-key': `${DefaultServiceCatalogValues.DATADOG_APP_KEY}`, 192 | 'service-name': `${serviceName}`, 193 | description: `${serviceInfo.description ?? DefaultServiceCatalogValues.NOT_PROVIDED}`, 194 | application: `${serviceInfo.application ?? DefaultServiceCatalogValues.NOT_PROVIDED}`, 195 | tier: `${serviceInfo.tier ?? DefaultServiceCatalogValues.TIER}`, 196 | lifecycle: `${serviceInfo.lifecycle ?? DefaultServiceCatalogValues.NOT_PROVIDED}`, 197 | team: `${serviceInfo.team ?? DefaultServiceCatalogValues.NOT_PROVIDED}`, 198 | 'slack-support-channel': `${DefaultServiceCatalogValues.SLACK_SUPPORT_CHANNEL}`, 199 | contacts, 200 | links, 201 | tags, 202 | }, 203 | }; 204 | steps.push(step); 205 | } 206 | return steps; 207 | } 208 | 209 | /** 210 | * return the service tags that will be send to Datadog. 211 | * 212 | * @param project 213 | * @param userTags 214 | * @returns 215 | */ 216 | function mergeServiceTags(project: NodeProject, userTags?: Record): Record { 217 | const defaultTags: Record = { 218 | project: project.name, 219 | release: 'true', 220 | version: '${{ steps.event_metadata.outputs.release_tag }}', 221 | actor: '${{ github.actor }}', 222 | }; 223 | return { ...defaultTags, ...userTags }; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/datadog.ts: -------------------------------------------------------------------------------- 1 | import { stringify } from 'cson-parser'; 2 | import { JobPermission } from 'projen/lib/github/workflows-model'; 3 | import { NodeProject } from 'projen/lib/javascript'; 4 | 5 | export module datadog { 6 | export interface ReleaseEventTags { 7 | /** 8 | * @jsii ignore 9 | */ 10 | [key: string]: string; 11 | } 12 | 13 | /** 14 | * Options to set for the event sent to Datadog on release. 15 | */ 16 | export interface ReleaseEventOptions { 17 | /** 18 | * @default secrets.DD_PROJEN_RELEASE_API_KEY 19 | */ 20 | readonly datadogApiKey?: string; 21 | /** 22 | * @default The release repo and semantically versioned release number 23 | */ 24 | readonly eventTitle?: string; 25 | /** 26 | * @default The release repo and semantically versioned release number 27 | */ 28 | readonly eventText?: string; 29 | /** 30 | * @default normal 31 | */ 32 | readonly eventPriority?: 'normal' | 'low'; 33 | /** 34 | * @default true 35 | */ 36 | readonly datadogUs?: boolean; 37 | /** 38 | * Additional tags to append to the standard tags (project, release, version, actor) 39 | * @default undefined 40 | */ 41 | readonly eventTags?: ReleaseEventTags; 42 | } 43 | 44 | /** 45 | * When passed in ReleaseEventOptions is rendered, this interface is the result 46 | * which is passed directly to the GitHub Action inputs. JSII does not like 47 | * snake_case exposed key names, hence the snake_case changes here in the 48 | * unexposed interface. All types are the same as ReleaseEventOptions except 49 | * where noted. 50 | * Based on: https://github.com/Glennmen/datadog-event-action/releases/tag/1.1.0 51 | */ 52 | interface ReleaseEventActionInputs { 53 | readonly datadog_api_key: string; 54 | readonly event_title: string; 55 | readonly event_text: string; 56 | readonly event_priority: 'normal' | 'low'; 57 | readonly datadog_us: boolean; 58 | /** 59 | * Formatted as an array of stringified, colon delimited key:value pairs. 60 | * Example: '["Key1:Val1", "Key2:Val2"]' 61 | */ 62 | readonly event_tags: string; 63 | } 64 | 65 | /** 66 | * Adds a 'send Datadog release event' job to the release workflow, if it exists. 67 | * Uses the DD_PROJEN_RELEASE_API_KEY for authentication to Datadog. 68 | * 69 | * @param project The NodeProject to which the release event workflow will be added 70 | * @param opts Optional properties to send along with the DD release event 71 | */ 72 | export function addReleaseEvent(project: NodeProject, opts?: ReleaseEventOptions) { 73 | project.release?.addJobs({ 74 | send_release_event: { 75 | name: 'Send Release Event', 76 | permissions: { 77 | contents: JobPermission.READ, 78 | }, 79 | runsOn: ['ubuntu-latest'], 80 | needs: ['release'], 81 | if: 'needs.release.outputs.latest_commit == github.sha', 82 | env: { 83 | CI: 'true', 84 | }, 85 | steps: [ 86 | { 87 | name: 'Download build artifacts', 88 | uses: 'actions/download-artifact@v4', 89 | with: { 90 | name: 'build-artifact', 91 | path: project.release!.artifactsDirectory, 92 | }, 93 | }, 94 | { 95 | name: 'Get version', 96 | id: 'event_metadata', 97 | run: `echo "release_tag=$(cat ${project.release!.artifactsDirectory}/releasetag.txt)" >> $GITHUB_OUTPUT`, 98 | }, 99 | { 100 | name: 'Send Datadog event', 101 | // https://github.com/Glennmen/datadog-event-action/releases/tag/1.1.0 102 | uses: 'Glennmen/datadog-event-action@fb18624879901f1ff0c3c7e1e102179793bfe948', 103 | with: setReleaseEventInputs(project, opts), 104 | }, 105 | ], 106 | }, 107 | }); 108 | } 109 | 110 | function parseReleaseEventTags(tags: ReleaseEventTags): string { 111 | const tagsArr = Object.keys(tags).map((key) => `${key}:${tags[key]}`); 112 | return stringify(tagsArr); 113 | } 114 | 115 | function setReleaseEventInputs(project: NodeProject, opts?: ReleaseEventOptions): ReleaseEventActionInputs { 116 | const defaultTags: ReleaseEventTags = { 117 | project: project.name, 118 | release: 'true', 119 | version: '${{ steps.event_metadata.outputs.release_tag }}', 120 | actor: '${{ github.actor }}', 121 | }; 122 | const rendered: ReleaseEventActionInputs = { 123 | datadog_api_key: opts?.datadogApiKey ?? '${{ secrets.DD_PROJEN_RELEASE_API_KEY }}', 124 | datadog_us: opts?.datadogUs ?? true, 125 | event_title: 126 | opts?.eventTitle ?? `Released ${project.name} version \${{ steps.event_metadata.outputs.release_tag }}`, 127 | event_text: 128 | opts?.eventText ?? `Released ${project.name} version \${{ steps.event_metadata.outputs.release_tag }}`, 129 | event_priority: opts?.eventPriority ?? 'normal', 130 | event_tags: parseReleaseEventTags({ ...defaultTags, ...opts?.eventTags }), 131 | }; 132 | return rendered; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './add-to-project'; 2 | export * from './cdk-context-json'; 3 | export * from './cdk-diff-workflow'; 4 | export * from './clickup-cdk'; 5 | export * from './clickup-ts'; 6 | export * from './datadog'; 7 | export * from './datadog-service-catalog'; 8 | export * from './renovate-workflow'; 9 | export * from './slack-alert'; 10 | export * from './optional-node-version'; 11 | export * from './codecov-bypass-workflow'; 12 | -------------------------------------------------------------------------------- /src/node-version.ts: -------------------------------------------------------------------------------- 1 | import { TextFile, typescript } from 'projen'; 2 | import { OptionalNodeVersion } from './optional-node-version'; 3 | import { parameters } from './utils/parameters'; 4 | 5 | export module nodeVersion { 6 | /** 7 | * Add a `.nvmrc` file to the project. 8 | * @param project the project for which to create / add a .nvmrc file 9 | */ 10 | export function addNodeVersionFile(project: typescript.TypeScriptProject, options?: OptionalNodeVersion): void { 11 | new TextFile(project, '.nvmrc', { 12 | lines: [options?.nodeVersion ?? parameters.PROJEN_NODE_VERSION], 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/optional-node-version.ts: -------------------------------------------------------------------------------- 1 | export interface OptionalNodeVersion { 2 | /** 3 | * Specify a nodeVersion. 4 | * @default - should be parameters.PROJEN_NODE_VERSION 5 | */ 6 | readonly nodeVersion?: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/renovate-workflow.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-require-imports -- impossible for this to work with npx projen build otherwise 2 | import assert = require('node:assert'); 3 | import { typescript, YamlFile, RenovatebotOptions } from 'projen'; 4 | import merge from 'ts-deepmerge'; 5 | 6 | export module renovateWorkflow { 7 | export const RENOVATE_GITHUB_USERNAME = 'cu-infra-svc-git'; 8 | 9 | export const AUTO_APPROVE_PR_LABEL = 'auto-approve'; 10 | export const DEFAULT_RENOVATE_PR_LABEL = 'renovate'; 11 | export const OPTIONAL_RENOVATE_PR_LABEL = 'optional'; 12 | 13 | function getDefaultWorkflow(cronSchedule: string) { 14 | return { 15 | name: 'upgrade-main', 16 | on: { 17 | schedule: [ 18 | { 19 | cron: cronSchedule, 20 | }, 21 | ], 22 | workflow_dispatch: {}, 23 | // Run immediately on any changes to the renovate config or workflow 24 | push: { 25 | branches: ['main'], 26 | paths: ['renovate.json5', '.github/workflows/renovate.yml'], 27 | }, 28 | // Run immediately when a box to update a dependency is checked on the renovate dashboard issue 29 | issues: { 30 | types: ['edited'], 31 | }, 32 | // or when the rebase now checkbox is ticked on a renovate PR description 33 | pull_request: { 34 | types: ['edited'], 35 | }, 36 | }, 37 | // Prevent renovate running multiple times in parallel due to several subsequent trigger events occurring 38 | concurrency: { 39 | group: "${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.event_name || '' }}", 40 | }, 41 | jobs: { 42 | 'upgrade-main': { 43 | 'runs-on': 'ubuntu-latest', 44 | permissions: { 45 | contents: 'read', 46 | }, 47 | steps: [ 48 | { 49 | name: 'Checkout', 50 | uses: 'actions/checkout@v4', 51 | }, 52 | { 53 | name: 'Self-hosted Renovate', 54 | // NOTE: this doesn't get us onto node22 for renovatebot, it's running 20.17.16 55 | uses: 'renovatebot/github-action@v41.0.12', // We might want to un-pin this or figure out a renovate process for it. 56 | // Skip running renovate in a loop when renovate updates the dependency dashboard issue and re-triggers this workflow 57 | if: `(github.event_name != 'issues' && github.event_name != 'pull_request') || github.actor != '${RENOVATE_GITHUB_USERNAME}'`, 58 | with: { 59 | // projen creates this config for us 60 | configurationFile: 'renovate.json5', 61 | // Run projen as the cu-infra-svc-git user 62 | // We cannot use the default GITHUB_TOKEN to auth as explained here: 63 | // https://github.com/renovatebot/github-action#token 64 | token: 'x-access-token:${{ secrets.RENOVATEBOT_GITHUB_TOKEN }}', 65 | }, 66 | env: { 67 | // Private github packages auth 68 | RENOVATE_HOST_RULES: 69 | '[{"matchHost":"https://npm.pkg.github.com/","hostType":"npm","token":"${{ secrets.ALL_PACKAGE_READ_TOKEN }}"}]', 70 | RENOVATE_NPMRC: '@${{ github.repository_owner }}:registry=https://npm.pkg.github.com', 71 | // Only update npm dependencies for now 72 | RENOVATE_ENABLED_MANAGERS: '["npm"]', 73 | // Tell renovate to run on this repo, without this renovate won't run 74 | RENOVATE_REPOSITORIES: '["${{ github.repository }}"]', 75 | // Bypass schedule when the workflow is manually triggered, 76 | // otherwise the user has to then go and check the box on the dependency dashboard to 77 | // receive updates and this is kind of confusing 78 | RENOVATE_FORCE: "${{ github.event_name == 'workflow_dispatch' && '{\"schedule\":null}' || '' }}", 79 | }, 80 | }, 81 | ], 82 | }, 83 | }, 84 | }; 85 | } 86 | 87 | export interface RenovateOptionsConfig { 88 | /** 89 | * Whether to auto merge non breaking dependency updates. 90 | * 91 | * Note: if you have the "Require review from Code Owners" option enabled in the branch protection rules this will not work 92 | */ 93 | readonly autoMergeNonBreakingUpdates?: boolean; 94 | 95 | /** 96 | * Allows overriding any renovate config option default. This is deep merged into the default config 97 | */ 98 | readonly defaultOverrides?: RenovatebotOptions; 99 | } 100 | 101 | export function getRenovateOptions(options: RenovateOptionsConfig = {}) { 102 | return merge( 103 | { 104 | scheduleInterval: ['before 2am on Monday'], 105 | ignoreProjen: true, 106 | ignore: [ 107 | 'node', // managed by projen 108 | '@time-loop/clickup-projen', // managed as part of the projen upgrade workflow 109 | 'pnpm', // managed by projen 110 | ], 111 | overrideConfig: { 112 | /* override projen renovate defaults */ 113 | // Remove :preserveSemverRanges preset added by projen to make renovate update all non breaking dependencies 114 | extends: [ 115 | 'config:base', 116 | 'group:recommended', 117 | 'group:monorepos', 118 | // Add merge confidence columns to update PRs 119 | 'github>whitesource/merge-confidence:beta', 120 | ], 121 | packageRules: [ 122 | { 123 | // copied from this preset: https://docs.renovatebot.com/presets-group/#groupallnonmajor 124 | groupName: 'all non-major dependencies', 125 | groupSlug: 'all-minor-patch', 126 | matchPackagePatterns: ['*'], 127 | excludePackagePatterns: ['^@time-loop\\/clickup-projen'], 128 | matchUpdateTypes: ['minor', 'patch'], 129 | // Tell renovate to enable github's auto merge feature on the PR 130 | automerge: options.autoMergeNonBreakingUpdates ? true : undefined, 131 | // Adding the auto-approve label will make projens auto approve workflow approve the PR so it will be auto merged 132 | addLabels: [options.autoMergeNonBreakingUpdates ? AUTO_APPROVE_PR_LABEL : undefined], 133 | }, 134 | { 135 | matchDepTypes: ['optionalDependencies'], 136 | addLabels: [OPTIONAL_RENOVATE_PR_LABEL], 137 | }, 138 | { 139 | matchPackagePatterns: ['^@time-loop\\/clickup-projen'], 140 | // Bypass prerelease versions: 141 | // https://docs.renovatebot.com/configuration-options/#allowedversions 142 | // Ex: 1.1.1 is allowed, 1.1.1-beta.0 is not allowed. 143 | allowedVersions: '!/^[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?-(alpha|beta).*$/', 144 | }, 145 | { 146 | matchPackagePrefixes: ['@time-loop/'], 147 | matchUpdateTypes: ['major'], 148 | prBodyNotes: [ 149 | '# DANGER WILL ROBINSON!!!', 150 | "Applying major version updates without understanding what they do has caused outages at ClickUp. Don't be that guy. Read the release notes. The release notes are in a link buried in the rollup below. Sorry, unfortunately Renovate does not offer a reasonable way to expose them here. If in doubt, escalate to CloudPlatform.", 151 | 'CloudPlatform thanks you for being diligent with your library updates!', 152 | ], 153 | }, 154 | ], 155 | 156 | /* override defaults set in config:base preset */ 157 | // update all dependencies, not just major versions 158 | rangeStrategy: 'bump', 159 | // Create PRs for all updates in one go as we only run renovate once a week 160 | // as this option is designed for when renovate runs hourly 161 | prHourlyLimit: 0, 162 | // Have no limit on the number of renovate PRs open 163 | prConcurrentLimit: 0, 164 | // Always create PRs for auto merging to make required status checks run and provide an audit trail 165 | automergeType: 'pr', 166 | // Use github's auto merge feature and not renovate's built in alternative 167 | platformAutomerge: true, 168 | }, 169 | }, 170 | options.defaultOverrides ?? { 171 | labels: [DEFAULT_RENOVATE_PR_LABEL], 172 | }, 173 | ); 174 | } 175 | 176 | export function addRenovateWorkflowYml(project: typescript.TypeScriptProject, override?: any): void { 177 | new YamlFile(project, '.github/workflows/renovate.yml', { 178 | obj: { ...getDefaultWorkflow(getWorkflowCronSchedule(project.name)), ...override }, 179 | }); 180 | } 181 | } 182 | 183 | /** 184 | * As we have a lot of CDK repos, we want to spread out the renovate schedules over the first hour of the day 185 | * so they don't all run at the same time and end up failing due to github rate limiting the account used by renovate. 186 | * @param projectName - the CDK project name. Used so the calculated offset is deterministic for a given project. 187 | */ 188 | function getWorkflowCronSchedule(projectName: string): string { 189 | const projectNameHash = getProjectNameHash(projectName); 190 | const MINUTES_IN_HOUR = 60; 191 | const minute = projectNameHash % MINUTES_IN_HOUR; 192 | const MINUTE_GROUP_INTERVAL = 5; 193 | // Round to nearest 5 minutes to guarantee that previous renovate runs on other repos have finished 194 | const groupedMinute = Math.floor(minute / MINUTE_GROUP_INTERVAL) * MINUTE_GROUP_INTERVAL; 195 | assert( 196 | groupedMinute < MINUTES_IN_HOUR, 197 | `Renovate schedule minute should be less than ${MINUTES_IN_HOUR} but was ${groupedMinute} for project ${projectName}`, 198 | ); 199 | // Run daily to update the renovate dependency dashboard issue with new updates 200 | return `${groupedMinute} 0 * * MON-FRI`; 201 | } 202 | 203 | /** 204 | * Get a deterministic hash for a given project name 205 | * @param projectName 206 | */ 207 | function getProjectNameHash(projectName: string): number { 208 | return projectName.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); 209 | } 210 | -------------------------------------------------------------------------------- /src/slack-alert.ts: -------------------------------------------------------------------------------- 1 | import { JobPermission } from 'projen/lib/github/workflows-model'; 2 | import { NodeProject } from 'projen/lib/javascript'; 3 | 4 | export module slackAlert { 5 | /** 6 | * Options to set for the event sent to Slack on release. 7 | */ 8 | export interface ReleaseEventOptions { 9 | /** 10 | * @default secrets.PROJEN_RELEASE_SLACK_WEBHOOK 11 | */ 12 | readonly webhook?: string; 13 | 14 | /** 15 | * Override the default slack message title 16 | */ 17 | readonly messageTitle?: string; 18 | 19 | /** 20 | * Override the default slack message body 21 | */ 22 | readonly messageBody?: string; 23 | } 24 | 25 | /** 26 | * Options to add to interface for projects that wish to off send slack functionality. 27 | */ 28 | export interface SendSlackOptions { 29 | /** 30 | * Should we send a slack webhook on release (required for compliance audits) 31 | * 32 | * @default true 33 | */ 34 | readonly sendSlackWebhookOnRelease?: boolean; 35 | 36 | /** 37 | * Slack alert on release options. Only valid when `sendSlackWebhookOnRelease` is true. 38 | */ 39 | readonly sendSlackWebhookOnReleaseOpts?: slackAlert.ReleaseEventOptions; 40 | } 41 | 42 | /** 43 | * Adds a 'Send Release Alert to Slack' job to the release workflow, if it exists. 44 | * Uses the PROJEN_RELEASE_SLACK_WEBHOOK for authentication to Datadog. 45 | * 46 | * @param project The NodeProject to which the release event workflow will be added 47 | * @param opts Optional properties to send along with the DD release event 48 | */ 49 | export function addReleaseEvent(project: NodeProject, opts?: ReleaseEventOptions) { 50 | project.release?.addJobs({ 51 | send_release_event_to_slack: { 52 | name: 'Send Release Alert to Slack', 53 | permissions: { 54 | contents: JobPermission.READ, 55 | }, 56 | runsOn: ['ubuntu-latest'], 57 | needs: ['release'], 58 | if: 'needs.release.outputs.latest_commit == github.sha', 59 | steps: [ 60 | { 61 | name: 'Download build artifacts', 62 | uses: 'actions/download-artifact@v4', 63 | with: { 64 | name: 'build-artifact', 65 | path: project.release!.artifactsDirectory, 66 | }, 67 | }, 68 | { 69 | name: 'Get version', 70 | id: 'event_metadata', 71 | run: `echo "release_tag=$(cat ${project.release!.artifactsDirectory}/releasetag.txt)" >> $GITHUB_OUTPUT`, 72 | }, 73 | { 74 | name: 'Send Slack webhook event', 75 | uses: 'rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7', 76 | env: { 77 | SLACK_TITLE: 78 | opts?.messageTitle ?? 79 | '${{ github.repository }}@${{ steps.event_metadata.outputs.release_tag }} released!', 80 | SLACK_MESSAGE: 81 | opts?.messageBody ?? 82 | 'View the release notes here: https://github.com/${{ github.repository }}/releases/tag/${{ steps.event_metadata.outputs.release_tag }}', 83 | SLACK_WEBHOOK: opts?.webhook ?? '${{ secrets.PROJEN_RELEASE_SLACK_WEBHOOK }}', 84 | SLACK_FOOTER: '', 85 | SLACK_COLOR: 'success', 86 | MSG_MINIMAL: 'true', 87 | }, 88 | }, 89 | ], 90 | }, 91 | }); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/update-projen.ts: -------------------------------------------------------------------------------- 1 | import { YamlFile } from 'projen'; 2 | import { UpgradeDependencies, UpgradeDependenciesSchedule } from 'projen/lib/javascript'; 3 | import { TypeScriptProject } from 'projen/lib/typescript'; 4 | 5 | export module updateProjen { 6 | export const UPDATE_PROJEN_PR_LABEL = 'update-projen'; 7 | export const ADD_UPDATE_PROJEN_PROJECT_JOB_NAME = 'add-to-update-projen-project'; 8 | export const GITHUB_UPDATE_PROJEN_PROJECT_NUMBER = '6'; 9 | 10 | const addToProjectWorkflow = { 11 | name: ADD_UPDATE_PROJEN_PROJECT_JOB_NAME, 12 | on: { 13 | pull_request: { 14 | types: ['opened', 'labeled'], 15 | }, 16 | }, 17 | concurrency: { 18 | group: '${{ github.workflow }}-${{ github.ref_name }}', 19 | }, 20 | jobs: { 21 | // Can't use a variable here?!? 22 | 'add-to-update-projen-project': { 23 | 'runs-on': 'ubuntu-latest', 24 | permissions: { 25 | contents: 'read', 26 | }, 27 | steps: [ 28 | { 29 | name: 'Add to project', 30 | uses: 'actions/add-to-project@v0.4.1', 31 | with: { 32 | // github project url 33 | 'project-url': `https://github.com/orgs/time-loop/projects/${GITHUB_UPDATE_PROJEN_PROJECT_NUMBER}`, 34 | 'github-token': '${{ secrets.RENOVATEBOT_GITHUB_TOKEN }}', 35 | labeled: UPDATE_PROJEN_PR_LABEL, 36 | }, 37 | }, 38 | ], 39 | }, 40 | }, 41 | }; 42 | 43 | export function addWorkflows(project: TypeScriptProject, override?: any): void { 44 | new UpgradeDependencies(project, { 45 | include: ['projen', '@time-loop/clickup-projen'], 46 | pullRequestTitle: 'upgrade projen', 47 | semanticCommit: 'fix', // let's get this into the changelog. 48 | taskName: UPDATE_PROJEN_PR_LABEL, 49 | // Defaults to minor otherwise which means if we ship a breaking change to projen it never gets propagated out 50 | target: 'latest', 51 | workflowOptions: { 52 | schedule: UpgradeDependenciesSchedule.MONTHLY, 53 | labels: [UPDATE_PROJEN_PR_LABEL], 54 | }, 55 | }); 56 | 57 | new YamlFile(project, `.github/workflows/${ADD_UPDATE_PROJEN_PROJECT_JOB_NAME}.yml`, { 58 | obj: { ...addToProjectWorkflow, ...override }, 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/parameters.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Currently we just use these to track node versions. 3 | * https://nodejs.org/en/about/previous-releases 4 | */ 5 | export class parameters { 6 | // This should always be the oldest version which is still in maintenance. 7 | // This will ONLY drive the package.json minimum node version 8 | static PROJEN_MIN_ENGINE_NODE_VERSION: string = '18.17.1'; 9 | 10 | // This should be updated occasionally to the latest release of the current active version. 11 | // This will drive all the other node versions throughout the system. 12 | static PROJEN_NODE_VERSION: string = '22.14.0'; 13 | 14 | // This should be updated occasionally to the latest release of the current active version. 15 | static PROJEN_PNPM_VERSION: string = '9.15.5'; 16 | } 17 | -------------------------------------------------------------------------------- /test/__snapshots__/add-to-project.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addAddToProjectWorkflowYml file added 1`] = ` 4 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 5 | 6 | name: add-to-project 7 | on: 8 | pull_request: 9 | types: 10 | - opened 11 | - labeled 12 | concurrency: 13 | group: \${{ github.workflow }}-\${{ github.ref_name }} 14 | jobs: 15 | add-to-project: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | steps: 20 | - name: Add to project 21 | uses: actions/add-to-project@v0.4.1 22 | with: 23 | project-url: https://github.com/orgs/time-loop/projects/3 24 | github-token: \${{ secrets.RENOVATEBOT_GITHUB_TOKEN }} 25 | labeled: renovate 26 | " 27 | `; 28 | 29 | exports[`addAddToProjectWorkflowYml override 1`] = ` 30 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 31 | 32 | name: add-to-project 33 | on: 34 | pull_request: 35 | types: 36 | - opened 37 | - labeled 38 | concurrency: 39 | group: \${{ github.workflow }}-\${{ github.ref_name }} 40 | jobs: 41 | add-to-project: 42 | runs-on: ubuntu-latest 43 | permissions: 44 | contents: read 45 | steps: 46 | - name: Add to project 47 | uses: actions/add-to-project@v0.4.1 48 | with: 49 | project-url: https://github.com/orgs/time-loop/projects/3 50 | github-token: \${{ secrets.RENOVATEBOT_GITHUB_TOKEN }} 51 | labeled: renovate 52 | foo: bar 53 | " 54 | `; 55 | -------------------------------------------------------------------------------- /test/__snapshots__/cdk-context-json.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addOidcRoleStack adds stack definition 1`] = ` 4 | "import { core } from '@time-loop/cdk-library'; 5 | import { aws_iam, Stage } from 'aws-cdk-lib'; 6 | import { Construct } from 'constructs'; 7 | import { Namer } from 'multi-convention-namer'; 8 | 9 | export class GitHubActionsOIDCCdkContextLookupRole extends core.Stack { 10 | static asStage(scope: Construct, stageName: string, stageProps: core.StageProps): Stage { 11 | return new (class extends Stage { 12 | constructor() { 13 | super(scope, stageName, stageProps); 14 | new GitHubActionsOIDCCdkContextLookupRole(this, stageProps); 15 | } 16 | })(); 17 | } 18 | constructor(scope: Construct, props: core.StackProps) { 19 | const projectName = 'test'; 20 | // NOTE: you should never deploy this anywhere but the cdkPipelines account 21 | // And the GitHubActionsOIDCPermissions class for cdk-diff should never be deployed 22 | // in the cdkPipelines account. Name collision is intentional here. 23 | let id = new Namer([...projectName.split('-'), 'github', 'actions']); 24 | super(scope, id, props); 25 | 26 | const githubActionsRoleName = id.addSuffix(['role']).kebab; 27 | const githubActionsRole = new aws_iam.Role(this, githubActionsRoleName, { 28 | roleName: githubActionsRoleName, 29 | assumedBy: new aws_iam.FederatedPrincipal( 30 | \`arn:aws:iam::\${this.account}:oidc-provider/token.actions.githubusercontent.com\`, 31 | { 32 | StringEquals: { 33 | 'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com', 34 | }, 35 | StringLike: { 36 | 'token.actions.githubusercontent.com:sub': \`repo:time-loop/\${projectName}:*\`, 37 | }, 38 | }, 39 | 'sts:AssumeRoleWithWebIdentity', 40 | ), 41 | }); 42 | 43 | const githubActionsPolicyName = id.addSuffix(['policy']).kebab; 44 | const githubActionsPolicy = new aws_iam.Policy(this, githubActionsPolicyName, { 45 | policyName: githubActionsPolicyName, 46 | statements: [ 47 | new aws_iam.PolicyStatement({ 48 | actions: [ 'sts:AssumeRole' ], 49 | resources: [ 'arn:aws:iam::*:role/cdk-*lookup-role*' ], 50 | }), 51 | ], 52 | }); 53 | 54 | // Attach IAM policy to IAM role 55 | githubActionsPolicy.attachToRole(githubActionsRole); 56 | } 57 | } 58 | " 59 | `; 60 | 61 | exports[`injectAwsAuthIntoBuild updates build WF 1`] = ` 62 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 63 | 64 | name: build 65 | on: 66 | pull_request: {} 67 | workflow_dispatch: {} 68 | jobs: 69 | build: 70 | runs-on: ubuntu-latest 71 | permissions: 72 | contents: write 73 | id-token: write 74 | outputs: 75 | self_mutation_happened: \${{ steps.self_mutation.outputs.self_mutation_happened }} 76 | env: 77 | CI: "true" 78 | steps: 79 | - name: Configure AWS Credentials 80 | uses: aws-actions/configure-aws-credentials@v2 81 | with: 82 | aws-region: us-west-2 83 | role-to-assume: arn:aws:iam::123412341234:role/test-github-actions-role 84 | role-duration-seconds: 900 85 | - name: Checkout 86 | uses: actions/checkout@v4 87 | with: 88 | ref: \${{ github.event.pull_request.head.ref }} 89 | repository: \${{ github.event.pull_request.head.repo.full_name }} 90 | - name: Install dependencies 91 | run: yarn install --check-files 92 | - name: build 93 | run: npx projen build 94 | - name: Find mutations 95 | id: self_mutation 96 | run: |- 97 | git add . 98 | git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 99 | working-directory: ./ 100 | - name: Upload patch 101 | if: steps.self_mutation.outputs.self_mutation_happened 102 | uses: actions/upload-artifact@v4.4.0 103 | with: 104 | name: repo.patch 105 | path: repo.patch 106 | overwrite: true 107 | - name: Fail build on mutation 108 | if: steps.self_mutation.outputs.self_mutation_happened 109 | run: |- 110 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 111 | cat repo.patch 112 | exit 1 113 | self-mutation: 114 | needs: build 115 | runs-on: ubuntu-latest 116 | permissions: 117 | contents: write 118 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 119 | steps: 120 | - name: Checkout 121 | uses: actions/checkout@v4 122 | with: 123 | token: \${{ secrets.PROJEN_GITHUB_TOKEN }} 124 | ref: \${{ github.event.pull_request.head.ref }} 125 | repository: \${{ github.event.pull_request.head.repo.full_name }} 126 | - name: Download patch 127 | uses: actions/download-artifact@v4 128 | with: 129 | name: repo.patch 130 | path: \${{ runner.temp }} 131 | - name: Apply patch 132 | run: '[ -s \${{ runner.temp }}/repo.patch ] && git apply \${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 133 | - name: Set git identity 134 | run: |- 135 | git config user.name "github-actions" 136 | git config user.email "github-actions@github.com" 137 | - name: Push changes 138 | env: 139 | PULL_REQUEST_REF: \${{ github.event.pull_request.head.ref }} 140 | run: |- 141 | git add . 142 | git commit -s -m "chore: self mutation" 143 | git push origin HEAD:$PULL_REQUEST_REF 144 | " 145 | `; 146 | -------------------------------------------------------------------------------- /test/__snapshots__/clickup-ts.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ClickUpTypeScriptProject defaults .github/workflows/release.yml 1`] = ` 4 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 5 | 6 | name: release 7 | on: 8 | push: 9 | branches: 10 | - main 11 | workflow_dispatch: {} 12 | concurrency: 13 | group: \${{ github.workflow }} 14 | cancel-in-progress: false 15 | jobs: 16 | release: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | outputs: 21 | latest_commit: \${{ steps.git_remote.outputs.latest_commit }} 22 | tag_exists: \${{ steps.check_tag_exists.outputs.exists }} 23 | env: 24 | CI: "true" 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | - name: Set git identity 31 | run: |- 32 | git config user.name "github-actions" 33 | git config user.email "github-actions@github.com" 34 | - name: GitHub Packages authorization 35 | env: 36 | NPM_TOKEN: \${{ secrets.ALL_PACKAGE_READ_TOKEN }} 37 | run: |- 38 | cat > ~/.npmrc <> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 62 | cat $GITHUB_OUTPUT 63 | - name: Check for new commits 64 | id: git_remote 65 | run: |- 66 | echo "latest_commit=$(git ls-remote origin -h \${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 67 | cat $GITHUB_OUTPUT 68 | - name: Backup artifact permissions 69 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 70 | run: cd dist && getfacl -R . > permissions-backup.acl 71 | continue-on-error: true 72 | - name: Upload artifact 73 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 74 | uses: actions/upload-artifact@v4.4.0 75 | with: 76 | name: build-artifact 77 | path: dist 78 | overwrite: true 79 | release_github: 80 | name: Publish to GitHub Releases 81 | needs: 82 | - release 83 | - release_npm 84 | runs-on: ubuntu-latest 85 | permissions: 86 | contents: write 87 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 88 | steps: 89 | - uses: actions/setup-node@v4 90 | with: 91 | node-version: 22.14.0 92 | - name: Download build artifacts 93 | uses: actions/download-artifact@v4 94 | with: 95 | name: build-artifact 96 | path: dist 97 | - name: Restore build artifact permissions 98 | run: cd dist && setfacl --restore=permissions-backup.acl 99 | continue-on-error: true 100 | - name: Release 101 | env: 102 | GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} 103 | 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 104 | release_npm: 105 | name: Publish to npm 106 | needs: release 107 | runs-on: ubuntu-latest 108 | permissions: 109 | contents: read 110 | packages: write 111 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 112 | steps: 113 | - uses: actions/setup-node@v4 114 | with: 115 | node-version: 22.14.0 116 | - name: Download build artifacts 117 | uses: actions/download-artifact@v4 118 | with: 119 | name: build-artifact 120 | path: dist 121 | - name: Restore build artifact permissions 122 | run: cd dist && setfacl --restore=permissions-backup.acl 123 | continue-on-error: true 124 | - name: Release 125 | env: 126 | NPM_DIST_TAG: latest 127 | NPM_REGISTRY: npm.pkg.github.com 128 | NPM_TOKEN: \${{ secrets.GITHUB_TOKEN }} 129 | run: npx -p publib@latest publib-npm 130 | send_release_event_to_slack: 131 | name: Send Release Alert to Slack 132 | needs: release 133 | runs-on: ubuntu-latest 134 | permissions: 135 | contents: read 136 | if: needs.release.outputs.latest_commit == github.sha 137 | steps: 138 | - name: Download build artifacts 139 | uses: actions/download-artifact@v4 140 | with: 141 | name: build-artifact 142 | path: dist 143 | - name: Get version 144 | id: event_metadata 145 | run: echo "release_tag=$(cat dist/releasetag.txt)" >> $GITHUB_OUTPUT 146 | - name: Send Slack webhook event 147 | uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7 148 | env: 149 | SLACK_TITLE: \${{ github.repository }}@\${{ steps.event_metadata.outputs.release_tag }} released! 150 | SLACK_MESSAGE: "View the release notes here: https://github.com/\${{ github.repository }}/releases/tag/\${{ steps.event_metadata.outputs.release_tag }}" 151 | SLACK_WEBHOOK: \${{ secrets.PROJEN_RELEASE_SLACK_WEBHOOK }} 152 | SLACK_FOOTER: "" 153 | SLACK_COLOR: success 154 | MSG_MINIMAL: "true" 155 | " 156 | `; 157 | 158 | exports[`ClickUpTypeScriptProject defaults .github/workflows/upgrade-main.yml 1`] = `undefined`; 159 | 160 | exports[`ClickUpTypeScriptProject defaults .mergify.yml 1`] = ` 161 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 162 | 163 | queue_rules: 164 | - name: default 165 | update_method: merge 166 | conditions: 167 | - "#approved-reviews-by>=1" 168 | - -label~=(do-not-merge) 169 | - status-success=build 170 | merge_method: squash 171 | commit_message_template: |- 172 | {{ title }} (#{{ number }}) 173 | 174 | {{ body }} 175 | pull_request_rules: 176 | - name: Automatic merge on approval and successful build 177 | actions: 178 | delete_head_branch: {} 179 | queue: 180 | name: default 181 | conditions: 182 | - "#approved-reviews-by>=1" 183 | - -label~=(do-not-merge) 184 | - status-success=build 185 | " 186 | `; 187 | 188 | exports[`ClickUpTypeScriptProject defaults codecov.yml 1`] = ` 189 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 190 | 191 | coverage: 192 | precision: 2 193 | round: down 194 | status: 195 | project: 196 | default: 197 | target: auto 198 | threshold: 10% 199 | paths: 200 | - src 201 | if_ci_failed: error 202 | only_pulls: true 203 | patch: 204 | default: 205 | base: auto 206 | target: auto 207 | threshold: 10% 208 | paths: 209 | - src 210 | if_ci_failed: error 211 | only_pulls: true 212 | parsers: 213 | gcov: 214 | branch_detection: 215 | conditional: yes 216 | loop: yes 217 | method: no 218 | macro: no 219 | comment: 220 | layout: reach,diff,flags,files,footer 221 | behavior: default 222 | require_changes: no 223 | " 224 | `; 225 | 226 | exports[`ClickUpTypeScriptProject defaults package.json 1`] = ` 227 | { 228 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen".", 229 | "author": { 230 | "name": "ClickUp", 231 | "organization": true, 232 | }, 233 | "dependencies": { 234 | "@time-loop/clickup-projen": "*", 235 | }, 236 | "devDependencies": { 237 | "@types/jest": "*", 238 | "@types/node": "^18", 239 | "@typescript-eslint/eslint-plugin": "^8", 240 | "@typescript-eslint/parser": "^8", 241 | "commit-and-tag-version": "^12", 242 | "constructs": "^10.0.0", 243 | "esbuild": "*", 244 | "eslint": "^9", 245 | "eslint-config-prettier": "*", 246 | "eslint-import-resolver-typescript": "*", 247 | "eslint-plugin-import": "*", 248 | "eslint-plugin-prettier": "*", 249 | "jest": "*", 250 | "jest-junit": "^16", 251 | "jsii-release": "*", 252 | "prettier": "*", 253 | "projen": "*", 254 | "ts-jest": "*", 255 | "ts-node": "^10", 256 | "typedoc": "*", 257 | "typedoc-plugin-markdown": "*", 258 | "typescript": "*", 259 | }, 260 | "engines": { 261 | "node": ">= 18.17.1", 262 | }, 263 | "jest": { 264 | "clearMocks": true, 265 | "collectCoverage": true, 266 | "collectCoverageFrom": [ 267 | "src/**/*.ts", 268 | ], 269 | "coverageDirectory": "coverage", 270 | "coveragePathIgnorePatterns": [ 271 | "/node_modules/", 272 | ], 273 | "coverageProvider": "v8", 274 | "coverageReporters": [ 275 | "json", 276 | "lcov", 277 | "clover", 278 | "cobertura", 279 | "text", 280 | ], 281 | "reporters": [ 282 | "default", 283 | [ 284 | "jest-junit", 285 | { 286 | "outputDirectory": "test-reports", 287 | }, 288 | ], 289 | ], 290 | "testMatch": [ 291 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 292 | "/@(src|test)/**/__tests__/**/*.ts?(x)", 293 | ], 294 | "testPathIgnorePatterns": [ 295 | "/node_modules/", 296 | ], 297 | "transform": { 298 | "^.+\\.[t]sx?$": [ 299 | "ts-jest", 300 | { 301 | "tsconfig": "tsconfig.dev.json", 302 | }, 303 | ], 304 | }, 305 | "watchPathIgnorePatterns": [ 306 | "/node_modules/", 307 | ], 308 | }, 309 | "license": "UNLICENSED", 310 | "main": "lib/index.js", 311 | "name": "@time-loop/test", 312 | "publishConfig": { 313 | "registry": "https://npm.pkg.github.com/", 314 | }, 315 | "scripts": { 316 | "build": "npx projen build", 317 | "bump": "npx projen bump", 318 | "clobber": "npx projen clobber", 319 | "compile": "npx projen compile", 320 | "default": "npx projen default", 321 | "eject": "npx projen eject", 322 | "eslint": "npx projen eslint", 323 | "package": "npx projen package", 324 | "post-compile": "npx projen post-compile", 325 | "post-upgrade": "npx projen post-upgrade", 326 | "pre-compile": "npx projen pre-compile", 327 | "projen": "npx projen", 328 | "release": "npx projen release", 329 | "test": "npx projen test", 330 | "test:watch": "npx projen test:watch", 331 | "typedocDocgen": "npx projen typedocDocgen", 332 | "unbump": "npx projen unbump", 333 | "update-projen": "npx projen update-projen", 334 | "upgrade": "npx projen upgrade", 335 | "watch": "npx projen watch", 336 | }, 337 | "types": "lib/index.d.ts", 338 | "version": "0.0.0", 339 | } 340 | `; 341 | -------------------------------------------------------------------------------- /test/__snapshots__/codecov-bypass-workflow.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addCodecovBypassWorkflowYml - codecov-bypass .yml file added all default options 1`] = ` 4 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 5 | 6 | name: Code coverage increased 7 | on: 8 | check_suite: 9 | types: 10 | - completed 11 | pull_request: 12 | types: 13 | - labeled 14 | - unlabeled 15 | permissions: {} 16 | jobs: 17 | code-coverage-increased: 18 | runs-on: ubuntu-latest 19 | if: github.event_name != 'check_suite' || github.event.check_suite.app.id == 254 20 | steps: 21 | - name: Check codecov results 22 | uses: time-loop/github-actions/dist/wrap-check-suite@wrap-check-suite+0.3.2 23 | env: 24 | FORCE_COLOR: 3 25 | with: 26 | github-app-id: \${{ vars.CODECOV_GITHUB_APP_ID }} 27 | github-private-key: \${{ secrets.CODECOV_GITHUB_APP_PRIVATE_KEY }} 28 | skip-label: code coverage not required 29 | check-name: Code coverage increased 30 | check-suite-app-id: 254 31 | check-suite-check-names: codecov/patch,codecov/project 32 | details-url: https://app.codecov.io/gh/\${{ github.repository }}/pull/<% prNumber %> 33 | " 34 | `; 35 | 36 | exports[`addCodecovBypassWorkflowYml - codecov-bypass .yml file added all options provided 1`] = ` 37 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 38 | 39 | name: workflowName 40 | on: 41 | check_suite: 42 | types: 43 | - completed 44 | pull_request: 45 | types: 46 | - labeled 47 | - unlabeled 48 | permissions: {} 49 | jobs: 50 | code-coverage-increased: 51 | runs-on: ubuntu-latest 52 | if: github.event_name != 'check_suite' || github.event.check_suite.app.id == 254 53 | steps: 54 | - name: Check codecov results 55 | uses: time-loop/github-actions/dist/wrap-check-suite@wrap-check-suite+0.3.2 56 | env: 57 | FORCE_COLOR: 3 58 | with: 59 | github-app-id: \${{ vars.GH_APP_ID_VAR_NAME }} 60 | github-private-key: r\${{ secrets.GH_APP_ID_PRIVATE_KEY_SECRET_NAME }} 61 | skip-label: skipLabel 62 | check-name: checkName 63 | check-suite-app-id: 254 64 | check-suite-check-names: checkSuiteCheckName1,checkSuiteCheckName2 65 | details-url: some url 66 | " 67 | `; 68 | -------------------------------------------------------------------------------- /test/__snapshots__/codecov.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addCodeCovYml file added 1`] = ` 4 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 5 | 6 | coverage: 7 | precision: 2 8 | round: down 9 | status: 10 | project: 11 | default: 12 | target: auto 13 | threshold: 10% 14 | paths: 15 | - src 16 | if_ci_failed: error 17 | only_pulls: true 18 | patch: 19 | default: 20 | base: auto 21 | target: auto 22 | threshold: 10% 23 | paths: 24 | - src 25 | if_ci_failed: error 26 | only_pulls: true 27 | parsers: 28 | gcov: 29 | branch_detection: 30 | conditional: yes 31 | loop: yes 32 | method: no 33 | macro: no 34 | comment: 35 | layout: reach,diff,flags,files,footer 36 | behavior: default 37 | require_changes: no 38 | " 39 | `; 40 | 41 | exports[`addCodeCovYml override 1`] = ` 42 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 43 | 44 | coverage: 45 | precision: 2 46 | round: down 47 | status: 48 | project: 49 | default: 50 | target: auto 51 | threshold: 10% 52 | paths: 53 | - src 54 | if_ci_failed: error 55 | only_pulls: true 56 | patch: 57 | default: 58 | base: auto 59 | target: auto 60 | threshold: 10% 61 | paths: 62 | - src 63 | if_ci_failed: error 64 | only_pulls: true 65 | parsers: 66 | gcov: 67 | branch_detection: 68 | conditional: yes 69 | loop: yes 70 | method: no 71 | macro: no 72 | comment: 73 | layout: reach,diff,flags,files,footer 74 | behavior: default 75 | require_changes: no 76 | foo: bar 77 | " 78 | `; 79 | -------------------------------------------------------------------------------- /test/__snapshots__/datadog-service-catalog.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addReleaseEvent add send_service_catalog job with all parameters 1`] = ` 4 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 5 | 6 | name: release 7 | on: 8 | push: 9 | branches: 10 | - main 11 | workflow_dispatch: {} 12 | concurrency: 13 | group: \${{ github.workflow }} 14 | cancel-in-progress: false 15 | jobs: 16 | release: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | outputs: 21 | latest_commit: \${{ steps.git_remote.outputs.latest_commit }} 22 | tag_exists: \${{ steps.check_tag_exists.outputs.exists }} 23 | env: 24 | CI: "true" 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | - name: Set git identity 31 | run: |- 32 | git config user.name "github-actions" 33 | git config user.email "github-actions@github.com" 34 | - name: Install dependencies 35 | run: yarn install --check-files --frozen-lockfile 36 | - name: release 37 | run: npx projen release 38 | - name: Check if version has already been tagged 39 | id: check_tag_exists 40 | run: |- 41 | TAG=$(cat dist/releasetag.txt) 42 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 43 | cat $GITHUB_OUTPUT 44 | - name: Check for new commits 45 | id: git_remote 46 | run: |- 47 | echo "latest_commit=$(git ls-remote origin -h \${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 48 | cat $GITHUB_OUTPUT 49 | - name: Backup artifact permissions 50 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 51 | run: cd dist && getfacl -R . > permissions-backup.acl 52 | continue-on-error: true 53 | - name: Upload artifact 54 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 55 | uses: actions/upload-artifact@v4.4.0 56 | with: 57 | name: build-artifact 58 | path: dist 59 | overwrite: true 60 | release_github: 61 | name: Publish to GitHub Releases 62 | needs: release 63 | runs-on: ubuntu-latest 64 | permissions: 65 | contents: write 66 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 67 | steps: 68 | - uses: actions/setup-node@v4 69 | with: 70 | node-version: lts/* 71 | - name: Download build artifacts 72 | uses: actions/download-artifact@v4 73 | with: 74 | name: build-artifact 75 | path: dist 76 | - name: Restore build artifact permissions 77 | run: cd dist && setfacl --restore=permissions-backup.acl 78 | continue-on-error: true 79 | - name: Release 80 | env: 81 | GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} 82 | 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 83 | send_service_catalog: 84 | name: Send to Datadog Service Catalog 85 | needs: release 86 | runs-on: ubuntu-latest 87 | permissions: 88 | contents: read 89 | if: needs.release.outputs.latest_commit == github.sha 90 | steps: 91 | - name: Download build artifacts 92 | uses: actions/download-artifact@v4 93 | with: 94 | name: build-artifact 95 | path: dist 96 | - name: Get version 97 | id: event_metadata 98 | run: echo "release_tag=$(cat dist/releasetag.txt)" >> $GITHUB_OUTPUT 99 | - name: Publish DD Service Catalog for test-service 100 | uses: arcxp/datadog-service-catalog-metadata-provider@v2 101 | with: 102 | schema-version: v2.1 103 | datadog-hostname: app.datadoghq.com 104 | datadog-key: \${{ secrets.DD_PROJEN_RELEASE_API_KEY }} 105 | datadog-app-key: \${{ secrets.DD_PROJEN_RELEASE_APP_KEY }} 106 | service-name: test-service 107 | description: test description test-service 1 108 | application: clickup 109 | tier: critical 110 | lifecycle: Not Provided 111 | team: Not Provided 112 | slack-support-channel: https://click-up.slack.com/archives/C043T0JBJKY 113 | contacts: |- 114 | 115 | - type: email 116 | contact: contacttest@clickup.com 117 | name: contact test 118 | links: |- 119 | 120 | - type: other 121 | url: https://test.clickup.com 122 | name: link test, 123 | - type: other 124 | url: https://staging.clickup.com 125 | name: staging link test 126 | tags: |- 127 | 128 | - project:test-cdk, 129 | - release:true, 130 | - version:\${{ steps.event_metadata.outputs.release_tag }}, 131 | - actor:\${{ github.actor }}, 132 | - tag1:tag1-test, 133 | - tag2:tag2-test 134 | " 135 | `; 136 | 137 | exports[`addReleaseEvent adds job to release workflow with service catalog options 1`] = ` 138 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 139 | 140 | name: release 141 | on: 142 | push: 143 | branches: 144 | - main 145 | workflow_dispatch: {} 146 | concurrency: 147 | group: \${{ github.workflow }} 148 | cancel-in-progress: false 149 | jobs: 150 | release: 151 | runs-on: ubuntu-latest 152 | permissions: 153 | contents: write 154 | outputs: 155 | latest_commit: \${{ steps.git_remote.outputs.latest_commit }} 156 | tag_exists: \${{ steps.check_tag_exists.outputs.exists }} 157 | env: 158 | CI: "true" 159 | steps: 160 | - name: Checkout 161 | uses: actions/checkout@v4 162 | with: 163 | fetch-depth: 0 164 | - name: Set git identity 165 | run: |- 166 | git config user.name "github-actions" 167 | git config user.email "github-actions@github.com" 168 | - name: Install dependencies 169 | run: yarn install --check-files --frozen-lockfile 170 | - name: release 171 | run: npx projen release 172 | - name: Check if version has already been tagged 173 | id: check_tag_exists 174 | run: |- 175 | TAG=$(cat dist/releasetag.txt) 176 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 177 | cat $GITHUB_OUTPUT 178 | - name: Check for new commits 179 | id: git_remote 180 | run: |- 181 | echo "latest_commit=$(git ls-remote origin -h \${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 182 | cat $GITHUB_OUTPUT 183 | - name: Backup artifact permissions 184 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 185 | run: cd dist && getfacl -R . > permissions-backup.acl 186 | continue-on-error: true 187 | - name: Upload artifact 188 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 189 | uses: actions/upload-artifact@v4.4.0 190 | with: 191 | name: build-artifact 192 | path: dist 193 | overwrite: true 194 | release_github: 195 | name: Publish to GitHub Releases 196 | needs: release 197 | runs-on: ubuntu-latest 198 | permissions: 199 | contents: write 200 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 201 | steps: 202 | - uses: actions/setup-node@v4 203 | with: 204 | node-version: lts/* 205 | - name: Download build artifacts 206 | uses: actions/download-artifact@v4 207 | with: 208 | name: build-artifact 209 | path: dist 210 | - name: Restore build artifact permissions 211 | run: cd dist && setfacl --restore=permissions-backup.acl 212 | continue-on-error: true 213 | - name: Release 214 | env: 215 | GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} 216 | 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 217 | send_service_catalog: 218 | name: Send to Datadog Service Catalog 219 | needs: release 220 | runs-on: ubuntu-latest 221 | permissions: 222 | contents: read 223 | if: needs.release.outputs.latest_commit == github.sha 224 | steps: 225 | - name: Download build artifacts 226 | uses: actions/download-artifact@v4 227 | with: 228 | name: build-artifact 229 | path: dist 230 | - name: Get version 231 | id: event_metadata 232 | run: echo "release_tag=$(cat dist/releasetag.txt)" >> $GITHUB_OUTPUT 233 | - name: Publish DD Service Catalog for test-service 234 | uses: arcxp/datadog-service-catalog-metadata-provider@v2 235 | with: 236 | schema-version: v2.1 237 | datadog-hostname: app.datadoghq.com 238 | datadog-key: \${{ secrets.DD_PROJEN_RELEASE_API_KEY }} 239 | datadog-app-key: \${{ secrets.DD_PROJEN_RELEASE_APP_KEY }} 240 | service-name: test-service 241 | description: test description test-service 1 242 | application: clickup 243 | tier: critical 244 | lifecycle: unit-test 245 | team: testing 246 | slack-support-channel: https://click-up.slack.com/archives/C043T0JBJKY 247 | contacts: undefined 248 | links: undefined 249 | tags: |- 250 | 251 | - project:test-cdk, 252 | - release:true, 253 | - version:\${{ steps.event_metadata.outputs.release_tag }}, 254 | - actor:\${{ github.actor }} 255 | - name: Publish DD Service Catalog for test-cdk 256 | uses: arcxp/datadog-service-catalog-metadata-provider@v2 257 | with: 258 | schema-version: v2.1 259 | datadog-hostname: app.datadoghq.com 260 | datadog-key: \${{ secrets.DD_PROJEN_RELEASE_API_KEY }} 261 | datadog-app-key: \${{ secrets.DD_PROJEN_RELEASE_APP_KEY }} 262 | service-name: test-cdk 263 | description: Not Provided 264 | application: Not Provided 265 | tier: low 266 | lifecycle: Not Provided 267 | team: testing 268 | slack-support-channel: https://click-up.slack.com/archives/C043T0JBJKY 269 | contacts: undefined 270 | links: undefined 271 | tags: |- 272 | 273 | - project:test-cdk, 274 | - release:true, 275 | - version:\${{ steps.event_metadata.outputs.release_tag }}, 276 | - actor:\${{ github.actor }} 277 | " 278 | `; 279 | -------------------------------------------------------------------------------- /test/__snapshots__/node-version.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addNodeVersionFile file added 1`] = `"22.14.0"`; 4 | 5 | exports[`addNodeVersionFile node20 1`] = `"22.14.0"`; 6 | -------------------------------------------------------------------------------- /test/__snapshots__/renovate-workflow.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addRenovateWorkflowYml file added 1`] = ` 4 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 5 | 6 | name: upgrade-main 7 | on: 8 | schedule: 9 | - cron: 25 0 * * MON-FRI 10 | workflow_dispatch: {} 11 | push: 12 | branches: 13 | - main 14 | paths: 15 | - renovate.json5 16 | - .github/workflows/renovate.yml 17 | issues: 18 | types: 19 | - edited 20 | pull_request: 21 | types: 22 | - edited 23 | concurrency: 24 | group: \${{ github.workflow }}-\${{ github.event_name == 'workflow_dispatch' && github.event_name || '' }} 25 | jobs: 26 | upgrade-main: 27 | runs-on: ubuntu-latest 28 | permissions: 29 | contents: read 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | - name: Self-hosted Renovate 34 | uses: renovatebot/github-action@v41.0.12 35 | if: (github.event_name != 'issues' && github.event_name != 'pull_request') || github.actor != 'cu-infra-svc-git' 36 | with: 37 | configurationFile: renovate.json5 38 | token: x-access-token:\${{ secrets.RENOVATEBOT_GITHUB_TOKEN }} 39 | env: 40 | RENOVATE_HOST_RULES: '[{"matchHost":"https://npm.pkg.github.com/","hostType":"npm","token":"\${{ secrets.ALL_PACKAGE_READ_TOKEN }}"}]' 41 | RENOVATE_NPMRC: "@\${{ github.repository_owner }}:registry=https://npm.pkg.github.com" 42 | RENOVATE_ENABLED_MANAGERS: '["npm"]' 43 | RENOVATE_REPOSITORIES: '["\${{ github.repository }}"]' 44 | RENOVATE_FORCE: \${{ github.event_name == 'workflow_dispatch' && '{"schedule":null}' || '' }} 45 | " 46 | `; 47 | 48 | exports[`addRenovateWorkflowYml override 1`] = ` 49 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 50 | 51 | name: upgrade-main 52 | on: 53 | schedule: 54 | - cron: 25 0 * * MON-FRI 55 | workflow_dispatch: {} 56 | push: 57 | branches: 58 | - main 59 | paths: 60 | - renovate.json5 61 | - .github/workflows/renovate.yml 62 | issues: 63 | types: 64 | - edited 65 | pull_request: 66 | types: 67 | - edited 68 | concurrency: 69 | group: \${{ github.workflow }}-\${{ github.event_name == 'workflow_dispatch' && github.event_name || '' }} 70 | jobs: 71 | upgrade-main: 72 | runs-on: ubuntu-latest 73 | permissions: 74 | contents: read 75 | steps: 76 | - name: Checkout 77 | uses: actions/checkout@v4 78 | - name: Self-hosted Renovate 79 | uses: renovatebot/github-action@v41.0.12 80 | if: (github.event_name != 'issues' && github.event_name != 'pull_request') || github.actor != 'cu-infra-svc-git' 81 | with: 82 | configurationFile: renovate.json5 83 | token: x-access-token:\${{ secrets.RENOVATEBOT_GITHUB_TOKEN }} 84 | env: 85 | RENOVATE_HOST_RULES: '[{"matchHost":"https://npm.pkg.github.com/","hostType":"npm","token":"\${{ secrets.ALL_PACKAGE_READ_TOKEN }}"}]' 86 | RENOVATE_NPMRC: "@\${{ github.repository_owner }}:registry=https://npm.pkg.github.com" 87 | RENOVATE_ENABLED_MANAGERS: '["npm"]' 88 | RENOVATE_REPOSITORIES: '["\${{ github.repository }}"]' 89 | RENOVATE_FORCE: \${{ github.event_name == 'workflow_dispatch' && '{"schedule":null}' || '' }} 90 | foo: bar 91 | " 92 | `; 93 | 94 | exports[`getRenovateOptions defaults 1`] = ` 95 | { 96 | "ignore": [ 97 | "node", 98 | "@time-loop/clickup-projen", 99 | "pnpm", 100 | ], 101 | "ignoreProjen": true, 102 | "labels": [ 103 | "renovate", 104 | ], 105 | "overrideConfig": { 106 | "automergeType": "pr", 107 | "extends": [ 108 | "config:base", 109 | "group:recommended", 110 | "group:monorepos", 111 | "github>whitesource/merge-confidence:beta", 112 | ], 113 | "packageRules": [ 114 | { 115 | "addLabels": [ 116 | undefined, 117 | ], 118 | "automerge": undefined, 119 | "excludePackagePatterns": [ 120 | "^@time-loop\\/clickup-projen", 121 | ], 122 | "groupName": "all non-major dependencies", 123 | "groupSlug": "all-minor-patch", 124 | "matchPackagePatterns": [ 125 | "*", 126 | ], 127 | "matchUpdateTypes": [ 128 | "minor", 129 | "patch", 130 | ], 131 | }, 132 | { 133 | "addLabels": [ 134 | "optional", 135 | ], 136 | "matchDepTypes": [ 137 | "optionalDependencies", 138 | ], 139 | }, 140 | { 141 | "allowedVersions": "!/^[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?-(alpha|beta).*$/", 142 | "matchPackagePatterns": [ 143 | "^@time-loop\\/clickup-projen", 144 | ], 145 | }, 146 | { 147 | "matchPackagePrefixes": [ 148 | "@time-loop/", 149 | ], 150 | "matchUpdateTypes": [ 151 | "major", 152 | ], 153 | "prBodyNotes": [ 154 | "# DANGER WILL ROBINSON!!!", 155 | "Applying major version updates without understanding what they do has caused outages at ClickUp. Don't be that guy. Read the release notes. The release notes are in a link buried in the rollup below. Sorry, unfortunately Renovate does not offer a reasonable way to expose them here. If in doubt, escalate to CloudPlatform.", 156 | "CloudPlatform thanks you for being diligent with your library updates!", 157 | ], 158 | }, 159 | ], 160 | "platformAutomerge": true, 161 | "prConcurrentLimit": 0, 162 | "prHourlyLimit": 0, 163 | "rangeStrategy": "bump", 164 | }, 165 | "scheduleInterval": [ 166 | "before 2am on Monday", 167 | ], 168 | } 169 | `; 170 | -------------------------------------------------------------------------------- /test/__snapshots__/slack-alert.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addReleaseEvent adds job to release workflow 1`] = ` 4 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 5 | 6 | name: release 7 | on: 8 | push: 9 | branches: 10 | - main 11 | workflow_dispatch: {} 12 | concurrency: 13 | group: \${{ github.workflow }} 14 | cancel-in-progress: false 15 | jobs: 16 | release: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | outputs: 21 | latest_commit: \${{ steps.git_remote.outputs.latest_commit }} 22 | tag_exists: \${{ steps.check_tag_exists.outputs.exists }} 23 | env: 24 | CI: "true" 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | - name: Set git identity 31 | run: |- 32 | git config user.name "github-actions" 33 | git config user.email "github-actions@github.com" 34 | - name: Install dependencies 35 | run: yarn install --check-files --frozen-lockfile 36 | - name: release 37 | run: npx projen release 38 | - name: Check if version has already been tagged 39 | id: check_tag_exists 40 | run: |- 41 | TAG=$(cat dist/releasetag.txt) 42 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 43 | cat $GITHUB_OUTPUT 44 | - name: Check for new commits 45 | id: git_remote 46 | run: |- 47 | echo "latest_commit=$(git ls-remote origin -h \${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 48 | cat $GITHUB_OUTPUT 49 | - name: Backup artifact permissions 50 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 51 | run: cd dist && getfacl -R . > permissions-backup.acl 52 | continue-on-error: true 53 | - name: Upload artifact 54 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 55 | uses: actions/upload-artifact@v4.4.0 56 | with: 57 | name: build-artifact 58 | path: dist 59 | overwrite: true 60 | release_github: 61 | name: Publish to GitHub Releases 62 | needs: release 63 | runs-on: ubuntu-latest 64 | permissions: 65 | contents: write 66 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 67 | steps: 68 | - uses: actions/setup-node@v4 69 | with: 70 | node-version: lts/* 71 | - name: Download build artifacts 72 | uses: actions/download-artifact@v4 73 | with: 74 | name: build-artifact 75 | path: dist 76 | - name: Restore build artifact permissions 77 | run: cd dist && setfacl --restore=permissions-backup.acl 78 | continue-on-error: true 79 | - name: Release 80 | env: 81 | GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} 82 | 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 83 | send_release_event_to_slack: 84 | name: Send Release Alert to Slack 85 | needs: release 86 | runs-on: ubuntu-latest 87 | permissions: 88 | contents: read 89 | if: needs.release.outputs.latest_commit == github.sha 90 | steps: 91 | - name: Download build artifacts 92 | uses: actions/download-artifact@v4 93 | with: 94 | name: build-artifact 95 | path: dist 96 | - name: Get version 97 | id: event_metadata 98 | run: echo "release_tag=$(cat dist/releasetag.txt)" >> $GITHUB_OUTPUT 99 | - name: Send Slack webhook event 100 | uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7 101 | env: 102 | SLACK_TITLE: \${{ github.repository }}@\${{ steps.event_metadata.outputs.release_tag }} released! 103 | SLACK_MESSAGE: "View the release notes here: https://github.com/\${{ github.repository }}/releases/tag/\${{ steps.event_metadata.outputs.release_tag }}" 104 | SLACK_WEBHOOK: \${{ secrets.PROJEN_RELEASE_SLACK_WEBHOOK }} 105 | SLACK_FOOTER: "" 106 | SLACK_COLOR: success 107 | MSG_MINIMAL: "true" 108 | " 109 | `; 110 | 111 | exports[`addReleaseEvent honors overrides 1`] = ` 112 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 113 | 114 | name: release 115 | on: 116 | push: 117 | branches: 118 | - main 119 | workflow_dispatch: {} 120 | concurrency: 121 | group: \${{ github.workflow }} 122 | cancel-in-progress: false 123 | jobs: 124 | release: 125 | runs-on: ubuntu-latest 126 | permissions: 127 | contents: write 128 | outputs: 129 | latest_commit: \${{ steps.git_remote.outputs.latest_commit }} 130 | tag_exists: \${{ steps.check_tag_exists.outputs.exists }} 131 | env: 132 | CI: "true" 133 | steps: 134 | - name: Checkout 135 | uses: actions/checkout@v4 136 | with: 137 | fetch-depth: 0 138 | - name: Set git identity 139 | run: |- 140 | git config user.name "github-actions" 141 | git config user.email "github-actions@github.com" 142 | - name: Install dependencies 143 | run: yarn install --check-files --frozen-lockfile 144 | - name: release 145 | run: npx projen release 146 | - name: Check if version has already been tagged 147 | id: check_tag_exists 148 | run: |- 149 | TAG=$(cat dist/releasetag.txt) 150 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 151 | cat $GITHUB_OUTPUT 152 | - name: Check for new commits 153 | id: git_remote 154 | run: |- 155 | echo "latest_commit=$(git ls-remote origin -h \${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 156 | cat $GITHUB_OUTPUT 157 | - name: Backup artifact permissions 158 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 159 | run: cd dist && getfacl -R . > permissions-backup.acl 160 | continue-on-error: true 161 | - name: Upload artifact 162 | if: \${{ steps.git_remote.outputs.latest_commit == github.sha }} 163 | uses: actions/upload-artifact@v4.4.0 164 | with: 165 | name: build-artifact 166 | path: dist 167 | overwrite: true 168 | release_github: 169 | name: Publish to GitHub Releases 170 | needs: release 171 | runs-on: ubuntu-latest 172 | permissions: 173 | contents: write 174 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 175 | steps: 176 | - uses: actions/setup-node@v4 177 | with: 178 | node-version: lts/* 179 | - name: Download build artifacts 180 | uses: actions/download-artifact@v4 181 | with: 182 | name: build-artifact 183 | path: dist 184 | - name: Restore build artifact permissions 185 | run: cd dist && setfacl --restore=permissions-backup.acl 186 | continue-on-error: true 187 | - name: Release 188 | env: 189 | GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }} 190 | 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 191 | send_release_event_to_slack: 192 | name: Send Release Alert to Slack 193 | needs: release 194 | runs-on: ubuntu-latest 195 | permissions: 196 | contents: read 197 | if: needs.release.outputs.latest_commit == github.sha 198 | steps: 199 | - name: Download build artifacts 200 | uses: actions/download-artifact@v4 201 | with: 202 | name: build-artifact 203 | path: dist 204 | - name: Get version 205 | id: event_metadata 206 | run: echo "release_tag=$(cat dist/releasetag.txt)" >> $GITHUB_OUTPUT 207 | - name: Send Slack webhook event 208 | uses: rtCamp/action-slack-notify@12e36fc18b0689399306c2e0b3e0f2978b7f1ee7 209 | env: 210 | SLACK_TITLE: title 211 | SLACK_MESSAGE: body 212 | SLACK_WEBHOOK: \${{ secrets.PROJEN_RELEASE_SLACK_WEBHOOK }} 213 | SLACK_FOOTER: "" 214 | SLACK_COLOR: success 215 | MSG_MINIMAL: "true" 216 | " 217 | `; 218 | 219 | exports[`addReleaseEvent performs no-op when release option is false 1`] = `undefined`; 220 | -------------------------------------------------------------------------------- /test/__snapshots__/update-projen.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addAddToProjectWorkflowYml file added 1`] = ` 4 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 5 | 6 | name: add-to-update-projen-project 7 | on: 8 | pull_request: 9 | types: 10 | - opened 11 | - labeled 12 | concurrency: 13 | group: \${{ github.workflow }}-\${{ github.ref_name }} 14 | jobs: 15 | add-to-update-projen-project: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | steps: 20 | - name: Add to project 21 | uses: actions/add-to-project@v0.4.1 22 | with: 23 | project-url: https://github.com/orgs/time-loop/projects/6 24 | github-token: \${{ secrets.RENOVATEBOT_GITHUB_TOKEN }} 25 | labeled: update-projen 26 | " 27 | `; 28 | 29 | exports[`addAddToProjectWorkflowYml file added 2`] = ` 30 | "# ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 31 | 32 | name: update-projen-main 33 | on: 34 | workflow_dispatch: {} 35 | schedule: 36 | - cron: 0 0 1 * * 37 | jobs: 38 | upgrade: 39 | name: Upgrade 40 | runs-on: ubuntu-latest 41 | permissions: 42 | contents: read 43 | outputs: 44 | patch_created: \${{ steps.create_patch.outputs.patch_created }} 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v4 48 | with: 49 | ref: main 50 | - name: Install dependencies 51 | run: yarn install --check-files --frozen-lockfile 52 | - name: Upgrade dependencies 53 | run: npx projen update-projen 54 | - name: Find mutations 55 | id: create_patch 56 | run: |- 57 | git add . 58 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 59 | working-directory: ./ 60 | - name: Upload patch 61 | if: steps.create_patch.outputs.patch_created 62 | uses: actions/upload-artifact@v4.4.0 63 | with: 64 | name: repo.patch 65 | path: repo.patch 66 | overwrite: true 67 | pr: 68 | name: Create Pull Request 69 | needs: upgrade 70 | runs-on: ubuntu-latest 71 | permissions: 72 | contents: read 73 | if: \${{ needs.upgrade.outputs.patch_created }} 74 | steps: 75 | - name: Checkout 76 | uses: actions/checkout@v4 77 | with: 78 | ref: main 79 | - name: Download patch 80 | uses: actions/download-artifact@v4 81 | with: 82 | name: repo.patch 83 | path: \${{ runner.temp }} 84 | - name: Apply patch 85 | run: '[ -s \${{ runner.temp }}/repo.patch ] && git apply \${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 86 | - name: Set git identity 87 | run: |- 88 | git config user.name "github-actions" 89 | git config user.email "github-actions@github.com" 90 | - name: Create Pull Request 91 | id: create-pr 92 | uses: peter-evans/create-pull-request@v6 93 | with: 94 | token: \${{ secrets.PROJEN_GITHUB_TOKEN }} 95 | commit-message: |- 96 | fix(deps): upgrade projen 97 | 98 | Upgrades project dependencies. See details in [workflow run]. 99 | 100 | [Workflow Run]: \${{ github.server_url }}/\${{ github.repository }}/actions/runs/\${{ github.run_id }} 101 | 102 | ------ 103 | 104 | *Automatically created by projen via the "update-projen-main" workflow* 105 | branch: github-actions/update-projen-main 106 | title: "fix(deps): upgrade projen" 107 | labels: update-projen 108 | body: |- 109 | Upgrades project dependencies. See details in [workflow run]. 110 | 111 | [Workflow Run]: \${{ github.server_url }}/\${{ github.repository }}/actions/runs/\${{ github.run_id }} 112 | 113 | ------ 114 | 115 | *Automatically created by projen via the "update-projen-main" workflow* 116 | author: github-actions 117 | committer: github-actions 118 | signoff: true 119 | " 120 | `; 121 | -------------------------------------------------------------------------------- /test/add-to-project.test.ts: -------------------------------------------------------------------------------- 1 | import { typescript, Testing } from 'projen'; 2 | 3 | import { addToProjectWorkflow } from '../src/add-to-project'; 4 | 5 | describe('addAddToProjectWorkflowYml', () => { 6 | test('file added', () => { 7 | const project = new typescript.TypeScriptProject({ 8 | defaultReleaseBranch: 'main', 9 | name: 'test', 10 | }); 11 | addToProjectWorkflow.addAddToProjectWorkflowYml(project); 12 | const synth = Testing.synth(project); 13 | expect(synth['.github/workflows/add-to-project.yml']).toMatchSnapshot(); 14 | }); 15 | test('override', () => { 16 | const project = new typescript.TypeScriptProject({ 17 | defaultReleaseBranch: 'main', 18 | name: 'test', 19 | }); 20 | addToProjectWorkflow.addAddToProjectWorkflowYml(project, { foo: 'bar' }); 21 | const synth = Testing.synth(project); 22 | expect(synth['.github/workflows/add-to-project.yml']).toMatchSnapshot(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/cdk-context-json.test.ts: -------------------------------------------------------------------------------- 1 | import { awscdk, Testing } from 'projen'; 2 | import { requiredParams } from './requiredParams'; 3 | import { cdkContextJson } from '../src/cdk-context-json'; 4 | 5 | describe('injectAwsAuthIntoBuild', () => { 6 | const project = new awscdk.AwsCdkTypeScriptApp(requiredParams); 7 | const lookupAccountId = '123412341234'; 8 | cdkContextJson.injectAwsAuthIntoBuild(project, { 9 | lookupAccountId, 10 | }); 11 | const synth = Testing.synth(project); 12 | 13 | test('updates build WF', () => { 14 | // This is fragile. It'll probably break when we update the underlying projen. 15 | // The better thing to do is to dig into the file and validate 16 | // permissions + id-token: write 17 | // and that the AWS credentials step is added. 18 | expect(synth['.github/workflows/build.yml']).toMatchSnapshot(); 19 | }); 20 | }); 21 | 22 | describe('addOidcRoleStack', () => { 23 | const project = new awscdk.AwsCdkTypeScriptApp(requiredParams); 24 | cdkContextJson.addOidcRoleStack(project); 25 | const synth = Testing.synth(project); 26 | test('adds stack definition', () => { 27 | expect(synth['src/github-actions-oidc-cdk-context-lookup-role.ts']).toMatchSnapshot(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/cdk-diff-workflow.test.ts: -------------------------------------------------------------------------------- 1 | import { Testing, javascript } from 'projen'; 2 | // import * as Yaml from 'yaml'; 3 | 4 | import { requiredParams } from './requiredParams'; 5 | import { cdkDiffWorkflow } from '../src/cdk-diff-workflow'; 6 | import { clickupCdk } from '../src/clickup-cdk'; 7 | 8 | describe('addCdkDiffWorkflowYml - cdk diff.yml file added', () => { 9 | test('a single env to diff', () => { 10 | const project = new clickupCdk.ClickUpCdkTypeScriptApp(requiredParams); 11 | cdkDiffWorkflow.addCdkDiffWorkflowYml(project, { 12 | envsToDiff: [ 13 | { 14 | name: 'qa', 15 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-qa', 16 | labelToApplyWhenNoDiffPresent: 'qa-no-changes', 17 | stackSearchString: 'Qa', 18 | }, 19 | ], 20 | }); 21 | const synth = Testing.synth(project); 22 | expect(synth['.github/workflows/cdk-diff.yml']).toMatchSnapshot(); 23 | }); 24 | 25 | test('diff with roleDuration value set', () => { 26 | const project = new clickupCdk.ClickUpCdkTypeScriptApp(requiredParams); 27 | cdkDiffWorkflow.addCdkDiffWorkflowYml(project, { 28 | envsToDiff: [ 29 | { 30 | name: 'qa', 31 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-qa', 32 | labelToApplyWhenNoDiffPresent: 'qa-no-changes', 33 | stackSearchString: 'Qa', 34 | roleDuration: 1800, 35 | }, 36 | ], 37 | }); 38 | const synth = Testing.synth(project); 39 | expect(synth['.github/workflows/cdk-diff.yml']).toMatchSnapshot(); 40 | }); 41 | 42 | test('node20', () => { 43 | const project = new clickupCdk.ClickUpCdkTypeScriptApp({ 44 | ...requiredParams, 45 | workflowNodeVersion: '20.5.1', 46 | }); 47 | cdkDiffWorkflow.addCdkDiffWorkflowYml(project, { 48 | envsToDiff: [ 49 | { 50 | name: 'qa', 51 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-qa', 52 | labelToApplyWhenNoDiffPresent: 'qa-no-changes', 53 | stackSearchString: 'Qa', 54 | }, 55 | ], 56 | }); 57 | const synth = Testing.synth(project); 58 | expect(synth['.github/workflows/cdk-diff.yml']).toMatchSnapshot(); 59 | }); 60 | 61 | test('pnpm', () => { 62 | const project = new clickupCdk.ClickUpCdkTypeScriptApp({ 63 | ...requiredParams, 64 | packageManager: javascript.NodePackageManager.PNPM, 65 | pnpmVersion: '9', 66 | }); 67 | cdkDiffWorkflow.addCdkDiffWorkflowYml(project, { 68 | envsToDiff: [ 69 | { 70 | name: 'qa', 71 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-qa', 72 | labelToApplyWhenNoDiffPresent: 'qa-no-changes', 73 | stackSearchString: 'Qa', 74 | }, 75 | ], 76 | }); 77 | const synth = Testing.synth(project); 78 | expect(synth['.github/workflows/cdk-diff.yml']).toMatchSnapshot(); 79 | }); 80 | 81 | test('a single env to diff - single explicit stack given to diff', () => { 82 | const project = new clickupCdk.ClickUpCdkTypeScriptApp(requiredParams); 83 | cdkDiffWorkflow.addCdkDiffWorkflowYml(project, { 84 | envsToDiff: [ 85 | { 86 | name: 'qa', 87 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-qa', 88 | labelToApplyWhenNoDiffPresent: 'qa-no-changes', 89 | stacks: ['UsQaSquadNameDynamoDBTableNameStack'], 90 | }, 91 | ], 92 | }); 93 | const synth = Testing.synth(project); 94 | expect(synth['.github/workflows/cdk-diff.yml']).toMatchSnapshot(); 95 | }); 96 | 97 | test('a single env to diff - multiple stacks given to diff', () => { 98 | const project = new clickupCdk.ClickUpCdkTypeScriptApp(requiredParams); 99 | cdkDiffWorkflow.addCdkDiffWorkflowYml(project, { 100 | envsToDiff: [ 101 | { 102 | name: 'qa', 103 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-qa', 104 | labelToApplyWhenNoDiffPresent: 'qa-no-changes', 105 | stacks: ['UsQaSquadNameDynamoDBTableNameStack', 'UsQaSquadNameS3BucketNameStack'], 106 | }, 107 | ], 108 | }); 109 | const synth = Testing.synth(project); 110 | expect(synth['.github/workflows/cdk-diff.yml']).toMatchSnapshot(); 111 | }); 112 | 113 | test('multiple envs to diff', () => { 114 | const project = new clickupCdk.ClickUpCdkTypeScriptApp(requiredParams); 115 | cdkDiffWorkflow.addCdkDiffWorkflowYml(project, { 116 | envsToDiff: [ 117 | { 118 | name: 'qa', 119 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-qa', 120 | labelToApplyWhenNoDiffPresent: 'qa-no-changes', 121 | stackSearchString: 'Qa', 122 | }, 123 | { 124 | name: 'staging', 125 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-staging', 126 | labelToApplyWhenNoDiffPresent: 'staging-no-changes', 127 | stackSearchString: 'Staging', 128 | }, 129 | { 130 | name: 'prod', 131 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-prod', 132 | labelToApplyWhenNoDiffPresent: 'prod-no-changes', 133 | stackSearchString: 'Prod', 134 | }, 135 | ], 136 | }); 137 | const synth = Testing.synth(project); 138 | expect(synth['.github/workflows/cdk-diff.yml']).toMatchSnapshot(); 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /test/clickup-cdk.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { javascript, Testing } from 'projen'; 3 | import { requiredParams } from './requiredParams'; 4 | import { clickupCdk } from '../src'; 5 | import { datadogServiceCatalog } from '../src/datadog-service-catalog'; 6 | 7 | describe('ClickUpCdkTypeScriptApp', () => { 8 | describe('defaults', () => { 9 | const p = new clickupCdk.ClickUpCdkTypeScriptApp(requiredParams); 10 | const synth = Testing.synth(p); 11 | [ 12 | 'README.md', 13 | 'package.json', 14 | 'src/main.ts', 15 | 'src/widget.ts', 16 | 'test/widget.test.ts', 17 | '.github/workflows/release.yml', 18 | ].forEach((file) => { 19 | test(file, () => { 20 | expect(synth[file]).toMatchSnapshot(); 21 | }); 22 | }); 23 | 24 | test('aws-cdk cli lib is at least 2.1007.0', () => { 25 | expect(synth['package.json'].dependencies['aws-cdk']).toBe('^2.1007.0'); 26 | }); 27 | test('prettier is enabled', () => { 28 | expect(p.prettier).toBeTruthy(); 29 | }); 30 | test('jest is enabled', () => { 31 | expect(p.jest).toBeTruthy(); 32 | }); 33 | test('datadog event sending is enabled', () => { 34 | expect(p.datadogEvent).toBeTruthy(); 35 | }); 36 | // TODO: soooo many more tests need to be written here. 37 | }); 38 | describe('options', () => { 39 | let p: clickupCdk.ClickUpCdkTypeScriptApp; 40 | test('datadog event sending can be disabled', () => { 41 | p = new clickupCdk.ClickUpCdkTypeScriptApp({ ...requiredParams, sendReleaseEvent: false }); 42 | expect(p.datadogEvent).toBeFalsy(); 43 | }); 44 | 45 | describe('node20', () => { 46 | p = new clickupCdk.ClickUpCdkTypeScriptApp({ 47 | ...requiredParams, 48 | minNodeVersion: '20.5.1', 49 | workflowNodeVersion: '20.5.1', 50 | }); 51 | const synth = Testing.synth(p); 52 | ['.nvmrc', 'package.json', '.github/workflows/release.yml'].forEach((file) => { 53 | test(file, () => { 54 | expect(synth[file]).toMatchSnapshot(); 55 | }); 56 | }); 57 | }); 58 | 59 | describe('pnpm', () => { 60 | p = new clickupCdk.ClickUpCdkTypeScriptApp({ 61 | ...requiredParams, 62 | packageManager: javascript.NodePackageManager.PNPM, 63 | }); 64 | const synth = Testing.synth(p); 65 | it('packageManager', () => { 66 | expect(synth['package.json'].packageManager).toBe('pnpm@9.15.5'); 67 | }); 68 | 69 | const npmrcEntries = [ 70 | 'package-manager-strict=false', 71 | // 'manage-package-manager-versions=true', 72 | 'use-node-version=22.14.0', 73 | ]; 74 | it.each(npmrcEntries)('%s', (npmrcEntry) => { 75 | expect(synth['.npmrc']).toContain(npmrcEntry); 76 | }); 77 | 78 | ['.npmrc', 'package.json'].forEach((file) => { 79 | test(file, () => { 80 | expect(synth[file]).toMatchSnapshot(); 81 | }); 82 | }); 83 | }); 84 | }); 85 | }); 86 | 87 | describe('ClickUpCdkConstructLibrary', () => { 88 | describe('defaults', () => { 89 | const p = new clickupCdk.ClickUpCdkConstructLibrary({ 90 | ...requiredParams, 91 | author: '', 92 | authorAddress: '', 93 | repositoryUrl: '', 94 | }); 95 | const synth = Testing.synth(p); 96 | ['package.json'].forEach((file) => { 97 | test(file, () => { 98 | expect(synth[file]).toMatchSnapshot(); 99 | }); 100 | }); 101 | test('datadog event sending is enabled', () => { 102 | expect(p.datadogEvent).toBeTruthy(); 103 | }); 104 | }); 105 | describe('options', () => { 106 | const commonProps = { 107 | ...requiredParams, 108 | author: '', 109 | authorAddress: '', 110 | repositoryUrl: '', 111 | }; 112 | let p: clickupCdk.ClickUpCdkConstructLibrary; 113 | test('datadog event sending can be disabled', () => { 114 | p = new clickupCdk.ClickUpCdkConstructLibrary({ 115 | ...commonProps, 116 | sendReleaseEvent: false, 117 | }); 118 | expect(p.datadogEvent).toBeFalsy(); 119 | }); 120 | test('release_npm exists', () => { 121 | p = new clickupCdk.ClickUpCdkConstructLibrary({ ...commonProps, releaseToNpm: true }); 122 | const synth = Testing.synth(p); 123 | const releaseFile = synth['.github/workflows/release.yml']; 124 | expect(releaseFile).toMatchSnapshot(); 125 | }); 126 | 127 | describe('pnpm', () => { 128 | p = new clickupCdk.ClickUpCdkConstructLibrary({ 129 | ...commonProps, 130 | packageManager: javascript.NodePackageManager.PNPM, 131 | }); 132 | const synth = Testing.synth(p); 133 | it('packageManager', () => { 134 | expect(synth['package.json'].packageManager).toBe('pnpm@9.15.5'); 135 | }); 136 | 137 | const npmrcEntries = [ 138 | 'package-manager-strict=false', 139 | 'manage-package-manager-versions=true', 140 | 'use-node-version=22.14.0', 141 | 'node-linker=hoisted', 142 | ]; 143 | it.each(npmrcEntries)('%s', (npmrcEntry) => { 144 | expect(synth['.npmrc']).toContain(npmrcEntry); 145 | }); 146 | }); 147 | 148 | describe('workflowNodeVersion', () => { 149 | p = new clickupCdk.ClickUpCdkConstructLibrary({ 150 | ...commonProps, 151 | packageManager: javascript.NodePackageManager.PNPM, 152 | workflowNodeVersion: '22.0.0', 153 | }); 154 | const synth = Testing.synth(p); 155 | it('use-node-version in .npmrc file', () => { 156 | expect(synth['.npmrc']).toContain('use-node-version=22.0.0'); 157 | }); 158 | }); 159 | }); 160 | }); 161 | 162 | describe('cdk-diff additions - ClickUpCdkTypeScriptApp', () => { 163 | const p = new clickupCdk.ClickUpCdkTypeScriptApp({ 164 | ...requiredParams, 165 | cdkDiffOptionsConfig: { 166 | envsToDiff: [ 167 | { 168 | name: 'qa', 169 | oidcRoleArn: 'arn:aws:iam::123456789012:role/squad-github-actions-oidc-role-name-qa', 170 | labelToApplyWhenNoDiffPresent: 'qa-no-changes', 171 | stackSearchString: 'Qa', 172 | }, 173 | ], 174 | createOidcRoleStack: true, 175 | }, 176 | }); 177 | const synth = Testing.synth(p); 178 | ['package.json', 'src/github-actions-oidc-permissions.ts'].forEach((file) => { 179 | test(file, () => { 180 | expect(synth[file]).toMatchSnapshot(); 181 | }); 182 | }); 183 | }); 184 | 185 | const serviceInfo = [ 186 | { 187 | serviceName: 'test-service', 188 | description: 'test description test-service 1', 189 | application: 'clickup', 190 | tier: 'critical', 191 | lifecycle: 'unit-test', 192 | team: 'testing', 193 | pagerdutyUrl: 'https://test.pagerduty.com', 194 | }, 195 | ]; 196 | 197 | const contacts = [ 198 | { 199 | name: 'contact test', 200 | type: datadogServiceCatalog.ContactType.EMAIL, 201 | contact: 'contacttest@clickup.com', 202 | }, 203 | ]; 204 | 205 | const links = [ 206 | { 207 | name: 'link test', 208 | type: datadogServiceCatalog.LinkType.OTHER, 209 | url: 'https://test.clickup.com', 210 | }, 211 | ]; 212 | 213 | test('sending service catalog options serviceInfo', () => { 214 | const project = new clickupCdk.ClickUpCdkTypeScriptApp({ 215 | ...requiredParams, 216 | serviceCatalogOptions: { 217 | serviceInfo: serviceInfo, 218 | }, 219 | }); 220 | const synth = Testing.synth(project); 221 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 222 | }); 223 | 224 | test('sending service catalog options serviceInfo and contacts', () => { 225 | const project = new clickupCdk.ClickUpCdkTypeScriptApp({ 226 | ...requiredParams, 227 | serviceCatalogOptions: { 228 | serviceInfo: serviceInfo, 229 | contacts: contacts, 230 | }, 231 | }); 232 | const synth = Testing.synth(project); 233 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 234 | }); 235 | 236 | test('sending service catalog options serviceInfo, contacts and links', () => { 237 | const project = new clickupCdk.ClickUpCdkTypeScriptApp({ 238 | ...requiredParams, 239 | serviceCatalogOptions: { 240 | serviceInfo: serviceInfo, 241 | contacts: contacts, 242 | links: links, 243 | }, 244 | }); 245 | const synth = Testing.synth(project); 246 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 247 | }); 248 | 249 | test('sending service catalog options with default values in serviceInfo, contacts and links', () => { 250 | const project = new clickupCdk.ClickUpCdkTypeScriptApp({ 251 | ...requiredParams, 252 | serviceCatalogOptions: { 253 | serviceInfo: [ 254 | { 255 | team: 'testing', 256 | }, 257 | ], 258 | }, 259 | }); 260 | const synth = Testing.synth(project); 261 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 262 | }); 263 | 264 | test('without service catalog options', () => { 265 | const project = new clickupCdk.ClickUpCdkTypeScriptApp({ 266 | ...requiredParams, 267 | }); 268 | const synth = Testing.synth(project); 269 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 270 | }); 271 | -------------------------------------------------------------------------------- /test/clickup-ts.test.ts: -------------------------------------------------------------------------------- 1 | import { Testing, javascript } from 'projen'; 2 | import { clickupTs } from '../src'; 3 | 4 | describe('ClickUpTypeScriptProject', () => { 5 | describe('defaults', () => { 6 | const p = new clickupTs.ClickUpTypeScriptProject({ 7 | name: '@time-loop/test', 8 | defaultReleaseBranch: 'main', 9 | }); 10 | const synth = Testing.synth(p); 11 | // console.log(Object.getOwnPropertyNames(synth)); 12 | [ 13 | 'codecov.yml', 14 | 'package.json', 15 | '.github/workflows/upgrade-main.yml', 16 | '.mergify.yml', 17 | '.github/workflows/release.yml', 18 | ].forEach((f) => { 19 | test(f, () => { 20 | expect(synth[f]).toMatchSnapshot(); 21 | }); 22 | }); 23 | test('prettier is enabled', () => { 24 | expect(p.prettier).toBeTruthy(); 25 | }); 26 | test('jest is enabled', () => { 27 | expect(p.jest).toBeTruthy(); 28 | }); 29 | test('min node version is 18', () => { 30 | expect(synth['package.json'].engines.node).toBe('>= 18.17.1'); 31 | }); 32 | // TODO: soooo many more tests need to be written here. 33 | }); 34 | describe('name', () => { 35 | let envCache = process.env; 36 | 37 | beforeEach(() => { 38 | process.env = envCache; 39 | }); 40 | 41 | test('should add prefix when missing', () => { 42 | jest.spyOn(console, 'log'); 43 | const p = new clickupTs.ClickUpTypeScriptProject({ 44 | name: 'missing-prefix', 45 | defaultReleaseBranch: 'main', 46 | }); 47 | expect(p.name).toBe('@time-loop/missing-prefix'); 48 | }); 49 | 50 | test('should respect GITHUB_OWNER', () => { 51 | jest.spyOn(console, 'log'); 52 | process.env.GITHUB_OWNER = 'fizzle'; 53 | const p = new clickupTs.ClickUpTypeScriptProject({ 54 | name: 'missing-prefix', 55 | defaultReleaseBranch: 'main', 56 | }); 57 | expect(p.name).toBe('@fizzle/missing-prefix'); 58 | }); 59 | }); 60 | describe('docgen', () => { 61 | const projenDocgenTaskName = 'docgen'; 62 | const clickupDocgenTaskName = 'typedocDocgen'; 63 | const commonOpts: clickupTs.ClickUpTypeScriptProjectOptions = { 64 | name: 'random-prefix', 65 | defaultReleaseBranch: 'main', 66 | }; 67 | 68 | test('succeeds with defaults', () => { 69 | new clickupTs.ClickUpTypeScriptProject(commonOpts); 70 | }); 71 | 72 | test('includes markdown plugin by default', () => { 73 | const project = new clickupTs.ClickUpTypeScriptProject(commonOpts); 74 | expect(project.deps.getDependency('typedoc-plugin-markdown')).toBeDefined(); 75 | }); 76 | 77 | test('excludes markdown plugin when html attr is set', () => { 78 | const project = new clickupTs.ClickUpTypeScriptProject({ ...commonOpts, docgenOptions: { html: true } }); 79 | const getDep = () => { 80 | project.deps.getDependency('typedoc-plugin-markdown'); 81 | }; 82 | expect(getDep).toThrowError(); 83 | }); 84 | 85 | test('includes typedocDocgen task by default', () => { 86 | const project = new clickupTs.ClickUpTypeScriptProject(commonOpts); 87 | expect(project.tasks.tryFind(clickupDocgenTaskName)).toBeDefined(); 88 | expect(project.tasks.tryFind(projenDocgenTaskName)).toBeUndefined(); 89 | }); 90 | 91 | test('contains no docgen task when docgen is false', () => { 92 | const project = new clickupTs.ClickUpTypeScriptProject({ ...commonOpts, docgen: false }); 93 | [clickupDocgenTaskName, projenDocgenTaskName].forEach((task) => 94 | expect(project.tasks.tryFind(task)).toBeUndefined(), 95 | ); 96 | }); 97 | 98 | test('creates typedoc config JSON file', () => { 99 | const project = new clickupTs.ClickUpTypeScriptProject(commonOpts); 100 | const matchedFiles = project.files.filter((file) => file.path === 'typedoc.json'); 101 | expect(matchedFiles).toHaveLength(1); 102 | }); 103 | 104 | test('respects configFilePath attr when set', () => { 105 | const testOverride = 'test.json'; 106 | const project = new clickupTs.ClickUpTypeScriptProject({ 107 | ...commonOpts, 108 | docgenOptions: { configFilePath: testOverride }, 109 | }); 110 | const matchedFiles = project.files.filter((file) => file.path === testOverride); 111 | expect(matchedFiles).toHaveLength(1); 112 | }); 113 | }); 114 | describe('pnpm', () => { 115 | const p = new clickupTs.ClickUpTypeScriptProject({ 116 | name: '@time-loop/test', 117 | defaultReleaseBranch: 'main', 118 | packageManager: javascript.NodePackageManager.PNPM, 119 | pnpmVersion: '9', 120 | }); 121 | const synth = Testing.synth(p); 122 | it('packageManager', () => { 123 | expect(synth['package.json'].packageManager).toBe('pnpm@9.15.5'); 124 | }); 125 | 126 | const npmrcEntries = [ 127 | 'package-manager-strict=false', 128 | 'manage-package-manager-versions=true', 129 | 'use-node-version=22.14.0', 130 | ]; 131 | it.each(npmrcEntries)('%s', (npmrcEntry) => { 132 | expect(synth['.npmrc']).toContain(npmrcEntry); 133 | }); 134 | }); 135 | 136 | describe('workflowNodeVersion', () => { 137 | const p = new clickupTs.ClickUpTypeScriptProject({ 138 | name: '@time-loop/test', 139 | defaultReleaseBranch: 'main', 140 | packageManager: javascript.NodePackageManager.PNPM, 141 | pnpmVersion: '9', 142 | workflowNodeVersion: '22.0.0', // test explicit downgrade 143 | }); 144 | const synth = Testing.synth(p); 145 | it('use-node-version in .npmrc file', () => { 146 | expect(synth['.npmrc']).toContain('use-node-version=22.0.0'); 147 | }); 148 | }); 149 | }); 150 | 151 | describe('normalizeName', () => { 152 | const envCache = process.env; 153 | 154 | beforeEach(() => { 155 | process.env = envCache; 156 | delete process.env.GITHUB_OWNER; 157 | }); 158 | 159 | test('should silently pass when prefix is present', () => { 160 | const logSpy = jest.spyOn(console, 'log'); 161 | const name = clickupTs.normalizeName('@time-loop/has-prefix'); 162 | expect(name).toBe('@time-loop/has-prefix'); 163 | expect(logSpy).not.toHaveBeenCalled(); 164 | }); 165 | 166 | test('should add prefix and complain when missing', () => { 167 | const logSpy = jest.spyOn(console, 'log'); 168 | const name = clickupTs.normalizeName('missing-prefix'); 169 | expect(name).toBe('@time-loop/missing-prefix'); 170 | expect(logSpy).toHaveBeenCalledWith( 171 | 'Adding mandatory prefix @time-loop/ to name. New name: @time-loop/missing-prefix', 172 | ); 173 | }); 174 | 175 | test('should respect GITHUB_OWNER', () => { 176 | const logSpy = jest.spyOn(console, 'log'); // silence console.log 177 | process.env.GITHUB_OWNER = 'fizzle'; 178 | const name = clickupTs.normalizeName('missing-prefix'); 179 | expect(name).toBe('@fizzle/missing-prefix'); 180 | expect(logSpy).toHaveBeenCalled(); 181 | }); 182 | }); 183 | -------------------------------------------------------------------------------- /test/codecov-bypass-workflow.test.ts: -------------------------------------------------------------------------------- 1 | import { Testing } from 'projen'; 2 | 3 | import { requiredParams } from './requiredParams'; 4 | import { clickupCdk } from '../src/clickup-cdk'; 5 | import { codecovBypassWorkflow } from '../src/codecov-bypass-workflow'; 6 | 7 | describe('addCodecovBypassWorkflowYml - codecov-bypass .yml file added', () => { 8 | test('all default options', () => { 9 | const project = new clickupCdk.ClickUpCdkTypeScriptApp(requiredParams); 10 | codecovBypassWorkflow.addCodecovBypassWorkflowYml(project); 11 | const synth = Testing.synth(project); 12 | expect(synth['.github/workflows/codecov-bypass.yml']).toMatchSnapshot(); 13 | }); 14 | 15 | test('all options provided', () => { 16 | const project = new clickupCdk.ClickUpCdkTypeScriptApp(requiredParams); 17 | codecovBypassWorkflow.addCodecovBypassWorkflowYml(project, { 18 | workflowName: 'workflowName', 19 | githubAppId: '${{ vars.GH_APP_ID_VAR_NAME }}', 20 | githubAppPrivateKey: 'r${{ secrets.GH_APP_ID_PRIVATE_KEY_SECRET_NAME }}', 21 | skipLabel: 'skipLabel', 22 | checkName: 'checkName', 23 | checkSuiteCheckNames: ['checkSuiteCheckName1', 'checkSuiteCheckName2'], 24 | detailsUrl: 'some url', 25 | }); 26 | const synth = Testing.synth(project); 27 | expect(synth['.github/workflows/codecov-bypass.yml']).toMatchSnapshot(); 28 | }); 29 | 30 | test('disabled', () => { 31 | const project = new clickupCdk.ClickUpCdkTypeScriptApp(requiredParams); 32 | codecovBypassWorkflow.addCodecovBypassWorkflowYml(project, { 33 | disabled: true, 34 | }); 35 | const synth = Testing.synth(project); 36 | expect(synth['.github/workflows/codecov-bypass.yml']).toBeUndefined(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/codecov.test.ts: -------------------------------------------------------------------------------- 1 | import { typescript, Testing } from 'projen'; 2 | 3 | import { codecov } from '../src/codecov'; 4 | 5 | describe('addCodeCovYml', () => { 6 | test('file added', () => { 7 | const project = new typescript.TypeScriptProject({ 8 | defaultReleaseBranch: 'main', 9 | name: 'test', 10 | }); 11 | codecov.addCodeCovYml(project); 12 | const synth = Testing.synth(project); 13 | expect(synth['codecov.yml']).toMatchSnapshot(); 14 | }); 15 | test('override', () => { 16 | const project = new typescript.TypeScriptProject({ 17 | defaultReleaseBranch: 'main', 18 | name: 'test', 19 | }); 20 | codecov.addCodeCovYml(project, { foo: 'bar' }); 21 | const synth = Testing.synth(project); 22 | expect(synth['codecov.yml']).toMatchSnapshot(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/datadog-service-catalog.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { typescript, Testing } from 'projen'; 3 | import { datadogServiceCatalog } from '../src/datadog-service-catalog'; 4 | 5 | describe('addReleaseEvent', () => { 6 | const commonProjectOpts: typescript.TypeScriptProjectOptions = { 7 | defaultReleaseBranch: 'main', 8 | name: 'test-cdk', 9 | }; 10 | const serviceCatalogOptions: datadogServiceCatalog.ServiceCatalogOptions = { 11 | serviceInfo: [ 12 | { 13 | serviceName: 'test-service', 14 | description: 'test description test-service 1', 15 | application: 'clickup', 16 | tier: 'critical', 17 | lifecycle: 'unit-test', 18 | team: 'testing', 19 | pagerdutyUrl: 'https://test.pagerduty.com', 20 | }, 21 | { 22 | team: 'testing', 23 | }, 24 | ], 25 | }; 26 | const serviceCatalogOptionsWithAllParameters: datadogServiceCatalog.ServiceCatalogOptions = { 27 | serviceInfo: [ 28 | { 29 | serviceName: 'test-service', 30 | description: 'test description test-service 1', 31 | application: 'clickup', 32 | tier: 'critical', 33 | pagerdutyUrl: 'https://test.pagerduty.com', 34 | }, 35 | ], 36 | contacts: [ 37 | { 38 | name: 'contact test', 39 | type: datadogServiceCatalog.ContactType.EMAIL, 40 | contact: 'contacttest@clickup.com', 41 | }, 42 | ], 43 | links: [ 44 | { 45 | name: 'link test', 46 | type: datadogServiceCatalog.LinkType.OTHER, 47 | url: 'https://test.clickup.com', 48 | }, 49 | { 50 | name: 'staging link test', 51 | type: datadogServiceCatalog.LinkType.OTHER, 52 | url: 'https://staging.clickup.com', 53 | }, 54 | ], 55 | serviceTags: { tag1: 'tag1-test', tag2: 'tag2-test' }, 56 | }; 57 | 58 | test('adds job to release workflow with service catalog options', () => { 59 | const project = new typescript.TypeScriptProject(commonProjectOpts); 60 | datadogServiceCatalog.addServiceCatalogEvent(project, serviceCatalogOptions); 61 | const synth = Testing.synth(project); 62 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 63 | }); 64 | 65 | test('add send_service_catalog job with all parameters', () => { 66 | const project = new typescript.TypeScriptProject(commonProjectOpts); 67 | datadogServiceCatalog.addServiceCatalogEvent(project, serviceCatalogOptionsWithAllParameters); 68 | const synth = Testing.synth(project); 69 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/datadog.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { typescript, Testing } from 'projen'; 3 | import { datadog } from '../src/datadog'; 4 | 5 | // Would be cool to find a way to not use snapshots here, without getting crazy 6 | describe('addReleaseEvent', () => { 7 | const commonProjectOpts: typescript.TypeScriptProjectOptions = { 8 | defaultReleaseBranch: 'main', 9 | name: 'test-cdk', 10 | }; 11 | test('adds job to release workflow', () => { 12 | const project = new typescript.TypeScriptProject(commonProjectOpts); 13 | datadog.addReleaseEvent(project); 14 | const synth = Testing.synth(project); 15 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 16 | }); 17 | test('performs no-op when release option is false', () => { 18 | const project = new typescript.TypeScriptProject({ 19 | ...commonProjectOpts, 20 | release: false, 21 | }); 22 | datadog.addReleaseEvent(project); 23 | const synth = Testing.synth(project); 24 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 25 | }); 26 | test('adds additional tags when passed', () => { 27 | const project = new typescript.TypeScriptProject(commonProjectOpts); 28 | datadog.addReleaseEvent(project, { eventTags: { TestKey: 'TestVal', TestKey2: 'TestVal2' } }); 29 | const synth = Testing.synth(project); 30 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 31 | }); 32 | test('honors overrides', () => { 33 | const project = new typescript.TypeScriptProject(commonProjectOpts); 34 | const opts: datadog.ReleaseEventOptions = { 35 | eventPriority: 'low', 36 | eventTitle: 'Test title', 37 | eventText: 'Test event text', 38 | datadogApiKey: 'TEST_API_KEY', 39 | datadogUs: false, 40 | eventTags: { 41 | TestTag1: 'TestTagVal1', 42 | TestTag2: 'TestTagVal2', 43 | }, 44 | }; 45 | datadog.addReleaseEvent(project, opts); 46 | const synth = Testing.synth(project); 47 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/node-version.test.ts: -------------------------------------------------------------------------------- 1 | import { typescript, Testing } from 'projen'; 2 | 3 | import { nodeVersion } from '../src/node-version'; 4 | 5 | describe('addNodeVersionFile', () => { 6 | test('file added', () => { 7 | const project = new typescript.TypeScriptProject({ 8 | defaultReleaseBranch: 'main', 9 | name: 'test', 10 | }); 11 | nodeVersion.addNodeVersionFile(project); 12 | const synth = Testing.synth(project); 13 | expect(synth['.nvmrc']).toMatchSnapshot(); 14 | }); 15 | 16 | test('node20', () => { 17 | const project = new typescript.TypeScriptProject({ 18 | defaultReleaseBranch: 'main', 19 | name: 'test', 20 | workflowNodeVersion: '20.5.1', 21 | minNodeVersion: '20.5.1', 22 | }); 23 | nodeVersion.addNodeVersionFile(project); 24 | const synth = Testing.synth(project); 25 | expect(synth['.nvmrc']).toMatchSnapshot(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/renovate-workflow.test.ts: -------------------------------------------------------------------------------- 1 | import { typescript, Testing } from 'projen'; 2 | 3 | import { renovateWorkflow } from '../src/renovate-workflow'; 4 | 5 | describe('addRenovateWorkflowYml', () => { 6 | test('file added', () => { 7 | const project = new typescript.TypeScriptProject({ 8 | defaultReleaseBranch: 'main', 9 | name: 'test', 10 | }); 11 | renovateWorkflow.addRenovateWorkflowYml(project); 12 | const synth = Testing.synth(project); 13 | expect(synth['.github/workflows/renovate.yml']).toMatchSnapshot(); 14 | }); 15 | test('override', () => { 16 | const project = new typescript.TypeScriptProject({ 17 | defaultReleaseBranch: 'main', 18 | name: 'test', 19 | }); 20 | renovateWorkflow.addRenovateWorkflowYml(project, { foo: 'bar' }); 21 | const synth = Testing.synth(project); 22 | expect(synth['.github/workflows/renovate.yml']).toMatchSnapshot(); 23 | }); 24 | test('schedule time based on project name', () => { 25 | const project = new typescript.TypeScriptProject({ 26 | defaultReleaseBranch: 'main', 27 | name: 'another-project', 28 | }); 29 | renovateWorkflow.addRenovateWorkflowYml(project); 30 | const synth = Testing.synth(project); 31 | expect(synth['.github/workflows/renovate.yml']).toContain('cron: 55 0 * * MON-FRI'); 32 | }); 33 | }); 34 | 35 | describe('getRenovateOptions', () => { 36 | test('defaults', () => { 37 | const options = renovateWorkflow.getRenovateOptions(); 38 | expect(options).toMatchSnapshot(); 39 | }); 40 | 41 | test('merges options', () => { 42 | const options = renovateWorkflow.getRenovateOptions({ defaultOverrides: { overrideConfig: { dryRun: true } } }); 43 | // custom option we set 44 | expect(options.overrideConfig.dryRun).toBe(true); 45 | // default option that should be deep merged in 46 | expect(options.overrideConfig.rangeStrategy).toBe('bump'); 47 | }); 48 | 49 | test('auto merge off by default', () => { 50 | const options = renovateWorkflow.getRenovateOptions(); 51 | expect(options.overrideConfig.packageRules[0].automerge).toBeUndefined(); 52 | expect(options.overrideConfig.packageRules[0].addLabels[0]).toBeUndefined(); 53 | expect(options.labels).toEqual([renovateWorkflow.DEFAULT_RENOVATE_PR_LABEL]); 54 | }); 55 | 56 | test('auto merge on', () => { 57 | const options = renovateWorkflow.getRenovateOptions({ autoMergeNonBreakingUpdates: true }); 58 | expect(options.overrideConfig.packageRules[0].automerge).toBe(true); 59 | expect(options.overrideConfig.packageRules[0].addLabels[0]).toEqual(renovateWorkflow.AUTO_APPROVE_PR_LABEL); 60 | expect(options.labels).toEqual([renovateWorkflow.DEFAULT_RENOVATE_PR_LABEL]); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/requiredParams.ts: -------------------------------------------------------------------------------- 1 | export const requiredParams = { 2 | name: 'test', 3 | cdkVersion: '2.138.0', 4 | defaultReleaseBranch: 'main', 5 | }; 6 | -------------------------------------------------------------------------------- /test/slack-alert.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { typescript, Testing } from 'projen'; 3 | import { slackAlert } from '../src/slack-alert'; 4 | 5 | // Would be cool to find a way to not use snapshots here, without getting crazy 6 | describe('addReleaseEvent', () => { 7 | const commonProjectOpts: typescript.TypeScriptProjectOptions = { 8 | defaultReleaseBranch: 'main', 9 | name: 'test-cdk', 10 | }; 11 | test('adds job to release workflow', () => { 12 | const project = new typescript.TypeScriptProject(commonProjectOpts); 13 | slackAlert.addReleaseEvent(project); 14 | const synth = Testing.synth(project); 15 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 16 | }); 17 | test('performs no-op when release option is false', () => { 18 | const project = new typescript.TypeScriptProject({ 19 | ...commonProjectOpts, 20 | release: false, 21 | }); 22 | slackAlert.addReleaseEvent(project); 23 | const synth = Testing.synth(project); 24 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 25 | }); 26 | test('honors overrides', () => { 27 | const project = new typescript.TypeScriptProject(commonProjectOpts); 28 | const opts: slackAlert.ReleaseEventOptions = { 29 | messageTitle: 'title', 30 | messageBody: 'body', 31 | }; 32 | slackAlert.addReleaseEvent(project, opts); 33 | const synth = Testing.synth(project); 34 | expect(synth[path.join('.github', 'workflows', 'release.yml')]).toMatchSnapshot(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/update-projen.test.ts: -------------------------------------------------------------------------------- 1 | import { typescript, Testing } from 'projen'; 2 | 3 | import { updateProjen } from '../src/update-projen'; 4 | 5 | describe('addAddToProjectWorkflowYml', () => { 6 | test('file added', () => { 7 | const project = new typescript.TypeScriptProject({ 8 | defaultReleaseBranch: 'main', 9 | name: 'test', 10 | }); 11 | updateProjen.addWorkflows(project); 12 | const synth = Testing.synth(project); 13 | expect(synth['.github/workflows/add-to-update-projen-project.yml']).toMatchSnapshot(); 14 | expect(synth['.github/workflows/update-projen-main.yml']).toMatchSnapshot(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /triggerRenovate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | NC='\033[0m' # No Color 7 | 8 | # Verifies GH CLI tool is installed 9 | function testGhCli() { 10 | if which gh 2>&1 >/dev/null; then 11 | echo -e 'Verified gh CLI tool is accessible.'; 12 | else 13 | echo -e "${RED}gh CLI tool not found.${NC} See https://cli.github.com/ for installation instructions."; 14 | exit 1; 15 | fi 16 | } 17 | 18 | # Finds all repos with a given suffix under REPO_OWNER org, then iterates over 19 | # them triggering a `renovate` workflow for each. 20 | function runUpgradeMain() { 21 | local owner="${REPO_OWNER}"; 22 | local repos=($(gh repo list "${owner}" --limit "${REPO_LIMIT}" --no-archived --json name -q ".[].name | select(endswith(\"${REPO_SUFFIX}\"))")); 23 | RC=0; # return code counts total number of failures 24 | for repo in ${repos[@]}; do 25 | echo "Triggering renovate workflow on repo ${repo}"; 26 | if gh workflow run --repo "${owner}/${repo}" --ref main renovate.yml; then 27 | sleep 2; # Give GitHub enough time to register the workflow_dispatch event 28 | echo 'Verifying workflow_dispatch event was actually created...'; 29 | local runningCount=$(gh run list --repo "${owner}/${repo}" --workflow=renovate.yml --json 'name,status' -q '.[0] | select((.status=="in_progress") or (.status=="queued"))' | wc -l); 30 | test "${runningCount}" -eq 0 && echo -e "${RED}Could not find trigger for renovate workflow on repo ${repo}.${NC}\n" && RC=$(($RC + 1)) \ 31 | || echo -e "${GREEN}Trigger succeeded. Continuing...${NC}\n"; 32 | else 33 | RC=$(($RC + 1)); 34 | echo -e "${RED}Trigger for renovate workflow on repo ${repo} failed.${NC}\n"; 35 | fi 36 | # Lets not slam the Github API and get rate limited 37 | sleep 5 38 | done 39 | echo -e "\nTotal failure count is: ${RC}"; 40 | return $RC; 41 | } 42 | 43 | # Entrypoint 44 | 45 | # Grab necessary vars 46 | REPO_SUFFIX=${REPO_SUFFIX:-'-cdk'}; 47 | REPO_LIMIT=${REPO_LIMIT:-250}; 48 | REPO_OWNER=${REPO_OWNER:-'time-loop'}; 49 | 50 | # Fire away. 51 | testGhCli; 52 | runUpgradeMain; 53 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------