From ec3f9adf23114a19282b58209988a55e5374d44e Mon Sep 17 00:00:00 2001 From: Joshua Feingold Date: Wed, 27 Aug 2025 15:39:38 -0500 Subject: [PATCH 1/6] NEW @W-19365711@ Converted package versioning enforcement to git hooks --- .github/workflows/verify-pr.yml | 42 ------------------- .../validate-changed-package-versions.js | 24 ++++++++--- .../validate-package-interdependencies.js | 0 .husky/pre-commit | 6 +++ package.json | 3 +- 5 files changed, 27 insertions(+), 48 deletions(-) rename {.github/workflows/verify-pr => .husky/node-scripts}/validate-changed-package-versions.js (74%) rename {.github/workflows/verify-pr => .husky/node-scripts}/validate-package-interdependencies.js (100%) create mode 100644 .husky/pre-commit diff --git a/.github/workflows/verify-pr.yml b/.github/workflows/verify-pr.yml index 23734056..6f1ce835 100644 --- a/.github/workflows/verify-pr.yml +++ b/.github/workflows/verify-pr.yml @@ -21,48 +21,6 @@ jobs: echo "::error::Invalid PR title: '$title'. Please following the format: POSTRELEASE|FIX|CHANGE|NEW (__) @W-XXXXXXXX@ Summary" exit 1 fi - check_for_postrelease_keyword: - runs-on: macos-latest - outputs: - is-postrelease: ${{ steps.main.outputs.is_postrelease }} - steps: - - name: Check for "Postrelease" keyword in PR title. - id: main - if: github.base_ref == 'dev' - run: | - title="${{ github.event.pull_request.title }}" - if [[ "$title" =~ ^POSTRELEASE ]]; then - echo "is_postrelease=true" >> "$GITHUB_OUTPUT" - else - echo "is_postrelease=false" >> "$GITHUB_OUTPUT" - fi - validate_packages: - runs-on: macos-latest - needs: check_for_postrelease_keyword - steps: - - name: Support long paths - run: git config --global core.longpaths true - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - - name: Validate that changed packages are versioned as snapshots - if: ${{ needs.check_for_postrelease_keyword.outputs.is-postrelease == 'false' }} - run: | - BASE_SHA=${{ github.event.pull_request.base.sha }} - HEAD_SHA=${{ github.event.pull_request.head.sha }} - git diff --name-only $HEAD_SHA $BASE_SHA > changed_files.txt - node ./.github/workflows/verify-pr/validate-changed-package-versions.js changed_files.txt - - name: Validate that packages properly depend on each other - if: ${{ needs.check_for_postrelease_keyword.outputs.is-postrelease == 'false' }} - run: | - cd packages - PACKAGE_NAMES=`ls` - cd .. - node ./.github/workflows/verify-pr/validate-package-interdependencies.js "$PACKAGE_NAMES" run_tests: runs-on: ${{ matrix.os }} strategy: diff --git a/.github/workflows/verify-pr/validate-changed-package-versions.js b/.husky/node-scripts/validate-changed-package-versions.js similarity index 74% rename from .github/workflows/verify-pr/validate-changed-package-versions.js rename to .husky/node-scripts/validate-changed-package-versions.js index f825b6ee..9e1e00e9 100644 --- a/.github/workflows/verify-pr/validate-changed-package-versions.js +++ b/.husky/node-scripts/validate-changed-package-versions.js @@ -1,5 +1,6 @@ const path = require('path'); const fs = require('fs'); +const cp = require('child_process'); function main() { const changedFiles = readChangedFilesFile(process.argv[2]); @@ -39,7 +40,7 @@ function displayList(header, list) { } function readChangedFilesFile(changedFilesFileName) { - return fs.readFileSync(path.join(__dirname, '..', '..', '..', changedFilesFileName), 'utf-8').split('\n').map(s => s.trim()); + return fs.readFileSync(changedFilesFileName, 'utf-8').split('\n').map(s => s.trim()); } function identifyMeaningfullyChangedPackages(changedFiles) { @@ -74,18 +75,31 @@ function isFileInTestFolder(changedFile) { function identifyIncorrectlyVersionedPackages(changedPackages) { const incorrectlyVersionedPackages = []; for (const changedPackage of changedPackages) { - //A temporary workaround for the rename of the flowtest-engine package to flow-engine - if (changedPackage === 'packages/code-analyzer-flowtest-engine') { - continue; - } const packageVersion = getPackageVersion(changedPackage); if (!packageVersion.endsWith('-SNAPSHOT')) { incorrectlyVersionedPackages.push(`${changedPackage} (currently versioned as ${packageVersion}) lacks a trailing "-SNAPSHOT"`); + continue; + } + const releasedPackageVersions = getReleasedPackageVersions(changedPackage); + if (releasedPackageVersions.includes(packageVersion.slice(0, packageVersion.length - 9))) { + incorrectlyVersionedPackages.push(`${changedPackage} (currently versioned as ${packageVersion}) corresponds to an already-published release, and must be incremented.`); } } return incorrectlyVersionedPackages; } +function getReleasedPackageVersions(changedPackage) { + const publishedPackageName = JSON.parse(fs.readFileSync(path.join(changedPackage, 'package.json'), 'utf-8')).name; + try { + const execResult = cp.execSync(`npm view ${publishedPackageName} versions --json`, { + encoding: 'utf-8' + }); + return JSON.parse(execResult); + } catch (e) { + console.log(`NOTE: Could not fetch released versions of ${publishedPackageName} (located in ${changedPackage}. Is that an error?`); + return []; + } +} function getPackageVersion(changedPackage) { const packageJsonPath = path.join(changedPackage, 'package.json'); diff --git a/.github/workflows/verify-pr/validate-package-interdependencies.js b/.husky/node-scripts/validate-package-interdependencies.js similarity index 100% rename from .github/workflows/verify-pr/validate-package-interdependencies.js rename to .husky/node-scripts/validate-package-interdependencies.js diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..6297520e --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +git diff --name-only --cached --diff-filter=ACMR > `pwd`/changed_files.txt +node ./.husky/node-scripts/validate-changed-package-versions.js `pwd`/changed_files.txt +cd packages +PACKAGE_NAMES=`ls` +cd .. +node ./.husky/node-scripts/validate-package-interdependencies.js "$PACKAGE_NAMES" diff --git a/package.json b/package.json index c3872a8f..e4cf84ce 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "scrub": "npm run clean && npm run scrub --workspaces --if-present && rimraf node_modules && rimraf package-lock.json", "showcoverage-java": "npm run showcoverage-java --workspaces --if-present", "showcoverage-typescript": "open ./coverage/lcov-report/index.html", - "showcoverage": "npm run showcoverage-java && npm run showcoverage-typescript" + "showcoverage": "npm run showcoverage-java && npm run showcoverage-typescript", + "prepare": "husky" }, "devDependencies": { "cross-env": "^10.0.0", From f88bd811e73e1ee37f721c53724cb22a5ecc807f Mon Sep 17 00:00:00 2001 From: Joshua Feingold Date: Wed, 27 Aug 2025 15:54:50 -0500 Subject: [PATCH 2/6] @W-19365711@ Restoring GHA checks --- .github/workflows/verify-pr.yml | 42 +++++++++++++++++++ .husky/pre-commit | 4 +- .../validate-changed-package-versions.js | 0 .../validate-package-interdependencies.js | 0 package-lock.json | 17 ++++++++ package.json | 1 + 6 files changed, 62 insertions(+), 2 deletions(-) rename {.husky/node-scripts => .node-scripts}/validate-changed-package-versions.js (100%) rename {.husky/node-scripts => .node-scripts}/validate-package-interdependencies.js (100%) diff --git a/.github/workflows/verify-pr.yml b/.github/workflows/verify-pr.yml index 6f1ce835..0f0c1d44 100644 --- a/.github/workflows/verify-pr.yml +++ b/.github/workflows/verify-pr.yml @@ -21,6 +21,48 @@ jobs: echo "::error::Invalid PR title: '$title'. Please following the format: POSTRELEASE|FIX|CHANGE|NEW (__) @W-XXXXXXXX@ Summary" exit 1 fi + check_for_postrelease_keyword: + runs-on: macos-latest + outputs: + is-postrelease: ${{ steps.main.outputs.is_postrelease }} + steps: + - name: Check for "Postrelease" keyword in PR title. + id: main + if: github.base_ref == 'dev' + run: | + title="${{ github.event.pull_request.title }}" + if [[ "$title" =~ ^POSTRELEASE ]]; then + echo "is_postrelease=true" >> "$GITHUB_OUTPUT" + else + echo "is_postrelease=false" >> "$GITHUB_OUTPUT" + fi + validate_packages: + runs-on: macos-latest + needs: check_for_postrelease_keyword + steps: + - name: Support long paths + run: git config --global core.longpaths true + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - name: Validate that changed packages are versioned as snapshots + if: ${{ needs.check_for_postrelease_keyword.outputs.is-postrelease == 'false' }} + run: | + BASE_SHA=${{ github.event.pull_request.base.sha }} + HEAD_SHA=${{ github.event.pull_request.head.sha }} + git diff --name-only $HEAD_SHA $BASE_SHA > `pwd`/changed_files.txt + node ./.node-scripts/validate-changed-package-versions.js `pwd`/changed_files.txt + - name: Validate that packages properly depend on each other + if: ${{ needs.check_for_postrelease_keyword.outputs.is-postrelease == 'false' }} + run: | + cd packages + PACKAGE_NAMES=`ls` + cd .. + node ./.node-scripts/validate-package-interdependencies.js "$PACKAGE_NAMES" run_tests: runs-on: ${{ matrix.os }} strategy: diff --git a/.husky/pre-commit b/.husky/pre-commit index 6297520e..aea227d5 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,6 @@ git diff --name-only --cached --diff-filter=ACMR > `pwd`/changed_files.txt -node ./.husky/node-scripts/validate-changed-package-versions.js `pwd`/changed_files.txt +node ./.node-scripts/validate-changed-package-versions.js `pwd`/changed_files.txt cd packages PACKAGE_NAMES=`ls` cd .. -node ./.husky/node-scripts/validate-package-interdependencies.js "$PACKAGE_NAMES" +node ./.node-scripts/validate-package-interdependencies.js "$PACKAGE_NAMES" diff --git a/.husky/node-scripts/validate-changed-package-versions.js b/.node-scripts/validate-changed-package-versions.js similarity index 100% rename from .husky/node-scripts/validate-changed-package-versions.js rename to .node-scripts/validate-changed-package-versions.js diff --git a/.husky/node-scripts/validate-package-interdependencies.js b/.node-scripts/validate-package-interdependencies.js similarity index 100% rename from .husky/node-scripts/validate-package-interdependencies.js rename to .node-scripts/validate-package-interdependencies.js diff --git a/package-lock.json b/package-lock.json index 8b1da131..e8fb8bb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ ], "devDependencies": { "cross-env": "^10.0.0", + "husky": "^9.1.7", "jest": "^30.0.5", "rimraf": "^6.0.1", "ts-jest": "^29.4.1" @@ -4862,6 +4863,22 @@ "node": ">=10.17.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", diff --git a/package.json b/package.json index e4cf84ce..f8643396 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "devDependencies": { "cross-env": "^10.0.0", + "husky": "^9.1.7", "jest": "^30.0.5", "rimraf": "^6.0.1", "ts-jest": "^29.4.1" From 79825edbd8444a45721c21b1f921f7401ce2136e Mon Sep 17 00:00:00 2001 From: Joshua Feingold Date: Wed, 27 Aug 2025 16:21:49 -0500 Subject: [PATCH 3/6] @W-19365711@ Feedback from code review --- .../validate-changed-package-versions.js | 17 +++++++++-------- package-lock.json | 1 + package.json | 1 + 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.node-scripts/validate-changed-package-versions.js b/.node-scripts/validate-changed-package-versions.js index 9e1e00e9..db4c6846 100644 --- a/.node-scripts/validate-changed-package-versions.js +++ b/.node-scripts/validate-changed-package-versions.js @@ -1,6 +1,7 @@ const path = require('path'); const fs = require('fs'); const cp = require('child_process'); +const semver = require('semver'); function main() { const changedFiles = readChangedFilesFile(process.argv[2]); @@ -80,24 +81,24 @@ function identifyIncorrectlyVersionedPackages(changedPackages) { incorrectlyVersionedPackages.push(`${changedPackage} (currently versioned as ${packageVersion}) lacks a trailing "-SNAPSHOT"`); continue; } - const releasedPackageVersions = getReleasedPackageVersions(changedPackage); - if (releasedPackageVersions.includes(packageVersion.slice(0, packageVersion.length - 9))) { - incorrectlyVersionedPackages.push(`${changedPackage} (currently versioned as ${packageVersion}) corresponds to an already-published release, and must be incremented.`); + const releasedPackageVersion = getLatestReleasedVersion(changedPackage); + if (semver.lte(semver.parse(packageVersion.slice(0, packageVersion.length - 9)), semver.parse(releasedPackageVersion))) { + incorrectlyVersionedPackages.push(`${changedPackage} (currently versioned as ${packageVersion}) is semantically behind latest published release ${releasedPackageVersion}`); } } return incorrectlyVersionedPackages; } -function getReleasedPackageVersions(changedPackage) { +function getLatestReleasedVersion(changedPackage) { const publishedPackageName = JSON.parse(fs.readFileSync(path.join(changedPackage, 'package.json'), 'utf-8')).name; try { - const execResult = cp.execSync(`npm view ${publishedPackageName} versions --json`, { + + return cp.execSync(`npm view ${publishedPackageName} version`, { encoding: 'utf-8' }); - return JSON.parse(execResult); } catch (e) { - console.log(`NOTE: Could not fetch released versions of ${publishedPackageName} (located in ${changedPackage}. Is that an error?`); - return []; + console.log(`NOTE: Could not fetch latest release version of ${publishedPackageName} (located in ${changedPackage}). Is that an error?`); + return undefined; } } diff --git a/package-lock.json b/package-lock.json index e8fb8bb2..3eb7ed6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "husky": "^9.1.7", "jest": "^30.0.5", "rimraf": "^6.0.1", + "semver": "^7.7.2", "ts-jest": "^29.4.1" } }, diff --git a/package.json b/package.json index f8643396..c89cd0e7 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "husky": "^9.1.7", "jest": "^30.0.5", "rimraf": "^6.0.1", + "semver": "^7.7.2", "ts-jest": "^29.4.1" }, "jest": { From 2dfee6f2eda23722b6560878f9469f5fb98bf4f1 Mon Sep 17 00:00:00 2001 From: Joshua Feingold Date: Wed, 27 Aug 2025 16:26:10 -0500 Subject: [PATCH 4/6] @W-19365711@ Fixing failure --- .github/workflows/verify-pr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/verify-pr.yml b/.github/workflows/verify-pr.yml index 0f0c1d44..fe441410 100644 --- a/.github/workflows/verify-pr.yml +++ b/.github/workflows/verify-pr.yml @@ -49,6 +49,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 'lts/*' + - run: npm install - name: Validate that changed packages are versioned as snapshots if: ${{ needs.check_for_postrelease_keyword.outputs.is-postrelease == 'false' }} run: | From 2e436f95deb9eb7cf6720d11e5dfdb7199bbc513 Mon Sep 17 00:00:00 2001 From: Joshua Feingold Date: Wed, 27 Aug 2025 16:37:45 -0500 Subject: [PATCH 5/6] @W-19365711@ Renamed scratch file --- .gitignore | 3 ++- .husky/pre-commit | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 802265ff..016e90ff 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ dist/ coverage/ *.tsbuildinfo *.tgz -.sfdx \ No newline at end of file +.sfdx +.scratchfile diff --git a/.husky/pre-commit b/.husky/pre-commit index aea227d5..0230fac6 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,5 @@ -git diff --name-only --cached --diff-filter=ACMR > `pwd`/changed_files.txt -node ./.node-scripts/validate-changed-package-versions.js `pwd`/changed_files.txt +git diff --name-only --cached --diff-filter=ACMR > `pwd`/.scratchfile +node ./.node-scripts/validate-changed-package-versions.js `pwd`/.scratchfile cd packages PACKAGE_NAMES=`ls` cd .. From dc74950402392416310e5bfa0917ff17a2600669 Mon Sep 17 00:00:00 2001 From: Joshua Feingold Date: Wed, 27 Aug 2025 16:52:21 -0500 Subject: [PATCH 6/6] @W-19365711@ Feedback from code review --- .node-scripts/validate-changed-package-versions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.node-scripts/validate-changed-package-versions.js b/.node-scripts/validate-changed-package-versions.js index db4c6846..831e6d8e 100644 --- a/.node-scripts/validate-changed-package-versions.js +++ b/.node-scripts/validate-changed-package-versions.js @@ -83,7 +83,7 @@ function identifyIncorrectlyVersionedPackages(changedPackages) { } const releasedPackageVersion = getLatestReleasedVersion(changedPackage); if (semver.lte(semver.parse(packageVersion.slice(0, packageVersion.length - 9)), semver.parse(releasedPackageVersion))) { - incorrectlyVersionedPackages.push(`${changedPackage} (currently versioned as ${packageVersion}) is semantically behind latest published release ${releasedPackageVersion}`); + incorrectlyVersionedPackages.push(`${changedPackage} (currently versioned as ${packageVersion}) is not semantically ahead of latest published release ${releasedPackageVersion}`); } } return incorrectlyVersionedPackages;