diff --git a/.gitattributes b/.gitattributes index d0a54e0f..78ce0383 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,6 +10,7 @@ /.github/workflows/lock.yml linguist-generated /.github/workflows/pull-request-lint.yml linguist-generated /.github/workflows/release.yml linguist-generated +/.github/workflows/upgrade-jsii-typescript.yml linguist-generated /.github/workflows/upgrade-main.yml linguist-generated /.github/workflows/upgrade-node.yml linguist-generated /.gitignore linguist-generated diff --git a/.github/workflows/upgrade-jsii-typescript.yml b/.github/workflows/upgrade-jsii-typescript.yml new file mode 100644 index 00000000..45dd0e67 --- /dev/null +++ b/.github/workflows/upgrade-jsii-typescript.yml @@ -0,0 +1,109 @@ +# ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". + +name: upgrade-jsii-typescript +on: + schedule: + - cron: 52 3 * * * + workflow_dispatch: + inputs: + new_version: + description: New JSII/TypeScript version (e.g. "5.4.0"), without carets or tildes + required: false + type: string +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} +jobs: + version: + name: Determine version to upgrade to + runs-on: ubuntu-latest + permissions: + contents: read + actions: write + outputs: + value: ${{ steps.latest_version.outputs.value }} + short: ${{ steps.latest_version.outputs.short }} + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - name: Setup Node.js + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af + with: + node-version: 18.12.0 + - name: Install + run: yarn install + - name: Get current JSII version + run: |- + CURRENT_VERSION=$(npm list jsii --depth=0 --json | jq -r '.dependencies.jsii.version') + CURRENT_VERSION_SHORT=$(cut -d "." -f 1,2 <<< "$CURRENT_VERSION") + CURRENT_VERSION_MAJOR=$(cut -d "." -f 1 <<< "$CURRENT_VERSION") + CURRENT_VERSION_MINOR=$(cut -d "." -f 2 <<< "$CURRENT_VERSION") + echo "CURRENT_JSII_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV + echo "CURRENT_JSII_VERSION_SHORT=$CURRENT_VERSION_SHORT" >> $GITHUB_ENV + echo "CURRENT_JSII_VERSION_MAJOR=$CURRENT_VERSION_MAJOR" >> $GITHUB_ENV + echo "CURRENT_JSII_VERSION_MINOR=$CURRENT_VERSION_MINOR" >> $GITHUB_ENV + - name: Get the earliest supported JSII version whose EOS date is at least a month away + if: ${{ ! inputs.new_version }} + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + script: |- + const script = require('./projenrc/scripts/check-jsii-versions.js') + await script({github, context, core}) + - name: Save the manually-input version to environment variables for comparison + if: ${{ inputs.new_version }} + env: + NEW_VERSION: ${{ inputs.new_version }} + run: |- + NEW_VERSION_SHORT=$(cut -d "." -f 1,2 <<< "$NEW_VERSION") + NEW_VERSION_MAJOR=$(cut -d "." -f 1 <<< "$NEW_VERSION") + NEW_VERSION_MINOR=$(cut -d "." -f 2 <<< "$NEW_VERSION") + echo "NEW_JSII_VERSION=$NEW_VERSION" >> $GITHUB_ENV + echo "NEW_JSII_VERSION_SHORT=$NEW_VERSION_SHORT" >> $GITHUB_ENV + echo "NEW_JSII_VERSION_MAJOR=$NEW_VERSION_MAJOR" >> $GITHUB_ENV + echo "NEW_JSII_VERSION_MINOR=$NEW_VERSION_MINOR" >> $GITHUB_ENV + - name: Cancel the rest of the run if the desired version isn't newer than what we have installed already + if: ${{ !((env.NEW_JSII_VERSION_MAJOR > env.CURRENT_JSII_VERSION_MAJOR) || (env.NEW_JSII_VERSION_MINOR > env.CURRENT_JSII_VERSION_MINOR)) }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUN_ID: ${{ github.run_id }} + run: |- + gh run cancel $RUN_ID + gh run watch $RUN_ID + - name: Output env variables for use in the next job + id: latest_version + run: |- + echo "value=$NEW_JSII_VERSION" >> $GITHUB_OUTPUT + echo "short=$NEW_JSII_VERSION_SHORT" >> $GITHUB_OUTPUT + upgrade: + name: Upgrade JSII & TypeScript + needs: version + runs-on: ubuntu-latest + permissions: + contents: read + env: + CI: "true" + CHECKPOINT_DISABLE: "1" + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - name: Setup Node.js + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af + with: + node-version: 18.12.0 + - name: Install + run: yarn install + - name: Run upgrade script + run: projenrc/scripts/update-jsii-typescript.sh ${{ needs.version.outputs.value }} + - name: Create Pull Request + uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f + with: + branch: auto/upgrade-jsii-ts-${{ needs.version.outputs.short }} + base: main + commit-message: "chore: upgrade jsii & typescript to v${{ needs.version.outputs.short }}" + title: "chore: upgrade jsii & typescript to v${{ needs.version.outputs.short }}" + body: "This PR increases the version of JSII and TypeScript to `~${{ needs.version.outputs.value }}` because the previous version is close to EOL or no longer supported. Support timeline: https://github.com/aws/jsii-compiler/blob/main/README.md#gear-maintenance--support" + labels: auto-approve,automerge,automated + token: ${{ secrets.PROJEN_GITHUB_TOKEN }} + author: team-tf-cdk + committer: team-tf-cdk + signoff: true + delete-branch: true diff --git a/.gitignore b/.gitignore index 8aa02561..0fcd3eda 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,6 @@ tsconfig.json !/.github/workflows/lock.yml !/.github/workflows/auto-approve.yml !/.github/workflows/automerge.yml +!/.github/workflows/upgrade-jsii-typescript.yml !/.github/workflows/upgrade-node.yml !/.projenrc.ts diff --git a/.projen/files.json b/.projen/files.json index ba0aae95..00986e07 100644 --- a/.projen/files.json +++ b/.projen/files.json @@ -8,6 +8,7 @@ ".github/workflows/lock.yml", ".github/workflows/pull-request-lint.yml", ".github/workflows/release.yml", + ".github/workflows/upgrade-jsii-typescript.yml", ".github/workflows/upgrade-main.yml", ".github/workflows/upgrade-node.yml", ".gitignore", diff --git a/.projenrc.ts b/.projenrc.ts index 722ff29f..3611faf3 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -5,6 +5,7 @@ import { cdk } from "projen"; import { UpgradeDependenciesSchedule } from "projen/lib/javascript"; +import { UpgradeJSIIAndTypeScript } from "./projenrc/upgrade-jsii-typescript"; import { UpgradeNode } from "./projenrc/upgrade-node"; import { AutoApprove } from "./src/auto-approve"; import { Automerge } from "./src/automerge"; @@ -29,6 +30,8 @@ const githubActionPinnedVersions = { "peter-evans/create-pull-request": "67ccf781d68cd99b580ae25a5c18a1cc84ffff1f", // v7.0.6 }; +/** JSII and TS should always use the same major/minor version range */ +const typescriptVersion = "~5.4.0"; const project = new cdk.JsiiProject({ name: "@cdktf/provider-project", author: "HashiCorp", @@ -37,8 +40,8 @@ const project = new cdk.JsiiProject({ authorOrganization: true, licensed: false, // we do supply our own license file with a custom header pullRequestTemplate: false, - jsiiVersion: "~5.4.0", - typescriptVersion: "~5.4.0", // should always be the same major/minor as JSII + typescriptVersion, + jsiiVersion: typescriptVersion, peerDeps: ["projen@^0.87.4", "constructs@^10.4.2"], deps: ["change-case", "fs-extra"], bundledDeps: ["change-case", "fs-extra"], @@ -107,6 +110,7 @@ new CustomizedLicense(project, 2020); new LockIssues(project); new AutoApprove(project); new Automerge(project); +new UpgradeJSIIAndTypeScript(project, typescriptVersion); new UpgradeNode(project); project.addPackageIgnore("projenrc"); diff --git a/projenrc/scripts/check-jsii-versions.js b/projenrc/scripts/check-jsii-versions.js new file mode 100755 index 00000000..a964cf0d --- /dev/null +++ b/projenrc/scripts/check-jsii-versions.js @@ -0,0 +1,42 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ +const today = new Date(); +const oneMonthFromToday = new Date(); +oneMonthFromToday.setDate(today.getDate() + 30); +// console.debug("oneMonthFromToday", oneMonthFromToday.toDateString()); + +/** Return the earliest supported version whose EOS date is at least a month away */ +async function getEarliestSupportedVersion() { + // https://github.com/aws/jsii-compiler/blob/main/releases.json + const response = await fetch("https://raw.githubusercontent.com/aws/jsii-compiler/main/releases.json"); + const data = await response.json(); + const activelySupportedVersions = Object.entries(data.maintenance).filter(([version, supportEndDate]) => { + return new Date(supportEndDate) > oneMonthFromToday; + }).sort((a, b) => { + // Very naive sorting function: treat "5.4" like (int) 54, "5.5" like (int) 55, etc. and compare accordingly + return parseInt(a[0].replace(".", ""), 10) > parseInt(b[0].replace(".", ""), 10); + }); + + console.debug("Actively supported versions with an EOS date at least 1 month away") + console.debug(Object.fromEntries(activelySupportedVersions)); + + return activelySupportedVersions[0][0]; +} + +async function getDesiredVersion() { + const earliestSupportedVersion = await getEarliestSupportedVersion(); + console.debug("earliestSupportedVersion", earliestSupportedVersion); + + return earliestSupportedVersion; +} + +module.exports = async ({github, context, core}) => { + const version = await getDesiredVersion(); + + core.exportVariable('NEW_JSII_VERSION', version + ".0"); // e.g. "5.4.0" + core.exportVariable('NEW_JSII_VERSION_SHORT', version); // e.g. "5.4" + core.exportVariable('NEW_JSII_VERSION_MAJOR', version.split(".")[0]); // e.g. "5" + core.exportVariable('NEW_JSII_VERSION_MINOR', version.split(".")[1]); // e.g. "4" +} diff --git a/projenrc/scripts/upgrade-jsii-typescript.sh b/projenrc/scripts/upgrade-jsii-typescript.sh new file mode 100755 index 00000000..2bf594b6 --- /dev/null +++ b/projenrc/scripts/upgrade-jsii-typescript.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +set -ex + +PROJECT_ROOT=$(cd "$(dirname "${BASH_SOURCE:-$0}")/.." && pwd) +NEW_VERSION=$1 + +if [ -z "$NEW_VERSION" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Updating JSII & TypeScript version to $NEW_VERSION" +yarn +sed -i "s/typescriptVersion = \".*\";/typescriptVersion = \"~$NEW_VERSION\";/" "$PROJECT_ROOT/.projenrc.ts" +CI=0 npx projen + +echo "Done" diff --git a/projenrc/upgrade-jsii-typescript.ts b/projenrc/upgrade-jsii-typescript.ts new file mode 100644 index 00000000..85fcd747 --- /dev/null +++ b/projenrc/upgrade-jsii-typescript.ts @@ -0,0 +1,199 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { javascript } from "projen"; +import { JobPermission } from "projen/lib/github/workflows-model"; +import { generateRandomCron } from "../src/util/random-cron"; + +/** + * Helper script for upgrading JSII and TypeScript in the right way. + * This currently isn't automated (the workflow must be manually run) + * because there is no way to programmatically determine the EOL date + * of a JSII version range. This can be found at: + * https://github.com/aws/jsii-compiler/blob/main/README.md#gear-maintenance--support + */ +export class UpgradeJSIIAndTypeScript { + constructor(project: javascript.NodeProject, typescriptVersion: string) { + const workflow = project.github?.addWorkflow("upgrade-jsii-typescript"); + if (!workflow) throw new Error("no workflow defined"); + + const plainVersion = typescriptVersion.replace("~", ""); + workflow.on({ + // run daily sometime between midnight and 6am UTC + schedule: [ + { cron: generateRandomCron({ project, maxHour: 3, hourOffset: 1 }) }, + ], + workflowDispatch: { + inputs: { + newVersion: { + description: `New JSII/TypeScript version (e.g. "${plainVersion}"), without carets or tildes`, + required: false, + type: "string", + }, + }, + }, + }); + + (workflow.concurrency as any) = { + group: "${{ github.workflow }}-${{ github.ref }}", + }; + + workflow.addJobs({ + version: { + name: "Determine version to upgrade to", + runsOn: ["ubuntu-latest"], + steps: [ + { + name: "Checkout", + uses: "actions/checkout@v3", + }, + { + name: "Setup Node.js", + uses: "actions/setup-node@v3", + with: { + "node-version": project.minNodeVersion, + }, + }, + { + name: "Install", + run: "yarn install", + }, + { + name: "Get current JSII version", + run: [ + `CURRENT_VERSION=$(npm list jsii --depth=0 --json | jq -r '.dependencies.jsii.version')`, + `CURRENT_VERSION_SHORT=$(cut -d "." -f 1,2 <<< "$CURRENT_VERSION")`, + `CURRENT_VERSION_MAJOR=$(cut -d "." -f 1 <<< "$CURRENT_VERSION")`, + `CURRENT_VERSION_MINOR=$(cut -d "." -f 2 <<< "$CURRENT_VERSION")`, + `echo "CURRENT_JSII_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV`, + `echo "CURRENT_JSII_VERSION_SHORT=$CURRENT_VERSION_SHORT" >> $GITHUB_ENV`, + `echo "CURRENT_JSII_VERSION_MAJOR=$CURRENT_VERSION_MAJOR" >> $GITHUB_ENV`, + `echo "CURRENT_JSII_VERSION_MINOR=$CURRENT_VERSION_MINOR" >> $GITHUB_ENV`, + ].join("\n"), + }, + { + name: "Get the earliest supported JSII version whose EOS date is at least a month away", + if: "${{ ! inputs.new_version }}", // should be newVersion but Projen converts it to snake_case + uses: "actions/github-script@v6", + with: { + script: [ + `const script = require('./projenrc/scripts/check-jsii-versions.js')`, + `await script({github, context, core})`, + ].join("\n"), + }, + }, + { + // In an ideal world this is where we'd validate that the manually-input version actually exists + // In practice, I couldn't figure out how to do this properly and it wasn't worth the effort + // name: "Check if the manually-input version actually exists (has been published to NPM)", + name: "Save the manually-input version to environment variables for comparison", + if: "${{ inputs.new_version }}", // should be newVersion but Projen converts it to snake_case + env: { + NEW_VERSION: "${{ inputs.new_version }}", + }, + run: [ + // My command line skillz aren't good enough to figure out how to make the below work (error if the version doesn't exist) + // `yarn info jsii versions --json | jq -e 'select(.data | index("$NEW_VERSION"))`, + `NEW_VERSION_SHORT=$(cut -d "." -f 1,2 <<< "$NEW_VERSION")`, + `NEW_VERSION_MAJOR=$(cut -d "." -f 1 <<< "$NEW_VERSION")`, + `NEW_VERSION_MINOR=$(cut -d "." -f 2 <<< "$NEW_VERSION")`, + `echo "NEW_JSII_VERSION=$NEW_VERSION" >> $GITHUB_ENV`, + `echo "NEW_JSII_VERSION_SHORT=$NEW_VERSION_SHORT" >> $GITHUB_ENV`, + `echo "NEW_JSII_VERSION_MAJOR=$NEW_VERSION_MAJOR" >> $GITHUB_ENV`, + `echo "NEW_JSII_VERSION_MINOR=$NEW_VERSION_MINOR" >> $GITHUB_ENV`, + ].join("\n"), + }, + { + name: "Cancel the rest of the run if the desired version isn't newer than what we have installed already", + if: "${{ !((env.NEW_JSII_VERSION_MAJOR > env.CURRENT_JSII_VERSION_MAJOR) || (env.NEW_JSII_VERSION_MINOR > env.CURRENT_JSII_VERSION_MINOR)) }}", + env: { + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}", + RUN_ID: "${{ github.run_id }}", + }, + run: "gh run cancel $RUN_ID \ngh run watch $RUN_ID", + }, + { + name: "Output env variables for use in the next job", + id: "latest_version", + run: [ + `echo "value=$NEW_JSII_VERSION" >> $GITHUB_OUTPUT`, + `echo "short=$NEW_JSII_VERSION_SHORT" >> $GITHUB_OUTPUT`, + ].join("\n"), + }, + ], + outputs: { + value: { + stepId: "latest_version", + outputName: "value", + }, + short: { + stepId: "latest_version", + outputName: "short", + }, + }, + permissions: { + contents: JobPermission.READ, + actions: JobPermission.WRITE, + }, + }, + upgrade: { + name: "Upgrade JSII & TypeScript", + runsOn: ["ubuntu-latest"], + needs: ["version"], + steps: [ + { + name: "Checkout", + uses: "actions/checkout@v3", + }, + { + name: "Setup Node.js", + uses: "actions/setup-node@v3", + with: { + "node-version": project.minNodeVersion, + }, + }, + { + name: "Install", + run: "yarn install", + }, + { + name: "Run upgrade script", + run: "projenrc/scripts/update-jsii-typescript.sh ${{ needs.version.outputs.value }}", + }, + { + name: "Create Pull Request", + uses: "peter-evans/create-pull-request@v3", + with: { + branch: "auto/upgrade-jsii-ts-${{ needs.version.outputs.short }}", + base: "main", + "commit-message": + "chore: upgrade jsii & typescript to v${{ needs.version.outputs.short }}", + title: + "chore: upgrade jsii & typescript to v${{ needs.version.outputs.short }}", + body: [ + "This PR increases the version of JSII and TypeScript to `~${{ needs.version.outputs.value }}` ", + "because the previous version is close to EOL or no longer supported. Support timeline: ", + "https://github.com/aws/jsii-compiler/blob/main/README.md#gear-maintenance--support", + ].join(" "), + labels: "auto-approve,automerge,automated", + token: "${{ secrets.PROJEN_GITHUB_TOKEN }}", + author: "team-tf-cdk ", + committer: "team-tf-cdk ", + signoff: true, + "delete-branch": true, + }, + }, + ], + env: { + CI: "true", + CHECKPOINT_DISABLE: "1", + }, + permissions: { + contents: JobPermission.READ, + }, + }, + }); + } +}