Skip to content

Commit c5eb7aa

Browse files
committed
chore: releqse script backport
1 parent 94003c4 commit c5eb7aa

File tree

13 files changed

+757
-36
lines changed

13 files changed

+757
-36
lines changed

.github/workflows/PublishMarketplace.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ name: "Publishes a package to marketplace"
33
on:
44
release:
55
types: [published]
6+
workflow_dispatch:
7+
inputs:
8+
package:
9+
description: Package name (e.g. accordion-web)
10+
required: true
611

712
jobs:
813
publish-new-version:
914
name: "Publish a new package version from GitHub release"
1015
runs-on: ubuntu-latest
1116
env:
12-
TAG: ${{ github.ref_name }}
17+
TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.package }}
1318

1419
steps:
1520
- name: Check release tag

automation/utils/bin/rui-prepare-release.ts

Lines changed: 454 additions & 0 deletions
Large diffs are not rendered by default.

automation/utils/bin/rui-publish-marketplace.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import assert from "node:assert/strict";
44
import { getPublishedInfo, gh } from "../src";
5-
import { fgGreen } from "../src/ansi-colors";
65
import { createDraft, publishDraft } from "../src/api/contributor";
6+
import chalk from "chalk";
77

88
async function main(): Promise<void> {
99
console.log(`Getting package information...`);
@@ -13,11 +13,11 @@ async function main(): Promise<void> {
1313
assert.ok(tag, "env.TAG is empty");
1414

1515
if (marketplace.appNumber === -1) {
16-
console.log(`Skipping release process for tag ${fgGreen(tag)}. appNumber is set to -1 in package.json.`);
16+
console.log(`Skipping release process for tag ${chalk.green(tag)}. appNumber is set to -1 in package.json.`);
1717
process.exit(2);
1818
}
1919

20-
console.log(`Starting release process for tag ${fgGreen(tag)}`);
20+
console.log(`Starting release process for tag ${chalk.green(tag)}`);
2121

2222
const artifactUrl = await gh.getMPKReleaseArtifactUrl(tag);
2323

automation/utils/bin/rui-verify-package-format.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
PublishedPackageSchema
1010
} from "../src";
1111
import { verify as verifyWidget } from "../src/verify-widget-manifest";
12-
import { fgCyan, fgGreen, fgYellow } from "../src/ansi-colors";
12+
import chalk from "chalk";
1313

1414
async function main(): Promise<void> {
1515
const path = process.cwd();
@@ -51,13 +51,13 @@ async function main(): Promise<void> {
5151

5252
// Changelog check coming soon...
5353

54-
console.log(fgGreen("Verification success"));
54+
console.log(chalk.green("Verification success"));
5555
} catch (error) {
5656
if (error instanceof ZodError) {
5757
for (const issue of error.issues) {
58-
const keys = issue.path.map(x => fgYellow(`${x}`));
58+
const keys = issue.path.map(x => chalk.yellow(`${x}`));
5959
const code = `[${issue.code}]`;
60-
console.error(`package.${keys.join(".")} - ${code} ${fgCyan(issue.message)}`);
60+
console.error(`package.${keys.join(".")} - ${code} ${chalk.cyan(issue.message)}`);
6161
}
6262
// Just for new line
6363
console.log("");

automation/utils/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"bin": {
88
"rui-create-gh-release": "bin/rui-create-gh-release.ts",
99
"rui-create-translation": "bin/rui-create-translation.ts",
10+
"rui-prepare-release": "bin/rui-prepare-release.ts",
1011
"rui-publish-marketplace": "bin/rui-publish-marketplace.ts",
1112
"rui-update-changelog-module": "bin/rui-update-changelog-module.ts",
1213
"rui-update-changelog-widget": "bin/rui-update-changelog-widget.ts",
@@ -27,6 +28,7 @@
2728
"format": "prettier --write .",
2829
"lint": "eslint --ext .jsx,.js,.ts,.tsx src/",
2930
"prepare": "pnpm run compile:parser:widget && pnpm run compile:parser:module && tsc",
31+
"prepare-release": "ts-node bin/rui-prepare-release.ts",
3032
"start": "tsc --watch",
3133
"version": "ts-node bin/rui-bump-version.ts"
3234
},
@@ -35,9 +37,9 @@
3537
"@mendix/prettier-config-web-widgets": "workspace:*",
3638
"@types/cross-zip": "^4.0.2",
3739
"@types/node-fetch": "2.6.2",
38-
"chalk": "^4.1.2",
40+
"chalk": "^5.4.1",
3941
"cross-zip": "^4.0.1",
40-
"enquirer": "^2.3.6",
42+
"enquirer": "^2.4.1",
4143
"execa": "^5.1.1",
4244
"fast-xml-parser": "^4.1.3",
4345
"node-fetch": "^2.6.9",

automation/utils/src/ansi-colors.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

automation/utils/src/api/contributor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import assert from "node:assert/strict";
2-
import { fetch, BodyInit } from "../fetch";
2+
import { BodyInit, fetch } from "../fetch";
33
import { z } from "zod";
44
import { Version } from "../version";
5-
import { fgGreen } from "../ansi-colors";
5+
import chalk from "chalk";
66

77
export interface CreateDraftSuccessResponse {
88
App: App;
@@ -115,7 +115,7 @@ export async function createDraft(params: CreateDraftParams): Promise<CreateDraf
115115
const { appName, appNumber, version, studioProVersion, artifactUrl, reactReady } = CreateDraftParams.parse(params);
116116
console.log(`Creating draft in the Mendix Marketplace...`);
117117
console.log(
118-
fgGreen(
118+
chalk.green(
119119
`AppName: ${appName} - AppNumber: ${appNumber} - Version: ${version.format()} - StudioPro: ${studioProVersion.format()}`
120120
)
121121
);

automation/utils/src/build-config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { join } from "node:path";
2-
import { fgGreen } from "./ansi-colors";
2+
import chalk from "chalk";
33
import { getModuleInfo, getWidgetInfo, ModuleInfo, WidgetInfo } from "./package-info";
44

55
export interface Output<Dirs, Files> {
@@ -65,7 +65,7 @@ export async function getWidgetBuildConfig({
6565
console.info(`Creating build config for ${packageName}...`);
6666

6767
if (MX_PROJECT_PATH) {
68-
console.info(fgGreen(`targetProject: using project path from MX_PROJECT_PATH.`));
68+
console.info(chalk.green(`targetProject: using project path from MX_PROJECT_PATH.`));
6969
}
7070

7171
const paths = {
@@ -118,7 +118,7 @@ export async function getModuleBuildConfig({
118118
console.info(`Creating build config for ${packageName}...`);
119119

120120
if (MX_PROJECT_PATH) {
121-
console.info(fgGreen(`targetProject: using project path from MX_PROJECT_PATH.`));
121+
console.info(chalk.green(`targetProject: using project path from MX_PROJECT_PATH.`));
122122
}
123123

124124
const paths = {

automation/utils/src/github.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,46 @@ export class GitHub {
136136

137137
return filePath;
138138
}
139+
140+
private async triggerGithubWorkflow(params: {
141+
workflowId: string;
142+
ref: string;
143+
inputs: Record<string, string>;
144+
owner?: string;
145+
repo?: string;
146+
}): Promise<void> {
147+
await this.ensureAuth();
148+
149+
const { workflowId, ref, inputs, owner = "mendix", repo = "web-widgets" } = params;
150+
151+
// Convert inputs object to CLI parameters
152+
const inputParams = Object.entries(inputs)
153+
.map(([key, value]) => `-f ${key}=${value}`)
154+
.join(" ");
155+
156+
const repoParam = `${owner}/${repo}`;
157+
158+
const command = [`gh workflow run`, `"${workflowId}"`, `--ref "${ref}"`, inputParams, `-R "${repoParam}"`]
159+
.filter(Boolean)
160+
.join(" ");
161+
162+
try {
163+
await exec(command);
164+
console.log(`Successfully triggered workflow '${workflowId}'`);
165+
} catch (error) {
166+
throw new Error(`Failed to trigger workflow '${workflowId}': ${error}`);
167+
}
168+
}
169+
170+
async triggerCreateReleaseWorkflow(packageName: string, ref = "main"): Promise<void> {
171+
return this.triggerGithubWorkflow({
172+
workflowId: "CreateGitHubRelease.yml",
173+
ref,
174+
inputs: {
175+
package: packageName
176+
}
177+
});
178+
}
139179
}
140180

141181
export const gh = new GitHub();

automation/utils/src/jira.ts

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import nodefetch, { RequestInit } from "node-fetch";
2+
3+
interface JiraVersion {
4+
id: string;
5+
name: string;
6+
archived: boolean;
7+
released: boolean;
8+
}
9+
10+
interface JiraProject {
11+
id: string;
12+
key: string;
13+
name: string;
14+
}
15+
16+
interface JiraIssue {
17+
key: string;
18+
fields: {
19+
summary: string;
20+
};
21+
}
22+
23+
export class Jira {
24+
private projectKey: string;
25+
private baseUrl: string;
26+
private apiToken: string;
27+
28+
private projectId: string | undefined;
29+
private projectVersions: JiraVersion[] | undefined;
30+
31+
constructor(projectKey: string, baseUrl: string, apiToken: string) {
32+
if (!apiToken) {
33+
throw new Error("API token is required.");
34+
}
35+
this.projectKey = projectKey;
36+
this.baseUrl = baseUrl;
37+
38+
this.apiToken = Buffer.from(apiToken).toString("base64"); // Convert to Base64
39+
}
40+
41+
// Private helper method for making API requests
42+
private async apiRequest<T = unknown>(
43+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
44+
endpoint: string,
45+
body?: object
46+
): Promise<T> {
47+
const url = `${this.baseUrl}/rest/api/3${endpoint}`;
48+
const headers = { Authorization: `Basic ${this.apiToken}` };
49+
50+
const httpsOptions: RequestInit = {
51+
method,
52+
redirect: "follow",
53+
headers: {
54+
Accept: "application/json",
55+
...headers,
56+
...(body && { "Content-Type": "application/json" })
57+
},
58+
body: body ? JSON.stringify(body) : undefined
59+
};
60+
61+
let response;
62+
try {
63+
response = await nodefetch(url, httpsOptions);
64+
} catch (error) {
65+
throw new Error(`API request failed: ${(error as Error).message}`);
66+
}
67+
68+
if (!response.ok) {
69+
throw new Error(`API request failed (${response.status}): ${response.statusText}`);
70+
}
71+
72+
if (response.status === 204) {
73+
// No content, return empty object
74+
return {} as T;
75+
}
76+
77+
return response.json();
78+
}
79+
80+
async initializeProjectData(): Promise<void> {
81+
const projectData = await this.apiRequest<JiraProject & { versions: JiraVersion[] }>(
82+
"GET",
83+
`/project/${this.projectKey}`
84+
);
85+
86+
this.projectId = projectData.id; // Save project ID
87+
this.projectVersions = projectData.versions.reverse(); // Save list of versions
88+
}
89+
90+
private versions(): JiraVersion[] {
91+
if (!this.projectVersions) {
92+
throw new Error("Project versions not initialized. Call initializeProjectData() first.");
93+
}
94+
return this.projectVersions;
95+
}
96+
97+
getVersions(): JiraVersion[] {
98+
return this.versions();
99+
}
100+
101+
findVersion(versionName: string): JiraVersion | undefined {
102+
return this.versions().find(version => version.name === versionName);
103+
}
104+
105+
async createVersion(name: string): Promise<JiraVersion> {
106+
const version = await this.apiRequest<JiraVersion>("POST", `/version`, {
107+
projectId: this.projectId,
108+
name
109+
});
110+
111+
this.projectVersions!.unshift(version);
112+
113+
return version;
114+
}
115+
116+
async assignVersionToIssue(versionId: string, issueKey: string): Promise<void> {
117+
const currentVersions = await this.getFixVersionsForIssue(issueKey);
118+
if (currentVersions.some(version => version.id === versionId)) {
119+
// Version already exists
120+
return;
121+
}
122+
123+
const updatedVersions = [...currentVersions.map(version => ({ id: version.id })), { id: versionId }];
124+
125+
await this.apiRequest("PUT", `/issue/${issueKey}`, {
126+
fields: {
127+
fixVersions: updatedVersions
128+
}
129+
});
130+
}
131+
132+
async deleteVersion(versionId: string): Promise<void> {
133+
await this.apiRequest("DELETE", `/version/${versionId}`);
134+
135+
// Remove the version from the cached project versions
136+
this.projectVersions = this.projectVersions?.filter(version => version.id !== versionId);
137+
}
138+
139+
async getFixVersionsForIssue(issueKey: string): Promise<JiraVersion[]> {
140+
const issue = await this.apiRequest<{ fields: { fixVersions: JiraVersion[] } }>(
141+
"GET",
142+
`/issue/${issueKey}?fields=fixVersions`
143+
);
144+
145+
return issue.fields.fixVersions || [];
146+
}
147+
148+
async removeFixVersionFromIssue(versionId: string, issueKey: string): Promise<void> {
149+
// First, get current fix versions
150+
const currentVersions = await this.getFixVersionsForIssue(issueKey);
151+
152+
// Filter out the version to remove
153+
const updatedVersions = currentVersions
154+
.filter(version => version.id !== versionId)
155+
.map(version => ({ id: version.id }));
156+
157+
// Update the issue with the filtered versions
158+
await this.apiRequest("PUT", `/issue/${issueKey}`, {
159+
fields: {
160+
fixVersions: updatedVersions
161+
}
162+
});
163+
}
164+
165+
private async getIssuesForVersion(versionId: string): Promise<string[]> {
166+
const issues = await this.apiRequest<{ issues: Array<{ key: string }> }>(
167+
"GET",
168+
`/search?jql=fixVersion=${versionId}`
169+
);
170+
171+
return issues.issues.map(issue => issue.key);
172+
}
173+
174+
async getIssuesWithDetailsForVersion(versionId: string): Promise<JiraIssue[]> {
175+
const response = await this.apiRequest<{ issues: JiraIssue[] }>(
176+
"GET",
177+
`/search?jql=fixVersion=${versionId}&fields=summary`
178+
);
179+
180+
return response.issues;
181+
}
182+
183+
async searchIssueByKey(issueKey: string): Promise<JiraIssue | null> {
184+
try {
185+
const issue = await this.apiRequest<JiraIssue>("GET", `/issue/${issueKey}?fields=summary`);
186+
return issue;
187+
} catch (_e) {
188+
// If issue not found or other error
189+
return null;
190+
}
191+
}
192+
}

0 commit comments

Comments
 (0)