Skip to content

Commit 182dc97

Browse files
authored
chore: add MPK re-packaging step into release (#2004)
2 parents dac41c5 + a3db73b commit 182dc97

File tree

9 files changed

+171
-56
lines changed

9 files changed

+171
-56
lines changed

.github/workflows/PublishMarketplace.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ jobs:
4747
fi
4848
- name: Install dependencies
4949
run: pnpm install
50+
- name: Include OSS README in MPK artifact
51+
run: pnpm run include-oss-in-artifact
52+
env:
53+
GH_PAT: ${{ secrets.GITHUB_TOKEN }}
5054
- name: Run publish script
5155
run: pnpm run publish-marketplace --filter=$PACKAGE
5256
env:
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env ts-node-script
2+
3+
import { gh } from "../src/github";
4+
import { includeReadmeOssIntoMpk } from "../src/oss-clearance";
5+
import { rm } from "../src/shell";
6+
import { mkdtemp } from "node:fs/promises";
7+
import { join } from "node:path";
8+
import { tmpdir } from "node:os";
9+
import chalk from "chalk";
10+
11+
/**
12+
* This script processes GitHub release artifacts to include READMEOSS HTML files into MPK files.
13+
*
14+
* Workflow:
15+
* 1. Check release assets for .mpk and .html files
16+
* 2. If only MPK exists - do nothing
17+
* 3. If both MPK and HTML exist:
18+
* - Download both files
19+
* - Merge HTML into MPK
20+
* - Replace the MPK asset in the release
21+
*/
22+
23+
async function main(): Promise<void> {
24+
const releaseTag = process.env.TAG;
25+
26+
if (!releaseTag) {
27+
throw new Error("TAG environment variable is required");
28+
}
29+
30+
await gh.ensureAuth();
31+
32+
console.log(chalk.bold.cyan(`\n🔍 Processing release: ${releaseTag}\n`));
33+
34+
const releaseId = await gh.getReleaseIdByReleaseTag(releaseTag);
35+
36+
if (!releaseId) {
37+
throw new Error(`Could not find release ID for tag '${releaseTag}'`);
38+
}
39+
40+
// Step 1: Get release artifacts
41+
console.log(chalk.blue("📦 Fetching release artifacts..."));
42+
const artifacts = await gh.listReleaseAssets(releaseId);
43+
44+
const mpkAsset = artifacts.find(asset => asset.name.endsWith(".mpk"));
45+
const htmlAsset = artifacts.find(asset => asset.name.endsWith(".html"));
46+
47+
if (!mpkAsset) {
48+
throw new Error(`No MPK file found in release '${releaseTag}'`);
49+
}
50+
51+
console.log(chalk.green(`✅ Found MPK: ${mpkAsset.name}`));
52+
53+
// Step 2: Check if HTML file exists
54+
if (!htmlAsset) {
55+
console.log(chalk.yellow("⚠️ No HTML file found in release - nothing to include"));
56+
console.log(chalk.gray(" Skipping MPK modification\n"));
57+
process.exit(0);
58+
}
59+
60+
console.log(chalk.green(`✅ Found HTML: ${htmlAsset.name}`));
61+
62+
// Step 3: Download both files to temp directory
63+
console.log(chalk.blue("\n📥 Downloading artifacts..."));
64+
65+
const tmpFolder = await mkdtemp(join(tmpdir(), "mpk-oss-include-"));
66+
const mpkPath = join(tmpFolder, mpkAsset.name);
67+
const htmlPath = join(tmpFolder, htmlAsset.name);
68+
69+
try {
70+
console.log(chalk.gray(` → Downloading ${mpkAsset.name}...`));
71+
await gh.downloadReleaseAsset(mpkAsset.id, mpkPath);
72+
73+
console.log(chalk.gray(` → Downloading ${htmlAsset.name}...`));
74+
await gh.downloadReleaseAsset(htmlAsset.id, htmlPath);
75+
76+
console.log(chalk.green("✅ Downloads completed"));
77+
78+
// Step 4: Include HTML into MPK
79+
console.log(chalk.blue("\n🔧 Merging HTML into MPK..."));
80+
await includeReadmeOssIntoMpk(htmlPath, mpkPath);
81+
console.log(chalk.green("✅ Merge completed"));
82+
83+
// Step 5: Remove old assets, upload patched MPK
84+
console.log(chalk.blue("\n🔄 Replacing MPK asset in release..."));
85+
86+
console.log(chalk.gray(` → Deleting old MPK asset...`));
87+
await gh.deleteReleaseAsset(mpkAsset.id);
88+
89+
console.log(chalk.gray(` → Deleting old HTML asset...`));
90+
await gh.deleteReleaseAsset(htmlAsset.id);
91+
92+
console.log(chalk.gray(` → Uploading modified MPK...`));
93+
const newAsset = await gh.uploadReleaseAsset(releaseId, mpkPath, mpkAsset.name);
94+
95+
console.log(chalk.green(`✅ Successfully replaced MPK asset (ID: ${newAsset.id})`));
96+
console.log(chalk.bold.green(`\n🎉 Process completed successfully!\n`));
97+
} finally {
98+
// Step 8: Cleanup temp files
99+
console.log(chalk.gray("🧹 Cleaning up temporary files..."));
100+
await rm("-rf", tmpFolder);
101+
}
102+
}
103+
104+
main().catch(error => {
105+
console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
106+
console.error(error);
107+
process.exit(1);
108+
});

automation/utils/bin/rui-oss-clearance.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import {
1212
createSBomGeneratorFolderStructure,
1313
findAllReadmeOssLocally,
1414
generateSBomArtifactsInFolder,
15-
getRecommendedReadmeOss,
16-
includeReadmeOssIntoMpk
15+
getRecommendedReadmeOss
1716
} from "../src/oss-clearance";
1817

1918
// ============================================================================
@@ -216,7 +215,6 @@ async function handleIncludeCommand(): Promise<void> {
216215
printHeader("OSS Clearance Readme Include");
217216

218217
try {
219-
// TODO: Implement include command logic
220218
// Step 1: Verify authentication
221219
await verifyGitHubAuth();
222220

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ async function main(): Promise<void> {
1919

2020
console.log(`Starting release process for tag ${chalk.green(tag)}`);
2121

22-
const artifactUrl = await gh.getMPKReleaseArtifactUrl(tag);
22+
const artifactUrl = await gh.getMPKReleaseAssetUrl(tag);
2323

2424
const draft = await createDraft({
2525
appName: marketplace.appName,

automation/utils/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"rui-create-gh-release": "bin/rui-create-gh-release.ts",
1010
"rui-create-translation": "bin/rui-create-translation.ts",
1111
"rui-generate-package-xml": "bin/rui-generate-package-xml.ts",
12+
"rui-include-oss-in-artifact": "bin/rui-include-oss-in-artifact.ts",
1213
"rui-prepare-release": "bin/rui-prepare-release.ts",
1314
"rui-publish-marketplace": "bin/rui-publish-marketplace.ts",
1415
"rui-update-changelog-module": "bin/rui-update-changelog-module.ts",
@@ -29,6 +30,7 @@
2930
"compile:parser:module": "peggy -o ./src/changelog-parser/parser/widget/widget.js ./src/changelog-parser/parser/widget/widget.pegjs",
3031
"compile:parser:widget": "peggy -o ./src/changelog-parser/parser/module/module.js ./src/changelog-parser/parser/module/module.pegjs",
3132
"format": "prettier --write .",
33+
"include-oss-in-artifact": "ts-node bin/rui-include-oss-in-artifact.ts",
3234
"lint": "eslint --ext .jsx,.js,.ts,.tsx src/",
3335
"oss-clearance": "ts-node bin/rui-oss-clearance.ts",
3436
"prepare": "pnpm run compile:parser:widget && pnpm run compile:parser:module && tsc",

automation/utils/src/github.ts

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -161,25 +161,12 @@ export class GitHub {
161161
}
162162
}
163163

164-
async getReleaseArtifacts(releaseTag: string): Promise<Array<{ name: string; browser_download_url: string }>> {
164+
async getMPKReleaseAssetUrl(releaseTag: string): Promise<string> {
165165
const releaseId = await this.getReleaseIdByReleaseTag(releaseTag);
166-
167166
if (!releaseId) {
168167
throw new Error(`Could not find release with tag '${releaseTag}' on GitHub`);
169168
}
170-
171-
return fetch<
172-
Array<{
173-
name: string;
174-
browser_download_url: string;
175-
}>
176-
>("GET", `https://api.github.com/repos/${this.owner}/${this.repo}/releases/${releaseId}/assets`, undefined, {
177-
...this.ghAPIHeaders
178-
});
179-
}
180-
181-
async getMPKReleaseArtifactUrl(releaseTag: string): Promise<string> {
182-
const artifacts = await this.getReleaseArtifacts(releaseTag);
169+
const artifacts = await this.listReleaseAssets(releaseId);
183170

184171
const downloadUrl = artifacts.find(asset => asset.name.endsWith(".mpk"))?.browser_download_url;
185172

@@ -204,39 +191,6 @@ export class GitHub {
204191
return releases.filter(release => release.draft);
205192
}
206193

207-
async downloadReleaseAsset(assetId: string, destinationPath: string): Promise<void> {
208-
await this.ensureAuth();
209-
210-
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/releases/assets/${assetId}`;
211-
212-
try {
213-
const response = await nodefetch(url, {
214-
method: "GET",
215-
headers: {
216-
Accept: "application/octet-stream",
217-
...this.ghAPIHeaders
218-
},
219-
redirect: "follow"
220-
});
221-
222-
if (!response.ok) {
223-
throw new Error(`Failed to download asset ${assetId}: ${response.status} ${response.statusText}`);
224-
}
225-
226-
if (!response.body) {
227-
throw new Error(`No response body received for asset ${assetId}`);
228-
}
229-
230-
// Stream the response body to the file
231-
const fileStream = createWriteStream(destinationPath);
232-
await pipeline(response.body, fileStream);
233-
} catch (error) {
234-
throw new Error(
235-
`Failed to download release asset ${assetId}: ${error instanceof Error ? error.message : String(error)}`
236-
);
237-
}
238-
}
239-
240194
async createReleaseNotesFile(releaseNotesText: string): Promise<string> {
241195
const filePath = await this.createTempFile();
242196
await writeFile(filePath, releaseNotesText);
@@ -284,6 +238,50 @@ export class GitHub {
284238
});
285239
}
286240

241+
async listReleaseAssets(releaseId: string): Promise<GitHubReleaseAsset[]> {
242+
return fetch<GitHubReleaseAsset[]>(
243+
"GET",
244+
`https://api.github.com/repos/${this.owner}/${this.repo}/releases/${releaseId}/assets`,
245+
undefined,
246+
{
247+
...this.ghAPIHeaders
248+
}
249+
);
250+
}
251+
252+
async downloadReleaseAsset(assetId: string, destinationPath: string): Promise<void> {
253+
await this.ensureAuth();
254+
255+
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/releases/assets/${assetId}`;
256+
257+
try {
258+
const response = await nodefetch(url, {
259+
method: "GET",
260+
headers: {
261+
Accept: "application/octet-stream",
262+
...this.ghAPIHeaders
263+
},
264+
redirect: "follow"
265+
});
266+
267+
if (!response.ok) {
268+
throw new Error(`Failed to download asset ${assetId}: ${response.status} ${response.statusText}`);
269+
}
270+
271+
if (!response.body) {
272+
throw new Error(`No response body received for asset ${assetId}`);
273+
}
274+
275+
// Stream the response body to the file
276+
const fileStream = createWriteStream(destinationPath);
277+
await pipeline(response.body, fileStream);
278+
} catch (error) {
279+
throw new Error(
280+
`Failed to download release asset ${assetId}: ${error instanceof Error ? error.message : String(error)}`
281+
);
282+
}
283+
}
284+
287285
/**
288286
* Delete a release asset by ID
289287
*/

automation/utils/src/oss-clearance.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,14 @@ export function findAllReadmeOssLocally(): string[] {
2727
return matchingFiles1.concat(matchingFiles2);
2828
}
2929

30-
export function getRecommendedReadmeOss(name: string, version: string, availableReadmes: string[]): string | undefined {
31-
const fileNames = availableReadmes.map(r => basename(r));
32-
33-
return fileNames.find(r => r.includes(name) && r.includes(version));
30+
export function getRecommendedReadmeOss(
31+
packageName: string,
32+
packageVersion: string,
33+
availableReadmes: string[]
34+
): string | undefined {
35+
const fileNames = availableReadmes.map(r => [basename(r), r]);
36+
37+
return fileNames.find(([name]) => name.includes(packageName) && name.includes(packageVersion))?.at(1);
3438
}
3539

3640
export async function createSBomGeneratorFolderStructure(

docs/oss-readme-inclusion.md

Whitespace-only changes.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"prepare": "husky install",
1818
"prepare-release": "pnpm --filter @mendix/automation-utils run prepare-release",
1919
"publish-marketplace": "turbo run publish-marketplace",
20+
"include-oss-in-artifact": "pnpm --filter @mendix/automation-utils run include-oss-in-artifact",
2021
"release": "turbo run release",
2122
"test": "turbo run test --continue --concurrency 1",
2223
"verify": "turbo run verify --continue --concurrency 1",

0 commit comments

Comments
 (0)