-
Notifications
You must be signed in to change notification settings - Fork 87
chore(skills): add release-widget skill #2370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
yordan-st
wants to merge
14
commits into
main
Choose a base branch
from
skill/release-widget
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
d628af5
chore(skills): add release-widget skill
yordan-st adae4af
refactor(automation-utils): extract release-widget skill scripts into…
yordan-st 83421ce
fix(skills): extract changelog read into rui-changelog CLI helper
yordan-st e896d93
fix(release-widget): address all review feedback
yordan-st fd2129d
fix(release-widget): address remaining review feedback
yordan-st 1ac50b0
fix(release-widget): derive release title inside OSS scripts
yordan-st 5850809
feat(automation-utils): add package path resolution and releasability…
yordan-st b7d006c
fix(automation-utils): resolve releases by tag for drafts too
yordan-st 6b709d5
fix(automation-utils): count module subcomponent entries as unreleased
yordan-st a6eb882
fix(automation-utils): validate version bumps and bump wrapped widget…
yordan-st 3e2efd2
fix(automation-utils): name SBOM zip after the real MPK hash
yordan-st 1bf2eef
fix(automation-utils): report existing READMEOSS asset instead of fai…
yordan-st 16ff628
docs(automation-utils): correct rui-create-jira-version exit code com…
yordan-st 48d9b8a
docs(release-widget): align skill with helper behaviour and trim prose
yordan-st File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| #!/usr/bin/env ts-node-script | ||
|
|
||
| import { bumpPackageJson, bumpXml, getNewVersion, hasPackageXml } from "../src/bump-version"; | ||
| import { resolvePackagePath } from "../src/monorepo"; | ||
| import { getPackageInfo, isReleasable } from "../src/package-info"; | ||
| import { Version, versionRegex } from "../src/version"; | ||
|
|
||
| async function bumpPackage(path: string, version: string): Promise<boolean> { | ||
| bumpPackageJson(path, version); | ||
|
|
||
| if (!hasPackageXml(path)) { | ||
| return false; // modules have no package.xml | ||
| } | ||
|
|
||
| await bumpXml(path, version); | ||
| return true; | ||
| } | ||
|
|
||
| function shortName(npmPackageName: string): string { | ||
| return npmPackageName.replace(/^@mendix\//, ""); | ||
| } | ||
|
|
||
| function resolveVersion(bumpType: string, previousVersion: string): string { | ||
| const version = getNewVersion(bumpType, previousVersion); | ||
|
|
||
| if (!versionRegex.test(version)) { | ||
| throw new Error(`'${bumpType}' is not a bump type (patch|minor|major) nor a valid version number`); | ||
| } | ||
|
|
||
| if (!Version.fromString(version).isGreaterThan(Version.fromString(previousVersion))) { | ||
| throw new Error(`Version '${version}' is not greater than the current version '${previousVersion}'`); | ||
| } | ||
|
|
||
| return version; | ||
| } | ||
|
|
||
| async function main(): Promise<void> { | ||
| const npmPackageName = process.argv[2]; | ||
| const bumpType = process.argv[3]; | ||
|
|
||
| if (!npmPackageName || !bumpType) { | ||
| throw new Error( | ||
| "Usage: rui-bump-version <npm-package-name> <patch|minor|major|x.y.z>\nExample: rui-bump-version @mendix/combobox-web patch" | ||
| ); | ||
| } | ||
|
|
||
| const path = await resolvePackagePath(npmPackageName); | ||
| const info = await getPackageInfo(path); | ||
|
|
||
| if (!isReleasable(info)) { | ||
| throw new Error( | ||
| `'${npmPackageName}' has no positive marketplace.appNumber, so it is not published on its own. If it is a widget, bump the module wrapping it instead.` | ||
| ); | ||
| } | ||
|
|
||
| const previousVersion = info.version.format(); | ||
| const version = resolveVersion(bumpType, previousVersion); | ||
|
|
||
| const xmlBumped = await bumpPackage(path, version); | ||
| const bumpedPackages = [shortName(info.name)]; | ||
| const changedPaths = [path]; | ||
|
|
||
| // Wrapped widgets are released as part of the target and share its version, | ||
| // so all of them are bumped, not only the ones with changelog entries. | ||
| for (const dependencyName of info.mxpackage.dependencies) { | ||
| const dependencyPath = await resolvePackagePath(dependencyName); | ||
|
|
||
| await bumpPackage(dependencyPath, version); | ||
| bumpedPackages.push(shortName(dependencyName)); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we strip |
||
| changedPaths.push(dependencyPath); | ||
| } | ||
|
|
||
| console.log(JSON.stringify({ previousVersion, version, xmlBumped, bumpedPackages, changedPaths })); | ||
| } | ||
|
|
||
| main().catch(error => { | ||
| console.error(error instanceof Error ? error.message : String(error)); | ||
| process.exit(1); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| #!/usr/bin/env ts-node-script | ||
|
|
||
| import { getPackageChangelog } from "../src/changelog-parser"; | ||
| import { resolvePackagePath } from "../src/monorepo"; | ||
| import { getPackageInfo, isReleasable } from "../src/package-info"; | ||
|
|
||
| async function main(): Promise<void> { | ||
| const npmPackageName = process.argv[2]; | ||
|
|
||
| if (!npmPackageName) { | ||
| throw new Error("Usage: rui-changelog <npm-package-name>\nExample: rui-changelog @mendix/combobox-web"); | ||
| } | ||
|
|
||
| const path = await resolvePackagePath(npmPackageName); | ||
| const info = await getPackageInfo(path); | ||
|
|
||
| if (!isReleasable(info)) { | ||
| throw new Error( | ||
| `'${npmPackageName}' has no positive marketplace.appNumber, so it is not published on its own. If it is a widget, read the changelog of the module wrapping it instead.` | ||
| ); | ||
| } | ||
|
|
||
| const changelog = await getPackageChangelog(path); | ||
| // The parsers keep the Unreleased entry first, released versions follow. | ||
| const unreleased = changelog.changelog.content[0]; | ||
| const subcomponents = "subcomponents" in unreleased ? unreleased.subcomponents : []; | ||
|
|
||
| console.log( | ||
| JSON.stringify({ | ||
| hasUnreleasedLogs: changelog.hasUnreleasedLogs(), | ||
| sections: unreleased.sections, | ||
| subcomponents | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| main().catch(error => { | ||
| console.error(error instanceof Error ? error.message : String(error)); | ||
| process.exit(1); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| #!/usr/bin/env ts-node-script | ||
|
|
||
| import { Jira } from "../src/jira"; | ||
|
|
||
| /** | ||
| * Jira version creation has historically 404'd transiently and must never block | ||
| * a release, so a missing token or any API failure is reported as `skipped` on | ||
| * stdout with exit code 0. Only a usage error (no version name) exits non-zero. | ||
| */ | ||
| async function main(): Promise<void> { | ||
| const versionName = process.argv[2]; | ||
|
|
||
| if (!versionName) { | ||
| throw new Error( | ||
| "Usage: rui-create-jira-version <version-name>\nExample: rui-create-jira-version combobox-web-v2.9.0" | ||
| ); | ||
| } | ||
|
|
||
| const apiToken = process.env.JIRA_API_TOKEN; | ||
| if (!apiToken) { | ||
| console.log(JSON.stringify({ status: "skipped", reason: "JIRA_API_TOKEN not set" })); | ||
| return; | ||
| } | ||
|
|
||
| const projectKey = process.env.JIRA_PROJECT_KEY ?? "WC"; | ||
| const baseUrl = process.env.JIRA_BASE_URL ?? "https://mendix.atlassian.net"; | ||
|
|
||
| try { | ||
| const jira = new Jira(projectKey, baseUrl, apiToken); | ||
| await jira.initializeProjectData(); | ||
|
|
||
| const existing = jira.findVersion(versionName); | ||
| if (existing) { | ||
| console.log(JSON.stringify({ status: "exists", name: existing.name, id: existing.id })); | ||
| return; | ||
| } | ||
|
|
||
| const created = await jira.createVersion(versionName); | ||
| console.log(JSON.stringify({ status: "created", name: created.name, id: created.id })); | ||
| } catch (error) { | ||
| console.log( | ||
| JSON.stringify({ status: "skipped", reason: error instanceof Error ? error.message : String(error) }) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| main().catch(error => { | ||
| console.error(error instanceof Error ? error.message : String(error)); | ||
| process.exit(1); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| #!/usr/bin/env ts-node-script | ||
|
|
||
| import { homedir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { gh } from "../src/github"; | ||
| import { | ||
| createSBomGeneratorFolderStructure, | ||
| generateSBomArtifactsInFolder, | ||
| verifyAssetDigest | ||
| } from "../src/oss-clearance"; | ||
|
|
||
| async function main(): Promise<void> { | ||
| const releaseTag = process.argv[2]; | ||
|
|
||
| if (!releaseTag) { | ||
| throw new Error( | ||
| "Usage: rui-generate-oss-sbom <release-tag>\nExample: rui-generate-oss-sbom combobox-web-v2.9.0" | ||
| ); | ||
| } | ||
|
|
||
| await gh.ensureAuth(); | ||
|
|
||
| const release = await gh.getReleaseByTag(releaseTag); | ||
| if (!release) { | ||
| throw new Error(`No GitHub release found for tag '${releaseTag}'`); | ||
| } | ||
| const releaseName = release.name; | ||
|
|
||
| const mpk = release.assets.find(asset => asset.name.endsWith(".mpk")); | ||
| if (!mpk) { | ||
| throw new Error(`No .mpk asset found on release '${releaseTag}'`); | ||
| } | ||
|
|
||
| const [tmpFolder, downloadPath] = await createSBomGeneratorFolderStructure(releaseName); | ||
| await gh.downloadReleaseAsset(mpk.id, downloadPath); | ||
| const fileHash = await verifyAssetDigest(mpk, downloadPath); | ||
|
|
||
| const generatorJar = process.env.SBOM_GENERATOR_JAR ?? join(homedir(), "SBOM_Generator.jar"); | ||
| const finalPath = join(homedir(), "Downloads", `${releaseName} [${fileHash}].zip`); | ||
|
|
||
| await generateSBomArtifactsInFolder(tmpFolder, generatorJar, releaseName, finalPath); | ||
|
|
||
| console.log(JSON.stringify({ path: finalPath, mpk: mpk.name, sha256: fileHash })); | ||
| } | ||
|
|
||
| main().catch(error => { | ||
| console.error(error instanceof Error ? error.message : String(error)); | ||
| process.exit(1); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| #!/usr/bin/env ts-node-script | ||
|
|
||
| import { getPackageInfo } from "../src/package-info"; | ||
|
|
||
| async function main(): Promise<void> { | ||
| const path = process.cwd(); | ||
| const info = await getPackageInfo(path); | ||
|
|
||
| console.log( | ||
| JSON.stringify({ | ||
| name: info.name, | ||
| version: info.version.format(), | ||
| appNumber: info.marketplace.appNumber ?? null, | ||
| appName: info.marketplace.appName ?? null | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| main().catch(error => { | ||
| console.error(error instanceof Error ? error.message : String(error)); | ||
| process.exit(1); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| #!/usr/bin/env ts-node-script | ||
|
|
||
| import { basename } from "node:path"; | ||
| import { gh } from "../src/github"; | ||
| import { findAllReadmeOssLocally, getRecommendedReadmeOss, hasReadmeOssInAssets } from "../src/oss-clearance"; | ||
|
|
||
| async function main(): Promise<void> { | ||
| const releaseTag = process.argv[2]; | ||
| const explicitPath = process.argv[3]; | ||
|
|
||
| if (!releaseTag) { | ||
| throw new Error( | ||
| "Usage: rui-upload-readme-oss <release-tag> [explicit-path]\nExample: rui-upload-readme-oss combobox-web-v2.9.0" | ||
| ); | ||
| } | ||
|
|
||
| await gh.ensureAuth(); | ||
|
|
||
| const release = await gh.getReleaseByTag(releaseTag); | ||
| if (!release) { | ||
| throw new Error(`No GitHub release found for tag '${releaseTag}'`); | ||
| } | ||
|
|
||
| // Uploading a name that is already attached fails with a 422, so a re-run of | ||
| // this step reports the existing asset instead of trying again. | ||
| const attached = release.assets.filter(asset => hasReadmeOssInAssets([asset.name])); | ||
| if (attached.length > 0) { | ||
| console.log(JSON.stringify({ uploaded: attached[0].name, status: "exists" })); | ||
| return; | ||
| } | ||
|
|
||
| const readmePath = explicitPath ?? getRecommendedReadmeOss(release.name, findAllReadmeOssLocally()); | ||
| if (!readmePath) { | ||
| throw new Error( | ||
| `No matching READMEOSS found in ~/Downloads or ~/Documents for '${release.name}'. Pass the path explicitly as a 2nd argument.` | ||
| ); | ||
| } | ||
|
|
||
| const asset = await gh.uploadReleaseAsset(release.id, readmePath, basename(readmePath)); | ||
| console.log(JSON.stringify({ uploaded: asset.name, status: "created" })); | ||
| } | ||
|
|
||
| main().catch(error => { | ||
| console.error(error instanceof Error ? error.message : String(error)); | ||
| process.exit(1); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like getNewVersion already does what
Versionclass is capable of. We should use Version math instead of using manual math done ingetNewVersion.