From 7abf7c3917eb7d5954cebc68ae2916b0a1126958 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 11 Jun 2026 13:42:44 -0700 Subject: [PATCH 1/3] fix: Improve API spec summarization and versioning --- .github/workflows/update.yaml | 23 +++---- scripts/summarize-spec-diff.js | 121 ++++++++++++++++++++++++--------- 2 files changed, 97 insertions(+), 47 deletions(-) diff --git a/.github/workflows/update.yaml b/.github/workflows/update.yaml index 84c7e086..485d84cb 100644 --- a/.github/workflows/update.yaml +++ b/.github/workflows/update.yaml @@ -72,16 +72,6 @@ jobs: -v "$PWD/api-definitions:/adef" \ tufin/oasdiff:v1.18.1 \ changelog /sdk/.openapi-snapshot.yaml /adef/catalog/all.yaml -f json > /tmp/spec-diff.json - counts=$(docker run --rm \ - -v "$PWD/sdk:/sdk" \ - -v "$PWD/api-definitions:/adef" \ - tufin/oasdiff:v1.18.1 \ - changelog /sdk/.openapi-snapshot.yaml /adef/catalog/all.yaml -f singleline 2>/dev/null | head -1) - echo "summary=$counts" >> $GITHUB_OUTPUT - if echo "$counts" | grep -qE "[1-9][0-9]* error"; then echo "bump=major" >> $GITHUB_OUTPUT - elif echo "$counts" | grep -qE "[1-9][0-9]* warning"; then echo "bump=minor" >> $GITHUB_OUTPUT - else echo "bump=patch" >> $GITHUB_OUTPUT - fi - name: Summarize spec diff id: summarize @@ -149,14 +139,17 @@ jobs: working-directory: ./sdk run: cp ../api-definitions/catalog/all.yaml .openapi-snapshot.yaml - - name: Add changeset + - name: Write spec-diff changeset if: steps.summarize.outputs.has_content == 'true' working-directory: ./sdk run: | - npm ci - node ./scripts/add-changeset.js ${{ steps.oasdiff.outputs.bump }} "$(cat /tmp/changeset-body.md)" - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + { + echo '---' + echo '"@rebilly/client-php": patch' + echo '---' + echo '' + cat /tmp/changeset-body.md + } > .changeset/spec-diff.md - name: Create PR id: cpr diff --git a/scripts/summarize-spec-diff.js b/scripts/summarize-spec-diff.js index c0483920..3ab82184 100644 --- a/scripts/summarize-spec-diff.js +++ b/scripts/summarize-spec-diff.js @@ -209,22 +209,53 @@ function suppressCompositionNoise(data) { return kept; } -function renderOne(verb, side, leafName, schemas, breaking, endpointCount, examples, bucket) { - const sidePart = side ? ` ${side} property` : ''; +function renderOne(verb, sides, leafName, schemas, breaking, bucket) { const schemaPart = schemas.size ? ` on \`${[...schemas].sort().join(', ')}\`` : ''; const bk = breaking ? ' **(breaking)**' : ''; - let endpointPart; - if (endpointCount === 1) { - endpointPart = examples.length ? ` — ${examples[0]}` : ''; - } else { - const sample = examples.slice(0, 2).join(', '); - const more = endpointCount > 2 ? ` + ${endpointCount - 2} more` : ''; - endpointPart = examples.length ? ` — ${sample}${more}` : ` (${endpointCount} endpoints)`; - } if (bucket === 'endpoint') { - return `- ${verb}${endpointPart}${bk}`; + return `- ${verb} \`${leafName}\`${bk}`; + } + let sideHint = ''; + if (sides.size === 1) { + const only = [...sides][0]; + if (only) sideHint = ` (${only} only)`; } - return `- ${verb}${sidePart} \`${leafName}\`${schemaPart}${endpointPart}${bk}`; + return `- ${verb} \`${leafName}\`${schemaPart}${sideHint}${bk}`; +} + +const OTHER_VERBS = { + 'min-decreased': 'Lowered minimum on', + 'min-increased': 'Raised minimum on', + 'max-decreased': 'Lowered maximum on', + 'max-increased': 'Raised maximum on', + 'min-length-decreased': 'Shortened minimum length on', + 'min-length-increased': 'Raised minimum length on', + 'max-length-decreased': 'Shortened maximum length on', + 'max-length-increased': 'Raised maximum length on', + 'discriminator-mapping-added': 'Added discriminator mapping for', + 'discriminator-mapping-deleted': 'Removed discriminator mapping for', + 'discriminator-mapping-changed': 'Changed discriminator mapping for', + 'all-of-added': 'Composed schema with', + 'all-of-removed': 'Uncomposed schema from', + 'any-of-added': 'Added union member to', + 'any-of-removed': 'Removed union member from', + 'one-of-added': 'Added discriminator variant to', + 'one-of-removed': 'Removed discriminator variant from', + 'became-nullable': 'Made nullable', + 'became-not-nullable': 'Made not nullable', + 'default-value-added': 'Default added on', + 'default-value-removed': 'Default removed on', + 'min-items-decreased': 'Lowered minItems on', + 'max-items-decreased': 'Lowered maxItems on', + 'min-items-increased': 'Raised minItems on', + 'max-items-increased': 'Raised maxItems on', +}; + +function otherChangeFamily(id) { + // Strip request/response/-body-/-property- prefixes and discriminator wrappers. + return id + .replace(/^(request|response)(-body|-property)?-/, '') + .replace(/^(request|response)-/, ''); } function recordEndpoint(rec, ep) { @@ -252,15 +283,15 @@ function main() { for (const it of data) { if (ENUM_IDS.has(it.id)) { const [value, prop] = enumValueAndProp(it.text); - const verb = it.id.includes('added') ? 'Enum value added' : 'Enum value removed'; + const verb = it.id.includes('added') ? 'added' : 'removed'; const side = it.id.includes('request') ? 'request' : 'response'; - const key = `${verb}\x1f${side}\x1f${leaf(prop) || '?'}`; + const key = `${verb}\x1f${leaf(prop) || '?'}`; const rec = getOrCreate(enumRows, key, () => ({ - verb, side, prop: leaf(prop) || '?', - values: [], endpoints: new Set(), examples: [], breaking: false, schemas: new Set(), + verb, prop: leaf(prop) || '?', + values: [], sides: new Set(), breaking: false, schemas: new Set(), })); if (value && !rec.values.includes(value)) rec.values.push(value); - recordEndpoint(rec, endpointOf(it)); + rec.sides.add(side); const sch = deepestSchema(it.text); if (sch) rec.schemas.add(sch); if ((it.level || 1) >= 3) rec.breaking = true; @@ -277,21 +308,33 @@ function main() { } const rule = RULES.get(it.id); if (!rule) { - const rec = getOrCreate(unknown, it.id, () => []); - rec.push(it); + const prop = firstProp(it.text) || ''; + const leafName = leaf(prop); + const family = otherChangeFamily(it.id); + const key = `${family}\x1f${leafName || '?'}`; + const rec = getOrCreate(unknown, key, () => ({ + family, leafName, + sides: new Set(), schemas: new Set(), items: [], + })); + rec.sides.add(it.id.includes('request') ? 'request' : 'response'); + const sch = deepestSchema(it.text); + if (sch) rec.schemas.add(sch); + rec.items.push(it); continue; } const [verb, side, bucket] = rule; const prop = firstProp(it.text) || ''; const leafName = leaf(prop) || it.text.slice(0, 50); const schema = deepestSchema(it.text); - const key = `${bucket}\x1f${verb}\x1f${leafName}\x1f${side || ''}`; + // Strip the request/response split: collapse same logical change. + const baseVerb = verb.replace(/^Added (required|optional|union member|discriminator variant|parameter)$/, 'Added').replace(/^Removed (required|optional|union member|discriminator variant|parameter)$/, 'Removed'); + const key = `${bucket}\x1f${baseVerb}\x1f${leafName}`; const rec = getOrCreate(grouped, key, () => ({ - bucket, verb, leafName, side, - schemas: new Set(), endpoints: new Set(), examples: [], breaking: false, + bucket, verb: baseVerb, leafName, + schemas: new Set(), sides: new Set(), breaking: false, })); if (schema) rec.schemas.add(schema); - recordEndpoint(rec, endpointOf(it)); + if (side) rec.sides.add(side); if ((it.level || 1) >= 3) rec.breaking = true; } @@ -312,23 +355,24 @@ function main() { return byCodePoint(a.leafName, b.leafName); }); out.push(sections[bucket]); - for (const r of rows) out.push(renderOne(r.verb, r.side, r.leafName, r.schemas, r.breaking, r.endpoints.size, r.examples, bucket)); + for (const r of rows) out.push(renderOne(r.verb, r.sides, r.leafName, r.schemas, r.breaking, bucket)); out.push(''); } if (enumRows.size) { out.push('### Enum changes'); const rows = [...enumRows.values()].sort((a, b) => - byCodePoint(a.verb + a.side + a.prop, b.verb + b.side + b.prop) + byCodePoint(a.verb + a.prop, b.verb + b.prop) ); for (const r of rows) { const schemasS = r.schemas.size ? ` on \`${[...r.schemas].sort().join(', ')}\`` : ''; const bk = r.breaking ? ' **(breaking)**' : ''; const sampleV = r.values.slice(0, 6).map(v => `\`${v}\``).join(', '); const moreV = r.values.length > 6 ? ` + ${r.values.length - 6} more` : ''; - const sampleEp = r.examples.slice(0, 2).join(', '); - const moreEp = r.endpoints.size > 2 ? ` + ${r.endpoints.size - 2} more` : ''; - out.push(`- ${r.verb} on ${r.side} \`${r.prop}\`${schemasS}: ${sampleV}${moreV} — ${sampleEp}${moreEp}${bk}`); + const verbPhrase = r.verb === 'added' ? 'accepts new values' : 'no longer accepts'; + let sideHint = ''; + if (r.sides.size === 1) sideHint = ` (${[...r.sides][0]} only)`; + out.push(`- \`${r.prop}\`${schemasS}${sideHint} ${verbPhrase}: ${sampleV}${moreV}${bk}`); } out.push(''); } @@ -350,10 +394,23 @@ function main() { if (unknown.size) { out.push('### Other'); - const entries = [...unknown.entries()].sort((a, b) => b[1].length - a[1].length).slice(0, 10); - for (const [changeId, items] of entries) { - const ex = items[0].text.slice(0, 120); - out.push(`- ${changeId} (${items.length}x) — e.g. ${ex}`); + const rows = [...unknown.values()].sort((a, b) => + byCodePoint((a.family || '') + (a.leafName || ''), (b.family || '') + (b.leafName || '')) + ); + for (const r of rows) { + const verb = OTHER_VERBS[r.family]; + const schemaPart = r.schemas.size ? ` on \`${[...r.schemas].sort().join(', ')}\`` : ''; + let sideHint = ''; + if (r.sides.size === 1) sideHint = ` (${[...r.sides][0]} only)`; + if (verb && r.leafName) { + out.push(`- ${verb} \`${r.leafName}\`${schemaPart}${sideHint}`); + } else if (r.leafName) { + out.push(`- ${r.family} on \`${r.leafName}\`${schemaPart}${sideHint}`); + } else { + // Fallback for changes without a clear property path. + const ex = r.items[0].text.slice(0, 120); + out.push(`- ${r.family} (${r.items.length}x) — e.g. ${ex}`); + } } out.push(''); } From de74b11c6d926fdc4e0224083a2e9dceeb8e657e Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 11 Jun 2026 13:46:12 -0700 Subject: [PATCH 2/3] Create whole-pens-give.md --- .changeset/whole-pens-give.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changeset/whole-pens-give.md diff --git a/.changeset/whole-pens-give.md b/.changeset/whole-pens-give.md new file mode 100644 index 00000000..8a229d1e --- /dev/null +++ b/.changeset/whole-pens-give.md @@ -0,0 +1,3 @@ +--- +"@rebilly/client-php": patch +--- From 17ad7a87545d7b3a069a459b1079069cc439cf84 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 15 Jun 2026 13:07:23 -0700 Subject: [PATCH 3/3] Remove unused add-changeset.js --- package-lock.json | 29 ----------------------------- package.json | 1 - scripts/add-changeset.js | 28 ---------------------------- 3 files changed, 58 deletions(-) delete mode 100644 scripts/add-changeset.js diff --git a/package-lock.json b/package-lock.json index 49b72715..30357c53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "4.0.0", "devDependencies": { "@changesets/cli": "^2.26.2", - "@changesets/write": "^0.2.3", "@rebilly/regenerator": "^0.0.10", "ts-node": "^10.9.2" } @@ -276,27 +275,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@changesets/write": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.2.3.tgz", - "integrity": "sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/types": "^5.2.1", - "fs-extra": "^7.0.1", - "human-id": "^1.0.2", - "prettier": "^2.7.1" - } - }, - "node_modules/@changesets/write/node_modules/@changesets/types": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-5.2.1.tgz", - "integrity": "sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==", - "dev": true, - "license": "MIT" - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1568,13 +1546,6 @@ "uglify-js": "^3.1.4" } }, - "node_modules/human-id": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/human-id/-/human-id-1.0.2.tgz", - "integrity": "sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==", - "dev": true, - "license": "MIT" - }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", diff --git a/package.json b/package.json index 414c924b..97a5491b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "version": "4.0.0", "devDependencies": { "@changesets/cli": "^2.26.2", - "@changesets/write": "^0.2.3", "@rebilly/regenerator": "^0.0.10", "ts-node": "^10.9.2" }, diff --git a/scripts/add-changeset.js b/scripts/add-changeset.js deleted file mode 100644 index f14b27fe..00000000 --- a/scripts/add-changeset.js +++ /dev/null @@ -1,28 +0,0 @@ -const fs = require('fs'); -const { default: write } = require('@changesets/write'); - -async function run() { - if (process.argv.length < 4) { - console.error('Usage: node scripts/add-changeset.js '); - process.exit(1); - } - const type = process.argv[2]; - const summary = process.argv.slice(3).join(' '); - - const cwd = process.cwd(); - const packageJson = JSON.parse(fs.readFileSync('./package.json').toString()); - - const changesetId = await write({ - summary, - releases: [ - { - name: packageJson.name, - type, - }, - ], - }, cwd); - - console.log(changesetId); -} - -run();